diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..3c8f10d --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,125 @@ +# CTAO Science Portal — design system + +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. + +## 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) | +| `--indigo` | `#00009C` | Gradient depth, link hover | +| `--text` / `--muted` / `--border` / `--link` | derived | Body ink; `--link #0057C2` is azure darkened to pass AA | + +## 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 | + +Always end `font-family` with a generic (`sans-serif`) — lint requirement. + +## Spacing scale (fluid; mobile ≈ min, desktop ≈ max) + +| Token | Value | Use | +|---|---|---| +| `--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-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 | + +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. + +## Radius & elevation + +Radius: `--radius 16px` (cards/panels), `--radius-pill` (buttons, chips, inputs). + +Only two shadows exist, both Galaxy-tinted (`rgba(0,0,74,…)`): + +| Token | Use | +|---|---| +| `--shadow-ambient` | Resting cards, panels, search rows | +| `--shadow-lifted` | Hover/focus cards, dropdowns (suggest, mobile menu), 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. + +## Motion policy (CSS-only, Apple-tier restraint) + +ctao.org animates 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 pseudo-layer drifting via + `transform` only, 22s alternate. Clearly visible glow, never neon. +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 ``: **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. Nothing else. + +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). +- If an effect calls attention to itself as an effect, cut it. + +## 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. +- 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): `
` collapsed under the title +(< 1200px) and `