Grok Build 0.1
xAIAugust 2025
What it wrote
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AETHER • Incremental</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Space+Grotesk:wght@500;600&display=swap');
:root {
--bg: #0a0b14;
--bg-elev: #12131f;
--bg-glass: rgba(18, 19, 31, 0.85);
--accent: #7c3aed;
--accent-2: #22d3ee;
--accent-3: #a78bfa;
--text: #e2e8f0;
--text-muted: #94a3b8;
--border: rgba(148, 163, 184, 0.15);
--success: #34d399;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--text);
font-family: 'Inter', system_ui, sans-serif;
min-height: 100vh;
overflow-x: hidden;
}
.stars {
position: fixed;
inset: 0;
z-index: -1;
background:
radial-gradient(circle at 20% 30%, rgba(124, 58, 237, 0.08) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(34, 211, 238, 0.06) 0%, transparent 50%),
var(--bg);
}
.container {
max-width: 1180px;
margin: 0 auto;
padding: 24px;
}
header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 32px;
}
.logo {
display: flex;
align-items: center;
gap: 14px;
}
.logo-icon {
width: 42px;
height: 42px;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
box-shadow: 0 0 40px rgba(124, 58, 237, 0.4);
}
.logo-text {
font-family: 'Space Grotesk', sans-serif;
font-size: 28px;
font-weight: 600;
letter-spacing: -1px;
background: linear-gradient(90deg, #e2e8f0, #cbd5e1);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.header-stats {
display: flex;
gap: 32px;
font-size: 13px;
}
.header-stat {
text-align: right;
}
.header-stat-label {
color: var(--text-muted);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.header-stat-value {
font-weight: 600;
font-size: 15px;
font-variant-numeric: tabular-nums;
}
.main-grid {
display: grid;
grid-template-columns: 420px 1fr;
gap: 28px;
align-items: start;
}
@media (max-width: 900px) {
.main-grid {
grid-template-columns: 1fr;
}
}
.core-panel {
position: sticky;
top: 24px;
}
.resources {
text-align: center;
margin-bottom: 24px;
}
.aether-amount {
font-family: 'Space Grotesk', sans-serif;
font-size: 64px;
font-weight: 600;
line-height: 1;
letter-spacing: -3px;
background: linear-gradient(180deg, #f1f5f9 0%, #cbd5e1 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
font-variant-numeric: tabular-nums;
min-height: 68px;
}
.aether-label {
color: var(--accent-2);
font-size: 13px;
letter-spacing: 3px;
text-transform: uppercase;
font-weight: 500;
margin-top: 4px;
}
.aps {
margin-top: 8px;
font-size: 15px;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
}
.aps span {
color: var(--accent-2);
font-weight: 600;
}
.orb-wrapper {
position: relative;
width: 320px;
height: 320px;
margin: 0 auto 28px;
cursor: pointer;
user-select: none;
-webkit-user-select: none;
}
.orb {
position: absolute;
inset: 0;
border-radius: 9999px;
background:
radial-gradient(circle at 38% 32%, #f0abfc 0%, transparent 45%),
radial-gradient(circle at 62% 68%, #67e8f9 0%, transparent 50%),
linear-gradient(145deg, #7c3aed 0%, #312e81 45%, #1e1b4b 100%);
box-shadow:
0 0 0 12px rgba(124, 58, 237, 0.15),
0 0 0 28px rgba(124, 58, 237, 0.08),
0 25px 70px -15px rgba(0, 0, 0, 0.6),
inset 0 4px 20px rgba(255,255,255,0.35),
inset 40px 40px 60px rgba(255,255,255,0.12),
inset -30px -30px 50px rgba(0,0,0,0.5);
transition: transform 0.08s cubic-bezier(0.23, 1, 0.32, 1),
box-shadow 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.orb::before {
content: '';
position: absolute;
inset: 0;
border-radius: 9999px;
background: linear-gradient(
120deg,
transparent 30%,
rgba(255,255,255,0.45) 48%,
transparent 65%
);
background-size: 200% 200%;
animation: orb-shimmer 4s linear infinite;
pointer-events: none;
}
.orb::after {
content: '';
position: absolute;
inset: 18px;
border-radius: 9999px;
background: radial-gradient(circle at 35% 30%, rgba(255,255,255,0.5), transparent 70%);
pointer-events: none;
}
.orb:active {
transform: scale(0.94);
box-shadow:
0 0 0 8px rgba(124, 58, 237, 0.2),
0 0 0 18px rgba(124, 58, 237, 0.1),
0 15px 40px -10px rgba(0, 0, 0, 0.5),
inset 0 2px 12px rgba(255,255,255,0.3),
inset -20px -20px 35px rgba(0,0,0,0.55);
}
.orb-label {
position: relative;
z-index: 2;
font-family: 'Space Grotesk', sans-serif;
font-size: 17px;
font-weight: 600;
letter-spacing: 4px;
color: rgba(255,255,255,0.95);
text-shadow: 0 2px 12px rgba(0,0,0,0.4);
}
.orb-glow {
position: absolute;
inset: -60px;
border-radius: 9999px;
background: radial-gradient(circle, rgba(124, 58, 237, 0.25) 0%, transparent 70%);
pointer-events: none;
transition: opacity 0.3s ease;
z-index: -1;
}
.orb-wrapper:hover .orb-glow {
opacity: 1;
}
@keyframes orb-shimmer {
0% { background-position: 200% 50%; }
100% { background-position: -200% 50%; }
}
.canvas-particles {
position: absolute;
inset: -40px;
border-radius: 50%;
pointer-events: none;
z-index: 3;
}
.floating-text {
position: absolute;
color: #c0a7ff;
font-weight: 600;
font-size: 15px;
pointer-events: none;
text-shadow: 0 0 8px rgba(124, 58, 237, 0.6);
z-index: 10;
animation: float-up 0.9s ease-out forwards;
font-variant-numeric: tabular-nums;
}
@keyframes float-up {
0% {
transform: translateY(0) scale(1);
opacity: 1;
}
100% {
transform: translateY(-70px) scale(0.85);
opacity: 0;
}
}
.panel {
background: var(--bg-glass);
border: 1px solid var(--border);
border-radius: 20px;
padding: 24px;
backdrop-filter: blur(20px);
}
.panel-title {
font-size: 11px;
letter-spacing: 2px;
text-transform: uppercase;
color: var(--text-muted);
font-weight: 600;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.panel-title::after {
content: '';
flex: 1;
height: 1px;
background: linear-gradient(to right, var(--border), transparent);
}
.generators {
display: flex;
flex-direction: column;
gap: 10px;
}
.generator {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 16px;
padding: 14px 18px;
background: rgba(255,255,255,0.025);
border: 1px solid var(--border);
border-radius: 14px;
transition: all 0.15s ease;
cursor: pointer;
position: relative;
overflow: hidden;
}
.generator:hover {
background: rgba(124, 58, 237, 0.08);
border-color: rgba(124, 58, 237, 0.3);
transform: translateY(-1px);
}
.generator.cant-afford {
opacity: 0.55;
cursor: not-allowed;
}
.generator.cant-afford:hover {
transform: none;
background: rgba(255,255,255,0.025);
border-color: var(--border);
}
.gen-icon {
width: 42px;
height: 42px;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
background: linear-gradient(135deg, rgba(124,58,237,0.2), rgba(34,211,238,0.1));
border-radius: 11px;
flex-shrink: 0;
}
.gen-info {
min-width: 0;
}
.gen-name {
font-weight: 600;
font-size: 15px;
margin-bottom: 2px;
}
.gen-desc {
font-size: 12px;
color: var(--text-muted);
}
.gen-stats {
text-align: right;
font-variant-numeric: tabular-nums;
}
.gen-owned {
font-size: 18px;
font-weight: 600;
line-height: 1;
color: var(--accent-2);
}
.gen-cost {
font-size: 13px;
color: #a5b4fc;
margin-top: 2px;
}
.gen-rate {
font-size: 11px;
color: var(--text-muted);
margin-top: 1px;
}
.buy-controls {
display: flex;
gap: 6px;
margin-top: 16px;
flex-wrap: wrap;
}
.buy-btn {
flex: 1;
min-width: 58px;
padding: 8px 10px;
background: rgba(255,255,255,0.06);
border: 1px solid var(--border);
color: var(--text);
border-radius: 9px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.1s ease;
}
.buy-btn:hover {
background: rgba(124, 58, 237, 0.15);
border-color: var(--accent);
}
.buy-btn:active {
transform: scale(0.96);
}
.buy-btn.active {
background: var(--accent);
border-color: var(--accent);
color: white;
}
.upgrades-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
gap: 10px;
}
.upgrade {
padding: 14px 16px;
background: rgba(255,255,255,0.025);
border: 1px solid var(--border);
border-radius: 12px;
font-size: 13px;
transition: all 0.15s ease;
cursor: pointer;
display: flex;
flex-direction: column;
gap: 6px;
}
.upgrade:hover:not(.owned) {
border-color: rgba(124, 58, 237, 0.4);
background: rgba(124, 58, 237, 0.06);
}
.upgrade.owned {
border-color: rgba(52, 211, 153, 0.3);
background: rgba(52, 211, 153, 0.06);
cursor: default;
opacity: 0.75;
}
.upgrade-name {
font-weight: 600;
font-size: 13.5px;
}
.upgrade-effect {
font-size: 11.5px;
color: var(--text-muted);
line-height: 1.3;
}
.upgrade-cost {
font-size: 12px;
color: #a5b4fc;
font-weight: 500;
margin-top: 4px;
}
.prestige-section {
margin-top: 28px;
}
.prestige-card {
background: linear-gradient(145deg, rgba(124, 58, 237, 0.12), rgba(34, 211, 238, 0.06));
border: 1px solid rgba(124, 58, 237, 0.25);
border-radius: 18px;
padding: 22px;
position: relative;
overflow: hidden;
}
.prestige-card::before {
content: '';
position: absolute;
top: -50%;
right: -20%;
width: 200px;
height: 200px;
background: radial-gradient(circle, rgba(124,58,237,0.3) 0%, transparent 70%);
pointer-events: none;
}
.prestige-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.prestige-title {
font-weight: 600;
font-size: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.prestige-multi {
font-family: 'Space Grotesk', sans-serif;
font-size: 22px;
font-weight: 600;
color: #c0a7ff;
}
.prestige-info {
font-size: 13px;
color: var(--text-muted);
margin-bottom: 16px;
line-height: 1.4;
}
.prestige-btn {
width: 100%;
padding: 14px;
background: linear-gradient(135deg, var(--accent), #5b21b6);
color: white;
border: none;
border-radius: 12px;
font-size: 14px;
font-weight: 700;
letter-spacing: 0.3px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
box-shadow: 0 10px 30px -10px rgba(124, 58, 237, 0.5);
}
.prestige-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 20px 40px -12px rgba(124, 58, 237, 0.6);
}
.prestige-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
background: #312e81;
}
.prestige-gain {
text-align: center;
font-size: 12px;
margin-top: 10px;
color: #c0a7ff;
}
.stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-top: 22px;
font-size: 12px;
}
.stat {
background: rgba(255,255,255,0.025);
border: 1px solid var(--border);
border-radius: 10px;
padding: 10px 12px;
}
.stat-label {
color: var(--text-muted);
font-size: 10.5px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.stat-value {
font-weight: 600;
font-variant-numeric: tabular-nums;
margin-top: 1px;
}
.achievements {
margin-top: 28px;
}
.achievements-grid {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.achievement {
font-size: 12px;
padding: 6px 12px;
background: rgba(52, 211, 153, 0.1);
border: 1px solid rgba(52, 211, 153, 0.25);
border-radius: 9999px;
color: #34d399;
display: flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.toast-container {
position: fixed;
bottom: 24px;
right: 24px;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 10px;
pointer-events: none;
}
.toast {
background: var(--bg-elev);
border: 1px solid var(--border);
border-left: 4px solid var(--success);
padding: 12px 18px;
border-radius: 10px;
box-shadow: 0 10px 30px -10px rgba(0,0,0,0.5);
max-width: 280px;
pointer-events: all;
animation: toast-in 0.2s ease forwards;
}
.toast-title {
font-weight: 600;
font-size: 13px;
color: #34d399;
}
.toast-desc {
font-size: 12.5px;
color: var(--text-muted);
margin-top: 2px;
}
@keyframes toast-in {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.offline-banner {
position: fixed;
top: 0;
left: 0;
right: 0;
background: linear-gradient(to right, #312e81, #1e1b4b);
color: #c0a7ff;
text-align: center;
padding: 9px 20px;
font-size: 13px;
z-index: 100;
display: none;
border-bottom: 1px solid rgba(124, 58, 237, 0.3);
}
.offline-banner.show {
display: block;
}
.section-divider {
height: 1px;
background: linear-gradient(to right, transparent, var(--border), transparent);
margin: 28px 0;
}
.empty-state {
font-size: 12px;
color: var(--text-muted);
text-align: center;
padding: 18px;
background: rgba(255,255,255,0.015);
border-radius: 12px;
border: 1px dashed var(--border);
}
</style>
</head>
<body>
<div class="stars" id="stars"></div>
<div class="offline-banner" id="offline-banner"></div>
<div class="container">
<header>
<div class="logo">
<div class="logo-icon">✧</div>
<div>
<div class="logo-text">AETHER</div>
</div>
</div>
<div class="header-stats">
<div class="header-stat">
<div class="header-stat-label">Total Harvested</div>
<div class="header-stat-value" id="total-harvested">0</div>
</div>
<div class="header-stat">
<div class="header-stat-label">Transcendences</div>
<div class="header-stat-value" id="transcendences">0</div>
</div>
</div>
</header>
<div class="main-grid">
<!-- CORE -->
<div class="core-panel">
<div class="resources">
<div class="aether-amount" id="aether">0</div>
<div class="aether-label">Aether</div>
<div class="aps">
<span id="aps">0</span> per second
</div>
</div>
<div class="orb-wrapper" id="orb-wrapper">
<canvas class="canvas-particles" id="particle-canvas" width="400" height="400"></canvas>
<div class="orb" id="orb">
<div class="orb-label">THE CORE</div>
</div>
<div class="orb-glow" style="opacity: 0.6;"></div>
</div>
<div class="prestige-card">
<div class="prestige-header">
<div class="prestige-title">
<span>✧</span>
<span>Transcendence</span>
</div>
<div class="prestige-multi" id="prestige-multi">1.00×</div>
</div>
<div class="prestige-info">
Transcend to reset your progress and gain permanent <strong>Astral Power</strong>.
The more Aether you've ever collected, the stronger your next life becomes.
</div>
<button class="prestige-btn" id="prestige-btn" disabled>
Transcend
</button>
<div class="prestige-gain" id="prestige-gain">Need 1.00M total Aether to transcend</div>
</div>
<div class="stats">
<div class="stat">
<div class="stat-label">Total Clicks</div>
<div class="stat-value" id="total-clicks">0</div>
</div>
<div class="stat">
<div class="stat-label">Peak APS</div>
<div class="stat-value" id="peak-aps">0</div>
</div>
<div class="stat">
<div class="stat-label">Astral Power</div>
<div class="stat-value" id="astral-power">+0%</div>
</div>
<div class="stat">
<div class="stat-label">Time Played</div>
<div class="stat-value" id="time-played">0m</div>
</div>
</div>
</div>
<!-- RIGHT COLUMN -->
<div>
<!-- GENERATORS -->
<div class="panel">
<div class="panel-title">Generators</div>
<div class="buy-controls">
<button class="buy-btn active" data-multi="1">1</button>
<button class="buy-btn" data-multi="10">10</button>
<button class="buy-btn" data-multi="100">100</button>
<button class="buy-btn" data-multi="max">MAX</button>
</div>
<div class="generators" id="generators"></div>
</div>
<div class="section-divider"></div>
<!-- UPGRADES -->
<div class="panel">
<div class="panel-title">Upgrades</div>
<div class="upgrades-grid" id="upgrades"></div>
</div>
<div class="achievements">
<div class="panel-title">Achievements</div>
<div class="achievements-grid" id="achievements"></div>
</div>
</div>
</div>
</div>
<div class="toast-container" id="toast-container"></div>
<script>
// ==================== GAME STATE ====================
const state = {
aether: 0,
totalAetherEarned: 0,
totalClicks: 0,
peakAps: 0,
startTime: Date.now(),
lastSave: Date.now(),
lastTick: Date.now(),
// Prestige
transcendences: 0,
astralShards: 0,
// Generators: id -> { owned, ... }
generators: {},
// Upgrades purchased (set of ids)
upgrades: new Set(),
// Unlocked achievements
achievements: new Set(),
};
// ==================== GENERATOR DEFINITIONS ====================
const generators = [
{
id: 'shard',
name: 'Aether Shard',
icon: '◆',
baseCost: 12,
baseProduction: 0.1,
desc: 'Tiny fragments humming with potential',
},
{
id: 'condenser',
name: 'Flux Condenser',
icon: '◈',
baseCost: 110,
baseProduction: 1.1,
desc: 'Compresses raw void into usable essence',
},
{
id: 'voidtap',
name: 'Void Tap',
icon: '◉',
baseCost: 1400,
baseProduction: 13,
desc: 'Draws directly from the space between stars',
},
{
id: 'crucible',
name: 'Star Crucible',
icon: '✧',
baseCost: 18500,
baseProduction: 92,
desc: 'Forges newborn stars into pure Aether',
},
{
id: 'nebula',
name: 'Nebula Loom',
icon: '❋',
baseCost: 260000,
baseProduction: 780,
desc: 'Weaves entire nebulae into threads of power',
},
{
id: 'singularity',
name: 'Singularity Anchor',
icon: '◌',
baseCost: 4200000,
baseProduction: 9200,
desc: 'Binds the impossible. Reality bends to your will',
},
];
// ==================== UPGRADE DEFINITIONS ====================
const upgrades = [
{ id: 'tap1', name: 'Keen Resonance', cost: 85, effect: '+1 click power', type: 'click', value: 1 },
{ id: 'tap2', name: 'Amplified Tap', cost: 620, effect: 'Click power ×2', type: 'clickMulti', value: 2 },
{ id: 'tap3', name: 'Essence Overdrive', cost: 12500, effect: 'Click power ×3', type: 'clickMulti', value: 3 },
{ id: 'tap4', name: 'Quantum Touch', cost: 185000, effect: 'Click power ×4', type: 'clickMulti', value: 4 },
{ id: 'prod1', name: 'Harmonic Tuning', cost: 340, effect: 'All production +25%', type: 'global', value: 1.25 },
{ id: 'prod2', name: 'Astral Efficiency', cost: 5200, effect: 'All production +40%', type: 'global', value: 1.40 },
{ id: 'prod3', name: 'Celestial Cascade', cost: 78000, effect: 'All production +60%', type: 'global', value: 1.60 },
{ id: 'prod4', name: 'Infinite Bloom', cost: 1450000, effect: 'All production +100%', type: 'global', value: 2.0 },
{ id: 'shard1', name: 'Purer Shards', cost: 280, effect: 'Shards ×2.2', type: 'gen', target: 'shard', value: 2.2 },
{ id: 'cond1', name: 'Resonant Flux', cost: 1650, effect: 'Condensers ×2.4', type: 'gen', target: 'condenser', value: 2.4 },
{ id: 'void1', name: 'Deeper Wells', cost: 18500, effect: 'Void Taps ×2.5', type: 'gen', target: 'voidtap', value: 2.5 },
{ id: 'cruc1', name: 'Stellar Fury', cost: 260000, effect: 'Star Crucibles ×2.6', type: 'gen', target: 'crucible', value: 2.6 },
{ id: 'neb1', name: 'Loom Mastery', cost: 3100000, effect: 'Nebula Looms ×3.0', type: 'gen', target: 'nebula', value: 3.0 },
{ id: 'sing1', name: 'Event Horizon', cost: 72000000, effect: 'Singularity Anchors ×3.5', type: 'gen', target: 'singularity', value: 3.5 },
{ id: 'final1', name: 'Breath of the Infinite', cost: 880000, effect: 'All production +80%', type: 'global', value: 1.8 },
{ id: 'final2', name: 'One With The Void', cost: 12500000, effect: 'All production +120%', type: 'global', value: 2.2 },
];
// ==================== ACHIEVEMENTS ====================
const achievementsList = [
{ id: 'first', name: 'First Touch', desc: 'Click the Core once', check: () => state.totalClicks >= 1 },
{ id: 'hundred', name: 'Hundredfold', desc: 'Click the Core 100 times', check: () => state.totalClicks >= 100 },
{ id: 'thousand', name: 'Thousand Echoes', desc: 'Click the Core 1,000 times', check: () => state.totalClicks >= 1000 },
{ id: 'a1k', name: 'A Thousand Sparks', desc: 'Earn 1,000 total Aether', check: () => state.totalAetherEarned >= 1000 },
{ id: 'a1m', name: 'A Million Breaths', desc: 'Earn 1 million total Aether', check: () => state.totalAetherEarned >= 1e6 },
{ id: 'a1b', name: 'Cosmic Scale', desc: 'Earn 1 billion total Aether', check: () => state.totalAetherEarned >= 1e9 },
{ id: 'shard10', name: 'Shard Collector', desc: 'Own 10 Aether Shards', check: () => (state.generators.shard?.owned || 0) >= 10 },
{ id: 'sing1', name: 'Reality Anchor', desc: 'Build your first Singularity Anchor', check: () => (state.generators.singularity?.owned || 0) >= 1 },
{ id: 'trans1', name: 'First Transcendence', desc: 'Transcend once', check: () => state.transcendences >= 1 },
{ id: 'trans5', name: 'Eternal Walker', desc: 'Transcend 5 times', check: () => state.transcendences >= 5 },
];
// ==================== UTILITIES ====================
function formatNumber(n) {
if (n < 1000) return n.toFixed(n < 10 ? 2 : 1).replace(/\.?0+$/, '');
const suffixes = ['', 'K', 'M', 'B', 'T', 'Qa', 'Qi', 'Sx', 'Sp', 'Oc', 'No', 'Dc'];
let tier = 0;
let value = n;
while (value >= 1000 && tier < suffixes.length - 1) {
value /= 1000;
tier++;
}
if (tier === 0) return Math.floor(n).toString();
const fixed = value < 10 ? 2 : value < 100 ? 1 : 0;
return value.toFixed(fixed).replace(/\.?0+$/, '') + suffixes[tier];
}
function getClickPower() {
let power = 1;
// Flat bonuses
for (const upg of upgrades) {
if (state.upgrades.has(upg.id) && upg.type === 'click') {
power += upg.value;
}
}
// Multipliers
for (const upg of upgrades) {
if (state.upgrades.has(upg.id) && upg.type === 'clickMulti') {
power *= upg.value;
}
}
return Math.floor(power);
}
function getGlobalMultiplier() {
let multi = 1;
// From upgrades
for (const upg of upgrades) {
if (state.upgrades.has(upg.id) && upg.type === 'global') {
multi *= upg.value;
}
}
// From astral shards (prestige)
multi *= (1 + state.astralShards * 0.08); // 8% per shard
return multi;
}
function getGeneratorProduction(genId) {
const genDef = generators.find(g => g.id === genId);
if (!genDef) return 0;
const owned = state.generators[genId]?.owned || 0;
if (owned === 0) return 0;
let prod = genDef.baseProduction * owned;
// Generator-specific upgrades
for (const upg of upgrades) {
if (state.upgrades.has(upg.id) && upg.type === 'gen' && upg.target === genId) {
prod *= upg.value;
}
}
prod *= getGlobalMultiplier();
return prod;
}
function getTotalAps() {
let total = 0;
for (const g of generators) {
total += getGeneratorProduction(g.id);
}
return total;
}
function getGeneratorCost(genId, count = 1) {
const genDef = generators.find(g => g.id === genId);
const currentOwned = state.generators[genId]?.owned || 0;
let totalCost = 0;
let nextCost = genDef.baseCost * Math.pow(1.14, currentOwned);
for (let i = 0; i < count; i++) {
totalCost += nextCost;
nextCost *= 1.14;
}
return totalCost;
}
function getMaxAffordable(genId) {
const genDef = generators.find(g => g.id === genId);
let owned = state.generators[genId]?.owned || 0;
let money = state.aether;
let cost = genDef.baseCost * Math.pow(1.14, owned);
let canBuy = 0;
while (money >= cost && canBuy < 1000) {
money -= cost;
cost *= 1.14;
canBuy++;
}
return canBuy;
}
function calculateAstralShards(totalEarned) {
if (totalEarned < 1e6) return 0;
// Logarithmic but satisfying: ~ every order of magnitude after 1M
return Math.floor(Math.pow(totalEarned / 1e6, 0.42));
}
function getCurrentPrestigeBonus() {
return 1 + state.astralShards * 0.08;
}
// ==================== PARTICLES & VISUALS ====================
let particleCanvas, particleCtx;
let particles = [];
function initParticles() {
particleCanvas = document.getElementById('particle-canvas');
particleCtx = particleCanvas.getContext('2d', { alpha: true });
// Make canvas cover the orb nicely
particleCanvas.width = 400;
particleCanvas.height = 400;
// Click particles
function createClickParticles(x, y, amount) {
const count = Math.min(28, 8 + Math.floor(Math.log10(amount + 1) * 3));
for (let i = 0; i < count; i++) {
const angle = (Math.random() * Math.PI * 2) - Math.PI / 2 - 0.2;
const speed = 1.8 + Math.random() * 3.4;
particles.push({
x: x + (Math.random() - 0.5) * 28,
y: y + (Math.random() - 0.5) * 18,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed - 1.6,
life: 38 + Math.random() * 24,
size: 2.5 + Math.random() * 3.2,
hue: 260 + Math.random() * 60,
});
}
// Bonus big central burst
for (let i = 0; i < 5; i++) {
particles.push({
x: x + (Math.random() - 0.5) * 14,
y: y + (Math.random() - 0.5) * 8,
vx: (Math.random() - 0.5) * 1.6,
vy: -2.8 - Math.random() * 2.5,
life: 52,
size: 4.5 + Math.random() * 1.8,
hue: 265,
});
}
}
// Expose for click handler
window.createClickParticles = createClickParticles;
// Animation loop
function animateParticles() {
particleCtx.clearRect(0, 0, particleCanvas.width, particleCanvas.height);
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.vy += 0.055; // gentle gravity
p.life -= 1;
p.vx *= 0.985;
if (p.life <= 0) {
particles.splice(i, 1);
continue;
}
const alpha = Math.max(0.05, p.life / 62);
particleCtx.fillStyle = `hsla(${p.hue}, 92%, 86%, ${alpha})`;
particleCtx.beginPath();
particleCtx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
particleCtx.fill();
// soft glow
if (p.size > 3) {
particleCtx.fillStyle = `hsla(${p.hue}, 92%, 92%, ${alpha * 0.35})`;
particleCtx.beginPath();
particleCtx.arc(p.x, p.y, p.size * 1.9, 0, Math.PI * 2);
particleCtx.fill();
}
}
requestAnimationFrame(animateParticles);
}
animateParticles();
}
function spawnFloatingText(text, x, y) {
const container = document.getElementById('orb-wrapper');
const el = document.createElement('div');
el.className = 'floating-text';
el.textContent = text;
// Position relative to orb wrapper
const rect = container.getBoundingClientRect();
el.style.left = `${x - rect.left + (Math.random() - 0.5) * 18}px`;
el.style.top = `${y - rect.top}px`;
container.appendChild(el);
setTimeout(() => {
el.remove();
}, 950);
}
// Background starfield
function initStarfield() {
const starsEl = document.getElementById('stars');
const canvas = document.createElement('canvas');
canvas.style.position = 'absolute';
canvas.style.inset = '0';
canvas.style.width = '100%';
canvas.style.height = '100%';
starsEl.appendChild(canvas);
const ctx = canvas.getContext('2d');
let w, h, stars = [];
function resize() {
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;
stars = [];
const count = Math.floor((w * h) / 4200);
for (let i = 0; i < count; i++) {
stars.push({
x: Math.random() * w,
y: Math.random() * h,
size: Math.random() * 1.6 + 0.3,
speed: Math.random() * 0.018 + 0.004,
alpha: Math.random() * 0.6 + 0.35,
});
}
}
window.addEventListener('resize', resize);
resize();
function draw() {
ctx.clearRect(0, 0, w, h);
for (const s of stars) {
s.y += s.speed;
if (s.y > h) {
s.y = 0;
s.x = Math.random() * w;
}
ctx.fillStyle = `rgba(226, 232, 240, ${s.alpha})`;
ctx.fillRect(s.x, s.y, s.size, s.size);
// occasional brighter stars
if (s.size > 1.3) {
ctx.fillStyle = `rgba(192, 167, 255, ${s.alpha * 0.4})`;
ctx.fillRect(s.x - 0.5, s.y - 0.5, s.size + 1, s.size + 1);
}
}
requestAnimationFrame(draw);
}
draw();
}
// ==================== RENDERING ====================
let currentBuyMulti = 1;
function renderAether() {
document.getElementById('aether').textContent = formatNumber(state.aether);
document.getElementById('total-harvested').textContent = formatNumber(state.totalAetherEarned);
}
function renderAps() {
const aps = getTotalAps();
document.getElementById('aps').textContent = formatNumber(aps);
if (aps > state.peakAps) {
state.peakAps = aps;
document.getElementById('peak-aps').textContent = formatNumber(aps);
}
// Dynamic orb energy based on production rate (feels alive)
const glow = document.querySelector('.orb-glow');
if (glow) {
const intensity = Math.min(1.6, Math.log10(aps + 1) * 0.32 + 0.35);
glow.style.opacity = intensity.toFixed(2);
glow.style.transform = `scale(${1 + Math.min(0.35, Math.log10(aps + 2) * 0.06)})`;
}
}
function renderStats() {
document.getElementById('total-clicks').textContent = state.totalClicks.toLocaleString();
document.getElementById('peak-aps').textContent = formatNumber(state.peakAps);
document.getElementById('transcendences').textContent = state.transcendences;
const multi = getCurrentPrestigeBonus();
document.getElementById('prestige-multi').textContent = multi.toFixed(2) + '×';
document.getElementById('astral-power').textContent = '+' + Math.round((multi - 1) * 100) + '%';
const minutes = Math.floor((Date.now() - state.startTime) / 60000);
document.getElementById('time-played').textContent = minutes + 'm';
}
function renderGenerators() {
const container = document.getElementById('generators');
container.innerHTML = '';
for (const def of generators) {
const owned = state.generators[def.id]?.owned || 0;
const production = getGeneratorProduction(def.id);
const cost1 = getGeneratorCost(def.id, 1);
let costText = formatNumber(cost1);
let buyCount = currentBuyMulti;
if (currentBuyMulti === 'max') {
buyCount = getMaxAffordable(def.id);
if (buyCount > 0) {
costText = formatNumber(getGeneratorCost(def.id, buyCount));
}
} else {
costText = formatNumber(getGeneratorCost(def.id, currentBuyMulti));
}
const canAfford = state.aether >= (currentBuyMulti === 'max' ? getGeneratorCost(def.id, buyCount || 1) : getGeneratorCost(def.id, currentBuyMulti));
const el = document.createElement('div');
el.className = `generator ${canAfford ? '' : 'cant-afford'}`;
el.innerHTML = `
<div class="gen-icon">${def.icon}</div>
<div class="gen-info">
<div class="gen-name">${def.name}</div>
<div class="gen-desc">${def.desc}</div>
<div class="gen-rate">+${formatNumber(production)}/s</div>
</div>
<div class="gen-stats">
<div class="gen-owned">${owned}</div>
<div class="gen-cost">${costText}</div>
</div>
`;
el.addEventListener('click', () => {
buyGenerator(def.id, currentBuyMulti);
});
container.appendChild(el);
}
}
function renderUpgrades() {
const container = document.getElementById('upgrades');
container.innerHTML = '';
let hasAny = false;
for (const upg of upgrades) {
if (state.upgrades.has(upg.id)) continue;
// Only show upgrades the player can reasonably reach
const canSee = state.totalAetherEarned >= upg.cost * 0.35;
if (!canSee) continue;
hasAny = true;
const canAfford = state.aether >= upg.cost;
const el = document.createElement('div');
el.className = `upgrade ${canAfford ? '' : 'cant-afford'}`;
el.innerHTML = `
<div class="upgrade-name">${upg.name}</div>
<div class="upgrade-effect">${upg.effect}</div>
<div class="upgrade-cost">${formatNumber(upg.cost)}</div>
`;
if (canAfford) {
el.addEventListener('click', () => buyUpgrade(upg.id));
}
container.appendChild(el);
}
if (!hasAny) {
const empty = document.createElement('div');
empty.className = 'empty-state';
empty.textContent = 'New upgrades will appear as you grow stronger.';
container.appendChild(empty);
}
}
function renderAchievements() {
const container = document.getElementById('achievements');
container.innerHTML = '';
for (const ach of achievementsList) {
if (!state.achievements.has(ach.id)) continue;
const el = document.createElement('div');
el.className = 'achievement';
el.innerHTML = `
<span>★</span>
<span>${ach.name}</span>
`;
el.title = ach.desc;
container.appendChild(el);
}
}
function renderPrestigeButton() {
const btn = document.getElementById('prestige-btn');
const gainEl = document.getElementById('prestige-gain');
const shardsToGain = calculateAstralShards(state.totalAetherEarned);
const nextShards = shardsToGain - state.astralShards;
if (nextShards > 0) {
btn.disabled = false;
gainEl.textContent = `Transcend to gain +${nextShards} Astral Shard${nextShards > 1 ? 's' : ''}`;
gainEl.style.color = '#c0a7ff';
} else {
btn.disabled = true;
const needed = Math.ceil(Math.pow((state.astralShards + 1) / 0.42, 1 / 0.42) * 1e6);
gainEl.textContent = `Need ${formatNumber(needed)} total Aether to gain another shard`;
gainEl.style.color = 'var(--text-muted)';
}
}
function renderAll() {
renderAether();
renderAps();
renderStats();
renderGenerators();
renderUpgrades();
renderAchievements();
renderPrestigeButton();
}
// ==================== GAME ACTIONS ====================
function addAether(amount, fromClick = false) {
state.aether += amount;
state.totalAetherEarned += amount;
// Floating text on the orb
if (fromClick && amount > 0) {
const wrapper = document.getElementById('orb-wrapper');
const rect = wrapper.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
spawnFloatingText('+' + formatNumber(amount), centerX, centerY);
// Trigger particles
if (window.createClickParticles) {
window.createClickParticles(200, 200, amount);
}
}
checkAchievements();
renderAll();
}
function buyGenerator(genId, multi) {
const def = generators.find(g => g.id === genId);
if (!def) return;
const currentOwned = state.generators[genId]?.owned || 0;
let buyAmount = multi;
if (multi === 'max') {
buyAmount = getMaxAffordable(genId);
}
if (buyAmount < 1) return;
const cost = getGeneratorCost(genId, buyAmount);
if (state.aether < cost) return;
state.aether -= cost;
if (!state.generators[genId]) {
state.generators[genId] = { owned: 0 };
}
state.generators[genId].owned += buyAmount;
// Small satisfying particle pop on generator
const genEls = document.querySelectorAll('#generators .generator');
// We could do more advanced targeting but this is fine for now
checkAchievements();
renderAll();
}
function buyUpgrade(upgId) {
const upg = upgrades.find(u => u.id === upgId);
if (!upg || state.upgrades.has(upgId)) return;
if (state.aether < upg.cost) return;
state.aether -= upg.cost;
state.upgrades.add(upgId);
// Nice visual feedback
const toast = createToast('Upgrade Acquired', upg.name + ' — ' + upg.effect);
checkAchievements();
renderAll();
}
function transcend() {
const shardsToGain = calculateAstralShards(state.totalAetherEarned);
if (shardsToGain <= state.astralShards) return;
const gained = shardsToGain - state.astralShards;
// Perform the prestige
state.astralShards = shardsToGain;
state.transcendences += 1;
// Reset most progress
state.aether = 0;
state.generators = {};
for (const g of generators) {
state.generators[g.id] = { owned: 0 };
}
state.upgrades = new Set();
// Keep totalAetherEarned and totalClicks for achievements and history
// (this feels good — your legacy remains visible)
createToast('Transcended', `You gained ${gained} Astral Shard${gained > 1 ? 's' : ''}. Reality is yours to reshape.`, true);
renderAll();
saveGame();
}
// ==================== ACHIEVEMENTS ====================
function checkAchievements() {
let unlocked = false;
for (const ach of achievementsList) {
if (state.achievements.has(ach.id)) continue;
if (ach.check()) {
state.achievements.add(ach.id);
unlocked = true;
createToast('Achievement Unlocked', ach.name + ' — ' + ach.desc);
}
}
if (unlocked) {
renderAchievements();
}
}
// ==================== TOASTS ====================
function createToast(title, desc, isBig = false) {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = 'toast';
if (isBig) toast.style.borderLeftColor = '#c0a7ff';
toast.innerHTML = `
<div class="toast-title">${title}</div>
<div class="toast-desc">${desc}</div>
`;
container.appendChild(toast);
setTimeout(() => {
toast.style.transition = 'all 0.25s ease';
toast.style.opacity = '0';
toast.style.transform = 'translateY(8px)';
setTimeout(() => toast.remove(), 200);
}, isBig ? 5200 : 3400);
return toast;
}
// ==================== GAME LOOP ====================
let gameLoopInterval = null;
function gameTick() {
const now = Date.now();
const delta = (now - state.lastTick) / 1000;
state.lastTick = now;
// Production
const production = getTotalAps() * delta;
if (production > 0) {
state.aether += production;
state.totalAetherEarned += production;
}
// Update UI at a reasonable rate (not every single frame)
if (Math.random() < 0.4) {
renderAether();
renderAps();
renderGenerators();
renderUpgrades();
renderPrestigeButton();
}
// Occasional full render + save
if (Math.random() < 0.035) {
renderStats();
saveGame();
}
// Auto-check achievements
checkAchievements();
}
function startGameLoop() {
if (gameLoopInterval) clearInterval(gameLoopInterval);
gameLoopInterval = setInterval(gameTick, 110);
}
// ==================== CLICK HANDLING ====================
let audioCtx;
function playClickSound(power) {
try {
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
const filter = audioCtx.createBiquadFilter();
osc.type = 'sine';
osc.frequency.value = 680 + Math.min(420, power * 1.6);
filter.type = 'lowpass';
filter.frequency.value = 1400;
gain.gain.value = Math.min(0.22, 0.07 + power * 0.0015);
const decay = audioCtx.createGain();
decay.gain.value = 1;
const now = audioCtx.currentTime;
decay.gain.setValueAtTime(1, now);
decay.gain.linearRampToValueAtTime(0.0001, now + 0.38);
osc.connect(filter);
filter.connect(gain);
gain.connect(decay);
decay.connect(audioCtx.destination);
osc.start(now);
osc.stop(now + 0.45);
} catch (_) {}
}
function setupClicking() {
const orb = document.getElementById('orb');
const wrapper = document.getElementById('orb-wrapper');
function handleClick(e) {
const power = getClickPower();
addAether(power, true);
playClickSound(power);
state.totalClicks += 1;
// Click feedback
orb.style.transform = 'scale(0.92)';
setTimeout(() => {
if (orb) orb.style.transform = '';
}, 70);
// Floating text at click position
const rect = wrapper.getBoundingClientRect();
spawnFloatingText('+' + formatNumber(power), e.clientX, e.clientY);
// Particles at orb center
if (window.createClickParticles) {
window.createClickParticles(200, 200, power);
}
// Update stats occasionally
if (state.totalClicks % 7 === 0) {
renderStats();
}
checkAchievements();
}
orb.addEventListener('click', handleClick);
orb.addEventListener('mousedown', () => {
orb.style.boxShadow = `
0 0 0 8px rgba(124, 58, 237, 0.2),
0 0 0 18px rgba(124, 58, 237, 0.1),
0 10px 30px -8px rgba(0, 0, 0, 0.4),
inset 0 2px 10px rgba(255,255,255,0.25),
inset -20px -20px 30px rgba(0,0,0,0.55)
`;
});
window.addEventListener('mouseup', () => {
if (orb) {
orb.style.boxShadow = `
0 0 0 12px rgba(124, 58, 237, 0.15),
0 0 0 28px rgba(124, 58, 237, 0.08),
0 25px 70px -15px rgba(0, 0, 0, 0.6),
inset 0 4px 20px rgba(255,255,255,0.35),
inset 40px 40px 60px rgba(255,255,255,0.12),
inset -30px -30px 50px rgba(0,0,0,0.5)
`;
}
});
// Keyboard support (space or enter while focused)
orb.setAttribute('tabindex', '0');
orb.addEventListener('keydown', (e) => {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
const rect = orb.getBoundingClientRect();
handleClick({ clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2 });
}
});
}
// ==================== BUY MULTIPLIER ====================
function setupBuyMultipliers() {
const buttons = document.querySelectorAll('.buy-btn');
buttons.forEach(btn => {
btn.addEventListener('click', () => {
buttons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const val = btn.dataset.multi;
currentBuyMulti = val === 'max' ? 'max' : parseInt(val, 10);
renderGenerators();
});
});
}
// ==================== PRESTIGE ====================
function setupPrestige() {
const btn = document.getElementById('prestige-btn');
btn.addEventListener('click', () => {
if (!btn.disabled) {
if (confirm('Transcend now? All generators and upgrades will reset, but you will gain permanent Astral Power.')) {
transcend();
}
}
});
}
// ==================== SAVE / LOAD ====================
const SAVE_KEY = 'aether_save_v1';
function saveGame() {
const saveData = {
...state,
upgrades: Array.from(state.upgrades),
achievements: Array.from(state.achievements),
lastSave: Date.now(),
};
localStorage.setItem(SAVE_KEY, JSON.stringify(saveData));
}
function loadGame() {
const raw = localStorage.getItem(SAVE_KEY);
if (!raw) return false;
try {
const saved = JSON.parse(raw);
// Merge into state
Object.assign(state, saved);
// Reconstruct sets
state.upgrades = new Set(saved.upgrades || []);
state.achievements = new Set(saved.achievements || []);
// Restore generators properly
state.generators = saved.generators || {};
// Handle offline progress
const now = Date.now();
const offlineSeconds = Math.min(60 * 60 * 6, Math.floor((now - (saved.lastSave || now)) / 1000));
if (offlineSeconds > 8) {
const offlineProduction = getTotalAps() * offlineSeconds * 0.35; // 35% offline efficiency
if (offlineProduction > 1) {
state.aether += offlineProduction;
state.totalAetherEarned += offlineProduction;
// Show offline banner
const banner = document.getElementById('offline-banner');
banner.textContent = `While you were away, you harvested ${formatNumber(offlineProduction)} Aether`;
banner.classList.add('show');
setTimeout(() => {
banner.classList.remove('show');
}, 6200);
}
}
// Make sure startTime makes sense
if (!state.startTime || state.startTime > now) {
state.startTime = now - 1000 * 60 * 12;
}
state.lastTick = now;
return true;
} catch (e) {
console.warn('Failed to load save:', e);
return false;
}
}
function setupAutoSave() {
setInterval(() => {
saveGame();
}, 1000 * 22);
// Save on page hide / before unload
window.addEventListener('beforeunload', saveGame);
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
saveGame();
}
});
}
// ==================== INITIALIZATION ====================
function init() {
// Load previous save if exists
const hadSave = loadGame();
// Initialize particle system
initParticles();
initStarfield();
// Initial generators state if new game
for (const g of generators) {
if (!state.generators[g.id]) {
state.generators[g.id] = { owned: 0 };
}
}
// If it's a brand new game, give a nice little starting gift
if (!hadSave) {
state.aether = 24;
state.totalAetherEarned = 24;
state.startTime = Date.now();
}
// Wire up interactions
setupClicking();
setupBuyMultipliers();
setupPrestige();
setupAutoSave();
// First render
renderAll();
// Start production
startGameLoop();
// Gentle initial hint
if (!hadSave) {
setTimeout(() => {
const hint = document.createElement('div');
hint.style.cssText = 'position:absolute; bottom:-34px; left:50%; transform:translateX(-50%); font-size:11px; color:#64748b; white-space:nowrap; pointer-events:none;';
hint.textContent = 'Click the glowing core';
document.getElementById('orb-wrapper').appendChild(hint);
setTimeout(() => hint.remove(), 2600);
}, 1800);
}
// Easter egg: konami code gives a burst of aether
let konami = '';
const code = 'ArrowUpArrowUpArrowDownArrowDownArrowLeftArrowRightArrowLeftArrowRightba';
document.addEventListener('keydown', (e) => {
konami += e.key;
if (konami.length > code.length) konami = konami.slice(-code.length);
if (konami === code) {
konami = '';
const burst = Math.max(500, state.aether * 0.07);
addAether(burst, true);
createToast('Secret', 'The universe smiles upon you.');
}
});
// Dev helper: type "aetherdebug" to give big resources
let debugBuffer = '';
document.addEventListener('keypress', (e) => {
debugBuffer += e.key;
if (debugBuffer.length > 12) debugBuffer = debugBuffer.slice(-12);
if (debugBuffer.includes('aetherdebug')) {
debugBuffer = '';
state.aether += 1e7;
state.totalAetherEarned += 1e7;
renderAll();
createToast('Debug', 'Added 10 million Aether');
}
});
// Make it feel alive — subtle random glow pulses on the orb
const orb = document.getElementById('orb');
setInterval(() => {
if (!orb || document.hidden) return;
const intensity = 0.6 + Math.random() * 0.7;
orb.style.boxShadow = `
0 0 0 12px rgba(124, 58, 237, ${0.12 * intensity}),
0 0 0 28px rgba(124, 58, 237, ${0.06 * intensity}),
0 25px 70px -15px rgba(0, 0, 0, 0.6),
inset 0 4px 20px rgba(255,255,255,0.35),
inset 40px 40px 60px rgba(255,255,255,0.12),
inset -30px -30px 50px rgba(0,0,0,0.5)
`;
setTimeout(() => {
if (orb) {
orb.style.boxShadow = `
0 0 0 12px rgba(124, 58, 237, 0.15),
0 0 0 28px rgba(124, 58, 237, 0.08),
0 25px 70px -15px rgba(0, 0, 0, 0.6),
inset 0 4px 20px rgba(255,255,255,0.35),
inset 40px 40px 60px rgba(255,255,255,0.12),
inset -30px -30px 50px rgba(0,0,0,0.5)
`;
}
}, 420);
}, 12500);
// Welcome message for new players
if (!hadSave) {
setTimeout(() => {
// No intrusive toast on first load — the game speaks for itself
}, 800);
} else {
// Returning player
setTimeout(() => {
if (state.transcendences > 0) {
// subtle welcome back
}
}, 1200);
}
// Expose a tiny bit for fun / debugging in console (not necessary for players)
window.AETHER = { state, save: saveGame, transcend: () => transcend() };
}
// Boot the game
init();
</script>
</body>
</html>