(function () { "use strict"; /* * Hidden site details * ------------------- * This file adds small hidden interactions across the site: footer notes, * click targets, search/keyboard secrets, rare messages, and page-specific * behavior for the hidden /play pages. * * How to add your own things: * - Add new text to EDITABLE CONTENT arrays such as `details`, `poems`, * `greetings`, or `lore`. * - Add search-triggered toast messages to `SEARCH_TOASTS`. * - Add search-triggered page redirects to `SEARCH_ROUTES`. * - Add typed keyboard phrases to `KEYBOARD_SECRETS` or `LONG_KEYBOARD_SECRETS`. * - Add a new feature by writing an `addYourFeature(memory)` function and * adding it to the `FEATURES` list at the bottom of the file. */ // Storage keys. v1 is read once so old visitors keep their hidden progress. const STORAGE_KEY = "zxh_hidden_details_v2"; const OLD_STORAGE_KEY = "zxh_hidden_details_v1"; // Shared page context. These are captured once so daily random picks stay stable. const now = new Date(); const hour = now.getHours(); const path = window.location.pathname; const state = readState(); // ----------------------------- // EDITABLE CONTENT // ----------------------------- // Generated by the hidden narrative authoring page. // Friendly source of truth: assets/content/hidden-details.json const familyLayers = []; const details = [ "Keep one small appointment with yourself today.", "Leave one kindness prepared for tomorrow-you.", "Discipline can speak in a calm voice.", "When the task feels heavy, remember the life it is trying to make possible.", "Pause here: you are allowed to be proud of the person who kept returning." ]; const poems = []; const greetings = []; const nightMessages = []; const lore = { "quotes": [ "14/07/26 - big demo meeting with CEO tomorrow !!", "Readiness often arrives ten minutes after you begin.", "Rest is not a reward for becoming exhausted. It is maintenance for the person doing the becoming.", "Before the world starts making requests, give your own life a few undivided minutes.", "Every meaningful yes needs a few well-placed noes around it.", "Motivation is weather. Build a shelter sturdy enough to keep going when the sky changes.", "Do not design only for your best days. Create a minimum version of the habit that even a tired day can hold.", "No repetition is wasted just because nobody applauded it.", "Look backwards long enough to notice the ground you have already crossed.", "Use the voice that helps you stand up again.", "Sometimes courage is simply opening the document again.", "When resistance feels enormous, promise only five minutes. The aim is not to finish; it is to make starting feel safe again.", "If the habit keeps failing, redesign the doorway: put the book on the pillow, the shoes by the door, the difficult task at the hour your mind is clearest.", "The exciting beginning and the visible result are connected by ordinary days. Learn to respect the bridge.", "Completion creates its own kind of energy. Close one tab, send one message, clear one surface, finish one small loop." ], "conversations": [], "journals": [ "10/07/26 - surviving a heatwave whilst working :-)", "You do not need the whole staircase today. Name the next honest step, make it small enough to begin, and let motion teach you what planning cannot.", "Replace ‘I am lazy’ with evidence: what drained me, what helped me, and what is the smallest change I can test tomorrow? Curiosity builds what shame only bruises.", "A missed day is data, not destiny. Return before the story in your head turns one pause into an identity." ], "warnings": [], "dreams": [ "The path was hidden, but the compass still answered: values before mood, direction before speed, one careful footfall before certainty.", "Long after inspiration went home, a single workshop light stayed on. Inside, patience was sanding rough effort into skill.", "For a long time the seed had nothing visible to show. It was still becoming roots." ], "cassettes": [], "fakeUsers": [], "seasonal": {}, "homepageTakeovers": [], "roomLinks": [] }; // Exact search text -> toast message. const SEARCH_TOASTS = {}; // Exact search text -> hidden page route. const SEARCH_ROUTES = {}; // Short typed phrases. Stored in sessionStorage as a rolling key chain. const KEYBOARD_SECRETS = []; // Longer typed phrases and character names. const LONG_KEYBOARD_SECRETS = []; // ----------------------------- // STATE HELPERS // ----------------------------- function readState() { let current = {}; try { current = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}"); } catch (_) {} if (!current.visits) { try { const oldState = JSON.parse(localStorage.getItem(OLD_STORAGE_KEY) || "{}"); current = { ...oldState, migratedFromV1: true }; localStorage.setItem(STORAGE_KEY, JSON.stringify(current)); } catch (_) {} } return current; } function writeState(next) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(next)); } catch (_) {} } function pick(list, salt) { const seed = hash(`${path}|${now.toDateString()}|${salt || ""}`); return list[seed % list.length]; } function hash(value) { let h = 2166136261; for (let i = 0; i < value.length; i += 1) { h ^= value.charCodeAt(i); h = Math.imul(h, 16777619); } return Math.abs(h >>> 0); } // ----------------------------- // UI HELPERS // ----------------------------- function hiddenMessageMarkup(message) { return `${escapeHtml(message)}`; } function setHiddenMessage(el, message) { delete el.dataset.hiddenCharacter; el.innerHTML = hiddenMessageMarkup(message); } function toast(message, duration) { let el = document.querySelector(".hidden-toast"); if (!el) { el = document.createElement("div"); el.className = "hidden-toast"; el.setAttribute("role", "status"); document.body.appendChild(el); } setHiddenMessage(el, message); el.classList.add("is-visible"); window.clearTimeout(el._timer); el._timer = window.setTimeout(() => el.classList.remove("is-visible"), duration || 5600); } function redirectTo(route) { window.location.href = route; } function runSecretAction(secret) { if (secret.route) redirectTo(secret.route); if (secret.message) toast(secret.message); if (secret.song) playTinySong(); } function layerForSearch(value) { if (value.includes("future")) return 4; if (value.includes("sensei") || value.includes("aphy")) return 3; return 2; } function rememberVisit() { const visits = (state.visits || 0) + 1; const seen = Array.isArray(state.seen) ? state.seen : []; const pathCounts = { ...(state.pathCounts || {}) }; pathCounts[path] = (pathCounts[path] || 0) + 1; const layerVisits = { ...(state.layerVisits || {}) }; const next = { ...state, visits, lastPath: path, pathCounts, layerVisits, seen: Array.from(new Set([...seen, path])).slice(-100), firstSeen: state.firstSeen || new Date().toISOString(), lastSeen: new Date().toISOString() }; writeState(next); return next; } function awardLayer(layer, reason) { const next = readState(); next.layerVisits = { ...(next.layerVisits || {}) }; next.layerVisits[layer] = (next.layerVisits[layer] || 0) + 1; next.lastLayerReason = reason; writeState(next); } // ----------------------------- // GLOBAL FEATURES // ----------------------------- function addFooterNote(memory) { const footer = document.querySelector("footer"); if (!footer || !details.length) return; const note = document.createElement("p"); note.className = "hidden-footer-note"; const base = pick(details, "footer"); setHiddenMessage(note, memory.visits > 4 ? `${base} You have passed through ${memory.visits} times.` : base); footer.appendChild(note); } function addHomepageGreeting(memory) { const target = document.querySelector("#db-greeting"); if (!target || !greetings.length) return; setHiddenMessage(target, pick(greetings, `greeting-${memory.visits}`)); const extra = document.createElement("p"); extra.className = "hidden-greeting"; setHiddenMessage(extra, ""); target.closest("div")?.appendChild(extra); } function addWhispers() { // Event: click one of the tiny "?" marks appended to long paragraphs. const paragraphs = Array.from(document.querySelectorAll("main p, #content p, article p")).filter((p) => p.textContent.trim().length > 80); paragraphs.slice(0, 5).forEach((p, index) => { if ((hash(path + index) + index) % 3 !== 0) return; const mark = document.createElement("span"); mark.className = "hidden-whisper"; mark.tabIndex = 0; mark.textContent = " ?"; const source = index % 2 ? poems : details; if (!source.length) return; mark.title = pick(source, `whisper-${index}`); mark.addEventListener("click", () => { awardLayer(index % 2 ? 2 : 1, "paragraph whisper"); toast(mark.title); }); p.appendChild(mark); }); } function addCornerObject() { // Event: click the small fixed button in the bottom-left corner. const object = document.createElement("button"); object.className = "hidden-corner-object"; object.type = "button"; object.title = "hidden interaction"; object.textContent = state.visits % 2 ? "*" : "~"; document.body.appendChild(object); let clicks = 0; object.addEventListener("click", () => { clicks += 1; awardLayer(clicks > 3 ? 3 : 1, "corner object"); toast("Hidden interaction recorded."); if (clicks === 7) window.location.href = "/play/left-behind.html"; }); } function addLogoSearchAndKeyboardSecrets(memory) { // Events: // - Click `.site-brand` repeatedly on the homepage. // - Type exact words into `#search-input`. // - Type phrases anywhere on the page. const nav = document.querySelector(".site-brand"); if (nav) { let taps = Number(sessionStorage.getItem("zxh_logo_taps") || 0); nav.addEventListener("click", (event) => { if (path === "/" || path === "/index.html") { event.preventDefault(); taps += 1; sessionStorage.setItem("zxh_logo_taps", String(taps)); awardLayer(taps >= 5 ? 3 : 1, "logo taps"); toast(taps >= 5 ? "Hidden route available: /play/dream-corridor.html" : "Hidden interaction recorded."); if (taps >= 8) window.location.href = "/play/dream-corridor.html"; } }); } const search = document.querySelector("#search-input"); if (search) { search.addEventListener("input", () => { const value = search.value.trim().toLowerCase(); if (SEARCH_TOASTS[value]) toast(SEARCH_TOASTS[value]); if (SEARCH_ROUTES[value]) { awardLayer(layerForSearch(value), `search ${value}`); redirectTo(SEARCH_ROUTES[value]); } }); } document.addEventListener("keydown", (event) => { const key = event.key.toLowerCase(); const chain = `${sessionStorage.getItem("zxh_key_chain") || ""}${key}`.slice(-18); sessionStorage.setItem("zxh_key_chain", chain); KEYBOARD_SECRETS .filter((secret) => chain.endsWith(secret.phrase)) .forEach(runSecretAction); }); if (memory.seen?.length >= 6 && !state.travellerNoteShown) { window.setTimeout(() => { toast("Hidden path recorded."); writeState({ ...readState(), travellerNoteShown: true }); }, 1600); } } function addRareEvents() { if (hour >= 22 || hour < 5) { document.body.classList.add("hidden-late-night"); if (nightMessages.length) window.setTimeout(() => toast(pick(nightMessages, "night"), 2200), 900); } const roll = Math.random(); if (roll < 0.003) { window.setTimeout(() => showDriftNote("", ""), 1800); awardLayer(3, "rare sensei appearance"); } else if (roll < 0.008) { window.setTimeout(() => toast("Hidden warning."), 2400); awardLayer(4, "future warning"); } } function showDriftNote(text, art) { const panel = document.createElement("aside"); panel.className = "hidden-drift-note"; panel.innerHTML = `
${hiddenMessageMarkup(text)}
${escapeHtml(art)}
`; panel.querySelector("button").addEventListener("click", () => panel.remove()); document.body.appendChild(panel); } function addSecretLinks() { [ ["/play/left-behind.html", "left behind"], ["/play/dream-corridor.html", "dream corridor"], ["/play/kitchen-light.html", "kitchen light"], ...lore.roomLinks ].forEach(([href, label], index) => { const link = document.createElement("a"); link.className = "hidden-secret-link"; link.href = href; link.textContent = label; link.style.left = `${index + 1}px`; document.body.appendChild(link); }); } function enhanceErrors() { if (document.title.match(/404|not found/i) || document.body.textContent.match(/404|not found/i)) { toast("Page not found."); } } function addContinuity(memory) { const milestones = { 64: "Hidden layer unlocked." }; if (milestones[memory.visits] && !state[`milestone_${memory.visits}`]) { const layer = memory.visits >= 31 ? 4 : memory.visits >= 9 ? 3 : 2; awardLayer(layer, `visit milestone ${memory.visits}`); window.setTimeout(() => toast(milestones[memory.visits], 7000), 1200); writeState({ ...readState(), [`milestone_${memory.visits}`]: true }); } const count = memory.pathCounts?.[path] || 0; if ([3, 7, 12].includes(count)) { window.setTimeout(() => toast("Page revisit recorded.", 6400), 1700); } } function addSeasonAndDates(memory) { const month = now.getMonth(); const day = now.getDate(); const season = month < 2 || month === 11 ? "winter" : month < 5 ? "spring" : month < 8 ? "summer" : "autumn"; if (Math.random() < 0.18 && lore.seasonal[season]) window.setTimeout(() => toast(lore.seasonal[season], 5600), 2600); const first = memory.firstSeen ? new Date(memory.firstSeen) : null; if (first && memory.visits > 1 && first.getMonth() === month && first.getDate() === day) { window.setTimeout(() => toast("Visitor anniversary recorded.", 7000), 1400); } } function addObjectConstellation() { // Event: click the small keepsake buttons along the bottom rail. const rail = document.createElement("div"); rail.className = "hidden-object-rail"; rail.setAttribute("aria-label", "Hidden objects"); const objects = [ ["ring", "Hidden object recorded."], ["bot", "Hidden object recorded."], ["tea", "Hidden object recorded."], ["crayon", "Hidden object recorded."], ["clock", "Hidden object recorded."] ]; objects.forEach(([name, message]) => { const button = document.createElement("button"); button.type = "button"; button.className = `hidden-keepsake hidden-keepsake--${name}`; button.title = name; button.textContent = { ring: "o", bot: "#", tea: "u", crayon: "/", clock: ":" }[name]; button.addEventListener("click", () => { const next = readState(); next.keepsakes = Array.from(new Set([...(next.keepsakes || []), name])); writeState(next); awardLayer(next.keepsakes.length >= 3 ? 3 : 2, `keepsake ${name}`); toast(message); if (next.keepsakes.length >= objects.length) { window.setTimeout(() => toast("Hidden object set completed."), 1100); } }); rail.appendChild(button); }); document.body.appendChild(rail); } function addWeatherWindow(memory) { // Event: click the small "window" button. This asks for browser geolocation. if (memory.visits < 3 || !("geolocation" in navigator)) return; const button = document.createElement("button"); button.className = "hidden-weather-window"; button.type = "button"; button.title = "Check outside weather"; button.textContent = "window"; button.addEventListener("click", () => { toast("Checking outside weather."); navigator.geolocation.getCurrentPosition((pos) => { const { latitude, longitude } = pos.coords; fetch(`https://api.open-meteo.com/v1/forecast?latitude=${latitude.toFixed(3)}&longitude=${longitude.toFixed(3)}¤t=temperature_2m,precipitation&timezone=auto`) .then((res) => res.json()) .then((data) => { const current = data.current || {}; const rain = Number(current.precipitation || 0) > 0; const temp = Math.round(Number(current.temperature_2m)); toast(rain ? `Outside: rain, ${temp}C.` : `Outside: ${temp}C.`); }) .catch(() => toast("Weather check failed.")); }, () => toast("No outside weather today.")); }); document.body.appendChild(button); } function addOldWebLayer(memory) { if (Math.random() < 0.08 || memory.visits > 10) { const stamp = document.createElement("div"); stamp.className = "hidden-oldweb-stamp"; if (!lore.fakeUsers.length) return; stamp.title = pick(lore.fakeUsers, "fake-user"); stamp.textContent = "best viewed with patience"; stamp.addEventListener("click", () => { awardLayer(1, "old web stamp"); toast(stamp.title); }); document.body.appendChild(stamp); } const comments = document.querySelector("#comments"); if (comments && lore.fakeUsers.length && !comments.querySelector(".hidden-guestbook-line")) { const line = document.createElement("p"); line.className = "hidden-guestbook-line"; setHiddenMessage(line, pick(lore.fakeUsers, "comment-lore")); comments.appendChild(line); } } function addSourceRelics() { const marker = document.createComment("hidden-details"); document.documentElement.appendChild(marker); document.querySelectorAll("img[alt=''], img:not([alt])").forEach((img, index) => { if (index > 2) return; img.alt = "Decorative image."; }); } function addLongKeyboardSecrets() { // Event: type longer hidden phrases anywhere on the page. document.addEventListener("keydown", (event) => { const chain = `${sessionStorage.getItem("zxh_second_chain") || ""}${event.key.toLowerCase()}`.slice(-24); sessionStorage.setItem("zxh_second_chain", chain); LONG_KEYBOARD_SECRETS .filter((secret) => chain.endsWith(secret.phrase)) .forEach(runSecretAction); }); } function addPatienceRewards(memory) { // Event: no movement, key presses, scrolling, or clicking for 90 seconds. if (memory.visits < 2) return; let idleTimer = null; const startIdle = () => { window.clearTimeout(idleTimer); idleTimer = window.setTimeout(() => { awardLayer(5, "patience"); toast("Idle moment recorded.", 7600); const next = readState(); next.patientMoments = (next.patientMoments || 0) + 1; writeState(next); }, 90000); }; ["mousemove", "keydown", "scroll", "click"].forEach((name) => document.addEventListener(name, startIdle, { passive: true })); startIdle(); } function addRareSecondLayer() { const roll = Math.random(); if ((path === "/" || path === "/index.html") && roll < 0.006) { document.body.classList.add("hidden-home-takeover"); if (lore.homepageTakeovers.length) window.setTimeout(() => showDriftNote(pick(lore.homepageTakeovers, "takeover"), ""), 600); } else if (roll < 0.002) { window.setTimeout(() => showDriftNote("", ""), 1500); } else if (roll < 0.006) { if (lore.dreams.length) window.setTimeout(() => toast(pick(lore.dreams, "rare-dream"), 7600), 2000); } else if (roll < 0.014) { if (lore.warnings.length) window.setTimeout(() => toast(pick(lore.warnings, "rare-warning"), 6800), 2300); } } function addFamilyLayerIndex(memory) { if (!path.includes("/play/family-layer-index")) return; const target = document.querySelector("[data-family-layer-index]"); if (!target) return; const visits = readState().layerVisits || {}; target.innerHTML = familyLayers.map((line, layer) => { const count = visits[layer] || 0; return `
  • ${hiddenMessageMarkup(line)}
    local encounters: ${count}
  • `; }).join(""); } function addPageSpecificSecrets() { // Events only used by specific `/play/...` pages. if (path.includes("/play/cassette-log")) { document.querySelectorAll("[data-cassette]").forEach((button, index) => { button.addEventListener("click", () => { awardLayer(index >= 2 ? 4 : 2, "voice note"); if (lore.cassettes.length) toast(lore.cassettes[index % lore.cassettes.length], 7600); playTinySong(); }); }); } if (path.includes("/play/patience-game")) { const target = document.querySelector("[data-patience-target]"); const count = document.querySelector("[data-patience-count]"); if (target && count) { let seconds = 0; setInterval(() => { seconds += 1; count.textContent = String(seconds); if ([30, 90, 180].includes(seconds)) { setHiddenMessage(target, "Patience milestone recorded."); } }, 1000); } } if (path.includes("/play/terminal-cupboard")) { const input = document.querySelector("[data-cupboard-input]"); const log = document.querySelector("[data-cupboard-log]"); const commands = { help: "commands: layer, exit", layer: familyLayers.join("\n"), exit: "Closed." }; input?.addEventListener("keydown", (event) => { if (event.key !== "Enter") return; const value = input.value.trim().toLowerCase(); const line = document.createElement("p"); setHiddenMessage(line, `> ${value}\n${commands[value] || "Unknown command."}`); log.appendChild(line); input.value = ""; }); } } function addInvisibleHoverSecrets() { document.querySelectorAll("h1, h2").forEach((heading, index) => { if (index > 5) return; heading.classList.add("hidden-hover-memory"); const source = [...lore.quotes, ...lore.journals, ...poems]; if (!source.length) return; heading.dataset.hiddenMemory = pick(source, `heading-${index}`); }); } function playTinySong() { try { const AudioContext = window.AudioContext || window.webkitAudioContext; if (!AudioContext) return; const ctx = new AudioContext(); const notes = [392, 494, 440, 330, 392]; notes.forEach((freq, index) => { const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.frequency.value = freq; osc.type = "sine"; gain.gain.setValueAtTime(0.0001, ctx.currentTime + index * 0.18); gain.gain.exponentialRampToValueAtTime(0.05, ctx.currentTime + index * 0.18 + 0.03); gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + index * 0.18 + 0.16); osc.connect(gain); gain.connect(ctx.destination); osc.start(ctx.currentTime + index * 0.18); osc.stop(ctx.currentTime + index * 0.18 + 0.18); }); } catch (_) {} } function escapeHtml(value) { return String(value).replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[char])); } // Features run in this order on every page after the DOM is ready. // Each function receives the current `memory` object. const FEATURES = [ addFooterNote, addHomepageGreeting, addWhispers, addCornerObject, addLogoSearchAndKeyboardSecrets, addRareEvents, addSecretLinks, enhanceErrors, addContinuity, addSeasonAndDates, addObjectConstellation, addWeatherWindow, addOldWebLayer, addSourceRelics, addLongKeyboardSecrets, addPatienceRewards, addRareSecondLayer, addFamilyLayerIndex, addPageSpecificSecrets, addInvisibleHoverSecrets ]; document.addEventListener("DOMContentLoaded", () => { const memory = rememberVisit(); FEATURES.forEach((addFeature) => addFeature(memory)); }); }());