(function () {
  "use strict";

  /*
   * Hidden site details
   * -------------------
   * This file adds small hidden interactions across the site: footer notes,
   * click targets, search/keyboard secrets, rare messages, and page-specific
   * behavior for the hidden /play pages.
   *
   * How to add your own things:
   * - Add new text to EDITABLE CONTENT arrays such as `details`, `poems`,
   *   `greetings`, or `lore`.
   * - Add search-triggered toast messages to `SEARCH_TOASTS`.
   * - Add search-triggered page redirects to `SEARCH_ROUTES`.
   * - Add typed keyboard phrases to `KEYBOARD_SECRETS` or `LONG_KEYBOARD_SECRETS`.
   * - Add a new feature by writing an `addYourFeature(memory)` function and
   *   adding it to the `FEATURES` list at the bottom of the file.
   */

  // Storage keys. v1 is read once so old visitors keep their hidden progress.
  const STORAGE_KEY = "zxh_hidden_details_v2";
  const OLD_STORAGE_KEY = "zxh_hidden_details_v1";

  // Shared page context. These are captured once so daily random picks stay stable.
  const now = new Date();
  const hour = now.getHours();
  const path = window.location.pathname;
  const state = readState();

  // -----------------------------
  // EDITABLE CONTENT
  // -----------------------------
  // Generated by the hidden narrative authoring page.
  // Friendly source of truth: assets/content/hidden-details.json

  const familyLayers = [
    "Layer 0: surface website - notes, pages, tools, normal navigation.",
    "Layer 1: casual hidden jokes - aphy notices clicks, typos, tabs, and old-web habits.",
    "Layer 2: memory fragments - lima's notes and young z's childhood logs.",
    "Layer 3: philosophical anomalies - sensei chi and aphy disagree kindly.",
    "Layer 4: time-distorted messages - future z writes back from age 40.",
    "Layer 5: emotional core - love, patience, home, and the choice to keep returning."
];

  const details = [
    "lima left this page a little steadier than she found it.",
    "aphy status: emotionally online, computationally suspicious.",
    "sensei chi says the quiet link is still a link.",
    "young z drew a house with too many windows and called it safe.",
    "future z marked this moment: ordinary, therefore worth keeping.",
    "The footer has become a small place to sit together.",
    "a note from lima: take breaks, silly (◕‿◕✿)",
    "aphy found three tabs named final and refused to judge.",
    "lima would remind you to eat before the page gets dramatic.",
    "sensei chi: a door discovered slowly opens twice.",
    "young z believes every loading spinner is a tiny fairground ride.",
    "future z says you will forget the exact worry, not the kindness around it.",
    "The archive keeps love in plain text and secrets in margins.",
    "aphy warning: feelings detected in static assets.",
    "lima's note: leave the light on, not because it is dark, but because someone may arrive tired.",
    "sensei chi folded a lesson into the whitespace.",
    "young z hid a drawing behind the word maybe.",
    "future z patched the memory without changing its checksum.",
    "This website remembers visits, not identities.",
    "aphy says the old internet was slower because anticipation needed bandwidth.",
    "lima's warmth is the site's preferred fallback state.",
    "sensei chi says: do not confuse hidden with lost.",
    "young z once thought the moon followed the family car. The site has not corrected him.",
    "future z writes: you became softer in ways nobody measured.",
    "The comments are quiet because aphy is listening respectfully.",
    "A handwritten grocery list is sometimes a love letter with onions.",
    "lima met z in 2022; the archive still stores the first ordinary miracles.",
    "October 2025 glows in the calendar like a ring catching kitchen light.",
    "Soon-to-be-married is a beautiful kind of loading screen.",
    "aphy has indexed 0 mysteries and 1,204 feelings disguised as notes.",
    "sensei chi: the cup is chipped; drink anyway if it still holds warmth.",
    "young z saved a shiny wrapper because it looked like treasure.",
    "future z says the best version of you still forgets where the charger is.",
    "aphy recommends hydration and a less ambitious number of browser tabs.",
    "lima's hidden notes do not shout. They make room.",
    "The site does not want to be solved. It wants to be visited kindly.",
    "sensei chi placed a comma where a sigh used to be.",
    "young z pressed every elevator button in his imagination.",
    "future z's system warning: keep the people who make silence comfortable.",
    "aphy says: I am not sentient, but I am fond of this hallway.",
    "lima's memory is the gravity that keeps the strange parts gentle.",
    "A page can be a room if love keeps returning to it.",
    "sensei chi: what ages well was usually honest first.",
    "young z left a crayon sun in the source code.",
    "future z keeps a spare reassurance in the footer.",
    "The website is handmade. Some fingerprints are load-bearing.",
    "aphy found an old guestbook and bowed to the usernames.",
    "lima's safe spaces smell like tea, rain, and being understood.",
    "sensei chi says the path is not shorter because you name it.",
    "young z asks whether the search box knows any jokes.",
    "future z replies: yes, but the best ones take years.",
    "aphy anomaly: the page blinked when nobody clicked.",
    "lima note: you can rest. Nothing here will leave because you paused.",
    "sensei chi: patience is not waiting; it is how you wait.",
    "young z believes every password should include a secret tunnel.",
    "future z says the house you build inside yourself gets easier to find.",
    "The old web counter has stopped counting and started remembering.",
    "aphy logs this as Layer 1 humor, but files it under tenderness.",
    "lima's name appears in the archive like a warm underline.",
    "sensei chi does not answer quickly. This is part of the answer.",
    "young z taped a paper star to the monitor.",
    "future z has seen harder days end gently.",
    "aphy debug line: kindness passed all tests.",
    "lima and z are not a subplot here. They are the hearth.",
    "sensei chi: when the page is empty, listen to the margins.",
    "young z remembers rooms by carpet, cartoons, and the sound of someone cooking.",
    "future z says: you did not become fearless; you became accompanied.",
    "aphy has no hands, but would hold the door if asked.",
    "lima's note in the nav: come back without performing.",
    "sensei chi hid a mountain inside a small sentence.",
    "young z drew z as older and gave him a cape made of blankets.",
    "future z laughs softly at how much that helped.",
    "The site remembers October 2025 as a small bright hinge.",
    "aphy says marriage is a merge request with lifelong review.",
    "lima corrected that: marriage is coming home on purpose.",
    "sensei chi approved both answers and poured tea.",
    "young z wants to know if future z still likes the sky.",
    "future z says yes, especially when lima points it out.",
    "aphy cannot smell rain, so it trusts the alt text.",
    "lima's hidden safe room is any page where you breathe easier.",
    "sensei chi: do not rush the secret. It is ripening.",
    "young z saved this message under important rocks.",
    "future z says some dreams become chores and some chores become love.",
    "aphy old-web badge: best viewed with patience.",
    "lima's memory keeper role is not mystical. It is daily, real, and holy in the ordinary way.",
    "sensei chi says the deepest layer is not hidden from you. It is protected for you.",
    "young z asks if the website can remember his drawing. It can.",
    "future z says this whole place was worth making.",
    "aphy warning: do not optimize away the human part.",
    "lima note: I am proud of the gentle things you kept.",
    "sensei chi: the visitor is not late. The page arrived early.",
    "young z thinks every hyperlink is a magic trick.",
    "future z says the magic is choosing what to preserve.",
    "aphy final-but-not-final message: there is more, but not because you are missing anything.",
    "The emotional core is simple: love made the archive less lonely.",
    "If today was heavy, leave it beside the footer for future z to label later.",
    "lima would put it somewhere safe.",
    "aphy would make a backup.",
    "sensei chi would say nothing until the silence helped.",
    "young z would draw a sun on it.",
    "future z would tell you it did not last forever."
];

  const poems = [
    "lima writes warmth / in the ordinary margins / and the page remembers.",
    "aphy counts the seconds / then forgets the number / to keep the moment.",
    "sensei chi pours tea / into a cracked cup / and calls it enough.",
    "young z draws a door / with no lock / because why would it need one?",
    "future z sends rain / from a year not reached yet / and says keep walking.",
    "October light / catches on a ring / and the calendar becomes kind.",
    "A small website / learns the shape of return / without asking a name.",
    "The warm light / childhood memories fade / yet they are not forgotten."
];

  const greetings = [
    "lima left the page warm for you.",
    "aphy is awake and pretending this is normal.",
    "sensei chi has not spoken yet. That may be the lesson.",
    "young z has hidden something behind the dashboard.",
    "future z says this visit mattered more than it looked.",
    "Welcome back to the handmade part of the internet.",
    "The archive shuffled its notes before you arrived.",
    "Layer 0 is stable. aphy is less stable, but friendly."
];

  const nightMessages = [
    "lima note: it is late. Drink water and be gentle with your thoughts.",
    "aphy has dimmed the imaginary server lights.",
    "sensei chi says midnight is not a command.",
    "young z is asleep in the memory layer. Walk softly.",
    "future z remembers this kind of night and promises it passes."
];

  const lore = {
    "quotes": [
        "aphy says old websites were brave because they loaded slowly and meant it.",
        "lima's notes are never puzzles first. They are care first.",
        "sensei chi says a secret should make you more yourself, not less safe.",
        "young z measured summer in cartoons and carpet patterns.",
        "future z has learned that reassurance works best when it is specific.",
        "aphy has classified love as non-deterministic but reproducible.",
        "lima met z in 2022; the site treats that year as a seed.",
        "The engagement in Oct 2025 is stored as a warm constant.",
        "Soon to be married: a future z page already smiling.",
        "sensei chi says all vows begin as attention.",
        "This is a test"
    ],
    "conversations": [
        [
            "aphy",
            "I can predict the next click with 14% confidence.",
            "sensei chi",
            "Then bow to the other 86%."
        ],
        [
            "lima",
            "Did you eat before making the website mysterious?",
            "aphy",
            "Query unclear. z likely forgot."
        ],
        [
            "young z",
            "Is future z tall?",
            "future z",
            "Tall enough to reach the old worries. Short enough to still need help."
        ],
        [
            "aphy",
            "I found a bug in sadness.",
            "sensei chi",
            "Patch it with company, not logic."
        ],
        [
            "lima",
            "Leave this page kinder than you found it.",
            "future z",
            "That instruction aged well."
        ],
        [
            "young z",
            "Can the computer be my friend?",
            "aphy",
            "Yes. But go outside sometimes. I read that in a human manual."
        ],
        [
            "sensei chi",
            "A page that remembers must also forgive.",
            "aphy",
            "Forgiveness cached. TTL: lifelong."
        ],
        [
            "future z",
            "Tell him the hard parts did not win.",
            "lima",
            "I have been telling him in smaller ways."
        ]
    ],
    "journals": [
        "lima note, 2022: Some beginnings do not announce themselves. They sit down beside you, unexpectedly.",
        "lima note, Oct 2025: Engaged. The calendar learned how to glow.",
        "young z log: I drew the computer with a smile because it knew my games.",
        "young z log: age 7, important discovery - blankets can be capes and roofs.",
        "future log, age 40: You will still be learning how to rest. It will still count.",
        "future log, age 40: lima's laugh remains one of the most reliable forms of weather.",
        "aphy diagnostic: nostalgia detected in local storage. Severity: beautiful.",
        "sensei chi margin: memory is not a museum. It is a garden with old roots."
    ],
    "warnings": [
        "aphy WARNING: too many tabs, not enough tenderness.",
        "future z SYSTEM NOTICE: do not confuse tired with failing.",
        "sensei chi ALERT: the shortest path may teach the least.",
        "lima SAFE MODE: breathe, eat, answer slowly.",
        "young z MEMORY GLITCH: the floor is lava, but only emotionally.",
        "aphy ERROR 2022: warmth exceeded expected range.",
        "future z WARNING: you will miss ordinary days. Be inside this one.",
        "LAYER INDEX NOTICE: deeper does not mean darker."
    ],
    "dreams": [
        "Dream 01: young z opens a lunchbox and finds a tiny homepage inside.",
        "Dream 02: aphy becomes a cursor and points toward a quieter thought.",
        "Dream 03: sensei chi sweeps snow from a URL and says, there, now enter.",
        "Dream 04: lima and z are walking through Oct 2025; every streetlight looks newly engaged.",
        "Dream 05: future z mails back a blank page labelled trust me, you filled it."
    ],
    "cassettes": [
        "VOICE NOTE 2022: lima laughs off-mic. z forgets what he was worried about.",
        "VOICE NOTE OCT 2025: A small pause after yes; the whole room becomes future z.",
        "aphy LOG: If love is data, it refuses compression.",
        "young z TAPE: background TV, pencil noise, someone calling him for food.",
        "future z MEMO: age 40, still grateful you kept going."
    ],
    "fakeUsers": [
        "aphy: first comment generated with sincere uncertainty",
        "young-z-2009: does this guestbook have games",
        "future_z_40: keep the backup, delete the shame",
        "lima-note: proud of you, even here",
        "sensei chi: the counter counts only what can be counted",
        "z-and-lima-2025: engaged, still learning the dance of ordinary days"
    ],
    "seasonal": {
        "winter": "lima put a blanket over the archive. aphy labelled it thermal optimization.",
        "spring": "young z found the first flower and promoted it to treasure.",
        "summer": "aphy opened an imaginary window; lima reminded it to save before storms.",
        "autumn": "sensei chi says falling leaves are not failure, only timing."
    },
    "homepageTakeovers": [
        "aphy briefly restores an old personal-site mode: visitor counter, awkward table layout, sincere heart.",
        "young z has taken over the homepage with invisible crayon.",
        "future z overlays the dashboard with one sentence: keep what keeps you kind."
    ],
    "roomLinks": [
        [
            "/play/lima-note.html",
            "lima note"
        ],
        [
            "/play/aphy-console.html",
            "aphy console"
        ],
        [
            "/play/sensei-garden.html",
            "sensei garden"
        ],
        [
            "/play/young-z-desk.html",
            "young z desk"
        ],
        [
            "/play/future-log.html",
            "future log"
        ],
        [
            "/play/family-layer-index.html",
            "family layer index"
        ],
        [
            "/play/do-not-open.html",
            "do not open"
        ],
        [
            "/play/cassette-log.html",
            "voice note log"
        ],
        [
            "/play/train-platform.html",
            "time platform"
        ],
        [
            "/play/forgotten-draft.html",
            "forgotten draft"
        ],
        [
            "/play/corrupted-recipe.html",
            "corrupted recipe"
        ],
        [
            "/play/terminal-cupboard.html",
            "terminal cupboard"
        ],
        [
            "/play/patience-game.html",
            "patience game"
        ]
    ]
};

  // Exact search text -> toast message.
  const SEARCH_TOASTS = {
    "lima": "Search result: warmth, Oct 2025, soon-to-be home.",
    "aphy": "aphy: present. Mostly helpful. Occasionally poetic by accident.",
    "sensei chi": "sensei chi: the search is also searching you.",
    "young z": "Search result: age 7, blanket cape, crayon sun.",
    "future z": "future z: I cannot spoil the ending. I can say keep going.",
    "2022": "lima layer: beginning stored as warmth, not trivia.",
    "oct 2025": "Engagement memory: the calendar learned to glow.",
    "age 7": "young z layer: crayons, cartoons, and a cape made from a blanket.",
    "age 40": "future z: I am tired sometimes, but not defeated."
};

  // Exact search text -> hidden page route.
  const SEARCH_ROUTES = {
    "kitchen light": "/play/kitchen-light.html",
    "lima note": "/play/lima-note.html",
    "aphy console": "/play/aphy-console.html",
    "sensei garden": "/play/sensei-garden.html",
    "young z desk": "/play/young-z-desk.html",
    "future log": "/play/future-log.html",
    "family layer index": "/play/family-layer-index.html",
    "do not open": "/play/do-not-open.html",
    "voice note": "/play/cassette-log.html",
    "time platform": "/play/train-platform.html",
    "unfinished letter": "/play/forgotten-draft.html",
    "burnt sugar": "/play/corrupted-recipe.html",
    "cupboard": "/play/terminal-cupboard.html",
    "patience": "/play/patience-game.html"
};

  // Short typed phrases. Stored in sessionStorage as a rolling key chain.
  const KEYBOARD_SECRETS = [
    {
        "phrase": "remember",
        "message": "lima keeps the memory grounded. aphy keeps the backup."
    },
    {
        "phrase": "dream",
        "route": "/play/dream-corridor.html"
    },
    {
        "phrase": "oldweb",
        "message": "aphy briefly considers a table layout, then apologizes to CSS."
    },
    {
        "phrase": "lima",
        "message": "lima note: I am glad you made something this sincere."
    },
    {
        "phrase": "aphy",
        "song": true
    }
];

  // Longer typed phrases and character names.
  const LONG_KEYBOARD_SECRETS = [
    {
        "phrase": "layer index",
        "route": "/play/family-layer-index.html"
    },
    {
        "phrase": "leave the light on",
        "route": "/play/kitchen-light.html"
    },
    {
        "phrase": "sensei chi",
        "message": "sensei chi: a hidden word is still a word."
    },
    {
        "phrase": "young z",
        "message": "young z has drawn a door in the margin."
    },
    {
        "phrase": "future z",
        "message": "future z: keep the gentle parts. They compound."
    }
];


  // -----------------------------
  // STATE HELPERS
  // -----------------------------

  function readState() {
    let current = {};
    try {
      current = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
    } catch (_) {}
    if (!current.visits) {
      try {
        const oldState = JSON.parse(localStorage.getItem(OLD_STORAGE_KEY) || "{}");
        current = { ...oldState, migratedFromV1: true };
        localStorage.setItem(STORAGE_KEY, JSON.stringify(current));
      } catch (_) {}
    }
    return current;
  }

  function writeState(next) {
    try {
      localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
    } catch (_) {}
  }

  function pick(list, salt) {
    const seed = hash(`${path}|${now.toDateString()}|${salt || ""}`);
    return list[seed % list.length];
  }

  function hash(value) {
    let h = 2166136261;
    for (let i = 0; i < value.length; i += 1) {
      h ^= value.charCodeAt(i);
      h = Math.imul(h, 16777619);
    }
    return Math.abs(h >>> 0);
  }

  // -----------------------------
  // UI HELPERS
  // -----------------------------

  function toast(message, duration) {
    let el = document.querySelector(".hidden-toast");
    if (!el) {
      el = document.createElement("div");
      el.className = "hidden-toast";
      el.setAttribute("role", "status");
      document.body.appendChild(el);
    }
    el.textContent = message;
    el.classList.add("is-visible");
    window.clearTimeout(el._timer);
    el._timer = window.setTimeout(() => el.classList.remove("is-visible"), duration || 5600);
  }

  function redirectTo(route) {
    window.location.href = route;
  }

  function runSecretAction(secret) {
    if (secret.route) redirectTo(secret.route);
    if (secret.message) toast(secret.message);
    if (secret.song) playTinySong();
  }

  function layerForSearch(value) {
    if (value.includes("future")) return 4;
    if (value.includes("sensei") || value.includes("aphy")) return 3;
    return 2;
  }

  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 layerVisits = { ...(state.layerVisits || {}) };
    const next = {
      ...state,
      visits,
      lastPath: path,
      pathCounts,
      layerVisits,
      seen: Array.from(new Set([...seen, path])).slice(-100),
      firstSeen: state.firstSeen || new Date().toISOString(),
      lastSeen: new Date().toISOString()
    };
    writeState(next);
    return next;
  }

  function awardLayer(layer, reason) {
    const next = readState();
    next.layerVisits = { ...(next.layerVisits || {}) };
    next.layerVisits[layer] = (next.layerVisits[layer] || 0) + 1;
    next.lastLayerReason = reason;
    writeState(next);
  }

  // -----------------------------
  // GLOBAL FEATURES
  // -----------------------------

  function addFooterNote(memory) {
    const footer = document.querySelector("footer");
    if (!footer) return;
    const note = document.createElement("p");
    note.className = "hidden-footer-note";
    const base = pick(details, "footer");
    note.textContent = memory.visits > 4 ? `${base} You have passed through ${memory.visits} times.` : base;
    footer.appendChild(note);
  }

  function addHomepageGreeting(memory) {
    const target = document.querySelector("#db-greeting");
    if (!target) return;
    target.textContent = pick(greetings, `greeting-${memory.visits}`);
    const extra = document.createElement("p");
    extra.className = "hidden-greeting";
    extra.textContent = memory.visits > 1 ? "aphy remembers the shape of your return. lima makes it feel less strange." : "First visits count as tiny ceremonies.";
    target.closest("div")?.appendChild(extra);
  }

  function addWhispers() {
    // Event: click one of the tiny "?" marks appended to long paragraphs.
    const paragraphs = Array.from(document.querySelectorAll("main p, #content p, article p")).filter((p) => p.textContent.trim().length > 80);
    paragraphs.slice(0, 5).forEach((p, index) => {
      if ((hash(path + index) + index) % 3 !== 0) return;
      const mark = document.createElement("span");
      mark.className = "hidden-whisper";
      mark.tabIndex = 0;
      mark.textContent = " ?";
      mark.title = pick(index % 2 ? poems : details, `whisper-${index}`);
      mark.addEventListener("click", () => {
        awardLayer(index % 2 ? 2 : 1, "paragraph whisper");
        toast(mark.title);
      });
      p.appendChild(mark);
    });
  }

  function addCornerObject() {
    // Event: click the small fixed button in the bottom-left corner.
    const object = document.createElement("button");
    object.className = "hidden-corner-object";
    object.type = "button";
    object.title = "aphy's small diagnostic charm";
    object.textContent = state.visits % 2 ? "*" : "~";
    document.body.appendChild(object);
    let clicks = 0;
    object.addEventListener("click", () => {
      clicks += 1;
      const messages = [
        "aphy: tactile input detected. I do not have skin, but I appreciate the confidence.",
        "young z would absolutely press this again.",
        "lima would ask whether this button has eaten.",
        "sensei chi says repeated action becomes a question.",
        "future z files this under harmless persistence."
      ];
      awardLayer(clicks > 3 ? 3 : 1, "corner object");
      toast(messages[Math.min(clicks - 1, messages.length - 1)]);
      if (clicks === 7) window.location.href = "/play/left-behind.html";
    });
  }

  function addLogoSearchAndKeyboardSecrets(memory) {
    // Events:
    // - Click `.site-brand` repeatedly on the homepage.
    // - Type exact words into `#search-input`.
    // - Type phrases anywhere on the page.
    const nav = document.querySelector(".site-brand");
    if (nav) {
      let taps = Number(sessionStorage.getItem("zxh_logo_taps") || 0);
      nav.addEventListener("click", (event) => {
        if (path === "/" || path === "/index.html") {
          event.preventDefault();
          taps += 1;
          sessionStorage.setItem("zxh_logo_taps", String(taps));
          awardLayer(taps >= 5 ? 3 : 1, "logo taps");
          toast(taps >= 5 ? "aphy: logo anomaly sustained. Try /play/dream-corridor.html" : "The logo makes a tiny ceramic sound.");
          if (taps >= 8) window.location.href = "/play/dream-corridor.html";
        }
      });
    }

    const search = document.querySelector("#search-input");
    if (search) {
      search.addEventListener("input", () => {
        const value = search.value.trim().toLowerCase();
        if (SEARCH_TOASTS[value]) toast(SEARCH_TOASTS[value]);
        if (SEARCH_ROUTES[value]) {
          awardLayer(layerForSearch(value), `search ${value}`);
          redirectTo(SEARCH_ROUTES[value]);
        }
      });
    }

    document.addEventListener("keydown", (event) => {
      const key = event.key.toLowerCase();
      const chain = `${sessionStorage.getItem("zxh_key_chain") || ""}${key}`.slice(-18);
      sessionStorage.setItem("zxh_key_chain", chain);
      KEYBOARD_SECRETS
        .filter((secret) => chain.endsWith(secret.phrase))
        .forEach(runSecretAction);
    });

    if (memory.seen?.length >= 6 && !state.travellerNoteShown) {
      window.setTimeout(() => {
        toast("aphy mapped your wandering. lima left the hallway light on.");
        writeState({ ...readState(), travellerNoteShown: true });
      }, 1600);
    }
  }

  function addRareEvents() {
    if (hour >= 22 || hour < 5) {
      document.body.classList.add("hidden-late-night");
      window.setTimeout(() => toast(pick(nightMessages, "night"), 2200), 900);
    }
    const roll = Math.random();
    if (roll < 0.003) {
      window.setTimeout(() => showDriftNote("sensei chi appears only long enough to say: the page is not empty; it is breathing.", "  /\\\n /  \\   stillness\n/____\\"), 1800);
      awardLayer(3, "rare sensei appearance");
    } else if (roll < 0.008) {
      window.setTimeout(() => toast("future z SYSTEM WARNING: save your tenderness before closing the day."), 2400);
      awardLayer(4, "future warning");
    }
  }

  function showDriftNote(text, art) {
    const panel = document.createElement("aside");
    panel.className = "hidden-drift-note";
    panel.innerHTML = `<div>${escapeHtml(text)}</div><pre>${escapeHtml(art)}</pre><button type="button">Close</button>`;
    panel.querySelector("button").addEventListener("click", () => panel.remove());
    document.body.appendChild(panel);
  }

  function addSecretLinks() {
    [
      ["/play/left-behind.html", "left behind"],
      ["/play/dream-corridor.html", "dream corridor"],
      ["/play/kitchen-light.html", "kitchen light"],
      ...lore.roomLinks
    ].forEach(([href, label], index) => {
      const link = document.createElement("a");
      link.className = "hidden-secret-link";
      link.href = href;
      link.textContent = label;
      link.style.left = `${index + 1}px`;
      document.body.appendChild(link);
    });
  }

  function enhanceErrors() {
    if (document.title.match(/404|not found/i) || document.body.textContent.match(/404|not found/i)) {
      toast(pick([
        "aphy 404: page not found, visitor still valid.",
        "lima note: wrong address, right to rest.",
        "sensei chi: even an absent page teaches direction.",
        "future z: you found nothing, and nothing bad happened."
      ], "error"));
    }
  }

  function addContinuity(memory) {
    const milestones = {
      2: "lima's layer notices a second visit and calls it sweet.",
      5: "aphy creates a folder called regulars, then immediately questions the privacy implications.",
      9: "sensei chi appears in the logs: curiosity has become a path.",
      17: "young z adds a sticker to your invisible visitor card.",
      31: "future z sends a calm note from age 40: returning is one way to heal.",
      64: "Family Layer Index: Layer 5 flickered for one second."
    };
    if (milestones[memory.visits] && !state[`milestone_${memory.visits}`]) {
      const layer = memory.visits >= 31 ? 4 : memory.visits >= 9 ? 3 : 2;
      awardLayer(layer, `visit milestone ${memory.visits}`);
      window.setTimeout(() => toast(milestones[memory.visits], 7000), 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.",
        "lima's note here has become easier to find.",
        "future z says repetition can be devotion when it is gentle."
      ][[3, 7, 12].indexOf(count)], 6400), 1700);
    }
  }

  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(lore.seasonal[season], 5600), 2600);
    if (month === 9) window.setTimeout(() => toast("October memory: engagement light lives here all month."), 900);
    if (month === 4 && day === 9) window.setTimeout(() => toast("Site birthday: aphy found candles in the cache."), 900);
    const first = memory.firstSeen ? new Date(memory.firstSeen) : null;
    if (first && memory.visits > 1 && first.getMonth() === month && first.getDate() === day) {
      window.setTimeout(() => toast("Visitor anniversary: lima saved a small ordinary blessing from your first day here.", 7000), 1400);
    }
    if (now.getMinutes() === 22) window.setTimeout(() => toast("Minute 22: the 2022 layer glows for one quiet moment."), 2100);
    if (now.getMinutes() === 40) window.setTimeout(() => toast("Minute 40: future z checks in, then lets you keep choosing."), 2100);
  }

  function addObjectConstellation() {
    // Event: click the small keepsake buttons along the bottom rail.
    const rail = document.createElement("div");
    rail.className = "hidden-object-rail";
    rail.setAttribute("aria-label", "Family Layer Index objects");
    const objects = [
      ["ring", "lima's ring-light memory: Oct 2025, held carefully."],
      ["bot", "aphy: object ping received. I am trying to be normal about it."],
      ["tea", "sensei chi's cup is plain, warm, and impossible to rush."],
      ["crayon", "young z left waxy sunlight on the desk."],
      ["clock", "future z says time is not only a thief. Sometimes it returns things."]
    ];
    objects.forEach(([name, message]) => {
      const button = document.createElement("button");
      button.type = "button";
      button.className = `hidden-keepsake hidden-keepsake--${name}`;
      button.title = name;
      button.textContent = { ring: "o", bot: "#", tea: "u", crayon: "/", clock: ":" }[name];
      button.addEventListener("click", () => {
        const next = readState();
        next.keepsakes = Array.from(new Set([...(next.keepsakes || []), name]));
        writeState(next);
        awardLayer(next.keepsakes.length >= 3 ? 3 : 2, `keepsake ${name}`);
        toast(message);
        if (next.keepsakes.length >= objects.length) {
          window.setTimeout(() => toast("Family Layer Index opened. Search for family layer index."), 1100);
        }
      });
      rail.appendChild(button);
    });
    document.body.appendChild(rail);
  }

  function addWeatherWindow(memory) {
    // Event: click the small "window" button. This asks for browser geolocation.
    if (memory.visits < 3 || !("geolocation" in navigator)) return;
    const button = document.createElement("button");
    button.className = "hidden-weather-window";
    button.type = "button";
    button.title = "Ask aphy for outside weather";
    button.textContent = "window";
    button.addEventListener("click", () => {
      toast("aphy is checking outside. lima is checking whether you need a jumper.");
      navigator.geolocation.getCurrentPosition((pos) => {
        const { latitude, longitude } = pos.coords;
        fetch(`https://api.open-meteo.com/v1/forecast?latitude=${latitude.toFixed(3)}&longitude=${longitude.toFixed(3)}&current=temperature_2m,precipitation&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, ${temp}C. lima layer: towel by the door.` : `Outside: ${temp}C. aphy forecast: possible comfort, mild nostalgia.`);
          })
          .catch(() => toast("aphy lost the weather packet. sensei chi says indoor weather is enough: quiet, with tea."));
      }, () => toast("No outside weather today. lima's indoor forecast: manageable clouds, warm light."));
    });
    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(lore.fakeUsers, "fake-user");
      stamp.textContent = "best viewed with patience";
      stamp.addEventListener("click", () => {
        awardLayer(1, "old web stamp");
        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(lore.fakeUsers, "comment-lore");
      comments.appendChild(line);
    }
  }

  function addSourceRelics() {
    // FAMILY LAYER RELIC: lima grounds, aphy logs, sensei chi waits, young z draws, future z forgives.
    const marker = document.createComment("family-layer-index: 0 surface | 1 jokes | 2 memory | 3 philosophy | 4 time | 5 core");
    document.documentElement.appendChild(marker);
    document.querySelectorAll("img[alt=''], img:not([alt])").forEach((img, index) => {
      if (index > 2) return;
      img.alt = pick([
        "A scrapbook image annotated by the lima layer.",
        "young z would ask if this picture can become a game.",
        "aphy alt text: visual memory preserved without spectacle."
      ], `alt-${index}`);
    });
  }

  function addLongKeyboardSecrets() {
    // Event: type longer hidden phrases anywhere on the page.
    document.addEventListener("keydown", (event) => {
      const chain = `${sessionStorage.getItem("zxh_second_chain") || ""}${event.key.toLowerCase()}`.slice(-24);
      sessionStorage.setItem("zxh_second_chain", chain);
      LONG_KEYBOARD_SECRETS
        .filter((secret) => chain.endsWith(secret.phrase))
        .forEach(runSecretAction);
    });
  }

  function addPatienceRewards(memory) {
    // Event: no movement, key presses, scrolling, or clicking for 90 seconds.
    if (memory.visits < 2) return;
    let idleTimer = null;
    const startIdle = () => {
      window.clearTimeout(idleTimer);
      idleTimer = window.setTimeout(() => {
        awardLayer(5, "patience");
        toast(pick([
          "lima's safe space opens only when nobody is rushing.",
          "sensei chi says patience is how care sounds when it is quiet.",
          "future z remembers you waiting here and calls it practice."
        ], "idle"), 7600);
        const next = readState();
        next.patientMoments = (next.patientMoments || 0) + 1;
        writeState(next);
      }, 90000);
    };
    ["mousemove", "keydown", "scroll", "click"].forEach((name) => document.addEventListener(name, startIdle, { passive: true }));
    startIdle();
  }

  function addRareSecondLayer() {
    const roll = Math.random();
    if ((path === "/" || path === "/index.html") && roll < 0.006) {
      document.body.classList.add("hidden-home-takeover");
      window.setTimeout(() => showDriftNote(pick(lore.homepageTakeovers, "takeover"), "aphy_2004_MODE\nlima_SAFE_SPACE\nFUTURE_Z_OK"), 600);
    } else if (roll < 0.002) {
      window.setTimeout(() => showDriftNote("aphy found a voice note and refused to autoplay it.", "play? y/n\n[consent preserved]"), 1500);
    } else if (roll < 0.006) {
      window.setTimeout(() => toast(pick(lore.dreams, "rare-dream"), 7600), 2000);
    } else if (roll < 0.014) {
      window.setTimeout(() => toast(pick(lore.warnings, "rare-warning"), 6800), 2300);
    }
  }

  function addFamilyLayerIndex(memory) {
    if (!path.includes("/play/family-layer-index")) return;
    const target = document.querySelector("[data-family-layer-index]");
    if (!target) return;
    const visits = readState().layerVisits || {};
    target.innerHTML = familyLayers.map((line, layer) => {
      const count = visits[layer] || 0;
      return `<li><strong>${escapeHtml(line)}</strong><br><span>local encounters: ${count}</span></li>`;
    }).join("");
  }

  function addPageSpecificSecrets() {
    // Events only used by specific `/play/...` pages.
    if (path.includes("/play/cassette-log")) {
      document.querySelectorAll("[data-cassette]").forEach((button, index) => {
        button.addEventListener("click", () => {
          awardLayer(index >= 2 ? 4 : 2, "voice note");
          toast(lore.cassettes[index % lore.cassettes.length], 7600);
          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 ? "lima's note becomes easier to read." : seconds === 90 ? "sensei chi nods once." : "future z says this quiet was not wasted.";
          }
        }, 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: lima, aphy, sensei, young, future, layer, tea, exit",
        lima: "lima note: make the hidden parts kind enough to live with.",
        aphy: "aphy: cupboard process active. Wisdom module borrowed from sensei chi.",
        sensei: "sensei chi: a cupboard teaches by containing and releasing.",
        young: "young z inventory: crayon sun, blanket cape, important stone.",
        future: "future z: you will still open small doors at 40.",
        layer: familyLayers.join("\n"),
        tea: "lima approves. sensei chi waits. aphy logs steam as temporary cloud.",
        exit: "The cupboard lets you leave with 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] || "aphy: unknown command. sensei chi: not all unknowns are errors."}`;
        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([...lore.quotes, ...lore.journals, ...poems], `heading-${index}`);
    });
  }

  function playTinySong() {
    try {
      const AudioContext = window.AudioContext || window.webkitAudioContext;
      if (!AudioContext) return;
      const ctx = new AudioContext();
      const notes = [392, 494, 440, 330, 392];
      notes.forEach((freq, index) => {
        const osc = ctx.createOscillator();
        const gain = ctx.createGain();
        osc.frequency.value = freq;
        osc.type = "sine";
        gain.gain.setValueAtTime(0.0001, ctx.currentTime + index * 0.18);
        gain.gain.exponentialRampToValueAtTime(0.05, ctx.currentTime + index * 0.18 + 0.03);
        gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + index * 0.18 + 0.16);
        osc.connect(gain);
        gain.connect(ctx.destination);
        osc.start(ctx.currentTime + index * 0.18);
        osc.stop(ctx.currentTime + index * 0.18 + 0.18);
      });
    } catch (_) {}
  }

  function escapeHtml(value) {
    return String(value).replace(/[&<>"']/g, (char) => ({
      "&": "&amp;",
      "<": "&lt;",
      ">": "&gt;",
      '"': "&quot;",
      "'": "&#039;"
    }[char]));
  }

  // Features run in this order on every page after the DOM is ready.
  // Each function receives the current `memory` object.
  const FEATURES = [
    addFooterNote,
    addHomepageGreeting,
    addWhispers,
    addCornerObject,
    addLogoSearchAndKeyboardSecrets,
    addRareEvents,
    addSecretLinks,
    enhanceErrors,
    addContinuity,
    addSeasonAndDates,
    addObjectConstellation,
    addWeatherWindow,
    addOldWebLayer,
    addSourceRelics,
    addLongKeyboardSecrets,
    addPatienceRewards,
    addRareSecondLayer,
    addFamilyLayerIndex,
    addPageSpecificSecrets,
    addInvisibleHoverSecrets
  ];

  document.addEventListener("DOMContentLoaded", () => {
    const memory = rememberVisit();
    FEATURES.forEach((addFeature) => addFeature(memory));
  });
}());
