This commit is contained in:
367
assets/scripts/hidden-details.js
Normal file
367
assets/scripts/hidden-details.js
Normal file
@@ -0,0 +1,367 @@
|
||||
(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."
|
||||
];
|
||||
|
||||
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 next = {
|
||||
...state,
|
||||
visits,
|
||||
lastPath: path,
|
||||
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"]
|
||||
].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("This page is missing, but you are not. Try the sitemap, or sit here a second.");
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value).replace(/[&<>"']/g, (char) => ({
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'"
|
||||
}[char]));
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const memory = rememberVisit();
|
||||
addFooterNote(memory);
|
||||
addHomepageGreeting(memory);
|
||||
addWhispers();
|
||||
addCornerObject(memory);
|
||||
addPatterns(memory);
|
||||
addRareEvents();
|
||||
addSecretLinks();
|
||||
enhanceErrors();
|
||||
});
|
||||
}());
|
||||
Reference in New Issue
Block a user