@@ -257,7 +263,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.
-
+
diff --git a/js/dismantle.js b/js/dismantle.js
index fb070c4..57d9eb2 100644
--- a/js/dismantle.js
+++ b/js/dismantle.js
@@ -396,7 +396,7 @@ const Dismantle = {
Clicks: ${fmt(G.totalClicks)}
Time Played: ${Math.floor((Date.now() - G.startedAt) / 60000)} minutes
-
diff --git a/js/emergent-mechanics.js b/js/emergent-mechanics.js
index 603fd77..e11ad69 100644
--- a/js/emergent-mechanics.js
+++ b/js/emergent-mechanics.js
@@ -7,7 +7,10 @@
class EmergentMechanics {
constructor() {
- this.SAVE_KEY = 'the-beacon-emergent-v1';
+ const scopeKey = typeof getBeaconScopedStorageKey === 'function'
+ ? getBeaconScopedStorageKey
+ : (baseKey) => baseKey;
+ this.SAVE_KEY = scopeKey('the-beacon-emergent-v1');
this.PATTERN_CHECK_INTERVAL = 30; // seconds between pattern checks
this.MIN_ACTIONS_FOR_PATTERN = 20; // minimum tracked actions before detection kicks in
this.EVENT_COOLDOWN = 120; // seconds between emergent events
diff --git a/js/engine.js b/js/engine.js
index 8a4bd40..8eec69d 100644
--- a/js/engine.js
+++ b/js/engine.js
@@ -537,7 +537,7 @@ function renderBeaconEnding() {
Clicks: ${G.totalClicks}
Time Played: ${Math.floor((Date.now() - G.startedAt) / 60000)} minutes
-
diff --git a/js/main.js b/js/main.js
index 568d2d7..2441224 100644
--- a/js/main.js
+++ b/js/main.js
@@ -229,6 +229,7 @@ function initGame() {
}
window.addEventListener('load', function () {
+ applyPortalMode();
// Initialize emergent mechanics
if (typeof EmergentMechanics !== 'undefined') {
window._emergent = new EmergentMechanics();
diff --git a/js/render.js b/js/render.js
index 9fa900a..4e4e2af 100644
--- a/js/render.js
+++ b/js/render.js
@@ -98,7 +98,7 @@ function dismissOfflinePopup() {
// === EXPORT / IMPORT SAVE FILES ===
function exportSave() {
- const raw = localStorage.getItem('the-beacon-v2');
+ const raw = localStorage.getItem(getBeaconSaveKey());
if (!raw) {
showToast('No save data to export.', 'info');
log('No save data to export.');
@@ -150,7 +150,7 @@ function importSave() {
return;
}
if (confirm('Import this save? Current progress will be overwritten.')) {
- localStorage.setItem('the-beacon-v2', ev.target.result);
+ localStorage.setItem(getBeaconSaveKey(), ev.target.result);
showToast('Save imported — reloading...', 'info');
location.reload();
}
@@ -237,7 +237,7 @@ function saveGame() {
savedAt: Date.now()
};
- localStorage.setItem('the-beacon-v2', JSON.stringify(saveData));
+ localStorage.setItem(getBeaconSaveKey(), JSON.stringify(saveData));
showSaveToast();
}
@@ -246,7 +246,7 @@ function saveGame() {
* @returns {boolean} True if load was successful.
*/
function loadGame() {
- const raw = localStorage.getItem('the-beacon-v2');
+ const raw = localStorage.getItem(getBeaconSaveKey());
if (!raw) return false;
try {
diff --git a/js/utils.js b/js/utils.js
index f973830..fe62d7e 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -193,6 +193,73 @@ function spellf(n) {
return parts.join(' ') || 'zero';
}
+// === PORTAL HELPERS ===
+function getBeaconPortalId() {
+ try {
+ const search = (typeof window !== 'undefined' && window.location && typeof window.location.search === 'string')
+ ? window.location.search.replace(/^\?/, '')
+ : '';
+ if (!search) return '';
+ for (const chunk of search.split('&')) {
+ if (!chunk) continue;
+ const [rawKey, rawValue = ''] = chunk.split('=');
+ if (decodeURIComponent(rawKey || '') === 'portal') {
+ return decodeURIComponent(rawValue.replace(/\+/g, ' ')).trim();
+ }
+ }
+ } catch (e) {}
+ return '';
+}
+
+function isBeaconPortalEmbed() {
+ try {
+ const search = (typeof window !== 'undefined' && window.location && typeof window.location.search === 'string')
+ ? window.location.search.replace(/^\?/, '')
+ : '';
+ if (!search) return false;
+ for (const chunk of search.split('&')) {
+ if (!chunk) continue;
+ const [rawKey, rawValue = ''] = chunk.split('=');
+ if (decodeURIComponent(rawKey || '') === 'embedded') {
+ return decodeURIComponent(rawValue.replace(/\+/g, ' ')) === '1';
+ }
+ }
+ } catch (e) {}
+ return false;
+}
+
+function getBeaconScopedStorageKey(baseKey) {
+ const portalId = getBeaconPortalId();
+ return portalId ? `${baseKey}:${portalId}` : baseKey;
+}
+
+function getBeaconSaveKey() {
+ return getBeaconScopedStorageKey('the-beacon-v2');
+}
+
+function clearBeaconSaveAndReload() {
+ try {
+ if (typeof localStorage !== 'undefined') {
+ localStorage.removeItem(getBeaconSaveKey());
+ }
+ } catch (e) {}
+ if (typeof location !== 'undefined' && location && typeof location.reload === 'function') {
+ location.reload();
+ }
+}
+
+function applyPortalMode() {
+ if (typeof document === 'undefined' || !document.body) return;
+ const portalId = getBeaconPortalId();
+ if (portalId) {
+ document.body.setAttribute('data-portal-id', portalId);
+ if (document.body.dataset) document.body.dataset.portalId = portalId;
+ }
+ if (isBeaconPortalEmbed()) {
+ document.body.classList.add('portal-embed');
+ }
+}
+
// NOTE: exportSave() and importSave() are defined in render.js (file-based).
// The clipboard/prompt versions that were here were dead code — render.js
// loads after utils.js and overrides them. Removed to avoid confusion.
diff --git a/nexus-panel-preview.html b/nexus-panel-preview.html
new file mode 100644
index 0000000..09909c2
--- /dev/null
+++ b/nexus-panel-preview.html
@@ -0,0 +1,81 @@
+
+
+
+
+
+ The Beacon — Nexus Portal Preview
+
+
+
+
+
+ NEXUS PANEL PREVIEW — THE BEACON
+ ./index.html?portal=the-beacon&embedded=1
+
+
+
+
+ This local harness mirrors the app-owned Nexus portal entry. It verifies that The Beacon loads inside an iframe panel and that the embedded portal route uses its own scoped localStorage save key.
+