This commit is contained in:
@@ -34,6 +34,75 @@
|
||||
// Generated by the hidden narrative authoring page.
|
||||
// Friendly source of truth: assets/content/hidden-details.json
|
||||
|
||||
const CHARACTER_REGISTRY = {
|
||||
"young z": {
|
||||
"id": "young z",
|
||||
"displayLabel": "young z",
|
||||
"aliases": ["Young Z", "young z", "young-z", "young_z", "young"],
|
||||
"territoryColor": "#d8a95a",
|
||||
"symbol": "Y",
|
||||
"avatar": {"shape": "sun-child", "accent": "#f1bc5b", "shadow": "#7a5630", "mood": "excited", "motif": "crayon sun", "description": "small crayon-sun spirit with oversized curiosity"},
|
||||
"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",
|
||||
"avatar": {"shape": "traveler", "accent": "#d6c38a", "shadow": "#5d533a", "mood": "receptive", "motif": "small lantern", "description": "present observer spirit, quiet and receptive"},
|
||||
"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",
|
||||
"avatar": {"shape": "terminal", "accent": "#7fd0a0", "shadow": "#315944", "mood": "curious", "motif": "terminal cursor", "description": "curious tiny system companion with a soft terminal glow"},
|
||||
"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",
|
||||
"avatar": {"shape": "hearth", "accent": "#e78f8d", "shadow": "#6d3f43", "mood": "warm", "motif": "kitchen light", "description": "warm grounding spirit with calm eyes and a soft light"},
|
||||
"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",
|
||||
"avatar": {"shape": "sage", "accent": "#84b8c8", "shadow": "#36566a", "mood": "smiling", "motif": "tea lantern", "description": "patient elderly spirit in simple robes"},
|
||||
"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",
|
||||
"avatar": {"shape": "moon", "accent": "#b8a2d7", "shadow": "#51496a", "mood": "reflective", "motif": "distant clock", "description": "older reflective spirit, distant but caring"},
|
||||
"motifs": ["clock", "age 40", "future log"],
|
||||
"themes": ["time", "reassurance", "continuity"],
|
||||
"affinities": ["z", "young z", "lima", "sensei chi"]
|
||||
}
|
||||
};
|
||||
|
||||
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.",
|
||||
@@ -498,7 +567,70 @@
|
||||
// UI HELPERS
|
||||
// -----------------------------
|
||||
|
||||
function toast(message, duration) {
|
||||
function characterFromText(value) {
|
||||
const text = String(value || "").toLowerCase();
|
||||
let best = "z";
|
||||
let bestScore = 0;
|
||||
Object.entries(CHARACTER_REGISTRY).forEach(([id, config]) => {
|
||||
let score = text.includes(id) ? 6 : 0;
|
||||
(config.aliases || []).forEach((alias) => {
|
||||
if (text.includes(String(alias).toLowerCase())) score += 3;
|
||||
});
|
||||
(config.themes || []).forEach((theme) => {
|
||||
if (text.includes(String(theme).toLowerCase())) score += 2;
|
||||
});
|
||||
(config.motifs || []).forEach((motif) => {
|
||||
if (text.includes(String(motif).toLowerCase())) score += 3;
|
||||
});
|
||||
if (id === "lima" && /warm|home|safe|kitchen|ring|love|rest/.test(text)) score += 3;
|
||||
if (id === "aphy" && /system|terminal|logic|error|debug|console|css|search|backup/.test(text)) score += 3;
|
||||
if (id === "sensei chi" && /quiet|patience|tea|garden|lesson|wisdom|still/.test(text)) score += 3;
|
||||
if (id === "young z" && /play|child|crayon|sun|blanket|game|desk|drawing/.test(text)) score += 3;
|
||||
if (id === "future z" && /future|time|age 40|later|return|warning|ordinary/.test(text)) score += 3;
|
||||
if (score > bestScore) {
|
||||
best = id;
|
||||
bestScore = score;
|
||||
}
|
||||
});
|
||||
return best;
|
||||
}
|
||||
|
||||
function avatarExpression(character, text) {
|
||||
const value = String(text || "").toLowerCase();
|
||||
if (character === "aphy" && /error|glitch|system|diagnostic|debug|console/.test(value)) return "glitching";
|
||||
if (character === "lima" && /safe|home|warm|protect|care|eat|rest|light/.test(value)) return "concerned";
|
||||
if (character === "young z" && /draw|crayon|game|play|sun|blanket/.test(value)) return "drawing";
|
||||
if (character === "future z" && /time|future|return|ordinary|warning|later/.test(value)) return "reflective";
|
||||
if (character === "sensei chi" && /quiet|tea|patience|lesson|garden/.test(value)) return "smiling";
|
||||
return CHARACTER_REGISTRY[character]?.avatar?.mood || "soft";
|
||||
}
|
||||
|
||||
function avatarSvg(character, expression) {
|
||||
const config = CHARACTER_REGISTRY[character] || CHARACTER_REGISTRY.z;
|
||||
const avatar = config.avatar || {};
|
||||
const accent = avatar.accent || config.territoryColor || "#d3a64d";
|
||||
const shadow = avatar.shadow || "#5d4632";
|
||||
const symbol = escapeHtml(config.symbol || "?");
|
||||
const shape = avatar.shape || "traveler";
|
||||
const eye = expression === "reflective" || expression === "smiling" ? "M12 15h3M21 15h3" : expression === "glitching" ? "M11 14h5M21 13h3M22 17h4" : "M12 14h3M21 14h3";
|
||||
const mouth = expression === "concerned" ? "M15 23q3-2 6 0" : expression === "excited" || expression === "drawing" || expression === "smiling" ? "M15 22q3 3 7 0" : "M16 22h6";
|
||||
const motif = {
|
||||
"sun-child": `<path d="M18 3v5M18 28v5M4 18h5M27 18h5M8 8l4 4M28 8l-4 4" stroke="${accent}" stroke-width="2" stroke-linecap="round" opacity=".72"></path>`,
|
||||
terminal: `<path d="M9 8h18v19H9z" fill="none" stroke="${accent}" stroke-width="2" opacity=".62"></path><path d="M13 12l3 3-3 3M18 19h6" stroke="${accent}" stroke-width="2" stroke-linecap="round" opacity=".82"></path>`,
|
||||
sage: `<path d="M12 28c3-5 9-5 13 0" fill="${shadow}" opacity=".48"></path><path d="M25 9c2 2 2 5 0 7" stroke="${accent}" stroke-width="2" fill="none" opacity=".64"></path>`,
|
||||
hearth: `<path d="M18 5c4 4 8 7 8 12 0 6-4 10-8 10s-8-4-8-10c0-5 4-8 8-12z" fill="${accent}" opacity=".24"></path>`,
|
||||
moon: `<path d="M23 6c-5 2-8 6-8 11 0 6 4 10 10 11-2 2-5 3-8 3-7 0-12-5-12-12S10 7 17 7c2 0 4 0 6-1z" fill="${accent}" opacity=".22"></path>`,
|
||||
traveler: `<path d="M10 27c3-4 13-4 16 0" fill="${shadow}" opacity=".42"></path><path d="M26 24l4 5" stroke="${accent}" stroke-width="2" stroke-linecap="round" opacity=".7"></path>`
|
||||
}[shape] || "";
|
||||
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="7" fill="#17120f"></rect>${motif}<path d="M10 18c0-7 4-11 9-11s9 4 9 11c0 8-4 12-9 12s-9-4-9-12z" fill="${accent}" opacity=".86"></path><path d="M11 20c2 5 5 8 8 8s6-3 8-8" fill="${shadow}" opacity=".28"></path><path d="${eye}" stroke="#1a120f" stroke-width="2.2" stroke-linecap="round"></path><path d="${mouth}" stroke="#1a120f" stroke-width="2" stroke-linecap="round" fill="none"></path><text x="28" y="31" text-anchor="middle" font-size="8" fill="#f8ead0" font-family="monospace">${symbol}</text></svg>`;
|
||||
}
|
||||
|
||||
function avatarHtml(character, text) {
|
||||
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>`;
|
||||
}
|
||||
|
||||
function toast(message, duration, character) {
|
||||
let el = document.querySelector(".hidden-toast");
|
||||
if (!el) {
|
||||
el = document.createElement("div");
|
||||
@@ -506,7 +638,8 @@
|
||||
el.setAttribute("role", "status");
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
el.textContent = message;
|
||||
const spirit = character || characterFromText(message);
|
||||
el.innerHTML = `${avatarHtml(spirit, message)}<span>${escapeHtml(message)}</span>`;
|
||||
el.classList.add("is-visible");
|
||||
window.clearTimeout(el._timer);
|
||||
el._timer = window.setTimeout(() => el.classList.remove("is-visible"), duration || 5600);
|
||||
@@ -590,9 +723,10 @@
|
||||
mark.tabIndex = 0;
|
||||
mark.textContent = " ?";
|
||||
mark.title = pick(index % 2 ? poems : details, `whisper-${index}`);
|
||||
mark.dataset.character = characterFromText(mark.title);
|
||||
mark.addEventListener("click", () => {
|
||||
awardLayer(index % 2 ? 2 : 1, "paragraph whisper");
|
||||
toast(mark.title);
|
||||
toast(mark.title, undefined, mark.dataset.character);
|
||||
});
|
||||
p.appendChild(mark);
|
||||
});
|
||||
@@ -688,8 +822,9 @@
|
||||
|
||||
function showDriftNote(text, art) {
|
||||
const panel = document.createElement("aside");
|
||||
const character = characterFromText(text);
|
||||
panel.className = "hidden-drift-note";
|
||||
panel.innerHTML = `<div>${escapeHtml(text)}</div><pre>${escapeHtml(art)}</pre><button type="button">Close</button>`;
|
||||
panel.innerHTML = `<div class="hidden-drift-note__head">${avatarHtml(character, text)}<div>${escapeHtml(text)}</div></div><pre>${escapeHtml(art)}</pre><button type="button">Close</button>`;
|
||||
panel.querySelector("button").addEventListener("click", () => panel.remove());
|
||||
document.body.appendChild(panel);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user