WOAHHHH
All checks were successful
Build Org Website / build (push) Successful in 48s

This commit is contained in:
2026-05-10 00:01:54 +01:00
parent 4519cfe804
commit 83f18a26f0
12 changed files with 1212 additions and 2 deletions

View File

@@ -141,6 +141,117 @@
"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 Safiya", "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 Safiya", "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) || "{}");
@@ -186,10 +297,13 @@
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()
@@ -326,7 +440,8 @@
[
["/play/left-behind.html", "left behind"],
["/play/dream-corridor.html", "dream corridor"],
["/play/kitchen-light.html", "kitchen light"]
["/play/kitchen-light.html", "kitchen light"],
...secondLayer.roomLinks
].forEach(([href, label], index) => {
const link = document.createElement("a");
link.className = "hidden-secret-link";
@@ -339,10 +454,289 @@
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.");
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 Safiya 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("safiya")) toast("Aunt Safiya 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;",
@@ -363,5 +757,16 @@
addRareEvents();
addSecretLinks();
enhanceErrors();
addContinuity(memory);
addSeasonAndDates(memory);
addObjectConstellation(memory);
addWeatherWindow(memory);
addOldWebLayer(memory);
addSourceRelics();
addSearchAndKeySecrets();
addPatienceRewards(memory);
addRareSecondLayer(memory);
addPageSpecificSecrets(memory);
addInvisibleHoverSecrets();
});
}());

View File

@@ -108,6 +108,143 @@
margin-top: 0.8rem;
}
.hidden-object-rail {
position: fixed;
left: 50%;
bottom: 0.35rem;
display: flex;
gap: 0.3rem;
transform: translateX(-50%);
opacity: 0.18;
transition: opacity 180ms ease;
z-index: 20;
}
.hidden-object-rail:hover,
.hidden-object-rail:focus-within {
opacity: 0.94;
}
.hidden-keepsake {
width: 1.35rem;
height: 1.35rem;
border: 1px solid color-mix(in oklab, var(--border) 72%, transparent);
border-radius: 50%;
background: color-mix(in oklab, var(--surface) 78%, transparent);
color: var(--muted);
font: 0.8rem/1 monospace;
cursor: pointer;
}
.hidden-keepsake:hover,
.hidden-keepsake:focus-visible {
color: var(--fg);
background: var(--surface);
}
.hidden-weather-window {
position: fixed;
right: 0.7rem;
top: 7.5rem;
border: 1px solid var(--border);
border-radius: 8px;
background: color-mix(in oklab, var(--surface) 84%, transparent);
color: var(--muted);
font-size: 0.72rem;
padding: 0.25rem 0.45rem;
opacity: 0.24;
z-index: 20;
}
.hidden-weather-window:hover,
.hidden-weather-window:focus-visible {
opacity: 1;
color: var(--fg);
}
.hidden-oldweb-stamp {
position: fixed;
right: 0.75rem;
bottom: 4.2rem;
max-width: 12rem;
padding: 0.25rem 0.4rem;
border: 1px dashed var(--border);
background: var(--surface);
color: var(--muted);
font: 0.72rem/1.2 "Times New Roman", serif;
text-align: center;
opacity: 0.34;
cursor: help;
z-index: 20;
}
.hidden-oldweb-stamp:hover {
opacity: 1;
}
.hidden-guestbook-line {
color: var(--muted);
font-size: 0.86rem;
font-family: "Times New Roman", serif;
opacity: 0.78;
}
.hidden-hover-memory {
position: relative;
}
.hidden-hover-memory:hover::after {
content: attr(data-hidden-memory);
position: absolute;
left: 0;
top: 100%;
width: min(28rem, 80vw);
margin-top: 0.25rem;
padding: 0.55rem 0.65rem;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--surface);
color: var(--muted);
font-size: 0.78rem;
font-family: 'Noto', system-ui, sans-serif;
font-weight: 400;
line-height: 1.35;
z-index: 30;
box-shadow: 0 12px 28px color-mix(in oklab, #000 12%, transparent);
}
body.hidden-home-takeover .db-root {
filter: saturate(0.9) contrast(1.03);
}
body.hidden-home-takeover .db-hero::before {
content: "under construction, emotionally";
display: inline-block;
margin-bottom: 0.5rem;
color: var(--muted);
font: 0.82rem "Times New Roman", serif;
}
.hidden-terminal {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.hidden-terminal input {
width: 100%;
box-sizing: border-box;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--code-bg);
color: var(--fg);
padding: 0.65rem;
font: inherit;
}
.hidden-terminal-log {
min-height: 10rem;
white-space: pre-wrap;
}
.hidden-secret-link {
position: absolute;
width: 1px;