Page Layouts¶
This document defines the page-level layout specifications for each route. Component visual specs are in UI_COMPONENTS.md. Responsive adaptation rules are in RESPONSIVE_STRATEGY.md. Design tokens referenced here are defined in DESIGN_SYSTEM.md.
Related Documents¶
| Document | Scope |
|---|---|
| Design System | Tokens for colors, spacing, typography, shadows |
| UI Components | Component anatomy, states, and data-driven rendering rules |
| Responsive Strategy | Breakpoints and viewport-specific behavior |
| Schema Reference | Required vs optional vehicle data fields |
Table of Contents¶
Global Layout¶
The AppLayout wraps all pages and provides the persistent header and optional bottom navigation.
Desktop Structure¶
block-beta
columns 3
Header["HEADER: OpenEV Data Explorer [Search]"]:3
Space1:1
Main["<Outlet /> (Page Content)"]:1
Space2:1
style Header fill:#1f2937,stroke:#374151,color:#fff
style Main fill:#111827,stroke:#374151,color:#fff
style Space1 fill:none,stroke:none
style Space2 fill:none,stroke:none Mobile Structure¶
block-beta
columns 1
Header["HEADER (compact)"]:1
Main["<Outlet /> (Page Content)"]:1
Nav["BOTTOM NAV"]:1
style Header fill:#1f2937,stroke:#374151,color:#fff
style Main fill:#111827,stroke:#374151,color:#fff
style Nav fill:#1f2937,stroke:#374151,color:#fff Header Specs¶
| Property | Value |
|---|---|
| Height | 64px (desktop), 56px (mobile) |
| Background | --bg-surface-1 with backdrop-filter: blur(12px) |
| Border | 1px solid var(--border-subtle) bottom |
| Position | sticky, top: 0 |
| Z-Index | --z-sticky |
| Logo Font | --text-lg, --weight-bold, --text-primary |
| Logo Icon | ⚡ or custom SVG, --text-accent |
| Padding | 0 var(--space-6) (desktop), 0 var(--space-4) (mobile) |
Bottom Navigation (Mobile Only)¶
| Property | Value |
|---|---|
| Height | 56px |
| Background | --bg-surface-1 with backdrop-filter: blur(12px) |
| Border | 1px solid var(--border-subtle) top |
| Position | fixed, bottom: 0 |
| Z-Index | --z-sticky |
| Icon Size | --icon-lg (24px) |
| Label Font | --text-xs, --weight-medium |
| Active Color | --text-accent |
| Inactive Color | --text-tertiary |
Home Page¶
Route: / Component: HomePage Reference: A01 Desktop · M01 Mobile
Desktop Layout¶
block-beta
columns 4
Header["HEADER"]:4
block:Content:4
columns 2
Filter["FILTER PANEL (240px fixed)"]
block:Right
columns 1
TopBar["All Cards | Search"]
block:Grid
columns 4
Card1["Card"]
Card2["Card"]
Card3["Card"]
Card4["Card"]
Card5["Card"]
Card6["Card"]
Card7["Card"]
Card8["Card"]
end
Pagination["[Pagination]"]
end
end
style Header fill:#1f2937,stroke:#374151,color:#fff
style Filter fill:#1f2937,stroke:#374151,color:#fff
style TopBar fill:#374151,stroke:#4b5563,color:#fff
style Pagination fill:#374151,stroke:#4b5563,color:#fff
style Card1 fill:#1f2937,stroke:#4b5563,color:#fff
style Card2 fill:#1f2937,stroke:#4b5563,color:#fff
style Card3 fill:#1f2937,stroke:#4b5563,color:#fff
style Card4 fill:#1f2937,stroke:#4b5563,color:#fff
style Card5 fill:#1f2937,stroke:#4b5563,color:#fff
style Card6 fill:#1f2937,stroke:#4b5563,color:#fff
style Card7 fill:#1f2937,stroke:#4b5563,color:#fff
style Card8 fill:#1f2937,stroke:#4b5563,color:#fff Mobile Layout¶
block-beta
columns 1
Header["HEADER"]:1
Filters["[Filters & Sort ▾]"]:1
block:List
columns 1
Card1["[Img] Name | Stats | [Compare]"]:1
Card2["[Img] Name | Stats | [Compare]"]:1
More["...more cards"]:1
end
Nav["BOTTOM NAV"]:1
style Header fill:#1f2937,stroke:#374151,color:#fff
style Filters fill:#374151,stroke:#4b5563,color:#fff
style Nav fill:#1f2937,stroke:#374151,color:#fff
style Card1 fill:#1f2937,stroke:#4b5563,color:#fff
style Card2 fill:#1f2937,stroke:#4b5563,color:#fff Page Component Hierarchy¶
graph TD
HomePage --> FilterPanel
HomePage --> ContentHeader
ContentHeader --> ViewToggle
ContentHeader --> SearchInput
HomePage --> VehicleGrid
VehicleGrid --> VehicleCard
HomePage --> Pagination Spec Details¶
| Property | Desktop | Mobile |
|---|---|---|
| Main layout | display: grid; grid-template-columns: 240px 1fr | Single column |
| Content area padding | --space-6 | --space-4 |
| Card grid gap | --space-6 | --space-4 |
| Grid columns | repeat(auto-fill, minmax(240px, 1fr)) | 1fr |
| Secondary search | Top-right of content area | Hidden (uses header search) |
| Filter position | Sticky sidebar | Collapsible at top |
Comparison Overlay (Active)¶
Reference: A02 Desktop · M02 Mobile
When the user opens the comparison view from the Home Page:
Desktop: A panel slides in from the right side, overlaying ~40% of the viewport width. The card grid content is visible but dimmed behind the panel.
block-beta
columns 3
Filter["FILTER PANEL"]:1
Grid["Vehicle Grid (dimmed)"]:1
Panel["COMPARISON PANEL (~40% width)"]:1
style Filter fill:#1f2937,stroke:#374151,color:#fff
style Grid fill:#111827,stroke:#374151,color:#bbb
style Panel fill:#1f2937,stroke:#374151,color:#fff,stroke-width:2px Mobile: Full-screen overlay replacing the card list. The comparison panel takes 100% of the viewport with a "Close" button at top-right.
| Property | Desktop | Mobile |
|---|---|---|
| Panel width | ~40vw, min 380px | 100vw |
| Animation | slide-in-right, --duration-slow | slide-in-right or full opacity fade |
| Overlay bg | hsla(0,0%,0%,0.4) behind panel | None (full takeover) |
| Close button | × ghost button, top-right | "Close" text button, top-right |
Vehicle Detail Page¶
Route: /vehicle/:code Component: VehicleDetailPage Reference: A03 Desktop · M03 Mobile
Desktop Layout¶
block-beta
columns 4
Header["HEADER"]:4
Sidebar["Sidebar"]:1
Main["Main Content"]:3
block:SidebarContent
Back["← Back"]:1
Image["[Vehicle Image]"]:1
Highlights["Highlights List"]:1
Technology["Technology"]:1
end
block:MainContent
Title["Make Model Trim"]:1
Tabs["Overview | Charging | Range | Specs"]:1
Pills["Price | Range | Battery | 0-100"]:1
block:Charts
columns 2
Chart1["Charging Curve"]:1
Chart2["Range Estimates"]:1
end
end
style Header fill:#1f2937,stroke:#374151,color:#fff
style Sidebar fill:#1f2937,stroke:#374151,color:#fff
style Main fill:#111827,stroke:#374151,color:#fff Mobile Layout¶
block-beta
columns 1
Header["HEADER"]:1
Back["← Back"]:1
Title["BYD Seal AWD Performance"]:1
Image["[Vehicle Image]"]:1
Pills["Price | Range | Battery | 0-100"]:1
Tabs["Overview | Charging..."]:1
Highlights["Highlights"]:1
Charts["Range Estimates"]:1
Compare["Compare (Sticky)"]:1
Nav["BOTTOM NAV"]:1
style Header fill:#1f2937,stroke:#374151,color:#fff
style Compare fill:#10b981,stroke:#059669,color:#fff
style Nav fill:#1f2937,stroke:#374151,color:#fff Page Component Hierarchy¶
graph TD
VehicleDetailPage --> BackButton
VehicleDetailPage --> VehicleHeader
VehicleHeader --> VehicleName
VehicleHeader --> VehicleTags
VehicleDetailPage --> VehicleImage
VehicleDetailPage --> SpecsPills
VehicleDetailPage --> TabBar
VehicleDetailPage --> TabContent
TabContent --> OverviewTab
OverviewTab --> HighlightsList
OverviewTab --> ChargingOverviewTable
TabContent --> ChargingCurveTab
TabContent --> RangeScenariosTab
TabContent --> FullSpecsTab
VehicleDetailPage --> CompareButton [!IMPORTANT] Data-driven tab visibility: The tab bar only renders tabs whose underlying data exists for the current vehicle. The Overview tab is always present. Charging Curve requires
charging.dc_charge_curve. Range Scenarios requiresrange.real_world[]. Full Specs is always present but each section within it collapses when empty. See Data-Driven Rendering Principle for implementation details.
Spec Details¶
| Property | Desktop | Mobile |
|---|---|---|
| Layout | 2-column: sidebar 280px + main area | Single column |
| Sidebar bg | --bg-surface-1 | — |
| Image position | Sidebar, centered | Full width, below title |
| Image max height | 200px | 200px |
| Spec pills | Horizontal row in main area | Horizontal scroll |
| Tab bar position | Below spec pills in main | Below spec pills |
| Charts layout | Side by side (2 cols) | Stacked vertically |
| Compare button | Top-right area, ghost style | Sticky bottom, primary CTA |
Compare Page¶
Route: /compare Component: ComparePage
The standalone Compare Page provides a full-page comparison experience. This differs from the comparison panel overlay on the Home Page.
Layout¶
block-beta
columns 1
Header["HEADER"]:1
block:Page
Title["Comparison (X)"]
block:Grid
columns 3
V1["Vehicle 1 \n [Image] \n Tesla M3"]
V2["Vehicle 2 \n [Image] \n BYD Seal"]
V3["Vehicle 3 \n [Image] \n Porsche T"]
R1["Range | Price | 0-100"]
R2["Range | Price | 0-100"]
R3["Range | Price | 0-100"]
Chart1["[Radar] \n [Bars]"]
Chart2["[Radar] \n [Bars]"]
Chart3["[Radar] \n [Bars]"]
end
end
style Header fill:#1f2937,stroke:#374151,color:#fff
style Page fill:#111827,stroke:#374151,color:#fff
style V1 fill:#1f2937,stroke:#374151,color:#fff
style V2 fill:#1f2937,stroke:#374151,color:#fff
style V3 fill:#1f2937,stroke:#374151,color:#fff Comparison Data Sections¶
All comparison rows are data-driven. A row only renders if at least one of the compared vehicles has data for that field. If a vehicle is missing a field that others have, display "—" in that cell. See rendering rules.
- Key Metrics Table: Range, Price, 0-100 km/h, Battery — rows filtered by data availability
- Radar Charts: Only shown if enough metrics exist for a meaningful polygon (≥ 3 axes)
- Efficiency Bars: Only shown if
efficiency.energy_consumption_wh_per_kmexists for at least one vehicle - Charging Comparison: AC max power, DC max power, architecture — from required
chargingobject
Spec Details¶
| Property | Value |
|---|---|
| Max vehicles | 3 |
| Column min width | 200px (desktop), 160px (mobile) |
| Column header bg | --bg-glass |
| Row border | --border-subtle |
| Best value | --text-accent, --weight-bold |
| Radar chart size | 160px × 160px (desktop), 120px × 120px (mobile) |
| Radar colors | --chart-cyan, --chart-green, --chart-teal per vehicle |
| Remove button (×) | ghost button, top-right of column header |
Not Found Page¶
Route: * Component: NotFoundPage
Layout¶
block-beta
columns 1
Header["HEADER"]:1
Space1:1
Icon["⚠️ (404 Icon)"]:1
Title["Page Not Found"]:1
Msg["The page you're looking for doesn't exist."]:1
Btn["[ Go to Home ]"]:1
Space2:1
style Header fill:#1f2937,stroke:#374151,color:#fff
style Icon fill:none,stroke:none,color:#eab308
style Btn fill:#10b981,stroke:#059669,color:#fff Specs¶
| Property | Value |
|---|---|
| Layout | Flex column, centered both axes |
| Min height | calc(100vh - header height) |
| Icon | --icon-xl (32px), --color-warning |
| Title | --text-3xl, --weight-bold |
| Message | --text-base, --text-secondary |
| Button | primary variant, lg size |
Loading and Error States¶
Initial Loading (Full Page)¶
When the application first loads and fetches the vehicle dataset:
block-beta
columns 1
Header["HEADER"]:1
Space1:1
Spinner["(Spinner lg)"]:1
Text["Loading vehicle data..."]:1
Space2:1
style Header fill:#1f2937,stroke:#374151,color:#fff
style Spinner fill:none,stroke:none Skeleton Loading (Cards)¶
While data is being processed/filtered, card skeletons maintain layout stability:
block-beta
columns 1
Image["[Image Placeholder (pulse)]"]:1
Title["[Title Placeholder]"]:1
Stats["[Stats Placeholder]"]:1
Btn["[Button Placeholder]"]:1
style Image fill:#1f2937,stroke:none,color:#6b7280
style Title fill:#1f2937,stroke:none,color:#6b7280
style Stats fill:#1f2937,stroke:none,color:#6b7280
style Btn fill:#1f2937,stroke:none,color:#6b7280 Skeleton Specs¶
| Property | Value |
|---|---|
| Background | --bg-surface-2 |
| Animated gradient | linear-gradient(90deg, transparent, hsla(210,20%,30%,0.2), transparent) |
| Animation | pulse keyframe, 1.5s infinite |
| Border radius | Matches target element |
| Height | Matches expected content height |
Error State (API Failure)¶
block-beta
columns 1
Header["HEADER"]:1
Space1:1
Icon["⚠️ (Alert Icon)"]:1
Title["Unable to load vehicle data"]:1
Msg["Please check your connection and try again."]:1
Btn["[ Retry ]"]:1
Space2:1
style Header fill:#1f2937,stroke:#374151,color:#fff
style Icon fill:none,stroke:none,color:#ef4444
style Btn fill:#10b981,stroke:#059669,color:#fff | Property | Value |
|---|---|
| Component | ErrorFallback from shared/ui/ |
| Icon | Alert triangle, --color-warning |
| Title | --text-xl, --weight-semibold |
| Message | --text-base, --text-secondary |
| Retry button | primary variant |