Files
org_web/assets/scripts/hidden-details.js
Zaine Arch 3531636805
All checks were successful
Build Org Website / build (push) Successful in 48s
change name
2026-05-10 00:04:35 +01:00

773 lines
36 KiB
JavaScript

(function () {
"use strict";
const KEY = "zxh_hidden_details_v1";
const now = new Date();
const hour = now.getHours();
const path = window.location.pathname;
const state = readState();
const details = [
"The footer keeps a tiny chair for anyone who scrolls this far.",
"Someone labelled the spare cables: important, mysterious, probably for a printer.",
"Memory is just a room that learned how to echo kindly.",
"If you are reading this at night, please unclench your jaw.",
"A bookmark fell asleep here and dreamed it was a door.",
"SYSTEM: kettle confidence restored.",
"Mum says the rice is never late, only thinking.",
"Dad left a note: check the plug, then check your assumptions.",
"The attic server hums like it knows an old song.",
"A small lantern named Morrow was seen under the navigation bar.",
"Home is the place where the mismatched mugs know your hands.",
"This tooltip has been waiting all afternoon to feel useful.",
"404s are just hallways with very sincere lighting.",
"The website looked away politely while you reread that sentence.",
"Winter folded the garden into a quiet letter.",
"Spring arrived wearing yesterday's coat and nobody minded.",
"Autumn keeps receipts from every golden hour.",
"Summer is a glass of water forgotten beside a book.",
"Auntie wrote: do not trust empty biscuit tins. They are emotionally complicated.",
"The comment box occasionally practices being brave.",
"There is soup in the archive, but only metaphorically. Probably.",
"Every old hard drive contains at least one photo called final_final_really.",
"The cursor paused here once. We still talk about it.",
"Friendship is two people saving the better chair without discussing it.",
"Tonight, the page is trying to be a porch light.",
"A tiny bell rings whenever someone chooses kindness and nobody notices.",
"The moon asked the stylesheet for softer margins.",
"You have visited before. The dust remembered your shoes.",
"The search box knows more than it says and less than it implies.",
"A good recipe is a family argument that became edible.",
"Grandad marked this line with a pencil and then forgot why.",
"Do not feed the calendar after midnight; it gets sentimental.",
"The page is handmade. Some fingerprints are load-bearing.",
"Some dreams are just old rooms asking to be opened gently.",
"Loneliness is not proof that you are hard to love.",
"The sidebar once tried poetry. It was mostly indentation.",
"If the site blinks, blink back. It is only being polite.",
"The archive has a cousin called Nettle who files everything under Maybe.",
"Morrow says: the lake is not empty, it is saving room.",
"A spoon knocked twice from inside the recipe page.",
"There are no achievements here. Only small doors and warm lights.",
"The browser tab is pretending to be calm.",
"This sentence was left in a coat pocket from 2009.",
"A family tradition: someone says five minutes and everyone understands twenty.",
"The oldest photo on the shelf still remembers the smell of rain.",
"Some pages do not want to be found quickly.",
"The loading screen packed snacks, just in case.",
"You are allowed to leave a page unfinished.",
"The website is not judging your tabs. It has seen worse.",
"A small blue bowl appears in three memories and no inventories.",
"The old internet was slower, which gave anticipation somewhere to sit.",
"A guestbook entry from nobody says: I was here, kindly.",
"The clock does not chase you here.",
"Nettle borrowed the semicolon and promised to bring it back.",
"Every link is a tiny act of trust.",
"The error page brought a blanket.",
"Sometimes growing older is just learning which lights to leave on.",
"The hallway smells faintly of toast and forgotten passwords.",
"A dream page exists behind a word you have not clicked enough.",
"This site remembers visits, not identities.",
"A note under the doormat reads: take your time.",
"The system log says: heart rebooted successfully.",
"Somebody dusted the pixels before you arrived.",
"Morrow has a lantern. Nettle has a clipboard. Neither has a plan.",
"The kitchen light is on because somebody might come home late.",
"If you found this by accident, that still counts as being invited.",
"The page tried to hum but settled for rendering.",
"A childhood afternoon is stored here at room temperature.",
"The comments are quiet because they are listening.",
"The sitemap has dreams of becoming a treasure map.",
"Do not worry if the day felt unfinished. Most good days do.",
"This button has been clicked before and took it personally.",
"A cupboard in the footer contains emergency biscuits and one tiny apology.",
"Nostalgia is a window with fingerprints on both sides.",
"A cousin once renamed the Wi-Fi to Pretty Please Work.",
"The site keeps a spare sunrise for difficult mornings.",
"You can rest your eyes. The paragraph will wait.",
"A missing page is still a page if it misses you back.",
"The kettle, the clock, and the router have formed a committee.",
"Sometimes the kindest archive is the one that lets things fade.",
"The moon cached this page for offline tenderness.",
"A small paper star is stuck behind the header.",
"The old forum signature reads: be right back, becoming a person.",
"SYSTEM: nostalgia level acceptable. Tea recommended.",
"A dream wrote itself in lowercase and hid under the bed.",
"There is a room in the site where every chair faces the window.",
"A soup recipe was annotated with: add patience until it smells like home.",
"The site has a memory of dial-up and uses it only for dramatic timing.",
"Kindness is often just good timing with shoes on.",
"Some nights the archive counts sheep in binary.",
"The lamp on the study page knows exactly when you are tired.",
"A little paper boat named Afterward is still crossing the margin.",
"The scroll bar has travelled far and asks for no medal.",
"A fake system warning: feelings detected in production.",
"This hidden message has no puzzle value. It is just glad you came.",
"If today was heavy, put it down beside the footer for a minute.",
"Morrow saw your cursor and pretended not to.",
"A family note says: whoever finishes the milk becomes the historian.",
"The website keeps one chair empty for the person you used to be.",
"There is a tiny weather report inside the logo: possible comfort.",
"The secret is not that the site is alive. The secret is that you are.",
"A final scrap says: come back when the page is older."
];
const poems = [
"A room remembers softly / the coat hook, the rain, the key / and you, arriving later.",
"Time put sugar in its tea / and called the afternoon / almost enough.",
"An old friend writes once / and the whole year / grows a window.",
"Dreams leave socks on the floor / and deny everything / by morning.",
"Home is not a place / so much as a light / that forgives your lateness.",
"The garden went quiet / not from sadness / but from listening to roots.",
"A childhood summer / fits in one glass marble / if you hold it up to sun.",
"The clock kept walking / so I gave it a chair / and sat beside it."
];
const greetings = [
"The page saved you a seat.",
"Welcome back to the handmade part of the internet.",
"The kettle just clicked. Good timing.",
"A small light is on in the archive.",
"Today has entered quietly.",
"You are visitor number: emotionally significant.",
"The dashboard shuffled its papers before you arrived.",
"Morrow says hello from somewhere under the floorboards."
];
const nightMessages = [
"It is late here. Drink some water before the next page.",
"Night mode inside night mode: you are allowed to be gentle with yourself.",
"The site lowered its voice because the house is sleeping.",
"A late visitor gets the soft lamp."
];
const secondLayer = {
quotes: [
"The archive does not keep score. It keeps chairs.",
"Some families pass down recipes. This one also passes down tabs left open for later.",
"A page can be a room if enough people arrive softly.",
"The oldest link on the site still expects a hand cursor.",
"If a memory changes, it may be trying to fit through a smaller door.",
"The guestbook is not empty. It is holding its breath between signatures.",
"Every backup is a small superstition against forgetting.",
"Somewhere in the CSS, a lamp is deciding whether to stay on.",
"The radio under the stairs only plays songs it has already missed.",
"A train can leave a station and still remain in the family story.",
"Kindness is the only password the house accepts without checking case.",
"The website learned your pace and stopped rushing the hallway.",
"A draft is just a letter still gathering courage.",
"The cursor is a visitor. The margin is a host.",
"If you hover long enough, even a heading may remember being handwritten.",
"The old forum banner says: welcome back, even if you are new.",
"Not all broken pages need repair. Some need a name.",
"The site keeps its mysteries low to the ground, where tired people can reach them.",
"A year is a folder that keeps renaming itself.",
"Some evenings are survived by the exact right mug."
],
poems: [
"A train in the rain / waits beside an empty platform / so memory can board.",
"Grandma's radio / tuned between two stations / found us anyway.",
"A flower pressed flat / still teaches the book / how spring once leaned.",
"The lamp stayed awake / not because it was brave / but because someone might need it.",
"Old usernames fade / but the welcome thread remains / warm at the top.",
"I found a draft / with no ending / and felt less alone.",
"The house at midnight / is mostly refrigerator hum / and unsent kindness.",
"A calendar page / falls without bitterness / into tomorrow's hands.",
"There is a road home / made entirely of small tabs / you meant to close.",
"A cassette clicks / and the room becomes younger / for one more minute."
],
conversations: [
["Nettle", "The ticket drawer is humming again.", "Morrow", "That is normal. It hums when someone is almost ready."],
["Aunt Ma", "Who put the screwdriver in the biscuit tin?", "Nadir", "A person with vision and no labelling discipline."],
["Morrow", "The lamp keeps following the visitor.", "Etta", "Good. Lamps should have hobbies."],
["Old Guest 14", "is this page still alive?", "Webmaster", "yes, but quietly, please. the baby is asleep."],
["Nettle", "Filed under: feelings disguised as infrastructure.", "Morrow", "Approved without changes."],
["Luma", "The radio said my name.", "Aunt Ma", "Then say salaam back. Manners matter."],
["Nadir", "The server made soup noises.", "Etta", "That means the deployment is nearly done."],
["Morrow", "I found a page that says do not open.", "Nettle", "That usually means open gently."]
],
journals: [
"Journal scrap, 2006: We changed the wallpaper and everyone pretended it was not emotional.",
"Journal scrap, 2011: The old router blinked like it was trying to stay in the conversation.",
"Journal scrap, 2018: I learned that homesickness can happen inside your own house.",
"Journal scrap, 2024: Somebody laughed in the kitchen and the whole week became easier.",
"Journal scrap, undated: The marigolds came back where nobody planted them.",
"Journal scrap, undated: We kept the cracked mug because it knew too much.",
"Journal scrap, winter: I saved a voicemail just to remember the background noise.",
"Journal scrap, summer: The fan turned all afternoon and made the curtains look like boats."
],
warnings: [
"COMFORT WARNING: this page may contain traces of old carpet, warm rice, and unfinished errands.",
"SOFT ERROR: the archive attempted to be impressive and became sincere instead.",
"NOTICE: do not stack heavy feelings on the top shelf.",
"LEGACY WARNING: this layout remembers 800x600 and asks you to be respectful.",
"SYSTEM MESSAGE: no achievement unlocked. You were kind anyway.",
"CACHE NOTE: the page kept a copy of the good part.",
"RECOVERY MODE: make tea, rename file, try again.",
"PERMISSION DENIED: the sadness may enter, but it must wipe its feet."
],
dreams: [
"Dream 01: a train full of empty mugs waited at a station called Later.",
"Dream 02: the search box became a letterbox and returned every question with a family recipe.",
"Dream 03: a radio under the stairs played tomorrow's weather in a grandparent's voice.",
"Dream 04: the old homepage had a porch, and every link was a moth-wing-soft knock.",
"Dream 05: someone had folded the sitemap into a paper flower and left it in a browser tab."
],
cassettes: [
"CASSETTE A / side 1: You can hear plates, rain, and someone saying, no, leave the light on.",
"CASSETTE A / side 2: Three seconds of silence, then laughter too close to the microphone.",
"CASSETTE B / side 1: Nadir explains backups to a child using rice grains and a spoon.",
"CASSETTE C / side 2: A song with no words, hummed badly, loved completely.",
"CASSETTE D / side ?: The tape label only says for when the house feels far away."
],
fakeUsers: [
"sunnydialup88: first!!!! but emotionally",
"auntie-admin: please stop uploading files named newnewnewfinal",
"railway_morrow: platform 3 smells like rain today",
"nettle_mod: moved thread from Bugs to Feelings",
"luma-lamp: does anyone else hear the margins singing",
"guest_000: I signed this once and then grew up"
],
seasonal: {
winter: "The archive put an extra blanket on the footer.",
spring: "A pressed flower appeared between two cached pages.",
summer: "The page opened a window and let the fan turn the silence.",
autumn: "The old links sound like leaves when the cursor passes."
},
homepageTakeovers: [
"For one rare minute, the homepage believes it is an old family desktop.",
"The dashboard has become a late-night noticeboard with one pin missing.",
"The homepage found its old forum skin in a drawer and is trying it on."
],
roomLinks: [
["/play/radio-room.html", "radio room"],
["/play/guestbook-1999.html", "guestbook 1999"],
["/play/do-not-open.html", "do not open"],
["/play/cassette-log.html", "cassette log"],
["/play/train-platform.html", "train platform"],
["/play/forgotten-draft.html", "forgotten draft"],
["/play/corrupted-recipe.html", "corrupted recipe"],
["/play/terminal-cupboard.html", "terminal cupboard"],
["/play/patience-game.html", "patience game"]
]
};
function readState() {
try {
return JSON.parse(localStorage.getItem(KEY) || "{}");
} catch (_) {
return {};
}
}
function writeState(next) {
try {
localStorage.setItem(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);
}
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);
}
el.textContent = message;
el.classList.add("is-visible");
window.clearTimeout(el._timer);
el._timer = window.setTimeout(() => el.classList.remove("is-visible"), duration || 5200);
}
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 next = {
...state,
visits,
lastPath: path,
pathCounts,
seen: Array.from(new Set([...seen, path])).slice(-80),
firstSeen: state.firstSeen || new Date().toISOString(),
lastSeen: new Date().toISOString()
};
writeState(next);
return next;
}
function addFooterNote(memory) {
const footer = document.querySelector("footer");
if (!footer) return;
const note = document.createElement("p");
note.className = "hidden-footer-note";
const base = pick(details, "footer");
note.textContent = 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) return;
const greeting = pick(greetings, `greeting-${memory.visits}`);
target.textContent = greeting;
const extra = document.createElement("p");
extra.className = "hidden-greeting";
extra.textContent = memory.visits > 1 ? "The site remembers this is not your first doorway." : "First visits count as tiny ceremonies.";
target.closest("div")?.appendChild(extra);
}
function addWhispers() {
const paragraphs = Array.from(document.querySelectorAll("main p, #content p, article p")).filter((p) => p.textContent.trim().length > 80);
paragraphs.slice(0, 4).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 = " ?";
mark.title = pick(index % 2 ? poems : details, `whisper-${index}`);
mark.addEventListener("click", () => toast(mark.title));
p.appendChild(mark);
});
}
function addCornerObject(memory) {
const object = document.createElement("button");
object.className = "hidden-corner-object";
object.type = "button";
object.title = "A very small archive object";
object.textContent = memory.visits % 2 ? "*" : "~";
document.body.appendChild(object);
let clicks = 0;
object.addEventListener("click", () => {
clicks += 1;
const messages = [
"The object is warmer than before.",
"Tap tap. Somewhere, a spoon answers.",
"Morrow whispers: that is not a button, but it appreciates the attention.",
"Nettle files a report titled Repeated Kindness.",
"A loose panel opens in the idea of the page."
];
toast(messages[Math.min(clicks - 1, messages.length - 1)]);
if (clicks === 7) {
window.location.href = "/play/left-behind.html";
}
});
}
function addPatterns(memory) {
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));
toast(taps >= 5 ? "The logo opens one eye. Try /play/dream-corridor.html" : "The logo makes a tiny ceramic sound.");
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 (value === "morrow") toast("Search result: lantern, lake, unfinished apology.");
if (value === "nettle") toast("Search result: clipboard, cupboard, suspicious semicolon.");
if (value === "home") toast("Search result: warm window, spare key, somebody humming downstairs.");
if (value === "kitchen light") window.location.href = "/play/kitchen-light.html";
});
}
document.addEventListener("keydown", (event) => {
const key = event.key.toLowerCase();
const chain = `${sessionStorage.getItem("zxh_key_chain") || ""}${key}`.slice(-12);
sessionStorage.setItem("zxh_key_chain", chain);
if (chain.endsWith("remember")) toast("The website remembers gently, which is the only useful kind.");
if (chain.endsWith("dream")) window.location.href = "/play/dream-corridor.html";
});
if (memory.seen?.length >= 6 && !state.travellerNoteShown) {
window.setTimeout(() => {
toast("The map noticed you wandering and left the hallway lights on.");
writeState({ ...readState(), travellerNoteShown: true });
}, 1600);
}
}
function addRareEvents() {
if (hour >= 22 || hour < 5) {
document.body.classList.add("hidden-late-night");
window.setTimeout(() => toast(pick(nightMessages, "night"), 2200), 900);
}
const roll = Math.random();
if (roll < 0.004) {
window.setTimeout(() => showDriftNote("A very rare page breeze passed through.", " .--.\n ( ) <- a cloud pretending to be a thought\n `--'"), 1800);
} else if (roll < 0.012) {
window.setTimeout(() => toast("RARE SYSTEM EVENT: the archive found a biscuit in its coat pocket."), 2400);
}
}
function showDriftNote(text, art) {
const panel = document.createElement("aside");
panel.className = "hidden-drift-note";
panel.innerHTML = `<div>${escapeHtml(text)}</div><pre>${escapeHtml(art)}</pre><button type="button">Close</button>`;
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"],
...secondLayer.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(pick([
"This page is missing, but you are not. Try the sitemap, or sit here a second.",
"404: the page went to check on the rice and has not returned.",
"The address is wrong, but the welcome is still valid.",
"Nothing is here except a very apologetic lamp."
], "error"));
}
}
function addContinuity(memory) {
const milestones = {
2: "The site has placed your second visit beside the spare keys.",
5: "A quiet ledger now lists you under people who came back.",
9: "Nettle has started a folder called regular visitors, handle gently.",
17: "Morrow left a platform ticket in the footer for you.",
31: "The archive trusts you enough to misplace something important nearby.",
64: "A very old counter turned over and made the sound of a cupboard closing."
};
if (milestones[memory.visits] && !state[`milestone_${memory.visits}`]) {
window.setTimeout(() => toast(milestones[memory.visits], 6500), 1200);
writeState({ ...readState(), [`milestone_${memory.visits}`]: true });
}
const count = memory.pathCounts?.[path] || 0;
if ([3, 7, 12].includes(count)) {
window.setTimeout(() => toast([
"This page recognizes the shape of your return.",
"The paragraph straightened the cushions before you arrived.",
"A repeat visit is the gentlest kind of knock."
][[3, 7, 12].indexOf(count)], 6200), 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) {
window.setTimeout(() => toast(secondLayer.seasonal[season], 5200), 2600);
}
if (month === 4 && day === 9) {
window.setTimeout(() => toast("Site birthday: the candles are imaginary, but the crumbs are real."), 900);
}
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: the site saved a paper flower from your first day here.", 7000), 1400);
}
if (now.getMinutes() === 13) {
window.setTimeout(() => toast("At minute thirteen, the radio under the stairs clears its throat."), 2100);
}
}
function addObjectConstellation(memory) {
const rail = document.createElement("div");
rail.className = "hidden-object-rail";
rail.setAttribute("aria-label", "Hidden keepsake objects");
const objects = [
["lamp", "The lamp clicks once, then pretends it did not."],
["mug", "The mug is chipped in exactly the familiar place."],
["ticket", "The ticket says RETURN, but not to where."],
["radio", "The radio hums between weather and memory."],
["flower", "The pressed flower has kept its appointment with spring."]
];
objects.forEach(([name, message]) => {
const button = document.createElement("button");
button.type = "button";
button.className = `hidden-keepsake hidden-keepsake--${name}`;
button.title = name;
button.textContent = { lamp: "i", mug: "u", ticket: "=", radio: "o", flower: "+" }[name];
button.addEventListener("click", () => {
const next = readState();
next.keepsakes = Array.from(new Set([...(next.keepsakes || []), name]));
writeState(next);
toast(message);
if (next.keepsakes.length >= objects.length) {
window.setTimeout(() => toast("The five keepsakes align. Search for radio under stairs."), 1100);
}
});
rail.appendChild(button);
});
document.body.appendChild(rail);
}
function addWeatherWindow(memory) {
if (memory.visits < 3 || !("geolocation" in navigator)) return;
const button = document.createElement("button");
button.className = "hidden-weather-window";
button.type = "button";
button.title = "Ask the outside window";
button.textContent = "window";
button.addEventListener("click", () => {
toast("The window is checking whether the outside agrees with the archive.");
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)}&current=temperature_2m,precipitation,weather_code&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. Inside: the archive moved a towel closer to the door. ${temp}C.` : `Outside: ${temp}C. Inside: possible comfort, mild nostalgia.`);
})
.catch(() => toast("The weather window fogged up, so the site invented indoor weather: quiet with a chance of tea."));
}, () => toast("No outside weather today. The indoor forecast is lamp-light and manageable clouds."));
});
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";
stamp.title = pick(secondLayer.fakeUsers, "fake-user");
stamp.textContent = "best viewed with patience";
stamp.addEventListener("click", () => toast(stamp.title));
document.body.appendChild(stamp);
}
const comments = document.querySelector("#comments");
if (comments && !comments.querySelector(".hidden-guestbook-line")) {
const line = document.createElement("p");
line.className = "hidden-guestbook-line";
line.textContent = pick(secondLayer.fakeUsers, "comment-lore");
comments.appendChild(line);
}
}
function addSourceRelics() {
// RELIC: Aunt Ma says source comments are the attic of the page.
const marker = document.createComment("hidden-song: hum the lamp, hum the train, hum the long way home");
document.documentElement.appendChild(marker);
document.querySelectorAll("img[alt=''], img:not([alt])").forEach((img, index) => {
if (index > 2) return;
img.alt = pick([
"A quiet image that remembers being taped into a scrapbook.",
"An old visual keepsake, labelled by someone who arrived later.",
"A picture with its shoes by the door."
], `alt-${index}`);
});
}
function addSearchAndKeySecrets() {
const search = document.querySelector("#search-input");
if (search) {
search.addEventListener("input", () => {
const value = search.value.trim().toLowerCase();
const routes = {
"radio under stairs": "/play/radio-room.html",
"guestbook 1999": "/play/guestbook-1999.html",
"do not open": "/play/do-not-open.html",
"cassette": "/play/cassette-log.html",
"return ticket": "/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]) window.location.href = routes[value];
if (value === "marigold") toast("Search result: pressed flower, May rain, a promise nobody announced.");
if (value === "luma") toast("Search result: lamp-child, margin-song, brave small glow.");
if (value === "nadir") toast("Search result: backups, train tickets, practical tenderness.");
});
}
document.addEventListener("keydown", (event) => {
const chain = `${sessionStorage.getItem("zxh_second_chain") || ""}${event.key.toLowerCase()}`.slice(-18);
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("A 2004 table layout briefly considers returning, then thinks better of it.");
if (chain.endsWith("ma")) toast("Aunt ma says the screwdriver is absolutely not in the biscuit tin.");
if (chain.endsWith("luma")) playTinySong();
});
}
function addPatienceRewards(memory) {
if (memory.visits < 2) return;
let idleTimer = null;
const startIdle = () => {
window.clearTimeout(idleTimer);
idleTimer = window.setTimeout(() => {
toast(pick([
"Thank you for waiting. The page used the time to remember a window.",
"Patience noted. No badge will be shown, which is how the archive prefers it.",
"A tiny secret achievement happened quietly and did not ask to be announced."
], "idle"), 7000);
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(memory) {
const roll = Math.random();
if ((path === "/" || path === "/index.html") && roll < 0.006) {
document.body.classList.add("hidden-home-takeover");
window.setTimeout(() => showDriftNote(pick(secondLayer.homepageTakeovers, "takeover"), "WELCOME_BACK.GIF\nUNDER_CONSTRUCTION\nSIGN_GUESTBOOK"), 600);
} else if (roll < 0.002) {
window.setTimeout(() => showDriftNote("A cassette starts by itself, then politely asks permission.", "play? y/n\n[no autoplay. the site has manners.]"), 1500);
} else if (roll < 0.006) {
window.setTimeout(() => toast(pick(secondLayer.dreams, "rare-dream"), 7000), 2000);
} else if (roll < 0.014) {
window.setTimeout(() => toast(pick(secondLayer.warnings, "rare-warning"), 6200), 2300);
}
}
function addPageSpecificSecrets(memory) {
if (path.includes("/play/cassette-log")) {
document.querySelectorAll("[data-cassette]").forEach((button, index) => {
button.addEventListener("click", () => {
toast(secondLayer.cassettes[index % secondLayer.cassettes.length], 7000);
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)) {
target.textContent = seconds === 30 ? "The lamp notices you are still here." : seconds === 90 ? "The train has not left without you." : "The room trusts your quiet.";
}
}, 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: listen, inventory, tea, open, remember, exit",
listen: "Behind the jars: a radio, rain, one sentence from 2003.",
inventory: "one cracked mug; two return tickets; marigolds; screwdriver absent, allegedly.",
tea: "The cupboard approves. Morale +1. Biscuit tin remains suspicious.",
open: "The cupboard opens into another cupboard. This is a family tradition.",
remember: "You remember a room you may never have visited.",
exit: "The cupboard lets you leave with both hands full of nothing heavy."
};
input?.addEventListener("keydown", (event) => {
if (event.key !== "Enter") return;
const value = input.value.trim().toLowerCase();
const line = document.createElement("p");
line.textContent = `> ${value}\n${commands[value] || "The cupboard does not know that command, but respects the attempt."}`;
log.appendChild(line);
input.value = "";
});
}
}
function addInvisibleHoverSecrets() {
document.querySelectorAll("h1, h2").forEach((heading, index) => {
if (index > 5) return;
heading.classList.add("hidden-hover-memory");
heading.dataset.hiddenMemory = pick([...secondLayer.quotes, ...secondLayer.poems], `heading-${index}`);
});
}
function playTinySong() {
try {
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (!AudioContext) return;
const ctx = new AudioContext();
const notes = [392, 440, 330, 294, 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) => ({
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;"
}[char]));
}
document.addEventListener("DOMContentLoaded", () => {
const memory = rememberVisit();
addFooterNote(memory);
addHomepageGreeting(memory);
addWhispers();
addCornerObject(memory);
addPatterns(memory);
addRareEvents();
addSecretLinks();
enhanceErrors();
addContinuity(memory);
addSeasonAndDates(memory);
addObjectConstellation(memory);
addWeatherWindow(memory);
addOldWebLayer(memory);
addSourceRelics();
addSearchAndKeySecrets();
addPatienceRewards(memory);
addRareSecondLayer(memory);
addPageSpecificSecrets(memory);
addInvisibleHoverSecrets();
});
}());