removing and clean
All checks were successful
Build Authoring Service / build (push) Successful in 9s
All checks were successful
Build Authoring Service / build (push) Successful in 9s
This commit is contained in:
@@ -27,45 +27,6 @@
|
|||||||
const hour = now.getHours();
|
const hour = now.getHours();
|
||||||
const path = window.location.pathname;
|
const path = window.location.pathname;
|
||||||
const state = readState();
|
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
|
// EDITABLE CONTENT
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
@@ -151,39 +112,13 @@
|
|||||||
// UI HELPERS
|
// 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 `<img class="hidden-message-avatar" src="${escapeHtml(config.avatar)}" alt="" loading="lazy" style="--hidden-avatar-glow:${escapeHtml(config.glow)}" onerror="this.onerror=null;this.src='${escapeHtml(config.fallback)}'">`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hiddenMessageMarkup(message) {
|
function hiddenMessageMarkup(message) {
|
||||||
const character = characterForMessage(message);
|
return `<span class="hidden-message-text">${escapeHtml(message)}</span>`;
|
||||||
return character
|
|
||||||
? `${avatarMarkup(character)}<span class="hidden-message-text">${escapeHtml(message)}</span>`
|
|
||||||
: `<span class="hidden-message-text">${escapeHtml(message)}</span>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setHiddenMessage(el, 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;
|
delete el.dataset.hiddenCharacter;
|
||||||
el.textContent = message;
|
el.innerHTML = hiddenMessageMarkup(message);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toast(message, duration) {
|
function toast(message, duration) {
|
||||||
@@ -372,8 +307,7 @@
|
|||||||
function showDriftNote(text, art) {
|
function showDriftNote(text, art) {
|
||||||
const panel = document.createElement("aside");
|
const panel = document.createElement("aside");
|
||||||
panel.className = "hidden-drift-note";
|
panel.className = "hidden-drift-note";
|
||||||
const character = characterForMessage(text);
|
panel.innerHTML = `<div class="hidden-drift-message">${hiddenMessageMarkup(text)}</div><pre>${escapeHtml(art)}</pre><button type="button">Close</button>`;
|
||||||
panel.innerHTML = `<div class="hidden-drift-message${character ? " has-hidden-avatar" : ""}">${hiddenMessageMarkup(text)}</div><pre>${escapeHtml(art)}</pre><button type="button">Close</button>`;
|
|
||||||
panel.querySelector("button").addEventListener("click", () => panel.remove());
|
panel.querySelector("button").addEventListener("click", () => panel.remove());
|
||||||
document.body.appendChild(panel);
|
document.body.appendChild(panel);
|
||||||
}
|
}
|
||||||
@@ -568,8 +502,7 @@
|
|||||||
const visits = readState().layerVisits || {};
|
const visits = readState().layerVisits || {};
|
||||||
target.innerHTML = familyLayers.map((line, layer) => {
|
target.innerHTML = familyLayers.map((line, layer) => {
|
||||||
const count = visits[layer] || 0;
|
const count = visits[layer] || 0;
|
||||||
const character = characterForMessage(line);
|
return `<li class="hidden-layer-message"><strong>${hiddenMessageMarkup(line)}</strong><br><span>local encounters: ${count}</span></li>`;
|
||||||
return `<li class="${character ? "hidden-layer-message" : ""}"><strong>${hiddenMessageMarkup(line)}</strong><br><span>local encounters: ${count}</span></li>`;
|
|
||||||
}).join("");
|
}).join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,13 +94,6 @@ CHARACTER_REGISTRY = {
|
|||||||
"aliases": ["Young Z", "young z", "young-z", "young_z", "young"],
|
"aliases": ["Young Z", "young z", "young-z", "young_z", "young"],
|
||||||
"territoryColor": "#d8a95a",
|
"territoryColor": "#d8a95a",
|
||||||
"glow": "#f0b85c",
|
"glow": "#f0b85c",
|
||||||
"avatar": "https://zainezq.com/assets/avatars/young-z.jpeg",
|
|
||||||
"fallbackAvatar": "https://zainezq.com/assets/avatars/z.jpeg",
|
|
||||||
"expressions": {
|
|
||||||
"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},
|
"sizes": {"tiny": 18, "medium": 42, "close": 56, "focus": 82},
|
||||||
"observatory": {"territory": "nostalgic/playful memories", "shimmer": "soft"},
|
"observatory": {"territory": "nostalgic/playful memories", "shimmer": "soft"},
|
||||||
"presenceTones": ["nostalgic", "funny", "hopeful"],
|
"presenceTones": ["nostalgic", "funny", "hopeful"],
|
||||||
@@ -116,31 +109,6 @@ CHARACTER_REGISTRY = {
|
|||||||
"aliases": ["Z", "z", "zaine"],
|
"aliases": ["Z", "z", "zaine"],
|
||||||
"territoryColor": "#d6c38a",
|
"territoryColor": "#d6c38a",
|
||||||
"glow": "#ead68e",
|
"glow": "#ead68e",
|
||||||
"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", "shimmer": "steady"},
|
|
||||||
"presenceTones": ["warm", "soft", "hopeful"],
|
|
||||||
"presenceKeywords": ["website", "archive", "home", "self", "making", "return", "zaine"],
|
|
||||||
"symbol": "Z",
|
|
||||||
"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",
|
|
||||||
"glow": "#9dd3a6",
|
|
||||||
"avatar": "https://zainezq.com/assets/avatars/aphy.jpeg",
|
|
||||||
"fallbackAvatar": "https://zainezq.com/assets/avatars/z.jpeg",
|
|
||||||
"expressions": {
|
|
||||||
"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},
|
"sizes": {"tiny": 18, "medium": 42, "close": 56, "focus": 82},
|
||||||
"observatory": {"territory": "system/symbolic areas", "shimmer": "diagnostic"},
|
"observatory": {"territory": "system/symbolic areas", "shimmer": "diagnostic"},
|
||||||
"presenceTones": ["funny", "strange", "hopeful"],
|
"presenceTones": ["funny", "strange", "hopeful"],
|
||||||
@@ -156,14 +124,6 @@ CHARACTER_REGISTRY = {
|
|||||||
"aliases": ["Lima", "lima"],
|
"aliases": ["Lima", "lima"],
|
||||||
"territoryColor": "#d06b78",
|
"territoryColor": "#d06b78",
|
||||||
"glow": "#f2c58b",
|
"glow": "#f2c58b",
|
||||||
"avatar": "https://zainezq.com/assets/avatars/lima.jpg",
|
|
||||||
"fallbackAvatar": "https://zainezq.com/assets/avatars/z.jpeg",
|
|
||||||
"expressions": {
|
|
||||||
"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},
|
"sizes": {"tiny": 18, "medium": 42, "close": 56, "focus": 82},
|
||||||
"observatory": {"territory": "warm/protective arcs", "shimmer": "warm"},
|
"observatory": {"territory": "warm/protective arcs", "shimmer": "warm"},
|
||||||
"presenceTones": ["warm", "protective", "soft"],
|
"presenceTones": ["warm", "protective", "soft"],
|
||||||
@@ -179,13 +139,6 @@ CHARACTER_REGISTRY = {
|
|||||||
"aliases": ["Sensei Chi", "sensei chi", "sensei-chi", "sensei_chi", "sensei"],
|
"aliases": ["Sensei Chi", "sensei chi", "sensei-chi", "sensei_chi", "sensei"],
|
||||||
"territoryColor": "#75a9bd",
|
"territoryColor": "#75a9bd",
|
||||||
"glow": "#9ccddd",
|
"glow": "#9ccddd",
|
||||||
"avatar": "https://zainezq.com/assets/avatars/sensei-chi.jpeg",
|
|
||||||
"fallbackAvatar": "https://zainezq.com/assets/avatars/z.jpeg",
|
|
||||||
"expressions": {
|
|
||||||
"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},
|
"sizes": {"tiny": 18, "medium": 42, "close": 56, "focus": 82},
|
||||||
"observatory": {"territory": "reflective areas", "shimmer": "quiet"},
|
"observatory": {"territory": "reflective areas", "shimmer": "quiet"},
|
||||||
"presenceTones": ["wise", "melancholy", "soft"],
|
"presenceTones": ["wise", "melancholy", "soft"],
|
||||||
@@ -201,13 +154,6 @@ CHARACTER_REGISTRY = {
|
|||||||
"aliases": ["Future Z", "future z", "future-z", "future_z", "future"],
|
"aliases": ["Future Z", "future z", "future-z", "future_z", "future"],
|
||||||
"territoryColor": "#a58ac9",
|
"territoryColor": "#a58ac9",
|
||||||
"glow": "#c2a4ee",
|
"glow": "#c2a4ee",
|
||||||
"avatar": "https://zainezq.com/assets/avatars/future-z.jpeg",
|
|
||||||
"fallbackAvatar": "https://zainezq.com/assets/avatars/z.jpeg",
|
|
||||||
"expressions": {
|
|
||||||
"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},
|
"sizes": {"tiny": 18, "medium": 42, "close": 56, "focus": 82},
|
||||||
"observatory": {"territory": "temporal regions", "shimmer": "temporal"},
|
"observatory": {"territory": "temporal regions", "shimmer": "temporal"},
|
||||||
"presenceTones": ["hopeful", "melancholy", "wise"],
|
"presenceTones": ["hopeful", "melancholy", "wise"],
|
||||||
|
|||||||
@@ -1263,22 +1263,27 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
background: rgba(251, 241, 220, 0.08);
|
background: rgba(251, 241, 220, 0.08);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
.avatar-pill, .avatar-row, .character-tag {
|
.character-pill, .character-row, .character-tag {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 7px;
|
gap: 7px;
|
||||||
}
|
}
|
||||||
.avatar-thumb {
|
.character-mark {
|
||||||
width: var(--avatar-size, 28px);
|
width: var(--character-size, 28px);
|
||||||
height: var(--avatar-size, 28px);
|
height: var(--character-size, 28px);
|
||||||
flex: 0 0 var(--avatar-size, 28px);
|
flex: 0 0 var(--character-size, 28px);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
object-fit: cover;
|
|
||||||
border: 1px solid rgba(255, 246, 223, 0.72);
|
border: 1px solid rgba(255, 246, 223, 0.72);
|
||||||
background: rgba(251, 241, 220, 0.08);
|
background: rgba(251, 241, 220, 0.08);
|
||||||
box-shadow: 0 0 18px color-mix(in srgb, var(--avatar-glow, #d3a64d) 42%, transparent);
|
box-shadow: 0 0 18px color-mix(in srgb, var(--character-glow, #d3a64d) 42%, transparent);
|
||||||
|
color: #fff4d7;
|
||||||
|
display: inline-grid;
|
||||||
|
font-size: calc(var(--character-size, 28px) * 0.46);
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1;
|
||||||
|
place-items: center;
|
||||||
}
|
}
|
||||||
.avatar-row {
|
.character-row {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
@@ -1287,7 +1292,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: rgba(251, 241, 220, 0.055);
|
background: rgba(251, 241, 220, 0.055);
|
||||||
}
|
}
|
||||||
.avatar-row .subtle { font-size: 12px; }
|
.character-row .subtle { font-size: 12px; }
|
||||||
.character-tag {
|
.character-tag {
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
border: 1px solid rgba(232, 202, 139, 0.2);
|
border: 1px solid rgba(232, 202, 139, 0.2);
|
||||||
@@ -1298,7 +1303,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
.character-tag .avatar-thumb { --avatar-size: 24px; }
|
.character-tag .character-mark { --character-size: 24px; }
|
||||||
.layer-key { display: grid; gap: 8px; margin-top: 14px; }
|
.layer-key { display: grid; gap: 8px; margin-top: 14px; }
|
||||||
.layer-row {
|
.layer-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -1346,7 +1351,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
.memory-node.selected { opacity: 1; filter: drop-shadow(0 0 18px rgba(255, 231, 173, 0.45)); }
|
.memory-node.selected { opacity: 1; filter: drop-shadow(0 0 18px rgba(255, 231, 173, 0.45)); }
|
||||||
.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 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; }
|
||||||
.hit-target { fill: transparent; stroke: transparent; pointer-events: all; }
|
.hit-target { fill: transparent; stroke: transparent; pointer-events: all; }
|
||||||
.memory-node .node-shell, .memory-node .node-portrait, .memory-node .node-marker, .memory-node .fragment-star {
|
.memory-node .node-shell, .memory-node .node-shell, .memory-node .node-marker, .memory-node .fragment-star {
|
||||||
stroke: rgba(255, 246, 223, 0.82);
|
stroke: rgba(255, 246, 223, 0.82);
|
||||||
stroke-width: 1.4;
|
stroke-width: 1.4;
|
||||||
filter: drop-shadow(0 0 7px var(--node-glow, rgba(255, 232, 177, 0.18)));
|
filter: drop-shadow(0 0 7px var(--node-glow, rgba(255, 232, 177, 0.18)));
|
||||||
@@ -1354,16 +1359,16 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
}
|
}
|
||||||
.memory-node .fragment-star { stroke-width: 1.1; }
|
.memory-node .fragment-star { stroke-width: 1.1; }
|
||||||
.memory-node .fragment-ring { fill: none; stroke: rgba(255, 246, 223, 0.24); stroke-width: 1; stroke-dasharray: 2 7; }
|
.memory-node .fragment-ring { fill: none; stroke: rgba(255, 246, 223, 0.24); stroke-width: 1; stroke-dasharray: 2 7; }
|
||||||
.memory-node .node-breathe, .cluster-node .node-breathe { animation: avatar-breathe 8s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
.memory-node .node-breathe, .cluster-node .node-breathe { animation: character-breathe 8s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
||||||
.sequence-badge { fill: rgba(18, 16, 13, 0.88); stroke: rgba(255, 243, 199, 0.68); stroke-width: 1; }
|
.sequence-badge { fill: rgba(18, 16, 13, 0.88); stroke: rgba(255, 243, 199, 0.68); stroke-width: 1; }
|
||||||
.sequence-number { fill: #ffe7ad; font-size: 10px; font-weight: 850; paint-order: stroke; stroke: rgba(14, 11, 8, 0.72); stroke-width: 2px; }
|
.sequence-number { fill: #ffe7ad; font-size: 10px; font-weight: 850; paint-order: stroke; stroke: rgba(14, 11, 8, 0.72); stroke-width: 2px; }
|
||||||
.selection-halo { fill: rgba(255, 231, 173, 0.1); stroke: rgba(255, 243, 199, 0.95); stroke-width: 3; stroke-dasharray: 4 9; filter: drop-shadow(0 0 22px rgba(255, 231, 173, 0.62)); animation: selection-pulse 2.8s ease-in-out infinite; pointer-events: none; }
|
.selection-halo { fill: rgba(255, 231, 173, 0.1); stroke: rgba(255, 243, 199, 0.95); stroke-width: 3; stroke-dasharray: 4 9; filter: drop-shadow(0 0 22px rgba(255, 231, 173, 0.62)); animation: selection-pulse 2.8s ease-in-out infinite; pointer-events: none; }
|
||||||
.memory-node:hover .node-shell, .memory-node:hover .node-portrait, .memory-node:hover .fragment-star { filter: drop-shadow(0 0 20px var(--node-glow, rgba(255, 232, 177, 0.4))); }
|
.memory-node:hover .node-shell, .memory-node:hover .node-shell, .memory-node:hover .fragment-star { filter: drop-shadow(0 0 20px var(--node-glow, rgba(255, 232, 177, 0.4))); }
|
||||||
.memory-node.selected .node-shell, .memory-node.selected .node-portrait, .memory-node.selected .fragment-star { stroke: #fff3c7; stroke-width: 3; }
|
.memory-node.selected .node-shell, .memory-node.selected .node-shell, .memory-node.selected .fragment-star { stroke: #fff3c7; stroke-width: 3; }
|
||||||
.memory-node.dragging .node-shell, .memory-node.dragging .node-portrait { stroke: #f3cd7a; stroke-width: 3; opacity: 0.72; }
|
.memory-node.dragging .node-shell, .memory-node.dragging .node-shell { stroke: #f3cd7a; stroke-width: 3; opacity: 0.72; }
|
||||||
.memory-node.compatible .node-shell, .memory-node.compatible .node-portrait { stroke: rgba(255, 243, 199, 0.92); stroke-width: 2.4; filter: drop-shadow(0 0 16px rgba(255, 232, 177, 0.46)); }
|
.memory-node.compatible .node-shell, .memory-node.compatible .node-shell { stroke: rgba(255, 243, 199, 0.92); stroke-width: 2.4; filter: drop-shadow(0 0 16px rgba(255, 232, 177, 0.46)); }
|
||||||
.memory-node.drop-target .node-shell, .memory-node.drop-target .node-portrait { stroke: #fff5c9; stroke-width: 4; filter: drop-shadow(0 0 24px rgba(255, 232, 177, 0.7)); }
|
.memory-node.drop-target .node-shell, .memory-node.drop-target .node-shell { stroke: #fff5c9; stroke-width: 4; filter: drop-shadow(0 0 24px rgba(255, 232, 177, 0.7)); }
|
||||||
.memory-node.invalid-drop .node-shell, .memory-node.invalid-drop .node-portrait { stroke: rgba(208, 107, 120, 0.9); stroke-width: 3; }
|
.memory-node.invalid-drop .node-shell, .memory-node.invalid-drop .node-shell { stroke: rgba(208, 107, 120, 0.9); stroke-width: 3; }
|
||||||
.cluster-node { cursor: pointer; transition: transform 160ms ease, filter 160ms ease; }
|
.cluster-node { cursor: pointer; transition: transform 160ms ease, filter 160ms ease; }
|
||||||
.cluster-node circle {
|
.cluster-node circle {
|
||||||
stroke: rgba(255, 243, 199, 0.75);
|
stroke: rgba(255, 243, 199, 0.75);
|
||||||
@@ -1671,7 +1676,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
0%, 100% { opacity: 0.72; }
|
0%, 100% { opacity: 0.72; }
|
||||||
50% { opacity: 1; }
|
50% { opacity: 1; }
|
||||||
}
|
}
|
||||||
@keyframes avatar-breathe {
|
@keyframes character-breathe {
|
||||||
0%, 100% { transform: scale(1); opacity: 0.92; }
|
0%, 100% { transform: scale(1); opacity: 0.92; }
|
||||||
50% { transform: scale(1.035); opacity: 1; }
|
50% { transform: scale(1.035); opacity: 1; }
|
||||||
}
|
}
|
||||||
@@ -1890,7 +1895,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
<section class="panel">
|
<section class="panel">
|
||||||
<h2 id="drawerTitle">Choose a memory</h2>
|
<h2 id="drawerTitle">Choose a memory</h2>
|
||||||
<div id="drawerSub" class="subtle">The side panel opens from the constellation.</div>
|
<div id="drawerSub" class="subtle">The side panel opens from the constellation.</div>
|
||||||
<div id="drawerAvatars" class="pills" style="margin-top:10px"></div>
|
<div id="drawerCharacters" class="pills" style="margin-top:10px"></div>
|
||||||
<div id="preview" class="preview" style="margin-top:10px"></div>
|
<div id="preview" class="preview" style="margin-top:10px"></div>
|
||||||
</section>
|
</section>
|
||||||
<section class="panel editor-panel story-editor-panel" id="storyEditorPanel">
|
<section class="panel editor-panel story-editor-panel" id="storyEditorPanel">
|
||||||
@@ -2017,7 +2022,6 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
const storyFields = ["storyTitle","storyDescription","storyTone","storyDiscovery","storyCharacters","storySymbols","storyMarkers","storyLayers","storyUnlock","storyHidden"];
|
const storyFields = ["storyTitle","storyDescription","storyTone","storyDiscovery","storyCharacters","storySymbols","storyMarkers","storyLayers","storyUnlock","storyHidden"];
|
||||||
const $ = (id) => document.getElementById(id);
|
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" };
|
const toneColors = { warm: "#d3a64d", funny: "#7fb089", nostalgic: "#d06b78", wise: "#75a9bd", strange: "#a58ac9", soft: "#e6bd8c", hopeful: "#9fcf9f", protective: "#d98f71", melancholy: "#8fa7c6" };
|
||||||
const defaultAvatarPath = "/assets/avatars/z.jpeg";
|
|
||||||
const appearanceLabels = {
|
const appearanceLabels = {
|
||||||
observatory: "Observatory stars",
|
observatory: "Observatory stars",
|
||||||
tooltip: "Soft tooltips",
|
tooltip: "Soft tooltips",
|
||||||
@@ -2062,30 +2066,25 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
function entryById(id) {
|
function entryById(id) {
|
||||||
return state.entries.find((entry) => entry.id === id);
|
return state.entries.find((entry) => entry.id === id);
|
||||||
}
|
}
|
||||||
function avatarRegistry() {
|
function characterRegistry() {
|
||||||
return state.meta.characterRegistry || {};
|
return state.meta.characterRegistry || {};
|
||||||
}
|
}
|
||||||
function characterConfig(character) {
|
function characterConfig(character) {
|
||||||
const registry = avatarRegistry();
|
const registry = characterRegistry();
|
||||||
return registry[character] || registry.z || {};
|
return registry[character] || registry.z || {};
|
||||||
}
|
}
|
||||||
function characterLabel(character) {
|
function characterLabel(character) {
|
||||||
return characterConfig(character).displayLabel || character || "z";
|
return characterConfig(character).displayLabel || character || "z";
|
||||||
}
|
}
|
||||||
function avatarPath(character, expression = "default") {
|
function characterGlow(character) {
|
||||||
const config = characterConfig(character);
|
|
||||||
const expressions = config.expressions || {};
|
|
||||||
return expressions[expression] || expressions.default || config.avatar || config.fallbackAvatar || defaultAvatarPath;
|
|
||||||
}
|
|
||||||
function avatarGlow(character) {
|
|
||||||
const config = characterConfig(character);
|
const config = characterConfig(character);
|
||||||
return config.glow || config.territoryColor || "#d3a64d";
|
return config.glow || config.territoryColor || "#d3a64d";
|
||||||
}
|
}
|
||||||
function avatarSize(character, size = "medium") {
|
function characterSize(character, size = "medium") {
|
||||||
const sizes = characterConfig(character).sizes || {};
|
const sizes = characterConfig(character).sizes || {};
|
||||||
return Number(sizes[size] || sizes.medium || 42);
|
return Number(sizes[size] || sizes.medium || 42);
|
||||||
}
|
}
|
||||||
function avatarExpressionForEntry(entry) {
|
function characterExpressionForEntry(entry) {
|
||||||
const tone = entry?.emotionalTone || "";
|
const tone = entry?.emotionalTone || "";
|
||||||
if (tone === "protective") return "protective";
|
if (tone === "protective") return "protective";
|
||||||
if (tone === "wise") return "wise";
|
if (tone === "wise") return "wise";
|
||||||
@@ -2095,10 +2094,10 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
if (String(entry?.type || "").includes("future")) return "temporal";
|
if (String(entry?.type || "").includes("future")) return "temporal";
|
||||||
return "default";
|
return "default";
|
||||||
}
|
}
|
||||||
function avatarImg(character, size = "medium", expression = "default", className = "") {
|
function characterImg(character, size = "medium", expression = "default", className = "") {
|
||||||
const px = avatarSize(character, size);
|
const px = characterSize(character, size);
|
||||||
const label = characterLabel(character);
|
const label = characterLabel(character);
|
||||||
return `<img class="avatar-thumb ${html(className)}" src="${html(avatarPath(character, expression))}" alt="${html(label)} portrait" loading="lazy" style="--avatar-size:${px}px;--avatar-glow:${html(avatarGlow(character))}" onerror="this.onerror=null;this.src='${html(characterConfig(character).fallbackAvatar || defaultAvatarPath)}'">`;
|
return `<span class="character-mark ${html(className)}" title="${html(label)}" aria-label="${html(label)}" style="--character-size:${px}px;--character-glow:${html(characterGlow(character))}">${html(characterSymbol(character))}</span>`;
|
||||||
}
|
}
|
||||||
function entryTextCorpus(entry) {
|
function entryTextCorpus(entry) {
|
||||||
return [
|
return [
|
||||||
@@ -2127,7 +2126,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
return explicit + tone + keywords + motifs;
|
return explicit + tone + keywords + motifs;
|
||||||
}
|
}
|
||||||
function primaryCharacterForEntry(entry) {
|
function primaryCharacterForEntry(entry) {
|
||||||
const candidates = Object.keys(avatarRegistry());
|
const candidates = Object.keys(characterRegistry());
|
||||||
const scored = candidates.map((character) => ({ character, score: characterPresenceScore(entry, character) })).sort((a, b) => b.score - a.score);
|
const scored = candidates.map((character) => ({ character, score: characterPresenceScore(entry, character) })).sort((a, b) => b.score - a.score);
|
||||||
return scored[0]?.score > 0 ? scored[0].character : (entry.characters || [])[0] || "z";
|
return scored[0]?.score > 0 ? scored[0].character : (entry.characters || [])[0] || "z";
|
||||||
}
|
}
|
||||||
@@ -2136,13 +2135,11 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
const primary = primaryCharacterForEntry(entry);
|
const primary = primaryCharacterForEntry(entry);
|
||||||
return [primary, ...listed.filter((character) => character !== primary)].filter(Boolean);
|
return [primary, ...listed.filter((character) => character !== primary)].filter(Boolean);
|
||||||
}
|
}
|
||||||
function svgAvatar(character, radius, clipId, expression = "default", extraClass = "") {
|
function svgCharacter(character, radius, clipId, expression = "default", extraClass = "") {
|
||||||
const diameter = radius * 2;
|
|
||||||
const fallback = characterSymbol(character);
|
const fallback = characterSymbol(character);
|
||||||
return `<circle class="node-shell ${html(extraClass)}" r="${radius}" fill="${html(characterColor(character) || avatarGlow(character))}" opacity="0.22"></circle>
|
return `<circle class="node-shell ${html(extraClass)}" r="${radius}" fill="${html(characterColor(character) || characterGlow(character))}" opacity="0.22"></circle>
|
||||||
<image class="node-portrait ${html(extraClass)}" href="${html(avatarPath(character, expression))}" x="${-radius}" y="${-radius}" width="${diameter}" height="${diameter}" preserveAspectRatio="xMidYMid slice" clip-path="url(#${html(clipId)})"></image>
|
|
||||||
<circle class="node-shell ${html(extraClass)}" r="${radius}" fill="none"></circle>
|
<circle class="node-shell ${html(extraClass)}" r="${radius}" fill="none"></circle>
|
||||||
<text class="node-fallback" x="0" y="4" text-anchor="middle" opacity="0.32">${html(fallback)}</text>`;
|
<text class="node-fallback ${html(extraClass)}" x="0" y="4" text-anchor="middle" opacity="0.74">${html(fallback)}</text>`;
|
||||||
}
|
}
|
||||||
function normalizeCharacterName(value) {
|
function normalizeCharacterName(value) {
|
||||||
const raw = String(value || "").trim();
|
const raw = String(value || "").trim();
|
||||||
@@ -2511,11 +2508,11 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
}
|
}
|
||||||
function renderCharacterPresence(entry) {
|
function renderCharacterPresence(entry) {
|
||||||
const characters = charactersForEntry(entry);
|
const characters = charactersForEntry(entry);
|
||||||
$("drawerAvatars").innerHTML = characters.map((character, index) => `<span class="avatar-pill pill" style="--avatar-glow:${html(avatarGlow(character))}">${avatarImg(character, index === 0 ? "medium" : "tiny", avatarExpressionForEntry(entry))}${html(characterLabel(character))}</span>`).join("");
|
$("drawerCharacters").innerHTML = characters.map((character, index) => `<span class="character-pill pill" style="--character-glow:${html(characterGlow(character))}">${characterImg(character, index === 0 ? "medium" : "tiny", characterExpressionForEntry(entry))}${html(characterLabel(character))}</span>`).join("");
|
||||||
$("characterTags").innerHTML = characters.map((character) => `<span class="character-tag" style="--avatar-glow:${html(avatarGlow(character))}">${avatarImg(character, "tiny", avatarExpressionForEntry(entry))}${html(characterLabel(character))}</span>`).join("");
|
$("characterTags").innerHTML = characters.map((character) => `<span class="character-tag" style="--character-glow:${html(characterGlow(character))}">${characterImg(character, "tiny", characterExpressionForEntry(entry))}${html(characterLabel(character))}</span>`).join("");
|
||||||
const primary = primaryCharacterForEntry(entry);
|
const primary = primaryCharacterForEntry(entry);
|
||||||
const territory = characterConfig(primary).observatory?.territory || "this memory territory";
|
const territory = characterConfig(primary).observatory?.territory || "this memory territory";
|
||||||
$("presencePreview").innerHTML = `<span class="avatar-row" style="--avatar-glow:${html(avatarGlow(primary))}"><span>${avatarImg(primary, "medium", avatarExpressionForEntry(entry))}</span><span><strong>${html(characterLabel(primary))}</strong><br><span class="subtle">presence preview: ${html(territory)}</span></span></span>`;
|
$("presencePreview").innerHTML = `<span class="character-row" style="--character-glow:${html(characterGlow(primary))}"><span>${characterImg(primary, "medium", characterExpressionForEntry(entry))}</span><span><strong>${html(characterLabel(primary))}</strong><br><span class="subtle">presence preview: ${html(territory)}</span></span></span>`;
|
||||||
}
|
}
|
||||||
function layerName(layer) {
|
function layerName(layer) {
|
||||||
return (state.meta.layers || []).find((item) => item.id === String(layer))?.name || `Layer ${layer}`;
|
return (state.meta.layers || []).find((item) => item.id === String(layer))?.name || `Layer ${layer}`;
|
||||||
@@ -2867,13 +2864,13 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
const label = labelForCluster(cluster, pos, size, occupied);
|
const label = labelForCluster(cluster, pos, size, occupied);
|
||||||
const activeDrop = state.drag?.targetKind === "cluster" && state.drag.targetId === cluster.id;
|
const activeDrop = state.drag?.targetKind === "cluster" && state.drag.targetId === cluster.id;
|
||||||
const grow = activeDrop ? 1.08 : 1;
|
const grow = activeDrop ? 1.08 : 1;
|
||||||
const portraitRadius = Math.max(28, size * 0.46) * grow;
|
const symbolRadius = Math.max(28, size * 0.46) * grow;
|
||||||
const clipId = `clip-cluster-${slugify(cluster.id)}`;
|
const clipId = `clip-cluster-${slugify(cluster.id)}`;
|
||||||
defs.push(`<clipPath id="${html(clipId)}"><circle r="${portraitRadius}"></circle></clipPath>`);
|
defs.push(`<clipPath id="${html(clipId)}"><circle r="${symbolRadius}"></circle></clipPath>`);
|
||||||
return `<g class="cluster-node${activeDrop ? " drop-target" : ""}" data-id="${html(cluster.id)}" tabindex="0" transform="translate(${pos.x} ${pos.y})" style="--node-glow:${html(avatarGlow(character))}">
|
return `<g class="cluster-node${activeDrop ? " drop-target" : ""}" data-id="${html(cluster.id)}" tabindex="0" transform="translate(${pos.x} ${pos.y})" style="--node-glow:${html(characterGlow(character))}">
|
||||||
<circle class="cluster-halo" r="${(size + 13) * grow}"></circle>
|
<circle class="cluster-halo" r="${(size + 13) * grow}"></circle>
|
||||||
<circle r="${size * grow}" fill="${color}" opacity="0.58"></circle>
|
<circle r="${size * grow}" fill="${color}" opacity="0.58"></circle>
|
||||||
${state.zoom > 0 || state.mode === "character" ? svgAvatar(character, portraitRadius, clipId, "default", "node-breathe") : ""}
|
${state.zoom > 0 || state.mode === "character" ? svgCharacter(character, symbolRadius, clipId, "default", "node-breathe") : ""}
|
||||||
<text class="count" x="0" y="7" text-anchor="middle">${cluster.entries.length}</text>
|
<text class="count" x="0" y="7" text-anchor="middle">${cluster.entries.length}</text>
|
||||||
</g>${label}`;
|
</g>${label}`;
|
||||||
}).join("");
|
}).join("");
|
||||||
@@ -2884,7 +2881,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
const color = characterColor(character) || toneColors[entry.emotionalTone] || "#d3a64d";
|
const color = characterColor(character) || toneColors[entry.emotionalTone] || "#d3a64d";
|
||||||
const isFragment = (entry.contentClass || "fragment") === "fragment" || (entry.observatoryRole || "") === "ambient";
|
const isFragment = (entry.contentClass || "fragment") === "fragment" || (entry.observatoryRole || "") === "ambient";
|
||||||
const baseSize = (isFragment ? 10 : 18) + Math.min(isFragment ? 10 : 18, Number(entry.resonanceScore || 3) * (isFragment ? 1.6 : 2.2)) + (entry.rarity === "rare" || entry.rarity === "very rare" ? 8 : 0);
|
const baseSize = (isFragment ? 10 : 18) + Math.min(isFragment ? 10 : 18, Number(entry.resonanceScore || 3) * (isFragment ? 1.6 : 2.2)) + (entry.rarity === "rare" || entry.rarity === "very rare" ? 8 : 0);
|
||||||
const zoomSize = state.zoom === 0 ? avatarSize(character, "tiny") : state.zoom === 1 ? avatarSize(character, "medium") : state.zoom === 2 ? avatarSize(character, "close") : avatarSize(character, "focus");
|
const zoomSize = state.zoom === 0 ? characterSize(character, "tiny") : state.zoom === 1 ? characterSize(character, "medium") : state.zoom === 2 ? characterSize(character, "close") : characterSize(character, "focus");
|
||||||
const size = isFragment ? baseSize : Math.max(baseSize, zoomSize / 2);
|
const size = isFragment ? baseSize : Math.max(baseSize, zoomSize / 2);
|
||||||
const label = labelForEntry(entry, pos, size, occupied);
|
const label = labelForEntry(entry, pos, size, occupied);
|
||||||
const dragClass = dragClassForEntry(entry.id);
|
const dragClass = dragClassForEntry(entry.id);
|
||||||
@@ -2899,14 +2896,14 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
const sequenceBadge = sequenceNumber && (state.camera.k > 0.26 || activeStoryIds.has(entry.id) || related)
|
const sequenceBadge = sequenceNumber && (state.camera.k > 0.26 || activeStoryIds.has(entry.id) || related)
|
||||||
? `<g transform="translate(${Math.max(12, size * 0.62)} ${-Math.max(12, size * 0.62)})"><circle class="sequence-badge" r="${badgeRadius}"></circle><text class="sequence-number" x="0" y="3.5" text-anchor="middle">${sequenceNumber}</text></g>`
|
? `<g transform="translate(${Math.max(12, size * 0.62)} ${-Math.max(12, size * 0.62)})"><circle class="sequence-badge" r="${badgeRadius}"></circle><text class="sequence-number" x="0" y="3.5" text-anchor="middle">${sequenceNumber}</text></g>`
|
||||||
: "";
|
: "";
|
||||||
return `<g class="memory-node${isSelected ? " selected" : ""}${related && !isSelected ? " related-node" : ""}${dimmed ? " dimmed" : ""}${entry.enabled === false ? " resting" : ""}${dragClass}" data-id="${html(entry.id)}" tabindex="0" transform="translate(${pos.x} ${pos.y})" style="--node-glow:${html(avatarGlow(character))}">
|
return `<g class="memory-node${isSelected ? " selected" : ""}${related && !isSelected ? " related-node" : ""}${dimmed ? " dimmed" : ""}${entry.enabled === false ? " resting" : ""}${dragClass}" data-id="${html(entry.id)}" tabindex="0" transform="translate(${pos.x} ${pos.y})" style="--node-glow:${html(characterGlow(character))}">
|
||||||
<circle class="hit-target" r="${Math.max(34, size + 16)}"></circle>
|
<circle class="hit-target" r="${Math.max(34, size + 16)}"></circle>
|
||||||
${isSelected ? `<circle class="selection-halo" r="${Math.max(38, size + 18)}"></circle>` : ""}
|
${isSelected ? `<circle class="selection-halo" r="${Math.max(38, size + 18)}"></circle>` : ""}
|
||||||
${isFragment
|
${isFragment
|
||||||
? `<circle class="fragment-ring constellation-shimmer" r="${size + 9}" opacity="${entry.enabled === false ? 0.22 : 0.62}"></circle><circle class="fragment-star" r="${size}" fill="${color}" opacity="${entry.enabled === false ? 0.34 : 0.84}"></circle><text x="0" y="4" text-anchor="middle">${html(characterSymbol(character))}</text>`
|
? `<circle class="fragment-ring constellation-shimmer" r="${size + 9}" opacity="${entry.enabled === false ? 0.22 : 0.62}"></circle><circle class="fragment-star" r="${size}" fill="${color}" opacity="${entry.enabled === false ? 0.34 : 0.84}"></circle><text x="0" y="4" text-anchor="middle">${html(characterSymbol(character))}</text>`
|
||||||
: distant
|
: distant
|
||||||
? `<circle class="node-marker constellation-shimmer" r="${size}" fill="${color}" opacity="${entry.enabled === false ? 0.32 : 0.78}"></circle><text x="0" y="4" text-anchor="middle">${html(characterSymbol(character))}</text>`
|
? `<circle class="node-marker constellation-shimmer" r="${size}" fill="${color}" opacity="${entry.enabled === false ? 0.32 : 0.78}"></circle><text x="0" y="4" text-anchor="middle">${html(characterSymbol(character))}</text>`
|
||||||
: svgAvatar(character, size, clipId, avatarExpressionForEntry(entry), state.zoom >= 2 ? "node-breathe" : "")}
|
: svgCharacter(character, size, clipId, characterExpressionForEntry(entry), state.zoom >= 2 ? "node-breathe" : "")}
|
||||||
${sequenceBadge}
|
${sequenceBadge}
|
||||||
</g>${label}`;
|
</g>${label}`;
|
||||||
}).join("");
|
}).join("");
|
||||||
@@ -3055,7 +3052,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
function renderBreadcrumb(scope) {
|
function renderBreadcrumb(scope) {
|
||||||
const filters = activeFilters();
|
const filters = activeFilters();
|
||||||
const parts = [`<button type="button" data-action="home">Observatory</button>`, `<button type="button" data-clear="mode">${html(state.mode)}</button>`];
|
const parts = [`<button type="button" data-action="home">Observatory</button>`, `<button type="button" data-clear="mode">${html(state.mode)}</button>`];
|
||||||
filters.forEach((filter) => parts.push(`<button type="button" title="${html(filter.help)}" data-clear="${html(filter.id)}">${filter.character ? avatarImg(filter.character, "tiny") : ""}${html(filter.label)}</button>`));
|
filters.forEach((filter) => parts.push(`<button type="button" title="${html(filter.help)}" data-clear="${html(filter.id)}">${filter.character ? characterImg(filter.character, "tiny") : ""}${html(filter.label)}</button>`));
|
||||||
if (scope.focusedCluster) parts.push(`<button type="button" data-clear="focus">${html(scope.focusedCluster.label)}</button>`);
|
if (scope.focusedCluster) parts.push(`<button type="button" data-clear="focus">${html(scope.focusedCluster.label)}</button>`);
|
||||||
if (state.focusIds) parts.push(`<button type="button" data-clear="focus">${html(state.focusLabel || "Focused thread")}</button>`);
|
if (state.focusIds) parts.push(`<button type="button" data-clear="focus">${html(state.focusLabel || "Focused thread")}</button>`);
|
||||||
$("breadcrumb").innerHTML = parts.join("");
|
$("breadcrumb").innerHTML = parts.join("");
|
||||||
@@ -3085,7 +3082,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
$("activeState").innerHTML = `<span class="subtle">Whole sky visible. You can always return here.</span>`;
|
$("activeState").innerHTML = `<span class="subtle">Whole sky visible. You can always return here.</span>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$("activeState").innerHTML = active.map((item) => `<span class="filter-pill" title="${html(item.help)}">${item.character ? avatarImg(item.character, "tiny") : ""}${html(item.label)}${item.id === "selection" ? "" : `<button type="button" aria-label="Remove ${html(item.label)}" data-clear="${html(item.id)}">x</button>`}</span>`).join("") + `<button type="button" data-action="soft">Clear Focus</button><button type="button" data-action="full">Whole Sky</button>`;
|
$("activeState").innerHTML = active.map((item) => `<span class="filter-pill" title="${html(item.help)}">${item.character ? characterImg(item.character, "tiny") : ""}${html(item.label)}${item.id === "selection" ? "" : `<button type="button" aria-label="Remove ${html(item.label)}" data-clear="${html(item.id)}">x</button>`}</span>`).join("") + `<button type="button" data-action="soft">Clear Focus</button><button type="button" data-action="full">Whole Sky</button>`;
|
||||||
$("activeState").querySelectorAll("[data-clear]").forEach((button) => button.addEventListener("click", () => clearExplorationPart(button.dataset.clear)));
|
$("activeState").querySelectorAll("[data-clear]").forEach((button) => button.addEventListener("click", () => clearExplorationPart(button.dataset.clear)));
|
||||||
$("activeState").querySelector("[data-action='soft']")?.addEventListener("click", softReset);
|
$("activeState").querySelector("[data-action='soft']")?.addEventListener("click", softReset);
|
||||||
$("activeState").querySelector("[data-action='full']")?.addEventListener("click", fullReset);
|
$("activeState").querySelector("[data-action='full']")?.addEventListener("click", fullReset);
|
||||||
@@ -3554,8 +3551,8 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
}
|
}
|
||||||
function renderPreview(entry) {
|
function renderPreview(entry) {
|
||||||
const body = html(entry.content || "").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
const body = html(entry.content || "").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
||||||
const avatarPills = charactersForEntry(entry).map((character) => `<span class="avatar-pill pill" style="--avatar-glow:${html(avatarGlow(character))}">${avatarImg(character, "tiny", avatarExpressionForEntry(entry))}${html(characterLabel(character))}</span>`).join("");
|
const characterPills = charactersForEntry(entry).map((character) => `<span class="character-pill pill" style="--character-glow:${html(characterGlow(character))}">${characterImg(character, "tiny", characterExpressionForEntry(entry))}${html(characterLabel(character))}</span>`).join("");
|
||||||
$("preview").innerHTML = `${body || "<span class='subtle'>This entry is waiting for words.</span>"}<div class="pills" style="margin-top:10px">${avatarPills}<span class="pill">${html(entry.contentClass || "fragment")}</span><span class="pill">${html(entry.observatoryRole || "ambient")}</span><span class="pill">${html(layerName(depth(entry)))}</span><span class="pill">${html(entry.emotionalTone)}</span><span class="pill">${html(entry.rarity)}</span>${(entry.surfaces || []).map((item) => `<span class="pill">${html(item)}</span>`).join("")}${(entry.symbols || []).map((item) => `<span class="pill">${html(item)}</span>`).join("")}</div>`;
|
$("preview").innerHTML = `${body || "<span class='subtle'>This entry is waiting for words.</span>"}<div class="pills" style="margin-top:10px">${characterPills}<span class="pill">${html(entry.contentClass || "fragment")}</span><span class="pill">${html(entry.observatoryRole || "ambient")}</span><span class="pill">${html(layerName(depth(entry)))}</span><span class="pill">${html(entry.emotionalTone)}</span><span class="pill">${html(entry.rarity)}</span>${(entry.surfaces || []).map((item) => `<span class="pill">${html(item)}</span>`).join("")}${(entry.symbols || []).map((item) => `<span class="pill">${html(item)}</span>`).join("")}</div>`;
|
||||||
}
|
}
|
||||||
function renderConnections(entry) {
|
function renderConnections(entry) {
|
||||||
const stories = storiesForEntry(entry.id);
|
const stories = storiesForEntry(entry.id);
|
||||||
@@ -3582,7 +3579,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
$("storyFlow").innerHTML = "";
|
$("storyFlow").innerHTML = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const characters = (story.characters || []).map((character) => `<span class="avatar-pill pill">${avatarImg(character, "tiny")}${html(characterLabel(character))}</span>`).join("");
|
const characters = (story.characters || []).map((character) => `<span class="character-pill pill">${characterImg(character, "tiny")}${html(characterLabel(character))}</span>`).join("");
|
||||||
$("storyCover").innerHTML = `<strong>${html(story.title)}</strong><p class="subtle">${html(story.description || "A quiet route waiting for a summary.")}</p><div class="pills">${characters}<span class="pill">${html(story.tone || "warm")}</span><span class="pill">${html(story.discoveryStyle || "gradual")}</span>${story.hidden ? "<span class='pill'>hidden</span>" : ""}${(story.markers || []).map((item) => `<span class="pill">${html(item)}</span>`).join("")}</div><div class="actions" style="margin-top:10px"><button type="button" data-read-story="${html(story.id)}">Read this story</button></div>`;
|
$("storyCover").innerHTML = `<strong>${html(story.title)}</strong><p class="subtle">${html(story.description || "A quiet route waiting for a summary.")}</p><div class="pills">${characters}<span class="pill">${html(story.tone || "warm")}</span><span class="pill">${html(story.discoveryStyle || "gradual")}</span>${story.hidden ? "<span class='pill'>hidden</span>" : ""}${(story.markers || []).map((item) => `<span class="pill">${html(item)}</span>`).join("")}</div><div class="actions" style="margin-top:10px"><button type="button" data-read-story="${html(story.id)}">Read this story</button></div>`;
|
||||||
$("storyCover").querySelector("[data-read-story]")?.addEventListener("click", () => openStoryReader(story.id));
|
$("storyCover").querySelector("[data-read-story]")?.addEventListener("click", () => openStoryReader(story.id));
|
||||||
const items = storyItems(story);
|
const items = storyItems(story);
|
||||||
@@ -3631,7 +3628,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
const pool = state.entries.filter((entry) => !inStory.has(entry.id) && (!query || entryTextCorpus(entry).includes(query))).slice(0, 24);
|
const pool = state.entries.filter((entry) => !inStory.has(entry.id) && (!query || entryTextCorpus(entry).includes(query))).slice(0, 24);
|
||||||
$("memoryPool").innerHTML = pool.map((entry) => {
|
$("memoryPool").innerHTML = pool.map((entry) => {
|
||||||
const character = primaryCharacterForEntry(entry);
|
const character = primaryCharacterForEntry(entry);
|
||||||
return `<button class="connection avatar-row" type="button" draggable="true" data-id="${html(entry.id)}" style="--avatar-glow:${html(avatarGlow(character))}"><span>${avatarImg(character, "tiny", avatarExpressionForEntry(entry))}</span><span><strong>${html(entry.title)}</strong><br><span class="subtle">${html(entry.emotionalTone || "warm")} / ${html(entry.type || "memory")}</span></span></button>`;
|
return `<button class="connection character-row" type="button" draggable="true" data-id="${html(entry.id)}" style="--character-glow:${html(characterGlow(character))}"><span>${characterImg(character, "tiny", characterExpressionForEntry(entry))}</span><span><strong>${html(entry.title)}</strong><br><span class="subtle">${html(entry.emotionalTone || "warm")} / ${html(entry.type || "memory")}</span></span></button>`;
|
||||||
}).join("") || "<div class='subtle'>Every visible memory is already in this story.</div>";
|
}).join("") || "<div class='subtle'>Every visible memory is already in this story.</div>";
|
||||||
$("memoryPool").querySelectorAll("[data-id]").forEach((button) => {
|
$("memoryPool").querySelectorAll("[data-id]").forEach((button) => {
|
||||||
button.addEventListener("click", () => selectNode(button.dataset.id));
|
button.addEventListener("click", () => selectNode(button.dataset.id));
|
||||||
@@ -3672,7 +3669,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
$("readerStage").innerHTML = `<article class="reader-scene reader-section"><div class="reader-kicker">Story scene</div><h2>${html(item.title || "Story section")}</h2><p class="reader-copy">${html(item.content || "This scene is waiting for words.")}</p></article>`;
|
$("readerStage").innerHTML = `<article class="reader-scene reader-section"><div class="reader-kicker">Story scene</div><h2>${html(item.title || "Story section")}</h2><p class="reader-copy">${html(item.content || "This scene is waiting for words.")}</p></article>`;
|
||||||
} else {
|
} else {
|
||||||
const entry = entryById(item.id);
|
const entry = entryById(item.id);
|
||||||
const characters = charactersForEntry(entry).map((character) => `<span class="avatar-pill pill">${avatarImg(character, "tiny", avatarExpressionForEntry(entry))}${html(characterLabel(character))}</span>`).join("");
|
const characters = charactersForEntry(entry).map((character) => `<span class="character-pill pill">${characterImg(character, "tiny", characterExpressionForEntry(entry))}${html(characterLabel(character))}</span>`).join("");
|
||||||
const kind = (entry.contentClass || "fragment") === "fragment" ? "Reusable memory light" : "Reusable memory";
|
const kind = (entry.contentClass || "fragment") === "fragment" ? "Reusable memory light" : "Reusable memory";
|
||||||
$("readerStage").innerHTML = `<article class="reader-scene reader-memory"><div class="reader-kicker">${html(kind)}</div><h2>${html(entry.title || "Untitled memory")}</h2><p class="reader-copy">${html(entry.content || "This memory is waiting for words.")}</p><div class="pills" style="justify-content:center;margin-top:18px">${characters}<span class="pill">${html(entry.emotionalTone || "warm")}</span><span class="pill">appears in ${storiesForEntry(entry.id).length} ${storiesForEntry(entry.id).length === 1 ? "story" : "stories"}</span></div></article>`;
|
$("readerStage").innerHTML = `<article class="reader-scene reader-memory"><div class="reader-kicker">${html(kind)}</div><h2>${html(entry.title || "Untitled memory")}</h2><p class="reader-copy">${html(entry.content || "This memory is waiting for words.")}</p><div class="pills" style="justify-content:center;margin-top:18px">${characters}<span class="pill">${html(entry.emotionalTone || "warm")}</span><span class="pill">appears in ${storiesForEntry(entry.id).length} ${storiesForEntry(entry.id).length === 1 ? "story" : "stories"}</span></div></article>`;
|
||||||
}
|
}
|
||||||
@@ -3916,10 +3913,10 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
|||||||
$("toneFilter").innerHTML = opt("", "All tones") + state.meta.tones.map((value) => opt(value)).join("");
|
$("toneFilter").innerHTML = opt("", "All tones") + state.meta.tones.map((value) => opt(value)).join("");
|
||||||
$("rarityFilter").innerHTML = opt("", "All rarities") + state.meta.rarities.map((value) => opt(value)).join("");
|
$("rarityFilter").innerHTML = opt("", "All rarities") + state.meta.rarities.map((value) => opt(value)).join("");
|
||||||
$("layerKey").innerHTML = (state.meta.layers || []).map((layer) => `<div class="layer-row"><span class="depth-mark">${html(layer.id)}</span><span><strong>${html(layer.name)}</strong><span class="subtle">${html(layer.meaning)}</span></span></div>`).join("");
|
$("layerKey").innerHTML = (state.meta.layers || []).map((layer) => `<div class="layer-row"><span class="depth-mark">${html(layer.id)}</span><span><strong>${html(layer.name)}</strong><span class="subtle">${html(layer.meaning)}</span></span></div>`).join("");
|
||||||
$("characterAtlas").innerHTML = Object.keys(avatarRegistry()).map((character) => {
|
$("characterAtlas").innerHTML = Object.keys(characterRegistry()).map((character) => {
|
||||||
const config = characterConfig(character);
|
const config = characterConfig(character);
|
||||||
const territory = config.observatory?.territory || "memory territory";
|
const territory = config.observatory?.territory || "memory territory";
|
||||||
return `<button class="connection avatar-row" type="button" data-character="${html(character)}" style="--avatar-glow:${html(avatarGlow(character))}"><span>${avatarImg(character, "medium")}</span><span><strong>${html(characterLabel(character))}</strong><br><span class="subtle">${html(territory)}</span></span></button>`;
|
return `<button class="connection character-row" type="button" data-character="${html(character)}" style="--character-glow:${html(characterGlow(character))}"><span>${characterImg(character, "medium")}</span><span><strong>${html(characterLabel(character))}</strong><br><span class="subtle">${html(territory)}</span></span></button>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
$("characterAtlas").querySelectorAll("[data-character]").forEach((button) => button.addEventListener("click", () => {
|
$("characterAtlas").querySelectorAll("[data-character]").forEach((button) => button.addEventListener("click", () => {
|
||||||
snapshotExploration(`character territory: ${button.dataset.character}`);
|
snapshotExploration(`character territory: ${button.dataset.character}`);
|
||||||
|
|||||||
@@ -3,14 +3,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import mimetypes
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from email.utils import formatdate
|
from email.utils import formatdate
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from urllib.parse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
@@ -89,27 +87,6 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self.send_json({"error": str(exc)}, HTTPStatus.INTERNAL_SERVER_ERROR)
|
self.send_json({"error": str(exc)}, HTTPStatus.INTERNAL_SERVER_ERROR)
|
||||||
return
|
return
|
||||||
if parsed.path.startswith("/assets/avatars/"):
|
|
||||||
try:
|
|
||||||
relative = Path(parsed.path.lstrip("/"))
|
|
||||||
if relative.is_absolute() or ".." in relative.parts:
|
|
||||||
self.send_error(HTTPStatus.NOT_FOUND)
|
|
||||||
return
|
|
||||||
asset_path = (ROOT / relative).resolve()
|
|
||||||
avatars_root = (ROOT / "assets" / "avatars").resolve()
|
|
||||||
if not asset_path.is_file() or avatars_root not in asset_path.parents:
|
|
||||||
self.send_error(HTTPStatus.NOT_FOUND)
|
|
||||||
return
|
|
||||||
body = asset_path.read_bytes()
|
|
||||||
self.send_response(HTTPStatus.OK)
|
|
||||||
self.send_header("Content-Type", mimetypes.guess_type(asset_path.name)[0] or "application/octet-stream")
|
|
||||||
self.send_header("Content-Length", str(len(body)))
|
|
||||||
self.send_header("Cache-Control", "no-store")
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(body)
|
|
||||||
except Exception:
|
|
||||||
self.send_error(HTTPStatus.NOT_FOUND)
|
|
||||||
return
|
|
||||||
self.send_error(HTTPStatus.NOT_FOUND)
|
self.send_error(HTTPStatus.NOT_FOUND)
|
||||||
|
|
||||||
def do_POST(self) -> None:
|
def do_POST(self) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user