web: the dashboard page, slice 3 of M3

The milestone 2 parity page becomes the spec's dashboard: app bar with
wordmark, disclaimer badge and About popover, theme toggle (persisted,
defaults to prefers-color-scheme, applied pre-mount to avoid a flash),
hero with the preset narrative and live toned percentages, scenario
toolbar, panel cards with skeleton loading and dim-while-refreshing,
legend, and footer. ComparisonTable evolves into the collapsed
ResultsTable (test moved along, plus a formatPct rounding case).

NetworkView renders the real topology with the spec's shape encodings.
The layout is seeded d3-force (mulberry32 random source, seeded initial
positions, 300 synchronous ticks, cached per graph hash); anisotropic
forceX/forceY pulls settle the cloud into the wide card shape so the
fit stays uniform-scale and the spacing organic. Justin asked for a
taller network area (380x230) and a reading caption under the legend;
the caption is study copy, so StudyPreset gains a readingCaption field.

Slices still to come keep their controls visibly parked: Export and the
panel kebab render disabled until slices 11 and 7.
This commit is contained in:
Justin Visser 2026-06-10 16:55:14 +02:00
parent 2f21248c20
commit 71baf10e73
28 changed files with 1766 additions and 300 deletions

View file

@ -1,10 +1,25 @@
<!DOCTYPE html>
<html lang="">
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>spreadlab</title>
<script>
// Apply the persisted (or preferred) theme before the app mounts so
// dark mode never flashes light. Kept in sync with useTheme.ts.
try {
var storedTheme = localStorage.getItem('spreadlab-theme')
if (
storedTheme === 'dark' ||
(storedTheme !== 'light' && matchMedia('(prefers-color-scheme: dark)').matches)
) {
document.documentElement.classList.add('dark')
}
} catch (storageError) {
/* default to light */
}
</script>
</head>
<body>
<div id="app"></div>