Redesign Media Centre as a teletext broadcast information service

Replaces the neutral dark card-based theme with "Page 100": teletext's
locked eight colours at full saturation on pure black, numbered pages
reachable by typing the number (or the r/g/y/c fastext keys), and no
borders, boxes, or radius anywhere — grouping is carried by full-bleed
colour fields and row weight instead.

Chosen via the impeccable skill's replacement-world flow: seven visual
candidates derived from the audience's own world, a mandatory roll
assigned Ceefax/US-cable-guide teletext, six catalog challengers weighed
against it, and the user locked the assigned direction on the decision
page (seed aab4510e).

- New templates for all nine pages (home/watch/library/discover/
  requests/system/activity/login/settings) behind a shared _shell.html
  carrying the direction contract and _macros.html for the row/bar/tile
  vocabulary; deletes the old single-page dashboard.html and its
  dedicated stylesheets/script.
- static/css/base.css + teletext.css: the locked palette, a two-height
  type scale (Chivo Mono / DotGothic16), and the five disciplines the
  system is held to (no in-between tone, no enclosure, one shared bar
  ruler, trend only where real history exists, pages weighted by state).
- static/js/service.js: header clock, page-number jump, fastext keys,
  two-tap arm/confirm add, guarded live polling — replaces dashboard.js.
- app.py: nav_items now carry page numbers; fastext defaults added to
  the page-chrome context processor; version bumped to 2.0.0.
- DESIGN.md + .impeccable/design.json regenerated from the built world
  by the documenter pass; PRODUCT.md and CLAUDE.md updated to describe
  the new system in place of the retired one.
- Fixed a live bug found along the way: settings.html called
  url_for('dashboard'), an endpoint removed earlier in the branch, which
  would have 500'd the Settings page.
- Fixed a One-Ruler violation the finish review's recapture request
  surfaced: --bar-cells disagreed with the bar() macro's hardcoded cell
  count below the 760px breakpoint, so a fill could land mid-cell on a
  phone. Removed the mobile override; the ruler is now 20 cells at every
  width, matching the macro unconditionally.

Passed two inspection rounds (desktop + true-390px mobile, via an
iframe harness since macOS Chrome clamps --window-size) and a finish
review; QA screenshots and interview logs are gitignored as scratch,
not shipped in the repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBw1QodG8ZMYHrGeQfqyDS
This commit is contained in:
Barely Removable 2026-08-29 21:00:45 -07:00
parent 9a277b9031
commit 4b19cbdfb2
25 changed files with 3002 additions and 2343 deletions

View file

@ -1,75 +1,151 @@
/* ═══════════════════════════════════════════════════════
Media Centre — shared design tokens + base reset.
MEDIA CENTRE — "Page 100"
Single source of truth for the palette. Loaded by
dashboard.html, login.html and settings.html; page-specific
rules live in dashboard.css / login.css / settings.css.
A broadcast information service, not a dashboard. The world
is teletext (Ceefax, and the cable guide channel that did the
same job in America): a locked eight-colour palette on pure
black, one blocky monospace at exactly two heights, a strict
character-cell grid, and grouping carried by colour fields and
row weight — never by a border, never by a box.
To reshade the whole app, change the four hues below —
every dim/border variant is derived from them via color-mix.
Five disciplines this world is held to. Breaking one is a
regression, not a variation:
1. NO IN-BETWEEN TONE. There are eight colours. A mid-grey is
an ordered dither of two of them (--dither-*), never a
ninth colour smuggled into the palette.
2. NO ENCLOSURE. Nothing is ever surrounded by a border or a
box. Grouping is a coloured field row, indentation, and
weight. This is the exact reflex the old design was built
on and the main thing this world refuses.
3. ONE RULER. Every bar, meter and percentage on every page
shares one scale (--bar-cells), so 60% on the array and
60% on an indexer are the same width and compare directly.
4. EVERY READING CARRIES ITS DIRECTION. A value shows where
it is heading, so a disk becoming a problem is visible
before it is one.
5. PAGES DIFFER IN STOCK. A page holding critical state is
set heavier than a quiet one. Uniform texture across seven
pages is a failure.
═══════════════════════════════════════════════════════ */
:root {
/* ── Surfaces ── */
--bg: #141416;
--surface: #1a1a1d;
--surface-2: #202024;
--border: #2c2c31;
--border-lt: #38383f;
/* ── The locked eight ──────────────────────────────────
Teletext had exactly these, at full saturation, and the
product's semantics were already the same semantics:
green runs, yellow works, red is off air. Do not soften
them; the emissive-on-black scene is what they are for. */
--tt-black: #000000;
--tt-red: #ff0000;
--tt-green: #00ff00;
--tt-yellow: #ffff00;
--tt-blue: #0000ff;
--tt-magenta: #ff00ff;
--tt-cyan: #00ffff;
--tt-white: #ffffff;
/* ── Text (all ≥ 4.5:1 on every surface above) ── */
--text: #edecf1;
--text-2: #a8a7b0;
--text-3: #7c7b84;
/* ── Roles ─────────────────────────────────────────────
Each colour means one thing across all nine pages. */
--ink: var(--tt-white); /* body text */
--voice: var(--tt-yellow); /* the service speaking: mastheads, page numbers */
--live: var(--tt-cyan); /* live values, now showing */
--ok: var(--tt-green); /* running, healthy, available */
--alert: var(--tt-red); /* off air, failed, danger */
--pending: var(--tt-magenta); /* requested, waiting on a human */
--field: var(--tt-blue); /* the only "background" colour — header fields */
/* ── Accent + semantics (the four source hues) ── */
--accent: #7c86ff;
--ok: #68b98a;
--warn: #d0a75a;
--danger: #dc5b52;
/* ── Dither ────────────────────────────────────────────
The only legal way to make a tone that isn't one of the
eight. A checkerboard of the colour against black at cell
scale, exactly how a 16-colour screen faked a gradient.
Used for inactive bar cells and secondary text grounds. */
--dither-size: 2px;
/* --ink-dim is the ONLY dimmed value allowed to carry text: at 58% it
clears 4.5:1 on black. --ink-dimmer is for graphics only — bar
troughs, the dither field, the separator — because at 30% it does
not, and secondary text set in it was failing contrast outright. */
--ink-dim: color-mix(in srgb, var(--tt-white) 58%, var(--tt-black));
--ink-dimmer: color-mix(in srgb, var(--tt-white) 30%, var(--tt-black));
/* ── Derived tints — never hardcode these as literals ── */
--accent-dim: color-mix(in srgb, var(--accent) 14%, transparent);
--ok-dim: color-mix(in srgb, var(--ok) 14%, transparent);
--warn-dim: color-mix(in srgb, var(--warn) 14%, transparent);
--danger-dim: color-mix(in srgb, var(--danger) 14%, transparent);
--danger-border: color-mix(in srgb, var(--danger) 35%, transparent);
/* ── Type: one face, exactly two heights ───────────────
Teletext's entire scale. Emphasis comes from colour and
from doubling the height, never from a weight ramp and
never from a third size. */
--fs: 0.9375rem; /* 15px — the single body size */
--fs2: 1.875rem; /* 30px — double height, the only other size */
--lh: 1.45;
--row: calc(var(--fs) * var(--lh)); /* one character row */
--cell: 1ch;
/* ── Type scale (6 steps, no one-offs) ── */
--fs-xs: 0.75rem; /* 12px — smallest permitted */
--fs-sm: 0.8125rem; /* 13px */
--fs-base: 0.9375rem; /* 15px */
--fs-lg: 1.125rem; /* 18px */
--fs-xl: 1.5rem; /* 24px */
--fs-2xl: 2rem; /* 32px */
--font: 'Chivo Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
--font-dh: 'DotGothic16', 'Chivo Mono', ui-monospace, Menlo, monospace;
/* ── Spacing / shape ── */
--gap: 1rem;
--gap-lg: 1.5rem;
--radius: 10px;
--radius-sm: 7px;
--tap: 44px; /* minimum touch target */
--row-h: 4.25rem; /* paired Requests / Indexers row height */
/* ── Measure ───────────────────────────────────────────
A teletext page was 40 columns. A screen this wide gets
double that, so density survives without the grid dying. */
--page-cols: 92ch;
--gutter: 1.5rem;
--font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
/* ── The one ruler (discipline 3) ──────────────────────
Every bar on every page is this many cells wide, so any
two percentages anywhere in the service are comparable. */
--bar-cells: 20;
--bar-cell-w: 0.62em;
--tap: 44px;
}
*, *::before, *::after { box-sizing: border-box; }
html { background: var(--tt-black); }
body {
margin: 0;
background: var(--bg);
color: var(--text);
background: var(--tt-black);
color: var(--ink);
font-family: var(--font);
font-size: var(--fs-base);
line-height: 1.5;
font-size: var(--fs);
line-height: var(--lh);
font-variant-numeric: tabular-nums;
/* Teletext never anti-aliased into a soft grey edge. Keep the
letterforms crisp rather than letting the browser blend them
toward tones the palette does not contain. */
-webkit-font-smoothing: antialiased;
text-rendering: optimizeSpeed;
}
a { color: var(--accent); }
/* Phosphor bloom. Applied only where the world is speaking loudly —
double-height rows and status glyphs — because bloom under a dense
table of figures costs more legibility than it buys character. */
.bloom {
text-shadow: 0 0 0.4em color-mix(in srgb, currentColor 55%, transparent);
}
a { color: var(--live); text-decoration: none; }
a:hover { color: var(--tt-black); background: var(--live); }
/* Focus is a reversed cell, the way a teletext menu row selected
itself. No outline ring — a ring is an enclosure. */
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: 4px;
outline: none;
background: var(--voice);
color: var(--tt-black);
}
::selection { background: var(--live); color: var(--tt-black); }
/* Browser furniture, themed to the palette rather than left at the
defaults that belong to no design system. */
* { scrollbar-color: var(--ink-dimmer) var(--tt-black); scrollbar-width: thin; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--tt-black); }
::-webkit-scrollbar-thumb { background: var(--ink-dimmer); }
::-webkit-scrollbar-thumb:hover { background: var(--ink-dim); }
::-webkit-scrollbar-corner { background: var(--tt-black); }
input { caret-color: var(--voice); }
.visually-hidden {
position: absolute; width: 1px; height: 1px;
padding: 0; margin: -1px; overflow: hidden;
clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}