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
✦ Emergent Events: 0 | Patterns: 0 | Strategy: —
+
@@ -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;
}
/**