feat: Apple-grade pass — scale, type, photographic hero, de-noise, single-signal chrome, lighter header
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jWfn3RwPfFGTtBddm36Uy
This commit is contained in:
@@ -2,26 +2,44 @@
|
||||
|
||||
Single source of truth for look-and-feel. All values live as tokens in
|
||||
`src/styles/global.css`; this file states the rules. Brand basis: CTAO Brand
|
||||
Guidelines (Apr 2024) + techniques observed on ctao.org (nebula motion, generous
|
||||
spacing, 15/30/60 rounding). No frameworks, no JS for presentation.
|
||||
Guidelines (Apr 2024) + techniques observed on ctao.org (photography-led hero,
|
||||
nebula motion, generous spacing, 15/30/60 rounding, quiet-fade hovers).
|
||||
No frameworks, no JS for presentation.
|
||||
|
||||
## Color tokens (brand-fixed — never add hues)
|
||||
|
||||
| Token | Value | Use |
|
||||
|---|---|---|
|
||||
| `--galaxy` | `#00004A` | Headings, header/footer/hero bands |
|
||||
| `--cherenkov` | `#00E4D8` | Accents, flash, CTAs, hero subtitle. Never small text on light bg |
|
||||
| `--moon` | `#F5F5F5` | Utility surfaces (bands, TOC, chips) |
|
||||
| `--azure` | `#007AFF` | Focus rings, hover borders (UI only on light) |
|
||||
| `--galaxy` | `#00004A` | Headings, header/footer bands, hero scrim |
|
||||
| `--cherenkov` | `#00E4D8` | ONLY: primary CTA, flash, hero subtitle, logo cluster (+ functional: `<mark>`, TOC active rule). Never small text on light bg |
|
||||
| `--moon` | `#F5F5F5` | Utility surfaces (bands, neutral hovers) |
|
||||
| `--azure` | `#007AFF` | Focus rings and field-focus borders ONLY (no hover borders) |
|
||||
| `--indigo` | `#00009C` | Gradient depth, link hover |
|
||||
| `--text` / `--muted` / `--border` / `--link` | derived | Body ink; `--link #0057C2` is azure darkened to pass AA |
|
||||
|
||||
Accent discipline: Cherenkov is rationed to the four brand moments above.
|
||||
Status pills, category/date chips and metadata are plain muted text — the
|
||||
label carries the meaning, not the chrome.
|
||||
|
||||
## Typography
|
||||
|
||||
| Role | Font | Rule |
|
||||
|---|---|---|
|
||||
| h1, h2 | Space Grotesk (`--font-display`) | Headlines ONLY (brand D.3.2) |
|
||||
| Everything else | Inter (`--font-body`) | Includes h3+, cards, UI |
|
||||
| h1, h2 | Space Grotesk (`--font-display`) | Headlines ONLY (brand D.3.2), weight **500** (D.3.4 regular/medium), letter-spacing −0.025em, line-height 1.1 |
|
||||
| Everything else | Inter (`--font-body`) | Includes h3+ (weight 600), cards, UI |
|
||||
|
||||
Type scale — the ONLY font sizes in the codebase (no ad-hoc rem values):
|
||||
|
||||
| Token | Value | Use |
|
||||
|---|---|---|
|
||||
| `--fs-xs` | 0.8125rem | Meta, badges, labels, eyebrow, dates |
|
||||
| `--fs-s` | 0.9rem | Secondary text, nav, forms, footer, TOC |
|
||||
| (base) | 1rem | Body, line-height 1.5 (1.65 only on `.prose`; 1.55 `.standfirst`; 1.45 cards/footer) |
|
||||
| `--fs-l` | 1.125rem | Card titles, standfirst, panel titles, hero subtitle |
|
||||
| `--fs-xl` | 1.5rem | Prose h2, featured title, auth h1 |
|
||||
| `--fs-h2` | clamp(1.75–2.5rem) | Section heads |
|
||||
| `--fs-h1` | clamp(2.25–3.5rem) | Page/article headlines |
|
||||
| `--fs-display` | clamp(2.5–4.75rem) | Home hero only |
|
||||
|
||||
Always end `font-family` with a generic (`sans-serif`) — lint requirement.
|
||||
|
||||
@@ -31,10 +49,10 @@ Always end `font-family` with a generic (`sans-serif`) — lint requirement.
|
||||
|---|---|---|
|
||||
| `--gutter` | `clamp(18px, 4vw, 28px)` | Container/menu side padding |
|
||||
| `--space-s` | `12px` | Chip gaps, small stacks |
|
||||
| `--space-m` | `20px` | Card/panel padding, grid gaps |
|
||||
| `--space-m` | `24px` | Card/panel padding, grid gaps |
|
||||
| `--space-l` | `24–40px` | Article top, meta/cover margins, featured body |
|
||||
| `--space-xl` | `40–72px` | Section rhythm: every `.page` block, footer top |
|
||||
| `--space-band` | `48–96px` | Hero + auth band vertical padding |
|
||||
| `--space-xl` | `clamp(56px, 7vw, 120px)` | Section rhythm: every `.page` block, footer top |
|
||||
| `--space-band` | `clamp(72px, 10vw, 160px)` | Hero/auth band + landing-head padding |
|
||||
|
||||
Rule: section/container/card padding comes from this table — no ad-hoc px.
|
||||
Prose-internal margins (h2/p/blockquote) are content-local and stay as written.
|
||||
@@ -43,197 +61,233 @@ Prose-internal margins (h2/p/blockquote) are content-local and stay as written.
|
||||
|
||||
Radius: `--radius 16px` (cards/panels), `--radius-pill` (buttons, chips, inputs).
|
||||
|
||||
Only two shadows exist, both Galaxy-tinted (`rgba(0,0,74,…)`):
|
||||
Only two shadows exist, both Galaxy-tinted, tuned soft/large/low-alpha
|
||||
(2025/26 elevation convention: big blur, single-digit alpha at rest):
|
||||
|
||||
| Token | Use |
|
||||
|---|---|
|
||||
| `--shadow-ambient` | Resting cards, panels, search rows |
|
||||
| `--shadow-lifted` | Hover/focus cards, dropdowns (suggest, mobile menu), auth card |
|
||||
| `--shadow-lifted` | Hover/focus cards, dropdowns (suggest, menu, search row), auth card |
|
||||
|
||||
Never write a literal `box-shadow`; compose focus rings as
|
||||
`0 0 0 2px var(--azure), var(--shadow-lifted)`.
|
||||
|
||||
## Surfaces
|
||||
|
||||
Rhythm per page: Galaxy hero band → content → Galaxy footer (flush, no margin).
|
||||
White = editorial (news grids, articles). `.band--moon` = service/utility
|
||||
sections (home Services, /search, /support, /proposals, /dashboard). Cards and
|
||||
panels are always white and pop against Moon via border + ambient shadow.
|
||||
ONE surface per page — surface differentiation is by page archetype, never a
|
||||
mid-page seam (consistency heuristic: a surface change must read as deliberate
|
||||
system, not "why is this one gray?"):
|
||||
|
||||
- **Editorial pages** (`/news`, articles, static pages, home news section) are
|
||||
white — reading surfaces.
|
||||
- **Utility/app pages** (`/search`, `/proposals`, `/dashboard`, `/support`)
|
||||
are `.band--moon` from the landing head down to the footer — the app canvas
|
||||
against which borderless white cards/panels pop (standard app-vs-editorial
|
||||
surface split; home's Services section uses the same Moon band).
|
||||
- `/` and `/login` own the navy bands (photo hero / nebula).
|
||||
|
||||
Cards and panels are always white; ON MOON they are borderless (white-on-moon
|
||||
separates by itself), on white they keep the hairline. Chrome is
|
||||
single-signal: hover = the elevate gesture (shadow + 2px lift as ONE motion);
|
||||
no hover borders, no cover zoom.
|
||||
|
||||
## Motion policy (CSS-only, Apple-tier restraint)
|
||||
|
||||
ctao.org animates nebula videos under Galaxy vignettes; our translation:
|
||||
ctao.org drifts looping nebula videos under Galaxy vignettes; our translation:
|
||||
|
||||
1. **Band nebula** — one `--nebula` stack (Cherenkov .38 / Azure .33 /
|
||||
Indigo .80 radial fields) used by hero + auth bands, static and animated
|
||||
alike; the animated variant is an oversized `.band-bg::before` layer
|
||||
drifting via `transform` only, 22s alternate. Clearly visible glow, never
|
||||
neon. `.band-bg` (first child of every band) owns the `overflow: hidden`
|
||||
clip — NEVER put overflow on the band itself: it would clip overlays like
|
||||
the search-suggest dropdown.
|
||||
2. **Whole-page ambient** — `.has-ambient::before`: fixed brand-hue wash
|
||||
(Cherenkov .16 / Azure .12 / Indigo .10 — visible pastel fields, white
|
||||
cards pop against them) drifting on a 60s alternate cycle. Opt-in per page
|
||||
via `<Base ambient>`: **home only**. Login's full-viewport auth nebula
|
||||
already is its ambient; news/article/utility pages stay calm (reading
|
||||
focus). Body text NEVER sits on a moving gradient — cards, panels and
|
||||
bands are solid layers above it.
|
||||
3. One-shot hero `rise` stagger; hover lift/transitions, incl. the card-cover
|
||||
micro-zoom (scale 1.03, clipped by the card — AstroWind pattern). Nothing else.
|
||||
1. **Hero glow (home)** — the photo hero keeps its scrimmed photograph; a
|
||||
`--nebula-glow` layer (Cherenkov .30 / Azure .28, no indigo over
|
||||
photography) drifts above it inside the `.band-bg` clip frame: 16s
|
||||
alternate, transform-only, clearly perceptible within ~10s. `.band-bg`
|
||||
(first child of the band) owns `overflow: hidden` — NEVER put overflow on
|
||||
the band itself (it would clip the search-suggest dropdown).
|
||||
2. **Auth nebula (/login)** — the full `--nebula` stack (Cherenkov .38 /
|
||||
Azure .33 / Indigo .80), same 16s drifting layer; static gradient is the
|
||||
reduced-motion fallback.
|
||||
3. **Whole-page ambient** — `.has-ambient::before`: fixed brand-hue wash
|
||||
(Cherenkov .16 / Azure .12 / Indigo .10) drifting on a 36s alternate
|
||||
cycle. Opt-in via `<Base ambient>`: **home only**. Body text NEVER sits on
|
||||
a moving gradient — cards, panels and bands are solid layers above it.
|
||||
4. One-shot hero `rise` stagger; unified hover system (below). Nothing else.
|
||||
|
||||
**One hover language** (mid-pass audit: hovers were inconsistent per element;
|
||||
ctao.org uses a single quiet-fade token — we do the same): every interactive
|
||||
element transitions color/background/border/shadow on `--dur: 0.25s` +
|
||||
`--ease: ease-out` — buttons and links fade, cards/search rows elevate
|
||||
(shadow + 2px lift), pagination fills Moon. No width/layout animation.
|
||||
|
||||
Rules:
|
||||
- ALL `animation`/`transition` declarations live inside the single
|
||||
`@media (prefers-reduced-motion: no-preference)` block at the end of
|
||||
global.css; the `reduce` block additionally force-disables everything.
|
||||
- Animate `transform`/`opacity` only (compositor-only, per web.dev/MDN); never
|
||||
background-position/box-shadow in keyframes. No `will-change` (animated
|
||||
properties are auto-promoted — MDN). No `filter: blur` on large layers and no
|
||||
`background-attachment: fixed` (broken on iOS) — the wash is a fixed-position
|
||||
layer with pre-blurred radial gradients. No JS, no scroll-triggered motion.
|
||||
- Static gradients remain as the reduced-motion fallback (bands swap them for
|
||||
the animated layer — never both, keeping contrast bounded; the ambient wash
|
||||
simply stops moving).
|
||||
- Animate `transform`/`opacity` only (compositor-only); never
|
||||
background-position/box-shadow in keyframes. No `will-change`. No
|
||||
`filter: blur` on large layers and no `background-attachment: fixed`
|
||||
(broken on iOS). No JS, no scroll-triggered motion.
|
||||
- If an effect calls attention to itself as an effect, cut it.
|
||||
|
||||
## Photographic hero (home)
|
||||
|
||||
Image-led hero per brand D.6 (photography theme: the project): LST-1 under
|
||||
the night sky, copied from the content library to `public/brand/hero.jpg`
|
||||
(NEVER link `public/uploads` directly — that directory is content-synced).
|
||||
Text safety comes from `--hero-scrim` (Galaxy, `to right`:
|
||||
.84 → .76 @52% → .44 @78% → .26); ≤640px a flat .80 scrim replaces it (text
|
||||
spans the full width on mobile). The logo never sits on the photograph — it
|
||||
stays in the header bar (B.1.2 satisfied by construction).
|
||||
|
||||
Computed worst-case contrast over photo + scrim + drifting glow (glow fringe
|
||||
α .15 Cherenkov / core .28 Azure over the text zone; Cherenkov core is pinned
|
||||
≥80% band width by the blob geometry + drift range):
|
||||
|
||||
- Small-text zone (eyebrow/subtitle/CTA labels, ≤52% band width): white
|
||||
7.68:1 · Cherenkov subtitle 4.79:1 · eyebrow `#C7CDE9` 4.88:1 (all ≥4.5).
|
||||
- Display h1 zone (≤72% band width): white ≥5.20:1 (large text needs 3.0).
|
||||
- Mobile flat scrim: white 8.47:1 · subtitle 5.28:1 · eyebrow 5.38:1.
|
||||
|
||||
Recompute (script kept in the session scratchpad pattern: composite scrim →
|
||||
glow → WCAG luminance per pixel) if the photo, scrim stops, glow alphas or
|
||||
blob positions change.
|
||||
|
||||
## WCAG 2.1 AA notes (computed, worst-case animation frames)
|
||||
|
||||
- Nebula blob peaks over Galaxy: Cherenkov α=.38 → white 7.9:1, Cherenkov
|
||||
subtitle 4.9:1, eyebrow `#C7CDE9` 5.0:1. Azure α=.33 → all ≥ 7.7:1.
|
||||
Indigo α=.80 → all ≥ 9.6:1. Cherenkov/Azure CORES must stay in opposite
|
||||
corners (core-over-core would fail; fringe overlap ≈ 6:1).
|
||||
- On Moon: `--link` 6.1:1, `--muted` 7.1:1, `--text` 16.9:1.
|
||||
- Ambient wash worst overlap (`rgb(188,237,249)`): `--link` 5.3:1, `--muted`
|
||||
6.2:1, Galaxy h2 15.2:1.
|
||||
- Ceilings: nebula ≤ .38 Cherenkov / ≤ .35 Azure; wash ≤ .16/.12/.10 —
|
||||
recompute (WCAG relative luminance) if any alpha or hue placement changes.
|
||||
- Photo hero: see table above. Auth nebula over Galaxy: white 7.9:1,
|
||||
Cherenkov subtitle 4.9:1 at the worst blob frame; Cherenkov/Azure CORES stay
|
||||
in opposite corners. Ceilings: nebula ≤ .38 Cherenkov / ≤ .35 Azure; glow
|
||||
≤ .30/.28; wash ≤ .16/.12/.10 — recompute if any alpha or placement changes.
|
||||
- Translucent header (`color-mix` 88% Galaxy + backdrop blur, solid fallback
|
||||
via `@supports`): worst underlying content (white) → bar ≈ rgb(31,31,96);
|
||||
white text 14.8:1, muted nav `#C7CDE9` 9.4:1, Cherenkov sub-brand 9.2:1.
|
||||
- On Moon: `--link` 6.1:1, `--muted` 7.1:1, `--text` 16.9:1. Ambient wash
|
||||
worst overlap: `--link` 5.3:1, `--muted` 6.2:1.
|
||||
- Focus visible everywhere; tap targets ≥ 44px; skip link first.
|
||||
|
||||
## Article TOC pattern
|
||||
|
||||
Rendered only when ≥3 h2/h3. Two renderings of the same list (only one ever
|
||||
displayed): `<details class="toc toc--inline">` collapsed under the title
|
||||
(< 1200px) and `<nav class="toc toc--rail">` sticky right rail (≥ 1200px,
|
||||
outside the 70ch column via `.article-layout` grid). MDN/Stripe/NN-g pattern.
|
||||
displayed), both in the quiet Stripe/Apple-docs voice — muted small links,
|
||||
Galaxy on hover, no heavy box:
|
||||
|
||||
Scrollspy (Starlight's starlight-toc pattern, minimal vanilla): an
|
||||
IntersectionObserver band under the sticky header maps the visible prose block
|
||||
to its governing heading and sets `aria-current="true"` on the TOC link
|
||||
(Galaxy + 600 weight). Functional state, not motion — the "no JS motion" rule
|
||||
is untouched; the TOC works fully without JS. Same precedent covers the search
|
||||
`<mark>` term highlight (Cherenkov tint) and the `.pagination` pill nav
|
||||
(Galaxy pill = current page, 44px targets).
|
||||
- `<nav class="toc toc--rail">` (≥1200px): sticky whitespace column with a
|
||||
hairline LEFT rule, outside the 70ch column via the `.article-layout` grid;
|
||||
sticky offset 80px (56px header + 24), own scroll when long.
|
||||
- `<details class="toc toc--inline">` (<1200px): light disclosure row
|
||||
("On this page" + chevron from the `.ico` family), comfortable tap rows.
|
||||
|
||||
## Header search & one-row width math
|
||||
Scrollspy (Starlight pattern, minimal vanilla): an IntersectionObserver band
|
||||
under the sticky header maps the visible prose block to its governing heading
|
||||
and sets `aria-current="true"` on the TOC link — Galaxy + 600, and on the rail
|
||||
a 2px Cherenkov segment over the hairline. Functional navigation state, not
|
||||
decoration (outside the one-accent budget), same precedent as the search
|
||||
`<mark>` highlight (Cherenkov tint) and the `.pagination` current pill.
|
||||
The TOC works fully without JS.
|
||||
|
||||
Search lives in the header. Desktop (≥1025px): compact GET-form pill (flex
|
||||
130–180px) in the utility cluster; suggestions reuse `.suggest`. ≤1024px: a
|
||||
44px magnifier `<details>` reveals a full-width search row under the header
|
||||
(visible search beats search buried in a menu — NN/g mobile search). Both
|
||||
forms are plain `GET /search` (full no-JS path); `<details name="header-panel">`
|
||||
makes search + menu a native exclusive pair (no JS). The `/search` page stays
|
||||
the full-results and deep-link target.
|
||||
## Header
|
||||
|
||||
Nav content rule: the header nav reflects end-user tasks only (NN/g —
|
||||
navigation mirrors user goals, not org tooling). The content editor is
|
||||
internal-team tooling (SPEC §3.2), so it lives as a discreet "Content editor"
|
||||
utility link in the footer, pointed to by the demo note; /admin keeps working
|
||||
as the direct URL.
|
||||
56px bar (translucent Galaxy via `color-mix` + backdrop blur where supported,
|
||||
solid Galaxy fallback — see WCAG notes). One row always: `.nav > *` is
|
||||
`flex: none`, only the spacer flexes; nothing can wrap. Tiers: ≥1025px full
|
||||
nav · ≤1024px disclosure menu (`<details>`) · ≤640px sub-brand hidden ·
|
||||
≤480px icon-only menu · ≤360px compact paddings. Logo ≥22px tall everywhere
|
||||
(≈105px wide — brand minimum 100px digital).
|
||||
|
||||
One-row invariant: `.nav > *` is `flex: none`; only the spacer and the search
|
||||
field flex, so the row can never wrap or overflow. Tier math (Inter widths
|
||||
estimated ±8%; logo 153:32 → 124px at 26px height; inner width = viewport −
|
||||
2×gutter, container caps at 1120):
|
||||
Header search — decided by research, not aesthetics: NN/g's magnifying-glass
|
||||
-icon findings (icon-only search measurably reduces discoverability and use;
|
||||
show a visible box when search is an important task) beat the icon-only
|
||||
minimal header for this content-heavy, search-required portal (SPEC §3.3.1).
|
||||
Desktop ≥1025px: a VISIBLE quiet field (hairline on navy, 140–200px flex —
|
||||
the only flexing item, so the row never wraps). ≤1024px: the 44px magnifier
|
||||
`<details>` reveals a search row under the bar (NN/g mobile-search pattern).
|
||||
Both are plain `GET /search` (full no-JS path); `<details name="header-panel">`
|
||||
makes search + menu a native exclusive pair. `/search` stays the full-results
|
||||
and deep-link target. The footer "Search" link STAYS: SPEC §3.3.1 literally
|
||||
places "a search functionality" in the bottom menu, and the footer is the
|
||||
end-of-page fallback (NN/g footers) — one quiet word, clean compliance trail.
|
||||
Sign in is a ghost/outline pill — Cherenkov fill is reserved for the page's
|
||||
primary CTA.
|
||||
|
||||
| Tier | Budget | Available |
|
||||
|---|---|---|
|
||||
| ≥1100 | brand+sub 247 + nav 415 + search 186 + Sign in 91 + gaps 16 ≈ 955 | 1044 @1100 |
|
||||
| 1025–1099 (sub-brand hidden) | ≈ 842 | 969 @1025 |
|
||||
| 481–1024 (menu + magnifier) | brand 134 + magnifier 44 + Sign in 91 + Menu 92 + gaps 16 ≈ 377 | 445 @481 |
|
||||
| ≤480 (logo 22px, icon-only menu) | ≈ 312 | 339 @375 |
|
||||
| ≤360 (compact paddings, gap 2) | ≈ 277 | 292 @320 |
|
||||
|
||||
The search field's 50px shrink range (180 → 130) absorbs estimate error in
|
||||
every tier. No focus-grow on the field: a width transition would be a layout
|
||||
animation (motion policy allows transform/opacity only) and an unanimated
|
||||
snap is worse than a calm fixed width.
|
||||
Nav content rule: the header nav reflects end-user tasks only (NN/g). The
|
||||
content editor is internal-team tooling (SPEC §3.2) → discreet "Content
|
||||
editor" utility link in the footer; /admin keeps working as the direct URL.
|
||||
|
||||
## Content & microcopy
|
||||
|
||||
- Card metadata must differentiate (NN/g: omit non-differentiating metadata):
|
||||
category and author are identical on all articles, so cards show
|
||||
date · reading time (body words ÷ 220 wpm, computed at build). Category and
|
||||
author remain on the article page; meta order there is
|
||||
category · author · date · read time.
|
||||
- Badges: "Mock — <what's missing>" (bare "Mock" only on tiles); "External —
|
||||
<team>"; "Planned — not yet available". Mock badges are never removed.
|
||||
- Terminology is fixed: "Sign in" (never Log in), "Editor", "Search", "News".
|
||||
- Card metadata must differentiate (NN/g): cards show date · reading time
|
||||
(body words ÷ 220 wpm, computed at build). Category and author remain on
|
||||
the article page as one quiet muted meta line (category · author · date ·
|
||||
read time — plain text, no chips).
|
||||
- Honesty lives at the INTERACTION POINT (REQUIREMENTS §5): whisper badges
|
||||
("Mock — <what's missing>", "Planned integration — <team>") appear where a
|
||||
mock interaction actually happens — /proposals panel, /dashboard, /login,
|
||||
/support page head. Navigation tiles carry no status chrome; planned
|
||||
services are subdued tiles (muted, no link) with "— planned" folded into
|
||||
the description. There is NO phase banner (removed as clutter — the footer
|
||||
demo note + interaction-point badges carry the disclosure).
|
||||
- Terminology fixed: "Sign in" (never Log in), "Editor", "Search", "News".
|
||||
Buttons start with a verb, sentence case. Real ellipsis (…), never "...".
|
||||
- Search links (`.suggest a`, `.search-results a`) lay out as flowing text —
|
||||
never `display: flex` on the `<a>` itself: `<mark>` term-highlight splits the
|
||||
title into fragments, and flex would turn each into its own item.
|
||||
never `display: flex` on the `<a>` (mark-splitting). Dates are plain muted
|
||||
text, not chips.
|
||||
- Overflow containment happens at the source: wide `.prose` tables scroll in
|
||||
their own box, long words/URLs wrap via `overflow-wrap`. The
|
||||
`html, body { overflow-x: clip }` rule is a regression guard, not a fix —
|
||||
never rely on it for new components.
|
||||
- Articles end with `.post-nav` (Newer ← → Older from the date-sorted
|
||||
collection). Image captions: italic line under the image
|
||||
(`.prose img + em` / `p > em:only-child`), muted and smaller.
|
||||
`html, body { overflow-x: clip }` rule is a regression guard, not a fix.
|
||||
- Articles end with `.post-nav` (Newer ← → Older). Lead image is borderless
|
||||
and breaks out of the text column ≥900px. Image captions: italic muted line
|
||||
under the image.
|
||||
|
||||
### Element rules (condensed)
|
||||
|
||||
| Element | Rule | Source |
|
||||
|---|---|---|
|
||||
| Phase banner | THE one loud demo disclosure: Moon bar above header, "Demo" tag + one sentence | GOV.UK phase banner |
|
||||
| Header nav | End-user tasks only; order News · Data · Proposals · Dashboard · Support (entry first, help last, personal area next to Sign in) | NN/g serial-position |
|
||||
| Header search | Compact field ≥1025px, magnifier reveal ≤1024px; both plain GET /search | NN/g site search |
|
||||
| Hero (home) | Headline + brand subtitle + two task CTAs (Cherenkov primary, ghost secondary) + one-line Latest teaser. Never a second search box; no fake live widgets | hero-CTA convention, one-search-location |
|
||||
| Badges | Quiet hairline pill, muted sentence-case text, Galaxy dot on mocks; "Mock — reason"; never removed | REQUIREMENTS §5, NN/g hierarchy |
|
||||
| Cards | Cover · title (clamp 3) · desc (clamp 2) · date · read time; no non-differentiating metadata | NN/g metadata |
|
||||
| Pagination | Newer/Older + windowed numbers, 44px targets, aria-current | NN/g pagination |
|
||||
| Article | Back "← All news"; h1 → standfirst (description) → meta (category · author · date · read time) → cover → inline TOC → prose; italic muted captions; TOC at ≥3 headings; post-nav | Guardian/BBC anatomy |
|
||||
| Header nav | End-user tasks only; order News · Data · Proposals · Dashboard · Support | NN/g serial-position |
|
||||
| Header search | Icon-reveal row, all widths; plain GET /search | NN/g site search |
|
||||
| Hero (home) | Photo (LST under night sky) + Galaxy scrim + eyebrow + headline + Cherenkov subtitle + CTA pair (Cherenkov primary, ghost secondary). Nothing else — no teaser line, no widgets, no second search | Apple hero restraint |
|
||||
| Badges | Quiet hairline pill, muted sentence case, Galaxy dot on mocks; ONLY at mock interaction points | REQUIREMENTS §5 |
|
||||
| Cards | Cover · title (clamp 3) · desc (clamp 2) · date · read time; borderless on Moon; hover = elevate only | NN/g metadata |
|
||||
| Pagination | Newer/Older + windowed numbers, 44px targets, aria-current, Moon hover fill | NN/g pagination |
|
||||
| Article | Back "← All news"; h1 → standfirst → muted meta line → borderless breakout cover → inline TOC → prose → post-nav | Guardian/BBC anatomy |
|
||||
| Forms | Visible labels above fields; placeholders are examples only; buttons start with a verb | GOV.UK forms, NN/g |
|
||||
| Login | One line + button + one small-print line; no reassurance prose | auth brevity |
|
||||
| Footer | Identity line → Contact · Search · Disclaimer · Privacy · Site settings · Content editor → © line; no implementation talk in UI copy | NN/g footers |
|
||||
| Search results | Links lay out as flowing text (mark-safe); date chip; "Press Enter for all results" hint | — |
|
||||
| Login | One line + Cherenkov primary CTA + one small-print line; whisper badge top-right of the card (in flow ≤480px); no reassurance prose | auth brevity |
|
||||
| Footer | Identity line → Contact · Search · Disclaimer · Privacy · Site settings · Content editor → © line | NN/g footers, SPEC §3.3.1 |
|
||||
| Search results | Links lay out as flowing text (mark-safe); plain muted date; "Press Enter for all results" hint | — |
|
||||
|
||||
## Page composition (archetypes)
|
||||
|
||||
Every page follows one of three archetypes; a new page picks one instead of
|
||||
inventing a section order.
|
||||
inventing a section order. The navy band is reserved for `/` and `/login`.
|
||||
|
||||
| Archetype | Pages | Order |
|
||||
|---|---|---|
|
||||
| Portal landing | `/` | Hero (h1 + subtitle + CTA pair + one-line Latest teaser) → Services tiles (Moon) → Latest news 3-up + "All news" (white) |
|
||||
| Section landing | `/news`, `/search`, `/proposals`, `/dashboard`, `/support` | Galaxy hero band carrying the h1 + one-line subtitle → single content band (Moon = utility, white = editorial listing) → footer |
|
||||
| Portal landing | `/` | Photo hero (h1 + subtitle + CTA pair) → Services tiles (Moon) → Latest news 3-up + "All news" (white) |
|
||||
| Section landing | `/news`, `/search`, `/proposals`, `/dashboard`, `/support` | Landing head (`.page-head`: display Galaxy h1 + standfirst, `--space-band` top padding, no eyebrow, no navy band) → content → footer. The WHOLE page sits on its archetype surface: white (editorial /news) or Moon (utility) — head included, one surface per page |
|
||||
| Document | `/news/<slug>`, `/pages/<slug>` | Back link → h1 → standfirst → meta → cover → inline TOC → prose (→ post-nav on articles); no hero band — the document IS the page |
|
||||
|
||||
- Home is task-first (NN/g homepage principles: clear starting points for the
|
||||
top 1–4 tasks — for scientists that's proposals/data/support, not reading
|
||||
news). The hero teaser is the freshness signal (deep-links the newest
|
||||
article); the 3-card section below is the browse layer — different
|
||||
granularity, not redundant.
|
||||
- Document order is press anatomy (Guardian/BBC/Reuters): headline →
|
||||
standfirst → byline/meta → lead image → body. The inline TOC sits between
|
||||
lead image and body (Wikipedia/GOV.UK contents position) so the editorial
|
||||
head stays unbroken and the list is adjacent to what it indexes.
|
||||
- Back affordances: articles "← All news", static pages "← Home" (footer- and
|
||||
deep-link-reached documents must not be dead ends). Auth (`/login`) is the
|
||||
sole exception to the archetypes: one focused card, no hero, no sections.
|
||||
- Service tiles carry a stroke glyph (`.ico` family: currentColor, round caps,
|
||||
aria-hidden; 26px Galaxy) ABOVE the label — never instead of it (NN/g icon
|
||||
usability: icons are ambiguous alone; beside an always-visible label they
|
||||
differentiate otherwise-uniform tiles and speed grid scanning). Glyphs are
|
||||
wayfinding, not decoration — they don't count against the one-accent budget.
|
||||
- Home is task-first (NN/g): Services directly under the hero; the news
|
||||
section below is the browse layer.
|
||||
- Document order is press anatomy (Guardian/BBC/Reuters). The inline TOC sits
|
||||
between lead image and body.
|
||||
- Back affordances: articles "← All news", static pages "← Home". Auth
|
||||
(`/login`) is the sole exception to the archetypes: one focused card on the
|
||||
nebula band, no hero, no sections.
|
||||
- Service tiles carry a stroke glyph (`.ico` family: currentColor, round
|
||||
caps, aria-hidden; 26px Galaxy) ABOVE the label — never instead of it.
|
||||
Glyphs are wayfinding, not decoration.
|
||||
|
||||
## Do not
|
||||
|
||||
- No new hues, tints, or grays — derive via `color-mix` from brand tokens only.
|
||||
- No Space Grotesk outside h1/h2. No dark theme.
|
||||
- No ad-hoc spacing/shadow/radius values; extend the token tables instead.
|
||||
- No animation/transition outside the `no-preference` block; no JS motion.
|
||||
- No dependencies/frameworks (CSS or JS). No removal of "Mock" badges.
|
||||
- Don't lighten text over navy bands below the alphas listed above.
|
||||
- No Space Grotesk outside h1/h2; headline weight stays 500 (brand D.3.4).
|
||||
- No font sizes outside the type-scale tokens; no ad-hoc spacing/shadow/radius.
|
||||
- No animation/transition outside the `no-preference` block; no JS motion;
|
||||
no transition durations/easings outside `--dur`/`--ease`.
|
||||
- No dependencies/frameworks (CSS or JS). No removal of mock disclosure at
|
||||
interaction points. No phase banner — do not reintroduce it.
|
||||
- No Cherenkov outside: primary CTA, flash, hero subtitle, logo cluster
|
||||
(+ the two functional states listed under Color tokens). No hover borders.
|
||||
- Don't lighten text over navy/photo bands below the computed ceilings above.
|
||||
- No clip-path/CSS approximations of brand assets — the flash is the real SVG
|
||||
path from the official logo, or nothing.
|
||||
- Decoration budget: max ONE accent element per view (currently: footer flash,
|
||||
static). No twinkles, floating shapes, or stacked decorations.
|
||||
- Decoration budget: max ONE accent element per view (currently: footer
|
||||
flash, static). No twinkles, floating shapes, or stacked decorations.
|
||||
- Never link `public/uploads` from layout/brand surfaces — copy to
|
||||
`public/brand/` instead (uploads is content-synced).
|
||||
|
||||
Reference in New Issue
Block a user