avatars plus other changes
All checks were successful
Build Org Website / build (push) Successful in 51s
All checks were successful
Build Org Website / build (push) Successful in 51s
This commit is contained in:
150
assets/scripts/avatar-registry.js
Normal file
150
assets/scripts/avatar-registry.js
Normal file
@@ -0,0 +1,150 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const fallback = "z";
|
||||
const sharedSizes = {
|
||||
marker: 18,
|
||||
tiny: 22,
|
||||
small: 32,
|
||||
medium: 44,
|
||||
large: 64,
|
||||
focus: 88
|
||||
};
|
||||
|
||||
const characters = {
|
||||
"young z": {
|
||||
id: "young z",
|
||||
displayLabel: "young z",
|
||||
avatar: "/assets/avatars/young-z.jpeg",
|
||||
glow: "#d8a95a",
|
||||
accent: "#f4bf5f",
|
||||
fallback,
|
||||
expressions: {
|
||||
calm: "/assets/avatars/young-z.jpeg",
|
||||
happy: "/assets/avatars/young-z.jpeg",
|
||||
surprised: "/assets/avatars/young-z.jpeg"
|
||||
},
|
||||
sizes: sharedSizes,
|
||||
observatory: {
|
||||
territory: "nostalgic-playful",
|
||||
presenceKeywords: ["childhood", "play", "crayon", "sun", "blanket", "game", "desk", "drawing", "nostalgia"],
|
||||
atmosphere: "warm childhood light",
|
||||
focusScale: 1.08
|
||||
}
|
||||
},
|
||||
z: {
|
||||
id: "z",
|
||||
displayLabel: "z",
|
||||
avatar: "/assets/avatars/z.jpeg",
|
||||
glow: "#d6c38a",
|
||||
accent: "#d8c891",
|
||||
fallback,
|
||||
expressions: {
|
||||
calm: "/assets/avatars/z.jpeg",
|
||||
receptive: "/assets/avatars/z.jpeg"
|
||||
},
|
||||
sizes: sharedSizes,
|
||||
observatory: {
|
||||
territory: "archive-return",
|
||||
presenceKeywords: ["website", "archive", "observer", "traveler", "making", "return", "home"],
|
||||
atmosphere: "steady archive lantern",
|
||||
focusScale: 1
|
||||
}
|
||||
},
|
||||
aphy: {
|
||||
id: "aphy",
|
||||
displayLabel: "aphy",
|
||||
avatar: "/assets/avatars/aphy.jpeg",
|
||||
glow: "#7fb089",
|
||||
accent: "#83d4df",
|
||||
fallback,
|
||||
expressions: {
|
||||
calm: "/assets/avatars/aphy.jpeg",
|
||||
curious: "/assets/avatars/aphy.jpeg",
|
||||
thinking: "/assets/avatars/aphy.jpeg"
|
||||
},
|
||||
sizes: sharedSizes,
|
||||
observatory: {
|
||||
territory: "system-symbolic",
|
||||
presenceKeywords: ["system", "terminal", "logic", "error", "debug", "console", "css", "search", "backup", "symbol"],
|
||||
atmosphere: "soft diagnostic glow",
|
||||
focusScale: 1.04
|
||||
}
|
||||
},
|
||||
lima: {
|
||||
id: "lima",
|
||||
displayLabel: "lima",
|
||||
avatar: "/assets/avatars/lima.jpg",
|
||||
glow: "#d06b78",
|
||||
accent: "#e7a176",
|
||||
fallback,
|
||||
expressions: {
|
||||
calm: "/assets/avatars/lima.jpg",
|
||||
happy: "/assets/avatars/lima.jpg",
|
||||
protective: "/assets/avatars/lima.jpg"
|
||||
},
|
||||
sizes: sharedSizes,
|
||||
observatory: {
|
||||
territory: "warm-protective",
|
||||
presenceKeywords: ["warm", "home", "safe", "protect", "kitchen", "ring", "love", "rest", "care", "light"],
|
||||
atmosphere: "protective kitchen warmth",
|
||||
focusScale: 1.12
|
||||
}
|
||||
},
|
||||
"sensei chi": {
|
||||
id: "sensei chi",
|
||||
displayLabel: "sensei chi",
|
||||
avatar: "/assets/avatars/sensei-chi.jpeg",
|
||||
glow: "#75a9bd",
|
||||
accent: "#9ec4be",
|
||||
fallback,
|
||||
expressions: {
|
||||
calm: "/assets/avatars/sensei-chi.jpeg",
|
||||
reflective: "/assets/avatars/sensei-chi.jpeg"
|
||||
},
|
||||
sizes: sharedSizes,
|
||||
observatory: {
|
||||
territory: "reflective-patient",
|
||||
presenceKeywords: ["quiet", "reflect", "patience", "tea", "garden", "lesson", "wisdom", "still"],
|
||||
atmosphere: "slow reflective tea light",
|
||||
focusScale: 1.06
|
||||
}
|
||||
},
|
||||
"future z": {
|
||||
id: "future z",
|
||||
displayLabel: "future z",
|
||||
avatar: "/assets/avatars/future-z.jpeg",
|
||||
glow: "#a58ac9",
|
||||
accent: "#b7a6cf",
|
||||
fallback,
|
||||
expressions: {
|
||||
calm: "/assets/avatars/future-z.jpeg",
|
||||
nostalgic: "/assets/avatars/future-z.jpeg",
|
||||
warning: "/assets/avatars/future-z.jpeg"
|
||||
},
|
||||
sizes: sharedSizes,
|
||||
observatory: {
|
||||
territory: "temporal",
|
||||
presenceKeywords: ["future", "time", "age 40", "later", "return", "warning", "ordinary", "clock"],
|
||||
atmosphere: "distant temporal shimmer",
|
||||
focusScale: 1.1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function get(character) {
|
||||
return characters[character] || characters[fallback];
|
||||
}
|
||||
|
||||
function source(character, expression) {
|
||||
const config = get(character);
|
||||
return (config.expressions && expression && config.expressions[expression]) || config.avatar || characters[fallback].avatar;
|
||||
}
|
||||
|
||||
window.MemoryAvatarRegistry = {
|
||||
fallback,
|
||||
characters,
|
||||
get,
|
||||
source
|
||||
};
|
||||
}());
|
||||
@@ -103,6 +103,17 @@
|
||||
}
|
||||
};
|
||||
|
||||
const AVATAR_REGISTRY = window.MemoryAvatarRegistry || {
|
||||
fallback: "z",
|
||||
characters: CHARACTER_REGISTRY,
|
||||
get(character) {
|
||||
return this.characters[character] || this.characters[this.fallback] || {};
|
||||
},
|
||||
source(character) {
|
||||
return this.get(character).avatar || "";
|
||||
}
|
||||
};
|
||||
|
||||
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.",
|
||||
@@ -642,9 +653,15 @@
|
||||
return `<svg width="36" height="36" viewBox="0 0 36 36" aria-hidden="true" focusable="false"><rect x="2" y="2" width="32" height="32" rx="10" fill="${secondary}" opacity=".34"></rect><circle cx="18" cy="18" r="15" fill="${accent}" opacity=".18"></circle>${motif}<path d="M10 29c1.6-5.1 5-7.4 8.2-7.4S25 23.9 27 29" fill="${clothes}" opacity=".94"></path><path d="M9.5 16.4c.2-6.4 4.1-10.3 8.8-10.3s8.2 3.8 8.4 10.3c-2-2-4.4-3.1-7.2-3.1-3.7 0-6.7 1.1-10 3.1z" fill="${hair}"></path><path d="M10.6 17.8c0-5.3 3.4-8.7 7.7-8.7s7.7 3.4 7.7 8.7c0 5.9-3.3 9.7-7.7 9.7s-7.7-3.8-7.7-9.7z" fill="${skin}"></path><path d="M10.7 16.2c2.4-4.2 6.5-5.3 12.1-4.4 1.1.8 2.2 2.2 3 4.1-3.6-.7-6.5-2.2-8.8-4.1-1.3 2-3.5 3.6-6.3 4.4z" fill="${hair}"></path>${eye}<circle cx="12.4" cy="20" r="1.25" fill="#ef8e8e" opacity=".34"></circle><circle cx="24.2" cy="20" r="1.25" fill="#ef8e8e" opacity=".34"></circle><path d="${mouth}" stroke="#3f2d29" stroke-width="1.45" stroke-linecap="round" fill="none"></path><text x="28.8" y="31.4" text-anchor="middle" font-size="7" fill="#fff7e8" font-family="monospace">${symbol}</text></svg>`;
|
||||
}
|
||||
|
||||
function avatarHtml(character, text) {
|
||||
function avatarHtml(character, text, size = "small", extraClass = "") {
|
||||
const config = CHARACTER_REGISTRY[character] || CHARACTER_REGISTRY.z;
|
||||
return `<span class="hidden-avatar" style="--hidden-avatar-color:${config.territoryColor || "#d3a64d"}" title="${escapeHtml(config.displayLabel || character)}">${avatarSvg(character, avatarExpression(character, text))}</span>`;
|
||||
const portrait = AVATAR_REGISTRY.get(character);
|
||||
const expression = avatarExpression(character, text);
|
||||
const src = AVATAR_REGISTRY.source(character, expression);
|
||||
const color = portrait.glow || portrait.accent || config.territoryColor || "#d3a64d";
|
||||
const label = config.displayLabel || portrait.displayLabel || character;
|
||||
const fallback = escapeHtml(config.symbol || "?");
|
||||
return `<span class="hidden-avatar hidden-avatar--${escapeHtml(size)} ${escapeHtml(extraClass)}" style="--hidden-avatar-color:${escapeHtml(color)}" title="${escapeHtml(label)}" data-character="${escapeHtml(character)}" data-expression="${escapeHtml(expression)}"><img src="${escapeHtml(src)}" alt="" loading="lazy" decoding="async" onerror="this.hidden=true;this.nextElementSibling.hidden=false"><span class="hidden-avatar__fallback" hidden>${fallback}</span></span>`;
|
||||
}
|
||||
|
||||
function toast(message, duration, character) {
|
||||
|
||||
@@ -9,6 +9,53 @@
|
||||
"A tiny bell rings in a room you have not built yet.",
|
||||
"The page remembers that you came here."
|
||||
];
|
||||
const avatarRegistry = window.MemoryAvatarRegistry || { fallback: "z", characters: {} };
|
||||
|
||||
function avatarConfig(character) {
|
||||
return avatarRegistry.characters?.[character] || avatarRegistry.characters?.[avatarRegistry.fallback || "z"] || {};
|
||||
}
|
||||
|
||||
function avatarSource(character, expression = "calm") {
|
||||
const config = avatarConfig(character);
|
||||
return config.expressions?.[expression] || config.avatar || avatarConfig("z").avatar || "";
|
||||
}
|
||||
|
||||
function avatarExpression(entry, character) {
|
||||
const text = [entry?.title, entry?.content, entry?.triggerConditions, entry?.emotionalRole, entry?.mysteryLevel, ...(entry?.symbols || []), ...(entry?.tags || []), ...(entry?.narrativeArcs || [])].join(" ").toLowerCase();
|
||||
if (character === "aphy" && /error|terminal|system|diagnostic|debug|console|search|symbol/.test(text)) return "thinking";
|
||||
if (character === "lima" && /safe|home|warm|protect|care|eat|rest|light/.test(text)) return "protective";
|
||||
if (character === "young z" && /draw|crayon|game|play|sun|blanket|child/.test(text)) return "surprised";
|
||||
if (character === "future z" && /time|future|return|ordinary|warning|later/.test(text)) return "nostalgic";
|
||||
if (character === "sensei chi" && /quiet|tea|patience|lesson|garden|wisdom/.test(text)) return "reflective";
|
||||
return "calm";
|
||||
}
|
||||
|
||||
function primaryCharacter(entry) {
|
||||
return characterPresence(entry)[0]?.character || (entry?.characters || [])[0] || "z";
|
||||
}
|
||||
|
||||
function characterPresence(entry) {
|
||||
const text = [entry?.title, entry?.content, entry?.triggerConditions, entry?.pageLocation, entry?.emotionalRole, entry?.mysteryLevel, ...(entry?.tags || []), ...(entry?.symbols || []), ...(entry?.narrativeArcs || [])].join(" ").toLowerCase();
|
||||
const scores = Object.keys(avatarRegistry.characters || {}).map((character) => {
|
||||
const config = avatarConfig(character);
|
||||
let score = (entry?.characters || []).includes(character) ? 10 : 0;
|
||||
(config.observatory?.presenceKeywords || []).forEach((keyword) => {
|
||||
if (text.includes(String(keyword).toLowerCase())) score += 3;
|
||||
});
|
||||
if (character === "future z" && entryDepth(entry || {}) === 4) score += 3;
|
||||
return { character, score };
|
||||
}).filter((item) => item.score > 0).sort((a, b) => b.score - a.score);
|
||||
return scores.length ? scores : [{ character: "z", score: 1 }];
|
||||
}
|
||||
|
||||
function avatarHtml(character, entry, size = "small", extraClass = "") {
|
||||
const config = avatarConfig(character);
|
||||
const expression = avatarExpression(entry, character);
|
||||
const src = avatarSource(character, expression);
|
||||
const label = config.displayLabel || character;
|
||||
const color = config.glow || config.accent || "#d3a64d";
|
||||
return `<span class="observatory-avatar observatory-avatar--${escapeAttr(size)} ${escapeAttr(extraClass)}" style="--avatar-glow:${escapeAttr(color)}" title="${escapeAttr(label)}" data-character="${escapeAttr(character)}"><img src="${escapeAttr(src)}" alt="" loading="lazy" decoding="async" onerror="this.hidden=true;this.nextElementSibling.hidden=false"><span class="observatory-avatar__fallback" hidden>${escapeHtml((label || "?").slice(0, 1))}</span></span>`;
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const root = $(".play-root[data-play-page]");
|
||||
@@ -945,8 +992,14 @@
|
||||
const angle = (index / entries.length) * Math.PI * 2 - Math.PI / 2;
|
||||
const radius = 34 + entryDepth(entry) * 8;
|
||||
const active = selected.some((item) => item.id === entry.id);
|
||||
const character = primaryCharacter(entry);
|
||||
const presence = characterPresence(entry)[0]?.score || 1;
|
||||
const config = avatarConfig(character);
|
||||
const atmosphere = config.observatory?.atmosphere || "observatory light";
|
||||
const focus = active ? " focus" : presence >= 10 ? " close" : presence >= 6 ? " medium" : " distant";
|
||||
return `<button type="button" class="observatory-star${active ? " is-active" : ""}" data-id="${escapeAttr(entry.id)}" style="--x:${50 + Math.cos(angle) * radius}%;--y:${50 + Math.sin(angle) * radius * 0.74}%;--size:${18 + Number(entry.resonanceScore || 3) * 2}px">
|
||||
<span>${escapeHtml((entry.characters || ["z"])[0].slice(0, 1))}</span>
|
||||
${avatarHtml(character, entry, "marker", `observatory-star-avatar${focus}`)}
|
||||
<span class="observatory-star-label">${escapeHtml(entry.title || atmosphere)}</span>
|
||||
</button>`;
|
||||
}).join("");
|
||||
$$("[data-id]", sky).forEach((button) => {
|
||||
@@ -961,6 +1014,7 @@
|
||||
selected.push(entry);
|
||||
title.textContent = entry.title;
|
||||
reading.textContent = characterLine(entry);
|
||||
root.style.setProperty("--active-avatar-glow", avatarConfig(primaryCharacter(entry)).glow || "#d6b36a");
|
||||
if (selected.length === 2) saveThread(selected[0], selected[1]);
|
||||
render();
|
||||
}
|
||||
@@ -968,7 +1022,11 @@
|
||||
function renderLog() {
|
||||
const threads = state.restoredThreads || [];
|
||||
log.innerHTML = threads.length
|
||||
? threads.slice(-6).reverse().map((thread) => `<p><strong>${escapeHtml(thread.relation.replace("Links", ""))}</strong>: ${escapeHtml(thread.title)}</p>`).join("")
|
||||
? threads.slice(-6).reverse().map((thread) => {
|
||||
const entry = entries.find((item) => item.id === thread.source || item.id === thread.target);
|
||||
const character = primaryCharacter(entry || { characters: ["z"] });
|
||||
return `<p class="observatory-thread-row">${avatarHtml(character, entry, "tiny")}<span><strong>${escapeHtml(thread.relation.replace("Links", ""))}</strong>: ${escapeHtml(thread.title)}</span></p>`;
|
||||
}).join("")
|
||||
: "<p>No thread has been restored here yet.</p>";
|
||||
}
|
||||
|
||||
@@ -1024,8 +1082,9 @@
|
||||
: patient ? "sensei chi notices the quiet you brought from another room." : "aphy has no elapsed-time anomaly to report yet.";
|
||||
list.innerHTML = entries.map((entry, index) => {
|
||||
const seen = visits[entry.id] || {};
|
||||
const character = primaryCharacter(entry);
|
||||
return `<button type="button" class="return-memory" data-id="${escapeAttr(entry.id)}">
|
||||
<strong>${escapeHtml(entry.title)}</strong>
|
||||
<span class="return-memory-head">${avatarHtml(character, entry, "small")}<strong>${escapeHtml(entry.title)}</strong></span>
|
||||
<span>${seen.count ? `observed ${seen.count} time${seen.count === 1 ? "" : "s"}` : "not observed here yet"}</span>
|
||||
</button>`;
|
||||
}).join("");
|
||||
@@ -1079,9 +1138,12 @@
|
||||
const symbols = [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 16);
|
||||
|
||||
function render() {
|
||||
cloud.innerHTML = symbols.map(([symbol, count]) => `<button type="button" class="symbol-chip${selected.includes(symbol) ? " is-active" : ""}" data-symbol="${escapeAttr(symbol)}">
|
||||
<strong>${escapeHtml(symbol)}</strong><span>${count}</span>
|
||||
</button>`).join("");
|
||||
cloud.innerHTML = symbols.map(([symbol, count]) => {
|
||||
const character = primaryCharacter({ title: symbol, content: symbol, symbols: [symbol] });
|
||||
return `<button type="button" class="symbol-chip${selected.includes(symbol) ? " is-active" : ""}" data-symbol="${escapeAttr(symbol)}">
|
||||
<span class="symbol-chip-head">${avatarHtml(character, { title: symbol, symbols: [symbol] }, "tiny")}<strong>${escapeHtml(symbol)}</strong></span><span>${count}</span>
|
||||
</button>`;
|
||||
}).join("");
|
||||
$$("[data-symbol]", cloud).forEach((button) => button.addEventListener("click", () => toggleSymbol(button.dataset.symbol)));
|
||||
const known = state.discoveredSymbols || {};
|
||||
routes.innerHTML = Object.keys(known).length
|
||||
|
||||
@@ -49,8 +49,9 @@
|
||||
}
|
||||
|
||||
.hidden-avatar {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
--hidden-avatar-size: 2rem;
|
||||
width: var(--hidden-avatar-size);
|
||||
height: var(--hidden-avatar-size);
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
flex: 0 0 auto;
|
||||
@@ -60,13 +61,40 @@
|
||||
0 0 18px color-mix(in oklab, var(--hidden-avatar-color, var(--accent)) 18%, transparent);
|
||||
overflow: hidden;
|
||||
animation: hidden-avatar-breathe 6s ease-in-out infinite;
|
||||
transition: box-shadow 180ms ease, filter 180ms ease, transform 180ms ease;
|
||||
}
|
||||
|
||||
.hidden-avatar svg {
|
||||
.hidden-avatar--tiny { --hidden-avatar-size: 1.35rem; }
|
||||
.hidden-avatar--small { --hidden-avatar-size: 2rem; }
|
||||
.hidden-avatar--medium { --hidden-avatar-size: 2.75rem; }
|
||||
.hidden-avatar--large { --hidden-avatar-size: 4rem; }
|
||||
|
||||
.hidden-avatar img,
|
||||
.hidden-avatar__fallback {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
shape-rendering: geometricPrecision;
|
||||
}
|
||||
|
||||
.hidden-avatar img {
|
||||
object-fit: cover;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.hidden-avatar__fallback {
|
||||
place-items: center;
|
||||
color: color-mix(in oklab, var(--hidden-avatar-color, var(--accent)) 38%, var(--fg));
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.hidden-avatar__fallback:not([hidden]) {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.hidden-avatar:hover {
|
||||
box-shadow: 0 0 0 1px color-mix(in oklab, var(--hidden-avatar-color, var(--accent)) 54%, color-mix(in oklab, #fff 20%, transparent)),
|
||||
0 0 24px color-mix(in oklab, var(--hidden-avatar-color, var(--accent)) 28%, transparent);
|
||||
filter: saturate(1.08);
|
||||
}
|
||||
|
||||
.hidden-toast.is-visible {
|
||||
|
||||
@@ -502,6 +502,7 @@
|
||||
--ritual-red: #d06b78;
|
||||
--ritual-green: #7fb089;
|
||||
--ritual-violet: #a58ac9;
|
||||
--active-avatar-glow: var(--ritual-gold);
|
||||
}
|
||||
|
||||
.observatory-panel {
|
||||
@@ -519,6 +520,7 @@
|
||||
radial-gradient(circle at 50% 50%, rgba(214, 179, 106, 0.16), transparent 12%),
|
||||
radial-gradient(circle at 28% 34%, rgba(117, 169, 189, 0.14), transparent 18%),
|
||||
radial-gradient(circle at 72% 70%, rgba(208, 107, 120, 0.12), transparent 16%),
|
||||
radial-gradient(circle at 50% 48%, color-mix(in oklab, var(--active-avatar-glow) 14%, transparent), transparent 26%),
|
||||
repeating-radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.07) 0 1px, transparent 1px 58px),
|
||||
var(--ritual-sky);
|
||||
}
|
||||
@@ -547,6 +549,8 @@
|
||||
font-weight: 900;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow: 0 0 18px rgba(214, 179, 106, 0.28);
|
||||
transition: width 180ms ease, height 180ms ease, box-shadow 180ms ease, transform 180ms ease, filter 180ms ease;
|
||||
animation: observatory-breathe 7s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.observatory-star:hover,
|
||||
@@ -554,9 +558,10 @@
|
||||
background: radial-gradient(circle, #fff8e8 0 18%, var(--ritual-red) 19% 56%, rgba(208, 107, 120, 0.22) 57% 100%);
|
||||
color: #fff8e8;
|
||||
box-shadow: 0 0 28px rgba(208, 107, 120, 0.46);
|
||||
filter: saturate(1.08);
|
||||
}
|
||||
|
||||
.observatory-star span {
|
||||
.observatory-star > span:not(.observatory-avatar) {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 100%;
|
||||
@@ -565,6 +570,110 @@
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.observatory-avatar {
|
||||
--avatar-size: 2rem;
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
width: var(--avatar-size);
|
||||
height: var(--avatar-size);
|
||||
flex: 0 0 auto;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: color-mix(in oklab, var(--avatar-glow, var(--ritual-gold)) 20%, #17130f);
|
||||
box-shadow: 0 0 0 1px color-mix(in oklab, var(--avatar-glow, var(--ritual-gold)) 54%, rgba(255, 248, 232, 0.28)),
|
||||
0 0 20px color-mix(in oklab, var(--avatar-glow, var(--ritual-gold)) 28%, transparent);
|
||||
}
|
||||
|
||||
.observatory-avatar--marker { --avatar-size: 100%; }
|
||||
.observatory-avatar--tiny { --avatar-size: 1.35rem; }
|
||||
.observatory-avatar--small { --avatar-size: 2rem; }
|
||||
.observatory-avatar--medium { --avatar-size: 2.75rem; }
|
||||
.observatory-avatar--large { --avatar-size: 4rem; }
|
||||
|
||||
.observatory-avatar img,
|
||||
.observatory-avatar__fallback {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.observatory-avatar img {
|
||||
object-fit: cover;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.observatory-avatar__fallback:not([hidden]) {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: #fff8e8;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.observatory-star-avatar {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0.76;
|
||||
}
|
||||
|
||||
.observatory-star-avatar.distant {
|
||||
opacity: 0.42;
|
||||
transform: scale(0.58);
|
||||
}
|
||||
|
||||
.observatory-star-avatar.medium {
|
||||
opacity: 0.82;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.observatory-star-avatar.close,
|
||||
.observatory-star-avatar.focus {
|
||||
opacity: 1;
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.observatory-star-avatar.focus {
|
||||
transform: scale(1.22);
|
||||
box-shadow: 0 0 0 2px rgba(255, 248, 232, 0.42), 0 0 34px color-mix(in oklab, var(--avatar-glow, var(--ritual-gold)) 48%, transparent);
|
||||
}
|
||||
|
||||
.observatory-star-label {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: calc(100% + 0.38rem);
|
||||
width: max-content;
|
||||
max-width: 13rem;
|
||||
height: auto !important;
|
||||
padding: 0.18rem 0.42rem;
|
||||
border-radius: 999px;
|
||||
background: rgba(23, 19, 15, 0.72);
|
||||
color: #fff8e8;
|
||||
font-size: 0.68rem !important;
|
||||
line-height: 1.25;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
text-transform: none !important;
|
||||
transform: translateX(-50%) translateY(-2px);
|
||||
transition: opacity 160ms ease, transform 160ms ease;
|
||||
}
|
||||
|
||||
.observatory-star:hover .observatory-star-label,
|
||||
.observatory-star.is-active .observatory-star-label {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
.observatory-thread-row,
|
||||
.return-memory-head,
|
||||
.symbol-chip-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.observatory-thread-row {
|
||||
margin: 0.45rem 0;
|
||||
}
|
||||
|
||||
.observatory-drawer,
|
||||
.dream-sentence {
|
||||
padding: 1rem;
|
||||
@@ -591,6 +700,16 @@
|
||||
gap: 0.35rem;
|
||||
min-height: 100px;
|
||||
text-align: left;
|
||||
transition: transform 160ms ease, box-shadow 160ms ease, border-color 160ms ease;
|
||||
}
|
||||
|
||||
.return-memory:hover,
|
||||
.return-memory:focus-visible,
|
||||
.symbol-chip:hover,
|
||||
.symbol-chip:focus-visible {
|
||||
border-color: color-mix(in oklab, var(--active-avatar-glow, var(--ritual-gold)) 38%, var(--border));
|
||||
box-shadow: 0 0 18px color-mix(in oklab, var(--active-avatar-glow, var(--ritual-gold)) 18%, transparent);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.return-memory span,
|
||||
@@ -610,6 +729,7 @@
|
||||
text-align: left;
|
||||
background:
|
||||
linear-gradient(135deg, color-mix(in oklab, var(--surface) 88%, var(--ritual-blue) 12%), var(--surface));
|
||||
transition: transform 160ms ease, box-shadow 160ms ease, border-color 160ms ease;
|
||||
}
|
||||
|
||||
.symbol-chip.is-active {
|
||||
@@ -617,6 +737,11 @@
|
||||
color: #fff8e8;
|
||||
}
|
||||
|
||||
@keyframes observatory-breathe {
|
||||
0%, 100% { box-shadow: 0 0 18px rgba(214, 179, 106, 0.24); }
|
||||
50% { box-shadow: 0 0 26px rgba(214, 179, 106, 0.4); }
|
||||
}
|
||||
|
||||
.symbol-chip.is-active span {
|
||||
color: rgba(255, 248, 232, 0.72);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ IMAGE_ASSETS_DIR = ROOT / "assets" / "images"
|
||||
HZONE_ASSETS_DIR = IMAGE_ASSETS_DIR / "hzone"
|
||||
HIDDEN_DETAILS_JS = ROOT / "assets" / "scripts" / "hidden-details.js"
|
||||
HIDDEN_CONTENT_JSON = ROOT / "assets" / "content" / "hidden-details.json"
|
||||
AVATAR_REGISTRY_JS = ROOT / "assets" / "scripts" / "avatar-registry.js"
|
||||
HIDDEN_BACKUP_DIR = ROOT / "backups" / "hidden-details"
|
||||
EXCLUDED_CONTENT_DIR_NAMES = {
|
||||
".agents",
|
||||
@@ -270,6 +271,115 @@ CHARACTER_REGISTRY = {
|
||||
"affinities": ["z", "young z", "lima", "sensei chi"],
|
||||
},
|
||||
}
|
||||
|
||||
AVATAR_REGISTRY_META = {
|
||||
"young z": {
|
||||
"avatar": "/assets/avatars/young-z.jpeg",
|
||||
"glow": "#d8a95a",
|
||||
"accent": "#f4bf5f",
|
||||
"fallback": "z",
|
||||
"expressions": {
|
||||
"calm": "/assets/avatars/young-z.jpeg",
|
||||
"happy": "/assets/avatars/young-z.jpeg",
|
||||
"surprised": "/assets/avatars/young-z.jpeg",
|
||||
},
|
||||
"sizes": {"marker": 18, "tiny": 22, "small": 32, "medium": 44, "large": 64, "focus": 88},
|
||||
"observatory": {
|
||||
"territory": "nostalgic-playful",
|
||||
"presenceKeywords": ["childhood", "play", "crayon", "sun", "blanket", "game", "desk", "drawing", "nostalgia"],
|
||||
"atmosphere": "warm childhood light",
|
||||
"focusScale": 1.08,
|
||||
},
|
||||
},
|
||||
"z": {
|
||||
"avatar": "/assets/avatars/z.jpeg",
|
||||
"glow": "#d6c38a",
|
||||
"accent": "#d8c891",
|
||||
"fallback": "z",
|
||||
"expressions": {"calm": "/assets/avatars/z.jpeg", "receptive": "/assets/avatars/z.jpeg"},
|
||||
"sizes": {"marker": 18, "tiny": 22, "small": 32, "medium": 44, "large": 64, "focus": 88},
|
||||
"observatory": {
|
||||
"territory": "archive-return",
|
||||
"presenceKeywords": ["website", "archive", "observer", "traveler", "making", "return", "home"],
|
||||
"atmosphere": "steady archive lantern",
|
||||
"focusScale": 1,
|
||||
},
|
||||
},
|
||||
"aphy": {
|
||||
"avatar": "/assets/avatars/aphy.jpeg",
|
||||
"glow": "#7fb089",
|
||||
"accent": "#83d4df",
|
||||
"fallback": "z",
|
||||
"expressions": {
|
||||
"calm": "/assets/avatars/aphy.jpeg",
|
||||
"curious": "/assets/avatars/aphy.jpeg",
|
||||
"thinking": "/assets/avatars/aphy.jpeg",
|
||||
},
|
||||
"sizes": {"marker": 18, "tiny": 22, "small": 32, "medium": 44, "large": 64, "focus": 88},
|
||||
"observatory": {
|
||||
"territory": "system-symbolic",
|
||||
"presenceKeywords": ["system", "terminal", "logic", "error", "debug", "console", "css", "search", "backup", "symbol"],
|
||||
"atmosphere": "soft diagnostic glow",
|
||||
"focusScale": 1.04,
|
||||
},
|
||||
},
|
||||
"lima": {
|
||||
"avatar": "/assets/avatars/lima.jpg",
|
||||
"glow": "#d06b78",
|
||||
"accent": "#e7a176",
|
||||
"fallback": "z",
|
||||
"expressions": {
|
||||
"calm": "/assets/avatars/lima.jpg",
|
||||
"happy": "/assets/avatars/lima.jpg",
|
||||
"protective": "/assets/avatars/lima.jpg",
|
||||
},
|
||||
"sizes": {"marker": 18, "tiny": 22, "small": 32, "medium": 44, "large": 64, "focus": 88},
|
||||
"observatory": {
|
||||
"territory": "warm-protective",
|
||||
"presenceKeywords": ["warm", "home", "safe", "protect", "kitchen", "ring", "love", "rest", "care", "light"],
|
||||
"atmosphere": "protective kitchen warmth",
|
||||
"focusScale": 1.12,
|
||||
},
|
||||
},
|
||||
"sensei chi": {
|
||||
"avatar": "/assets/avatars/sensei-chi.jpeg",
|
||||
"glow": "#75a9bd",
|
||||
"accent": "#9ec4be",
|
||||
"fallback": "z",
|
||||
"expressions": {"calm": "/assets/avatars/sensei-chi.jpeg", "reflective": "/assets/avatars/sensei-chi.jpeg"},
|
||||
"sizes": {"marker": 18, "tiny": 22, "small": 32, "medium": 44, "large": 64, "focus": 88},
|
||||
"observatory": {
|
||||
"territory": "reflective-patient",
|
||||
"presenceKeywords": ["quiet", "reflect", "patience", "tea", "garden", "lesson", "wisdom", "still"],
|
||||
"atmosphere": "slow reflective tea light",
|
||||
"focusScale": 1.06,
|
||||
},
|
||||
},
|
||||
"future z": {
|
||||
"avatar": "/assets/avatars/future-z.jpeg",
|
||||
"glow": "#a58ac9",
|
||||
"accent": "#b7a6cf",
|
||||
"fallback": "z",
|
||||
"expressions": {
|
||||
"calm": "/assets/avatars/future-z.jpeg",
|
||||
"nostalgic": "/assets/avatars/future-z.jpeg",
|
||||
"warning": "/assets/avatars/future-z.jpeg",
|
||||
},
|
||||
"sizes": {"marker": 18, "tiny": 22, "small": 32, "medium": 44, "large": 64, "focus": 88},
|
||||
"observatory": {
|
||||
"territory": "temporal",
|
||||
"presenceKeywords": ["future", "time", "age 40", "later", "return", "warning", "ordinary", "clock"],
|
||||
"atmosphere": "distant temporal shimmer",
|
||||
"focusScale": 1.1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for character_id, avatar_meta in AVATAR_REGISTRY_META.items():
|
||||
if character_id in CHARACTER_REGISTRY:
|
||||
CHARACTER_REGISTRY[character_id]["avatar"] = {**CHARACTER_REGISTRY[character_id].get("avatar", {}), **avatar_meta}
|
||||
CHARACTER_REGISTRY[character_id]["glow"] = avatar_meta["glow"]
|
||||
CHARACTER_REGISTRY[character_id]["accent"] = avatar_meta["accent"]
|
||||
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"]
|
||||
@@ -2604,7 +2714,9 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.avatar svg { width: 100%; height: 100%; display: block; shape-rendering: geometricPrecision; }
|
||||
.avatar img, .avatar-fallback { width: 100%; height: 100%; display: block; }
|
||||
.avatar img { object-fit: cover; border-radius: inherit; }
|
||||
.avatar-fallback:not([hidden]) { display: grid; place-items: center; color: #fff4d3; font-size: 11px; font-weight: 850; }
|
||||
.avatar--xs { --avatar-size: 18px; }
|
||||
.avatar--sm { --avatar-size: 24px; }
|
||||
.avatar--md { --avatar-size: 34px; }
|
||||
@@ -2693,8 +2805,12 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
.memory-node text, .node-label text, .cluster-label text { fill: #f7ead3; paint-order: stroke; stroke: rgba(14, 11, 8, 0.82); stroke-width: 3px; stroke-linejoin: round; }
|
||||
.memory-node circle { stroke: rgba(255, 246, 223, 0.82); stroke-width: 1.4; filter: drop-shadow(0 0 7px rgba(255, 232, 177, 0.18)); transition: r 160ms ease, stroke 160ms ease, opacity 160ms ease; }
|
||||
.memory-node .character-halo { fill: color-mix(in oklab, var(--node-character-color, rgba(211, 166, 77, 0.46)) 8%, transparent); stroke: var(--node-character-color, rgba(211, 166, 77, 0.46)); stroke-width: 1.6; opacity: 0.34; stroke-dasharray: 2 7; animation: spirit-shimmer 8s ease-in-out infinite; }
|
||||
.memory-node .node-avatar { opacity: 0; transform: scale(0.72); transition: opacity 220ms ease, transform 220ms ease; }
|
||||
.zoom-2 .memory-node .node-avatar, .zoom-3 .memory-node .node-avatar, .memory-node.selected .node-avatar { opacity: 0.96; transform: scale(1); }
|
||||
.memory-node .node-avatar { opacity: 0; transform: scale(0.58); transition: opacity 220ms ease, transform 220ms ease; }
|
||||
.zoom-1 .memory-node .node-avatar { opacity: 0.5; transform: scale(0.7); }
|
||||
.zoom-2 .memory-node .node-avatar { opacity: 0.9; transform: scale(0.95); }
|
||||
.zoom-3 .memory-node .node-avatar, .memory-node.selected .node-avatar { opacity: 1; transform: scale(1.12); }
|
||||
.zoom-0 .memory-node text { opacity: 1; }
|
||||
.zoom-3 .memory-node text, .memory-node.selected text { transform: translateY(27px); }
|
||||
.memory-node.selected circle { stroke: #fff3c7; stroke-width: 3; }
|
||||
.memory-node.dragging circle { stroke: #f3cd7a; stroke-width: 3; opacity: 0.72; }
|
||||
.memory-node.compatible circle { stroke: rgba(255, 243, 199, 0.92); stroke-width: 2.4; filter: drop-shadow(0 0 16px rgba(255, 232, 177, 0.46)); }
|
||||
@@ -3041,6 +3157,7 @@ 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" };
|
||||
let avatarClipSeq = 0;
|
||||
|
||||
function html(value) {
|
||||
return String(value || "").replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[char]));
|
||||
@@ -3060,6 +3177,18 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
function avatarSvg(character, expression = "soft") {
|
||||
const config = characterConfig(character);
|
||||
const avatar = config.avatar || {};
|
||||
const srcSvg = avatar.expressions?.[expression] || avatar.avatar || config.avatarPath || config.portrait || state.meta.characterRegistry?.z?.avatar?.avatar || "/assets/avatars/z.jpeg";
|
||||
const colorSvg = config.glow || avatar.glow || config.territoryColor || avatar.accent || "#d3a64d";
|
||||
const labelSvg = html(config.displayLabel || character || "z");
|
||||
const symbolSvg = html(config.symbol || "?");
|
||||
const clipSvg = `avatarClip-${String(character || "z").replace(/[^a-z0-9]+/gi, "-")}-${String(expression || "calm").replace(/[^a-z0-9]+/gi, "-")}-${avatarClipSeq += 1}`;
|
||||
return `<svg width="36" height="36" viewBox="0 0 36 36" aria-label="${labelSvg}" role="img" focusable="false">
|
||||
<defs><clipPath id="${clipSvg}"><circle cx="18" cy="18" r="16"></circle></clipPath></defs>
|
||||
<circle cx="18" cy="18" r="17" fill="${html(colorSvg)}" opacity=".18"></circle>
|
||||
<circle cx="18" cy="18" r="16.5" fill="rgba(18,16,13,.72)" stroke="${html(colorSvg)}" stroke-width="1.6"></circle>
|
||||
<image href="${html(srcSvg)}" x="2" y="2" width="32" height="32" preserveAspectRatio="xMidYMid slice" clip-path="url(#${clipSvg})"></image>
|
||||
<text x="28.8" y="31.4" text-anchor="middle" font-size="7" fill="#fff7e8" font-family="monospace">${symbolSvg}</text>
|
||||
</svg>`;
|
||||
const accent = avatar.accent || config.territoryColor || "#d3a64d";
|
||||
const secondary = avatar.secondary || "#f6dfb4";
|
||||
const hair = avatar.hair || "#5d4632";
|
||||
@@ -3108,8 +3237,10 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
}
|
||||
function avatarHtml(character, size = "sm", expression = "soft", extraClass = "") {
|
||||
const config = characterConfig(character);
|
||||
const color = config.territoryColor || config.avatar?.accent || "#d3a64d";
|
||||
return `<span class="avatar avatar--${size} ${extraClass}" style="--avatar-color:${html(color)}" title="${html(config.displayLabel || character)} - ${html(config.avatar?.description || "observatory spirit")}">${avatarSvg(character, expression)}</span>`;
|
||||
const avatar = config.avatar || {};
|
||||
const color = config.glow || avatar.glow || config.territoryColor || avatar.accent || "#d3a64d";
|
||||
const src = avatar.expressions?.[expression] || avatar.avatar || state.meta.characterRegistry?.z?.avatar?.avatar || "/assets/avatars/z.jpeg";
|
||||
return `<span class="avatar avatar--${size} ${extraClass}" style="--avatar-color:${html(color)}" title="${html(config.displayLabel || character)} - ${html(config.avatar?.description || avatar.observatory?.atmosphere || "observatory spirit")}"><img src="${html(src)}" alt="" loading="lazy" decoding="async" onerror="this.hidden=true;this.nextElementSibling.hidden=false"><span class="avatar-fallback" hidden>${html(config.symbol || "?")}</span></span>`;
|
||||
}
|
||||
function avatarChip(character, entry = null) {
|
||||
const config = characterConfig(character);
|
||||
|
||||
@@ -215,6 +215,7 @@ FMT / ARGS are passed to `format'."
|
||||
(format "<script src=\"%s\" defer></script>"
|
||||
(z/asset-url (format "assets/scripts/%s" f))))
|
||||
'("script.js"
|
||||
"avatar-registry.js"
|
||||
"lunr.js"
|
||||
"competency-status-board.js"
|
||||
"notes.js"
|
||||
|
||||
Reference in New Issue
Block a user