diff --git a/flight-comparator/docs/PRD_DESIGN_SYSTEM.md b/flight-comparator/docs/PRD_DESIGN_SYSTEM.md
new file mode 100644
index 0000000..b65628f
--- /dev/null
+++ b/flight-comparator/docs/PRD_DESIGN_SYSTEM.md
@@ -0,0 +1,862 @@
+# PRD: Design System Implementation
+
+**Flight Radar Web Interface — Visual Redesign**
+
+| | |
+|---|---|
+| **Version** | 1.0 |
+| **Status** | Ready for Implementation |
+| **Date** | February 2026 |
+| **Design reference** | `docs/DESIGN_SYSTEM.md` |
+| **Target** | Claude Code Implementation |
+
+---
+
+## 1. Overview
+
+The Flight Radar web interface was built as a functional prototype. It works correctly but has no visual design system: navigation links run together into an unreadable string, stat cards are unstyled white boxes, forms lack any grouping or hierarchy, and the application is unusable on mobile devices.
+
+This PRD specifies the complete implementation of the Material Design 3–inspired design system defined in `docs/DESIGN_SYSTEM.md`. The result must be a production-quality UI that a non-technical user would consider polished and trustworthy.
+
+**No new features are added in this PRD.** Every existing function is preserved. This is a pure UI/UX replacement.
+
+---
+
+## 2. Goals
+
+- Implement the full design token system (colours, typography, spacing, elevation) from `docs/DESIGN_SYSTEM.md §3–§6`
+- Replace the broken top navigation with a proper sidebar on desktop and a bottom navigation bar on mobile
+- Redesign all 5 pages using the component specifications in `docs/DESIGN_SYSTEM.md §7–§8`
+- Build a reusable component library that makes future pages easy to build consistently
+- Achieve a fully functional, accessible mobile experience at 375px viewport width
+- Pass a manual accessibility review: keyboard navigation, colour contrast (WCAG AA), and focus management
+
+## 3. Non-Goals
+
+- No new API endpoints or backend changes
+- No dark mode (design tokens must be structured to support it later, but it need not be active)
+- No animations that require a JS animation library (CSS transitions only)
+- No redesign of the `AirportSearch` dropdown's behaviour — only its visual styling
+- No changes to `api.ts`, `App.tsx`, routing, or any Python backend file
+- No new pages beyond the existing 5
+
+---
+
+## 4. Success Criteria
+
+| Criterion | Measure |
+|-----------|---------|
+| Navigation is readable | Nav items are separated, active state is visually distinct, works on 375px |
+| Stats are scannable | Each stat card has an icon, colour accent, and label — no two look identical |
+| Scan list is parseable | Destination, status chip, date range, and flight count are each in distinct visual zones |
+| Form feels structured | Field groups are visually separated, labels float on focus, help text is present |
+| Empty states have actions | Every zero-data view has an illustration, headline, and a CTA button |
+| Loading has feedback | Every async wait shows a skeleton or spinner — never a blank area |
+| Mobile is functional | All 5 pages are usable at 375px — no horizontal overflow, no overlapping elements |
+| Accessibility baseline | All interactive elements ≥ 44×44px, focus rings visible, colour not sole differentiator |
+
+---
+
+## 5. Constraints
+
+- **Tech stack is fixed:** React 19, TypeScript strict mode, Tailwind CSS v4, Vite 7
+- **No CSS-in-JS:** all styling via Tailwind utility classes + custom CSS variables in `index.css`
+- **Tailwind v4 `@theme` syntax:** token definitions use `@theme { }` block, not `tailwind.config.js`
+- **Type-only imports:** `import type { X }` required for all interface/type imports (TypeScript strict)
+- **No jQuery, no Bootstrap, no external UI framework** (MUI, Chakra, Ant Design etc.)
+- **Three allowed new dependencies:** `lucide-react`, `clsx`, `tailwind-merge`
+
+---
+
+## 6. Implementation Phases
+
+The work is divided into 5 phases that must be executed in order, because each phase depends on the previous one. A phase is complete only when all its acceptance criteria pass.
+
+---
+
+### Phase 1 — Foundation (tokens, fonts, layout shell)
+
+**Objective:** Every page already looks better before a single page component is touched, because the global tokens and navigation are correct.
+
+#### 1.1 Design tokens
+
+**File:** `frontend/src/index.css`
+
+Replace the existing 3-line Tailwind import with:
+
+```css
+@import "tailwindcss";
+
+@theme {
+ /* Brand colours */
+ --color-primary: #1A73E8;
+ --color-primary-container: #D2E3FC;
+ --color-on-primary: #FFFFFF;
+ --color-on-primary-container: #003E8C;
+ --color-secondary: #0F9D58;
+ --color-secondary-container: #C8E6C9;
+ --color-on-secondary: #FFFFFF;
+ --color-on-secondary-container: #00391A;
+ --color-tertiary: #F4B400;
+ --color-tertiary-container: #FFF0C0;
+ --color-on-tertiary: #3B2A00;
+ --color-error: #D93025;
+ --color-error-container: #FDECEA;
+ --color-on-error: #FFFFFF;
+
+ /* Surface roles */
+ --color-bg: #F8F9FA;
+ --color-surface: #FFFFFF;
+ --color-surface-2: #F1F3F4;
+ --color-surface-variant: #E8EAED;
+ --color-on-surface: #202124;
+ --color-on-surface-variant: #5F6368;
+ --color-outline: #DADCE0;
+ --color-outline-variant: #F1F3F4;
+
+ /* Semantic status colours (used via CSS variables, not Tailwind classes) */
+ --color-status-completed-bg: #E6F4EA;
+ --color-status-completed-text: #137333;
+ --color-status-completed-border: #A8D5B5;
+ --color-status-running-bg: #E8F0FE;
+ --color-status-running-text: #1557B0;
+ --color-status-running-border: #A8C7FA;
+ --color-status-pending-bg: #FEF7E0;
+ --color-status-pending-text: #7A5200;
+ --color-status-pending-border: #F9D659;
+ --color-status-failed-bg: #FDECEA;
+ --color-status-failed-text: #A50E0E;
+ --color-status-failed-border: #F5C6C6;
+
+ /* Shape */
+ --radius-xs: 4px;
+ --radius-sm: 8px;
+ --radius-md: 12px;
+ --radius-lg: 16px;
+ --radius-xl: 20px;
+ --radius-full: 9999px;
+
+ /* Typography */
+ --font-sans: 'Google Sans', 'Roboto', system-ui, -apple-system, sans-serif;
+ --font-mono: 'Roboto Mono', 'JetBrains Mono', ui-monospace, monospace;
+
+ /* Elevation shadows */
+ --shadow-1: 0 1px 2px rgba(0,0,0,.08);
+ --shadow-2: 0 2px 6px rgba(0,0,0,.10);
+ --shadow-3: 0 4px 12px rgba(0,0,0,.12);
+}
+
+/* Base resets */
+body {
+ background-color: var(--color-bg);
+ color: var(--color-on-surface);
+ font-family: var(--font-sans);
+ -webkit-font-smoothing: antialiased;
+}
+
+/* Skeleton shimmer */
+@keyframes shimmer {
+ 0% { background-position: -200% 0; }
+ 100% { background-position: 200% 0; }
+}
+.skeleton {
+ background: linear-gradient(90deg, #E8EAED 25%, #F1F3F4 50%, #E8EAED 75%);
+ background-size: 200% 100%;
+ animation: shimmer 1.5s infinite;
+}
+
+/* Slide-up toast animation (keep existing) */
+@keyframes slide-up {
+ from { transform: translateY(100%); opacity: 0; }
+ to { transform: translateY(0); opacity: 1; }
+}
+.animate-slide-up { animation: slide-up 0.3s ease-out; }
+
+/* Spin for loading icons */
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
+.animate-spin-slow { animation: spin 0.6s linear infinite; }
+
+/* Reduced motion */
+@media (prefers-reduced-motion: reduce) {
+ *, *::before, *::after {
+ animation-duration: 0.01ms !important;
+ transition-duration: 0.01ms !important;
+ }
+}
+```
+
+#### 1.2 Google Fonts
+
+**File:** `frontend/index.html`
+
+Add inside `
`:
+
+```html
+
+
+
+```
+
+#### 1.3 New dependencies
+
+```bash
+npm install lucide-react clsx tailwind-merge
+```
+
+#### 1.4 Layout shell redesign
+
+**File:** `frontend/src/components/Layout.tsx`
+
+Full replacement. The new layout has two distinct structures based on viewport:
+
+**Desktop (≥ 1024px):**
+```
+┌──────────┬──────────────────────────────────────────┐
+│ Sidebar │ Top bar (64px) │
+│ (256px) ├──────────────────────────────────────────┤
+│ │ │
+│ Logo │ wrapped in │
+│ Nav │ max-w-5xl, px-6, py-8 │
+│ │ │
+└──────────┴──────────────────────────────────────────┘
+```
+
+**Mobile (< 1024px):**
+```
+┌──────────────────────────────────────────────────┐
+│ Top bar (56px): logo + page title │
+├──────────────────────────────────────────────────┤
+│ │
+│ px-4 py-4, pb-24 (room for nav) │
+│ │
+├──────────────────────────────────────────────────┤
+│ Bottom nav bar (56px + safe-area-bottom) │
+└──────────────────────────────────────────────────┘
+```
+
+**Sidebar nav item spec:**
+- Height: 48px, padding: 0 16px, border-radius: 28px (full bleed within sidebar)
+- Active: `bg-primary-container text-on-primary-container font-medium`
+- Inactive: `text-on-surface-variant hover:bg-surface-2`
+- Icon: 20px Lucide icon, left-aligned, `mr-3`
+- The active pill is the full row width minus 12px horizontal margin on each side
+
+**Bottom nav item spec:**
+- 4 items equally spaced
+- Active: icon in Primary colour, 32×16px Primary Container pill behind icon, label in Primary
+- Inactive: icon + label in `on-surface-variant`
+- Label: 12px, 500 weight
+
+**Nav items and routes:**
+```
+Icon Label Route
+LayoutDashboard Dashboard /
+ScanSearch Scans /scans
+MapPin Airports /airports
+ScrollText Logs /logs
+```
+
+**Top bar (desktop):**
+- Height: 64px, `bg-surface border-b border-outline shadow-1`
+- Left: empty (sidebar is present)
+- Center/Left: current page title via `useLocation()` lookup
+- Right: `+ New Scan` filled button (navigates to `/scans`), hidden on `/scans` page itself
+
+**Top bar (mobile):**
+- Height: 56px
+- Left: ✈ `PlaneTakeoff` icon (20px, Primary) + "Flight Radar" text (title-lg)
+- Right: `+ New Scan` icon button (`Plus` icon, 40×40px) on dashboard only
+
+**Phase 1 acceptance criteria:**
+- [ ] Sidebar is visible on desktop, hidden on mobile
+- [ ] Bottom nav is visible on mobile, hidden on desktop
+- [ ] Active nav item is visually distinct from inactive
+- [ ] No horizontal scrollbar at any viewport width ≥ 320px
+- [ ] Google Sans font is loading and applied
+- [ ] `bg-primary` Tailwind class resolves to `#1A73E8`
+
+---
+
+### Phase 2 — Shared Component Library
+
+**Objective:** Create all reusable components before touching any page. Pages are then assembled from these building blocks.
+
+All new component files live in `frontend/src/components/`.
+
+#### 2.1 `Button.tsx`
+
+Props: `variant: 'filled' | 'outlined' | 'text' | 'icon'`, `size: 'sm' | 'md'` (default `md`), `loading?: boolean`, `icon?: LucideIcon`, `iconPosition?: 'left' | 'right'`, `disabled?`, `onClick?`, `type?`, `className?`, `children`
+
+Spec per variant — see `docs/DESIGN_SYSTEM.md §7.2`.
+
+The `loading` prop replaces children with a 16px spinner icon; button width is preserved via `min-w` matching the expected label width.
+
+#### 2.2 `StatusChip.tsx`
+
+Props: `status: 'completed' | 'running' | 'pending' | 'failed'`
+
+Spec: `docs/DESIGN_SYSTEM.md §7.3`.
+
+The chip reads its colours directly from the CSS variables `--color-status-{status}-bg` etc., applied inline or via a class map. The `running` status dot pulses using a CSS animation class.
+
+Icon mapping:
+```
+completed → CheckCircle2 (16px)
+running → Loader2 (16px, animate-spin-slow)
+pending → Clock (16px)
+failed → XCircle (16px)
+```
+
+#### 2.3 `StatCard.tsx`
+
+Props: `label: string`, `value: number | string`, `icon: LucideIcon`, `variant: 'default' | 'primary' | 'secondary' | 'tertiary' | 'error'`, `trend?: string`
+
+Spec: `docs/DESIGN_SYSTEM.md §7.4`.
+
+Icon circle size: 40×40px. Colours per variant:
+```
+default #E8F0FE bg, Primary icon
+primary #E8F0FE bg, Primary icon
+secondary #E6F4EA bg, Secondary icon
+tertiary #FEF7E0 bg, Tertiary icon
+error #FDECEA bg, Error icon
+```
+
+Value typography: 28px (headline-md), 400 weight, `text-on-surface`.
+
+#### 2.4 `EmptyState.tsx`
+
+Props: `icon: LucideIcon`, `title: string`, `description?: string`, `action?: { label: string; onClick: () => void }`
+
+Layout: centred column, icon at 64px in a 96×96px circle with `bg-surface-2`, headline-sm title, body-md description in `text-on-surface-variant`, optional filled primary button.
+
+#### 2.5 `SkeletonCard.tsx`
+
+Props: `rows?: number` (default 3), `showHeader?: boolean`
+
+Renders an animated shimmer card placeholder. Uses the `.skeleton` CSS class from Phase 1.
+
+Variants to export separately:
+- `` — matches StatCard dimensions
+- `` — matches the scan list card height (72px)
+- `` — matches a table row (52px)
+
+#### 2.6 `SegmentedButton.tsx`
+
+Props: `options: Array<{ value: string; label: string; icon?: LucideIcon }>`, `value: string`, `onChange: (value: string) => void`
+
+Spec: `docs/DESIGN_SYSTEM.md §7.7`.
+
+The active segment shows a `Check` icon (16px) that slides in from the left using a CSS opacity+translate transition.
+
+#### 2.7 `AirportChip.tsx`
+
+Props: `code: string`, `onRemove: () => void`
+
+Spec: `docs/DESIGN_SYSTEM.md §7.8`.
+
+Shows a `PlaneTakeoff` icon (14px) before the code. The `×` button uses an `X` Lucide icon.
+
+#### 2.8 `Toast.tsx` (update existing)
+
+The existing `Toast.tsx` already handles dismissal logic. Update **only** the visual styling to match `docs/DESIGN_SYSTEM.md §7.12`.
+
+Changes needed:
+- New width (360px desktop / full-width mobile), border-radius 12px, shadow-3
+- Position: bottom-right on desktop (`fixed bottom-6 right-6`), bottom-center on mobile
+- Four variants: `success`, `error`, `info`, `warning` — each with distinct icon and colour per the design spec
+- Keep the existing `animate-slide-up` class
+
+**Phase 2 acceptance criteria:**
+- [ ] All 8 components render without TypeScript errors (`npm run build` passes)
+- [ ] `StatusChip` shows correct colour and icon for all 4 statuses
+- [ ] `SkeletonCard` shimmers
+- [ ] `SegmentedButton` check icon appears on the active segment
+- [ ] `Button` loading state preserves button width
+
+---
+
+### Phase 3 — Dashboard page
+
+**File:** `frontend/src/pages/Dashboard.tsx`
+
+**Objective:** Replace the current text-dump dashboard with a stats + card-list layout using Phase 2 components.
+
+#### 3.1 Loading state
+
+While `loading === true`, render:
+- 5× `` in the stats grid
+- 5× `` in the recent scans section
+
+Do **not** show a spinner or "Loading…" text.
+
+#### 3.2 Stats grid
+
+Replace the 5 plain white boxes with `` components:
+
+```
+
+
+
+
+
+```
+
+Grid: `grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4`
+
+#### 3.3 Recent scans section
+
+Header row: `"Recent Scans"` (title-lg, `text-on-surface`) + `"View all →"` text button (right-aligned, links to a future full scan list — for now it links to `/scans`).
+
+Each scan renders as a card (not a `
`). Card structure:
+
+```
+┌──────────────────────────────────────────────────────┐
+│ BDS → FMM, DUS ● completed → │
+│ 26 Feb 2026 – 27 May 2026 · 1 adult · Economy │
+│ 182 routes · 50 flights found │
+└──────────────────────────────────────────────────────┘
+```
+
+- Arrow `→` (`ArrowRight`, 16px, `text-on-surface-variant`) right-aligned
+- On hover: card lifts (`shadow-2`, `translateY(-2px)`) via CSS transition 150ms
+- `` wraps the entire card (no nested interactive elements inside except the chip)
+- Date string: `formatDate` replaced with a relative label where applicable (e.g. "Today", "Yesterday", or absolute date)
+
+#### 3.4 Empty state
+
+When `scans.length === 0`:
+```
+ navigate('/scans') }}
+/>
+```
+
+**Phase 3 acceptance criteria:**
+- [ ] Skeleton shows while loading, disappears when data arrives
+- [ ] Each of 5 stat cards has a distinct icon and colour accent
+- [ ] Scan cards are separated, hoverable, and fully clickable
+- [ ] Status chip is visually separate from destination text (no more `"FMM,DUScompleted"`)
+- [ ] Empty state renders with button when no scans exist
+
+---
+
+### Phase 4 — Create Scan form
+
+**File:** `frontend/src/pages/Scans.tsx`
+
+**Objective:** Transform the flat HTML form into a structured, guided form with clear visual grouping.
+
+#### 4.1 Page structure
+
+```
+"New Scan" (headline-md) [← Cancel — text button]
+
+┌ Section: Origin ─────────────────────────────────┐
+│ Origin Airport │
+│ [AirportSearch input — full width] │
+│ Help text: "3-letter IATA code (e.g. BDS)" │
+└───────────────────────────────────────────────────┘
+
+┌ Section: Destination ────────────────────────────┐
+│ [ 🌍 By Country | ✈ By Airports ] ← SegmentedButton │
+│ │
+│ [Country input — if country mode] │
+│ [AirportSearch + AirportChip list — if airports] │
+└───────────────────────────────────────────────────┘
+
+┌ Section: Parameters ─────────────────────────────┐
+│ [Search Window] [Seat Class] │
+│ [Adults] │
+└───────────────────────────────────────────────────┘
+
+ [Create Scan — filled button]
+```
+
+Each `┌ Section ┐` is a `bg-surface rounded-lg shadow-1 p-6` card with a section label in `label-sm uppercase text-on-surface-variant tracking-wider mb-4`.
+
+#### 4.2 Destination mode toggle
+
+Replace the two `