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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user