diff --git a/frontend/src/index.css b/frontend/src/index.css index 0b82463..642ffea 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1050,14 +1050,64 @@ button:disabled { opacity: 0.45; cursor: default; transform: none; box-shadow: n .insights-mode button { padding: 2px 8px; font-size: 0.75rem; } .token-total { font-size: 1rem; color: var(--text); margin-bottom: 4px; } .token-total.over { color: var(--danger); } -.token-bar { height: 6px; background: var(--bg-input); border-radius: 3px; overflow: hidden; margin-bottom: 8px; } -.token-bar-fill { - height: 100%; - background: linear-gradient(90deg, var(--accent-dim), var(--accent-bright)); - box-shadow: 0 0 8px var(--accent-glow); - border-radius: 3px; - transition: width 0.4s ease; +/* Stacked share-of-context bar. Segment colours come from SECTION_COLORS in + Play.jsx so the bar, its legend and each section header stay in sync. */ +.token-split { + display: flex; + height: 14px; + background: var(--bg-input); + border-radius: 4px; + overflow: hidden; + margin-bottom: 8px; } +.token-split.over { box-shadow: 0 0 0 1px var(--danger); } +.token-seg { + min-width: 2px; /* a 0.2% section is still worth seeing */ + border-right: 1px solid var(--bg-panel); + transition: opacity 0.15s ease; +} +.token-seg:last-child { border-right: none; } +.token-seg-free { background: transparent; min-width: 0; border-right: none; } +.token-seg.faded, .token-legend-item.faded { opacity: 0.25; } + +.token-legend { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); + gap: 1px 12px; + margin-bottom: 10px; +} +.token-legend-item { + display: flex; + align-items: center; + gap: 7px; + background: none; + border: none; + border-radius: 4px; + padding: 2px 5px; + font-size: 0.78rem; + color: var(--text-dim); + text-align: left; + transition: opacity 0.15s ease, background 0.15s ease, color 0.15s ease; +} +.token-legend-item:hover { background: rgba(255, 255, 255, 0.05); color: var(--text); } +.token-swatch { width: 9px; height: 9px; border-radius: 2px; flex: none; } +.token-legend-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.token-legend-pct { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; } +.token-legend-tok { + width: 4.5em; + text-align: right; + opacity: 0.65; + font-variant-numeric: tabular-nums; +} +.token-legend-more { + justify-self: start; + background: none; + border: none; + padding: 2px 5px; + font-size: 0.75rem; + color: var(--text-dim); +} +.token-legend-more:hover { color: var(--accent); background: none; } .insights-history { color: var(--text-dim); margin-bottom: 8px; } .insights-cards div { margin-bottom: 3px; } .insights-cards .dropped { color: var(--danger); } @@ -1085,17 +1135,8 @@ button:disabled { opacity: 0.45; cursor: default; transform: none; box-shadow: n max-height: 260px; overflow-y: auto; } -.ctx-narrator, .ctx-ai_instructions { border-left-color: #7d8fc9; } -.ctx-narrator .ctx-header, .ctx-ai_instructions .ctx-header { color: #7d8fc9; } -.ctx-plot_essentials, .ctx-script_context { border-left-color: #c97dc0; } -.ctx-plot_essentials .ctx-header, .ctx-script_context .ctx-header { color: #c97dc0; } -.ctx-story_summary { border-left-color: #7dc9a2; } -.ctx-story_summary .ctx-header { color: #7dc9a2; } -.ctx-world_lore { border-left-color: #c9b47d; } -.ctx-world_lore .ctx-header { color: #c9b47d; } -.ctx-history, .ctx-recent_history { border-left-color: #6a6a78; } -.ctx-authors_note, .ctx-front_memory { border-left-color: #c97d7d; } -.ctx-authors_note .ctx-header, .ctx-front_memory .ctx-header { color: #c97d7d; } +/* Per-section border/header colours are applied inline from SECTION_COLORS + (Play.jsx) — one palette drives the bar, the legend and these headers. */ /* ---------- Scripts ---------- */ diff --git a/frontend/src/pages/Play.jsx b/frontend/src/pages/Play.jsx index b280b24..6a2e852 100644 --- a/frontend/src/pages/Play.jsx +++ b/frontend/src/pages/Play.jsx @@ -51,8 +51,40 @@ const SECTION_LABELS = { authors_note: "Author's Note", recent_history: 'Recent history', front_memory: 'Front memory', + length_hint: 'Length guidance', + world_state_reminder: 'World State (emit reminder)', } +// One colour per context section, and the single source of truth for it: the +// token bar, the legend and each section's own header all read from here, so a +// slice of the bar and the text it stands for always carry the same colour. +// Related sections share a hue family but never an exact shade — in a stacked +// bar two identical colours read as one section. +const SECTION_COLORS = { + narrator: '#7d8fc9', + ai_instructions: '#9c8fd6', + plot_essentials: '#c97dc0', + script_context: '#d99ad0', + story_summary: '#7dc9a2', + used_memories: '#5fb8c9', + world_lore: '#c9b47d', + world_state: '#d79a63', + world_state_guide: '#b8834a', + world_state_rule: '#9d7a52', + world_state_reminder: '#8a6f52', + history: '#74748c', + recent_history: '#9d9db4', + authors_note: '#c97d7d', + front_memory: '#d99a9a', + length_hint: '#98a06b', +} +const SECTION_FALLBACK = '#6a6a78' +const sectionColor = (label) => SECTION_COLORS[label] || SECTION_FALLBACK + +// Share of the prompt, rounded for glanceability. Sections too small to round +// to a whole percent still say so rather than showing a misleading 0%. +const pctLabel = (pct) => (pct > 0 && pct < 1 ? '<1%' : `${Math.round(pct)}%`) + const FIELD_LABELS = { memory: 'Plot Essentials (Memory)', authors_note: "Author's Note", @@ -1044,12 +1076,16 @@ function ScriptReport({ script }) { )} {script.context_changed && ( <> -
{script.context_before}
{script.context_after}
{s.text}