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"},