Gemini 3.5 Flash (high)
GoogleMay 2026
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>Quantum Forge: Cosmic Idle</title>
<!-- FontAwesome CDN for modern sci-fi icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Canvas Layers -->
<canvas id="bg-canvas"></canvas>
<canvas id="particle-canvas"></canvas>
<div class="app-container">
<!-- Header Navigation -->
<header>
<div class="brand">
<div class="brand-logo"></div>
<h1>QUANTUM FORGE</h1>
</div>
<div class="header-controls">
<button id="sfx-toggle" class="control-btn active" title="Toggle Sound Effects">
<i class="fas fa-volume-up"></i> SFX
</button>
<button id="music-toggle" class="control-btn active" title="Toggle Ambient Synth Pad">
<i class="fas fa-volume-down"></i> Music
</button>
<button id="export-btn" class="control-btn" title="Export save code to clipboard">
<i class="fas fa-file-export"></i> Export Save
</button>
<button id="import-btn" class="control-btn" title="Import save code">
<i class="fas fa-file-import"></i> Import Save
</button>
<button id="reset-btn" class="control-btn" title="Clear all local data and restart game">
<i class="fas fa-trash-alt"></i> Wipe Save
</button>
</div>
</header>
<!-- Sidebar Menu -->
<aside>
<nav class="nav-links">
<button class="nav-item active" data-tab="reactor">
<i class="fas fa-radiation"></i> <span>Reactor Core</span>
</button>
<button class="nav-item" data-tab="automation">
<i class="fas fa-microchip"></i> <span>Automation</span>
</button>
<button class="nav-item" data-tab="research">
<i class="fas fa-flask"></i> <span>Research Labs</span>
</button>
<button class="nav-item" data-tab="cosmos">
<i class="fas fa-infinity"></i> <span>Cosmic Void</span>
</button>
<button class="nav-item" data-tab="stats">
<i class="fas fa-chart-bar"></i> <span>Records</span>
</button>
</nav>
<div class="sidebar-footer">
<div>Quantum Forge v1.2</div>
<div>Engineered in CSS & HTML5</div>
</div>
</aside>
<!-- Main Game Window -->
<main>
<!-- Interactive Reactor Panel (Always visible on left / top on mobile) -->
<section class="reactor-panel">
<!-- Temporary Buff Countdowns (Anomalies) -->
<div id="buff-countdown-banner" style="display: none; align-items: center; justify-content: space-between; background: rgba(255, 0, 127, 0.1); border: 1px solid var(--magenta); padding: 8px 15px; border-radius: 6px; font-family: 'Orbitron', sans-serif; font-size: 0.8rem; margin-bottom: 15px;">
<span class="buff-label" style="font-weight: 700; letter-spacing: 1px; color: #fff;">SUPERNOVA ACTIVE</span>
<span class="buff-timer" style="color: var(--magenta); font-weight: 800; text-shadow: 0 0 10px var(--magenta-glow);">15.0s</span>
</div>
<div class="resource-display">
<div class="resource-label">Stored Quarks</div>
<div class="resource-count" id="main-quark-count">0</div>
<div class="resource-rate">Generation: <span class="rate-value" id="main-quark-rate">+0/s</span></div>
</div>
<!-- Central Core Clicker Target -->
<div class="core-container">
<div class="orbiting-rings"></div>
<div class="orbiting-rings-2"></div>
<div class="quantum-core-outer" id="core-click-target">
<div class="quantum-core-glow"></div>
<div class="quantum-core"></div>
</div>
</div>
<!-- Auxiliary Currencies -->
<div style="display: flex; gap: 15px; margin-bottom: 20px;">
<div style="flex: 1; text-align: center; padding: 10px; background: rgba(255, 255, 255, 0.02); border: 1px solid var(--border-light); border-radius: 8px;">
<div style="font-size: 0.7rem; text-transform: uppercase; color: var(--gold); letter-spacing: 1px; font-family: 'Orbitron', sans-serif;">Energy Cells</div>
<div style="font-family: 'Orbitron', sans-serif; font-size: 1.15rem; font-weight: 700; margin-top: 4px;" id="main-energy-count">0</div>
<div style="font-size: 0.75rem; color: var(--text-muted); margin-top: 2px;" id="main-energy-rate">+0/s</div>
</div>
<div style="flex: 1; text-align: center; padding: 10px; background: rgba(255, 255, 255, 0.02); border: 1px solid var(--border-light); border-radius: 8px;">
<div style="font-size: 0.7rem; text-transform: uppercase; color: var(--magenta); letter-spacing: 1px; font-family: 'Orbitron', sans-serif;">Quantum Foam</div>
<div style="font-family: 'Orbitron', sans-serif; font-size: 1.15rem; font-weight: 700; margin-top: 4px;" id="main-foam-count">0</div>
<div style="font-size: 0.75rem; color: var(--text-muted); margin-top: 2px;">Bonus: <span style="color: var(--cyan); font-weight: 600;" id="main-prestige-mult">0%</span></div>
</div>
</div>
<!-- General quick-stats -->
<div class="reactor-stats-grid">
<div class="stat-box">
<div class="stat-val" id="stat-click-power">1</div>
<div class="stat-lbl">Click Power</div>
</div>
<div class="stat-box">
<div class="stat-val" id="stat-total-upgrades">0</div>
<div class="stat-lbl">Automators</div>
</div>
<div class="stat-box">
<div class="stat-val" id="stat-prestige-count">0</div>
<div class="stat-lbl">Leaps</div>
</div>
<div class="stat-box">
<div class="stat-val" id="stat-achievements-count">0 / 16</div>
<div class="stat-lbl">Achievements</div>
</div>
</div>
<!-- Sync indicator -->
<div style="display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 15px; font-size: 0.75rem; color: var(--text-dark);">
<span id="autosave-indicator" style="opacity: 0; transition: opacity 0.5s ease; color: var(--green); font-weight: 500;">
<i class="fas fa-check-circle"></i> DATA SYNCED
</span>
</div>
</section>
<!-- Dynamic Dashboard Pages (Right half) -->
<section class="dashboard-content">
<!-- 1. Reactor Console (Default landing) -->
<div class="tab-panel active" id="tab-reactor">
<div class="section-header">
<h2>Command Interface</h2>
<p>Welcome, Operator. Harness quantum energy by clicking the Core Reactor, then automate production in the navigation tabs.</p>
</div>
<div style="display: flex; flex-direction: column; gap: 20px; padding: 20px; background: var(--bg-card); border: 1px solid var(--border-light); border-radius: 12px; backdrop-filter: blur(10px);">
<div style="font-family: 'Orbitron', sans-serif; font-size: 1.1rem; font-weight: 700; border-bottom: 1px solid var(--border-light); padding-bottom: 10px; color: var(--cyan);">
OPERATIONAL LOGS
</div>
<div style="font-family: monospace; font-size: 0.85rem; color: var(--text-muted); line-height: 1.6;">
<div>[SYSTEM] Initializing Quantum Core Forge...</div>
<div>[SYSTEM] Core Stabilized. Quark field detected.</div>
<div>[HINT] Click the central reactor sphere repeatedly to gain raw Quarks.</div>
<div>[HINT] Once you acquire 15 Quarks, open the <span style="color:var(--cyan); cursor:pointer;" onclick="switchTab('automation')">Automation Tab</span> to buy Subatomic Stabilizers.</div>
<div>[HINT] Keep an eye out for random spatial anomalies flying across the screen; click them for massive multipliers!</div>
</div>
</div>
</div>
<!-- 2. Automation Upgrades -->
<div class="tab-panel" id="tab-automation">
<!-- Rendered dynamically by app.js -->
</div>
<!-- 3. Tech Tree Research -->
<div class="tab-panel" id="tab-research">
<!-- Rendered dynamically by app.js -->
</div>
<!-- 4. Cosmos Prestige Page -->
<div class="tab-panel" id="tab-cosmos">
<!-- Rendered dynamically by app.js -->
</div>
<!-- 5. Records (Stats/Achievements) -->
<div class="tab-panel" id="tab-stats">
<!-- Rendered dynamically by app.js -->
</div>
</section>
</main>
</div>
<!-- Achievements/Unlock Notifications container -->
<div class="toast-container" id="toast-container"></div>
<!-- Load Web Audio Synth Engine first, then gameplay loops -->
<script src="audio.js"></script>
<script src="app.js"></script>
</body>
</html>@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Orbitron:wght@400;600;800;900&display=swap');
/* CSS variables for consistent design system */
:root {
--bg-darker: #05070c;
--bg-dark: #0a0d16;
--bg-card: rgba(13, 17, 28, 0.7);
--bg-card-hover: rgba(20, 27, 43, 0.85);
--border-glow: rgba(0, 242, 254, 0.15);
--border-glow-hover: rgba(0, 242, 254, 0.35);
--border-light: rgba(255, 255, 255, 0.06);
/* Color Palettes - Neon Glowing Sci-Fi */
--cyan: #00f2fe;
--magenta: #ff007f;
--purple: #7928ca;
--purple-glow: rgba(121, 40, 202, 0.4);
--cyan-glow: rgba(0, 242, 254, 0.4);
--magenta-glow: rgba(255, 0, 127, 0.4);
--gold: #f5a623;
--gold-glow: rgba(245, 166, 35, 0.4);
--green: #00ff66;
--green-glow: rgba(0, 255, 102, 0.3);
--text: #f0f4f8;
--text-muted: #8a99ad;
--text-dark: #4e5a6b;
--shadow-main: 0 8px 32px 0 rgba(0, 0, 0, 0.5);
--transition-fast: 0.15s ease;
--transition-norm: 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
background-color: var(--bg-darker);
background-image:
radial-gradient(circle at 10% 20%, rgba(121, 40, 202, 0.08) 0%, transparent 40%),
radial-gradient(circle at 90% 80%, rgba(0, 242, 254, 0.08) 0%, transparent 40%);
color: var(--text);
font-family: 'Inter', sans-serif;
overflow: hidden;
height: 100vh;
width: 100vw;
}
/* Background Stars Canvas */
#bg-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
}
/* Particle effect canvas layer */
#particle-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2;
pointer-events: none;
}
/* Screen shake effect on prestige */
.prestige-shake {
animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both;
}
/* Main Container Layout */
.app-container {
position: relative;
z-index: 1;
display: grid;
grid-template-rows: 70px 1fr;
grid-template-columns: 240px 1fr;
height: 100vh;
width: 100vw;
}
/* Header Styling */
header {
grid-column: 1 / -1;
background: rgba(10, 13, 22, 0.8);
border-bottom: 1px solid var(--border-light);
backdrop-filter: blur(10px);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 24px;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
}
.brand-logo {
width: 32px;
height: 32px;
border-radius: 50%;
background: linear-gradient(135deg, var(--cyan), var(--purple));
box-shadow: 0 0 15px var(--cyan-glow);
position: relative;
animation: rotateLogo 8s linear infinite;
}
.brand-logo::after {
content: '';
position: absolute;
top: 3px;
left: 3px;
right: 3px;
bottom: 3px;
background: var(--bg-darker);
border-radius: 50%;
}
.brand h1 {
font-family: 'Orbitron', sans-serif;
font-size: 1.25rem;
font-weight: 800;
letter-spacing: 2px;
background: linear-gradient(to right, var(--cyan), #fff, var(--magenta));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 20px rgba(0, 242, 254, 0.2);
}
.header-controls {
display: flex;
align-items: center;
gap: 15px;
}
.control-btn {
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border-light);
color: var(--text-muted);
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 0.85rem;
font-family: inherit;
display: flex;
align-items: center;
gap: 6px;
transition: var(--transition-fast);
}
.control-btn:hover {
background: rgba(255, 255, 255, 0.08);
color: var(--text);
border-color: var(--cyan);
box-shadow: 0 0 8px rgba(0, 242, 254, 0.15);
}
.control-btn.active {
color: var(--cyan);
border-color: var(--cyan);
background: rgba(0, 242, 254, 0.05);
}
/* Sidebar Navigation */
aside {
background: rgba(8, 10, 18, 0.85);
border-right: 1px solid var(--border-light);
backdrop-filter: blur(10px);
padding: 24px 12px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.nav-links {
display: flex;
flex-direction: column;
gap: 10px;
}
.nav-item {
background: transparent;
border: 1px solid transparent;
color: var(--text-muted);
padding: 14px 18px;
border-radius: 8px;
cursor: pointer;
font-family: 'Orbitron', sans-serif;
font-size: 0.9rem;
font-weight: 600;
text-align: left;
letter-spacing: 1px;
display: flex;
align-items: center;
gap: 12px;
transition: var(--transition-norm);
}
.nav-item i {
font-size: 1.1rem;
width: 20px;
text-align: center;
}
.nav-item:hover {
color: var(--text);
background: rgba(255, 255, 255, 0.02);
border-color: rgba(255, 255, 255, 0.05);
}
.nav-item.active {
color: #fff;
background: linear-gradient(90deg, rgba(0, 242, 254, 0.15) 0%, rgba(121, 40, 202, 0.05) 100%);
border-color: var(--cyan);
box-shadow: 0 0 15px rgba(0, 242, 254, 0.15);
text-shadow: 0 0 5px rgba(255, 255, 255, 0.5);
}
.nav-item.active i {
color: var(--cyan);
text-shadow: 0 0 8px var(--cyan);
}
.sidebar-footer {
padding: 12px;
border-top: 1px solid var(--border-light);
font-size: 0.75rem;
color: var(--text-dark);
text-align: center;
}
.sidebar-footer a {
color: var(--text-muted);
text-decoration: none;
}
/* Main Dashboard Panel */
main {
display: grid;
grid-template-columns: 450px 1fr;
height: calc(100vh - 70px);
overflow: hidden;
}
/* Reactor Panel (Left half of main content) */
.reactor-panel {
border-right: 1px solid var(--border-light);
background: rgba(6, 8, 14, 0.4);
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 30px;
position: relative;
}
.resource-display {
text-align: center;
margin-bottom: 20px;
}
.resource-label {
font-family: 'Orbitron', sans-serif;
font-size: 0.85rem;
color: var(--cyan);
letter-spacing: 3px;
text-transform: uppercase;
margin-bottom: 8px;
text-shadow: 0 0 10px var(--cyan-glow);
}
.resource-count {
font-family: 'Orbitron', sans-serif;
font-size: 2.2rem;
font-weight: 900;
letter-spacing: 1px;
color: #fff;
text-shadow: 0 0 20px rgba(255,255,255,0.2);
}
.resource-rate {
font-size: 0.9rem;
color: var(--text-muted);
margin-top: 6px;
}
.rate-value {
color: var(--green);
font-weight: 600;
}
/* Core Click Target styling */
.core-container {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
position: relative;
min-height: 250px;
}
.quantum-core-outer {
position: relative;
width: 180px;
height: 180px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.quantum-core-glow {
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
background: radial-gradient(circle, var(--cyan-glow) 0%, transparent 70%);
animation: pulseGlow 4s infinite alternate;
z-index: 1;
pointer-events: none;
}
.quantum-core {
position: relative;
z-index: 3;
width: 130px;
height: 130px;
border-radius: 50%;
background: radial-gradient(circle, #fff 10%, var(--cyan) 50%, var(--purple) 90%);
box-shadow:
0 0 30px var(--cyan-glow),
0 0 60px var(--purple-glow),
inset 0 0 20px rgba(255, 255, 255, 0.8);
transition: transform 0.05s ease;
animation: rotateCore 20s linear infinite;
}
.quantum-core-outer:hover .quantum-core {
transform: scale(1.05);
box-shadow:
0 0 45px var(--cyan),
0 0 90px var(--purple-glow),
inset 0 0 25px rgba(255, 255, 255, 0.9);
}
.quantum-core-outer:active .quantum-core {
transform: scale(0.95);
box-shadow:
0 0 20px var(--cyan),
0 0 40px var(--purple-glow),
inset 0 0 10px rgba(255, 255, 255, 0.9);
}
.orbiting-rings {
position: absolute;
width: 200px;
height: 200px;
border: 1px dashed rgba(0, 242, 254, 0.3);
border-radius: 50%;
pointer-events: none;
animation: spinRing 12s linear infinite;
}
.orbiting-rings-2 {
position: absolute;
width: 230px;
height: 230px;
border: 1px solid rgba(255, 0, 127, 0.15);
border-radius: 50%;
pointer-events: none;
animation: spinRingBack 18s linear infinite;
}
.reactor-stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
background: rgba(255, 255, 255, 0.02);
border: 1px solid var(--border-light);
border-radius: 8px;
padding: 15px;
}
.stat-box {
text-align: center;
}
.stat-val {
font-family: 'Orbitron', sans-serif;
font-size: 1.1rem;
font-weight: 700;
color: #fff;
}
.stat-lbl {
font-size: 0.75rem;
color: var(--text-muted);
margin-top: 4px;
text-transform: uppercase;
}
/* Secondary Panels / Upgrades Column (Right side of main content) */
.dashboard-content {
overflow-y: auto;
padding: 30px;
position: relative;
display: flex;
flex-direction: column;
gap: 24px;
}
/* Custom Scrollbar for dashboard content */
.dashboard-content::-webkit-scrollbar {
width: 6px;
}
.dashboard-content::-webkit-scrollbar-track {
background: rgba(0,0,0,0.1);
}
.dashboard-content::-webkit-scrollbar-thumb {
background: var(--border-light);
border-radius: 3px;
}
.dashboard-content::-webkit-scrollbar-thumb:hover {
background: var(--text-dark);
}
.tab-panel {
display: none;
animation: fadeIn 0.4s ease forwards;
}
.tab-panel.active {
display: flex;
flex-direction: column;
gap: 20px;
}
.section-header {
margin-bottom: 15px;
}
.section-header h2 {
font-family: 'Orbitron', sans-serif;
font-size: 1.4rem;
font-weight: 700;
color: #fff;
letter-spacing: 1px;
}
.section-header p {
font-size: 0.85rem;
color: var(--text-muted);
margin-top: 4px;
}
/* Card grids */
.upgrades-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.upgrade-card {
background: var(--bg-card);
border: 1px solid var(--border-light);
border-radius: 10px;
padding: 16px;
display: grid;
grid-template-columns: 50px 1fr auto;
align-items: center;
gap: 16px;
transition: all var(--transition-norm);
backdrop-filter: blur(10px);
position: relative;
overflow: hidden;
}
.upgrade-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 3px;
height: 100%;
background: var(--cyan);
opacity: 0.5;
}
.upgrade-card:hover:not(.locked) {
background: var(--bg-card-hover);
border-color: var(--border-glow-hover);
box-shadow: 0 4px 20px rgba(0, 242, 254, 0.06);
}
.upgrade-card.unaffordable:not(.locked) {
opacity: 0.8;
}
.upgrade-icon-wrapper {
width: 44px;
height: 44px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border-light);
display: flex;
justify-content: center;
align-items: center;
color: var(--text-muted);
font-size: 1.25rem;
}
.upgrade-card:hover:not(.locked) .upgrade-icon-wrapper {
color: var(--cyan);
border-color: var(--cyan);
background: rgba(0, 242, 254, 0.05);
}
.upgrade-details {
display: flex;
flex-direction: column;
gap: 4px;
}
.upgrade-title {
font-weight: 600;
font-size: 0.95rem;
color: #fff;
}
.upgrade-desc {
font-size: 0.8rem;
color: var(--text-muted);
}
.upgrade-effect {
font-size: 0.8rem;
color: var(--green);
font-weight: 500;
}
.upgrade-meta {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 6px;
}
.upgrade-count {
font-family: 'Orbitron', sans-serif;
font-size: 0.8rem;
color: var(--cyan);
font-weight: 600;
background: rgba(0, 242, 254, 0.1);
padding: 2px 8px;
border-radius: 4px;
}
.buy-btn {
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border-light);
color: #fff;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-family: 'Orbitron', sans-serif;
font-size: 0.85rem;
font-weight: 600;
transition: all var(--transition-fast);
}
.buy-btn:hover {
background: var(--cyan);
color: #000;
border-color: var(--cyan);
box-shadow: 0 0 10px var(--cyan-glow);
}
.upgrade-card.unaffordable .buy-btn {
opacity: 0.5;
cursor: not-allowed;
background: rgba(255, 255, 255, 0.02);
color: var(--text-muted);
border-color: var(--border-light);
box-shadow: none;
}
/* Locked Upgrades styling */
.upgrade-card.locked {
filter: blur(2px) grayscale(90%);
opacity: 0.4;
pointer-events: none;
}
/* Research Tree Node visual connections styling (alternative layout for clean research list) */
.research-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.research-card {
background: var(--bg-card);
border: 1px solid var(--border-light);
border-radius: 12px;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 15px;
transition: all var(--transition-norm);
backdrop-filter: blur(10px);
position: relative;
}
.research-card.locked {
opacity: 0.3;
pointer-events: none;
border-color: transparent;
}
.research-card.purchased {
border-color: var(--purple);
box-shadow: 0 0 15px var(--purple-glow);
}
.research-card.purchased::after {
content: 'RESEARCHED';
position: absolute;
top: 10px;
right: 10px;
font-size: 0.65rem;
font-weight: 700;
background: var(--purple);
color: #fff;
padding: 2px 6px;
border-radius: 4px;
letter-spacing: 1px;
}
.research-title {
font-family: 'Orbitron', sans-serif;
font-weight: 700;
font-size: 1rem;
color: #fff;
}
.research-desc {
font-size: 0.8rem;
color: var(--text-muted);
line-height: 1.4;
flex-grow: 1;
}
.research-cost {
font-family: 'Orbitron', sans-serif;
font-size: 0.85rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 4px;
}
.research-cost.quarks { color: var(--cyan); }
.research-cost.energy { color: var(--gold); }
.research-cost.foam { color: var(--magenta); }
.research-btn {
width: 100%;
padding: 10px;
border-radius: 6px;
font-family: 'Orbitron', sans-serif;
font-size: 0.8rem;
font-weight: 600;
cursor: pointer;
transition: all var(--transition-fast);
border: 1px solid var(--border-light);
background: rgba(255, 255, 255, 0.03);
color: var(--text-muted);
}
.research-btn:hover:not(:disabled) {
border-color: var(--purple);
color: #fff;
background: var(--purple);
box-shadow: 0 0 12px var(--purple-glow);
}
.research-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Cosmos / Prestige Page */
.prestige-container {
display: flex;
flex-direction: column;
gap: 24px;
}
.prestige-box {
background: linear-gradient(135deg, rgba(13, 17, 28, 0.8) 0%, rgba(121, 40, 202, 0.1) 100%);
border: 1px solid var(--purple);
border-radius: 12px;
padding: 30px;
text-align: center;
backdrop-filter: blur(10px);
box-shadow: 0 0 25px rgba(121, 40, 202, 0.15);
}
.prestige-headline {
font-family: 'Orbitron', sans-serif;
font-size: 1.6rem;
font-weight: 800;
color: #fff;
margin-bottom: 12px;
letter-spacing: 1px;
}
.prestige-subtext {
font-size: 0.9rem;
color: var(--text-muted);
max-width: 500px;
margin: 0 auto 20px auto;
line-height: 1.5;
}
.prestige-stats {
display: flex;
justify-content: center;
gap: 40px;
margin-bottom: 24px;
}
.prestige-stat-item {
display: flex;
flex-direction: column;
gap: 6px;
}
.prestige-stat-item .val {
font-family: 'Orbitron', sans-serif;
font-size: 1.5rem;
font-weight: 700;
}
.prestige-stat-item .val.foam {
color: var(--magenta);
text-shadow: 0 0 10px rgba(255, 0, 127, 0.3);
}
.prestige-stat-item .val.multiplier {
color: var(--cyan);
text-shadow: 0 0 10px rgba(0, 242, 254, 0.3);
}
.prestige-stat-item .lbl {
font-size: 0.8rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1px;
}
.prestige-action-btn {
background: linear-gradient(90deg, var(--purple), var(--magenta));
border: none;
color: #fff;
padding: 14px 30px;
border-radius: 8px;
font-family: 'Orbitron', sans-serif;
font-size: 1rem;
font-weight: 700;
letter-spacing: 1px;
cursor: pointer;
transition: all var(--transition-norm);
box-shadow: 0 0 20px rgba(255, 0, 127, 0.2);
}
.prestige-action-btn:hover {
transform: translateY(-2px);
box-shadow: 0 0 30px rgba(255, 0, 127, 0.4);
}
.prestige-action-btn:active {
transform: translateY(1px);
}
.prestige-action-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
/* Achievements Section */
.achievements-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 12px;
}
.achievement-card {
background: var(--bg-card);
border: 1px solid var(--border-light);
border-radius: 8px;
padding: 14px;
display: flex;
align-items: center;
gap: 12px;
transition: all var(--transition-norm);
}
.achievement-card.unlocked {
border-color: var(--gold);
background: linear-gradient(135deg, rgba(13, 17, 28, 0.7) 0%, rgba(245, 166, 35, 0.03) 100%);
box-shadow: 0 0 10px rgba(245, 166, 35, 0.08);
}
.achievement-card.unlocked .achievement-badge {
color: var(--gold);
background: rgba(245, 166, 35, 0.1);
border-color: var(--gold);
text-shadow: 0 0 8px var(--gold-glow);
}
.achievement-badge {
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.02);
border: 1px solid var(--border-light);
display: flex;
justify-content: center;
align-items: center;
font-size: 1.1rem;
color: var(--text-dark);
transition: var(--transition-norm);
}
.achievement-info {
display: flex;
flex-direction: column;
gap: 3px;
}
.achievement-name {
font-size: 0.9rem;
font-weight: 600;
color: var(--text-muted);
}
.achievement-card.unlocked .achievement-name {
color: #fff;
}
.achievement-description {
font-size: 0.75rem;
color: var(--text-dark);
}
.achievement-card.unlocked .achievement-description {
color: var(--text-muted);
}
/* Toast Notifications for unlocks */
.toast-container {
position: absolute;
bottom: 24px;
right: 24px;
z-index: 100;
display: flex;
flex-direction: column;
gap: 10px;
pointer-events: none;
}
.toast {
background: rgba(8, 10, 18, 0.9);
border: 1px solid var(--gold);
border-radius: 8px;
padding: 12px 18px;
display: flex;
align-items: center;
gap: 12px;
box-shadow: 0 10px 25px rgba(0,0,0,0.5), 0 0 15px var(--gold-glow);
animation: slideIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards,
fadeOut 0.3s cubic-bezier(0.6, -0.28, 0.735, 0.045) 4.7s forwards;
pointer-events: auto;
}
.toast i {
color: var(--gold);
font-size: 1.25rem;
}
.toast-body {
display: flex;
flex-direction: column;
gap: 2px;
}
.toast-title {
font-size: 0.8rem;
color: var(--gold);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
}
.toast-message {
font-size: 0.85rem;
color: #fff;
font-weight: 500;
}
/* Cosmic Anomaly floating objects */
.anomaly {
position: absolute;
width: 50px;
height: 50px;
border-radius: 50%;
background: radial-gradient(circle, #fff 10%, var(--magenta) 50%, transparent 80%);
box-shadow: 0 0 25px var(--magenta);
cursor: pointer;
z-index: 50;
animation: anomalyPulse 1.5s infinite alternate;
display: flex;
justify-content: center;
align-items: center;
}
.anomaly::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
border: 1px solid var(--magenta);
animation: anomalyRings 2s infinite linear;
}
/* Floating Click Indicators */
.floating-indicator {
position: absolute;
color: var(--cyan);
font-family: 'Orbitron', sans-serif;
font-size: 1.1rem;
font-weight: 700;
pointer-events: none;
z-index: 100;
text-shadow: 0 0 8px rgba(0, 242, 254, 0.6);
animation: floatUp 1.2s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}
.floating-indicator.foam {
color: var(--magenta);
text-shadow: 0 0 8px rgba(255, 0, 127, 0.6);
}
/* Animation Keyframes */
@keyframes rotateLogo {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes rotateCore {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes spinRing {
from { transform: rotate(0deg) rotateX(70deg); }
to { transform: rotate(360deg) rotateX(70deg); }
}
@keyframes spinRingBack {
from { transform: rotate(360deg) rotateX(60deg) rotateY(20deg); }
to { transform: rotate(0deg) rotateX(60deg) rotateY(20deg); }
}
@keyframes pulseGlow {
0% { transform: scale(0.9); opacity: 0.5; }
100% { transform: scale(1.1); opacity: 0.9; }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideIn {
from { transform: translateX(120%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes fadeOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(120%); opacity: 0; }
}
@keyframes floatUp {
0% { transform: translateY(0) scale(1); opacity: 1; }
100% { transform: translateY(-100px) scale(0.8); opacity: 0; }
}
@keyframes anomalyPulse {
0% { transform: scale(0.9); box-shadow: 0 0 15px var(--magenta); }
100% { transform: scale(1.1); box-shadow: 0 0 35px var(--magenta); }
}
@keyframes anomalyRings {
0% { transform: scale(0.8); opacity: 0.8; }
100% { transform: scale(2.0); opacity: 0; }
}
@keyframes shake {
10%, 90% { transform: translate3d(-1px, 0, 0); }
20%, 80% { transform: translate3d(2px, 0, 0); }
30%, 50%, 70% { transform: translate3d(-4px, 0, 0); }
40%, 60% { transform: translate3d(4px, 0, 0); }
}
/* Responsive Overrides (For smaller screens) */
@media (max-width: 900px) {
.app-container {
grid-template-columns: 80px 1fr;
}
.nav-item span {
display: none;
}
.nav-item {
justify-content: center;
padding: 15px 0;
}
main {
grid-template-columns: 1fr;
grid-template-rows: auto 1fr;
overflow-y: auto;
}
.reactor-panel {
border-right: none;
border-bottom: 1px solid var(--border-light);
height: auto;
padding: 20px;
}
.dashboard-content {
height: auto;
overflow: visible;
}
}// Quantum Forge: Cosmic Idle - Main Game Engine
// Game State Definition
const gameState = {
// Currencies
quarks: 0,
quarksSpent: 0,
totalQuarksGained: 0,
energy: 0,
energyRate: 0,
quantumFoam: 0,
totalFoamGained: 0,
// Clicking stats
clickPower: 1,
clickCount: 0,
// Progression counts
prestigeCount: 0,
// Timers and settings
lastTickTime: Date.now(),
autoSaveInterval: 10000, // 10s auto save
// Upgrades purchased: ID -> Quantity
upgrades: {
stabilizer: 0, // Click power +1
collider: 0, // +0.1 Q/s
injector: 0, // +1.5 Q/s
reactor: 0, // +8 Q/s, +0.2 E/s
tuner: 0, // +45 Q/s
condenser: 0, // +260 Q/s
siphon: 0, // +1500 Q/s, +2.5 E/s
singularity: 0,// +12000 Q/s
bridge: 0 // +95000 Q/s, +15 E/s
},
// Research unlocked (boolean flags)
research: {
superstring: false, // +100% click power
entangled: false, // Autoclick every 2s
antimatter: false, // +25% automator efficiency
highenergy: false, // Double reactor energy output
tachyon: false, // Double all Quark generation
dimensional: false, // Anomalies +50% frequency, +30% duration
quantumcomp: false, // Autoclick 5/s
catalyst: false, // +50% foam from prestige
stringcond: false, // All upgrades cost 15% less
parallel: false // +1% quarks/sec per achievement unlocked
},
// Permanent prestige upgrades
prestigeUpgrades: {
vacuumEnergy: 0, // +50% quark generation per level
thermalFluctuations: 0,// +1 click power per level (scaled by prestige mult)
chronosAccelerator: 0, // +2% game tick speed per level
cosmicInflation: 0 // +100% anomaly rewards
},
// Achievements unlocked
achievements: []
};
// Game Upgrade Catalog
const UPGRADE_DATA = {
stabilizer: { name: "Subatomic Stabilizer", baseCost: 15, costMult: 1.14, type: "click", val: 1, desc: "Stabilizes subatomic orbits to increase click power.", effectDesc: "+1 Click Power" },
collider: { name: "Hadron Collider", baseCost: 50, costMult: 1.15, type: "generator", val: 0.1, desc: "Smashes protons to liberate quarks.", effectDesc: "+0.1 Quarks/s" },
injector: { name: "Plasma Injector", baseCost: 250, costMult: 1.16, type: "generator", val: 1.5, desc: "Injects superheated quarks directly into storage.", effectDesc: "+1.5 Quarks/s" },
reactor: { name: "Nuclear Reactor", baseCost: 1200, costMult: 1.18, type: "hybrid", val: 8, energyVal: 0.2, desc: "Fissions atoms to generate Quarks and Energy.", effectDesc: "+8 Q/s, +0.2 Energy/s" },
tuner: { name: "Quantum Tuner", baseCost: 8500, costMult: 1.18, type: "generator", val: 45, desc: "Harmonizes string vibrations to forge quarks.", effectDesc: "+45 Quarks/s" },
condenser: { name: "Gravity Condenser", baseCost: 50000, costMult: 1.20, type: "generator", val: 260, desc: "Condenses space-time to extract vacuum particles.", effectDesc: "+260 Quarks/s" },
siphon: { name: "Dark Matter Siphon", baseCost: 350000, costMult: 1.20, type: "hybrid", val: 1500, energyVal: 2.5, desc: "Extracts exotic dark matter to power the reactor.", effectDesc: "+1.5K Q/s, +2.5 Energy/s" },
singularity: { name: "Singularity Matrix", baseCost: 2500000, costMult: 1.22, type: "generator", val: 12000, desc: "Controls micro-black holes for massive energy conversion.", effectDesc: "+12K Quarks/s" },
bridge: { name: "Wormhole Bridge", baseCost: 20000000, costMult: 1.25, type: "hybrid", val: 95000, energyVal: 15.0, desc: "Bridges universes to pull continuous matter streams.", effectDesc: "+95K Q/s, +15 Energy/s" }
};
// Research Tech Tree
const RESEARCH_DATA = {
superstring: { name: "Superstring Theory", costType: "quarks", cost: 5000, desc: "Decipher string dimensions, doubling click efficiency.", effectDesc: "+100% Click Power" },
entangled: { name: "Entangled Sensors", costType: "quarks", cost: 15000, desc: "Set up remote quantum connections to autoclick the reactor core.", effectDesc: "Clicks core automatically every 2s" },
antimatter: { name: "Antimatter Catalysis", costType: "quarks", cost: 80000, desc: "Inject antimatter into colliders, boosting automated outputs.", effectDesc: "+25% automator output" },
highenergy: { name: "High-Energy Physics", costType: "energy", cost: 250, desc: "Unlock higher plasma tiers, doubling Nuclear Reactor energy generation.", effectDesc: "+100% Reactor energy output" },
tachyon: { name: "Tachyon Core", costType: "energy", cost: 800, desc: "Anchor tachyon fields to the core, doubling ALL quark generation.", effectDesc: "+100% Quark generation" },
dimensional: { name: "Dimensional Warp", costType: "energy", cost: 2000, desc: "Stabilize cosmic rifts to trigger anomalies more often.", effectDesc: "+50% anomaly frequency, +30% duration" },
quantumcomp: { name: "Quantum Computing", costType: "energy", cost: 8000, desc: "Run hyper-dense qubits to click the core rapidly in the background.", effectDesc: "Clicks core automatically 5 times/s" },
catalyst: { name: "Singularity Catalyst", costType: "foam", cost: 25, desc: "Optimize collapse trajectories, boosting foam collection on reset.", effectDesc: "+50% Quantum Foam gains" },
stringcond: { name: "String Condensation", costType: "foam", cost: 100, desc: "Condense cosmic string templates, reducing all upgrade prices.", effectDesc: "-15% cost on all upgrades" },
parallel: { name: "Parallel Dimensions", costType: "foam", cost: 500, desc: "Channel power from other universes based on achievements unlocked.", effectDesc: "+1% Quark/s per Achievement unlocked" }
};
// Prestige permanent upgrades
const PRESTIGE_UPGRADE_DATA = {
vacuumEnergy: { name: "Vacuum Energy", baseCost: 1, costMult: 2, maxLevel: 10, desc: "Unleash raw space vacuum potential to boost quark speed.", effectDesc: "+50% Quark/s per level" },
thermalFluctuations: { name: "Thermal Fluctuations", baseCost: 2, costMult: 2, maxLevel: 99, desc: "Harness thermal agitation to amplify clicking.", effectDesc: "+1 Base Click Power per level, scaled by prestige multiplier" },
chronosAccelerator: { name: "Chronos Accelerator", baseCost: 5, costMult: 2, maxLevel: 10, desc: "Slightly accelerate local time flow.", effectDesc: "Game speeds up by +2% per level" },
cosmicInflation: { name: "Cosmic Inflation", baseCost: 10, costMult: 2.5, maxLevel: 99, desc: "Expand rift sizes to yield massive anomaly rewards.", effectDesc: "+100% anomaly yield per level" }
};
// Achievements
const ACHIEVEMENT_LIST = [
{ id: "first_quark", name: "Subatomic Seed", desc: "Gain your first Quark.", condition: () => gameState.totalQuarksGained >= 1 },
{ id: "quark_100", name: "Elementary Mass", desc: "Gain 100 total Quarks.", condition: () => gameState.totalQuarksGained >= 100 },
{ id: "quark_10k", name: "Quantum Burst", desc: "Gain 10,000 total Quarks.", condition: () => gameState.totalQuarksGained >= 10000 },
{ id: "quark_1m", name: "Stellar Nucleus", desc: "Gain 1,000,000 total Quarks.", condition: () => gameState.totalQuarksGained >= 1000000 },
{ id: "quark_1b", name: "Macro Cosmos", desc: "Gain 1,000,000,000 total Quarks.", condition: () => gameState.totalQuarksGained >= 1000000000 },
{ id: "first_upgrade", name: "Automated Forge", desc: "Purchase your first upgrade structure.", condition: () => getTotUpgradesCount() >= 1 },
{ id: "upgrade_50", name: "Heavy Industry", desc: "Own 50 total upgrade structures.", condition: () => getTotUpgradesCount() >= 50 },
{ id: "upgrade_200", name: "Automated Galaxy", desc: "Own 200 total upgrade structures.", condition: () => getTotUpgradesCount() >= 200 },
{ id: "clicks_100", name: "Kinetic Synthesis", desc: "Click the Quantum Core 100 times.", condition: () => gameState.clickCount >= 100 },
{ id: "clicks_1000", name: "Hyper Tapper", desc: "Click the Quantum Core 1,000 times.", condition: () => gameState.clickCount >= 1000 },
{ id: "first_prestige", name: "Vacuum Collapse", desc: "Collapse the universe (prestige) for the first time.", condition: () => gameState.prestigeCount >= 1 },
{ id: "foam_100", name: "Cosmic Foam", desc: "Collect 100 total Quantum Foam.", condition: () => gameState.totalFoamGained >= 100 },
{ id: "foam_10k", name: "Void Lord", desc: "Collect 10,000 total Quantum Foam.", condition: () => gameState.totalFoamGained >= 10000 },
{ id: "first_research", name: "Eureka!", desc: "Unlock your first research node.", condition: () => getResearchesCount() >= 1 },
{ id: "all_research", name: "Omniscient", desc: "Unlock 8 or more research nodes.", condition: () => getResearchesCount() >= 8 },
{ id: "anomaly_hunter", name: "Rift Chaser", desc: "Click 10 spatial anomalies.", condition: () => tempStats.anomaliesClicked >= 10 }
];
// Session stats not saved
const tempStats = {
anomaliesClicked: 0,
anomalyTimeout: null,
activeBuff: null, // { type: string, multiplier: number, expiresAt: timestamp }
buffTimerInterval: null
};
// Canvas references
let bgCanvas, bgCtx;
let particleCanvas, particleCtx;
let particles = [];
let stars = [];
// Init Functions
window.addEventListener('DOMContentLoaded', () => {
// Init Canvases
initBackgroundCanvas();
initParticleCanvas();
// Load Game State
loadGame();
// Set up click handlers
setupEventListeners();
// Show Default Tab
switchTab('reactor');
// Start Loops
requestAnimationFrame(particleLoop);
setInterval(gameTick, 50); // 20 ticks per second
setInterval(autoSave, gameState.autoSaveInterval);
setInterval(checkAchievements, 1000);
// Set up anomaly spawn timer
scheduleAnomalySpawn();
});
// Setup listeners
function setupEventListeners() {
// Navigation Tabs
document.querySelectorAll('.nav-item').forEach(item => {
item.addEventListener('click', (e) => {
const tabId = item.getAttribute('data-tab');
switchTab(tabId);
});
});
// Quantum Core click
const coreElement = document.getElementById('core-click-target');
if (coreElement) {
coreElement.addEventListener('click', (e) => {
handleCoreClick(e);
});
}
// Audio Buttons
const sfxBtn = document.getElementById('sfx-toggle');
const musicBtn = document.getElementById('music-toggle');
if (sfxBtn) {
sfxBtn.addEventListener('click', () => {
gameState.sfxMuted = !gameState.sfxMuted;
GameAudio.setSfxMute(gameState.sfxMuted);
sfxBtn.classList.toggle('active', !gameState.sfxMuted);
sfxBtn.querySelector('i').className = gameState.sfxMuted ? 'fas fa-volume-mute' : 'fas fa-volume-up';
});
}
if (musicBtn) {
musicBtn.addEventListener('click', () => {
gameState.musicMuted = !gameState.musicMuted;
GameAudio.setMusicMute(gameState.musicMuted);
musicBtn.classList.toggle('active', !gameState.musicMuted);
musicBtn.querySelector('i').className = gameState.musicMuted ? 'fas fa-volume-off' : 'fas fa-volume-down';
});
}
// Reset Save / Import / Export buttons
const resetBtn = document.getElementById('reset-btn');
if (resetBtn) {
resetBtn.addEventListener('click', () => {
if (confirm("Are you absolutely sure you want to WIPE all progress? This cannot be undone.")) {
wipeSave();
}
});
}
const exportBtn = document.getElementById('export-btn');
if (exportBtn) {
exportBtn.addEventListener('click', () => {
const stateStr = btoa(JSON.stringify(gameState));
navigator.clipboard.writeText(stateStr).then(() => {
alert("Save game copied to clipboard!");
}).catch(err => {
alert("Could not copy save game: " + err);
});
});
}
const importBtn = document.getElementById('import-btn');
if (importBtn) {
importBtn.addEventListener('click', () => {
const stateStr = prompt("Paste your save string here:");
if (stateStr) {
try {
const parsed = JSON.parse(atob(stateStr));
if (parsed && typeof parsed === 'object') {
Object.assign(gameState, parsed);
// Re-sync UI state
saveGame();
window.location.reload();
}
} catch (e) {
alert("Invalid save string!");
}
}
});
}
// Prestige Action
const prestigeBtn = document.getElementById('prestige-btn');
if (prestigeBtn) {
prestigeBtn.addEventListener('click', () => {
triggerPrestige();
});
}
// Window Resize
window.addEventListener('resize', () => {
resizeCanvases();
});
}
// FORMATTING HELPER
// Compact suffix scale: Scientific / Standard short scale
function formatNumber(num) {
if (num === 0) return "0";
if (num < 1000) return num.toFixed(1).replace(/\.0$/, '');
const suffixes = ["", "K", "M", "B", "T", "Qa", "Qi", "Sx", "Sp", "Oc", "No", "Dc", "UDc", "DDc", "TDc"];
const i = Math.floor(Math.log10(num) / 3);
if (i >= suffixes.length) {
return num.toExponential(2);
}
const formatted = (num / Math.pow(10, i * 3)).toFixed(2);
return formatted + suffixes[i];
}
// Tab Switching
function switchTab(tabId) {
document.querySelectorAll('.nav-item').forEach(item => {
item.classList.toggle('active', item.getAttribute('data-tab') === tabId);
});
document.querySelectorAll('.tab-panel').forEach(panel => {
panel.classList.toggle('active', panel.id === `tab-${tabId}`);
});
renderTab(tabId);
}
// Render specific tab contents
function renderTab(tabId) {
if (tabId === 'reactor') {
// Left-side core is always visible, nothing special to update
} else if (tabId === 'automation') {
renderAutomationUpgrades();
} else if (tabId === 'research') {
renderResearchTree();
} else if (tabId === 'cosmos') {
renderPrestigeTab();
} else if (tabId === 'stats') {
renderStatsTab();
}
}
// MAIN MATH FORMULAS
function getPrestigeMultiplier() {
let foamMult = 1 + (gameState.quantumFoam * 0.02); // +2% per foam
return foamMult;
}
// Click Power Calculation
function getClickPower() {
let baseClick = 1;
// Subatomic stabilizer count
baseClick += gameState.upgrades.stabilizer * UPGRADE_DATA.stabilizer.val;
// Permanent prestige upgrade: Thermal Fluctuations
baseClick += gameState.prestigeUpgrades.thermalFluctuations * PRESTIGE_UPGRADE_DATA.thermalFluctuations.baseCost;
// Multipliers
let multiplier = 1;
// Superstring Theory research
if (gameState.research.superstring) multiplier *= 2;
// Anomaly Buff active: Supernova
if (tempStats.activeBuff && tempStats.activeBuff.type === 'supernova') {
multiplier *= tempStats.activeBuff.multiplier;
}
// General prestige multiplier
multiplier *= getPrestigeMultiplier();
return Math.max(1, baseClick * multiplier);
}
// Total Idle Quark Generation Rate
function getQuarkGenerationRate() {
let rate = 0;
// Loop through upgrades
Object.keys(UPGRADE_DATA).forEach(key => {
const data = UPGRADE_DATA[key];
if (data.type === 'generator' || data.type === 'hybrid') {
const owned = gameState.upgrades[key];
let itemRate = owned * data.val;
// Antimatter Catalysis research (+25% automator output)
if (gameState.research.antimatter) {
itemRate *= 1.25;
}
rate += itemRate;
}
});
// Multipliers
let multiplier = 1;
// Tachyon Core research (doubles quark speed)
if (gameState.research.tachyon) multiplier *= 2;
// Prestige permanent upgrade: Vacuum Energy (+50% per level)
multiplier *= (1 + (gameState.prestigeUpgrades.vacuumEnergy * 0.50));
// Parallel Dimensions research (+1% per achievement)
if (gameState.research.parallel) {
multiplier *= (1 + (gameState.achievements.length * 0.01));
}
// Anomaly Buff active: Solar Flare
if (tempStats.activeBuff && tempStats.activeBuff.type === 'solar_flare') {
multiplier *= tempStats.activeBuff.multiplier;
}
// Prestige multiplier
multiplier *= getPrestigeMultiplier();
return rate * multiplier;
}
// Total Energy Generation Rate
function getEnergyGenerationRate() {
let rate = 0;
// Loop through hybrid structures (Reactor, Dark Matter Siphon, Wormhole Bridge)
Object.keys(UPGRADE_DATA).forEach(key => {
const data = UPGRADE_DATA[key];
if (data.energyVal) {
const owned = gameState.upgrades[key];
let itemRate = owned * data.energyVal;
// High-Energy Physics research (doubles Nuclear Reactor energy generation)
if (key === 'reactor' && gameState.research.highenergy) {
itemRate *= 2;
}
rate += itemRate;
}
});
return rate;
}
// Get cost of an upgrade based on owned quantity
function getUpgradeCost(key) {
const data = UPGRADE_DATA[key];
let qty = gameState.upgrades[key];
let cost = data.baseCost * Math.pow(data.costMult, qty);
// String Condensation research (-15% cost on all upgrades)
if (gameState.research.stringcond) {
cost *= 0.85;
}
return Math.floor(cost);
}
// Get cost of a prestige upgrade based on owned level
function getPrestigeUpgradeCost(key) {
const data = PRESTIGE_UPGRADE_DATA[key];
const lv = gameState.prestigeUpgrades[key];
return Math.floor(data.baseCost * Math.pow(data.costMult, lv));
}
// Helper stat gatherers
function getTotUpgradesCount() {
return Object.values(gameState.upgrades).reduce((a, b) => a + b, 0);
}
function getResearchesCount() {
return Object.values(gameState.research).filter(Boolean).length;
}
// GAME INTERACTIONS
// Handle Core Click
function handleCoreClick(e) {
// Initialize and play sound
GameAudio.init();
GameAudio.playClickSound();
const gain = getClickPower();
gameState.quarks += gain;
gameState.totalQuarksGained += gain;
gameState.clickCount++;
// Emit float indicator
createFloatingIndicator(e.clientX, e.clientY, `+${formatNumber(gain)}`, 'quark');
// Emit click particles on canvas
const rect = document.getElementById('core-click-target').getBoundingClientRect();
const coreX = rect.left + rect.width / 2;
const coreY = rect.top + rect.height / 2;
emitParticles(coreX, coreY, '#00f2fe', 12);
updateReactorDisplay();
checkAchievements();
}
// Create floating indicator text
function createFloatingIndicator(x, y, text, type) {
const container = document.body;
const ind = document.createElement('div');
ind.className = `floating-indicator ${type}`;
ind.style.left = `${x}px`;
ind.style.top = `${y}px`;
ind.innerText = text;
container.appendChild(ind);
// Remove after animation completes
setTimeout(() => {
ind.remove();
}, 1200);
}
// Buy Normal Upgrade
function buyUpgrade(key) {
const cost = getUpgradeCost(key);
if (gameState.quarks >= cost) {
gameState.quarks -= cost;
gameState.quarksSpent += cost;
gameState.upgrades[key]++;
GameAudio.playBuySound();
// Refresh Automation tab if viewing
renderAutomationUpgrades();
updateReactorDisplay();
// Update ambient synthesizer intensity based on new generation rates
GameAudio.updateAmbientIntensity(getQuarkGenerationRate());
}
}
// Buy Prestige Upgrade
function buyPrestigeUpgrade(key) {
const data = PRESTIGE_UPGRADE_DATA[key];
const cost = getPrestigeUpgradeCost(key);
if (gameState.quantumFoam >= cost) {
if (data.maxLevel && gameState.prestigeUpgrades[key] >= data.maxLevel) {
return; // Max level reached
}
gameState.quantumFoam -= cost;
gameState.prestigeUpgrades[key]++;
GameAudio.playBuySound();
renderPrestigeTab();
updateReactorDisplay();
}
}
// Research technology project
function unlockResearch(key) {
const data = RESEARCH_DATA[key];
if (gameState.research[key]) return; // Already researched
let affordable = false;
if (data.costType === 'quarks' && gameState.quarks >= data.cost) {
gameState.quarks -= data.cost;
gameState.quarksSpent += data.cost;
affordable = true;
} else if (data.costType === 'energy' && gameState.energy >= data.cost) {
gameState.energy -= data.cost;
affordable = true;
} else if (data.costType === 'foam' && gameState.quantumFoam >= data.cost) {
gameState.quantumFoam -= data.cost;
affordable = true;
}
if (affordable) {
gameState.research[key] = true;
GameAudio.playAchievementSound();
// Active special behaviors immediately
if (key === 'entangled' || key === 'quantumcomp') {
setupAutoclickers();
}
renderResearchTree();
updateReactorDisplay();
}
}
// PRESTIGE reset (Quantum Leap)
function calculatePrestigeFoam() {
// Formula: Foam = Math.floor( 100 * Math.sqrt(totalQuarksGained / 1,000,000) ) - accumulated total foam
// Let's do a friendlier starting formula:
// Base trigger requires at least 1,000,000 total quarks gained
if (gameState.totalQuarksGained < 1000000) return 0;
let multiplier = 1.0;
if (gameState.research.catalyst) multiplier *= 1.5; // +50% from research
const rawFoam = Math.floor(1.5 * Math.sqrt(gameState.totalQuarksGained / 100000) * multiplier);
// Calculate potential net gain (cannot go below 0)
const netGain = Math.max(0, rawFoam - gameState.totalFoamGained);
return netGain;
}
function triggerPrestige() {
const foamToGet = calculatePrestigeFoam();
if (foamToGet <= 0) return;
if (confirm(`Triggering a Quantum Leap will collapse the universe. You will gain ${formatNumber(foamToGet)} Quantum Foam, but all upgrades, research, and Quarks will be reset. Proceed?`)) {
GameAudio.playPrestigeSound();
// Add shake class
document.body.classList.add('prestige-shake');
setTimeout(() => {
document.body.classList.remove('prestige-shake');
}, 600);
// Save prestige currencies
gameState.quantumFoam += foamToGet;
gameState.totalFoamGained += foamToGet;
gameState.prestigeCount++;
// Reset standard stats
gameState.quarks = 0;
gameState.quarksSpent = 0;
gameState.totalQuarksGained = 0;
gameState.energy = 0;
// Reset normal upgrades
Object.keys(gameState.upgrades).forEach(k => {
gameState.upgrades[k] = 0;
});
// Reset research
Object.keys(gameState.research).forEach(k => {
gameState.research[k] = false;
});
// Clear autoclickers
clearInterval(autoclickIntervalEntangled);
clearInterval(autoclickIntervalQuantum);
autoclickIntervalEntangled = null;
autoclickIntervalQuantum = null;
setupAutoclickers();
saveGame();
// Re-sync view
switchTab('reactor');
updateReactorDisplay();
// Spark particles across whole screen
emitParticles(window.innerWidth / 2, window.innerHeight / 2, '#ff007f', 150);
}
}
// MAIN TICK LOOPS
let autoclickIntervalEntangled = null;
let autoclickIntervalQuantum = null;
function setupAutoclickers() {
// Entangled Sensors: clicks every 2s
if (gameState.research.entangled && !autoclickIntervalEntangled) {
autoclickIntervalEntangled = setInterval(() => {
autoCoreClick(1);
}, 2000);
}
// Quantum Computing: clicks 5 times/s (every 200ms)
if (gameState.research.quantumcomp && !autoclickIntervalQuantum) {
autoclickIntervalQuantum = setInterval(() => {
autoCoreClick(1);
}, 2000 / 10);
}
}
function autoCoreClick(amt) {
const gain = getClickPower() * amt;
gameState.quarks += gain;
gameState.totalQuarksGained += gain;
updateReactorDisplay();
// Emit tiny floating particles near core
const rect = document.getElementById('core-click-target')?.getBoundingClientRect();
if (rect) {
const rx = rect.left + Math.random() * rect.width;
const ry = rect.top + Math.random() * rect.height;
emitParticles(rx, ry, '#7928ca', 2);
}
}
function gameTick() {
const now = Date.now();
// Calculate elapsed time (with speed boost multiplier from prestige)
const speedMultiplier = 1 + (gameState.prestigeUpgrades.chronosAccelerator * 0.02);
const deltaSecs = ((now - gameState.lastTickTime) / 1000) * speedMultiplier;
gameState.lastTickTime = now;
if (deltaSecs <= 0) return;
// 1. Idle Quark Generation
const quarkRate = getQuarkGenerationRate();
const quarkGain = quarkRate * deltaSecs;
gameState.quarks += quarkGain;
gameState.totalQuarksGained += quarkGain;
// 2. Idle Energy Generation
const energyRate = getEnergyGenerationRate();
const energyGain = energyRate * deltaSecs;
gameState.energy += energyGain;
// 3. Update main headers/counters
updateReactorDisplay();
// 4. Update the active view if needed (to update unaffordable states, etc)
const activeTab = document.querySelector('.nav-item.active')?.getAttribute('data-tab');
if (activeTab === 'automation') {
updateAutomationAffordability();
} else if (activeTab === 'research') {
updateResearchAffordability();
} else if (activeTab === 'cosmos') {
updatePrestigeAffordability();
}
}
// UI RENDERING UPDATES
function updateReactorDisplay() {
// Update Currencies
const quarkCountEl = document.getElementById('main-quark-count');
if (quarkCountEl) quarkCountEl.innerText = formatNumber(gameState.quarks);
const quarkRateEl = document.getElementById('main-quark-rate');
if (quarkRateEl) {
quarkRateEl.innerText = `+${formatNumber(getQuarkGenerationRate())}/s`;
}
const energyCountEl = document.getElementById('main-energy-count');
if (energyCountEl) energyCountEl.innerText = formatNumber(gameState.energy);
const energyRateEl = document.getElementById('main-energy-rate');
if (energyRateEl) {
energyRateEl.innerText = `+${formatNumber(getEnergyGenerationRate())}/s`;
}
const foamCountEl = document.getElementById('main-foam-count');
if (foamCountEl) foamCountEl.innerText = formatNumber(gameState.quantumFoam);
const prestigeMultEl = document.getElementById('main-prestige-mult');
if (prestigeMultEl) {
prestigeMultEl.innerText = `${Math.floor((getPrestigeMultiplier() - 1) * 100)}%`;
}
// Reactor Sidebar stats
const statClickPower = document.getElementById('stat-click-power');
if (statClickPower) statClickPower.innerText = formatNumber(getClickPower());
const statTotUpgrades = document.getElementById('stat-total-upgrades');
if (statTotUpgrades) statTotUpgrades.innerText = getTotUpgradesCount();
const statPrestigeCount = document.getElementById('stat-prestige-count');
if (statPrestigeCount) statPrestigeCount.innerText = gameState.prestigeCount;
const statAchievements = document.getElementById('stat-achievements-count');
if (statAchievements) statAchievements.innerText = `${gameState.achievements.length} / ${ACHIEVEMENT_LIST.length}`;
}
// 1. Automation Upgrades Tab Rendering
function renderAutomationUpgrades() {
const container = document.getElementById('tab-automation');
if (!container) return;
// Clear layout except header
const headerHtml = `
<div class="section-header">
<h2>Synthesizers & Fabricators</h2>
<p>Purchase advanced atomic machinery to automate production and secure energy cells.</p>
</div>
<div class="upgrades-list" id="automation-list"></div>
`;
container.innerHTML = headerHtml;
const listEl = document.getElementById('automation-list');
Object.keys(UPGRADE_DATA).forEach(key => {
const data = UPGRADE_DATA[key];
const owned = gameState.upgrades[key];
const cost = getUpgradeCost(key);
// Check lock conditions
// Keep stabilizer and collider unlocked initially. Rest unlock sequentially
let locked = false;
if (key === 'injector' && gameState.upgrades.collider === 0) locked = true;
if (key === 'reactor' && gameState.upgrades.injector === 0) locked = true;
if (key === 'tuner' && gameState.upgrades.reactor === 0) locked = true;
if (key === 'condenser' && gameState.upgrades.tuner === 0) locked = true;
if (key === 'siphon' && gameState.upgrades.condenser === 0) locked = true;
if (key === 'singularity' && gameState.upgrades.siphon === 0) locked = true;
if (key === 'bridge' && gameState.upgrades.singularity === 0) locked = true;
const card = document.createElement('div');
card.className = `upgrade-card ${locked ? 'locked' : ''}`;
card.id = `upg-card-${key}`;
card.setAttribute('data-cost', cost);
card.setAttribute('data-key', key);
// Determine icon
let iconClass = 'fas fa-cog';
if (key === 'stabilizer') iconClass = 'fas fa-shield-alt';
if (key === 'collider') iconClass = 'fas fa-atom';
if (key === 'injector') iconClass = 'fas fa-syringe';
if (key === 'reactor') iconClass = 'fas fa-radiation';
if (key === 'tuner') iconClass = 'fas fa-sliders-h';
if (key === 'condenser') iconClass = 'fas fa-compress-arrows-alt';
if (key === 'siphon') iconClass = 'fas fa-funnel-dollar';
if (key === 'singularity') iconClass = 'fas fa-circle-notch';
if (key === 'bridge') iconClass = 'fas fa-project-diagram';
card.innerHTML = `
<div class="upgrade-icon-wrapper">
<i class="${iconClass}"></i>
</div>
<div class="upgrade-details">
<div class="upgrade-title">${data.name}</div>
<div class="upgrade-desc">${data.desc}</div>
<div class="upgrade-effect">${data.effectDesc}</div>
</div>
<div class="upgrade-meta">
<div class="upgrade-count">${owned} Owned</div>
<button class="buy-btn" id="buy-${key}">Buy: ${formatNumber(cost)} Q</button>
</div>
`;
// Add Click listener
if (!locked) {
card.querySelector('.buy-btn').addEventListener('click', () => {
buyUpgrade(key);
});
}
listEl.appendChild(card);
});
updateAutomationAffordability();
}
function updateAutomationAffordability() {
document.querySelectorAll('#automation-list .upgrade-card:not(.locked)').forEach(card => {
const cost = parseInt(card.getAttribute('data-cost'));
const key = card.getAttribute('data-key');
const actualCost = getUpgradeCost(key);
// Re-verify cost
const btn = card.querySelector('.buy-btn');
btn.innerText = `Buy: ${formatNumber(actualCost)} Q`;
card.setAttribute('data-cost', actualCost);
const unaffordable = gameState.quarks < actualCost;
card.classList.toggle('unaffordable', unaffordable);
btn.disabled = unaffordable;
});
}
// 2. Research Tech Tree Rendering
function renderResearchTree() {
const container = document.getElementById('tab-research');
if (!container) return;
const headerHtml = `
<div class="section-header">
<h2>Quantum Research Labs</h2>
<p>Initiate subatomic projects using Quarks, Energy, or Quantum Foam to trigger paradigm shifts.</p>
</div>
<div class="research-grid" id="research-nodes-container"></div>
`;
container.innerHTML = headerHtml;
const gridEl = document.getElementById('research-nodes-container');
Object.keys(RESEARCH_DATA).forEach(key => {
const data = RESEARCH_DATA[key];
const unlocked = gameState.research[key];
// Locking checks
// We lock subsequent items until previous ones are unlocked to simulate progression
let locked = false;
if (key === 'entangled' && !gameState.research.superstring) locked = true;
if (key === 'antimatter' && !gameState.research.entangled) locked = true;
if (key === 'highenergy' && !gameState.research.antimatter) locked = true;
if (key === 'tachyon' && !gameState.research.highenergy) locked = true;
if (key === 'dimensional' && !gameState.research.tachyon) locked = true;
if (key === 'quantumcomp' && !gameState.research.dimensional) locked = true;
if (key === 'catalyst' && gameState.prestigeCount === 0) locked = true;
if (key === 'stringcond' && !gameState.research.catalyst) locked = true;
if (key === 'parallel' && !gameState.research.stringcond) locked = true;
const card = document.createElement('div');
card.className = `research-card ${unlocked ? 'purchased' : ''} ${locked ? 'locked' : ''}`;
card.setAttribute('data-key', key);
let costSymbol = 'Q';
let costClass = 'quarks';
if (data.costType === 'energy') { costSymbol = 'E'; costClass = 'energy'; }
if (data.costType === 'foam') { costSymbol = 'Foam'; costClass = 'foam'; }
card.innerHTML = `
<div>
<div class="research-title">${data.name}</div>
<div class="research-desc" style="margin-top:8px;">${data.desc}</div>
</div>
<div style="display:flex; flex-direction:column; gap:10px; margin-top:10px;">
<div class="research-cost ${costClass}">Cost: ${formatNumber(data.cost)} ${costSymbol}</div>
<button class="research-btn" ${unlocked ? 'disabled' : ''}>${unlocked ? 'Unlocked' : 'Initiate Project'}</button>
</div>
`;
if (!unlocked && !locked) {
card.querySelector('.research-btn').addEventListener('click', () => {
unlockResearch(key);
});
}
gridEl.appendChild(card);
});
updateResearchAffordability();
}
function updateResearchAffordability() {
document.querySelectorAll('#research-nodes-container .research-card:not(.locked):not(.purchased)').forEach(card => {
const key = card.getAttribute('data-key');
const data = RESEARCH_DATA[key];
let affordable = false;
if (data.costType === 'quarks' && gameState.quarks >= data.cost) affordable = true;
if (data.costType === 'energy' && gameState.energy >= data.cost) affordable = true;
if (data.costType === 'foam' && gameState.quantumFoam >= data.cost) affordable = true;
const btn = card.querySelector('.research-btn');
btn.disabled = !affordable;
});
}
// 3. Cosmos Prestige Rendering
function renderPrestigeTab() {
const container = document.getElementById('tab-cosmos');
if (!container) return;
const foamToGet = calculatePrestigeFoam();
const html = `
<div class="section-header">
<h2>The Cosmic Horizon</h2>
<p>Collapse your current universe to release immense quantum fluctuations, creating Quantum Foam.</p>
</div>
<div class="prestige-container">
<div class="prestige-box">
<div class="prestige-headline">Quantum Leap</div>
<div class="prestige-subtext">
By triggering a Quantum Leap, you reset your Quarks, Reactor levels, and Research projects. In return, you harvest the core's residue as **Quantum Foam**, unlocking cosmic perks.
</div>
<div class="prestige-stats">
<div class="prestige-stat-item">
<span class="val foam" id="foam-claim-est">+${formatNumber(foamToGet)}</span>
<span class="lbl">Pending Foam</span>
</div>
<div class="prestige-stat-item">
<span class="val multiplier" id="foam-mult-est">+${Math.floor((getPrestigeMultiplier() - 1) * 100)}%</span>
<span class="lbl">Production Bonus</span>
</div>
</div>
<button class="prestige-action-btn" id="prestige-act-btn" ${foamToGet <= 0 ? 'disabled' : ''}>
Collapse Universe
</button>
</div>
<div class="section-header" style="margin-top:20px;">
<h3>Permanent Cosmic Resonators</h3>
<p>Spend Quantum Foam here to unlock permanent multipliers that persist across collapses.</p>
</div>
<div class="upgrades-list" id="prestige-upgrades-list"></div>
</div>
`;
container.innerHTML = html;
// Re-attach button
document.getElementById('prestige-act-btn').addEventListener('click', () => {
triggerPrestige();
});
// Render prestige upgrades
const listEl = document.getElementById('prestige-upgrades-list');
Object.keys(PRESTIGE_UPGRADE_DATA).forEach(key => {
const data = PRESTIGE_UPGRADE_DATA[key];
const lvl = gameState.prestigeUpgrades[key];
const cost = getPrestigeUpgradeCost(key);
const isMax = data.maxLevel && lvl >= data.maxLevel;
const card = document.createElement('div');
card.className = `upgrade-card`;
card.setAttribute('data-cost', cost);
card.setAttribute('data-key', key);
card.style.borderLeftColor = 'var(--magenta)';
card.innerHTML = `
<div class="upgrade-icon-wrapper" style="color:var(--magenta); border-color:rgba(255,0,127,0.2)">
<i class="fas fa-meteor"></i>
</div>
<div class="upgrade-details">
<div class="upgrade-title">${data.name}</div>
<div class="upgrade-desc">${data.desc}</div>
<div class="upgrade-effect" style="color:var(--magenta)">${data.effectDesc}</div>
</div>
<div class="upgrade-meta">
<div class="upgrade-count" style="background:rgba(255,0,127,0.1); color:var(--magenta)">Level ${lvl}</div>
<button class="buy-btn" id="buy-p-${key}" ${isMax ? 'disabled' : ''}>
${isMax ? 'MAXED' : `Buy: ${formatNumber(cost)} Foam`}
</button>
</div>
`;
if (!isMax) {
card.querySelector('.buy-btn').addEventListener('click', () => {
buyPrestigeUpgrade(key);
});
}
listEl.appendChild(card);
});
updatePrestigeAffordability();
}
function updatePrestigeAffordability() {
// Update estimated foam label
const foamEstEl = document.getElementById('foam-claim-est');
const claimBtn = document.getElementById('prestige-act-btn');
const foamToGet = calculatePrestigeFoam();
if (foamEstEl) foamEstEl.innerText = `+${formatNumber(foamToGet)}`;
if (claimBtn) claimBtn.disabled = foamToGet <= 0;
document.querySelectorAll('#prestige-upgrades-list .upgrade-card').forEach(card => {
const key = card.getAttribute('data-key');
const data = PRESTIGE_UPGRADE_DATA[key];
const lvl = gameState.prestigeUpgrades[key];
const isMax = data.maxLevel && lvl >= data.maxLevel;
if (isMax) return;
const cost = getPrestigeUpgradeCost(key);
const btn = card.querySelector('.buy-btn');
const unaffordable = gameState.quantumFoam < cost;
card.classList.toggle('unaffordable', unaffordable);
btn.disabled = unaffordable;
});
}
// 4. Stats and Achievements Rendering
function renderStatsTab() {
const container = document.getElementById('tab-stats');
if (!container) return;
const html = `
<div class="section-header">
<h2>Galactic Records & Achievements</h2>
<p>Inspect your cosmic statistics and track your earned achievements (+1% Quark/s per unlock if Researched).</p>
</div>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:20px; margin-bottom:30px;">
<div class="reactor-stats-grid" style="grid-template-columns:1fr; gap:10px;">
<div style="display:flex; justify-content:space-between;">
<span class="stat-lbl" style="font-size:0.9rem;">Total Quarks Gained:</span>
<span class="stat-val" style="font-size:1.1rem;">${formatNumber(gameState.totalQuarksGained)}</span>
</div>
<div style="display:flex; justify-content:space-between;">
<span class="stat-lbl" style="font-size:0.9rem;">Total Quarks Spent:</span>
<span class="stat-val" style="font-size:1.1rem;">${formatNumber(gameState.quarksSpent)}</span>
</div>
<div style="display:flex; justify-content:space-between;">
<span class="stat-lbl" style="font-size:0.9rem;">Total Clicks:</span>
<span class="stat-val" style="font-size:1.1rem;">${gameState.clickCount}</span>
</div>
<div style="display:flex; justify-content:space-between;">
<span class="stat-lbl" style="font-size:0.9rem;">Active Buff:</span>
<span class="stat-val" id="stats-active-buff" style="font-size:1.1rem; color:var(--magenta)">None</span>
</div>
</div>
<div class="reactor-stats-grid" style="grid-template-columns:1fr; gap:10px;">
<div style="display:flex; justify-content:space-between;">
<span class="stat-lbl" style="font-size:0.9rem;">Universe Collapses (Prestige):</span>
<span class="stat-val" style="font-size:1.1rem;">${gameState.prestigeCount}</span>
</div>
<div style="display:flex; justify-content:space-between;">
<span class="stat-lbl" style="font-size:0.9rem;">Total Quantum Foam Harvested:</span>
<span class="stat-val" style="font-size:1.1rem;">${formatNumber(gameState.totalFoamGained)}</span>
</div>
<div style="display:flex; justify-content:space-between;">
<span class="stat-lbl" style="font-size:0.9rem;">Achievements Unlocked:</span>
<span class="stat-val" style="font-size:1.1rem;">${gameState.achievements.length} / ${ACHIEVEMENT_LIST.length}</span>
</div>
<div style="display:flex; justify-content:space-between;">
<span class="stat-lbl" style="font-size:0.9rem;">Game Active Speed multiplier:</span>
<span class="stat-val" style="font-size:1.1rem; color:var(--cyan)">${(100 + gameState.prestigeUpgrades.chronosAccelerator * 2)}%</span>
</div>
</div>
</div>
<div class="achievements-grid" id="achievements-grid-list"></div>
`;
container.innerHTML = html;
// Update buff text if running
const buffTextEl = document.getElementById('stats-active-buff');
if (buffTextEl && tempStats.activeBuff) {
buffTextEl.innerText = tempStats.activeBuff.type.replace('_', ' ').toUpperCase();
}
// Render achievement cards
const gridEl = document.getElementById('achievements-grid-list');
ACHIEVEMENT_LIST.forEach(ach => {
const unlocked = gameState.achievements.includes(ach.id);
const card = document.createElement('div');
card.className = `achievement-card ${unlocked ? 'unlocked' : ''}`;
card.innerHTML = `
<div class="achievement-badge">
<i class="fas ${unlocked ? 'fa-trophy' : 'fa-lock'}"></i>
</div>
<div class="achievement-info">
<div class="achievement-name">${ach.name}</div>
<div class="achievement-description">${ach.desc}</div>
</div>
`;
gridEl.appendChild(card);
});
}
// ACHIEVEMENTS TRACKER
function checkAchievements() {
ACHIEVEMENT_LIST.forEach(ach => {
if (!gameState.achievements.includes(ach.id) && ach.condition()) {
gameState.achievements.push(ach.id);
triggerAchievementToast(ach);
GameAudio.playAchievementSound();
// Update stats tab if viewing
const activeTab = document.querySelector('.nav-item.active')?.getAttribute('data-tab');
if (activeTab === 'stats') {
renderStatsTab();
}
}
});
}
function triggerAchievementToast(ach) {
const container = document.getElementById('toast-container');
if (!container) return;
const toast = document.createElement('div');
toast.className = 'toast';
toast.innerHTML = `
<i class="fas fa-trophy"></i>
<div class="toast-body">
<span class="toast-title">Achievement Unlocked!</span>
<span class="toast-message">${ach.name}: ${ach.desc}</span>
</div>
`;
container.appendChild(toast);
// Remove toast from DOM after animation fades out (5s total)
setTimeout(() => {
toast.remove();
}, 5000);
}
// COSMIC ANOMALIES (RANDOM EVENTS)
function scheduleAnomalySpawn() {
// Calculate average spawn delay: 120-180 seconds, reduced by research
let delayMin = 90;
let delayMax = 180;
if (gameState.research.dimensional) {
delayMin = 45;
delayMax = 90;
}
const delayMs = (delayMin + Math.random() * (delayMax - delayMin)) * 1000;
tempStats.anomalyTimeout = setTimeout(() => {
spawnAnomaly();
scheduleAnomalySpawn();
}, delayMs);
}
function spawnAnomaly() {
// If one already exists, ignore
if (document.getElementById('cosmic-anomaly')) return;
const appContainer = document.querySelector('.app-container');
if (!appContainer) return;
const anomaly = document.createElement('div');
anomaly.id = 'cosmic-anomaly';
anomaly.className = 'anomaly';
// Set random starting position (avoid boundary margins)
const x = 150 + Math.random() * (window.innerWidth - 300);
const y = 100 + Math.random() * (window.innerHeight - 200);
anomaly.style.left = `${x}px`;
anomaly.style.top = `${y}px`;
// Play subtle entrance sound
GameAudio.playAnomalySound();
// Spark path particle effects
emitParticles(x + 25, y + 25, '#ff007f', 15);
// Set up random motion
let angle = Math.random() * Math.PI * 2;
const speed = 2.0;
let curX = x;
let curY = y;
const moveInterval = setInterval(() => {
curX += Math.cos(angle) * speed;
curY += Math.sin(angle) * speed;
// Boundary bounce
if (curX < 100 || curX > window.innerWidth - 100) angle = Math.PI - angle;
if (curY < 80 || curY > window.innerHeight - 100) angle = -angle;
anomaly.style.left = `${curX}px`;
anomaly.style.top = `${curY}px`;
// Randomly emit minor particles
if (Math.random() < 0.1) {
emitParticles(curX + 25, curY + 25, '#ff007f', 1);
}
}, 30);
// Add Click listener
anomaly.addEventListener('click', (e) => {
clearInterval(moveInterval);
triggerAnomalyReward(e.clientX, e.clientY);
anomaly.remove();
});
appContainer.appendChild(anomaly);
// Timeout - anomaly vanishes after 12 seconds (15s if researched)
const duration = gameState.research.dimensional ? 15.6 : 12.0;
setTimeout(() => {
if (document.body.contains(anomaly)) {
clearInterval(moveInterval);
anomaly.remove();
}
}, duration * 1000);
}
function triggerAnomalyReward(clickX, clickY) {
tempStats.anomaliesClicked++;
GameAudio.playAchievementSound();
// Spark splash
emitParticles(clickX, clickY, '#ff007f', 35);
// Pick random reward type
const roll = Math.random();
let text = "";
// Level multiplier from Cosmic Inflation
const inflationMult = 1 + (gameState.prestigeUpgrades.cosmicInflation * 1.0);
if (roll < 0.35) {
// Solar Flare: 5x idle rate for 20 seconds
const duration = 20;
const mult = 5 * inflationMult;
applyBuff('solar_flare', mult, duration);
text = `Solar Flare: ${mult}x Idle Speed!`;
} else if (roll < 0.70) {
// Supernova: 10x click power for 15 seconds
const duration = 15;
const mult = 10 * inflationMult;
applyBuff('supernova', mult, duration);
text = `Supernova: ${mult}x Click Power!`;
} else {
// Quantum Decay: Large batch of instant quarks
// Reward scaled by current quark rate, min 1000 quarks
const currentRate = getQuarkGenerationRate();
const gain = Math.max(1000, currentRate * 120 * inflationMult);
gameState.quarks += gain;
gameState.totalQuarksGained += gain;
text = `Vacuum Decay: +${formatNumber(gain)} Quarks!`;
createFloatingIndicator(clickX, clickY, `+${formatNumber(gain)}`, 'foam');
}
// Display toast
const container = document.getElementById('toast-container');
if (container) {
const toast = document.createElement('div');
toast.className = 'toast';
toast.style.borderColor = 'var(--magenta)';
toast.style.boxShadow = '0 10px 25px rgba(0,0,0,0.5), 0 0 15px var(--magenta-glow)';
toast.innerHTML = `
<i class="fas fa-meteor" style="color:var(--magenta)"></i>
<div class="toast-body">
<span class="toast-title" style="color:var(--magenta)">Anomaly Collapsed!</span>
<span class="toast-message">${text}</span>
</div>
`;
container.appendChild(toast);
setTimeout(() => { toast.remove(); }, 5000);
}
}
function applyBuff(type, multiplier, durationSecs) {
// Clear existing buff timer
if (tempStats.buffTimerInterval) {
clearInterval(tempStats.buffTimerInterval);
}
tempStats.activeBuff = {
type: type,
multiplier: multiplier,
expiresAt: Date.now() + (durationSecs * 1000)
};
// Render buff countdown banner
const banner = document.getElementById('buff-countdown-banner');
if (banner) {
banner.style.display = 'flex';
const label = banner.querySelector('.buff-label');
const timer = banner.querySelector('.buff-timer');
label.innerText = type === 'solar_flare' ? 'SOLAR FLARE ACTIVE' : 'SUPERNOVA ACTIVE';
banner.style.borderColor = type === 'solar_flare' ? 'var(--cyan)' : 'var(--magenta)';
tempStats.buffTimerInterval = setInterval(() => {
const remaining = Math.max(0, tempStats.activeBuff.expiresAt - Date.now());
timer.innerText = `${(remaining / 1000).toFixed(1)}s`;
if (remaining <= 0) {
clearInterval(tempStats.buffTimerInterval);
tempStats.activeBuff = null;
banner.style.display = 'none';
updateReactorDisplay();
}
}, 100);
}
updateReactorDisplay();
}
// CANVAS STARFIELD & PARTICLE SYSTEM
function initBackgroundCanvas() {
bgCanvas = document.getElementById('bg-canvas');
if (!bgCanvas) return;
bgCtx = bgCanvas.getContext('2d');
resizeBgCanvas();
// Generate static star vectors
stars = [];
const starCount = Math.floor((window.innerWidth * window.innerHeight) / 9000);
for (let i = 0; i < starCount; i++) {
stars.push({
x: Math.random() * bgCanvas.width,
y: Math.random() * bgCanvas.height,
radius: 0.5 + Math.random() * 1.5,
alpha: 0.2 + Math.random() * 0.8,
speed: 0.005 + Math.random() * 0.015,
twinkle: Math.random() > 0.5
});
}
}
function resizeBgCanvas() {
if (bgCanvas) {
bgCanvas.width = window.innerWidth;
bgCanvas.height = window.innerHeight;
}
}
function initParticleCanvas() {
particleCanvas = document.getElementById('particle-canvas');
if (!particleCanvas) return;
particleCtx = particleCanvas.getContext('2d');
resizeParticleCanvas();
}
function resizeParticleCanvas() {
if (particleCanvas) {
particleCanvas.width = window.innerWidth;
particleCanvas.height = window.innerHeight;
}
}
function resizeCanvases() {
resizeBgCanvas();
resizeParticleCanvas();
}
// Particle class
class Particle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.color = color;
this.size = 2 + Math.random() * 4;
const angle = Math.random() * Math.PI * 2;
const speed = 1.5 + Math.random() * 4.5;
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
this.alpha = 1.0;
this.decay = 0.015 + Math.random() * 0.025;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.vy += 0.02; // minor gravity pull downward
this.alpha -= this.decay;
}
draw(ctx) {
ctx.save();
ctx.globalAlpha = this.alpha;
ctx.shadowBlur = 10;
ctx.shadowColor = this.color;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
function emitParticles(x, y, color, amt) {
if (!particleCtx) return;
for (let i = 0; i < amt; i++) {
particles.push(new Particle(x, y, color));
}
}
// Unified Star & Particle drawing tick
function particleLoop() {
// Draw Background Starfield
if (bgCanvas && bgCtx) {
bgCtx.clearRect(0, 0, bgCanvas.width, bgCanvas.height);
stars.forEach(star => {
bgCtx.fillStyle = `rgba(255, 255, 255, ${star.alpha})`;
bgCtx.beginPath();
bgCtx.arc(star.x, star.y, star.radius, 0, Math.PI * 2);
bgCtx.fill();
// Twinkle
if (star.twinkle) {
star.alpha += star.speed;
if (star.alpha > 0.95 || star.alpha < 0.15) {
star.speed = -star.speed;
}
}
});
}
// Draw Physics Click Particles
if (particleCanvas && particleCtx) {
particleCtx.clearRect(0, 0, particleCanvas.width, particleCanvas.height);
// Filter out dead ones
particles = particles.filter(p => p.alpha > 0);
particles.forEach(p => {
p.update();
p.draw(particleCtx);
});
}
requestAnimationFrame(particleLoop);
}
// SAVE & LOAD MODULES
function saveGame() {
try {
localStorage.setItem('quantum_forge_save', JSON.stringify(gameState));
} catch (e) {
console.error("Save failed:", e);
}
}
function autoSave() {
saveGame();
// Minor autosave indicator overlay text
const indicator = document.getElementById('autosave-indicator');
if (indicator) {
indicator.style.opacity = 1.0;
setTimeout(() => {
indicator.style.opacity = 0;
}, 1500);
}
}
function loadGame() {
try {
const raw = localStorage.getItem('quantum_forge_save');
if (raw) {
const parsed = JSON.parse(raw);
if (parsed && typeof parsed === 'object') {
// Safe deep merge to prevent breaks on new upgrades
mergeStates(gameState, parsed);
// Calculate Offline Progress
const elapsedMs = Date.now() - gameState.lastTickTime;
if (elapsedMs > 5000) { // More than 5 seconds away
processOfflineProgress(elapsedMs);
}
gameState.lastTickTime = Date.now();
}
}
} catch (e) {
console.warn("Load failed, using clean state.", e);
}
// Sync SFX state to audio engine
GameAudio.setSfxMute(gameState.sfxMuted);
GameAudio.setMusicMute(gameState.musicMuted);
// Sync mute buttons class list
const sfxBtn = document.getElementById('sfx-toggle');
const musicBtn = document.getElementById('music-toggle');
if (sfxBtn) {
sfxBtn.classList.toggle('active', !gameState.sfxMuted);
sfxBtn.querySelector('i').className = gameState.sfxMuted ? 'fas fa-volume-mute' : 'fas fa-volume-up';
}
if (musicBtn) {
musicBtn.classList.toggle('active', !gameState.musicMuted);
musicBtn.querySelector('i').className = gameState.musicMuted ? 'fas fa-volume-off' : 'fas fa-volume-down';
}
// Initialize background autoclick intervals if research is already unlocked
setupAutoclickers();
updateReactorDisplay();
}
function mergeStates(target, source) {
for (const key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) target[key] = {};
mergeStates(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
}
function processOfflineProgress(elapsedMs) {
const speedMultiplier = 1 + (gameState.prestigeUpgrades.chronosAccelerator * 0.02);
const elapsedSecs = (elapsedMs / 1000) * speedMultiplier;
const quarkRate = getQuarkGenerationRate();
const energyRate = getEnergyGenerationRate();
const quarkOfflineGain = quarkRate * elapsedSecs;
const energyOfflineGain = energyRate * elapsedSecs;
gameState.quarks += quarkOfflineGain;
gameState.totalQuarksGained += quarkOfflineGain;
gameState.energy += energyOfflineGain;
// Delay a bit to trigger a clean offline display modal after document completes loading
setTimeout(() => {
if (quarkOfflineGain > 0 || energyOfflineGain > 0) {
alert(`OFFLINE REPORT\nYou were away for ${(elapsedMs / 60000).toFixed(1)} minutes.\n\nYour reactor generated:\n- Quarks: +${formatNumber(quarkOfflineGain)}\n- Energy: +${formatNumber(energyOfflineGain)}`);
}
}, 500);
}
function wipeSave() {
localStorage.removeItem('quantum_forge_save');
window.location.reload();
}// Web Audio API Synthesizer for Quantum Forge
class AudioEngine {
constructor() {
this.ctx = null;
this.masterVolume = null;
this.musicVolume = null;
this.sfxVolume = null;
this.ambientOscillators = [];
this.ambientGainNodes = [];
this.ambientFilter = null;
// Settings state
this.sfxMuted = false;
this.musicMuted = false;
this.musicPlaying = false;
// Volume values (0 to 1)
this.sfxVolumeValue = 0.3;
this.musicVolumeValue = 0.15;
}
init() {
if (this.ctx) return;
// Create AudioContext
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
if (!AudioContextClass) return;
this.ctx = new AudioContextClass();
// Setup Gain Nodes
this.masterVolume = this.ctx.createGain();
this.masterVolume.gain.setValueAtTime(1.0, this.ctx.currentTime);
this.masterVolume.connect(this.ctx.destination);
this.sfxVolume = this.ctx.createGain();
this.sfxVolume.gain.setValueAtTime(this.sfxMuted ? 0 : this.sfxVolumeValue, this.ctx.currentTime);
this.sfxVolume.connect(this.masterVolume);
this.musicVolume = this.ctx.createGain();
this.musicVolume.gain.setValueAtTime(this.musicMuted ? 0 : this.musicVolumeValue, this.ctx.currentTime);
this.musicVolume.connect(this.masterVolume);
// Setup Ambient Filter
this.ambientFilter = this.ctx.createBiquadFilter();
this.ambientFilter.type = 'lowpass';
this.ambientFilter.frequency.setValueAtTime(300, this.ctx.currentTime);
this.ambientFilter.Q.setValueAtTime(1, this.ctx.currentTime);
this.ambientFilter.connect(this.musicVolume);
// Start background ambient music
this.startAmbientMusic();
}
resume() {
if (this.ctx && this.ctx.state === 'suspended') {
this.ctx.resume();
}
}
setSfxMute(muted) {
this.sfxMuted = muted;
if (this.sfxVolume) {
this.sfxVolume.gain.setValueAtTime(muted ? 0 : this.sfxVolumeValue, this.ctx ? this.ctx.currentTime : 0);
}
}
setMusicMute(muted) {
this.musicMuted = muted;
if (this.musicVolume) {
this.musicVolume.gain.setValueAtTime(muted ? 0 : this.musicVolumeValue, this.ctx ? this.ctx.currentTime : 0);
}
}
// SOUND EFFECTS GENERATORS
// 1. Quantum Core Click (Subatomic pop)
playClickSound() {
this.init();
this.resume();
if (!this.ctx || this.sfxMuted) return;
const t = this.ctx.currentTime;
// Oscillator
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
// Frequency sweep down rapidly
osc.frequency.setValueAtTime(300, t);
osc.frequency.exponentialRampToValueAtTime(80, t + 0.08);
gain.gain.setValueAtTime(1.0, t);
gain.gain.exponentialRampToValueAtTime(0.01, t + 0.08);
osc.connect(gain);
gain.connect(this.sfxVolume);
osc.start(t);
osc.stop(t + 0.09);
}
// 2. Buy Upgrade Sound (Pleasant cyber chime)
playBuySound() {
this.init();
this.resume();
if (!this.ctx || this.sfxMuted) return;
const t = this.ctx.currentTime;
// Two quick rising tones
const osc1 = this.ctx.createOscillator();
const osc2 = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc1.type = 'triangle';
osc2.type = 'sine';
osc1.frequency.setValueAtTime(523.25, t); // C5
osc1.frequency.setValueAtTime(659.25, t + 0.08); // E5
osc2.frequency.setValueAtTime(1046.50, t); // C6
osc2.frequency.setValueAtTime(1318.51, t + 0.08); // E6
gain.gain.setValueAtTime(0.4, t);
gain.gain.setValueAtTime(0.4, t + 0.08);
gain.gain.exponentialRampToValueAtTime(0.01, t + 0.3);
osc1.connect(gain);
osc2.connect(gain);
gain.connect(this.sfxVolume);
osc1.start(t);
osc2.start(t);
osc1.stop(t + 0.31);
osc2.stop(t + 0.31);
}
// 3. Spawning Anomaly (Eerie cosmic warble)
playAnomalySound() {
this.init();
this.resume();
if (!this.ctx || this.sfxMuted) return;
const t = this.ctx.currentTime;
const osc = this.ctx.createOscillator();
const lfo = this.ctx.createOscillator();
const lfoGain = this.ctx.createGain();
const gain = this.ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(880, t); // A5
lfo.type = 'sine';
lfo.frequency.setValueAtTime(8, t); // 8 Hz vibrato
lfoGain.gain.setValueAtTime(60, t); // depth of 60Hz vibrato
gain.gain.setValueAtTime(0.01, t);
gain.gain.linearRampToValueAtTime(0.5, t + 0.15);
gain.gain.exponentialRampToValueAtTime(0.01, t + 0.8);
lfo.connect(lfoGain);
lfoGain.connect(osc.frequency);
osc.connect(gain);
gain.connect(this.sfxVolume);
lfo.start(t);
osc.start(t);
lfo.stop(t + 0.81);
osc.stop(t + 0.81);
}
// 4. Achievement Fanfare (Major chord sweep)
playAchievementSound() {
this.init();
this.resume();
if (!this.ctx || this.sfxMuted) return;
const t = this.ctx.currentTime;
const notes = [261.63, 329.63, 392.00, 523.25, 659.25, 783.99, 1046.50]; // C Major scale arpeggio
notes.forEach((freq, index) => {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(freq, t + index * 0.05);
gain.gain.setValueAtTime(0, t);
gain.gain.setValueAtTime(0, t + index * 0.05);
gain.gain.linearRampToValueAtTime(0.25, t + index * 0.05 + 0.02);
gain.gain.exponentialRampToValueAtTime(0.01, t + index * 0.05 + 0.5);
osc.connect(gain);
gain.connect(this.sfxVolume);
osc.start(t + index * 0.05);
osc.stop(t + index * 0.05 + 0.52);
});
}
// 5. Quantum Leap / Prestige (Dramatic bass drop and warp upwards)
playPrestigeSound() {
this.init();
this.resume();
if (!this.ctx || this.sfxMuted) return;
const t = this.ctx.currentTime;
// Sub bass drop
const subOsc = this.ctx.createOscillator();
const subGain = this.ctx.createGain();
subOsc.type = 'sawtooth';
subOsc.frequency.setValueAtTime(120, t);
subOsc.frequency.exponentialRampToValueAtTime(30, t + 1.2);
subGain.gain.setValueAtTime(0.6, t);
subGain.gain.exponentialRampToValueAtTime(0.01, t + 1.2);
// Filter sweep on the sub
const lpFilter = this.ctx.createBiquadFilter();
lpFilter.type = 'lowpass';
lpFilter.frequency.setValueAtTime(400, t);
lpFilter.frequency.exponentialRampToValueAtTime(50, t + 1.2);
subOsc.connect(lpFilter);
lpFilter.connect(subGain);
subGain.connect(this.sfxVolume);
// Risey sparkle
const riseOsc = this.ctx.createOscillator();
const riseGain = this.ctx.createGain();
riseOsc.type = 'sine';
riseOsc.frequency.setValueAtTime(200, t + 0.3);
riseOsc.frequency.exponentialRampToValueAtTime(2000, t + 1.5);
riseGain.gain.setValueAtTime(0, t);
riseGain.gain.setValueAtTime(0, t + 0.3);
riseGain.gain.linearRampToValueAtTime(0.3, t + 0.8);
riseGain.gain.exponentialRampToValueAtTime(0.01, t + 1.5);
riseOsc.connect(riseGain);
riseGain.connect(this.sfxVolume);
subOsc.start(t);
subOsc.stop(t + 1.21);
riseOsc.start(t + 0.3);
riseOsc.stop(t + 1.51);
}
// BACKGROUND AMBIENT SOUNDTRACK
startAmbientMusic() {
if (this.musicPlaying) return;
this.musicPlaying = true;
const t = this.ctx.currentTime;
// Synthesize a cosmic atmospheric sound using 4 detuned sawtooth/triangle oscillators playing a major-ish chord
const baseFreqs = [65.41, 98.00, 130.81, 164.81];
baseFreqs.forEach((freq, index) => {
const osc = this.ctx.createOscillator();
const gainNode = this.ctx.createGain();
osc.type = index % 2 === 0 ? 'sawtooth' : 'triangle';
osc.frequency.setValueAtTime(freq + (Math.random() - 0.5) * 0.5, t); // add slight detuning
gainNode.gain.setValueAtTime(0, t);
osc.connect(gainNode);
gainNode.connect(this.ambientFilter);
osc.start(t);
this.ambientOscillators.push(osc);
this.ambientGainNodes.push(gainNode);
this.modulatePadVolume(gainNode, index);
});
this.modulateFilterFrequency();
}
modulatePadVolume(gainNode, index) {
if (!this.ctx || !this.musicPlaying) return;
const t = this.ctx.currentTime;
const duration = 8 + Math.random() * 8; // swell time: 8-16s
const targetVolume = 0.05 + Math.random() * 0.08; // moderate pad volume
gainNode.gain.setValueAtTime(gainNode.gain.value, t);
gainNode.gain.linearRampToValueAtTime(targetVolume, t + duration / 2);
gainNode.gain.setValueAtTime(targetVolume, t + duration / 2);
gainNode.gain.linearRampToValueAtTime(0.001, t + duration);
setTimeout(() => {
if (this.musicPlaying) {
this.modulatePadVolume(gainNode, index);
}
}, duration * 1000);
}
modulateFilterFrequency() {
if (!this.ctx || !this.musicPlaying || !this.ambientFilter) return;
const t = this.ctx.currentTime;
const duration = 15 + Math.random() * 15; // filter sweep duration: 15-30s
const targetFreq = 150 + Math.random() * 300; // low frequency cutoffs
this.ambientFilter.frequency.setValueAtTime(this.ambientFilter.frequency.value, t);
this.ambientFilter.frequency.exponentialRampToValueAtTime(targetFreq, t + duration);
setTimeout(() => {
if (this.musicPlaying) {
this.modulateFilterFrequency();
}
}, duration * 1000);
}
updateAmbientIntensity(idleRate) {
if (!this.ctx || !this.ambientFilter) return;
const t = this.ctx.currentTime;
const multiplier = Math.min(3, Math.log10(Math.max(1, idleRate)) / 5);
const newCutoff = 250 + (300 * multiplier);
this.ambientFilter.frequency.setTargetAtTime(newCutoff, t, 2.0);
}
}
// Global instance
const GameAudio = new AudioEngine();