From 127802748436875bab2a1a922f786f694c00efe1 Mon Sep 17 00:00:00 2001 From: Zaine Date: Mon, 11 May 2026 16:30:31 +0100 Subject: [PATCH] avatar support next to messages x2 --- assets/scripts/hidden-details.js | 93 ++++++++++++++++++++++++++++---- assets/styles/hidden-details.css | 47 ++++++++++++++++ authoring_server.py | 58 ++++++++++---------- 3 files changed, 160 insertions(+), 38 deletions(-) diff --git a/assets/scripts/hidden-details.js b/assets/scripts/hidden-details.js index 0e63312..6a578b8 100644 --- a/assets/scripts/hidden-details.js +++ b/assets/scripts/hidden-details.js @@ -27,6 +27,44 @@ const hour = now.getHours(); const path = window.location.pathname; const state = readState(); + const CHARACTER_AVATARS = { + "lima": { + avatar: "/assets/avatars/lima.jpg", + fallback: "/assets/avatars/z.jpeg", + aliases: ["lima"], + glow: "#f2c58b" + }, + "aphy": { + avatar: "/assets/avatars/aphy.jpeg", + fallback: "/assets/avatars/z.jpeg", + aliases: ["aphy"], + glow: "#9dd3a6" + }, + "sensei chi": { + avatar: "/assets/avatars/sensei-chi.jpeg", + fallback: "/assets/avatars/z.jpeg", + aliases: ["sensei chi", "sensei"], + glow: "#9ccddd" + }, + "young z": { + avatar: "/assets/avatars/young-z.jpeg", + fallback: "/assets/avatars/z.jpeg", + aliases: ["young z", "young"], + glow: "#f0b85c" + }, + "future z": { + avatar: "/assets/avatars/future-z.jpeg", + fallback: "/assets/avatars/z.jpeg", + aliases: ["future z", "future"], + glow: "#c2a4ee" + }, + "z": { + avatar: "/assets/avatars/z.jpeg", + fallback: "/assets/avatars/z.jpeg", + aliases: ["z", "zaine"], + glow: "#ead68e" + } + }; // ----------------------------- // EDITABLE CONTENT @@ -474,6 +512,41 @@ // UI HELPERS // ----------------------------- + function characterForMessage(message) { + const text = String(message || "").toLowerCase(); + return Object.entries(CHARACTER_AVATARS).find(([, config]) => { + return (config.aliases || []).some((alias) => { + const escaped = alias.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return new RegExp(`(^|[^a-z0-9-])${escaped}([^a-z0-9-]|$)`).test(text); + }); + })?.[0] || ""; + } + + function avatarMarkup(character) { + const config = CHARACTER_AVATARS[character]; + if (!config) return ""; + return ``; + } + + function hiddenMessageMarkup(message) { + const character = characterForMessage(message); + return character + ? `${avatarMarkup(character)}${escapeHtml(message)}` + : `${escapeHtml(message)}`; + } + + function setHiddenMessage(el, message) { + const character = characterForMessage(message); + el.classList.toggle("has-hidden-avatar", Boolean(character)); + if (character) { + el.dataset.hiddenCharacter = character; + el.innerHTML = hiddenMessageMarkup(message); + } else { + delete el.dataset.hiddenCharacter; + el.textContent = message; + } + } + function toast(message, duration) { let el = document.querySelector(".hidden-toast"); if (!el) { @@ -482,7 +555,7 @@ el.setAttribute("role", "status"); document.body.appendChild(el); } - el.textContent = message; + setHiddenMessage(el, message); el.classList.add("is-visible"); window.clearTimeout(el._timer); el._timer = window.setTimeout(() => el.classList.remove("is-visible"), duration || 5600); @@ -542,17 +615,17 @@ 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; + setHiddenMessage(note, 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}`); + setHiddenMessage(target, 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."; + setHiddenMessage(extra, 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); } @@ -665,7 +738,8 @@ function showDriftNote(text, art) { const panel = document.createElement("aside"); panel.className = "hidden-drift-note"; - panel.innerHTML = `
${escapeHtml(text)}
${escapeHtml(art)}
`; + const character = characterForMessage(text); + panel.innerHTML = `
${hiddenMessageMarkup(text)}
${escapeHtml(art)}
`; panel.querySelector("button").addEventListener("click", () => panel.remove()); document.body.appendChild(panel); } @@ -813,7 +887,7 @@ if (comments && !comments.querySelector(".hidden-guestbook-line")) { const line = document.createElement("p"); line.className = "hidden-guestbook-line"; - line.textContent = pick(lore.fakeUsers, "comment-lore"); + setHiddenMessage(line, pick(lore.fakeUsers, "comment-lore")); comments.appendChild(line); } } @@ -886,7 +960,8 @@ const visits = readState().layerVisits || {}; target.innerHTML = familyLayers.map((line, layer) => { const count = visits[layer] || 0; - return `
  • ${escapeHtml(line)}
    local encounters: ${count}
  • `; + const character = characterForMessage(line); + return `
  • ${hiddenMessageMarkup(line)}
    local encounters: ${count}
  • `; }).join(""); } @@ -910,7 +985,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."; + setHiddenMessage(target, seconds === 30 ? "lima's note becomes easier to read." : seconds === 90 ? "sensei chi nods once." : "future z says this quiet was not wasted."); } }, 1000); } @@ -933,7 +1008,7 @@ 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."}`; + setHiddenMessage(line, `> ${value}\n${commands[value] || "aphy: unknown command. sensei chi: not all unknowns are errors."}`); log.appendChild(line); input.value = ""; }); diff --git a/assets/styles/hidden-details.css b/assets/styles/hidden-details.css index 668ecd9..edc5f60 100644 --- a/assets/styles/hidden-details.css +++ b/assets/styles/hidden-details.css @@ -50,6 +50,53 @@ transform: translateY(0); } +.has-hidden-avatar { + display: inline-flex; + align-items: center; + gap: 0.55rem; +} + +.hidden-toast.has-hidden-avatar, +.hidden-drift-message.has-hidden-avatar { + display: flex; +} + +.hidden-footer-note.has-hidden-avatar, +.hidden-greeting.has-hidden-avatar, +.hidden-guestbook-line.has-hidden-avatar { + display: flex; + justify-content: center; +} + +.hidden-message-avatar { + width: 2.1rem; + height: 2.1rem; + flex: 0 0 2.1rem; + border-radius: 50%; + object-fit: cover; + border: 1px solid color-mix(in oklab, var(--hidden-avatar-glow, var(--accent)) 62%, var(--border)); + box-shadow: 0 0 18px color-mix(in oklab, var(--hidden-avatar-glow, var(--accent)) 34%, transparent); + background: color-mix(in oklab, var(--surface) 82%, transparent); +} + +.hidden-message-text { + min-width: 0; +} + +.hidden-layer-message strong { + display: inline-flex; + align-items: center; + gap: 0.55rem; +} + +.hidden-terminal-log .has-hidden-avatar { + align-items: flex-start; +} + +.hidden-terminal-log .hidden-message-avatar { + margin-top: 0.1rem; +} + .hidden-corner-object { position: fixed; left: 0.65rem; diff --git a/authoring_server.py b/authoring_server.py index f709eaa..b9d5dff 100755 --- a/authoring_server.py +++ b/authoring_server.py @@ -150,12 +150,12 @@ CHARACTER_REGISTRY = { "aliases": ["Young Z", "young z", "young-z", "young_z", "young"], "territoryColor": "#d8a95a", "glow": "#f0b85c", - "avatar": "/assets/avatars/young-z.jpeg", - "fallbackAvatar": "/assets/avatars/z.jpeg", + "avatar": "https://zainezq.com/assets/avatars/young-z.jpeg", + "fallbackAvatar": "https://zainezq.com/assets/avatars/z.jpeg", "expressions": { - "default": "/assets/avatars/young-z.jpeg", - "nostalgic": "/assets/avatars/young-z.jpeg", - "playful": "/assets/avatars/young-z.jpeg", + "default": "https://zainezq.com/assets/avatars/young-z.jpeg", + "nostalgic": "https://zainezq.com/assets/avatars/young-z.jpeg", + "playful": "https://zainezq.com/assets/avatars/young-z.jpeg", }, "sizes": {"tiny": 18, "medium": 42, "close": 56, "focus": 82}, "observatory": {"territory": "nostalgic/playful memories", "anchor": {"x": 0.23, "y": 0.7}, "shimmer": "soft"}, @@ -172,9 +172,9 @@ CHARACTER_REGISTRY = { "aliases": ["Z", "z", "zaine"], "territoryColor": "#d6c38a", "glow": "#ead68e", - "avatar": "/assets/avatars/z.jpeg", - "fallbackAvatar": "/assets/avatars/z.jpeg", - "expressions": {"default": "/assets/avatars/z.jpeg", "calm": "/assets/avatars/z.jpeg"}, + "avatar": "https://zainezq.com/assets/avatars/z.jpeg", + "fallbackAvatar": "https://zainezq.com/assets/avatars/z.jpeg", + "expressions": {"default": "https://zainezq.com/assets/avatars/z.jpeg", "calm": "https://zainezq.com/assets/avatars/z.jpeg"}, "sizes": {"tiny": 18, "medium": 42, "close": 56, "focus": 82}, "observatory": {"territory": "self/archive memories", "anchor": {"x": 0.5, "y": 0.52}, "shimmer": "steady"}, "presenceTones": ["warm", "soft", "hopeful"], @@ -190,12 +190,12 @@ CHARACTER_REGISTRY = { "aliases": ["Aphy", "aphy", "aphy_bot", "aphy bot"], "territoryColor": "#7fb089", "glow": "#9dd3a6", - "avatar": "/assets/avatars/aphy.jpeg", - "fallbackAvatar": "/assets/avatars/z.jpeg", + "avatar": "https://zainezq.com/assets/avatars/aphy.jpeg", + "fallbackAvatar": "https://zainezq.com/assets/avatars/z.jpeg", "expressions": { - "default": "/assets/avatars/aphy.jpeg", - "system": "/assets/avatars/aphy.jpeg", - "amused": "/assets/avatars/aphy.jpeg", + "default": "https://zainezq.com/assets/avatars/aphy.jpeg", + "system": "https://zainezq.com/assets/avatars/aphy.jpeg", + "amused": "https://zainezq.com/assets/avatars/aphy.jpeg", }, "sizes": {"tiny": 18, "medium": 42, "close": 56, "focus": 82}, "observatory": {"territory": "system/symbolic areas", "anchor": {"x": 0.77, "y": 0.35}, "shimmer": "diagnostic"}, @@ -212,13 +212,13 @@ CHARACTER_REGISTRY = { "aliases": ["Lima", "lima"], "territoryColor": "#d06b78", "glow": "#f2c58b", - "avatar": "/assets/avatars/lima.jpg", - "fallbackAvatar": "/assets/avatars/z.jpeg", + "avatar": "https://zainezq.com/assets/avatars/lima.jpg", + "fallbackAvatar": "https://zainezq.com/assets/avatars/z.jpeg", "expressions": { - "default": "/assets/avatars/lima.jpg", - "calm": "/assets/avatars/lima.jpg", - "warm": "/assets/avatars/lima.jpg", - "protective": "/assets/avatars/lima.jpg", + "default": "https://zainezq.com/assets/avatars/lima.jpg", + "calm": "https://zainezq.com/assets/avatars/lima.jpg", + "warm": "https://zainezq.com/assets/avatars/lima.jpg", + "protective": "https://zainezq.com/assets/avatars/lima.jpg", }, "sizes": {"tiny": 18, "medium": 42, "close": 56, "focus": 82}, "observatory": {"territory": "warm/protective arcs", "anchor": {"x": 0.29, "y": 0.36}, "shimmer": "warm"}, @@ -235,12 +235,12 @@ CHARACTER_REGISTRY = { "aliases": ["Sensei Chi", "sensei chi", "sensei-chi", "sensei_chi", "sensei"], "territoryColor": "#75a9bd", "glow": "#9ccddd", - "avatar": "/assets/avatars/sensei-chi.jpeg", - "fallbackAvatar": "/assets/avatars/z.jpeg", + "avatar": "https://zainezq.com/assets/avatars/sensei-chi.jpeg", + "fallbackAvatar": "https://zainezq.com/assets/avatars/z.jpeg", "expressions": { - "default": "/assets/avatars/sensei-chi.jpeg", - "reflective": "/assets/avatars/sensei-chi.jpeg", - "wise": "/assets/avatars/sensei-chi.jpeg", + "default": "https://zainezq.com/assets/avatars/sensei-chi.jpeg", + "reflective": "https://zainezq.com/assets/avatars/sensei-chi.jpeg", + "wise": "https://zainezq.com/assets/avatars/sensei-chi.jpeg", }, "sizes": {"tiny": 18, "medium": 42, "close": 56, "focus": 82}, "observatory": {"territory": "reflective areas", "anchor": {"x": 0.68, "y": 0.68}, "shimmer": "quiet"}, @@ -257,12 +257,12 @@ CHARACTER_REGISTRY = { "aliases": ["Future Z", "future z", "future-z", "future_z", "future"], "territoryColor": "#a58ac9", "glow": "#c2a4ee", - "avatar": "/assets/avatars/future-z.jpeg", - "fallbackAvatar": "/assets/avatars/z.jpeg", + "avatar": "https://zainezq.com/assets/avatars/future-z.jpeg", + "fallbackAvatar": "https://zainezq.com/assets/avatars/z.jpeg", "expressions": { - "default": "/assets/avatars/future-z.jpeg", - "temporal": "/assets/avatars/future-z.jpeg", - "reassuring": "/assets/avatars/future-z.jpeg", + "default": "https://zainezq.com/assets/avatars/future-z.jpeg", + "temporal": "https://zainezq.com/assets/avatars/future-z.jpeg", + "reassuring": "https://zainezq.com/assets/avatars/future-z.jpeg", }, "sizes": {"tiny": 18, "medium": 42, "close": 56, "focus": 82}, "observatory": {"territory": "temporal regions", "anchor": {"x": 0.73, "y": 0.18}, "shimmer": "temporal"},