story redesign 2
All checks were successful
Build Authoring Service / build (push) Successful in 10s
All checks were successful
Build Authoring Service / build (push) Successful in 10s
This commit is contained in:
@@ -1355,6 +1355,8 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
.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 .node-breathe, .cluster-node .node-breathe { animation: avatar-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-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; }
|
||||
.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.selected .node-shell, .memory-node.selected .node-portrait, .memory-node.selected .fragment-star { stroke: #fff3c7; stroke-width: 3; }
|
||||
@@ -2631,6 +2633,74 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
function relationshipPairsForClusters(clusters) {
|
||||
return [];
|
||||
}
|
||||
function storyLayoutItems(story, route, visibleEntries) {
|
||||
const visible = new Set(visibleEntries.map((entry) => entry.id));
|
||||
const routeIds = new Set(route.map((entry) => entry.id));
|
||||
const items = [];
|
||||
let sectionIndex = 0;
|
||||
let sequenceIndex = 0;
|
||||
storyItems(story).forEach((item) => {
|
||||
if (item.kind === "section") {
|
||||
sectionIndex += 1;
|
||||
return;
|
||||
}
|
||||
if (!visible.has(item.id) || !routeIds.has(item.id)) return;
|
||||
items.push({ id: item.id, sectionIndex, sequenceIndex });
|
||||
sequenceIndex += 1;
|
||||
});
|
||||
return items;
|
||||
}
|
||||
function storyConstellationPoint(layout, item) {
|
||||
const count = layout.count;
|
||||
if (count <= 6) {
|
||||
const step = item.sequenceIndex - (count - 1) / 2;
|
||||
const arc = Math.sin(item.sequenceIndex / Math.max(1, count - 1) * Math.PI) * 58;
|
||||
return {
|
||||
x: step * 156,
|
||||
y: arc + (item.sectionIndex % 2) * 18,
|
||||
};
|
||||
}
|
||||
|
||||
const row = Math.floor(item.sequenceIndex / layout.columns);
|
||||
const indexInRow = item.sequenceIndex % layout.columns;
|
||||
const reversed = row % 2 === 1;
|
||||
const actualColumns = row === layout.rows - 1 && count % layout.columns ? count % layout.columns : layout.columns;
|
||||
const col = reversed ? actualColumns - 1 - indexInRow : indexInRow;
|
||||
const wave = Math.sin((indexInRow / Math.max(1, actualColumns - 1)) * Math.PI) * layout.arc;
|
||||
const sectionLift = (item.sectionIndex % 3 - 1) * 18;
|
||||
return {
|
||||
x: (col - (actualColumns - 1) / 2) * layout.columnGap,
|
||||
y: (row - (layout.rows - 1) / 2) * layout.rowGap + wave + sectionLift,
|
||||
};
|
||||
}
|
||||
function storyConstellationLayout(story, route, entries, storyIndex, storyCount) {
|
||||
const angle = (Math.PI * 2 * storyIndex) / Math.max(1, storyCount) + hash(story.id) / 9000;
|
||||
const baseRadius = 520 + (storyIndex % 4) * 250;
|
||||
const count = route.length;
|
||||
const columns = count > 18 ? Math.ceil(Math.sqrt(count * 1.9)) : count > 10 ? Math.ceil(Math.sqrt(count * 1.65)) : Math.max(1, count);
|
||||
const rows = Math.max(1, Math.ceil(count / Math.max(1, columns)));
|
||||
const layout = {
|
||||
count,
|
||||
columns: Math.max(1, columns),
|
||||
rows,
|
||||
columnGap: count > 18 ? 178 : 160,
|
||||
rowGap: count > 18 ? 134 : 122,
|
||||
arc: count > 18 ? 38 : 52,
|
||||
};
|
||||
const anchorX = WORLD.cx + Math.cos(angle) * baseRadius;
|
||||
const anchorY = WORLD.cy + Math.sin(angle) * baseRadius * 0.68;
|
||||
const driftX = Math.cos(angle) * Math.min(130, 30 + count * 4);
|
||||
const driftY = Math.sin(angle) * Math.min(92, 20 + count * 2.8);
|
||||
const layoutItems = storyLayoutItems(story, route, entries);
|
||||
layoutItems.forEach((item) => {
|
||||
const point = storyConstellationPoint(layout, item);
|
||||
state.positions.set(item.id, {
|
||||
x: anchorX + driftX + point.x,
|
||||
y: anchorY + driftY + point.y,
|
||||
sequenceIndex: item.sequenceIndex,
|
||||
});
|
||||
});
|
||||
}
|
||||
function layoutEntries(entries) {
|
||||
const cx = WORLD.cx;
|
||||
const cy = WORLD.cy;
|
||||
@@ -2638,23 +2708,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
const stories = filteredStories().filter((story) => (story.nodes || []).some((id) => entries.some((entry) => entry.id === id)));
|
||||
stories.forEach((story, storyIndex) => {
|
||||
const route = storyEntries(story).filter((entry) => entries.some((item) => item.id === entry.id));
|
||||
const angle = (Math.PI * 2 * storyIndex) / Math.max(1, stories.length) + hash(story.id) / 9000;
|
||||
const baseRadius = 520 + (storyIndex % 4) * 250;
|
||||
const anchorX = cx + Math.cos(angle) * baseRadius;
|
||||
const anchorY = cy + Math.sin(angle) * baseRadius * 0.68;
|
||||
const denseRoute = route.length > 12;
|
||||
const stepX = denseRoute ? 188 : 150;
|
||||
const stepY = denseRoute ? 138 : 110;
|
||||
const curveLift = denseRoute ? 150 : 90;
|
||||
route.forEach((entry, index) => {
|
||||
const step = index - (route.length - 1) / 2;
|
||||
const curve = Math.sin(index / Math.max(1, route.length - 1) * Math.PI) * curveLift;
|
||||
const direction = angle + Math.PI / 2;
|
||||
const lane = denseRoute ? ((index % 2) - 0.5) * 70 : 0;
|
||||
const x = anchorX + Math.cos(direction) * step * stepX + Math.cos(angle) * curve + Math.cos(angle) * lane;
|
||||
const y = anchorY + Math.sin(direction) * step * stepY + Math.sin(angle) * curve * 0.7 + Math.sin(angle) * lane * 0.55;
|
||||
state.positions.set(entry.id, { x, y });
|
||||
});
|
||||
storyConstellationLayout(story, route, entries, storyIndex, stories.length);
|
||||
});
|
||||
const loose = entries.filter((entry) => !state.positions.has(entry.id));
|
||||
const byLayer = new Map();
|
||||
@@ -2840,6 +2894,11 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
const clipId = `clip-node-${slugify(entry.id)}`;
|
||||
defs.push(`<clipPath id="${html(clipId)}"><circle r="${size}"></circle></clipPath>`);
|
||||
const distant = state.zoom === 0;
|
||||
const sequenceNumber = Number.isInteger(pos.sequenceIndex) ? pos.sequenceIndex + 1 : 0;
|
||||
const badgeRadius = sequenceNumber > 99 ? 14 : sequenceNumber > 9 ? 12 : 10;
|
||||
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>`
|
||||
: "";
|
||||
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))}">
|
||||
<circle class="hit-target" r="${Math.max(34, size + 16)}"></circle>
|
||||
${isSelected ? `<circle class="selection-halo" r="${Math.max(38, size + 18)}"></circle>` : ""}
|
||||
@@ -2848,6 +2907,7 @@ HIDDEN_APP_HTML = r"""<!doctype html>
|
||||
: 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>`
|
||||
: svgAvatar(character, size, clipId, avatarExpressionForEntry(entry), state.zoom >= 2 ? "node-breathe" : "")}
|
||||
${sequenceBadge}
|
||||
</g>${label}`;
|
||||
}).join("");
|
||||
graph.innerHTML = `<defs>${defs.join("")}</defs><g id="world" transform="${cameraTransform()}">${rings}${storyRegions}${clusterEdges}${edges}${clusterNodes}${nodes}<g id="dragLayer"></g></g>`;
|
||||
|
||||
Reference in New Issue
Block a user