This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -36,123 +36,123 @@
|
||||
|
||||
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 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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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."
|
||||
"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.",
|
||||
"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."
|
||||
@@ -160,82 +160,82 @@
|
||||
|
||||
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.",
|
||||
"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."
|
||||
"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."
|
||||
"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.",
|
||||
"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.",
|
||||
"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 page already smiling.",
|
||||
"Sensei Chi says all vows begin as attention.",
|
||||
"Soon to be married: a future z page already smiling.",
|
||||
"sensei chi says all vows begin as attention.",
|
||||
"This is a test"
|
||||
],
|
||||
"conversations": [
|
||||
[
|
||||
"Aphy",
|
||||
"aphy",
|
||||
"I can predict the next click with 14% confidence.",
|
||||
"Sensei Chi",
|
||||
"sensei chi",
|
||||
"Then bow to the other 86%."
|
||||
],
|
||||
[
|
||||
"lima",
|
||||
"Did you eat before making the website mysterious?",
|
||||
"Aphy",
|
||||
"Query unclear. Z likely forgot."
|
||||
"aphy",
|
||||
"Query unclear. z likely forgot."
|
||||
],
|
||||
[
|
||||
"Young Z",
|
||||
"Is Future Z tall?",
|
||||
"Future Z",
|
||||
"young z",
|
||||
"Is future z tall?",
|
||||
"future z",
|
||||
"Tall enough to reach the old worries. Short enough to still need help."
|
||||
],
|
||||
[
|
||||
"Aphy",
|
||||
"aphy",
|
||||
"I found a bug in sadness.",
|
||||
"Sensei Chi",
|
||||
"sensei chi",
|
||||
"Patch it with company, not logic."
|
||||
],
|
||||
[
|
||||
"lima",
|
||||
"Leave this page kinder than you found it.",
|
||||
"Future Z",
|
||||
"future z",
|
||||
"That instruction aged well."
|
||||
],
|
||||
[
|
||||
"Young Z",
|
||||
"young z",
|
||||
"Can the computer be my friend?",
|
||||
"Aphy",
|
||||
"aphy",
|
||||
"Yes. But go outside sometimes. I read that in a human manual."
|
||||
],
|
||||
[
|
||||
"Sensei Chi",
|
||||
"sensei chi",
|
||||
"A page that remembers must also forgive.",
|
||||
"Aphy",
|
||||
"aphy",
|
||||
"Forgiveness cached. TTL: lifelong."
|
||||
],
|
||||
[
|
||||
"Future Z",
|
||||
"future z",
|
||||
"Tell him the hard parts did not win.",
|
||||
"lima",
|
||||
"I have been telling him in smaller ways."
|
||||
@@ -244,55 +244,55 @@
|
||||
"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 Z log, age 40: You will still be learning how to rest. It will still count.",
|
||||
"Future Z 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."
|
||||
"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.",
|
||||
"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.",
|
||||
"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."
|
||||
"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.",
|
||||
"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."
|
||||
"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_bot: first comment generated with sincere uncertainty",
|
||||
"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",
|
||||
"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."
|
||||
"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."
|
||||
"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": [
|
||||
[
|
||||
@@ -353,14 +353,14 @@
|
||||
// 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.",
|
||||
"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.",
|
||||
"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."
|
||||
"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.
|
||||
@@ -385,7 +385,7 @@
|
||||
const KEYBOARD_SECRETS = [
|
||||
{
|
||||
"phrase": "remember",
|
||||
"message": "lima keeps the memory grounded. Aphy keeps the backup."
|
||||
"message": "lima keeps the memory grounded. aphy keeps the backup."
|
||||
},
|
||||
{
|
||||
"phrase": "dream",
|
||||
@@ -393,16 +393,12 @@
|
||||
},
|
||||
{
|
||||
"phrase": "oldweb",
|
||||
"message": "Aphy briefly considers a table layout, then apologizes to CSS."
|
||||
"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": "sensei",
|
||||
"message": "Sensei Chi: a hidden word is still a word."
|
||||
},
|
||||
{
|
||||
"phrase": "aphy",
|
||||
"song": true
|
||||
@@ -419,13 +415,17 @@
|
||||
"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."
|
||||
"message": "young z has drawn a door in the margin."
|
||||
},
|
||||
{
|
||||
"phrase": "future z",
|
||||
"message": "Future Z: keep the gentle parts. They compound."
|
||||
"message": "future z: keep the gentle parts. They compound."
|
||||
}
|
||||
];
|
||||
|
||||
@@ -551,7 +551,7 @@
|
||||
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.";
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -578,18 +578,18 @@
|
||||
const object = document.createElement("button");
|
||||
object.className = "hidden-corner-object";
|
||||
object.type = "button";
|
||||
object.title = "Aphy's small diagnostic charm";
|
||||
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.",
|
||||
"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."
|
||||
"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)]);
|
||||
@@ -611,7 +611,7 @@
|
||||
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.");
|
||||
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";
|
||||
}
|
||||
});
|
||||
@@ -640,7 +640,7 @@
|
||||
|
||||
if (memory.seen?.length >= 6 && !state.travellerNoteShown) {
|
||||
window.setTimeout(() => {
|
||||
toast("Aphy mapped your wandering. lima left the hallway light on.");
|
||||
toast("aphy mapped your wandering. lima left the hallway light on.");
|
||||
writeState({ ...readState(), travellerNoteShown: true });
|
||||
}, 1600);
|
||||
}
|
||||
@@ -653,10 +653,10 @@
|
||||
}
|
||||
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);
|
||||
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);
|
||||
window.setTimeout(() => toast("future z SYSTEM WARNING: save your tenderness before closing the day."), 2400);
|
||||
awardLayer(4, "future warning");
|
||||
}
|
||||
}
|
||||
@@ -688,10 +688,10 @@
|
||||
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.",
|
||||
"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."
|
||||
"sensei chi: even an absent page teaches direction.",
|
||||
"future z: you found nothing, and nothing bad happened."
|
||||
], "error"));
|
||||
}
|
||||
}
|
||||
@@ -699,10 +699,10 @@
|
||||
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.",
|
||||
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}`]) {
|
||||
@@ -717,7 +717,7 @@
|
||||
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."
|
||||
"future z says repetition can be devotion when it is gentle."
|
||||
][[3, 7, 12].indexOf(count)], 6400), 1700);
|
||||
}
|
||||
}
|
||||
@@ -728,13 +728,13 @@
|
||||
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);
|
||||
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);
|
||||
if (now.getMinutes() === 40) window.setTimeout(() => toast("Minute 40: future z checks in, then lets you keep choosing."), 2100);
|
||||
}
|
||||
|
||||
function addObjectConstellation() {
|
||||
@@ -744,10 +744,10 @@
|
||||
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."]
|
||||
["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");
|
||||
@@ -776,10 +776,10 @@
|
||||
const button = document.createElement("button");
|
||||
button.className = "hidden-weather-window";
|
||||
button.type = "button";
|
||||
button.title = "Ask Aphy for outside weather";
|
||||
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.");
|
||||
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)}¤t=temperature_2m,precipitation&timezone=auto`)
|
||||
@@ -788,9 +788,9 @@
|
||||
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.`);
|
||||
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."));
|
||||
.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);
|
||||
@@ -818,15 +818,15 @@
|
||||
}
|
||||
|
||||
function addSourceRelics() {
|
||||
// FAMILY LAYER RELIC: lima grounds, Aphy logs, Sensei Chi waits, Young Z draws, Future Z forgives.
|
||||
// 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."
|
||||
"young z would ask if this picture can become a game.",
|
||||
"aphy alt text: visual memory preserved without spectacle."
|
||||
], `alt-${index}`);
|
||||
});
|
||||
}
|
||||
@@ -852,8 +852,8 @@
|
||||
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."
|
||||
"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;
|
||||
@@ -868,9 +868,9 @@
|
||||
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);
|
||||
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);
|
||||
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) {
|
||||
@@ -909,7 +909,7 @@
|
||||
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.";
|
||||
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);
|
||||
}
|
||||
@@ -920,19 +920,19 @@
|
||||
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.",
|
||||
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.",
|
||||
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."}`;
|
||||
line.textContent = `> ${value}\n${commands[value] || "aphy: unknown command. sensei chi: not all unknowns are errors."}`;
|
||||
log.appendChild(line);
|
||||
input.value = "";
|
||||
});
|
||||
|
||||
@@ -121,11 +121,11 @@ HIDDEN_CONTENT_TYPES = [
|
||||
"loading screen message",
|
||||
"secret interaction",
|
||||
"hidden tooltip",
|
||||
"Future Z message",
|
||||
"Young Z memory fragment",
|
||||
"Sensei Chi wisdom entry",
|
||||
"Aphy system message",
|
||||
"Lima note/message",
|
||||
"future z message",
|
||||
"young z memory fragment",
|
||||
"sensei chi wisdom entry",
|
||||
"aphy system message",
|
||||
"lima note/message",
|
||||
"dream sequence",
|
||||
"terminal log",
|
||||
"fake error message",
|
||||
@@ -142,7 +142,69 @@ HIDDEN_CONTENT_TYPES = [
|
||||
"keyboard secret",
|
||||
]
|
||||
|
||||
HIDDEN_CHARACTERS = ["Lima", "Aphy", "Sensei Chi", "Young Z", "Future Z", "Z"]
|
||||
CHARACTER_REGISTRY = {
|
||||
"young z": {
|
||||
"id": "young z",
|
||||
"displayLabel": "young z",
|
||||
"aliases": ["Young Z", "young z", "young-z", "young_z", "young"],
|
||||
"territoryColor": "#d8a95a",
|
||||
"symbol": "Y",
|
||||
"motifs": ["crayon sun", "blanket cape", "childhood desk"],
|
||||
"themes": ["childhood", "play", "memory", "safety"],
|
||||
"affinities": ["z", "future z", "aphy", "lima"],
|
||||
},
|
||||
"z": {
|
||||
"id": "z",
|
||||
"displayLabel": "z",
|
||||
"aliases": ["Z", "z", "zaine"],
|
||||
"territoryColor": "#d6c38a",
|
||||
"symbol": "Z",
|
||||
"motifs": ["archive", "website", "home"],
|
||||
"themes": ["selfhood", "return", "making"],
|
||||
"affinities": ["lima", "young z", "future z"],
|
||||
},
|
||||
"aphy": {
|
||||
"id": "aphy",
|
||||
"displayLabel": "aphy",
|
||||
"aliases": ["Aphy", "aphy", "aphy_bot", "aphy bot"],
|
||||
"territoryColor": "#7fb089",
|
||||
"symbol": "A",
|
||||
"motifs": ["console", "diagnostic", "backup"],
|
||||
"themes": ["humor", "systems", "care through tools"],
|
||||
"affinities": ["z", "lima", "sensei chi"],
|
||||
},
|
||||
"lima": {
|
||||
"id": "lima",
|
||||
"displayLabel": "lima",
|
||||
"aliases": ["Lima", "lima"],
|
||||
"territoryColor": "#d06b78",
|
||||
"symbol": "L",
|
||||
"motifs": ["warmth", "kitchen light", "ring"],
|
||||
"themes": ["love", "home", "grounding"],
|
||||
"affinities": ["z", "aphy", "future z", "young z"],
|
||||
},
|
||||
"sensei chi": {
|
||||
"id": "sensei chi",
|
||||
"displayLabel": "sensei chi",
|
||||
"aliases": ["Sensei Chi", "sensei chi", "sensei-chi", "sensei_chi", "sensei"],
|
||||
"territoryColor": "#75a9bd",
|
||||
"symbol": "S",
|
||||
"motifs": ["tea", "garden", "quiet lesson"],
|
||||
"themes": ["reflection", "patience", "wisdom"],
|
||||
"affinities": ["aphy", "future z"],
|
||||
},
|
||||
"future z": {
|
||||
"id": "future z",
|
||||
"displayLabel": "future z",
|
||||
"aliases": ["Future Z", "future z", "future-z", "future_z", "future"],
|
||||
"territoryColor": "#a58ac9",
|
||||
"symbol": "F",
|
||||
"motifs": ["clock", "age 40", "future log"],
|
||||
"themes": ["time", "reassurance", "continuity"],
|
||||
"affinities": ["z", "young z", "lima", "sensei chi"],
|
||||
},
|
||||
}
|
||||
HIDDEN_CHARACTERS = list(CHARACTER_REGISTRY)
|
||||
HIDDEN_TONES = ["warm", "funny", "nostalgic", "wise", "strange", "soft", "hopeful", "protective", "melancholy"]
|
||||
HIDDEN_RARITIES = ["common", "uncommon", "rare", "very rare", "seasonal", "timed"]
|
||||
HIDDEN_LAYER_DEPTHS = [
|
||||
@@ -159,17 +221,17 @@ HIDDEN_LAYER_DEPTHS = [
|
||||
{
|
||||
"id": "2",
|
||||
"name": "Memory Layer",
|
||||
"meaning": "Young Z memories, Lima notes, nostalgia, and emotional fragments.",
|
||||
"meaning": "young z memories, lima notes, nostalgia, and emotional fragments.",
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"name": "Reflection Layer",
|
||||
"meaning": "Sensei Chi philosophy, Aphy conversations, and introspection.",
|
||||
"meaning": "sensei chi philosophy, aphy conversations, and introspection.",
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"name": "Time Layer",
|
||||
"meaning": "Future Z logs, time anomalies, long-term revisits, and future/past echoes.",
|
||||
"meaning": "future z logs, time anomalies, long-term revisits, and future/past echoes.",
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
@@ -773,11 +835,67 @@ def js_literal_to_json(value: str) -> Any:
|
||||
return json.loads(cleaned)
|
||||
|
||||
|
||||
def character_alias_lookup() -> dict[str, str]:
|
||||
aliases = {}
|
||||
for canonical, config in CHARACTER_REGISTRY.items():
|
||||
aliases[canonical] = canonical
|
||||
for alias in config["aliases"]:
|
||||
aliases[slugify(str(alias)).replace("-", " ")] = canonical
|
||||
aliases[str(alias).strip().lower()] = canonical
|
||||
return aliases
|
||||
|
||||
|
||||
def normalize_character_name(value: Any) -> str | None:
|
||||
raw = str(value or "").strip()
|
||||
if not raw:
|
||||
return None
|
||||
simplified = slugify(raw).replace("-", " ")
|
||||
return character_alias_lookup().get(raw.lower()) or character_alias_lookup().get(simplified)
|
||||
|
||||
|
||||
def normalize_character_list(values: Any) -> list[str]:
|
||||
if isinstance(values, str):
|
||||
raw_values = re.split(r"[,/]", values)
|
||||
elif isinstance(values, list):
|
||||
raw_values = values
|
||||
else:
|
||||
raw_values = []
|
||||
normalized = []
|
||||
for item in raw_values:
|
||||
canonical = normalize_character_name(item)
|
||||
if canonical and canonical not in normalized:
|
||||
normalized.append(canonical)
|
||||
return normalized
|
||||
|
||||
|
||||
def normalize_character_text_refs(value: Any) -> Any:
|
||||
if isinstance(value, list):
|
||||
return [normalize_character_text_refs(item) for item in value]
|
||||
if isinstance(value, dict):
|
||||
return {key: normalize_character_text_refs(item) for key, item in value.items()}
|
||||
if not isinstance(value, str):
|
||||
return value
|
||||
text = value
|
||||
replacements = []
|
||||
for canonical, config in CHARACTER_REGISTRY.items():
|
||||
for alias in config["aliases"]:
|
||||
if alias == canonical:
|
||||
continue
|
||||
if alias.lower() in {"young", "future", "sensei"}:
|
||||
continue
|
||||
replacements.append((alias, canonical))
|
||||
replacements.sort(key=lambda item: len(item[0]), reverse=True)
|
||||
for alias, canonical in replacements:
|
||||
pattern = r"(?<![A-Za-z0-9_-])" + re.escape(alias) + r"(?![A-Za-z0-9_-])"
|
||||
text = re.sub(pattern, canonical, text, flags=re.IGNORECASE)
|
||||
return text
|
||||
|
||||
|
||||
def detect_hidden_characters(text: str) -> list[str]:
|
||||
lowered = text.lower()
|
||||
found = []
|
||||
normalized_text = normalize_character_text_refs(text).lower()
|
||||
for character in HIDDEN_CHARACTERS:
|
||||
if character.lower() in lowered:
|
||||
if re.search(r"(?<![A-Za-z0-9_-])" + re.escape(character) + r"(?![A-Za-z0-9_-])", normalized_text):
|
||||
found.append(character)
|
||||
return found
|
||||
|
||||
@@ -887,7 +1005,7 @@ def migrate_hidden_entries_from_js() -> list[dict[str, Any]]:
|
||||
for index, (query, route) in enumerate(search_routes.items()):
|
||||
add(make_hidden_entry("search route", route, index, title=f"Route: {query}", category="search", pageLocation=route, triggerConditions=query, query=query))
|
||||
for index, item in enumerate(keyboard_secrets + long_keyboard_secrets):
|
||||
content = item.get("message") or item.get("route") or ("play tiny Aphy song" if item.get("song") else "")
|
||||
content = item.get("message") or item.get("route") or ("play tiny aphy song" if item.get("song") else "")
|
||||
add(make_hidden_entry("keyboard secret", content, index, title=f"Keyboard: {item.get('phrase')}", category="keyboard", pageLocation=item.get("route", ""), triggerConditions=item.get("phrase", ""), keyboard=item))
|
||||
return entries
|
||||
|
||||
@@ -896,9 +1014,10 @@ def normalize_hidden_entry(raw: dict[str, Any], existing: dict[str, Any] | None
|
||||
today = datetime.now().date().isoformat()
|
||||
entry = existing.copy() if existing else {}
|
||||
entry.update(raw)
|
||||
title = str(entry.get("title") or "").strip()
|
||||
content = str(entry.get("content") or "").replace("\r\n", "\n")
|
||||
title = str(normalize_character_text_refs(entry.get("title") or "")).strip()
|
||||
content = str(normalize_character_text_refs(entry.get("content") or "")).replace("\r\n", "\n")
|
||||
content_type = str(entry.get("type") or "quote").strip()
|
||||
content_type = str(normalize_character_text_refs(content_type))
|
||||
if content_type not in HIDDEN_CONTENT_TYPES:
|
||||
raise ValueError(f"Unsupported hidden content type: {content_type}")
|
||||
if not title:
|
||||
@@ -909,18 +1028,30 @@ def normalize_hidden_entry(raw: dict[str, Any], existing: dict[str, Any] | None
|
||||
entry["title"] = title
|
||||
entry["type"] = content_type
|
||||
entry["content"] = content
|
||||
entry["characters"] = [str(item).strip() for item in entry.get("characters", []) if str(item).strip()]
|
||||
detected_characters = detect_hidden_characters(" ".join([
|
||||
title,
|
||||
content,
|
||||
str(entry.get("triggerConditions") or ""),
|
||||
str(entry.get("notes") or ""),
|
||||
str(entry.get("category") or ""),
|
||||
]))
|
||||
entry["characters"] = normalize_character_list(entry.get("characters", []))
|
||||
for character in detected_characters:
|
||||
if character not in entry["characters"]:
|
||||
entry["characters"].append(character)
|
||||
if not entry["characters"]:
|
||||
entry["characters"] = ["z"]
|
||||
entry["emotionalTone"] = str(entry.get("emotionalTone") or "warm")
|
||||
entry["rarity"] = str(entry.get("rarity") or "common")
|
||||
entry["triggerConditions"] = str(entry.get("triggerConditions") or "")
|
||||
entry["triggerConditions"] = str(normalize_character_text_refs(entry.get("triggerConditions") or ""))
|
||||
entry["tags"] = normalise_tags(entry.get("tags", []))
|
||||
entry["category"] = str(entry.get("category") or content_type)
|
||||
entry["category"] = str(normalize_character_text_refs(entry.get("category") or content_type))
|
||||
entry["pageLocation"] = str(entry.get("pageLocation") or "")
|
||||
entry["familyLayer"] = str(entry.get("familyLayer") or "")
|
||||
entry["enabled"] = bool(entry.get("enabled", True))
|
||||
entry["createdDate"] = str(entry.get("createdDate") or today)
|
||||
entry["modifiedDate"] = today
|
||||
entry["notes"] = str(entry.get("notes") or "")
|
||||
entry["notes"] = str(normalize_character_text_refs(entry.get("notes") or ""))
|
||||
entry["audioSettings"] = str(entry.get("audioSettings") or "")
|
||||
entry["animationTrigger"] = str(entry.get("animationTrigger") or "")
|
||||
entry["cssClassHooks"] = str(entry.get("cssClassHooks") or "")
|
||||
@@ -944,7 +1075,10 @@ def normalize_hidden_entry(raw: dict[str, Any], existing: dict[str, Any] | None
|
||||
"symbolicLinks",
|
||||
"triggerLinks",
|
||||
]:
|
||||
entry[key] = [str(item).strip() for item in entry.get(key, []) if str(item).strip()]
|
||||
entry[key] = [str(normalize_character_text_refs(item)).strip() for item in entry.get(key, []) if str(item).strip()]
|
||||
for key in ["dialogue", "keyboard"]:
|
||||
if key in entry:
|
||||
entry[key] = normalize_character_text_refs(entry[key])
|
||||
return entry
|
||||
|
||||
|
||||
@@ -970,6 +1104,8 @@ def load_hidden_store() -> dict[str, Any]:
|
||||
"migratedFromJs": migrated,
|
||||
"types": HIDDEN_CONTENT_TYPES,
|
||||
"characters": HIDDEN_CHARACTERS,
|
||||
"characterRegistry": CHARACTER_REGISTRY,
|
||||
"validation": validate_hidden_integrity(normalized),
|
||||
"tones": HIDDEN_TONES,
|
||||
"rarities": HIDDEN_RARITIES,
|
||||
"layers": HIDDEN_LAYER_DEPTHS,
|
||||
@@ -978,6 +1114,36 @@ def load_hidden_store() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def validate_hidden_integrity(entries: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
ids = {entry["id"] for entry in entries}
|
||||
unknown_characters = []
|
||||
orphan_nodes = []
|
||||
stale_links = []
|
||||
for entry in entries:
|
||||
characters = entry.get("characters", [])
|
||||
if not characters:
|
||||
orphan_nodes.append(entry["id"])
|
||||
for character in characters:
|
||||
if character not in CHARACTER_REGISTRY:
|
||||
unknown_characters.append({"entry": entry["id"], "character": character})
|
||||
for key in ["chainReferences", "continuationLinks", "parentLinks", "childLinks", "echoes", "mirroredEntries", "thematicLinks", "symbolicLinks", "triggerLinks"]:
|
||||
for target in entry.get(key, []):
|
||||
if target and target not in ids and not str(target).startswith("/"):
|
||||
stale_links.append({"entry": entry["id"], "field": key, "target": target})
|
||||
return {
|
||||
"ok": not unknown_characters and not orphan_nodes and not stale_links,
|
||||
"unknownCharacters": unknown_characters[:50],
|
||||
"orphanNodes": orphan_nodes[:50],
|
||||
"staleLinks": stale_links[:50],
|
||||
"summary": {
|
||||
"unknownCharacterCount": len(unknown_characters),
|
||||
"orphanNodeCount": len(orphan_nodes),
|
||||
"staleLinkCount": len(stale_links),
|
||||
},
|
||||
"repairPolicy": "Unknown aliases are normalized through the registry. Empty character lists are repaired to z so every memory has a territory.",
|
||||
}
|
||||
|
||||
|
||||
def hidden_architecture_recommendations() -> dict[str, Any]:
|
||||
return {
|
||||
"storage": "Use assets/content/hidden-details.json as the friendly source of truth for narrative nodes, then regenerate the editable constants in assets/scripts/hidden-details.js.",
|
||||
@@ -1005,7 +1171,7 @@ def generated_hidden_content_block(entries: list[dict[str, Any]]) -> str:
|
||||
entry["content"] for entry in hidden_entries_by_type(entries, "rare event")
|
||||
if "night" in f"{entry.get('category', '')} {entry.get('triggerConditions', '')}".lower()
|
||||
]
|
||||
quote_like = hidden_contents(entries, "quote") + hidden_contents(entries, "Sensei Chi wisdom entry") + hidden_contents(entries, "Aphy system message") + hidden_contents(entries, "Lima note/message") + hidden_contents(entries, "Future Z message") + hidden_contents(entries, "Young Z memory fragment")
|
||||
quote_like = hidden_contents(entries, "quote") + hidden_contents(entries, "sensei chi wisdom entry") + hidden_contents(entries, "aphy system message") + hidden_contents(entries, "lima note/message") + hidden_contents(entries, "future z message") + hidden_contents(entries, "young z memory fragment")
|
||||
lore = {
|
||||
"quotes": quote_like,
|
||||
"conversations": [
|
||||
@@ -1046,7 +1212,7 @@ def generated_hidden_content_block(entries: list[dict[str, Any]]) -> str:
|
||||
secret["phrase"] = secret.get("phrase") or entry.get("triggerConditions") or entry.get("title", "")
|
||||
if entry.get("pageLocation"):
|
||||
secret["route"] = entry.get("pageLocation")
|
||||
elif entry.get("content") == "play tiny Aphy song":
|
||||
elif entry.get("content") == "play tiny aphy song":
|
||||
secret["song"] = True
|
||||
else:
|
||||
secret["message"] = entry.get("content", "")
|
||||
@@ -2534,7 +2700,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
<aside class="left">
|
||||
<h1>Memory Observatory</h1>
|
||||
<div class="subtle">A constellation map for the hidden soul of the website.</div>
|
||||
<div id="status" class="status">Aphy is dimming the room lights.</div>
|
||||
<div id="status" class="status">aphy is dimming the room lights.</div>
|
||||
<div class="modebar" id="modebar">
|
||||
<button data-mode="graph" class="active">Emotional graph</button>
|
||||
<button data-mode="timeline">Timeline</button>
|
||||
@@ -2556,19 +2722,23 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
<div class="guide-card">
|
||||
<h2>How to Read the Observatory</h2>
|
||||
<p class="subtle">This is not a database. It is a map of emotional relationships. Layers are depths, nodes are memories and hidden interactions, and threads show meaning, echoes, symbols, or continuation.</p>
|
||||
<p class="subtle"><strong>Aphy:</strong> zoomed out, I show islands. zoom in, I show memories. this prevents the sky from becoming noise.</p>
|
||||
<p class="subtle"><strong>Sensei Chi:</strong> depth is not importance. Depth is how quietly a thing asks to be approached.</p>
|
||||
<p class="subtle"><strong>aphy:</strong> zoomed out, I show islands. zoom in, I show memories. this prevents the sky from becoming noise.</p>
|
||||
<p class="subtle"><strong>sensei chi:</strong> depth is not importance. Depth is how quietly a thing asks to be approached.</p>
|
||||
</div>
|
||||
<div class="guide-card">
|
||||
<h2>Guided Exploration</h2>
|
||||
<div class="tour-list">
|
||||
<button data-tour="lima" type="button">show hidden Lima memories</button>
|
||||
<button data-tour="lima" type="button">show hidden lima memories</button>
|
||||
<button data-tour="rare" type="button">show the rarest memories</button>
|
||||
<button data-tour="future" type="button">show discoveries tied to Future Z</button>
|
||||
<button data-tour="future" type="button">show discoveries tied to future z</button>
|
||||
<button data-tour="unresolved" type="button">show unresolved threads</button>
|
||||
<button data-tour="connected" type="button">show emotionally connected nodes</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="guide-card">
|
||||
<h2>Integrity</h2>
|
||||
<div id="integrityStatus" class="subtle">checking character territories.</div>
|
||||
</div>
|
||||
<div class="layer-key" id="layerKey"></div>
|
||||
</aside>
|
||||
|
||||
@@ -2608,7 +2778,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
<label class="full">Name<input id="title" /></label>
|
||||
<label>Kind<select id="type"></select></label>
|
||||
<label>Depth<select id="familyLayer"></select></label>
|
||||
<label>Characters<input id="characters" placeholder="Lima, Aphy" /></label>
|
||||
<label>Characters<input id="characters" placeholder="lima, aphy" /></label>
|
||||
<label>Tone<select id="tone"></select></label>
|
||||
<label>Rarity<select id="rarity"></select></label>
|
||||
<label>Discovery<select id="discoveryDifficulty" placeholder="gentle, patient, hidden" /></label>
|
||||
@@ -2660,7 +2830,6 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
const fields = ["title","type","familyLayer","characters","tone","rarity","discoveryDifficulty","mysteryLevel","resonanceScore","pageLocation","triggerConditions","content","symbols","narrativeArcs","tags","emotionalRole","continuationLinks","echoes","thematicLinks","symbolicLinks","triggerLinks","parentLinks","childLinks","mirroredEntries","enabled","cssClassHooks","audioSettings","animationTrigger","notes"];
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const toneColors = { warm: "#d3a64d", funny: "#7fb089", nostalgic: "#d06b78", wise: "#75a9bd", strange: "#a58ac9", soft: "#e6bd8c", hopeful: "#9fcf9f", protective: "#d98f71", melancholy: "#8fa7c6" };
|
||||
const characterSymbols = { Lima: "L", Aphy: "A", "Sensei Chi": "S", "Young Z": "Y", "Future Z": "F", Z: "Z" };
|
||||
|
||||
function html(value) {
|
||||
return String(value || "").replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[char]));
|
||||
@@ -2668,6 +2837,25 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
function splitList(value) {
|
||||
return String(value || "").split(",").map((item) => item.trim()).filter(Boolean);
|
||||
}
|
||||
function normalizeCharacterName(value) {
|
||||
const raw = String(value || "").trim();
|
||||
if (!raw) return "";
|
||||
const simplified = slugify(raw).replace(/-/g, " ");
|
||||
const registry = state.meta.characterRegistry || {};
|
||||
for (const [id, config] of Object.entries(registry)) {
|
||||
const aliases = [id, ...(config.aliases || [])].map((item) => String(item).toLowerCase());
|
||||
if (aliases.includes(raw.toLowerCase()) || aliases.map((item) => slugify(item).replace(/-/g, " ")).includes(simplified)) return id;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
function normalizeCharacters(value) {
|
||||
const normalized = [];
|
||||
splitList(value).forEach((item) => {
|
||||
const character = normalizeCharacterName(item);
|
||||
if (character && !normalized.includes(character)) normalized.push(character);
|
||||
});
|
||||
return normalized.length ? normalized : ["z"];
|
||||
}
|
||||
function slugify(value) {
|
||||
return String(value || "memory").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "memory";
|
||||
}
|
||||
@@ -2761,9 +2949,9 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
function depth(entry) {
|
||||
const value = String(entry.familyLayer || "").match(/[0-5]/);
|
||||
if (value) return Number(value[0]);
|
||||
if ((entry.characters || []).includes("Future Z")) return 4;
|
||||
if ((entry.characters || []).includes("Sensei Chi")) return 3;
|
||||
if ((entry.characters || []).includes("Young Z") || (entry.characters || []).includes("Lima")) return 2;
|
||||
if ((entry.characters || []).includes("future z")) return 4;
|
||||
if ((entry.characters || []).includes("sensei chi")) return 3;
|
||||
if ((entry.characters || []).includes("young z") || (entry.characters || []).includes("lima")) return 2;
|
||||
return entry.rarity === "rare" || entry.rarity === "very rare" ? 5 : 1;
|
||||
}
|
||||
function currentEntry() {
|
||||
@@ -2777,7 +2965,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
title: $("title").value.trim() || "Untitled memory",
|
||||
type: $("type").value,
|
||||
familyLayer: $("familyLayer").value,
|
||||
characters: splitList($("characters").value),
|
||||
characters: normalizeCharacters($("characters").value),
|
||||
emotionalTone: $("tone").value,
|
||||
rarity: $("rarity").value,
|
||||
discoveryDifficulty: $("discoveryDifficulty").value,
|
||||
@@ -2896,7 +3084,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
|
||||
function clusterKey(entry) {
|
||||
if (state.mode === "timeline") return `${entry.modifiedDate || entry.createdDate || "undated"}`.slice(0, 7);
|
||||
if (state.mode === "character") return (entry.characters || [])[0] || "Unclaimed";
|
||||
if (state.mode === "character") return (entry.characters || [])[0] || "z";
|
||||
if (state.mode === "layer") return `Layer ${depth(entry)} - ${layerName(depth(entry))}`;
|
||||
if (state.mode === "flow") return entry.triggerConditions ? `Trigger: ${entry.triggerConditions.split(/[,:]/)[0]}` : `Layer ${depth(entry)} discovery`;
|
||||
return (entry.narrativeArcs || [])[0] || (entry.symbols || [])[0] || entry.emotionalTone || "Loose memories";
|
||||
@@ -2915,7 +3103,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
cluster.entries.push(entry);
|
||||
cluster.depthTotal += depth(entry);
|
||||
cluster.tones.set(entry.emotionalTone || "warm", (cluster.tones.get(entry.emotionalTone || "warm") || 0) + 1);
|
||||
(entry.characters || ["Unclaimed"]).forEach((name) => cluster.characters.set(name, (cluster.characters.get(name) || 0) + 1));
|
||||
(entry.characters || ["z"]).forEach((name) => cluster.characters.set(name, (cluster.characters.get(name) || 0) + 1));
|
||||
if (["rare", "very rare", "seasonal", "timed"].includes(entry.rarity)) cluster.rarity += 1;
|
||||
});
|
||||
return [...clusters.values()].map((cluster) => {
|
||||
@@ -3057,10 +3245,16 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
}
|
||||
}
|
||||
function characterBucket(entry) {
|
||||
const first = (entry.characters || [])[0] || "Other";
|
||||
const first = (entry.characters || [])[0] || "z";
|
||||
const index = Math.max(0, state.meta.characters.indexOf(first));
|
||||
return index % 6;
|
||||
}
|
||||
function characterSymbol(character) {
|
||||
return state.meta.characterRegistry?.[character]?.symbol || "*";
|
||||
}
|
||||
function characterColor(character) {
|
||||
return state.meta.characterRegistry?.[character]?.territoryColor || "";
|
||||
}
|
||||
function hash(value) {
|
||||
let h = 0;
|
||||
for (let i = 0; i < value.length; i += 1) h = (h * 31 + value.charCodeAt(i)) % 9999;
|
||||
@@ -3094,7 +3288,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
const occupied = [];
|
||||
const clusterNodes = clusters.map((cluster) => {
|
||||
const pos = state.clusterPositions.get(cluster.id);
|
||||
const color = toneColors[cluster.tone] || "#d3a64d";
|
||||
const color = state.mode === "character" ? (characterColor(cluster.label) || toneColors[cluster.tone] || "#d3a64d") : (toneColors[cluster.tone] || "#d3a64d");
|
||||
const size = Math.min(120, 48 + Math.sqrt(cluster.entries.length) * 18 + cluster.rarity * 3);
|
||||
const label = labelForCluster(cluster, pos, size, occupied);
|
||||
return `<g class="cluster-node" data-id="${html(cluster.id)}" tabindex="0" transform="translate(${pos.x} ${pos.y})">
|
||||
@@ -3108,7 +3302,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
if (!isWorldVisible(pos.x, pos.y, 220)) return "";
|
||||
const color = toneColors[entry.emotionalTone] || "#d3a64d";
|
||||
const size = 18 + Math.min(18, Number(entry.resonanceScore || 3) * 2.2) + (entry.rarity === "rare" || entry.rarity === "very rare" ? 8 : 0);
|
||||
const char = characterSymbols[(entry.characters || [])[0]] || "*";
|
||||
const char = characterSymbol((entry.characters || [])[0]);
|
||||
const label = labelForEntry(entry, pos, size, occupied);
|
||||
return `<g class="memory-node${entry.id === state.selectedId ? " selected" : ""}${entry.enabled === false ? " resting" : ""}" data-id="${html(entry.id)}" tabindex="0" transform="translate(${pos.x} ${pos.y})">
|
||||
<circle r="${size}" fill="${color}" opacity="${entry.enabled === false ? 0.42 : 0.92}"></circle>
|
||||
@@ -3137,7 +3331,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
const titles = {
|
||||
graph: ["Emotional Graph", "Relationship web: continuations, echoes, symbols, characters, triggers."],
|
||||
timeline: ["Timeline View", "Memories arranged by modified date so the constellation becomes a diary."],
|
||||
character: ["Character View", "Nodes gather around Lima, Aphy, Sensei Chi, Young Z, Future Z, and Z."],
|
||||
character: ["Character View", "Nodes gather around lima, aphy, sensei chi, young z, future z, and z."],
|
||||
layer: ["Layer Dive", "Depth zones show the emotional pressure from surface reality to the core."],
|
||||
flow: ["Discovery Flow", "A possible path from ordinary encounter to patient secret."],
|
||||
};
|
||||
@@ -3147,6 +3341,15 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
$("mapNote").textContent = `${scopeText} Zoom in or open an island to reveal detail. Drag memories to weave continuation threads.`;
|
||||
}
|
||||
|
||||
function renderValidation() {
|
||||
const validation = state.meta.validation || {};
|
||||
const summary = validation.summary || {};
|
||||
const ok = validation.ok && !summary.unknownCharacterCount && !summary.orphanNodeCount && !summary.staleLinkCount;
|
||||
$("integrityStatus").innerHTML = ok
|
||||
? "all memories belong to canonical character territories. no orphan nodes, unknown aliases, or stale relationship links found."
|
||||
: `needs attention: ${summary.unknownCharacterCount || 0} unknown characters, ${summary.orphanNodeCount || 0} orphan memories, ${summary.staleLinkCount || 0} stale links. ${html(validation.repairPolicy || "")}`;
|
||||
}
|
||||
|
||||
function isWorldVisible(x, y, pad = 0) {
|
||||
const p = worldToScreen(x, y);
|
||||
return p.x >= -pad && p.x <= SCREEN.width + pad && p.y >= -pad && p.y <= SCREEN.height + pad;
|
||||
@@ -3320,7 +3523,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
state.focusIds = null;
|
||||
state.zoom = 2;
|
||||
if (name === "lima") {
|
||||
$("characterFilter").value = "Lima";
|
||||
$("characterFilter").value = "lima";
|
||||
$("rarityFilter").value = "";
|
||||
$("toneFilter").value = "";
|
||||
$("search").value = "";
|
||||
@@ -3332,7 +3535,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
$("search").value = "";
|
||||
}
|
||||
if (name === "future") {
|
||||
$("characterFilter").value = "Future Z";
|
||||
$("characterFilter").value = "future z";
|
||||
$("rarityFilter").value = "";
|
||||
$("toneFilter").value = "";
|
||||
$("search").value = "";
|
||||
@@ -3444,8 +3647,10 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
try {
|
||||
const data = await api("/api/hidden", { method: "POST", body: JSON.stringify({ entries: state.entries }) });
|
||||
state.entries = data.entries;
|
||||
state.meta = data;
|
||||
localStorage.removeItem(draftKey);
|
||||
setStatus(data.message || "constellation stored safely.");
|
||||
renderValidation();
|
||||
render();
|
||||
} catch (err) {
|
||||
setStatus(err.message);
|
||||
@@ -3462,6 +3667,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
state.selectedId = draft.selectedId || "";
|
||||
}
|
||||
populateControls();
|
||||
renderValidation();
|
||||
setStatus(data.migratedFromJs ? "Existing hidden details became a memory constellation." : "observatory open.");
|
||||
selectNode(state.selectedId || state.entries[0]?.id);
|
||||
fitCameraTo({ minX: 250, minY: 220, maxX: WORLD.width - 250, maxY: WORLD.height - 220 }, false);
|
||||
|
||||
1011
backups/hidden-details/20260510-162430-hidden-details.js
Normal file
1011
backups/hidden-details/20260510-162430-hidden-details.js
Normal file
File diff suppressed because it is too large
Load Diff
6383
backups/hidden-details/20260510-162430-hidden-details.json
Normal file
6383
backups/hidden-details/20260510-162430-hidden-details.json
Normal file
File diff suppressed because it is too large
Load Diff
1011
backups/hidden-details/20260510-162614-hidden-details.js
Normal file
1011
backups/hidden-details/20260510-162614-hidden-details.js
Normal file
File diff suppressed because it is too large
Load Diff
9501
backups/hidden-details/20260510-162614-hidden-details.json
Normal file
9501
backups/hidden-details/20260510-162614-hidden-details.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user