From 2e3efb201b7fb7e7162277412a82913b6e7b1a38 Mon Sep 17 00:00:00 2001 From: Zaine Arch Date: Sun, 10 May 2026 15:00:06 +0100 Subject: [PATCH] fixing the hidden js --- assets/scripts/hidden-details.js | 231 +++++++++++++++++++++---------- 1 file changed, 157 insertions(+), 74 deletions(-) diff --git a/assets/scripts/hidden-details.js b/assets/scripts/hidden-details.js index 3962099..d9e710a 100644 --- a/assets/scripts/hidden-details.js +++ b/assets/scripts/hidden-details.js @@ -1,13 +1,37 @@ (function () { "use strict"; - const KEY = "zxh_hidden_details_v2"; - const OLD_KEY = "zxh_hidden_details_v1"; + /* + * 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 + // ----------------------------- + const familyLayers = [ "Layer 0: surface website - notes, pages, tools, normal navigation.", "Layer 1: casual hidden jokes - Aphy notices clicks, typos, tabs, and old-web habits.", @@ -244,16 +268,69 @@ ] }; + // Exact search text -> toast message. + const SEARCH_TOASTS = { + lima: "Search result: warmth, Oct 2025, soon-to-be home.", + aphy: "Aphy: present. Mostly helpful. Occasionally poetic by accident.", + "sensei chi": "Sensei Chi: the search is also searching you.", + "young z": "Search result: age 7, blanket cape, crayon sun.", + "future z": "Future Z: I cannot spoil the ending. I can say keep going.", + "2022": "lima layer: beginning stored as warmth, not trivia.", + "oct 2025": "Engagement memory: the calendar learned to glow.", + "age 7": "Young Z layer: crayons, cartoons, and a cape made from a blanket.", + "age 40": "Future Z: I am tired sometimes, but not defeated." + }; + + // Exact search text -> hidden page route. + const SEARCH_ROUTES = { + "kitchen light": "/play/kitchen-light.html", + "lima note": "/play/lima-note.html", + "aphy console": "/play/aphy-console.html", + "sensei garden": "/play/sensei-garden.html", + "young z desk": "/play/young-z-desk.html", + "future log": "/play/future-log.html", + "family layer index": "/play/family-layer-index.html", + "do not open": "/play/do-not-open.html", + "voice note": "/play/cassette-log.html", + "time platform": "/play/train-platform.html", + "unfinished letter": "/play/forgotten-draft.html", + "burnt sugar": "/play/corrupted-recipe.html", + cupboard: "/play/terminal-cupboard.html", + patience: "/play/patience-game.html" + }; + + // Short typed phrases. Stored in sessionStorage as a rolling key chain. + const KEYBOARD_SECRETS = [ + { phrase: "remember", message: "lima keeps the memory grounded. Aphy keeps the backup." }, + { phrase: "dream", route: "/play/dream-corridor.html" }, + { phrase: "layer index", route: "/play/family-layer-index.html" } + ]; + + // Longer typed phrases and character names. + const LONG_KEYBOARD_SECRETS = [ + { phrase: "leave the light on", route: "/play/kitchen-light.html" }, + { phrase: "oldweb", message: "Aphy briefly considers a table layout, then apologizes to CSS." }, + { phrase: "lima", message: "lima note: I am glad you made something this sincere." }, + { phrase: "sensei", message: "Sensei Chi: a hidden word is still a word." }, + { phrase: "young z", message: "Young Z has drawn a door in the margin." }, + { phrase: "future z", message: "Future Z: keep the gentle parts. They compound." }, + { phrase: "aphy", song: true } + ]; + + // ----------------------------- + // STATE HELPERS + // ----------------------------- + function readState() { let current = {}; try { - current = JSON.parse(localStorage.getItem(KEY) || "{}"); + current = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}"); } catch (_) {} if (!current.visits) { try { - const oldState = JSON.parse(localStorage.getItem(OLD_KEY) || "{}"); + const oldState = JSON.parse(localStorage.getItem(OLD_STORAGE_KEY) || "{}"); current = { ...oldState, migratedFromV1: true }; - localStorage.setItem(KEY, JSON.stringify(current)); + localStorage.setItem(STORAGE_KEY, JSON.stringify(current)); } catch (_) {} } return current; @@ -261,7 +338,7 @@ function writeState(next) { try { - localStorage.setItem(KEY, JSON.stringify(next)); + localStorage.setItem(STORAGE_KEY, JSON.stringify(next)); } catch (_) {} } @@ -279,6 +356,10 @@ return Math.abs(h >>> 0); } + // ----------------------------- + // UI HELPERS + // ----------------------------- + function toast(message, duration) { let el = document.querySelector(".hidden-toast"); if (!el) { @@ -293,6 +374,22 @@ 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 : []; @@ -321,6 +418,10 @@ writeState(next); } + // ----------------------------- + // GLOBAL FEATURES + // ----------------------------- + function addFooterNote(memory) { const footer = document.querySelector("footer"); if (!footer) return; @@ -342,6 +443,7 @@ } 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; @@ -359,6 +461,7 @@ } 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"; @@ -381,7 +484,11 @@ }); } - function addPatterns(memory) { + 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); @@ -401,12 +508,11 @@ if (search) { search.addEventListener("input", () => { const value = search.value.trim().toLowerCase(); - if (value === "lima") toast("Search result: warmth, Oct 2025, soon-to-be home."); - if (value === "aphy") toast("Aphy: present. Mostly helpful. Occasionally poetic by accident."); - if (value === "sensei chi") toast("Sensei Chi: the search is also searching you."); - if (value === "young z") toast("Search result: age 7, blanket cape, crayon sun."); - if (value === "future z") toast("Future Z: I cannot spoil the ending. I can say keep going."); - if (value === "kitchen light") window.location.href = "/play/kitchen-light.html"; + if (SEARCH_TOASTS[value]) toast(SEARCH_TOASTS[value]); + if (SEARCH_ROUTES[value]) { + awardLayer(layerForSearch(value), `search ${value}`); + redirectTo(SEARCH_ROUTES[value]); + } }); } @@ -414,9 +520,9 @@ const key = event.key.toLowerCase(); const chain = `${sessionStorage.getItem("zxh_key_chain") || ""}${key}`.slice(-18); sessionStorage.setItem("zxh_key_chain", chain); - if (chain.endsWith("remember")) toast("lima keeps the memory grounded. Aphy keeps the backup."); - if (chain.endsWith("dream")) window.location.href = "/play/dream-corridor.html"; - if (chain.endsWith("layer index")) window.location.href = "/play/family-layer-index.html"; + KEYBOARD_SECRETS + .filter((secret) => chain.endsWith(secret.phrase)) + .forEach(runSecretAction); }); if (memory.seen?.length >= 6 && !state.travellerNoteShown) { @@ -519,6 +625,7 @@ } 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", "Family Layer Index objects"); @@ -551,6 +658,7 @@ } 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"; @@ -610,51 +718,19 @@ }); } - function addSearchAndKeySecrets() { - const search = document.querySelector("#search-input"); - if (search) { - search.addEventListener("input", () => { - const value = search.value.trim().toLowerCase(); - const routes = { - "lima note": "/play/lima-note.html", - "aphy console": "/play/aphy-console.html", - "sensei garden": "/play/sensei-garden.html", - "young z desk": "/play/young-z-desk.html", - "future log": "/play/future-log.html", - "family layer index": "/play/family-layer-index.html", - "do not open": "/play/do-not-open.html", - "voice note": "/play/cassette-log.html", - "time platform": "/play/train-platform.html", - "unfinished letter": "/play/forgotten-draft.html", - "burnt sugar": "/play/corrupted-recipe.html", - "cupboard": "/play/terminal-cupboard.html", - "patience": "/play/patience-game.html" - }; - if (routes[value]) { - awardLayer(value.includes("future") ? 4 : value.includes("sensei") || value.includes("aphy") ? 3 : 2, `search ${value}`); - window.location.href = routes[value]; - } - if (value === "2022") toast("lima layer: beginning stored as warmth, not trivia."); - if (value === "oct 2025") toast("Engagement memory: the calendar learned to glow."); - if (value === "age 7") toast("Young Z layer: crayons, cartoons, and a cape made from a blanket."); - if (value === "age 40") toast("Future Z: I am tired sometimes, but not defeated."); - }); - } - + 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); - if (chain.endsWith("leave the light on")) window.location.href = "/play/kitchen-light.html"; - if (chain.endsWith("oldweb")) toast("Aphy briefly considers a table layout, then apologizes to CSS."); - if (chain.endsWith("lima")) toast("lima note: I am glad you made something this sincere."); - if (chain.endsWith("sensei")) toast("Sensei Chi: a hidden word is still a word."); - if (chain.endsWith("young z")) toast("Young Z has drawn a door in the margin."); - if (chain.endsWith("future z")) toast("Future Z: keep the gentle parts. They compound."); - if (chain.endsWith("aphy")) playTinySong(); + 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 = () => { @@ -701,6 +777,7 @@ } 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", () => { @@ -789,27 +866,33 @@ }[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(); - addFooterNote(memory); - addHomepageGreeting(memory); - addWhispers(); - addCornerObject(); - addPatterns(memory); - addRareEvents(); - addSecretLinks(); - enhanceErrors(); - addContinuity(memory); - addSeasonAndDates(memory); - addObjectConstellation(); - addWeatherWindow(memory); - addOldWebLayer(memory); - addSourceRelics(); - addSearchAndKeySecrets(); - addPatienceRewards(memory); - addRareSecondLayer(memory); - addFamilyLayerIndex(memory); - addPageSpecificSecrets(memory); - addInvisibleHoverSecrets(); + FEATURES.forEach((addFeature) => addFeature(memory)); }); }());