diff --git a/index.html b/index.html index dc8594d..2c6b632 100644 --- a/index.html +++ b/index.html @@ -116,6 +116,8 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code #custom-tooltip .tt-label{color:#4a9eff;font-weight:600;margin-bottom:4px;font-size:11px} #custom-tooltip .tt-desc{color:#aaa;font-size:10px;margin-bottom:4px} #custom-tooltip .tt-edu{color:#888;font-style:italic;font-size:9px} +body.prestige-run #header{border-color:#4a9eff;box-shadow:0 0 32px rgba(74,158,255,0.18)} +#prestige-status{display:none;color:#7fb8ff;font-size:10px;line-height:1.8;margin-top:8px;padding-top:8px;border-top:1px solid #16263a} /* Mute & contrast buttons */ .header-btns{position:absolute;right:16px;top:50%;transform:translateY(-50%);display:flex;gap:6px} .header-btn{background:#0e0e1a;border:1px solid #333;color:#666;font-size:13px;width:28px;height:28px;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all 0.15s;font-family:inherit} @@ -180,7 +182,7 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code - +

BUILDINGS

@@ -203,6 +205,7 @@ Harmony: 50
Drift: 0
Events Resolved: 0
+
@@ -257,7 +260,7 @@ The light is on. The room is empty."

Drift: 100 — Total Code: 0

Every alignment shortcut moved you further from the people you served.

- + @@ -270,6 +273,7 @@ The light is on. The room is empty." + diff --git a/js/data.js b/js/data.js index 21992ea..b7db414 100644 --- a/js/data.js +++ b/js/data.js @@ -160,6 +160,12 @@ const G = { startTime: 0, flags: {}, + // Prestige / New Game+ + prestigeTotal: 0, + prestigeSignal: 0, + prestigeRoots: 0, + prestigePath: '', + // Endgame sequence beaconEnding: false, dismantleTriggered: false, diff --git a/js/dismantle.js b/js/dismantle.js index fb070c4..60be3f9 100644 --- a/js/dismantle.js +++ b/js/dismantle.js @@ -396,10 +396,12 @@ const Dismantle = { Clicks: ${fmt(G.totalClicks)}
Time Played: ${Math.floor((Date.now() - G.startedAt) / 60000)} minutes - + ${typeof Prestige !== 'undefined' + ? Prestige.getEndingMarkup() + : ``} `; document.body.appendChild(overlay); diff --git a/js/engine.js b/js/engine.js index 8a4bd40..875f20e 100644 --- a/js/engine.js +++ b/js/engine.js @@ -108,6 +108,10 @@ function updateRates() { if (debuff.applyFn) debuff.applyFn(); } } + + if (typeof Prestige !== 'undefined') { + Prestige.applyRateMultipliers(); + } } // === CORE FUNCTIONS === @@ -519,6 +523,12 @@ function renderBeaconEnding() { const overlay = document.createElement('div'); overlay.id = 'beacon-ending'; 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 2s ease'; + const endingControls = typeof Prestige !== 'undefined' + ? Prestige.getEndingMarkup() + : ``; overlay.innerHTML = `

THE BEACON SHINES

Someone found the light tonight.

@@ -537,10 +547,7 @@ function renderBeaconEnding() { Clicks: ${G.totalClicks}
Time Played: ${Math.floor((Date.now() - G.startedAt) / 60000)} minutes - + ${endingControls} `; document.body.appendChild(overlay); if (typeof Sound !== 'undefined') Sound.playBeaconEnding(); diff --git a/js/main.js b/js/main.js index 568d2d7..517fed0 100644 --- a/js/main.js +++ b/js/main.js @@ -229,6 +229,7 @@ function initGame() { } window.addEventListener('load', function () { + if (typeof Prestige !== 'undefined') Prestige.syncToGame(); // Initialize emergent mechanics if (typeof EmergentMechanics !== 'undefined') { window._emergent = new EmergentMechanics(); diff --git a/js/prestige.js b/js/prestige.js new file mode 100644 index 0000000..e3111da --- /dev/null +++ b/js/prestige.js @@ -0,0 +1,137 @@ +// ============================================================ +// THE BEACON - Prestige / New Game+ +// Carries forward path-based bonuses across completed runs. +// ============================================================ + +const Prestige = { + STORAGE_KEY: 'the-beacon-prestige', + + emptyState() { + return { total: 0, signal: 0, roots: 0, lastChoice: '' }; + }, + + loadState() { + try { + const raw = localStorage.getItem(this.STORAGE_KEY); + if (!raw) return this.emptyState(); + const parsed = JSON.parse(raw); + return { + total: Number(parsed.total || 0), + signal: Number(parsed.signal || 0), + roots: Number(parsed.roots || 0), + lastChoice: parsed.lastChoice || '' + }; + } catch (e) { + return this.emptyState(); + } + }, + + saveState(state) { + try { + localStorage.setItem(this.STORAGE_KEY, JSON.stringify(state)); + } catch (e) { + // Ignore storage failures — the run can still continue. + } + }, + + syncToGame() { + const state = this.loadState(); + G.prestigeTotal = state.total; + G.prestigeSignal = state.signal; + G.prestigeRoots = state.roots; + G.prestigePath = state.lastChoice; + return state; + }, + + hasPrestige() { + return (G.prestigeTotal || 0) > 0; + }, + + getSignalMultiplier() { + return 1 + ((G.prestigeSignal || 0) * 0.10); + }, + + getRootsMultiplier() { + return 1 + ((G.prestigeRoots || 0) * 0.10); + }, + + applyRateMultipliers() { + const signal = this.getSignalMultiplier(); + const roots = this.getRootsMultiplier(); + G.codeRate *= signal; + G.computeRate *= signal; + G.knowledgeRate *= signal; + G.userRate *= signal; + G.impactRate *= signal; + G.rescuesRate *= signal; + G.opsRate *= signal; + G.trustRate *= signal; + G.creativityRate *= roots; + }, + + renderStatus() { + const el = document.getElementById('prestige-status'); + if (!el) return; + + if (!this.hasPrestige()) { + el.textContent = ''; + el.style.display = 'none'; + if (document.body && document.body.classList) document.body.classList.remove('prestige-run'); + return; + } + + const signalBonus = Math.round((this.getSignalMultiplier() - 1) * 100); + const rootsBonus = Math.round((this.getRootsMultiplier() - 1) * 100); + el.textContent = `Prestige Run — New Signal x${G.prestigeSignal || 0} (+${signalBonus}% production/click) • Deeper Roots x${G.prestigeRoots || 0} (+${rootsBonus}% creativity)`; + el.style.display = 'block'; + if (document.body && document.body.classList) document.body.classList.add('prestige-run'); + }, + + getEndingMarkup() { + const signalBonus = Math.round((this.getSignalMultiplier() - 1) * 100); + const rootsBonus = Math.round((this.getRootsMultiplier() - 1) * 100); + return ` +
+
NEW GAME+
+
Choose what carries forward into the next run.
+
Current legacy: New Signal x${G.prestigeSignal || 0} • Deeper Roots x${G.prestigeRoots || 0}
+
+ + +
+
+ `; + }, + + startNewRun(path) { + const state = this.loadState(); + state.total += 1; + if (path === 'signal') state.signal += 1; + if (path === 'roots') state.roots += 1; + state.lastChoice = path; + this.saveState(state); + this.syncToGame(); + this.resetForReload(false); + }, + + resetForReload(clearPrestige) { + window.__beaconResetInProgress = true; + localStorage.removeItem('the-beacon-v2'); + if (clearPrestige) localStorage.removeItem(this.STORAGE_KEY); + location.reload(); + }, + + fullReset() { + this.resetForReload(true); + } +}; + +window.Prestige = Prestige; diff --git a/js/render.js b/js/render.js index 9fa900a..1058b1a 100644 --- a/js/render.js +++ b/js/render.js @@ -13,6 +13,7 @@ function render() { renderPulse(); renderStrategy(); renderClickPower(); + if (typeof Prestige !== 'undefined') Prestige.renderStatus(); Combat.renderCombatPanel(); } @@ -192,6 +193,7 @@ function showSaveToast() { * Persists the current game state to localStorage. */ function saveGame() { + if (typeof window !== 'undefined' && window.__beaconResetInProgress) return; // Save debuff IDs (can't serialize functions) const debuffIds = (G.activeDebuffs || []).map(d => d.id); const saveData = { diff --git a/js/utils.js b/js/utils.js index f973830..38bad95 100644 --- a/js/utils.js +++ b/js/utils.js @@ -282,7 +282,8 @@ function spendProject(project) { } function getClickPower() { - return (1 + Math.floor(G.buildings.autocoder * 0.5) + Math.max(0, (G.phase - 1)) * 2) * G.codeBoost; + const signalMult = typeof Prestige !== 'undefined' ? Prestige.getSignalMultiplier() : 1; + return (1 + Math.floor(G.buildings.autocoder * 0.5) + Math.max(0, (G.phase - 1)) * 2) * G.codeBoost * signalMult; } /**