diff --git a/flight-comparator/docs/DESIGN_SYSTEM.md b/flight-comparator/docs/DESIGN_SYSTEM.md new file mode 100644 index 0000000..20f84b6 --- /dev/null +++ b/flight-comparator/docs/DESIGN_SYSTEM.md @@ -0,0 +1,1015 @@ +# Flight Radar — Design System + +**Version 1.0 · February 2026** + +This document is the authoritative reference for the visual design and UX of the Flight Radar web application. It covers the full design system: principles, tokens, components, and page-level specifications — ready for direct implementation in Tailwind CSS v4 + React. + +--- + +## Table of Contents + +1. [Current State Audit](#1-current-state-audit) +2. [Design Principles](#2-design-principles) +3. [Color System](#3-color-system) +4. [Typography](#4-typography) +5. [Spacing & Layout Grid](#5-spacing--layout-grid) +6. [Elevation & Shadow](#6-elevation--shadow) +7. [Component Library](#7-component-library) +8. [Page Specifications](#8-page-specifications) +9. [Motion & Animation](#9-motion--animation) +10. [Icons](#10-icons) +11. [Implementation Checklist](#11-implementation-checklist) + +--- + +## 1. Current State Audit + +### What's broken today + +| Area | Problem | Impact | +|------|---------|--------| +| **Navigation** | Nav links butted together with no gaps: renders as `DashboardScansAirportsLogs` | Unreadable, looks broken | +| **Dashboard stats** | Five identical white boxes with no accent, icon, or trend | Zero visual hierarchy | +| **Scan list items** | Status badge glued to destination: `BDS → FMM,DUScompleted` | Parsing required by user | +| **Form** | Flat input fields, no grouping, no visual section breaks | Feels like a raw HTML form | +| **Mode toggle** | Full-width button pair looks like a toggle switch gone wrong | Confusing affordance | +| **Airports page** | Empty white canvas with an input box — no state, no branding | Dead end feeling | +| **Logs page** | Wall of monospaced text with no log-level differentiation | No scanability | +| **Mobile** | Nav links overflow and collapse poorly | Unusable on small screens | +| **Loading states** | Plain text "Loading…" or invisible spinner | No feedback | +| **Empty states** | Plain text only, no illustration or call to action | Abandoned feeling | +| **Colors** | Two blues + basic status colours, no system | Incoherent palette | +| **Typography** | Browser default font, no size scale | Low credibility | +| **Branding** | ✈️ emoji as logo | Not scalable, not professional | + +### What's good and must be kept + +- Tailwind CSS v4 already installed — just needs design tokens +- React Router working, page structure sound +- Component boundaries (AirportSearch, Layout, pages) are clean +- Status badge color logic is correct +- Auto-refresh on running scans is a great UX feature +- Expandable flight rows in the route table are the right pattern + +--- + +## 2. Design Principles + +### 1. Data first, chrome second +Flight data is complex (many numbers, dates, airports). The UI must recede and let data breathe. Avoid decorative elements that compete with content. + +### 2. Material Design 3 — adapted, not copied +Use MD3's color system (tonal palettes, surface roles), elevation model (tonal elevation, not drop shadows), and component shapes (rounded corners). Ditch components that don't fit a desktop tool (FABs, bottom nav for desktop). + +### 3. Mobile-first, desktop-optimised +Start every layout from 320 px. Desktop adds a sidebar and wider data tables — it doesn't redesign the page. + +### 4. Instant feedback everywhere +Every async action shows a skeleton, spinner, or progress indicator. Every destructive or slow action is confirmed. Success and error are always communicated via toast. + +### 5. One primary action per view +Each page has exactly one primary CTA (blue filled button). Everything else is secondary or ghost. + +--- + +## 3. Color System + +### Brand palette (MD3 tonal palette — primary hue: blue 220°) + +``` +Primary #1A73E8 (Google Blue — familiar, trustworthy for a data tool) +On Primary #FFFFFF +Primary Cont. #D2E3FC (primary container — used for active nav, chips) +On Primary Cont.#003E8C + +Secondary #0F9D58 (green — price / success / confirmed flights) +On Secondary #FFFFFF +Secondary Cont. #C8E6C9 + +Tertiary #F4B400 (amber — warnings, pending states) +On Tertiary #3B2A00 +Tertiary Cont. #FFF0C0 + +Error #D93025 (red — failed, error) +Error Cont. #FDECEA + +Surface #F8F9FA (page background — very light grey) +Surface 1 #FFFFFF (card background — level 1) +Surface 2 #F1F3F4 (table row alternate / subtle separators) +Surface Variant #E8EAED (input backgrounds, chip backgrounds) +On Surface #202124 (primary text) +On Surface Var. #5F6368 (secondary text, labels, metadata) +Outline #DADCE0 (borders, dividers) +Outline Var. #F1F3F4 (very subtle separators) +``` + +### Semantic status colours + +| Status | Background | Text | Border | Usage | +|--------|-----------|------|--------|-------| +| `completed` | `#E6F4EA` | `#137333` | `#A8D5B5` | Scan finished, route confirmed | +| `running` | `#E8F0FE` | `#1557B0` | `#A8C7FA` | Scan in progress | +| `pending` | `#FEF7E0` | `#7A5200` | `#F9D659` | Queued, not started | +| `failed` | `#FDECEA` | `#A50E0E` | `#F5C6C6` | Error state | + +### Dark mode (implement via CSS variables) + +```css +/* light (default) */ +:root { + --color-bg: #F8F9FA; + --color-surface: #FFFFFF; + --color-surface-2: #F1F3F4; + --color-on-surface: #202124; + --color-on-surface-var: #5F6368; + --color-outline: #DADCE0; + --color-primary: #1A73E8; + --color-secondary: #0F9D58; +} + +/* dark */ +@media (prefers-color-scheme: dark) { + :root { + --color-bg: #1C1B1F; + --color-surface: #2C2B30; + --color-surface-2: #36343B; + --color-on-surface: #E6E1E5; + --color-on-surface-var: #938F99; + --color-outline: #49454F; + --color-primary: #A8C7FA; + --color-secondary: #81C995; + } +} +``` + +--- + +## 4. Typography + +### Font stack + +```css +font-family: 'Google Sans', 'Roboto', system-ui, -apple-system, sans-serif; +font-family-mono: 'Roboto Mono', 'JetBrains Mono', monospace; /* logs, IATA codes */ +``` + +> Load from Google Fonts: `Google Sans` (300, 400, 500) + `Roboto Mono` (400) + +### Type scale (MD3 aligned) + +| Token | Size | Weight | Line height | Usage | +|-------|------|--------|-------------|-------| +| `display-sm` | 36px / 2.25rem | 400 | 44px | Page hero (empty states) | +| `headline-lg` | 32px / 2rem | 400 | 40px | — | +| `headline-md` | 28px / 1.75rem | 400 | 36px | Page title | +| `headline-sm` | 24px / 1.5rem | 400 | 32px | Section title | +| `title-lg` | 22px / 1.375rem | 500 | 28px | Card title | +| `title-md` | 16px / 1rem | 500 | 24px | List item title, form label | +| `title-sm` | 14px / 0.875rem | 500 | 20px | Table header, tab label | +| `body-lg` | 16px / 1rem | 400 | 24px | Body copy, descriptions | +| `body-md` | 14px / 0.875rem | 400 | 20px | Secondary body, list metadata | +| `body-sm` | 12px / 0.75rem | 400 | 16px | Captions, helper text | +| `label-lg` | 14px / 0.875rem | 500 | 20px | Button labels | +| `label-md` | 12px / 0.75rem | 500 | 16px | Badge labels, chip labels | +| `label-sm` | 11px / 0.6875rem | 500 | 16px | Overlines, micro labels | +| `mono-md` | 13px / 0.8125rem | 400 | 20px | IATA codes, log messages | + +--- + +## 5. Spacing & Layout Grid + +### Spacing scale (4px base) + +``` +1 → 4px (tight gaps: icon-to-text, badge padding) +2 → 8px (small gaps: form field helper text) +3 → 12px (medium-small: list item inner padding) +4 → 16px (base unit: card inner padding mobile) +5 → 20px (—) +6 → 24px (card inner padding desktop, section gaps) +8 → 32px (between major sections) +10 → 40px (—) +12 → 48px (page top padding) +16 → 64px (large empty state illustrations) +``` + +### Layout grid + +**Mobile (< 600px)** +- 1 column, 16px margins, 16px gutters +- Full-width cards +- Bottom navigation bar (56px) + +**Tablet (600–1024px)** +- 8 columns, 24px margins, 24px gutters +- Side rail navigation (72px collapsed) +- Cards: 2-column grid for stats + +**Desktop (> 1024px)** +- 12 columns, 24px margins, 24px gutters +- Fixed sidebar navigation (256px) +- Max content width: 1280px +- Cards: 3–5 column grid for stats + +### Sidebar layout (desktop) + +``` +┌──────────┬────────────────────────────────┐ +│ │ Top bar (64px, search + user) │ +│ Sidebar ├────────────────────────────────┤ +│ (256px) │ │ +│ │ Main content area │ +│ Logo │ max-w: 1024px, centered │ +│ Nav │ px: 24px │ +│ items │ │ +│ │ │ +│ ────── │ │ +│ Logs │ │ +└──────────┴────────────────────────────────┘ +``` + +--- + +## 6. Elevation & Shadow + +MD3 uses **tonal elevation** (surface tint) as primary depth cue, with subtle shadows for resting state only. + +| Level | Shadow | Tint overlay | Usage | +|-------|--------|-------------|-------| +| 0 | none | 0% | Page background | +| 1 | `0 1px 2px rgba(0,0,0,.08)` | 5% primary | Cards (resting) | +| 2 | `0 2px 6px rgba(0,0,0,.10)` | 8% primary | Dropdowns, popovers | +| 3 | `0 4px 12px rgba(0,0,0,.12)` | 11% primary | Modals, dialogs | +| 4 | `0 6px 20px rgba(0,0,0,.14)` | 12% primary | Navigation drawer | + +--- + +## 7. Component Library + +### 7.1 Navigation + +#### Sidebar (desktop ≥ 1024px) + +``` +Width: 256px (expanded) / 72px (collapsed — future) +Background: Surface (white) +Border-right: 1px solid Outline (#DADCE0) + +Logo area (64px tall): + ✈ icon (24px, Primary blue) + "Flight Radar" (title-lg, On Surface) + +Nav item (48px tall, full width): + - Inactive: transparent bg, On Surface Var text, 16px side padding + - Active: Primary Container bg (#D2E3FC), On Primary Container text, + bold icon, rounded 28px pill shape (full bleed) + - Hover: Surface 2 bg, On Surface text + - Icon: 20px, left-aligned + - Label: body-md 500 weight, 12px left of icon + +Section divider: + Between main items and Logs: 1px divider + "Developer" label (label-sm, muted) + +Items (in order): + 🏠 Dashboard → / + 🔍 Scans → /scans + ✈ Airports → /airports + ── (divider) + 📋 Logs → /logs +``` + +#### Top app bar (desktop) + +``` +Height: 64px +Background: Surface (white) +Border-bottom: 1px solid Outline +Shadow: Level 1 on scroll + +Left: (sidebar occupies) +Center: Page title (headline-sm, On Surface) — OR breadcrumb +Right: + New Scan button (filled, primary) +``` + +#### Bottom navigation (mobile < 600px) + +``` +Height: 80px (includes safe area) +Background: Surface +Border-top: 1px solid Outline +Shadow: Level 2 (upward) + +4 items: Dashboard | Scans | Airports | Logs +Each: icon (24px) + label (label-md) stacked +Active: Primary colour icon + Primary Container indicator pill behind icon (56×32px) +Inactive: On Surface Var colour +``` + +--- + +### 7.2 Buttons + +#### Filled (primary action — one per page) + +``` +Background: Primary (#1A73E8) +Text: On Primary (#FFFFFF) +Height: 40px +Padding: 0 24px +Border-radius: 20px (fully rounded — MD3 style) +Font: label-lg (14px/500) +Shadow: Level 1 +Hover: darken 8% + Level 2 shadow +Pressed: darken 12%, Level 1 +Disabled: 38% opacity + +Icons: optional leading icon at 18px +``` + +#### Outlined (secondary action) + +``` +Border: 1px solid Outline (#DADCE0) +Text: Primary (#1A73E8) +Background: transparent +Same sizing/radius as filled +Hover: Primary Container bg (#D2E3FC) +``` + +#### Text (tertiary / destructive link) + +``` +Text: Primary or Error +No border, no background +Hover: surface tint +Padding: 0 12px +``` + +#### Icon button + +``` +Size: 40×40px +Shape: circle +Background: transparent → Surface 2 on hover +Icon: 20px, On Surface Var → On Surface on hover +``` + +--- + +### 7.3 Status Chip / Badge + +``` +Height: 24px +Padding: 0 10px +Border-radius: 12px (pill) +Font: label-md (12px/500) +Border: 1px solid (matching border column from §3) + +┌──────────────────────┐ +│ ● completed │ green bg, green text, green dot +│ ↻ running │ blue bg, blue text, spinning dot (CSS animation) +│ ⧖ pending │ amber bg, amber text, static dot +│ ✕ failed │ red bg, red text, X icon +└──────────────────────┘ +``` + +Implementation: The colored dot/icon is a 6px circle or SVG icon on the left at 8px margin-right. + +--- + +### 7.4 Stat Card + +Used on Dashboard (5 cards) and Scan Details (3 cards). + +``` +┌────────────────────────┐ +│ ┌──┐ │ +│ │ 🔍│ Total Scans │ ← icon in tinted circle (40px), label (body-sm, muted) +│ └──┘ │ +│ │ +│ 54 │ ← number (display-sm or headline-md) +│ │ +│ +3 today │ ← optional trend (body-sm, secondary/error) +└────────────────────────┘ + +Padding: 24px +Border-radius: 16px (MD3 large shape) +Background: Surface (white) +Shadow: Level 1 +Width: fluid (grid cell) + +Icon circle: 40×40px, border-radius 20px + Total: #E8F0FE bg, Primary icon + Pending: #FEF7E0 bg, Tertiary icon + Running: #E8F0FE bg, Primary icon (animated pulse) + Completed: #E6F4EA bg, Secondary icon + Failed: #FDECEA bg, Error icon +``` + +--- + +### 7.5 List / Scan Card + +Replaces the current flat `` row in the dashboard. + +``` +┌──────────────────────────────────────────────────────────┐ +│ BDS → FMM, DUS ● completed 25.2.2026 │ +│ 26 Feb 2026 – 27 May 2026 · 1 adult · Economy │ +│ │ +│ 182 routes · 50 flights found → │ +└──────────────────────────────────────────────────────────┘ + +Padding: 16px 20px +Border-radius: 12px (on hover, or always) +Background: Surface +Hover: Surface 2 bg + Level 2 shadow (lift effect) +Divider: 1px inside, OR 8px gap between cards (prefer cards) +Cursor: pointer + +Route arrow (→): On Surface Var, shifts right on hover (transform) + +Status chip: right-aligned in first row, always visible +Date metadata: body-sm, On Surface Var +Stats line: body-sm, On Surface Var, shown only when > 0 +``` + +--- + +### 7.6 Text Input & Form Field + +MD3 "Outlined" input variant. + +``` +Height: 56px +Border: 1px solid Outline +Border-radius: 4px (corners) — MD3 uses 4px for inputs (not pills) +Background: transparent (on Surface bg it appears inset) +Padding: 16px + +Labels: floating label (HTML standard — use