diff --git a/assets/scripts/hidden-details.js b/assets/scripts/hidden-details.js index d162e7f..cf8c227 100644 --- a/assets/scripts/hidden-details.js +++ b/assets/scripts/hidden-details.js @@ -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(); }); }()); diff --git a/assets/styles/hidden-details.css b/assets/styles/hidden-details.css index 4be504e..668ecd9 100644 --- a/assets/styles/hidden-details.css +++ b/assets/styles/hidden-details.css @@ -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; diff --git a/docs/hidden-soul-bible.md b/docs/hidden-soul-bible.md new file mode 100644 index 0000000..04f550d --- /dev/null +++ b/docs/hidden-soul-bible.md @@ -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`. diff --git a/play/cassette-log.org b/play/cassette-log.org new file mode 100644 index 0000000..4a53ee9 --- /dev/null +++ b/play/cassette-log.org @@ -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 +
+
+
+

Audio shelf

+

Cassette Log

+

Transcripts from tapes with labels in three different handwritings.

+
+
+
+
+ + + +

The buttons play tiny original tones and reveal transcripts. Nothing starts without being asked.

+
+
+
+#+END_EXPORT diff --git a/play/corrupted-recipe.org b/play/corrupted-recipe.org new file mode 100644 index 0000000..6381ed6 --- /dev/null +++ b/play/corrupted-recipe.org @@ -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 +
+
+
+

Recovered file

+

Corrupted Recipe

+

The ingredients survived. The measurements became folklore.

+
+
+
+
+

1 onion, finely remembered

+

2 cups rice, or enough for whoever says they are not hungry

+

burnt sugar until the kitchen smells like almost

+

[DATA LOST] but Aunt Safiya says add patience here

+

Serve with the story about the train, but skip the sad part unless someone asks.

+
+
+
+#+END_EXPORT diff --git a/play/do-not-open.org b/play/do-not-open.org new file mode 100644 index 0000000..768cb31 --- /dev/null +++ b/play/do-not-open.org @@ -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 +
+
+
+

Envelope

+

Do Not Open

+

The instruction was written by someone who hoped you would be careful, not obedient.

+
+
+
+
+

Dear whoever needed a door,

+

I wrote "do not open" because I was embarrassed by how much I wanted to be found.

+

If you are here, please take the small ordinary blessing: may the next room be kinder than the last one.

+

- Etta

+
+
+
+#+END_EXPORT diff --git a/play/forgotten-draft.org b/play/forgotten-draft.org new file mode 100644 index 0000000..df00b73 --- /dev/null +++ b/play/forgotten-draft.org @@ -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 +
+
+
+

Unsent

+

Forgotten Draft

+

A letter that never became brave enough to be email.

+
+
+
+
+

I keep meaning to say that the house was not the same after everyone stopped being in the same room at once.

+

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.

+

I suppose this is what the website is for.

+
+
+
+#+END_EXPORT diff --git a/play/guestbook-1999.org b/play/guestbook-1999.org new file mode 100644 index 0000000..98495cb --- /dev/null +++ b/play/guestbook-1999.org @@ -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 +
+
+
+

Old web room

+

Guestbook 1999

+

Signatures from usernames that may never have existed.

+
+
+
+
+

sunnydialup88: first!!!! but emotionally

+

auntie-admin: stop making the background tiled unless you mean it.

+

guest_000: I was here once, then became older, then came back.

+

nettle_mod: thread moved from Bugs to Feelings.

+

webmaster: if the counter breaks, count kindness manually.

+
+
+
+#+END_EXPORT diff --git a/play/patience-game.org b/play/patience-game.org new file mode 100644 index 0000000..6de3d15 --- /dev/null +++ b/play/patience-game.org @@ -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 +
+
+
+

Tiny game

+

Patience Game

+

The only move is to stay a little while.

+
+
+
+
+

The lamp is on. Nothing else is required.

+

0 seconds kept company.

+
+
+
+#+END_EXPORT diff --git a/play/radio-room.org b/play/radio-room.org new file mode 100644 index 0000000..c907f11 --- /dev/null +++ b/play/radio-room.org @@ -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 +
+
+
+

Unlisted room

+

Radio Room

+

A room under the stairs where every station is almost home.

+
+
+
+
+

Tuning log

+

Between stations

+

89.1: rain on the kitchen roof.

+

91.4: Nadir saying backups are just promises with timestamps.

+

99.9: Luma humming an original song about lamps, trains, and coming back slowly.

+

The dial is warm. It does not play automatically because even ghosts should ask permission.

+
+
+
+#+END_EXPORT diff --git a/play/terminal-cupboard.org b/play/terminal-cupboard.org new file mode 100644 index 0000000..21aa475 --- /dev/null +++ b/play/terminal-cupboard.org @@ -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 +
+
+
+

Hidden terminal

+

Terminal Cupboard

+

A command line behind the jars.

+
+
+
+
+

cupboard v0.9. Type help.

+ +
+
+
+#+END_EXPORT diff --git a/play/train-platform.org b/play/train-platform.org new file mode 100644 index 0000000..e12866d --- /dev/null +++ b/play/train-platform.org @@ -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 +
+
+
+

Platform 3

+

Train Platform

+

The departure board changes every month, but every destination is a feeling.

+
+
+
+
+

23:13 - Later - delayed by tenderness

+

00:04 - Home - expected when ready

+

06:30 - The Long Way - boarding quietly

+

Morrow is on the bench with a return ticket he refuses to explain.

+
+
+
+#+END_EXPORT