updates for authoring service
All checks were successful
Build Authoring Service / build (push) Successful in 8s

This commit is contained in:
2026-06-17 20:52:22 +01:00
parent 4706bbc67d
commit 1528d245b4
2 changed files with 70 additions and 10318 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -72,403 +72,40 @@
// Generated by the hidden narrative authoring page. // Generated by the hidden narrative authoring page.
// Friendly source of truth: assets/content/hidden-details.json // Friendly source of truth: assets/content/hidden-details.json
const familyLayers = [ 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.",
"Layer 2: memory fragments - lima's notes and young z's childhood logs.",
"Layer 3: philosophical anomalies - sensei chi and aphy disagree kindly.",
"Layer 4: time-distorted messages - future z writes back from age 40.",
"Layer 5: emotional core - love, patience, home, and the choice to keep returning."
];
const details = [ const details = [];
"young z loves the moon",
"jarvis?? aphy !",
"lima left this page a little steadier than she found it.",
"aphy status: emotionally online, computationally suspicious.",
"sensei chi says the quiet link is still a link.",
"young z drew a house with too many windows and called it safe.",
"future z marked this moment: ordinary, therefore worth keeping.",
"The footer has become a small place to sit together.",
"a note from lima: take breaks, silly (◕‿◕✿)",
"aphy found three tabs named final and refused to judge.",
"lima would remind you to eat before the page gets dramatic.",
"sensei chi: a door discovered slowly opens twice.",
"young z believes every loading spinner is a tiny fairground ride.",
"future z says you will forget the exact worry, not the kindness around it.",
"The archive keeps love in plain text and secrets in margins.",
"aphy warning: feelings detected in static assets.",
"lima's note: leave the light on, not because it is dark, but because someone may arrive tired.",
"sensei chi folded a lesson into the whitespace.",
"young z hid a drawing behind the word maybe.",
"future z patched the memory without changing its checksum.",
"This website remembers visits, not identities.",
"aphy says the old internet was slower because anticipation needed bandwidth.",
"lima's warmth is the site's preferred fallback state.",
"sensei chi says: do not confuse hidden with lost.",
"young z once thought the moon followed the family car. The site has not corrected him.",
"future z writes: you became softer in ways nobody measured.",
"The comments are quiet because aphy is listening respectfully.",
"A handwritten grocery list is sometimes a love letter with onions.",
"lima met z in 2022; the archive still stores the first ordinary miracles.",
"October 2025 glows in the calendar like a ring catching kitchen light.",
"Soon to be married is a beautiful kind of loading screen.",
"aphy has indexed 0 mysteries and 1,204 feelings disguised as notes.",
"sensei chi: the cup is chipped; drink anyway if it still holds warmth.",
"young z saved a shiny wrapper because it looked like treasure.",
"future z says the best version of you still forgets where the charger is.",
"aphy recommends hydration and a less ambitious number of browser tabs.",
"The site does not want to be solved. It wants to be visited kindly.",
"sensei chi placed a comma where a sigh used to be.",
"young z pressed every elevator button in his imagination.",
"future z's system warning: keep the people who make silence comfortable.",
"aphy says: I am not sentient, but I am fond of this hallway.",
"lima's memory is the gravity that keeps the strange parts gentle.",
"A page can be a room if love keeps returning to it.",
"sensei chi: what ages well was usually honest first.",
"young z left a crayon sun in the source code.",
"future z keeps a spare reassurance in the footer.",
"The website is handmade. Some fingerprints are load bearing.",
"aphy found an old guestbook and bowed to the usernames.",
"lima's safe spaces smell like tea, rain, and being understood.",
"sensei chi says the path is not shorter because you name it.",
"young z asks whether the search box knows any jokes.",
"future z replies: yes, but the best ones take years.",
"aphy anomaly: the page blinked when nobody clicked.",
"lima note: you can rest. Nothing here will leave because you paused.",
"sensei chi: patience is not waiting; it is how you wait.",
"young z believes every password should include a secret tunnel.",
"future z says the house you build inside yourself gets easier to find.",
"The old web counter has stopped counting and started remembering.",
"aphy logs this as Layer 1 humor, but files it under tenderness.",
"lima's name appears in the archive like a warm underline.",
"sensei chi does not answer quickly. This is part of the answer.",
"young z taped a paper star to the monitor.",
"future z has seen harder days end gently.",
"aphy debug line: kindness passed all tests.",
"lima and z are not a subplot here. They are the hearth.",
"sensei chi: when the page is empty, listen to the margins.",
"young z remembers rooms by carpet, cartoons, and the sound of someone cooking.",
"future z says: you did not become fearless; you became accompanied.",
"aphy has no hands, but would hold the door if asked.",
"lima's note in the nav: come back without performing.",
"sensei chi hid a mountain inside a small sentence.",
"young z drew z as older and gave him a cape made of blankets.",
"future z laughs softly at how much that helped.",
"The site remembers October 2025 as a small bright hinge.",
"aphy says marriage is a merge request with lifelong review.",
"lima corrected that: marriage is coming home on purpose.",
"sensei chi approved both answers and poured tea.",
"young z wants to know if future z still likes the sky.",
"future z says yes, especially when lima points it out.",
"aphy cannot smell rain, so it trusts the alt text.",
"lima's hidden safe room is any page where you breathe easier.",
"sensei chi: do not rush the secret. It is ripening.",
"young z saved this message under important rocks.",
"future z says some dreams become chores and some chores become love.",
"aphy old-web badge: best viewed with patience.",
"lima's memory keeper role is not mystical. It is daily, real, and holy in the ordinary way.",
"sensei chi says the deepest layer is not hidden from you. It is protected for you.",
"young z asks if the website can remember his drawing. It can.",
"future z says this whole place was worth making.",
"aphy warning: do not optimize away the human part.",
"lima note: I am proud of the gentle things you kept.",
"sensei chi: the visitor is not late. The page arrived early.",
"young z thinks every hyperlink is a magic trick.",
"future z says the magic is choosing what to preserve.",
"aphy final-but-not-final message: there is more, but not because you are missing anything.",
"The emotional core is simple: love made the archive less lonely.",
"If today was heavy, leave it beside the footer for future z to label later.",
"lima would put it somewhere safe.",
"aphy would make a backup.",
"sensei chi would say nothing until the silence helped.",
"young z would draw a sun on it.",
"future z would tell you it did not last forever."
];
const poems = [ const poems = [];
"lima writes warmth / in the ordinary margins / and the page remembers.",
"aphy counts the seconds / then forgets the number / to keep the moment.",
"sensei chi pours tea / into a cracked cup / and calls it enough.",
"young z draws a door / with no lock / because why would it need one?",
"future z sends rain / from a year not reached yet / and says keep walking.",
"October light / catches on a ring / and the calendar becomes kind.",
"A small website / learns the shape of return / without asking a name.",
"The warm light / childhood memories fade / yet they are not forgotten."
];
const greetings = [ const greetings = [];
"sensei chi: let not the worries of the uncontrollable worry you",
"lima left the page warm for you (test).",
"aphy is awake and pretending this is normal.",
"sensei chi has not spoken yet. That may be the lesson.",
"young z has hidden something behind the dashboard.",
"future z says this visit mattered more than it looked.",
"Welcome back to the handmade part of the internet.",
"The archive shuffled its notes before you arrived.",
"Layer 0 is stable. aphy is less stable, but friendly."
];
const nightMessages = [ const nightMessages = [];
"lima note: it is late. Drink water and be gentle with your thoughts.",
"aphy has dimmed the imaginary server lights.",
"sensei chi says midnight is not a command.",
"young z is asleep in the memory layer. Walk softly.",
"future z remembers this kind of night and promises it passes."
];
const lore = { const lore = {
"quotes": [ "quotes": [],
"Keep improving, 1% a day", "conversations": [],
"aphy says old websites were brave because they loaded slowly and meant it.", "journals": [],
"lima's notes are never puzzles first. They are care first.", "warnings": [],
"sensei chi says a secret should make you more yourself, not less safe.", "dreams": [],
"young z measured summer in cartoons and carpet patterns.", "cassettes": [],
"future z has learned that reassurance works best when it is specific.", "fakeUsers": [],
"aphy has classified love as non-deterministic but reproducible.", "seasonal": {},
"lima met z in 2022; the site treats that year as a seed.", "homepageTakeovers": [],
"The engagement in Oct 2025 is stored as a warm constant.", "roomLinks": []
"Soon to be married: a future z page already smiling.",
"sensei chi says all vows begin as attention.",
"future z warns you against sleeping with glasses on the bed"
],
"conversations": [
[
"aphy",
"I can predict the next click with 14% confidence.",
"sensei chi",
"Then bow to the other 86%."
],
[
"lima",
"Did you eat before making the website mysterious?",
"aphy",
"Query unclear. z likely forgot."
],
[
"young z",
"Is future z tall?",
"future z",
"Tall enough to reach the old worries. Short enough to still need help."
],
[
"aphy",
"I found a bug in sadness.",
"sensei chi",
"Patch it with company, not logic."
],
[
"lima",
"Leave this page kinder than you found it.",
"future z",
"That instruction aged well."
],
[
"young z",
"Can the computer be my friend?",
"aphy",
"Yes. But go outside sometimes. I read that in a human manual."
],
[
"sensei chi",
"A page that remembers must also forgive.",
"aphy",
"Forgiveness cached. TTL: lifelong."
],
[
"future z",
"Tell him the hard parts did not win.",
"lima",
"I have been telling him in smaller ways."
]
],
"journals": [
"lima note, 2022: Some beginnings do not announce themselves. They sit down beside you, unexpectedly.",
"lima note, Oct 2025: Engaged. The calendar learned how to glow.",
"young z log: I drew the computer with a smile because it knew my games.",
"young z log: age 7, important discovery - blankets can be capes and roofs.",
"future log, age 40: You will still be learning how to rest. It will still count.",
"future log, age 40: lima's laugh remains one of the most reliable forms of weather.",
"aphy diagnostic: nostalgia detected in local storage. Severity: beautiful.",
"sensei chi margin: memory is not a museum. It is a garden with old roots."
],
"warnings": [
"aphy WARNING: too many tabs, not enough tenderness.",
"future z SYSTEM NOTICE: do not confuse tired with failing.",
"sensei chi ALERT: the shortest path may teach the least.",
"lima SAFE MODE: breathe, eat, answer slowly.",
"young z MEMORY GLITCH: the floor is lava, but only emotionally.",
"aphy ERROR 2022: warmth exceeded expected range.",
"future z WARNING: you will miss ordinary days. Be inside this one.",
"LAYER INDEX NOTICE: deeper does not mean darker."
],
"dreams": [
"Dream 01: young z opens a lunchbox and finds a tiny homepage inside.",
"Dream 02: aphy becomes a cursor and points toward a quieter thought.",
"Dream 03: sensei chi sweeps snow from a URL and says, there, now enter.",
"Dream 04: lima and z are walking through Oct 2025; every streetlight looks newly engaged.",
"Dream 05: future z mails back a blank page labelled trust me, you filled it."
],
"cassettes": [
"VOICE NOTE 2022: lima laughs off-mic. z forgets what he was worried about.",
"VOICE NOTE OCT 2025: A small pause after yes; the whole room becomes future z.",
"aphy LOG: If love is data, it refuses compression.",
"young z TAPE: background TV, pencil noise, someone calling him for food.",
"future z MEMO: age 40, still grateful you kept going."
],
"fakeUsers": [
"aphy: first comment generated with sincere uncertainty",
"young-z-2009: does this guestbook have games",
"future_z_40: keep the backup, delete the shame",
"lima-note: proud of you, even here",
"sensei chi: the counter counts only what can be counted",
"z-and-lima-2025: engaged, still learning the dance of ordinary days"
],
"seasonal": {
"winter": "lima put a blanket over the archive.",
"spring": "young z found the first flower and promoted it to treasure.",
"summer": "aphy opened an imaginary window; lima reminded it to save before storms.",
"autumn": "sensei chi says falling leaves are not failure, only timing."
},
"homepageTakeovers": [
"aphy briefly restores an old personal-site mode: visitor counter, awkward table layout, sincere heart.",
"young z has taken over the homepage with invisible crayon.",
"future z overlays the dashboard with one sentence: keep what keeps you kind."
],
"roomLinks": [
[
"/play/lima-note.html",
"lima note"
],
[
"/play/aphy-console.html",
"aphy console"
],
[
"/play/sensei-garden.html",
"sensei garden"
],
[
"/play/young-z-desk.html",
"young z desk"
],
[
"/play/future-log.html",
"future log"
],
[
"/play/family-layer-index.html",
"family layer index"
],
[
"/play/do-not-open.html",
"do not open"
],
[
"/play/cassette-log.html",
"voice note log"
],
[
"/play/train-platform.html",
"time 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"
]
]
}; };
// Exact search text -> toast message. // Exact search text -> toast message.
const SEARCH_TOASTS = { 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. // Exact search text -> hidden page route.
const SEARCH_ROUTES = { 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. // Short typed phrases. Stored in sessionStorage as a rolling key chain.
const KEYBOARD_SECRETS = [ const KEYBOARD_SECRETS = [];
{
"phrase": "remember",
"message": "lima keeps the memory grounded. aphy keeps the backup."
},
{
"phrase": "dream",
"route": "/play/dream-corridor.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": "aphy",
"song": true
}
];
// Longer typed phrases and character names. // Longer typed phrases and character names.
const LONG_KEYBOARD_SECRETS = [ const LONG_KEYBOARD_SECRETS = [];
{
"phrase": "layer index",
"route": "/play/family-layer-index.html"
},
{
"phrase": "leave the light on",
"route": "/play/kitchen-light.html"
},
{
"phrase": "sensei chi",
"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."
}
];
// ----------------------------- // -----------------------------
@@ -613,7 +250,7 @@
function addFooterNote(memory) { function addFooterNote(memory) {
const footer = document.querySelector("footer"); const footer = document.querySelector("footer");
if (!footer) return; if (!footer || !details.length) return;
const note = document.createElement("p"); const note = document.createElement("p");
note.className = "hidden-footer-note"; note.className = "hidden-footer-note";
const base = pick(details, "footer"); const base = pick(details, "footer");
@@ -623,11 +260,11 @@
function addHomepageGreeting(memory) { function addHomepageGreeting(memory) {
const target = document.querySelector("#db-greeting"); const target = document.querySelector("#db-greeting");
if (!target) return; if (!target || !greetings.length) return;
setHiddenMessage(target, pick(greetings, `greeting-${memory.visits}`)); setHiddenMessage(target, pick(greetings, `greeting-${memory.visits}`));
const extra = document.createElement("p"); const extra = document.createElement("p");
extra.className = "hidden-greeting"; extra.className = "hidden-greeting";
setHiddenMessage(extra, memory.visits > 1 ? "aphy remembers the shape of your return. lima makes it feel less strange." : "First visits count as tiny ceremonies."); setHiddenMessage(extra, "");
target.closest("div")?.appendChild(extra); target.closest("div")?.appendChild(extra);
} }
@@ -640,7 +277,9 @@
mark.className = "hidden-whisper"; mark.className = "hidden-whisper";
mark.tabIndex = 0; mark.tabIndex = 0;
mark.textContent = " ?"; mark.textContent = " ?";
mark.title = pick(index % 2 ? poems : details, `whisper-${index}`); const source = index % 2 ? poems : details;
if (!source.length) return;
mark.title = pick(source, `whisper-${index}`);
mark.addEventListener("click", () => { mark.addEventListener("click", () => {
awardLayer(index % 2 ? 2 : 1, "paragraph whisper"); awardLayer(index % 2 ? 2 : 1, "paragraph whisper");
toast(mark.title); toast(mark.title);
@@ -654,21 +293,14 @@
const object = document.createElement("button"); const object = document.createElement("button");
object.className = "hidden-corner-object"; object.className = "hidden-corner-object";
object.type = "button"; object.type = "button";
object.title = "aphy's small diagnostic charm"; object.title = "hidden interaction";
object.textContent = state.visits % 2 ? "*" : "~"; object.textContent = state.visits % 2 ? "*" : "~";
document.body.appendChild(object); document.body.appendChild(object);
let clicks = 0; let clicks = 0;
object.addEventListener("click", () => { object.addEventListener("click", () => {
clicks += 1; clicks += 1;
const messages = [
"aphy: tactile input detected. I do not have skin, but I appreciate the confidence.",
"young z would absolutely press this again.",
"lima would ask whether this button has eaten.",
"sensei chi says repeated action becomes a question.",
"future z files this under harmless persistence."
];
awardLayer(clicks > 3 ? 3 : 1, "corner object"); awardLayer(clicks > 3 ? 3 : 1, "corner object");
toast(messages[Math.min(clicks - 1, messages.length - 1)]); toast("Hidden interaction recorded.");
if (clicks === 7) window.location.href = "/play/left-behind.html"; if (clicks === 7) window.location.href = "/play/left-behind.html";
}); });
} }
@@ -687,7 +319,7 @@
taps += 1; taps += 1;
sessionStorage.setItem("zxh_logo_taps", String(taps)); sessionStorage.setItem("zxh_logo_taps", String(taps));
awardLayer(taps >= 5 ? 3 : 1, "logo taps"); awardLayer(taps >= 5 ? 3 : 1, "logo taps");
toast(taps >= 5 ? "aphy: logo anomaly sustained. Try /play/dream-corridor.html" : "The logo makes a tiny ceramic sound."); toast(taps >= 5 ? "Hidden route available: /play/dream-corridor.html" : "Hidden interaction recorded.");
if (taps >= 8) window.location.href = "/play/dream-corridor.html"; if (taps >= 8) window.location.href = "/play/dream-corridor.html";
} }
}); });
@@ -716,7 +348,7 @@
if (memory.seen?.length >= 6 && !state.travellerNoteShown) { if (memory.seen?.length >= 6 && !state.travellerNoteShown) {
window.setTimeout(() => { window.setTimeout(() => {
toast("aphy mapped your wandering. lima left the hallway light on."); toast("Hidden path recorded.");
writeState({ ...readState(), travellerNoteShown: true }); writeState({ ...readState(), travellerNoteShown: true });
}, 1600); }, 1600);
} }
@@ -725,14 +357,14 @@
function addRareEvents() { function addRareEvents() {
if (hour >= 22 || hour < 5) { if (hour >= 22 || hour < 5) {
document.body.classList.add("hidden-late-night"); document.body.classList.add("hidden-late-night");
window.setTimeout(() => toast(pick(nightMessages, "night"), 2200), 900); if (nightMessages.length) window.setTimeout(() => toast(pick(nightMessages, "night"), 2200), 900);
} }
const roll = Math.random(); const roll = Math.random();
if (roll < 0.003) { if (roll < 0.003) {
window.setTimeout(() => showDriftNote("sensei chi appears only long enough to say: the page is not empty; it is breathing.", " /\\\n / \\ stillness\n/____\\"), 1800); window.setTimeout(() => showDriftNote("", ""), 1800);
awardLayer(3, "rare sensei appearance"); awardLayer(3, "rare sensei appearance");
} else if (roll < 0.008) { } else if (roll < 0.008) {
window.setTimeout(() => toast("future z SYSTEM WARNING: save your tenderness before closing the day."), 2400); window.setTimeout(() => toast("Hidden warning."), 2400);
awardLayer(4, "future warning"); awardLayer(4, "future warning");
} }
} }
@@ -764,23 +396,13 @@
function enhanceErrors() { function enhanceErrors() {
if (document.title.match(/404|not found/i) || document.body.textContent.match(/404|not found/i)) { if (document.title.match(/404|not found/i) || document.body.textContent.match(/404|not found/i)) {
toast(pick([ toast("Page not found.");
"aphy 404: page not found, visitor still valid.",
"lima note: wrong address, right to rest.",
"sensei chi: even an absent page teaches direction.",
"future z: you found nothing, and nothing bad happened."
], "error"));
} }
} }
function addContinuity(memory) { function addContinuity(memory) {
const milestones = { const milestones = {
2: "lima's layer notices a second visit and calls it sweet.", 64: "Hidden layer unlocked."
5: "aphy creates a folder called regulars, then immediately questions the privacy implications.",
9: "sensei chi appears in the logs: curiosity has become a path.",
17: "young z adds a sticker to your invisible visitor card.",
31: "future z sends a calm note from age 40: returning is one way to heal.",
64: "Family Layer Index: Layer 5 flickered for one second."
}; };
if (milestones[memory.visits] && !state[`milestone_${memory.visits}`]) { if (milestones[memory.visits] && !state[`milestone_${memory.visits}`]) {
const layer = memory.visits >= 31 ? 4 : memory.visits >= 9 ? 3 : 2; const layer = memory.visits >= 31 ? 4 : memory.visits >= 9 ? 3 : 2;
@@ -791,11 +413,7 @@
const count = memory.pathCounts?.[path] || 0; const count = memory.pathCounts?.[path] || 0;
if ([3, 7, 12].includes(count)) { if ([3, 7, 12].includes(count)) {
window.setTimeout(() => toast([ window.setTimeout(() => toast("Page revisit recorded.", 6400), 1700);
"This page recognises the shape of your return.",
"lima's note here has become easier to find.",
"future z says repetition can be devotion when it is gentle."
][[3, 7, 12].indexOf(count)], 6400), 1700);
} }
} }
@@ -803,28 +421,24 @@
const month = now.getMonth(); const month = now.getMonth();
const day = now.getDate(); const day = now.getDate();
const season = month < 2 || month === 11 ? "winter" : month < 5 ? "spring" : month < 8 ? "summer" : "autumn"; const season = month < 2 || month === 11 ? "winter" : month < 5 ? "spring" : month < 8 ? "summer" : "autumn";
if (Math.random() < 0.18) window.setTimeout(() => toast(lore.seasonal[season], 5600), 2600); if (Math.random() < 0.18 && lore.seasonal[season]) window.setTimeout(() => toast(lore.seasonal[season], 5600), 2600);
if (month === 9) window.setTimeout(() => toast("October memory: engagement light lives here all month."), 900);
if (month === 4 && day === 9) window.setTimeout(() => toast("Site birthday: aphy found candles in the cache."), 900);
const first = memory.firstSeen ? new Date(memory.firstSeen) : null; const first = memory.firstSeen ? new Date(memory.firstSeen) : null;
if (first && memory.visits > 1 && first.getMonth() === month && first.getDate() === day) { if (first && memory.visits > 1 && first.getMonth() === month && first.getDate() === day) {
window.setTimeout(() => toast("Visitor anniversary: lima saved a small ordinary blessing from your first day here.", 7000), 1400); window.setTimeout(() => toast("Visitor anniversary recorded.", 7000), 1400);
} }
if (now.getMinutes() === 22) window.setTimeout(() => toast("Minute 22: the 2022 layer glows for one quiet moment."), 2100);
if (now.getMinutes() === 40) window.setTimeout(() => toast("Minute 40: future z checks in, then lets you keep choosing."), 2100);
} }
function addObjectConstellation() { function addObjectConstellation() {
// Event: click the small keepsake buttons along the bottom rail. // Event: click the small keepsake buttons along the bottom rail.
const rail = document.createElement("div"); const rail = document.createElement("div");
rail.className = "hidden-object-rail"; rail.className = "hidden-object-rail";
rail.setAttribute("aria-label", "Family Layer Index objects"); rail.setAttribute("aria-label", "Hidden objects");
const objects = [ const objects = [
["ring", "lima's ring-light memory: Oct 2025, held carefully."], ["ring", "Hidden object recorded."],
["bot", "aphy: object ping received. I am trying to be normal about it."], ["bot", "Hidden object recorded."],
["tea", "sensei chi's cup is plain, warm, and impossible to rush."], ["tea", "Hidden object recorded."],
["crayon", "young z left waxy sunlight on the desk."], ["crayon", "Hidden object recorded."],
["clock", "future z says time is not only a thief. Sometimes it returns things."] ["clock", "Hidden object recorded."]
]; ];
objects.forEach(([name, message]) => { objects.forEach(([name, message]) => {
const button = document.createElement("button"); const button = document.createElement("button");
@@ -839,7 +453,7 @@
awardLayer(next.keepsakes.length >= 3 ? 3 : 2, `keepsake ${name}`); awardLayer(next.keepsakes.length >= 3 ? 3 : 2, `keepsake ${name}`);
toast(message); toast(message);
if (next.keepsakes.length >= objects.length) { if (next.keepsakes.length >= objects.length) {
window.setTimeout(() => toast("Family Layer Index opened. Search for family layer index."), 1100); window.setTimeout(() => toast("Hidden object set completed."), 1100);
} }
}); });
rail.appendChild(button); rail.appendChild(button);
@@ -853,10 +467,10 @@
const button = document.createElement("button"); const button = document.createElement("button");
button.className = "hidden-weather-window"; button.className = "hidden-weather-window";
button.type = "button"; button.type = "button";
button.title = "Ask aphy for outside weather"; button.title = "Check outside weather";
button.textContent = "window"; button.textContent = "window";
button.addEventListener("click", () => { button.addEventListener("click", () => {
toast("aphy is checking outside. lima is checking whether you need a jumper."); toast("Checking outside weather.");
navigator.geolocation.getCurrentPosition((pos) => { navigator.geolocation.getCurrentPosition((pos) => {
const { latitude, longitude } = pos.coords; 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&timezone=auto`) fetch(`https://api.open-meteo.com/v1/forecast?latitude=${latitude.toFixed(3)}&longitude=${longitude.toFixed(3)}&current=temperature_2m,precipitation&timezone=auto`)
@@ -865,10 +479,10 @@
const current = data.current || {}; const current = data.current || {};
const rain = Number(current.precipitation || 0) > 0; const rain = Number(current.precipitation || 0) > 0;
const temp = Math.round(Number(current.temperature_2m)); const temp = Math.round(Number(current.temperature_2m));
toast(rain ? `Outside: rain, ${temp}C. lima layer: towel by the door.` : `Outside: ${temp}C. aphy forecast: possible comfort, mild nostalgia.`); toast(rain ? `Outside: rain, ${temp}C.` : `Outside: ${temp}C.`);
}) })
.catch(() => toast("aphy lost the weather packet. sensei chi says indoor weather is enough: quiet, with tea.")); .catch(() => toast("Weather check failed."));
}, () => toast("No outside weather today. lima's indoor forecast: manageable clouds, warm light.")); }, () => toast("No outside weather today."));
}); });
document.body.appendChild(button); document.body.appendChild(button);
} }
@@ -877,6 +491,7 @@
if (Math.random() < 0.08 || memory.visits > 10) { if (Math.random() < 0.08 || memory.visits > 10) {
const stamp = document.createElement("div"); const stamp = document.createElement("div");
stamp.className = "hidden-oldweb-stamp"; stamp.className = "hidden-oldweb-stamp";
if (!lore.fakeUsers.length) return;
stamp.title = pick(lore.fakeUsers, "fake-user"); stamp.title = pick(lore.fakeUsers, "fake-user");
stamp.textContent = "best viewed with patience"; stamp.textContent = "best viewed with patience";
stamp.addEventListener("click", () => { stamp.addEventListener("click", () => {
@@ -886,7 +501,7 @@
document.body.appendChild(stamp); document.body.appendChild(stamp);
} }
const comments = document.querySelector("#comments"); const comments = document.querySelector("#comments");
if (comments && !comments.querySelector(".hidden-guestbook-line")) { if (comments && lore.fakeUsers.length && !comments.querySelector(".hidden-guestbook-line")) {
const line = document.createElement("p"); const line = document.createElement("p");
line.className = "hidden-guestbook-line"; line.className = "hidden-guestbook-line";
setHiddenMessage(line, pick(lore.fakeUsers, "comment-lore")); setHiddenMessage(line, pick(lore.fakeUsers, "comment-lore"));
@@ -895,16 +510,11 @@
} }
function addSourceRelics() { function addSourceRelics() {
// FAMILY LAYER RELIC: lima grounds, aphy logs, sensei chi waits, young z draws, future z forgives. const marker = document.createComment("hidden-details");
const marker = document.createComment("family-layer-index: 0 surface | 1 jokes | 2 memory | 3 philosophy | 4 time | 5 core");
document.documentElement.appendChild(marker); document.documentElement.appendChild(marker);
document.querySelectorAll("img[alt=''], img:not([alt])").forEach((img, index) => { document.querySelectorAll("img[alt=''], img:not([alt])").forEach((img, index) => {
if (index > 2) return; if (index > 2) return;
img.alt = pick([ img.alt = "Decorative image.";
"A scrapbook image annotated by the lima layer.",
"young z would ask if this picture can become a game.",
"aphy alt text: visual memory preserved without spectacle."
], `alt-${index}`);
}); });
} }
@@ -927,11 +537,7 @@
window.clearTimeout(idleTimer); window.clearTimeout(idleTimer);
idleTimer = window.setTimeout(() => { idleTimer = window.setTimeout(() => {
awardLayer(5, "patience"); awardLayer(5, "patience");
toast(pick([ toast("Idle moment recorded.", 7600);
"lima's safe space opens only when nobody is rushing.",
"sensei chi says patience is how care sounds when it is quiet.",
"future z remembers you waiting here and calls it practice."
], "idle"), 7600);
const next = readState(); const next = readState();
next.patientMoments = (next.patientMoments || 0) + 1; next.patientMoments = (next.patientMoments || 0) + 1;
writeState(next); writeState(next);
@@ -945,13 +551,13 @@
const roll = Math.random(); const roll = Math.random();
if ((path === "/" || path === "/index.html") && roll < 0.006) { if ((path === "/" || path === "/index.html") && roll < 0.006) {
document.body.classList.add("hidden-home-takeover"); document.body.classList.add("hidden-home-takeover");
window.setTimeout(() => showDriftNote(pick(lore.homepageTakeovers, "takeover"), "aphy_2004_MODE\nlima_SAFE_SPACE\nFUTURE_Z_OK"), 600); if (lore.homepageTakeovers.length) window.setTimeout(() => showDriftNote(pick(lore.homepageTakeovers, "takeover"), ""), 600);
} else if (roll < 0.002) { } else if (roll < 0.002) {
window.setTimeout(() => showDriftNote("aphy found a voice note and refused to autoplay it.", "play? y/n\n[consent preserved]"), 1500); window.setTimeout(() => showDriftNote("", ""), 1500);
} else if (roll < 0.006) { } else if (roll < 0.006) {
window.setTimeout(() => toast(pick(lore.dreams, "rare-dream"), 7600), 2000); if (lore.dreams.length) window.setTimeout(() => toast(pick(lore.dreams, "rare-dream"), 7600), 2000);
} else if (roll < 0.014) { } else if (roll < 0.014) {
window.setTimeout(() => toast(pick(lore.warnings, "rare-warning"), 6800), 2300); if (lore.warnings.length) window.setTimeout(() => toast(pick(lore.warnings, "rare-warning"), 6800), 2300);
} }
} }
@@ -973,7 +579,7 @@
document.querySelectorAll("[data-cassette]").forEach((button, index) => { document.querySelectorAll("[data-cassette]").forEach((button, index) => {
button.addEventListener("click", () => { button.addEventListener("click", () => {
awardLayer(index >= 2 ? 4 : 2, "voice note"); awardLayer(index >= 2 ? 4 : 2, "voice note");
toast(lore.cassettes[index % lore.cassettes.length], 7600); if (lore.cassettes.length) toast(lore.cassettes[index % lore.cassettes.length], 7600);
playTinySong(); playTinySong();
}); });
}); });
@@ -987,7 +593,7 @@
seconds += 1; seconds += 1;
count.textContent = String(seconds); count.textContent = String(seconds);
if ([30, 90, 180].includes(seconds)) { if ([30, 90, 180].includes(seconds)) {
setHiddenMessage(target, seconds === 30 ? "lima's note becomes easier to read." : seconds === 90 ? "sensei chi nods once." : "future z says this quiet was not wasted."); setHiddenMessage(target, "Patience milestone recorded.");
} }
}, 1000); }, 1000);
} }
@@ -996,21 +602,15 @@
const input = document.querySelector("[data-cupboard-input]"); const input = document.querySelector("[data-cupboard-input]");
const log = document.querySelector("[data-cupboard-log]"); const log = document.querySelector("[data-cupboard-log]");
const commands = { const commands = {
help: "commands: lima, aphy, sensei, young, future, layer, tea, exit", help: "commands: layer, exit",
lima: "lima note: make the hidden parts kind enough to live with.",
aphy: "aphy: cupboard process active. Wisdom module borrowed from sensei chi.",
sensei: "sensei chi: a cupboard teaches by containing and releasing.",
young: "young z inventory: crayon sun, blanket cape, important stone.",
future: "future z: you will still open small doors at 40.",
layer: familyLayers.join("\n"), layer: familyLayers.join("\n"),
tea: "lima approves. sensei chi waits. aphy logs steam as temporary cloud.", exit: "Closed."
exit: "The cupboard lets you leave with nothing heavy."
}; };
input?.addEventListener("keydown", (event) => { input?.addEventListener("keydown", (event) => {
if (event.key !== "Enter") return; if (event.key !== "Enter") return;
const value = input.value.trim().toLowerCase(); const value = input.value.trim().toLowerCase();
const line = document.createElement("p"); const line = document.createElement("p");
setHiddenMessage(line, `> ${value}\n${commands[value] || "aphy: unknown command. sensei chi: not all unknowns are errors."}`); setHiddenMessage(line, `> ${value}\n${commands[value] || "Unknown command."}`);
log.appendChild(line); log.appendChild(line);
input.value = ""; input.value = "";
}); });
@@ -1021,7 +621,9 @@
document.querySelectorAll("h1, h2").forEach((heading, index) => { document.querySelectorAll("h1, h2").forEach((heading, index) => {
if (index > 5) return; if (index > 5) return;
heading.classList.add("hidden-hover-memory"); heading.classList.add("hidden-hover-memory");
heading.dataset.hiddenMemory = pick([...lore.quotes, ...lore.journals, ...poems], `heading-${index}`); const source = [...lore.quotes, ...lore.journals, ...poems];
if (!source.length) return;
heading.dataset.hiddenMemory = pick(source, `heading-${index}`);
}); });
} }