This commit is contained in:
@@ -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)}¤t=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) => ({
|
||||
"&": "&",
|
||||
@@ -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();
|
||||
});
|
||||
}());
|
||||
|
||||
@@ -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;
|
||||
|
||||
444
docs/hidden-soul-bible.md
Normal file
444
docs/hidden-soul-bible.md
Normal file
@@ -0,0 +1,444 @@
|
||||
# Hidden Soul Bible
|
||||
|
||||
## 1. Creative Pillars
|
||||
|
||||
The hidden layer should feel like an emotional internet artifact: a personal site that has been lived in, repaired, renamed, backed up, forgotten, and returned to. It should reward attention without demanding completion.
|
||||
|
||||
Primary emotional tones:
|
||||
|
||||
- Cozy familial: kitchen lights, rice, mugs, family notes, practical tenderness.
|
||||
- Funny and self-aware: fake system warnings, old-web jokes, suspicious biscuit tins, web development folklore.
|
||||
- Reflective melancholy: unfinished letters, old usernames, returning to changed rooms.
|
||||
- Gentle mystery: Morrow, Nettle, Luma, Etta, Aunt Safiya, Nadir, the radio, the train platform.
|
||||
- Internet nostalgia: guestbooks, counters, dial-up usernames, under construction language, source-code relics.
|
||||
|
||||
## 2. Recurring Cast
|
||||
|
||||
- Morrow: a quiet figure tied to trains, platforms, lanterns, and unfinished apologies. Morrow represents return, hesitation, and the comfort of being expected.
|
||||
- Nettle: the archivist/moderator figure. Files impossible feelings under practical labels. Carries a clipboard and treats emotional continuity as infrastructure.
|
||||
- Luma: a lamp-associated presence who hums original tiny songs and notices margins. Luma represents small courage.
|
||||
- Etta: writer of the "Do Not Open" letter. Represents vulnerability hidden behind instructions and labels.
|
||||
- Aunt Safiya: warm, funny, practical family presence. Connected to recipes, suspicious biscuit tins, and household wisdom.
|
||||
- Nadir: backup-minded family historian. Explains technical care through domestic metaphors.
|
||||
- Old Guest 14 / guest_000 / sunnydialup88: old-web guestbook presences who blur past visitors, usernames, and memory.
|
||||
|
||||
## 3. Symbolic Objects
|
||||
|
||||
- Lamp: patience, late-night comfort, permission to rest.
|
||||
- Mug: continuity, small damage kept because it belongs.
|
||||
- Return ticket: coming back without needing to explain where from.
|
||||
- Radio: almost-heard family memory, voice messages, weather, original hums.
|
||||
- Pressed flower / marigold: seasonal return, promises that do not announce themselves.
|
||||
- Biscuit tin: running joke; allegedly contains tools, feelings, or neither.
|
||||
- Train platform: delay, return, growing older, being expected.
|
||||
- Cupboard: hidden terminal, family storage, recursive domestic mystery.
|
||||
|
||||
## 4. Implemented Global Systems
|
||||
|
||||
### Footer Notes
|
||||
|
||||
- Trigger: every page load with a footer.
|
||||
- Content: rotates through 102 first-layer messages plus new second-layer continuity.
|
||||
- Frequency: always visible but subtle.
|
||||
- Lore: the footer is the house threshold.
|
||||
- UX: muted centered text; should feel like a small afterthought.
|
||||
|
||||
### Homepage Greetings
|
||||
|
||||
- Trigger: homepage load.
|
||||
- Content: randomized greeting; repeat visitors receive memory-aware copy.
|
||||
- Frequency: always on homepage.
|
||||
- Lore: the site recognizes returning visitors locally through `localStorage`.
|
||||
|
||||
### Hover Whispers
|
||||
|
||||
- Trigger: some paragraphs and headings gain tiny hover/click notes.
|
||||
- Content: short poems, quotes, and second-layer hidden memories.
|
||||
- Frequency: deterministic per page; not every paragraph.
|
||||
- UX: dotted question marks and heading hover captions.
|
||||
|
||||
### Corner Object
|
||||
|
||||
- Trigger: click the small fixed corner symbol.
|
||||
- Sequence: five escalating messages, then seven clicks opens `/play/left-behind.html`.
|
||||
- Lore: a loose panel in the page.
|
||||
|
||||
### Keepsake Constellation
|
||||
|
||||
- Trigger: click the five tiny bottom keepsakes: lamp, mug, ticket, radio, flower.
|
||||
- Payoff: after all are clicked, the site suggests searching `radio under stairs`.
|
||||
- Frequency: always present, low opacity.
|
||||
- Lore: collecting ordinary objects aligns the hidden family memory.
|
||||
|
||||
### Weather Window
|
||||
|
||||
- Trigger: after at least three visits, a subtle `window` button appears. User click requests browser location.
|
||||
- Content: weather-aware line through Open-Meteo if permission/network works; indoor-weather fallback otherwise.
|
||||
- Frequency: repeat visitors only.
|
||||
- UX: no automatic permission prompt.
|
||||
|
||||
### Visit Milestones
|
||||
|
||||
- Trigger: visit counts 2, 5, 9, 17, 31, 64.
|
||||
- Content: progressively warmer messages about being remembered.
|
||||
- Lore: the site stores visits locally, not identities.
|
||||
|
||||
### Page Revisit Memory
|
||||
|
||||
- Trigger: 3rd, 7th, and 12th visit to the same path.
|
||||
- Content: page-specific return messages.
|
||||
- Lore: individual rooms recognize repeated footsteps.
|
||||
|
||||
### Seasonal Content
|
||||
|
||||
- Trigger: low-probability seasonal toast on page load.
|
||||
- Content: winter blanket, spring pressed flower, summer fan/window, autumn leaf-links.
|
||||
- Frequency: 18% chance.
|
||||
|
||||
### Date Events
|
||||
|
||||
- Site birthday: May 9. Message about imaginary candles.
|
||||
- Visitor anniversary: same month/day as first stored visit.
|
||||
- Minute thirteen: radio throat-clearing message.
|
||||
|
||||
### Night Messages
|
||||
|
||||
- Trigger: 22:00-04:59 local browser time.
|
||||
- Content: gentle late-night comfort.
|
||||
- Visual: soft bottom glow.
|
||||
|
||||
### Search Secrets
|
||||
|
||||
- `morrow`: lantern/lake/apology.
|
||||
- `nettle`: clipboard/cupboard/semicolon.
|
||||
- `home`: warm window/spare key.
|
||||
- `kitchen light`: opens `/play/kitchen-light.html`.
|
||||
- `radio under stairs`: opens `/play/radio-room.html`.
|
||||
- `guestbook 1999`: opens `/play/guestbook-1999.html`.
|
||||
- `do not open`: opens `/play/do-not-open.html`.
|
||||
- `cassette`: opens `/play/cassette-log.html`.
|
||||
- `return ticket`: opens `/play/train-platform.html`.
|
||||
- `unfinished letter`: opens `/play/forgotten-draft.html`.
|
||||
- `burnt sugar`: opens `/play/corrupted-recipe.html`.
|
||||
- `cupboard`: opens `/play/terminal-cupboard.html`.
|
||||
- `patience`: opens `/play/patience-game.html`.
|
||||
- `marigold`, `luma`, `nadir`: lore toasts.
|
||||
|
||||
### Typed Phrase Secrets
|
||||
|
||||
- `remember`: continuity toast.
|
||||
- `dream`: opens Dream Corridor.
|
||||
- `leave the light on`: opens Kitchen Light.
|
||||
- `oldweb`: old layout joke.
|
||||
- `safiya`: biscuit tin joke.
|
||||
- `luma`: plays a tiny original tone sequence.
|
||||
|
||||
### Rare Events
|
||||
|
||||
- First layer: rare page breeze and biscuit event.
|
||||
- Second layer:
|
||||
- 0.6% rare homepage old-web takeover.
|
||||
- 0.2% cassette asks permission.
|
||||
- 0.4% recurring dream line.
|
||||
- 0.8% cryptic comforting warning.
|
||||
- Rule: rare events should delight, not block navigation.
|
||||
|
||||
### Patience Rewards
|
||||
|
||||
- Trigger: 90 seconds of inactivity after repeat visits.
|
||||
- Content: quiet no-achievement reward.
|
||||
- Lore: patience and kindness count even when not announced.
|
||||
|
||||
### Source Relics and Alt Text
|
||||
|
||||
- Trigger: DOM load.
|
||||
- Behavior: adds a source-code comment and fills empty image alt text with scrapbook-like hidden descriptions.
|
||||
- Purpose: secrets inside source and accessibility-friendly hidden storytelling.
|
||||
|
||||
### Audio
|
||||
|
||||
- Trigger: user action only, never autoplay.
|
||||
- Content: short original Web Audio tone sequence associated with Luma/cassettes.
|
||||
- Note: no copyrighted lyrics or melodies.
|
||||
|
||||
## 5. Implemented Secret Pages
|
||||
|
||||
### `/play/left-behind.html`
|
||||
|
||||
- Trigger: seven clicks on corner object.
|
||||
- Tone: familial, emotional.
|
||||
- Lore: blue bowl, soup memory, Morrow at 2:13.
|
||||
|
||||
### `/play/dream-corridor.html`
|
||||
|
||||
- Trigger: logo taps or typed `dream`.
|
||||
- Tone: surreal, safe.
|
||||
- Lore: three shy doors, Nettle margin note.
|
||||
|
||||
### `/play/kitchen-light.html`
|
||||
|
||||
- Trigger: search `kitchen light` or phrase `leave the light on`.
|
||||
- Tone: late-night comfort.
|
||||
- Lore: rice in pot, towel on chair, light asks no questions.
|
||||
|
||||
### `/play/radio-room.html`
|
||||
|
||||
- Trigger: search `radio under stairs`; keepsake constellation hints it.
|
||||
- Tone: nostalgic audio memory.
|
||||
- Lore: Nadir backups, Luma humming, no autoplay manners.
|
||||
|
||||
### `/play/guestbook-1999.html`
|
||||
|
||||
- Trigger: search `guestbook 1999`.
|
||||
- Tone: old-web funny/melancholy.
|
||||
- Lore: fake users are recurring ghosts of the old internet.
|
||||
|
||||
### `/play/do-not-open.html`
|
||||
|
||||
- Trigger: search `do not open`.
|
||||
- Tone: emotionally meaningful warning.
|
||||
- Lore: Etta wanted to be found carefully.
|
||||
|
||||
### `/play/cassette-log.html`
|
||||
|
||||
- Trigger: search `cassette`.
|
||||
- Tone: archive/audio.
|
||||
- Interaction: buttons reveal cassette transcript toasts and play original tones.
|
||||
|
||||
### `/play/train-platform.html`
|
||||
|
||||
- Trigger: search `return ticket`.
|
||||
- Tone: patient return.
|
||||
- Lore: platform 3, Morrow, destinations as feelings.
|
||||
|
||||
### `/play/forgotten-draft.html`
|
||||
|
||||
- Trigger: search `unfinished letter`.
|
||||
- Tone: unsent vulnerability.
|
||||
- Lore: website as a way to recover rooms in pieces.
|
||||
|
||||
### `/play/corrupted-recipe.html`
|
||||
|
||||
- Trigger: search `burnt sugar`.
|
||||
- Tone: funny family warmth.
|
||||
- Lore: recipe corrupted into folklore.
|
||||
|
||||
### `/play/terminal-cupboard.html`
|
||||
|
||||
- Trigger: search `cupboard`.
|
||||
- Tone: hidden terminal, domestic mystery.
|
||||
- Commands: `help`, `listen`, `inventory`, `tea`, `open`, `remember`, `exit`.
|
||||
|
||||
### `/play/patience-game.html`
|
||||
|
||||
- Trigger: search `patience`.
|
||||
- Tone: quiet game.
|
||||
- Interaction: wait. Text changes at 30, 90, and 180 seconds.
|
||||
|
||||
## 6. Emotional Tone Categories
|
||||
|
||||
- Comfort: Kitchen Light, night messages, visitor milestones, patience rewards.
|
||||
- Humor: biscuit tin, fake users, old-web jokes, fake errors.
|
||||
- Melancholy: Forgotten Draft, train platform, cassettes, old usernames.
|
||||
- Mystery: Dream Corridor, radio, cupboard, rare dreams.
|
||||
- Familial warmth: corrupted recipe, Aunt Safiya, Nadir, blue bowl.
|
||||
- Meta/web: source relics, under construction takeover, cache warnings.
|
||||
|
||||
## 7. Page/Section Categories
|
||||
|
||||
- Global: footer notes, toasts, hover whispers, keepsakes, weather window, source comments.
|
||||
- Homepage: greetings, rare old-web takeover, logo taps.
|
||||
- Search/nav: keyword portals.
|
||||
- Play wing: secret rooms and tiny games.
|
||||
- Comments: fake guestbook line when comments exist.
|
||||
- Images/source: hidden alt text and source-code comment.
|
||||
|
||||
## 8. UI/UX Notes
|
||||
|
||||
- Hidden UI stays low opacity until hovered/focused.
|
||||
- No feature should obscure reading for long.
|
||||
- Audio requires explicit action.
|
||||
- Weather requests require explicit click and have graceful fallback.
|
||||
- Secret pages are buildable but marked `#+HIDDEN_PAGE: t` so they do not appear in index pages.
|
||||
- Rewards are intentionally non-competitive; no public checklist.
|
||||
|
||||
## 9. Audio/Visual Suggestions
|
||||
|
||||
- Add tiny original ambient loops only after explicit button press.
|
||||
- Use cassette hiss, lamp hum, rain-on-window synthesis, and soft train-platform chimes.
|
||||
- Consider small generated bitmap textures: paper receipt, cassette label, pressed flower, old guestbook banner.
|
||||
- Avoid horror stingers; even corrupted pages should be emotionally safe.
|
||||
|
||||
## 10. Long-Term Evolution
|
||||
|
||||
- Monthly train destination changes.
|
||||
- Seasonal recipe variants.
|
||||
- Yearly visitor anniversary letters.
|
||||
- A slow Luma arc where the lamp becomes less shy after enough patient moments.
|
||||
- Nettle's filing labels can become warmer over time.
|
||||
- Morrow's return ticket can gain stamps after visits to secret rooms.
|
||||
- Guestbook users can post new fake entries on real calendar dates.
|
||||
|
||||
## 11. Future Expansion Ideas
|
||||
|
||||
- Add a real local-only guestbook draft pad that stores messages in `localStorage`.
|
||||
- Add a hidden "attic" page unlocked after visiting all secret rooms.
|
||||
- Add generated cassette-label images for each log.
|
||||
- Add a no-score cooking memory game using family recipe fragments.
|
||||
- Add an RSS-feed-only hidden note.
|
||||
- Add source-map comments or HTML comments with letter fragments.
|
||||
- Add a "kindness route" where closing pages gently or waiting unlocks softer endings.
|
||||
|
||||
## 12. Maintaining Mystery
|
||||
|
||||
- Do not explain all triggers in the public UI.
|
||||
- Keep most secrets optional and small.
|
||||
- Never punish users for missing content.
|
||||
- Use recurrence, not randomness alone: objects and characters should reappear with altered context.
|
||||
- Make clues emotionally legible, not puzzle-hostile.
|
||||
- Avoid completion language. The site is a place, not a checklist.
|
||||
|
||||
## 13. Implementation Notes
|
||||
|
||||
- Current state is client-side only through `localStorage` key `zxh_hidden_details_v1`.
|
||||
- Secret routes are static Org pages.
|
||||
- `#+HIDDEN_PAGE: t` excludes pages from sitemap/recently-updated via `lisp/sitemaps.el` and `lisp/recently-updated.el`.
|
||||
- Add backend later only if real guestbook/comment persistence is desired.
|
||||
- Keep hidden scripts dependency-free.
|
||||
|
||||
## 14. Canon Rule
|
||||
|
||||
The hidden soul should always imply: somebody lived here, somebody cared enough to label the drawer badly, and the visitor is welcome without needing to prove they found everything.
|
||||
|
||||
## 15. Second-Layer Text Registry
|
||||
|
||||
These are the newly added second-layer strings. The first-layer registry remains in `assets/scripts/hidden-details.js` as the original 102-detail foundation.
|
||||
|
||||
### 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.
|
||||
|
||||
### Fake 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.
|
||||
|
||||
### Journal Fragments
|
||||
|
||||
- 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.
|
||||
|
||||
### Comforting 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.
|
||||
|
||||
### Recurring 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.
|
||||
|
||||
### Cassette Logs
|
||||
|
||||
- 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.
|
||||
|
||||
### Fake Users
|
||||
|
||||
- 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
|
||||
|
||||
### Rare Homepage Takeovers
|
||||
|
||||
- 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.
|
||||
|
||||
### Seasonal Lines
|
||||
|
||||
- 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.
|
||||
|
||||
### Secret Room Search Terms
|
||||
|
||||
- `radio under stairs` -> Radio Room.
|
||||
- `guestbook 1999` -> Guestbook 1999.
|
||||
- `do not open` -> Do Not Open.
|
||||
- `cassette` -> Cassette Log.
|
||||
- `return ticket` -> Train Platform.
|
||||
- `unfinished letter` -> Forgotten Draft.
|
||||
- `burnt sugar` -> Corrupted Recipe.
|
||||
- `cupboard` -> Terminal Cupboard.
|
||||
- `patience` -> Patience Game.
|
||||
|
||||
## 16. Maintenance Checklist
|
||||
|
||||
- When adding a new hidden page, include `#+HIDDEN_PAGE: t`.
|
||||
- Add the route to the invisible link registry only if accidental discovery is acceptable.
|
||||
- Add one lore connection, one emotional reason, and one non-obvious trigger.
|
||||
- Keep text short in toasts; move longer writing into hidden rooms.
|
||||
- Prefer local state and static pages. Add backend persistence only for explicitly public features.
|
||||
- Rebuild with `emacs -Q --script build-site.el`.
|
||||
- Verify secret pages build and do not appear in `sitemap.org` or `recently-updated.org`.
|
||||
25
play/cassette-log.org
Normal file
25
play/cassette-log.org
Normal file
@@ -0,0 +1,25 @@
|
||||
#+TITLE: Cassette Log
|
||||
#+OPTIONS: num:nil title:nil toc:nil
|
||||
#+NO_SIDENOTES: t
|
||||
#+HIDDEN_PAGE: t
|
||||
#+DATE: <2026-05-09 Sat>
|
||||
|
||||
#+BEGIN_EXPORT html
|
||||
<main class="play-root">
|
||||
<section class="play-hero">
|
||||
<div>
|
||||
<p class="play-kicker">Audio shelf</p>
|
||||
<h1>Cassette Log</h1>
|
||||
<p class="play-lede">Transcripts from tapes with labels in three different handwritings.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="play-console">
|
||||
<div class="play-console__screen">
|
||||
<button class="play-primary" type="button" data-cassette="a">Play A</button>
|
||||
<button class="play-primary" type="button" data-cassette="b">Play B</button>
|
||||
<button class="play-primary" type="button" data-cassette="c">Play C</button>
|
||||
<p>The buttons play tiny original tones and reveal transcripts. Nothing starts without being asked.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
#+END_EXPORT
|
||||
26
play/corrupted-recipe.org
Normal file
26
play/corrupted-recipe.org
Normal file
@@ -0,0 +1,26 @@
|
||||
#+TITLE: Corrupted Recipe
|
||||
#+OPTIONS: num:nil title:nil toc:nil
|
||||
#+NO_SIDENOTES: t
|
||||
#+HIDDEN_PAGE: t
|
||||
#+DATE: <2026-05-09 Sat>
|
||||
|
||||
#+BEGIN_EXPORT html
|
||||
<main class="play-root">
|
||||
<section class="play-hero">
|
||||
<div>
|
||||
<p class="play-kicker">Recovered file</p>
|
||||
<h1>Corrupted Recipe</h1>
|
||||
<p class="play-lede">The ingredients survived. The measurements became folklore.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="play-console">
|
||||
<div class="play-console__screen">
|
||||
<p>1 onion, finely remembered</p>
|
||||
<p>2 cups rice, or enough for whoever says they are not hungry</p>
|
||||
<p>burnt sugar until the kitchen smells like almost</p>
|
||||
<p>[DATA LOST] but Aunt Safiya says add patience here</p>
|
||||
<p>Serve with the story about the train, but skip the sad part unless someone asks.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
#+END_EXPORT
|
||||
25
play/do-not-open.org
Normal file
25
play/do-not-open.org
Normal file
@@ -0,0 +1,25 @@
|
||||
#+TITLE: Do Not Open
|
||||
#+OPTIONS: num:nil title:nil toc:nil
|
||||
#+NO_SIDENOTES: t
|
||||
#+HIDDEN_PAGE: t
|
||||
#+DATE: <2026-05-09 Sat>
|
||||
|
||||
#+BEGIN_EXPORT html
|
||||
<main class="play-root">
|
||||
<section class="play-hero">
|
||||
<div>
|
||||
<p class="play-kicker">Envelope</p>
|
||||
<h1>Do Not Open</h1>
|
||||
<p class="play-lede">The instruction was written by someone who hoped you would be careful, not obedient.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="play-console">
|
||||
<div class="play-console__screen">
|
||||
<p>Dear whoever needed a door,</p>
|
||||
<p>I wrote "do not open" because I was embarrassed by how much I wanted to be found.</p>
|
||||
<p>If you are here, please take the small ordinary blessing: may the next room be kinder than the last one.</p>
|
||||
<p>- Etta</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
#+END_EXPORT
|
||||
24
play/forgotten-draft.org
Normal file
24
play/forgotten-draft.org
Normal file
@@ -0,0 +1,24 @@
|
||||
#+TITLE: Forgotten Draft
|
||||
#+OPTIONS: num:nil title:nil toc:nil
|
||||
#+NO_SIDENOTES: t
|
||||
#+HIDDEN_PAGE: t
|
||||
#+DATE: <2026-05-09 Sat>
|
||||
|
||||
#+BEGIN_EXPORT html
|
||||
<main class="play-root">
|
||||
<section class="play-hero">
|
||||
<div>
|
||||
<p class="play-kicker">Unsent</p>
|
||||
<h1>Forgotten Draft</h1>
|
||||
<p class="play-lede">A letter that never became brave enough to be email.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="play-console">
|
||||
<div class="play-console__screen">
|
||||
<p>I keep meaning to say that the house was not the same after everyone stopped being in the same room at once.</p>
|
||||
<p>But then someone sends a photo of dinner, or a broken chair, or a sky that looks like 2007, and the room comes back in pieces.</p>
|
||||
<p>I suppose this is what the website is for.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
#+END_EXPORT
|
||||
26
play/guestbook-1999.org
Normal file
26
play/guestbook-1999.org
Normal file
@@ -0,0 +1,26 @@
|
||||
#+TITLE: Guestbook 1999
|
||||
#+OPTIONS: num:nil title:nil toc:nil
|
||||
#+NO_SIDENOTES: t
|
||||
#+HIDDEN_PAGE: t
|
||||
#+DATE: <2026-05-09 Sat>
|
||||
|
||||
#+BEGIN_EXPORT html
|
||||
<main class="play-root">
|
||||
<section class="play-hero">
|
||||
<div>
|
||||
<p class="play-kicker">Old web room</p>
|
||||
<h1>Guestbook 1999</h1>
|
||||
<p class="play-lede">Signatures from usernames that may never have existed.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="play-console">
|
||||
<div class="play-console__screen">
|
||||
<p><strong>sunnydialup88:</strong> first!!!! but emotionally</p>
|
||||
<p><strong>auntie-admin:</strong> stop making the background tiled unless you mean it.</p>
|
||||
<p><strong>guest_000:</strong> I was here once, then became older, then came back.</p>
|
||||
<p><strong>nettle_mod:</strong> thread moved from Bugs to Feelings.</p>
|
||||
<p><strong>webmaster:</strong> if the counter breaks, count kindness manually.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
#+END_EXPORT
|
||||
23
play/patience-game.org
Normal file
23
play/patience-game.org
Normal file
@@ -0,0 +1,23 @@
|
||||
#+TITLE: Patience Game
|
||||
#+OPTIONS: num:nil title:nil toc:nil
|
||||
#+NO_SIDENOTES: t
|
||||
#+HIDDEN_PAGE: t
|
||||
#+DATE: <2026-05-09 Sat>
|
||||
|
||||
#+BEGIN_EXPORT html
|
||||
<main class="play-root">
|
||||
<section class="play-hero">
|
||||
<div>
|
||||
<p class="play-kicker">Tiny game</p>
|
||||
<h1>Patience Game</h1>
|
||||
<p class="play-lede">The only move is to stay a little while.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="play-console">
|
||||
<div class="play-console__screen">
|
||||
<p data-patience-target>The lamp is on. Nothing else is required.</p>
|
||||
<p><span data-patience-count>0</span> seconds kept company.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
#+END_EXPORT
|
||||
27
play/radio-room.org
Normal file
27
play/radio-room.org
Normal file
@@ -0,0 +1,27 @@
|
||||
#+TITLE: Radio Room
|
||||
#+OPTIONS: num:nil title:nil toc:nil
|
||||
#+NO_SIDENOTES: t
|
||||
#+HIDDEN_PAGE: t
|
||||
#+DATE: <2026-05-09 Sat>
|
||||
|
||||
#+BEGIN_EXPORT html
|
||||
<main class="play-root">
|
||||
<section class="play-hero">
|
||||
<div>
|
||||
<p class="play-kicker">Unlisted room</p>
|
||||
<h1>Radio Room</h1>
|
||||
<p class="play-lede">A room under the stairs where every station is almost home.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="play-console">
|
||||
<div class="play-console__screen">
|
||||
<p class="play-console__eyebrow">Tuning log</p>
|
||||
<h2>Between stations</h2>
|
||||
<p>89.1: rain on the kitchen roof.</p>
|
||||
<p>91.4: Nadir saying backups are just promises with timestamps.</p>
|
||||
<p>99.9: Luma humming an original song about lamps, trains, and coming back slowly.</p>
|
||||
<p>The dial is warm. It does not play automatically because even ghosts should ask permission.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
#+END_EXPORT
|
||||
23
play/terminal-cupboard.org
Normal file
23
play/terminal-cupboard.org
Normal file
@@ -0,0 +1,23 @@
|
||||
#+TITLE: Terminal Cupboard
|
||||
#+OPTIONS: num:nil title:nil toc:nil
|
||||
#+NO_SIDENOTES: t
|
||||
#+HIDDEN_PAGE: t
|
||||
#+DATE: <2026-05-09 Sat>
|
||||
|
||||
#+BEGIN_EXPORT html
|
||||
<main class="play-root">
|
||||
<section class="play-hero">
|
||||
<div>
|
||||
<p class="play-kicker">Hidden terminal</p>
|
||||
<h1>Terminal Cupboard</h1>
|
||||
<p class="play-lede">A command line behind the jars.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="play-console hidden-terminal">
|
||||
<div class="play-console__screen">
|
||||
<div class="hidden-terminal-log" data-cupboard-log><p>cupboard v0.9. Type help.</p></div>
|
||||
<input data-cupboard-input type="text" autocomplete="off" spellcheck="false" aria-label="Cupboard command" />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
#+END_EXPORT
|
||||
25
play/train-platform.org
Normal file
25
play/train-platform.org
Normal file
@@ -0,0 +1,25 @@
|
||||
#+TITLE: Train Platform
|
||||
#+OPTIONS: num:nil title:nil toc:nil
|
||||
#+NO_SIDENOTES: t
|
||||
#+HIDDEN_PAGE: t
|
||||
#+DATE: <2026-05-09 Sat>
|
||||
|
||||
#+BEGIN_EXPORT html
|
||||
<main class="play-root">
|
||||
<section class="play-hero">
|
||||
<div>
|
||||
<p class="play-kicker">Platform 3</p>
|
||||
<h1>Train Platform</h1>
|
||||
<p class="play-lede">The departure board changes every month, but every destination is a feeling.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="play-console">
|
||||
<div class="play-console__screen">
|
||||
<p>23:13 - Later - delayed by tenderness</p>
|
||||
<p>00:04 - Home - expected when ready</p>
|
||||
<p>06:30 - The Long Way - boarding quietly</p>
|
||||
<p>Morrow is on the bench with a return ticket he refuses to explain.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
#+END_EXPORT
|
||||
Reference in New Issue
Block a user