diff --git a/PlotLine.Tests/Program.cs b/PlotLine.Tests/Program.cs
index f142590..b514c77 100644
--- a/PlotLine.Tests/Program.cs
+++ b/PlotLine.Tests/Program.cs
@@ -1179,6 +1179,10 @@ static void Phase21RSceneBrowserAndPanelsAreReplayFriendly()
Assert(css.Contains("scrollbar-width: none", StringComparison.Ordinal), "Side-panel native scrollbar tracks should be suppressed.");
Assert(css.Contains(".story-exp-rebuild {\n position: fixed;", StringComparison.Ordinal), "Replay rebuild controls should be fixed so they do not resize the whole screen.");
Assert(css.Contains(".story-exp-panel:has(.story-exp-insight-list:empty)", StringComparison.Ordinal), "Empty side panels should collapse instead of reserving large blank space.");
+ Assert(css.Contains("grid-template-columns: minmax(180px, 0.42fr) minmax(660px, 1.55fr) minmax(380px, 0.84fr);", StringComparison.Ordinal), "Right insight rail should be widened now that assets are not stage nodes.");
+ Assert(css.Contains("--story-side-body-font-size: 0.72rem;", StringComparison.Ordinal), "Left and right side panels should share body text sizing.");
+ Assert(!view.Contains("data-assets", StringComparison.Ordinal), "Asset visualisation lane should not be rendered on the main stage.");
+ Assert(script.Contains("Assets are recorded in insights/diagnostics but not rendered as stage nodes.", StringComparison.Ordinal), "Asset data should remain available without drawing asset stage nodes.");
Assert(!css.Contains("grid-template-rows: minmax(218px, 1.35fr) minmax(185px, 1.08fr) auto", StringComparison.Ordinal), "Right rail must not use fixed-height panel tracks.");
Assert(!css.Contains("grid-template-rows: auto auto minmax(170px, 1fr) minmax(96px, 0.56fr)", StringComparison.Ordinal), "Left rail must not use fixed-height panel tracks.");
Assert(script.Contains("shouldUseCharacterPortrait", StringComparison.Ordinal), "Characters should use evidence cards until portrait evidence is strong enough.");
diff --git a/PlotLine/Views/Development/StoryIntelligenceExperience.cshtml b/PlotLine/Views/Development/StoryIntelligenceExperience.cshtml
index 2cccfb5..c4a58c5 100644
--- a/PlotLine/Views/Development/StoryIntelligenceExperience.cshtml
+++ b/PlotLine/Views/Development/StoryIntelligenceExperience.cshtml
@@ -197,10 +197,6 @@
Location
-
diff --git a/PlotLine/wwwroot/css/story-intelligence-experience-prototype.css b/PlotLine/wwwroot/css/story-intelligence-experience-prototype.css
index b47920f..f29b045 100644
--- a/PlotLine/wwwroot/css/story-intelligence-experience-prototype.css
+++ b/PlotLine/wwwroot/css/story-intelligence-experience-prototype.css
@@ -16,6 +16,8 @@
--story-rose: #e38ca8;
--story-radius: 18px;
--story-shadow: 0 28px 84px rgba(0, 0, 0, 0.32);
+ --story-side-body-font-size: 0.72rem;
+ --story-side-title-font-size: 0.76rem;
--progress-value: 0deg;
}
@@ -341,7 +343,7 @@ input:focus-visible {
.story-exp-shell {
display: grid;
- grid-template-columns: minmax(180px, 0.46fr) minmax(820px, 2.72fr) minmax(188px, 0.48fr);
+ grid-template-columns: minmax(180px, 0.42fr) minmax(660px, 1.55fr) minmax(380px, 0.84fr);
gap: 12px;
min-height: 0;
padding: 12px 18px 58px;
@@ -538,7 +540,7 @@ input:focus-visible {
.story-exp-panel h2 {
margin: 0 0 5px;
- font-size: 0.82rem;
+ font-size: var(--story-side-title-font-size);
letter-spacing: 0.01em;
}
@@ -561,6 +563,7 @@ input:focus-visible {
padding: 6px 7px;
background: rgba(255, 255, 255, 0.035);
color: var(--story-soft);
+ font-size: var(--story-side-body-font-size);
min-width: 0;
overflow-wrap: anywhere;
}
@@ -778,11 +781,6 @@ input:focus-visible {
top: 22px;
}
-.story-exp-zone--assets .story-exp-zone-label {
- right: 8%;
- top: 22px;
-}
-
.story-exp-characters {
position: relative;
z-index: 120;
@@ -811,8 +809,7 @@ input:focus-visible {
}
.story-character__image,
-.story-location__image,
-.story-asset__image {
+.story-location__image {
position: relative;
z-index: 1;
display: block;
@@ -829,8 +826,7 @@ input:focus-visible {
}
.story-character__image img,
-.story-location__image img,
-.story-asset__image img {
+.story-location__image img {
width: 100%;
height: 100%;
object-fit: cover;
@@ -1027,108 +1023,6 @@ input:focus-visible {
white-space: nowrap;
}
-.story-exp-assets {
- position: relative;
- width: 100%;
- height: 100%;
-}
-
-.story-asset {
- position: absolute;
- display: grid;
- justify-items: center;
- gap: 6px;
- width: max(var(--node-size, 78px), 104px);
- opacity: var(--opacity, 1);
- transform: translate(var(--x, 0), var(--y, 0)) scale(var(--scale, 1));
- z-index: calc(var(--z, 3) + 30);
- pointer-events: auto;
- transition: transform 860ms cubic-bezier(.16, .84, .28, 1), opacity 620ms ease;
-}
-
-.story-asset__image {
- width: var(--node-size, 78px);
- height: var(--node-size, 78px);
- border-radius: 50%;
- border-color: rgba(215, 168, 93, 0.68);
- box-shadow: 0 0 0 4px rgba(215, 168, 93, 0.08), 0 18px 38px rgba(0, 0, 0, 0.28), 0 0 24px rgba(215, 168, 93, 0.13);
-}
-
-.story-asset__text {
- position: relative;
- z-index: 80;
- display: grid;
- justify-items: center;
- gap: 2px;
- width: min(140px, calc(var(--node-size, 78px) + 52px));
- border: 1px solid rgba(215, 168, 93, 0.11);
- border-radius: 10px;
- padding: 4px 7px 5px;
- background: rgba(4, 10, 20, 0.6);
- box-shadow: 0 12px 26px rgba(0, 0, 0, 0.22);
- backdrop-filter: blur(8px);
- text-align: center;
-}
-
-.story-asset.is-evidence-card {
- width: var(--node-size, 78px);
- height: var(--node-size, 78px);
-}
-
-.story-asset.is-evidence-card .story-asset__image {
- position: absolute;
- inset: 0;
- opacity: 0;
- pointer-events: none;
-}
-
-.story-asset.is-evidence-card .story-asset__text {
- position: absolute;
- inset: 0;
- align-content: center;
- justify-items: start;
- width: 100%;
- min-height: 100%;
- border-color: rgba(215, 168, 93, 0.18);
- padding: 7px 8px;
- background:
- linear-gradient(180deg, rgba(34, 29, 18, 0.88), rgba(7, 13, 21, 0.84));
- text-align: left;
-}
-
-.story-asset.is-evidence-card .story-asset__text strong,
-.story-asset.is-evidence-card .story-asset__text span {
- text-align: left;
-}
-
-.story-asset strong,
-.story-asset span {
- display: block;
- overflow: hidden;
- max-width: 100%;
- line-height: 1.08;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.story-asset strong {
- font-size: 0.78rem;
-}
-
-.story-asset span {
- color: var(--story-muted);
- font-size: 0.64rem;
-}
-
-.story-asset .story-entity-facts span {
- display: inline-flex;
- width: auto;
- max-width: 100%;
- border-radius: 999px;
- line-height: 1.2;
- text-overflow: ellipsis;
-}
-
.story-exp-ribbon-wrap {
display: grid;
grid-template-rows: auto minmax(104px, auto) 56px;
@@ -1342,7 +1236,7 @@ input:focus-visible {
display: -webkit-box;
overflow: hidden;
margin: 0;
- font-size: 0.72rem;
+ font-size: var(--story-side-body-font-size);
line-height: 1.16;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
@@ -1359,7 +1253,7 @@ input:focus-visible {
.story-exp-insight strong {
color: var(--story-text);
- font-size: 0.76rem;
+ font-size: var(--story-side-title-font-size);
line-height: 1.12;
}
@@ -1518,8 +1412,7 @@ input:focus-visible {
transform: translateY(12px) scale(0.96);
}
-.story-character.is-entering,
-.story-asset.is-entering {
+.story-character.is-entering {
opacity: 0;
transform: translate(var(--x, 0), var(--y, 0)) scale(0.82);
}
diff --git a/PlotLine/wwwroot/js/story-intelligence-experience-prototype.js b/PlotLine/wwwroot/js/story-intelligence-experience-prototype.js
index 4817325..226ab4e 100644
--- a/PlotLine/wwwroot/js/story-intelligence-experience-prototype.js
+++ b/PlotLine/wwwroot/js/story-intelligence-experience-prototype.js
@@ -110,7 +110,7 @@
const layout = sceneLayout(current);
reconcileCharacters(current, previous, layout);
reconcileLocation(current, previous);
- reconcileAssets(current, previous, layout);
+ clearAssetStage();
reconcileRelationships(current);
updateKnowledgeThreads(current);
updateSceneRibbon(current);
@@ -381,7 +381,7 @@
return [
scene.location?.imagePath,
...(scene.characters || []).map((character) => character.imagePath),
- ...(scene.assets || []).map((asset) => asset.imagePath)
+ // Assets are recorded in insights/diagnostics but not rendered as stage nodes.
].filter(Boolean);
}
@@ -490,43 +490,8 @@
node.title = `${scene.location.name}: ${scene.location.label}`;
}
- function reconcileAssets(scene, previous, layout) {
- const activeIds = new Set(scene.assets.map((asset) => asset.id));
- scene.assets.forEach((asset, index) => {
- let node = dom.assets.querySelector(`[data-asset-id="${cssEscape(asset.id)}"]`);
- if (!node) {
- node = createAssetNode(asset);
- node.classList.add("is-entering");
- dom.assets.append(node);
- settleEntering(node);
- }
-
- const position = layout.assets.get(asset.id) || assetPosition(asset, index, scene.assets.length);
- node.style.setProperty("--node-size", `${position.size}px`);
- node.style.setProperty("--x", `${position.x}px`);
- node.style.setProperty("--y", `${position.y}px`);
- node.style.setProperty("--scale", String(position.scale));
- node.style.setProperty("--z", String(position.z));
- node.style.setProperty("--opacity", "1");
- const assetImageReady = shouldUseAssetImage(asset);
- node.classList.toggle("is-evidence-card", !assetImageReady);
- setEntityImage(node.querySelector("img"), assetImageReady ? asset.imagePath : "", asset.imageResolution, `${asset.name} prototype asset`);
- text(node.querySelector("[data-asset-name]"), asset.name);
- text(node.querySelector("[data-asset-label]"), conciseAssetLabel(asset));
- renderFactChips(node.querySelector("[data-asset-facts]"), assetFacts(asset));
- node.title = `${asset.name}: ${asset.label}`;
- });
-
- dom.assets.querySelectorAll("[data-asset-id]").forEach((node) => {
- if (!activeIds.has(node.dataset.assetId)) {
- node.style.setProperty("--opacity", "0");
- node.style.setProperty("--scale", "0.8");
- window.setTimeout(() => {
- const currentIds = new Set(loadSceneState(state.index).assets.map((asset) => asset.id));
- if (!currentIds.has(node.dataset.assetId)) node.remove();
- }, reducedMotion ? 0 : 520);
- }
- });
+ function clearAssetStage() {
+ dom.assets?.replaceChildren();
}
function reconcileRelationships(scene) {
@@ -750,14 +715,6 @@
}
});
- scene.assets.forEach((asset, index) => {
- const assetNode = dom.visualStage.querySelector(`[data-asset-id="${cssEscape(asset.id)}"]`);
- if (!assetNode || !location) return;
- const id = `asset-${location.dataset.locationId}-${asset.id}`;
- activeIds.add(id);
- drawn.push(drawConnection(id, connectionPoint(location, stageRect, assetNode), connectionPoint(assetNode, stageRect, location), "evidence", asset.weight, index === 0 ? assetConnectionLabel(asset) : "", drawn.length, connectionObstacles(stageRect, location, assetNode), occupiedRects));
- });
-
removeInactiveSvgConnections(activeIds);
}
@@ -835,27 +792,12 @@
return node;
}
- function createAssetNode(asset) {
- const node = document.createElement("article");
- node.className = "story-asset";
- node.dataset.assetId = asset.id;
- node.innerHTML = `
-
- `;
- return node;
- }
-
function sceneLayout(scene) {
const bounds = dom.visualStage?.getBoundingClientRect();
const width = Math.max(760, bounds?.width || 1040);
const height = Math.max(500, bounds?.height || 560);
return {
- characters: characterLayout(scene, width, height),
- assets: assetLayout(scene, width, height)
+ characters: characterLayout(scene, width, height)
};
}
@@ -971,56 +913,6 @@
};
}
- function assetPosition(asset, index, total) {
- const bounds = dom.visualStage?.getBoundingClientRect();
- const width = Math.max(820, bounds?.width || 1040);
- const height = Math.max(500, bounds?.height || 560);
- const anchors = [
- { x: 0.83, y: 0.26 },
- { x: 0.84, y: 0.51 },
- { x: 0.82, y: 0.74 },
- { x: 0.72, y: 0.19 }
- ];
- const anchor = anchors[index % anchors.length];
- const size = Math.min(104, Math.max(68, (64 + Math.min(asset.weight, 100) * 0.42) * 0.9));
- return {
- x: Math.round(width * anchor.x - size / 2),
- y: Math.round(height * anchor.y - size / 2),
- size: Math.round(size),
- scale: 1
- };
- }
-
- function assetLayout(scene, width, height) {
- const positions = new Map();
- const total = scene.assets.length;
- const anchorsByTotal = {
- 1: [{ x: 0.83, y: 0.48 }],
- 2: [{ x: 0.83, y: 0.34 }, { x: 0.83, y: 0.62 }],
- 3: [{ x: 0.82, y: 0.25 }, { x: 0.85, y: 0.50 }, { x: 0.81, y: 0.75 }],
- 4: [{ x: 0.78, y: 0.16 }, { x: 0.90, y: 0.37 }, { x: 0.86, y: 0.62 }, { x: 0.73, y: 0.82 }],
- 5: [{ x: 0.76, y: 0.13 }, { x: 0.91, y: 0.30 }, { x: 0.80, y: 0.48 }, { x: 0.92, y: 0.66 }, { x: 0.72, y: 0.84 }]
- };
- const anchors = anchorsByTotal[Math.min(5, Math.max(1, total))] || anchorsByTotal[5];
- const placed = [];
- scene.assets.forEach((asset, index) => {
- const anchor = anchors[index] || anchors[anchors.length - 1];
- const important = index === 0 || asset.weight >= 90;
- const size = Math.round(important
- ? Math.min(104, Math.max(83, (70 + Math.min(asset.weight, 100) * 0.42) * 0.9))
- : Math.min(88, Math.max(68, (58 + Math.min(asset.weight, 100) * 0.32) * 0.9)));
- const desired = {
- x: width * anchor.x - size / 2,
- y: height * anchor.y - size / 2
- };
- const resolved = avoidNodeCollisions(desired, size, placed, width, height);
- const position = { x: resolved.x, y: resolved.y, size, scale: 1, z: important ? 4 : 3 };
- placed.push(position);
- positions.set(asset.id, position);
- });
- return positions;
- }
-
function avoidNodeCollisions(desired, size, placed, width, height) {
const padding = Math.max(16, size * 0.12);
const candidates = [
@@ -1066,11 +958,11 @@
}
function connectionPoint(node, parentRect, targetNode) {
- const image = node.querySelector(".story-character__image, .story-location__image, .story-asset__image") || node;
+ const image = node.querySelector(".story-character__image, .story-location__image") || node;
const centre = centrePoint(image, parentRect);
const rect = image.getBoundingClientRect();
const target = targetNode ? centrePoint(targetNode, parentRect) : centre;
- const targetImage = targetNode?.querySelector?.(".story-character__image, .story-location__image, .story-asset__image");
+ const targetImage = targetNode?.querySelector?.(".story-character__image, .story-location__image");
const targetCentre = targetImage ? centrePoint(targetImage, parentRect) : target;
const radius = Math.min(rect.width, rect.height) * 0.5;
const angle = Math.atan2(targetCentre.y - centre.y, targetCentre.x - centre.x);
@@ -1121,14 +1013,14 @@
}
function layoutRects(parentRect) {
- return [...dom.visualStage.querySelectorAll(".story-character__image, .story-location__image, .story-asset__image, .story-character__text, .story-location__text, .story-asset__text")]
+ return [...dom.visualStage.querySelectorAll(".story-character__image, .story-location__image, .story-character__text, .story-location__text")]
.map((node) => rectRelativeTo(node, parentRect, node.className.includes("__text") ? 8 : 10));
}
function connectionObstacles(parentRect, source, target) {
- const sourceImage = source.querySelector(".story-character__image, .story-location__image, .story-asset__image") || source;
- const targetImage = target.querySelector(".story-character__image, .story-location__image, .story-asset__image") || target;
- return [...dom.visualStage.querySelectorAll(".story-character__image, .story-location__image, .story-asset__image, .story-character__text, .story-location__text, .story-asset__text")]
+ const sourceImage = source.querySelector(".story-character__image, .story-location__image") || source;
+ const targetImage = target.querySelector(".story-character__image, .story-location__image") || target;
+ return [...dom.visualStage.querySelectorAll(".story-character__image, .story-location__image, .story-character__text, .story-location__text")]
.filter((node) => node !== sourceImage && node !== targetImage)
.map((node) => rectRelativeTo(node, parentRect, node.className.includes("__text") ? 8 : 12));
}
@@ -1659,7 +1551,7 @@
const layout = sceneLayout(current);
reconcileCharacters(current, current, layout);
reconcileLocation(current, current);
- reconcileAssets(current, current, layout);
+ clearAssetStage();
scheduleConnectionDraw();
});
@@ -1669,7 +1561,7 @@
const current = loadSceneState(state.index);
const layout = sceneLayout(current);
reconcileCharacters(current, current, layout);
- reconcileAssets(current, current, layout);
+ clearAssetStage();
scheduleConnectionDraw();
});
observer.observe(dom.visualStage);