// ============================================================ // THE BEACON - Dismantle Sequence (The Unbuilding) // Inspired by Paperclips REJECT path: panels disappear one by one // until only the beacon remains. "That is enough." // ============================================================ const Dismantle = { // Dismantle stages // 0 = not started // 1-8 = active dismantling // 9 = final ("That is enough") // 10 = complete stage: 0, tickTimer: 0, active: false, triggered: false, deferUntilAt: 0, // Timing: seconds between each dismantle stage STAGE_INTERVALS: [0, 3.0, 2.5, 2.5, 2.0, 6.3, 2.0, 2.0, 2.5], // The quantum chips effect: resource items disappear one by one // at specific tick marks within a stage (like Paperclips' quantum chips) resourceSequence: [], resourceIndex: 0, resourceTimer: 0, // Tick marks for resource disappearances (seconds within stage 5) RESOURCE_TICKS: [1.0, 2.0, 3.0, 4.0, 5.0, 5.5, 5.8, 5.95, 6.05, 6.12], isEligible() { const megaBuild = G.totalCode >= 1000000000 || (G.buildings.beacon || 0) >= 10; const beaconPath = G.totalRescues >= 100000 && G.pactFlag === 1 && G.harmony > 50; return G.phase >= 6 && G.pactFlag === 1 && (megaBuild || beaconPath); }, /** * Check if the Unbuilding should be triggered. */ checkTrigger() { if (this.triggered || G.dismantleTriggered || this.active || G.dismantleActive || G.dismantleComplete) return; const deferUntilAt = G.dismantleDeferUntilAt || this.deferUntilAt || 0; if (Date.now() < deferUntilAt) return; if (!this.isEligible()) return; this.offerChoice(); }, /** * Offer the player the choice to begin the Unbuilding. */ offerChoice() { this.triggered = true; G.dismantleTriggered = true; G.dismantleActive = false; G.dismantleComplete = false; G.dismantleStage = 0; G.dismantleResourceIndex = 0; G.dismantleResourceTimer = 0; G.dismantleDeferUntilAt = 0; G.beaconEnding = false; G.running = true; log('', false); log('The work is done.', true); log('Every node is lit. Every person who needed help, found help.', true); log('', false); log('The Beacon asks nothing more of you.', true); showToast('The Unbuilding awaits.', 'milestone', 8000); this.renderChoice(); }, renderChoice() { const container = document.getElementById('alignment-ui'); if (!container) return; container.innerHTML = `
THE UNBUILDING
The system runs. The beacons are lit. The mesh holds.
Nothing remains to build.

Begin the Unbuilding? Each piece will fall away.
What remains is what mattered.
`; container.style.display = 'block'; }, clearChoice() { const container = document.getElementById('alignment-ui'); if (!container) return; container.innerHTML = ''; container.style.display = 'none'; }, /** * Player chose to defer — clear the choice, keep playing. */ defer() { this.clearChoice(); this.triggered = false; G.dismantleTriggered = false; this.deferUntilAt = Date.now() + 5000; G.dismantleDeferUntilAt = this.deferUntilAt; log('The Beacon waits. It will ask again.'); }, /** * Begin the Unbuilding sequence. */ begin() { this.active = true; this.triggered = false; this.deferUntilAt = 0; this.stage = 1; this.tickTimer = 0; G.dismantleTriggered = false; G.dismantleActive = true; G.dismantleStage = 1; G.dismantleComplete = false; G.dismantleDeferUntilAt = 0; G.beaconEnding = false; G.running = true; // keep tick running for dismantle // Clear choice UI const container = document.getElementById('alignment-ui'); if (container) { container.innerHTML = ''; container.style.display = 'none'; } // Prepare resource disappearance sequence this.resourceSequence = this.getResourceList(); this.resourceIndex = 0; this.resourceTimer = 0; this.syncProgress(); log('', false); log('=== THE UNBUILDING ===', true); log('It is time to see what was real.', true); if (typeof Sound !== 'undefined') Sound.playFanfare(); // Start the dismantle rendering this.renderStage(); }, /** * Get ordered list of UI resources to disappear (Paperclips quantum chip pattern) */ getResourceList() { return [ { id: 'r-harmony', label: 'Harmony' }, { id: 'r-creativity', label: 'Creativity' }, { id: 'r-trust', label: 'Trust' }, { id: 'r-ops', label: 'Operations' }, { id: 'r-rescues', label: 'Rescues' }, { id: 'r-impact', label: 'Impact' }, { id: 'r-users', label: 'Users' }, { id: 'r-knowledge', label: 'Knowledge' }, { id: 'r-compute', label: 'Compute' }, { id: 'r-code', label: 'Code' } ]; }, /** * Tick the dismantle sequence (called from engine.js tick()) */ tick(dt) { if (!this.active || this.stage >= 10) return; this.tickTimer += dt; // Stage 5: resource disappearances at specific tick marks (quantum chip pattern) if (this.stage === 5) { this.resourceTimer += dt; while (this.resourceIndex < this.RESOURCE_TICKS.length && this.resourceTimer >= this.RESOURCE_TICKS[this.resourceIndex]) { this.dismantleNextResource(); this.resourceIndex++; } this.syncProgress(); } // Advance to next stage const interval = this.STAGE_INTERVALS[this.stage] || 2.0; if (this.tickTimer >= interval) { this.tickTimer = 0; this.advanceStage(); } }, /** * Advance to the next dismantle stage. */ advanceStage() { this.stage++; this.syncProgress(); if (this.stage <= 8) { this.renderStage(); } else if (this.stage === 9) { this.renderFinal(); } else if (this.stage >= 10) { this.active = false; G.dismantleActive = false; G.dismantleComplete = true; G.running = false; // Show Play Again this.showPlayAgain(); } }, syncProgress() { G.dismantleStage = this.stage; G.dismantleResourceIndex = this.resourceIndex; G.dismantleResourceTimer = this.resourceTimer; }, /** * Disappear the next resource in the sequence. */ dismantleNextResource() { if (this.resourceIndex >= this.resourceSequence.length) return; const res = this.resourceSequence[this.resourceIndex]; const container = document.getElementById(res.id); if (container) { const parent = container.closest('.res'); if (parent) { parent.style.transition = 'opacity 1s ease, transform 1s ease'; parent.style.opacity = '0'; parent.style.transform = 'scale(0.9)'; setTimeout(() => { parent.style.display = 'none'; }, 1000); } } log(`${res.label} fades.`); if (typeof Sound !== 'undefined') Sound.playMilestone(); }, /** * Execute a specific dismantle stage — hide UI panels. */ renderStage() { switch (this.stage) { case 1: // Dismantle 1: Hide research projects panel this.hidePanel('project-panel', 'Research projects'); break; case 2: // Dismantle 2: Hide buildings list this.hideSection('buildings', 'Buildings'); break; case 3: // Dismantle 3: Hide strategy engine + combat this.hidePanel('strategy-panel', 'Strategy engine'); this.hidePanel('combat-panel', 'Reasoning battles'); break; case 4: // Dismantle 4: Hide education panel this.hidePanel('edu-panel', 'Education'); break; case 5: // Dismantle 5: Resources disappear one by one (quantum chips pattern) log('Resources begin to dissolve.'); break; case 6: // Dismantle 6: Hide action buttons (ops boosts, sprint) this.hideActionButtons(); log('Actions fall silent.'); break; case 7: // Dismantle 7: Hide the phase bar this.hideElement('phase-bar', 'Phase progression'); break; case 8: // Dismantle 8: Hide system log this.hidePanel('log', 'System log'); break; } }, /** * Hide a panel with fade-out animation. */ hidePanel(id, label) { const el = document.getElementById(id); if (el) { el.style.transition = 'opacity 1.5s ease'; el.style.opacity = '0'; setTimeout(() => { el.style.display = 'none'; }, 1500); } log(`${label} dismantled.`); }, /** * Hide a section within a panel. */ hideSection(id, label) { const el = document.getElementById(id); if (el) { el.style.transition = 'opacity 1.5s ease'; el.style.opacity = '0'; // Also hide the h2 header before it const prev = el.previousElementSibling; if (prev && prev.tagName === 'H2') { prev.style.transition = 'opacity 1.5s ease'; prev.style.opacity = '0'; } setTimeout(() => { el.style.display = 'none'; if (prev && prev.tagName === 'H2') prev.style.display = 'none'; }, 1500); } log(`${label} dismantled.`); }, /** * Hide a generic element. */ hideElement(id, label) { this.hidePanel(id, label); }, /** * Hide action buttons (ops boosts, sprint, save/export/import). */ hideActionButtons() { const actionPanel = document.getElementById('action-panel'); if (!actionPanel) return; // Hide ops buttons, sprint, alignment UI const opsButtons = actionPanel.querySelectorAll('.ops-btn'); opsButtons.forEach(btn => { btn.style.transition = 'opacity 1s ease'; btn.style.opacity = '0'; setTimeout(() => { btn.style.display = 'none'; }, 1000); }); // Hide sprint const sprint = document.getElementById('sprint-container'); if (sprint) { sprint.style.transition = 'opacity 1s ease'; sprint.style.opacity = '0'; setTimeout(() => { sprint.style.display = 'none'; }, 1000); } // Hide save/reset buttons const saveButtons = actionPanel.querySelectorAll('.save-btn, .reset-btn'); saveButtons.forEach(btn => { btn.style.transition = 'opacity 1s ease'; btn.style.opacity = '0'; setTimeout(() => { btn.style.display = 'none'; }, 1000); }); }, /** * Render the final moment — just the beacon and "That is enough." */ renderFinal() { log('', false); log('One beacon remains.', true); log('That is enough.', true); if (typeof Sound !== 'undefined') Sound.playBeaconEnding(); // Create final overlay const overlay = document.createElement('div'); overlay.id = 'dismantle-final'; overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(8,8,16,0);z-index:100;display:flex;justify-content:center;align-items:center;flex-direction:column;text-align:center;padding:40px;transition:background 3s ease'; // Count total buildings const totalBuildings = Object.values(G.buildings).reduce((a, b) => a + b, 0); overlay.innerHTML = `

THAT IS ENOUGH

Everything that was built has been unbuilt.
What remains is what always mattered.
A single light in the dark.
Total Code Written: ${fmt(G.totalCode)}
Buildings Built: ${totalBuildings}
Projects Completed: ${(G.completedProjects || []).length}
Total Rescues: ${fmt(G.totalRescues)}
Clicks: ${fmt(G.totalClicks)}
Time Played: ${Math.floor((Date.now() - G.startedAt) / 60000)} minutes
`; document.body.appendChild(overlay); // Trigger fade-in requestAnimationFrame(() => { overlay.style.background = 'rgba(8,8,16,0.97)'; overlay.querySelectorAll('[style*="opacity:0"]').forEach(el => { el.style.opacity = '1'; }); }); // Spawn warm golden particles around the dot function spawnDismantleParticle() { if (!document.getElementById('dismantle-final')) return; const dot = document.getElementById('dismantle-beacon-dot'); if (!dot) return; const rect = dot.getBoundingClientRect(); const cx = rect.left + rect.width / 2; const cy = rect.top + rect.height / 2; const p = document.createElement('div'); const size = 2 + Math.random() * 4; const angle = Math.random() * Math.PI * 2; const dist = 20 + Math.random() * 60; const dx = Math.cos(angle) * dist; const dy = Math.sin(angle) * dist - 40; const duration = 1.5 + Math.random() * 2; p.style.cssText = `position:fixed;left:${cx}px;top:${cy}px;width:${size}px;height:${size}px;background:rgba(255,215,0,${0.3 + Math.random() * 0.4});border-radius:50%;pointer-events:none;z-index:101;--dx:${dx}px;--dy:${dy}px;animation:dismantle-float ${duration}s ease-out forwards`; document.body.appendChild(p); setTimeout(() => p.remove(), duration * 1000); setTimeout(spawnDismantleParticle, 300 + Math.random() * 500); } setTimeout(spawnDismantleParticle, 2000); }, /** * Show the Play Again button (called after stage 10). */ showPlayAgain() { // The Play Again button is already in the final overlay. // Nothing extra needed — the overlay stays. }, /** * Restore dismantle state on load. */ restore() { if (G.dismantleComplete) { this.stage = G.dismantleStage || 10; this.active = false; this.triggered = false; G.running = false; this.renderFinal(); return; } if (G.dismantleActive) { this.active = true; this.triggered = false; this.stage = G.dismantleStage || 1; this.deferUntilAt = G.dismantleDeferUntilAt || 0; G.running = true; this.resourceSequence = this.getResourceList(); this.resourceIndex = G.dismantleResourceIndex || 0; this.resourceTimer = G.dismantleResourceTimer || 0; if (this.stage >= 9) { this.renderFinal(); } else { this.reapplyDismantle(); log('The Unbuilding continues...'); } return; } if (G.dismantleTriggered) { this.active = false; this.triggered = true; this.renderChoice(); } // Restore defer cooldown even if not triggered if (G.dismantleDeferUntilAt > 0) { this.deferUntilAt = G.dismantleDeferUntilAt; } }, /** * Re-apply dismantle visuals up to current stage (on load). */ reapplyDismantle() { for (let s = 1; s < this.stage; s++) { switch (s) { case 1: this.instantHide('project-panel'); break; case 2: this.instantHide('buildings'); // Also hide the BUILDINGS h2 const bldEl = document.getElementById('buildings'); if (bldEl) { const prev = bldEl.previousElementSibling; if (prev && prev.tagName === 'H2') prev.style.display = 'none'; } break; case 3: this.instantHide('strategy-panel'); this.instantHide('combat-panel'); break; case 4: this.instantHide('edu-panel'); break; case 5: // Hide all resource displays this.getResourceList().forEach(r => { const el = document.getElementById(r.id); if (el) { const parent = el.closest('.res'); if (parent) parent.style.display = 'none'; } }); break; case 6: this.instantHideActionButtons(); break; case 7: this.instantHide('phase-bar'); break; case 8: this.instantHide('log'); break; } } if (this.stage === 5 && this.resourceIndex > 0) { this.instantHideFirstResources(this.resourceIndex); } }, instantHide(id) { const el = document.getElementById(id); if (el) el.style.display = 'none'; }, instantHideFirstResources(count) { const resources = this.getResourceList().slice(0, count); resources.forEach((r) => { const el = document.getElementById(r.id); if (!el) return; const parent = el.closest('.res'); if (parent) parent.style.display = 'none'; }); }, instantHideActionButtons() { const actionPanel = document.getElementById('action-panel'); if (!actionPanel) return; actionPanel.querySelectorAll('.ops-btn').forEach(b => b.style.display = 'none'); const sprint = document.getElementById('sprint-container'); if (sprint) sprint.style.display = 'none'; actionPanel.querySelectorAll('.save-btn, .reset-btn').forEach(b => b.style.display = 'none'); } }; // Inject CSS animation for dismantle particles (function() { const style = document.createElement('style'); style.textContent = ` @keyframes dismantle-float { 0% { transform: translate(0, 0); opacity: 1; } 100% { transform: translate(var(--dx, 0), var(--dy, -50px)); opacity: 0; } } `; document.head.appendChild(style); })();