From 46e7e63df75c2c1418d49a34fc6a7076ccf77c64 Mon Sep 17 00:00:00 2001 From: Babissimo Date: Thu, 24 Sep 2026 16:50:38 +0100 Subject: [PATCH] docs(brand): describe the console as one surface at app.retina.fm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit retina-server retired dash., map. and data.retina.fm in mid-September (#426, #433) and has since folded the map and the data explorer into the dashboard bundle as pages, served from the app host's root with one shared palette and component file (packages/shared/css). The guide still described six surfaces with three separate console-family columns, so most of what it said about the console had stopped being true: the map is no longer dark by default or Inter-only, it follows the console's three-state theme, its app-shell header is gone, and the data explorer sits in the console's sidebar layout rather than under its own top bar. The guide now has four surfaces. The console's palette columns are its light and dark halves, and the map keeps a column of its own only where it still departs from the rest of the console (layout, toolbar, toasts, focus ring, data palette). The shared ui.css also closed several §10 gaps (a shared input rule, one disabled rule for every button, six badge variants, notice banners in place of alert(), a shared Pager and chart theme), so those entries are gone; the ones that still hold (the phone layout, the one remaining confirm(), the map's lighter sunk tier) are restated against the current code. tokens.css follows: .dash, .data and .map become a .console pair plus a .console.map modifier, and preview.html renders the pair from them. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- docs/brand/brand-guide.md | 1076 ++++++++++++++++++------------------- docs/brand/preview.html | 64 +-- docs/brand/tokens.css | 204 +++---- 4 files changed, 636 insertions(+), 710 deletions(-) diff --git a/README.md b/README.md index 8fb70b7..31bcbe4 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ point at these rather than duplicating them, so there's one source of truth. And each subdirectory: - **`docs/brand/`** — a snapshot of how the surfaces look today (offworldlabs.com, - retina.fm, dash.retina.fm, and the dark map.retina.fm): the shared foundations + retina.fm, the console at app.retina.fm, and the node's own UI): the shared foundations and the per-surface differences, so a new page can match the existing design. Ships a `tokens.css` starter alongside the guide. Descriptive, not prescriptive. - **`docs/contracts/`** — the source of truth for cross-service interfaces: API diff --git a/docs/brand/brand-guide.md b/docs/brand/brand-guide.md index e20f7fb..4f716b2 100644 --- a/docs/brand/brand-guide.md +++ b/docs/brand/brand-guide.md @@ -9,34 +9,49 @@ > Companion file: [`tokens.css`](tokens.css) holds the same values as CSS custom > properties, so a page can consume them instead of transcribing hex codes. -The design lives in six hand-authored surfaces, none of which shares a +The design lives in four hand-authored surfaces, none of which shares a stylesheet with the others: -- **offworldlabs.com** ([`landing-page-owl`](https://github.com/offworldlabs/landing-page-owl)) — the lab -- **retina.fm** ([`landing-page-retina`](https://github.com/offworldlabs/landing-page-retina)) — the product -- **dash.retina.fm** (`retina-server/dashboard`) — the admin console -- **data.retina.fm** (`retina-server/data-explorer`) — the public archive browser -- **map.retina.fm** (`retina-server/frontend`) — the live radar map -- **owl.local** ([`retina-gui`](https://github.com/offworldlabs/retina-gui)) — the node's own UI - -They read as one family, but each has its own voice. Two sit apart: the map is -the only one dark by default, a Flightradar24-style operational console; owl-os -is the only one that runs on hardware in someone's house rather than on a -server. - -**Three of them theme, on one palette.** The map has offered light beside its -default dark for longest; dash and data now carry both halves of that same pair -rather than inventing a ramp of their own, and follow the OS unless told -otherwise (§3). The other three are light only. `admin.retina.fm` is dash's own -bundle behind a role check rather than a seventh surface, so it themes with it -and is not listed separately anywhere below. - -**A fourth surface runs the pair from outside `retina-server`.** Tower Finder -(`tower-finder-service/frontend`, the standalone illuminator search) took dash's -palette, token names and appearance switch wholesale rather than growing a look -of its own. It has no column in the tables below, and gets no voice of its own -in §1: it is dash's, on another host. Where §3 and §6 count the themed surfaces -or describe the switch, it is one of them. +- **offworldlabs.com** ([`landing-page-owl`](https://github.com/offworldlabs/landing-page-owl)): the lab +- **retina.fm** ([`landing-page-retina`](https://github.com/offworldlabs/landing-page-retina)): the product +- **app.retina.fm** (`retina-server/dashboard`): the console, holding the live + radar map at `/map` (where `/` lands), the public archive browser at `/data`, + and the node owner's pages +- **owl.local** ([`retina-gui`](https://github.com/offworldlabs/retina-gui)): the node's own UI + +They read as one family, but each has its own voice. Two sit apart: the console +is the only one that goes dark, and owl-os is the only one that runs on hardware +in someone's house rather than on a server. + +**The console is one bundle on one origin.** Its pages, the map and the data +explorer are routes of a single SPA, reading one palette and one component file +(`packages/shared/css/tokens.css` and `ui.css` in retina-server). They share a +hostname because the session cookie is host-only, so a sign-in covers only the +origin that set it. The retired names `dash.`, `map.` and `data.retina.fm` are +Cloudflare redirects into the matching path on `app.retina.fm`. +`admin.retina.fm` is the same bundle again, with the admin route table chosen by +hostname, so it looks like the console and is not listed separately anywhere +below. + +**The map is a page of the console with a voice of its own.** It sits inside +the console's chrome (sidebar, header, palette, type) and draws a full-bleed +operational view beneath it, Flightradar24-style: controls floating over the +basemap with a shadow, a data palette chosen by measurement, toasts, a compact +toolbar. Where the map departs from the rest of the console, the tables below +give it a column of its own; everywhere else "the console" includes it. + +**Only the console themes.** It carries one light/dark pair and follows the OS +unless told otherwise (§3), and the map wears whichever half the rest of the +console is wearing. The other three surfaces are light only. + +**A copy of the console's look runs outside `retina-server`.** Tower Finder +(`tower-finder-service/frontend`, the standalone illuminator search at +`towers.retina.fm`) took the console's palette, token names and appearance +switch rather than growing a look of its own, and sets them in Inter rather than +the system stack. It holds a copy rather than importing the shared package, +since the two build separately. It has no column in the tables below and gets +no voice of its own in §1; where §3 and §6 describe the themed cascade or the +switch, it is included. owl-os is the odd repository as well as the odd surface. The UI is authored in [`retina-gui`](https://github.com/offworldlabs/retina-gui) but shipped by @@ -65,82 +80,81 @@ blah2 rather than to any surface described here. --- -## 1. Brand architecture: one family, six voices +## 1. Brand architecture: one family, five voices The surfaces share a spine (a blue accent, green/amber status colours, a small uppercase micro-label, hairline rules) and then diverge in personality. The split -maps onto audience. Three of the six are light and nothing else; the other -three carry both halves of one palette pair, and differ in what decides which -half you get (§3). - -| | **owl** — offworldlabs.com | **retina** — retina.fm | **dash** — dash.retina.fm | **data** — data.retina.fm | **map** — map.retina.fm | **owl-os** — owl.local | -|---|---|---|---|---|---|---| -| **Role** | The lab / parent org | The product you buy | The console you operate | The archive you fetch from | The live picture | The node you own | -| **Audience** | Researchers, funders, press | Prospective node owners | Logged-in operators | Anyone wanting the data | Anyone watching the map | Whoever has the box | -| **Feel** | Editorial, institutional | Commercial, confident | Utilitarian, dense | Utilitarian, dense | Dark ops console (FR24-style) | Appliance settings, unhurried | -| **Serif** | Source Serif 4, wt 400 | Fraunces, wt 600 | none | none | none | none | -| **Sans** | Inter | DM Sans | system stack | system stack | Inter (only font) | Inter | -| **Mono** | IBM Plex Mono | JetBrains Mono | SF Mono | SF Mono | none (generic) | JetBrains Mono | -| **Canvas** | warm paper `#f7f6f2` | warm paper `#fafaf9` | cool slate `#f1f5f9`, dark `#0d1b2a` | cool slate `#f1f5f9`, dark `#0d1b2a` | **navy `#0d1b2a`**, light `#f1f5f9` | near-white warm `#fffdfb` | -| **Blue** | muted `#5b8dd9` | punchy `#2563eb` | `#3b82f6` → `#2563eb`, dark `#38bdf8` | `#3b82f6` → `#2563eb`, dark `#38bdf8` | sky `#38bdf8`, light `#3b82f6` | `#2f8adc`, accents only | -| **Radius** | 4–6px | 8–20px | 4–8px | 4–8px | 2–12px | 6–14px | -| **Buttons** | flat | lift + shadow | flat | flat | flat | flat, primary is ink | -| **Dark UI** | none | one dark section | **follows the OS**, the map's palette | **follows the OS**, the map's palette | **dark by default**, light opt-in | none | +maps onto audience. The console speaks in two registers, its pages and its map, +so the map has a column of its own here. Three of the four surfaces are light +and nothing else; the console carries both halves of one palette pair (§3). + +| | **owl** · offworldlabs.com | **retina** · retina.fm | **console** · app.retina.fm | **map** · app.retina.fm/map | **owl-os** · owl.local | +|---|---|---|---|---|---| +| **Role** | The lab / parent org | The product you buy | The console you operate, and the archive you fetch from | The live picture | The node you own | +| **Audience** | Researchers, funders, press | Prospective node owners | Signed-in node owners and operators; anyone on the archive | Anyone watching the map | Whoever has the box | +| **Feel** | Editorial, institutional | Commercial, confident | Utilitarian, dense | Operational map (FR24-style) under the console's chrome | Appliance settings, unhurried | +| **Serif** | Source Serif 4, wt 400 | Fraunces, wt 600 | none | none | none | +| **Sans** | Inter | DM Sans | system stack | the console's | Inter | +| **Mono** | IBM Plex Mono | JetBrains Mono | SF Mono | the console's | JetBrains Mono | +| **Canvas** | warm paper `#f7f6f2` | warm paper `#fafaf9` | cool slate `#f1f5f9`, dark navy `#0d1b2a` | the console's | near-white warm `#fffdfb` | +| **Blue** | muted `#5b8dd9` | punchy `#2563eb` | `#3b82f6` → `#2563eb`, dark `#38bdf8` | the console's, plus an amber selection | `#2f8adc`, accents only | +| **Radius** | 4–6px | 8–20px | 3–16px, 999px chips | 2–12px | 6–14px | +| **Buttons** | flat | lift + shadow | flat | flat, a compact toggle | flat, primary is ink | +| **Dark UI** | none | one dark section | **follows the OS**, three-state switch | the console's theme | none | A rough rule of thumb for a new page: **who is it for?** Reaching outward to the scientific or funding world reads as owl. Selling or explaining the kit reads as -retina. Anything behind a login reads as dash. Anything that hands the public -the archive reads as data, which is dash's vocabulary without the login and -without the sidebar. Anything that _is_ the live radar picture (a map, a track -view, an operational overlay) reads as map. Anything a node owner does to their -own hardware reads as owl-os. - -dash and owl-os are worth telling apart, since both are settings pages for the -same hardware. dash is an operator holding a fleet at arm's length: dense, -tabular, many rows on one screen, always behind a login. owl-os is one person and -one box on their own network, reached without a password, so it runs a 720px -column, one decision per row, and explains each in a sentence underneath. +retina. Anything behind a login, or anything that hands the public the archive, +reads as the console. Anything that _is_ the live radar picture (a map, a track +view, an operational overlay) reads as the map. Anything a node owner does to +their own hardware reads as owl-os. + +The console and owl-os are worth telling apart, since both are settings pages for +the same hardware. The console holds nodes at arm's length: dense, tabular, many +rows on one screen, and behind a sign-in for anything beyond the public map, +archive, leaderboard and knowledge pages. owl-os is one person and one box on +their own network, reached without a password, so it runs a 720px column, one +decision per row, and explains each in a sentence underneath. --- ## 2. Foundations -The parts that stay roughly constant across all six, and are the safest thing +The parts that stay roughly constant across all four, and are the safest thing to carry into new work. -- **A paper-like canvas.** Three of the six are near-white and only near-white +- **A paper-like canvas.** Three of the four are near-white and only near-white (warm on the marketing sites, `#fffdfb` on owl-os, which pushes furthest - toward white). dash and data are cool slate on a light OS and navy on a dark - one; the **map is navy** whatever the OS says. So "light paper" is the norm - for anything that explains or sells, and the three surfaces that operate the - network are the three that can go dark (§3). -- **A blue accent.** owl sits soft and desaturated, retina and dash land on the - brighter `#2563eb`/`#3b82f6`, owl-os lands between them at `#2f8adc`, and the - map goes brighter still (sky `#38bdf8`) to carry on dark, dropping back to - dash's `#3b82f6` when it is themed light. Blue is the only chromatic accent in - the UI chrome; everything else is ink on canvas. (The map adds an amber - `#fbbf24` as a selection/focus highlight.) owl-os spends its blue - the most sparingly: the primary button is ink, and the accent is kept for focus - rings, hover borders, selection washes and the one card that is _this_ node. - data spends dash's blue on light and the map's on dark, adding none of its own. + toward white). The console is cool slate on a light OS and navy on a dark + one, the map included. So "light paper" is the norm for anything that explains + or sells, and the surface that operates the network is the one that can go + dark (§3). +- **A blue accent.** owl sits soft and desaturated, retina and the console land + on the brighter `#2563eb`/`#3b82f6`, owl-os lands between them at `#2f8adc`, + and the console's dark half goes brighter still (sky `#38bdf8`) to carry on + navy. Blue is the only chromatic accent in the UI chrome; everything else is + ink on canvas. (The map adds amber for the selected track.) owl-os spends its + blue the most sparingly: the primary button is ink, and the accent is kept for + focus rings, hover borders, selection washes and the one card that is _this_ + node. - **Green and amber as status colours.** Green marks "live" / validated / ADS-B - truth; amber marks the radar echo, anomalies, and attention. The consoles add + truth; amber marks the radar echo, anomalies, and attention. The console adds red for outright errors. (The exact shades drift between surfaces; see §10.) - **A small uppercase micro-label.** Small, uppercase, letter-spaced, muted. Used for section eyebrows, stat labels, table headers, nav-section titles, and diagram annotations. This is the single most reused idiom and the quickest way - to make a new page read as "ours". All six surfaces have it, and only the two - marketing sites set it in their mono. dash, data, the map and owl-os keep the - uppercase-and-tracked treatment in their sans: the two consoles because dash - never spent its mono on it (stat labels, table headers and nav-section titles - are all the system stack, and data copied that), the map because it has no - mono, and owl-os by choice, having JetBrains Mono and spending it on data - instead. + to make a new page read as "ours". All four surfaces have it, and only the two + marketing sites set it in their mono. The console and owl-os keep the + uppercase-and-tracked treatment in their sans: the console because it never + spent its mono on it (stat labels, table headers and nav-section titles are all + the system stack), and owl-os by choice, having JetBrains Mono and spending it + on data instead. - **Hairline rules and grid dividers.** 1px borders at low contrast separate sections, table rows, fact lists, and card grids. Structure comes from lines, not shadows or fills. - **Flat and quiet.** Minimal ornamentation, few shadows (retina uses a subtle - button lift; dash reserves shadow for one dropdown), small status dots for + button lift; the console keeps shadow for what floats: the header dropdown, + two of the data explorer's panels and what floats over the map), small status dots for liveness. owl-os is the one partial exception: its cards carry a hairline shadow at rest and brighten their border plus deepen the shadow on hover, which is how a whole card reads as clickable without a border change alone carrying @@ -155,76 +169,79 @@ to carry into new work. ## 3. Colour Values are grouped by role. Per-surface specifics sit in the columns; foundation -roles (semantics) are shared unless a surface overrides them. +roles (semantics) are shared unless a surface overrides them. The console takes +two columns, one per half of its pair. ### Canvas and ink -| Role | owl | retina | dash / data | map | owl-os | +| Role | owl | retina | console, light | console, dark | owl-os | |---|---|---|---|---|---| | Canvas | `#f7f6f2` | `#fafaf9` | `#f1f5f9` | `#0d1b2a` | `#fffdfb` | | Sunk surface | `#edecea` | `#f5f4f0` | `#f1f5f9` (= canvas) | `#0f2035` | `#fbfaf8` | | Card / panel | `#ffffff` | `#ffffff` | `#ffffff` | `#132240` | `#ffffff` | -| Dark section | — | `#090904` / `#242422` | — | (the dark theme) | — | +| Dark section | n/a | `#090904` / `#242422` | n/a | (the whole surface) | n/a | | Ink (primary) | `#0e0e0c` | `#1a1a18` | `#0f172a` | `#e2e8f0` | `#13161c` | | Ink (muted) | `#444440` | `#6b6b63` | `#475569` | `#94a3b8` | `#494d54` | | Ink (subtle) | `#888882` | `#9c9c93` | `#94a3b8` | `#64748b` | `#83868c` | | Border | `rgba(14,14,12,.1)` | `#e8e8e3` | `#e2e8f0` | `rgba(100,180,255,.14)` | `#eae7e4` | -**dash and data share a column because they share every value in it.** data was -built by copying dash's token block, and the two have not diverged; where the -rest of this guide says "dash" about a colour, it is true of data as well. - -The marketing canvases are warm (a hint of yellow); the consoles are cool slate; -the map inverts to dark navy. The map's ink ramp is the dark mirror of the -console's slate ramp (`#94a3b8` / `#64748b` recur), and its hairlines are a -blue-tinted translucent white rather than a solid grey. - -**The map's column above is its dark theme, which is the default there.** These -are not two palettes but one pair, and four surfaces now run it. The map's -light theme takes dash's values almost wholesale (§10 has the one exception), -and dash, data and tower-finder carry the same pair with the halves the other -way up. tower-finder is the newest of them and the only one outside -`retina-server`, taking the pair and the control together from dash. - -**What decides which half you get is not the same question on all four**, and -it is the part most easily got wrong. dash, data and tower-finder **follow the -OS**: their control has three states, and the third, `system`, is the default -and the one a viewer who has never touched the control is on. It stamps no attribute at all +**The two console columns are one pair, held in one file.** +`packages/shared/css/tokens.css` is the source in retina-server, read by every +console page and by the map, so the map carries no chrome palette of its own. Three +consumers that cannot read a custom property hold values written out, each held +to the file by a test: the chart theme (Semantics, below), the map's data +palette (for Leaflet and SVG) and the API reference's Scalar theme. The map's +chrome adds a handful of tokens beside the pair (`--panel-shadow`, +`--map-chip-*`, `--violet`) and overrides `--tile-filter` and, on the light +half, `--bg-sunk`, in `dashboard/src/pages/map/map-surface.css`. tower-finder +holds a hand copy of the pair. + +The marketing canvases are warm (a hint of yellow); the console is cool slate, +inverting to dark navy. The dark ink ramp is the mirror of the light slate ramp +(`#94a3b8` / `#64748b` recur), and the dark hairlines are a blue-tinted +translucent white rather than a solid grey. + +**What decides which half you get** is the viewer, falling back to the OS. The +control has three states, and the third, `system`, is the default and the one a +viewer who has never touched the control is on. It stamps no attribute at all and lets a `prefers-color-scheme` block answer, so the preference keeps working -when the OS changes its mind mid-session — where stamping a resolved value -would pin the surface to whatever the OS happened to be at load. The map -**never asks the OS**: its control is a boolean, and dark is what a first visit -gets on a machine set to light. - -What all four share is the cascade rule. A surface puts on its bare selector -the half it must be able to paint before any JavaScript runs, and spends -`data-theme` on the other: dark on the map, light on the three consoles. The +when the OS changes its mind mid-session, where stamping a resolved value would +pin the surface to whatever the OS happened to be at load. The map has no +control of its own: it takes the console's resolved theme, so a first visit on +a machine set to light gets a light map. tower-finder behaves the same way with +its own copy of the control. + +The cascade follows from that. Light sits on the bare selector, because the +default must paint before any JavaScript runs, and `data-theme` buys dark: the attribute is stamped from JavaScript, so whichever half depends on it is the one -that can flash the other before first paint. On the three the media query is -guarded `:not([data-theme="light"])`, which is what lets an explicit light -choice beat a dark OS. The cost of the third state is the dark half written -twice, once per selector, since CSS cannot share a declaration block across a -media query boundary; dash and tower-finder each guard the two copies against -drifting with a test rather than with the stylesheet. Everything on all four is -written against the custom -properties, so the whole chrome inverts from one block. - -Two tokens exist because of that sharing. - -**`--bg-sunk` is the third surface tier**, and all three themed surfaces carry -it. On a light surface a recessed region can be made by letting the canvas show -through, the canvas already being darker than a card; the dark ramp inverts, -card lighter than canvas, so a recessed pane needs a colour of its own at -`#0f2035`. It pays for the map's left aircraft list and, on the Physics tab, the -scene, ground-truth and solver sections, and for data's appearance switch. The -light half of it is the one value the three do not agree on (§10). - -**`--accent-ink` is what sits _on_ the accent** rather than beside it, and is -dash's and data's alone — the map defines none. It is a token and not a literal -white because the dark accent is a bright sky blue, on which white is about -1.8:1; there it becomes a near-black `#082f49`. Any solid accent fill carrying -text — a primary button, the sidebar mark — needs it, so a component moved onto -the map has to bring its own answer. +that can flash the other before first paint. The media query is guarded +`:not([data-theme="light"])`, which is what lets an explicit light choice beat a +dark OS. The cost of the third state is the dark half written twice, once per +selector, since CSS cannot share a declaration block across a media query +boundary; the console (in `packages/shared`) and tower-finder each guard the two +copies against drifting with a test rather than with the stylesheet. The map +element's own blocks are keyed the other way round, dark unless +`data-theme="light"`, which is harmless because the map always carries the +console's resolved theme as its attribute. Everything is written against the +custom properties, so the whole chrome inverts from one block. + +Two tokens exist because of that pairing. + +**`--bg-sunk` is the third surface tier.** On the light half a recessed region +can be made by letting the canvas show through, the canvas already being darker +than a card; the dark ramp inverts, card lighter than canvas, so a recessed pane +needs a colour of its own at `#0f2035`. It pays for the map's left aircraft +list, the Physics page's scene, ground-truth and performance sections, the +config page's JSON view, and the ground of the appearance switch. The map lightens its +light half to `#f8fafc`, since its recessed pane sits against panels that +already float. That is also the light input ground, which is why the aircraft +list's inputs take the card colour instead (§6). + +**`--accent-ink` is what sits _on_ the accent** rather than beside it. It is a +token and not a literal white because the dark accent is a bright sky blue, on +which white is about 2.1:1; there it becomes a near-black `#082f49`. Any solid +accent fill carrying text needs it, and the shared `.btn-primary` already reads +it. owl-os crosses the two: a warm canvas like the marketing sites, over a **cool** ink ramp like the console. It is also the only surface authored in **OKLCH** @@ -239,54 +256,51 @@ thirty stacked rows from reading as a grid. ### Accent -| | owl | retina | dash / data | map | owl-os | +| | owl | retina | console, light | console, dark | owl-os | |---|---|---|---|---|---| | Accent | `#5b8dd9` | `#2563eb` | `#3b82f6` | `#38bdf8` | `#2f8adc` | | Hover / strong | `#4a7cc8` | `#3b82f6` | `#2563eb` | `#7dd3fc` | `#1a7acb` | | Wash | `rgba(91,141,217,.15)` | `rgba(37,99,235,.06)` | `rgba(59,130,246,.10)` | `rgba(56,189,248,.16)` | `#e3f4ff` (opaque) | -| Wash edge | — | `rgba(37,99,235,.15)` | — | — | `#c4daf2` | -| Tint (hover) | — | — | — | `rgba(56,189,248,.07)` | — | +| Wash edge | n/a | `rgba(37,99,235,.15)` | n/a | n/a | `#c4daf2` | +| Tint (hover) | n/a | n/a | `rgba(59,130,246,.05)` | `rgba(56,189,248,.07)` | n/a | owl's blue is deliberately soft and low-contrast (an editorial choice, though it -costs link legibility, see §10). retina and dash share the saturated blue, with -dash resting one step lighter and hovering to retina's resting value. The map -pushes to a brighter sky-blue so the accent reads on navy, and takes dash's -value when themed light rather than adding another. owl-os sits between dash and -the map, a shade cyan-ward of both. Still five distinct blues across six -surfaces: dash and data are one value, and the themed surfaces swap between two -that already exist rather than introducing a third. - -The map is the only surface with **two** accent washes. The tint is the wash at -hover strength and sits deliberately below the wash proper, so a hovered row and -a selected one stay apart instead of collapsing into the same fill (light: -`rgba(59,130,246,.05)` under `rgba(59,130,246,.10)`). +costs link legibility, see §10). retina and the console share the saturated +blue, with the console resting one step lighter and hovering to retina's resting +value. The console's dark half pushes to a brighter sky blue so the accent reads +on navy. owl-os sits between the console's two, a shade cyan-ward of both. That +makes five distinct blues across four surfaces, two of them the console's. + +The console is the only surface with **two** accent washes. The tint is the wash +at hover strength and sits deliberately below the wash proper, so a hovered row +and a selected one stay apart instead of collapsing into the same fill. Two things owl-os does differently with it. Its washes are **opaque tints**, not alpha overlays, so a wash keeps its colour over any ground it lands on rather than picking up whatever is behind it. And every wash carries a matching **edge** one step darker (`#c4daf2` for the accent, `#bee2c9` for success), so a tinted -region is bounded rather than bleeding into the page. The map adds an amber -`#fbbf24` for a selection/focus highlight; owl-os uses the accent wash plus a -3px inset rail for the same job. +region is bounded rather than bleeding into the page. The map adds amber for the +selected track (`#fbbf24` on dark, `#d97706` on light); owl-os uses the accent +wash plus a 3px inset rail for the same job. ### Semantics -| Role | Marketing (owl, retina) | Consoles (dash, data) | Map (map) | Node (owl-os) | +| Role | Marketing (owl, retina) | Console, light | Console, dark | Node (owl-os) | |---|---|---|---|---| | Success / good | `#16a34a` | `#10b981` (emerald) | `#4ade80` | `#33a868` | | Warning / attention | `#d97706` | `#f59e0b` (amber-500) | `#fbbf24` | `#e1a035` | | Error | (unused) | `#ef4444` (red-500) | `#f43f5e` | `#e64343` | -Same three roles, brightened a step on the dark map so they carry on navy, and -landing between the marketing and console sets on owl-os. The two sets travel -with the theme rather than with the surface: the map's light theme takes the -console set unchanged, and dash's and data's dark takes the map's. Each pairs +Same three roles, brightened a step on the console's dark half so they carry on +navy, and landing between the marketing and console sets on owl-os. Each pairs with a wash (~10% on light, ~15% on dark; opaque on owl-os) for tinted pill -backgrounds. The console's charts -extend the accent into a categorical palette (`#3b82f6, -#10b981, #f59e0b, #ef4444, #8b5cf6, #ec4899, #06b6d4, #84cc16, #f97316, -#14b8a6`, with `#94a3b8` for an "others" slice); reuse that ordering for any new -dashboard chart. +backgrounds. The console's charts extend the accent into a categorical palette +(`#3b82f6, #10b981, #f59e0b, #ef4444, #8b5cf6, #ec4899, #06b6d4, #84cc16, +#f97316, #14b8a6`, with `#94a3b8` for an "others" slice) and a lighter run of +the same hues for the dark half (`#60a5fa, #34d399, #fbbf24, #f87171, #a78bfa, +#f472b6, #22d3ee, #a3e635, #fb923c, #2dd4bf`); reuse that ordering for any new +chart. Both live in `useChartTheme()`, since Recharts paints from props and +cannot read a custom property. The map's live data colours are a separate, radar-specific scheme, not these status roles, and the one part of the estate chosen by measurement rather than by @@ -294,14 +308,18 @@ eye: each value is held to a contrast floor against its own basemap, and each pair of marks to a CIEDE2000 distance from the others. The second is the constraint that gets forgotten, and the one that binds here, because the four track lanes all draw the same aircraft glyph and colour is the only thing telling -them apart. **Truth** (the ADS-B fix the solves are measured against) wears no -hue at all: it sits at the far end of the neutral ramp from the canvas, near-white -`#f8fafc` on dark and slate `#1e293b` on light, since it is the reference rather -than a fifth lane. A simulated target flying without a transponder is grey beside -it. Radar detections run a **Doppler gradient** from dark-blue approaching -(`#1e3a8a`) through a neutral slate at zero to dark-red receding (`#991b1b`), so -no radial motion reads as the absence of a direction rather than as a third -colour. Green there means coverage polygons, never truth. See §8. +them apart. **Truth** (the ADS-B fix the solves are measured against) is drawn +only on a synthetic fleet's view (the admin console's `/sim`, or a local stack), +so the public map carries none. Where it is drawn it wears no lane hue: a +simulated target's truth sits at the far end of the neutral ramp from the +canvas, near-white `#f8fafc` on dark and slate `#1e293b` on light, since it is +the reference rather than a fifth lane, and one flying without a transponder is +grey beside it. Truth mirrored from the live ADS-B feed is teal, the one hue +family no lane uses. Radar detections run a **Doppler gradient** from dark-blue approaching +(`#1e3a8a`) through a neutral slate at zero to dark-red receding (`#991b1b` on +dark, `#7f1d1d` on light), so no radial motion reads as the absence of a +direction rather than as a third colour. Green there means coverage polygons, +never truth. See §8. Both palettes carry the same keys, so a component asks for a role and gets the value for whichever theme is drawn. The light theme is the tighter of the two: @@ -324,32 +342,31 @@ something other than health. Up to three registers per surface: a **display** face for headings, a **body** face for running text and UI, and a **mono** face for labels and data. The -console collapses everything into the system stack; the map collapses everything -into Inter. +console collapses everything into the system stack, the map included. -| Register | owl | retina | dash / data | map | owl-os | -|---|---|---|---|---|---| -| Display | Source Serif 4 | Fraunces | system sans | Inter | Inter | -| Body | Inter | DM Sans | system sans | Inter | Inter | -| Mono | IBM Plex Mono | JetBrains Mono | SF Mono | generic `monospace` | JetBrains Mono | +| Register | owl | retina | console | owl-os | +|---|---|---|---|---| +| Display | Source Serif 4 | Fraunces | system sans | Inter | +| Body | Inter | DM Sans | system sans | Inter | +| Mono | IBM Plex Mono | JetBrains Mono | SF Mono | JetBrains Mono | -Loaded from Google Fonts on the marketing sites, the map (Inter) and owl-os; the -two consoles load no web fonts at all (their CSP restricts `font-src`). Only the -two marketing sites use a serif; the four operational surfaces (dash, data, map, +Loaded from Google Fonts on the marketing sites and owl-os; the console loads no +web fonts at all (its CSP limits `font-src` to its own origin and `data:` URIs). Only the two +marketing sites use a serif; the two operational surfaces (the console and owl-os) are sans-only. owl-os is the only surface that borrows both its faces from siblings rather than -choosing new ones (Inter from owl and the map, JetBrains Mono from retina), and -so is the least type-divergent of the six. It also asks Inter for +choosing new ones (Inter from owl, JetBrains Mono from retina), and so is the +least type-divergent of the four. It also asks Inter for `font-feature-settings: 'ss01', 'cv11'` (the single-storey `g` and the straight-tailed `l`), which is what stops a screen full of node IDs and frequencies from reading as body copy. **Heading weight is the other big tell.** owl sets headings at 400 (light, -serious, editorial); retina and owl-os at 600 (present, product-confident); dash, -data and map at 700 (compact, functional). Marketing headings carry a tight +serious, editorial); retina and owl-os at 600 (present, product-confident); the +console at 700 (compact, functional). Marketing headings carry a tight `letter-spacing` around `-0.02em` and line-height near 1.1; the operational -surfaces run text small (down to ~0.62rem) and dense. +surfaces run text small (down to 8px on the map) and dense. owl-os keeps the marketing surfaces' `-0.02em` on page titles while sitting at console sizes, and runs a 14px base rather than 16px: small enough for a page of @@ -364,7 +381,7 @@ settings, large enough not to read as a table. - Body: 16px base, line-height ~1.6–1.7, colour = ink-muted for prose. owl-os runs 14px / 1.5, and drops to 12–12.5px for the help line under a field. - Page title (owl-os): a flat 26px / 600 / `-0.02em`, on Home, Config and every - wizard step alike. Console page titles are a flat 24px. + wizard step alike. - Micro-label: ~0.65–0.75rem, mono, uppercase, `letter-spacing` 0.04–0.14em, colour = ink-subtle. The `.label` class in `tokens.css` captures the common case. owl-os sets its own at 11–11.5px, weight 500–600, tracking 0.04–0.08em, @@ -379,28 +396,26 @@ settings, large enough not to read as a table. ### Shape -| | owl | retina | dash / data | map | owl-os | -|---|---|---|---|---|---| -| Radius (default) | 4px | 12px | 8px | 8px | 10px | -| Radius (small) | 3px | 8px | 4px | 4px | 6px | -| Radius (large) | 6px | 20px | 12px | 12px | 14px | - -Crisp on owl, soft on retina, moderate on dash, data and the map, which take the -same scale, second-softest on owl-os. data defines only the first two steps, -having nothing large enough to need the third. Borders are always 1px -hairlines; nothing uses a heavy stroke. The map's panels float over a moving -basemap rather than sitting on a page, so it is the one surface where a card -carries a shadow as -standard (`--panel-shadow`), heavier on dark than on light because a soft shadow -does almost nothing there and the elevation has to come from the panel being -lighter than the canvas. - -The three-step scale is a fair summary for five of the surfaces and a -simplification for one. The map is now the closest to keeping to it: most of its -radius rules read `--radius` or `--radius-sm`, with 12px reserved for pills and a -scatter of 2–6px literals left over. retina is the outlier, spending 5, 6, 8, 10, -12 and 20px, with neither of its two button radii matching the 12px recorded as -its default. +| | owl | retina | console | owl-os | +|---|---|---|---|---| +| Radius (default) | 4px | 12px | 8px | 10px | +| Radius (small) | 3px | 8px | 4px | 6px | +| Radius (large) | 6px | 20px | 12px, as a literal | 14px | + +Crisp on owl, soft on retina, moderate on the console, second-softest on owl-os. +The console's tokens hold only the first two steps: 12px is written as a literal +on its pills (the status badge, the map's count and source chips) and the +sign-in card, and the data explorer's filter chips go fully round at 999px. +Borders are always 1px hairlines; nothing uses a heavy stroke. What floats over +the map's moving basemap (the legend, the filters popover, the overflow menu, +Leaflet's controls) carries a shadow as standard (`--panel-shadow`), heavier on +dark than on light because a soft shadow does almost nothing there and the +elevation has to come from the panel being lighter than the canvas. + +The three-step scale is a fair summary for three of the surfaces and a +simplification for retina, which spends 5, 6, 8, 10, 12 and 20px, with neither +of its two button radii matching the 12px recorded as its default. The map keeps +mostly to the console's two tokens, with a scatter of 2–5px literals left over. owl-os is the only surface with a real elevation scale: a hairline `shadow-sm` at rest, a 24px-blur `shadow-md` on hover and for the sticky save bar, and a @@ -410,15 +425,15 @@ black, so a raised card warms rather than greys. ### Space - Marketing content sits in a centred column: `max-width` 860px for prose, - 1120–1200px for wide/hero rows. dash is full-width and fluid with a 250px - sidebar; data has no sidebar to hold (one page, no nav tree, no login) and - runs a 1400px centred column under a top bar instead. The map is full-bleed: - a 280px left aircraft list (collapsing to 36px), a 280px right detail panel, - and a full-width bottom playback bar. + 1120–1200px for wide/hero rows. The console is full-width and fluid, with a + 250px sidebar that collapses to a 64px icon rail and 24px of content padding. + The map drops the padding and starts with the rail collapsed, then goes + full-bleed under the header: a 280px left aircraft list (collapsing to 36px), + a 300px right detail panel, and a playback bar across the foot of the map. - owl-os runs a 720px column on Home, a 200px + fluid two-column shell on Config (max 1100px), and a fixed 560px column for every wizard step. One decision per row, with its explanation directly underneath. -- Nav / header height: 56px (owl), 64px (retina), 56px (dash and data). owl-os +- Nav / header height: 56px (owl), 64px (retina), 56px (the console). owl-os stacks two rows instead, a fleet bar over the page tabs, and its config side-nav sticks below both at 58px. - Section padding: ~5–6rem vertical on the marketing sites, 24px content padding @@ -435,44 +450,48 @@ Three patterns recur: 3. **Signal march.** The radar diagram animates `stroke-dashoffset` so the dashed signal paths flow from transmitter to target to node. -owl and dash animate in CSS; retina's hero mixes in inline SVG SMIL (``). +owl and the console animate in CSS; retina's hero mixes in inline SVG SMIL +(``). The console's keyframes are all on the map (the live pulse, a +panel slide, an anomaly pulse) bar one, the data explorer's drawer slide. owl-os runs none of the three. It defines no `@keyframes` at all: motion is limited to 120ms colour, border and shadow transitions on hover, a 150ms switch throw, and a 2px nudge on a card's arrow. Nothing on the page moves unless it was touched, which is reasonable for a settings surface someone reaches when something needs fixing, and the reason its one animated component, the simulator, -checks `prefers-reduced-motion` before starting. The map guards its whole -surface with a single reduced-motion block, and owl's `/learn` page carries its -own; the two marketing home pages still run their loops unguarded. The -`tokens.css` `.reveal` helper adds the guard, and new work should keep it. +checks `prefers-reduced-motion` before starting. The map guards its whole page +with a single reduced-motion block, though the rest of the console has none, so +the drawer slide runs regardless; owl's `/learn` page carries its own guard, and +the two marketing home pages still run their loops unguarded. The `tokens.css` +`.reveal` helper adds the guard, and new work should keep it. --- ## 6. Components -The six surfaces do not have the same component set, and the differences are -larger than the colour and type differences in §3 and §4. Before reaching for a -pattern, check it exists on the surface you are building for. - -| | owl | retina | dash | data | map | owl-os | -|---|---|---|---|---|---|---| -| Form controls | **none** | **none** | inline, ad hoc | one shared rule | one shared rule | a real set | -| Tables | none | none | yes | a grid listing, one table | none | one | -| Modal / dialog | none | none | **native `confirm()`** | one drawer | one | three | -| Toasts | none | none | none | none | yes | none | -| Tabs | none | none | one page | none | app shell only | yes | -| Icons | 36px, part-filled | Unicode glyphs | 24px, stroke 2 | 15px inline SVG | inline SVG | 24px, stroke 1.6 | -| Framework | none | none | none | none | none | Bootstrap 5.3 | -| Custom properties | yes | yes | yes, themed | yes, themed | yes, themed | yes | -| Theme control | none | none | `.theme-switch`, 3 states | `.theme-switch`, 3 states | a menu item, 2 states | none | -| `:focus` styling | none | none | **none** | inputs | whole surface | inputs only | +The four surfaces do not have the same component set, and the differences are +larger than the colour and type differences in §3 and §4; within the console, +the map differs from the other pages as much again. Before reaching for a +pattern, check it exists where you are building. + +| | owl | retina | console | map | owl-os | +|---|---|---|---|---|---| +| Form controls | **none** | **none** | a shared `.input`; the data explorer scopes its own | one rule for the page | a real set | +| Tables | none | none | yes | one, in the shortcut help | one | +| Modal / dialog | none | none | one drawer; one native `confirm()` | one | three | +| Toasts | none | none | none | yes | none | +| Tabs | none | none | one page | none | yes | +| Icons | 36px, part-filled | Unicode glyphs | 24px, stroke 2 | inline SVG | 24px, stroke 1.6 | +| Framework | none | none | none | none | Bootstrap 5.3 | +| Custom properties | yes | yes | yes, themed | the console's, plus its chrome | yes | +| Theme control | none | none | `.theme-switch`, 3 states | the console's | none | +| `:focus` styling | none | none | inputs, and two scoped rings | the whole page | inputs only | The two marketing sites have no `
`, ``, `