Updated DESIGN.md to reflect a new editorial design approach with a navy palette and refined typography. Enhanced global styles in globals.css, including new color tokens and layout adjustments. Refactored components in AppShell, Sidebar, and Topbar for improved consistency and theming. Introduced a ThemeToggle component for user theme preferences and updated various pages to utilize new styles and components, ensuring a cohesive user experience across the application.
444 lines
18 KiB
Markdown
444 lines
18 KiB
Markdown
---
|
||
version: alpha
|
||
name: ai-video-admin-design
|
||
description: A quietly editorial admin surface for managing AI video assistants, rendered in a navy palette with both a dark and a light theme. The dark canvas is deep navy (`#070b16`) holding off-white ink (`#f1f5ff`); the light canvas is a cool off-white (`#f3f5fb`) holding deep-navy ink (`#0c1426`). Brand voltage is photographic, not chromatic — soft pastel atmospheric gradient orbs (mint → peach → lavender → sky → rose) drift behind hero copy as the only "color" moments. Display runs Cormorant Garamond Light at weight 300 — the editorial signature; Inter carries body, navigation, captions. CTAs are subtle pills: a deep-navy ink pill in light, inverting to an off-white pill in dark. There is no neon accent and no saturated CTA color.
|
||
|
||
colors:
|
||
primary: "#1b2741"
|
||
primary-active: "#0c1426"
|
||
ink: "#0c1426"
|
||
body: "#44516c"
|
||
body-strong: "#1b2741"
|
||
muted: "#5d6b86"
|
||
muted-soft: "#94a0bd"
|
||
hairline: "#e3e7f1"
|
||
hairline-soft: "#eef1f8"
|
||
hairline-strong: "#cbd3e4"
|
||
canvas: "#f3f5fb"
|
||
canvas-soft: "#f9fafd"
|
||
surface-card: "#ffffff"
|
||
surface-strong: "#e9edf7"
|
||
on-primary: "#ffffff"
|
||
gradient-mint: "#a7e5d3"
|
||
gradient-peach: "#f4c5a8"
|
||
gradient-lavender: "#c8b8e0"
|
||
gradient-sky: "#a8c8e8"
|
||
gradient-rose: "#e8b8c4"
|
||
semantic-error: "#dc2626"
|
||
semantic-success: "#16a34a"
|
||
|
||
colors-dark:
|
||
primary: "#e8edf9"
|
||
primary-foreground: "#0c1426"
|
||
ink: "#f1f5ff"
|
||
foreground: "#e8edf9"
|
||
body: "#a6b2cb"
|
||
muted: "#93a0bb"
|
||
muted-soft: "#6c7a96"
|
||
hairline: "#1b2740"
|
||
hairline-soft: "#141d30"
|
||
hairline-strong: "#283450"
|
||
canvas: "#070b16"
|
||
canvas-soft: "#0b1322"
|
||
surface-card: "#0e1626"
|
||
surface-strong: "#18233a"
|
||
gradient-mint: "#5fae9b"
|
||
gradient-peach: "#c08a6b"
|
||
gradient-lavender: "#8a78ad"
|
||
gradient-sky: "#5f86b8"
|
||
gradient-rose: "#b07d8c"
|
||
semantic-error: "#f87171"
|
||
semantic-success: "#4ade80"
|
||
|
||
typography:
|
||
display-mega:
|
||
fontFamily: "'Cormorant Garamond', 'Times New Roman', serif"
|
||
fontSize: clamp(2.5rem, 5vw, 4rem)
|
||
fontWeight: 300
|
||
lineHeight: 1.05
|
||
letterSpacing: -0.03em
|
||
display-xl:
|
||
fontFamily: "'Cormorant Garamond', serif"
|
||
fontSize: clamp(2rem, 4vw, 3rem)
|
||
fontWeight: 300
|
||
lineHeight: 1.08
|
||
letterSpacing: -0.02em
|
||
display-lg:
|
||
fontFamily: "'Cormorant Garamond', serif"
|
||
fontSize: 36px
|
||
fontWeight: 300
|
||
lineHeight: 1.17
|
||
letterSpacing: -0.01em
|
||
display-md:
|
||
fontFamily: "'Cormorant Garamond', serif"
|
||
fontSize: 32px
|
||
fontWeight: 300
|
||
lineHeight: 1.13
|
||
letterSpacing: -0.01em
|
||
display-sm:
|
||
fontFamily: "'Cormorant Garamond', serif"
|
||
fontSize: 24px
|
||
fontWeight: 300
|
||
lineHeight: 1.2
|
||
letterSpacing: 0
|
||
title-md:
|
||
fontFamily: "'Inter', sans-serif"
|
||
fontSize: 20px
|
||
fontWeight: 500
|
||
lineHeight: 1.35
|
||
letterSpacing: 0
|
||
title-sm:
|
||
fontFamily: "'Inter', sans-serif"
|
||
fontSize: 18px
|
||
fontWeight: 500
|
||
lineHeight: 1.44
|
||
letterSpacing: 0
|
||
body-md:
|
||
fontFamily: "'Inter', sans-serif"
|
||
fontSize: 16px
|
||
fontWeight: 400
|
||
lineHeight: 1.5
|
||
letterSpacing: 0.01em
|
||
body-strong:
|
||
fontFamily: "'Inter', sans-serif"
|
||
fontSize: 16px
|
||
fontWeight: 500
|
||
lineHeight: 1.5
|
||
letterSpacing: 0.01em
|
||
body-sm:
|
||
fontFamily: "'Inter', sans-serif"
|
||
fontSize: 15px
|
||
fontWeight: 400
|
||
lineHeight: 1.47
|
||
letterSpacing: 0.01em
|
||
caption:
|
||
fontFamily: "'Inter', sans-serif"
|
||
fontSize: 14px
|
||
fontWeight: 400
|
||
lineHeight: 1.5
|
||
letterSpacing: 0
|
||
caption-label:
|
||
fontFamily: "'Inter', sans-serif"
|
||
fontSize: 12px
|
||
fontWeight: 600
|
||
lineHeight: 1.4
|
||
letterSpacing: 0.08em
|
||
textTransform: uppercase
|
||
button:
|
||
fontFamily: "'Inter', sans-serif"
|
||
fontSize: 14px
|
||
fontWeight: 500
|
||
lineHeight: 1.0
|
||
letterSpacing: 0
|
||
nav-link:
|
||
fontFamily: "'Inter', sans-serif"
|
||
fontSize: 14px
|
||
fontWeight: 400
|
||
lineHeight: 1.4
|
||
letterSpacing: 0
|
||
|
||
rounded:
|
||
none: 0px
|
||
sm: "calc(0.75rem * 0.6)" # ~7px
|
||
md: "calc(0.75rem * 0.8)" # ~10px
|
||
lg: 0.75rem # 12px (--radius)
|
||
xl: "calc(0.75rem * 1.4)" # ~17px
|
||
2xl: "calc(0.75rem * 1.8)" # ~22px
|
||
3xl: "calc(0.75rem * 2.2)" # ~26px
|
||
pill: 9999px
|
||
full: 9999px
|
||
|
||
spacing:
|
||
xxs: 4px
|
||
xs: 8px
|
||
sm: 12px
|
||
base: 16px
|
||
md: 20px
|
||
lg: 24px
|
||
xl: 32px
|
||
xxl: 48px
|
||
section: 96px
|
||
|
||
components:
|
||
app-shell:
|
||
backgroundColor: "{colors.canvas}"
|
||
textColor: "{colors.ink}"
|
||
typography: "{typography.body-md}"
|
||
sidebar:
|
||
backgroundColor: "{colors.surface-card}"
|
||
textColor: "{colors.body}"
|
||
borderColor: "{colors.hairline}"
|
||
width: 252px
|
||
width-collapsed: 76px
|
||
topbar:
|
||
backgroundColor: "{colors.canvas}"
|
||
textColor: "{colors.ink}"
|
||
borderColor: "{colors.hairline}"
|
||
height: 81px
|
||
button-primary:
|
||
backgroundColor: "{colors.primary}"
|
||
textColor: "{colors.on-primary}"
|
||
typography: "{typography.button}"
|
||
rounded: "{rounded.pill}"
|
||
height: 40px
|
||
button-outline:
|
||
backgroundColor: transparent
|
||
textColor: "{colors.ink}"
|
||
typography: "{typography.button}"
|
||
rounded: "{rounded.pill}"
|
||
borderColor: "{colors.hairline-strong}"
|
||
height: 40px
|
||
nav-item:
|
||
backgroundColor: transparent
|
||
textColor: "{colors.muted}"
|
||
typography: "{typography.nav-link}"
|
||
rounded: "{rounded.pill}"
|
||
height: 44px
|
||
nav-item-active:
|
||
backgroundColor: "{colors.surface-strong}"
|
||
textColor: "{colors.ink}"
|
||
hero-band:
|
||
backgroundColor: "{colors.canvas-soft}"
|
||
textColor: "{colors.ink}"
|
||
typography: "{typography.display-xl}"
|
||
rounded: "{rounded.3xl}"
|
||
borderColor: "{colors.hairline}"
|
||
padding: 64px
|
||
feature-card:
|
||
backgroundColor: "{colors.surface-card}"
|
||
textColor: "{colors.ink}"
|
||
typography: "{typography.title-sm}"
|
||
rounded: "{rounded.2xl}"
|
||
borderColor: "{colors.hairline}"
|
||
padding: 24px
|
||
section-card:
|
||
backgroundColor: "{colors.surface-card}"
|
||
textColor: "{colors.ink}"
|
||
rounded: "{rounded.2xl}"
|
||
borderColor: "{colors.hairline}"
|
||
padding: 24px
|
||
icon-plate:
|
||
backgroundColor: "{colors.surface-strong}"
|
||
textColor: "{colors.ink}"
|
||
rounded: "{rounded.full}"
|
||
size: 40px
|
||
text-input:
|
||
backgroundColor: "{colors.canvas}"
|
||
textColor: "{colors.ink}"
|
||
typography: "{typography.body-md}"
|
||
rounded: "{rounded.md}"
|
||
borderColor: "{colors.hairline-strong}"
|
||
badge-pill:
|
||
backgroundColor: "{colors.surface-strong}"
|
||
textColor: "{colors.muted}"
|
||
typography: "{typography.caption-label}"
|
||
rounded: "{rounded.pill}"
|
||
padding: 4px 12px
|
||
brand-icon:
|
||
backgroundColor: "{colors.primary} + sky/lavender gradient orb overlay"
|
||
textColor: "{colors.on-primary}"
|
||
rounded: "{rounded.xl}"
|
||
size: 44px
|
||
avatar:
|
||
backgroundColor: "{colors.primary} + sky gradient orb overlay"
|
||
textColor: "{colors.on-primary}"
|
||
rounded: "{rounded.full}"
|
||
size: 32px
|
||
gradient-orb:
|
||
background: "radial-gradient with one of {colors.gradient-*}"
|
||
blur: 48-64px
|
||
opacity: 0.45-0.6
|
||
---
|
||
|
||
## Overview
|
||
|
||
AI 视频助手管理台 reads like a quietly editorial print magazine that happens to be an admin console. It ships in **two themes built from one navy palette**:
|
||
|
||
- **Dark navy** (default): deep-navy canvas `{colors-dark.canvas}` (#070b16) holding off-white ink `{colors-dark.ink}` (#f1f5ff).
|
||
- **Light navy**: cool off-white canvas `{colors.canvas}` (#f3f5fb) holding deep-navy ink `{colors.ink}` (#0c1426).
|
||
|
||
The brand voltage is **photographic, not chromatic**: soft pastel atmospheric gradient orbs (mint, peach, lavender, sky, rose) drift behind hero/feature panels as the only "color" moments. There is no neon accent, no saturated CTA color, and no cyan/blue dev-console gradient anymore.
|
||
|
||
Type pairs **Cormorant Garamond Light** (display serif at weight 300 — the open-source substitute for Waldenburg) with **Inter** for body, navigation, captions, and buttons. The display weight at 300 is the editorial signature — never bold. Latin display runs in the serif; CJK display falls back to the system serif/sans at weight 300 (still light/editorial).
|
||
|
||
CTAs are subtle pills: in light mode the primary is a deep-navy ink pill (`{component.button-primary}`); in dark mode it inverts to an off-white pill (`{colors-dark.primary}` → off-white, text `{colors-dark.primary-foreground}`). The secondary is a transparent hairline-outline pill.
|
||
|
||
**Key Characteristics:**
|
||
- One navy palette, two themes; dark is the default, persisted to `localStorage` with a no-flash bootstrap script in `layout.tsx`.
|
||
- Off-white / deep-navy ink. No saturated CTA color — ink pill only.
|
||
- Display runs Cormorant Garamond Light at weight 300 — editorial voice.
|
||
- Body runs Inter at 400/500 with subtle +0.01em tracking.
|
||
- Pastel gradient orbs (5 tokens) used as atmospheric decoration only.
|
||
- Pill geometry (`{rounded.pill}`) for every CTA, nav item, and badge; `{rounded.2xl}` for cards, `{rounded.3xl}` for hero bands.
|
||
- Hairline borders + a single soft shadow tier — no layered-surface dev-console stack.
|
||
- 96px section rhythm; ~40px page padding; 1180px max content width.
|
||
|
||
## Colors
|
||
|
||
The two `colors` / `colors-dark` blocks above are the source of truth and are wired into `globals.css` as the shadcn variables (`--background`, `--foreground`, `--card`, `--primary`, `--border`, `--muted-foreground`, …) plus editorial extras (`--ink`, `--body-text`, `--surface-strong`, `--hairline*`, `--canvas-soft`, `--gradient-*`). Tailwind utilities like `bg-background`, `text-ink`, `border-hairline`, `bg-surface-strong`, and `text-muted-foreground` resolve to the active theme automatically.
|
||
|
||
### Primary (CTA)
|
||
- **Light** `{colors.primary}` (#1b2741): deep-navy ink pill, white text. Press → `{colors.primary-active}` (#0c1426).
|
||
- **Dark** `{colors-dark.primary}` (#e8edf9): off-white pill, deep-navy text (`#0c1426`). The editorial inversion — used scarcely.
|
||
|
||
### Surfaces
|
||
- **Canvas** — page floor. Light #f3f5fb / Dark #070b16.
|
||
- **Canvas Soft** — hero/placeholder bands. Light #f9fafd / Dark #0b1322.
|
||
- **Surface Card** — content cards, popovers. Light #ffffff / Dark #0e1626.
|
||
- **Surface Strong** — icon plates, badges, active nav. Light #e9edf7 / Dark #18233a.
|
||
|
||
### Hairlines
|
||
- **Hairline** — default 1px divider/card outline. Light #e3e7f1 / Dark #1b2740.
|
||
- **Hairline Soft** — lighter divider. Light #eef1f8 / Dark #141d30.
|
||
- **Hairline Strong** — input/outline-button border. Light #cbd3e4 / Dark #283450.
|
||
|
||
### Text
|
||
- **Ink** — display & primary text. Light #0c1426 / Dark #f1f5ff.
|
||
- **Body** — running copy. Light #44516c / Dark #a6b2cb.
|
||
- **Muted** — descriptions, sub-labels. Light #5d6b86 / Dark #93a0bb.
|
||
- **Muted Soft** — captions, placeholders. Light #94a0bd / Dark #6c7a96.
|
||
|
||
### Atmospheric Gradient Orbs (signature)
|
||
Five pastel stops — `gradient-mint`, `gradient-peach`, `gradient-lavender`, `gradient-sky`, `gradient-rose`. In dark mode they shift to lower-chroma navy-compatible variants (e.g. sky #a8c8e8 → #5f86b8). They appear ONLY as soft, blurred radial blooms behind hero/feature/placeholder copy and as the orb overlay on the brand icon/avatar. Never as button fills, text colors, or flat card surfaces.
|
||
|
||
### Semantic
|
||
- **Success** — Light #16a34a / Dark #4ade80.
|
||
- **Error** — Light #dc2626 / Dark #f87171.
|
||
|
||
## Typography
|
||
|
||
### Font Family
|
||
**Cormorant Garamond** is the display serif (`--font-display`, weight 300) — the open-source substitute for the licensed Waldenburg Light. **Inter** carries body, navigation, captions, and buttons (`--font-sans`). **Geist Mono** is loaded for monospace. Helper classes in `globals.css`: `.font-display`, `.display-mega/-xl/-lg/-md/-sm`, `.caption-label`.
|
||
|
||
### Hierarchy
|
||
|
||
| Token | Size | Weight | Tracking | Use |
|
||
|---|---|---|---|---|
|
||
| `{typography.display-mega}` | clamp 40–64px | 300 | -0.03em | Marketing-scale hero |
|
||
| `{typography.display-xl}` | clamp 32–48px | 300 | -0.02em | Homepage hero h1 |
|
||
| `{typography.display-lg}` | 36px | 300 | -0.01em | Page titles |
|
||
| `{typography.display-md}` | 32px | 300 | -0.01em | Section heads |
|
||
| `{typography.display-sm}` | 24px | 300 | 0 | Card group titles |
|
||
| `{typography.title-md}` | 20px | 500 | 0 | Component titles (Inter) |
|
||
| `{typography.title-sm}` | 18px | 500 | 0 | List labels |
|
||
| `{typography.body-md}` | 16px | 400 | +0.01em | Default body |
|
||
| `{typography.body-strong}` | 16px | 500 | +0.01em | Emphasized body |
|
||
| `{typography.body-sm}` | 15px | 400 | +0.01em | Compact body |
|
||
| `{typography.caption-label}` | 12px | 600 | +0.08em, UPPER | Section labels, badges |
|
||
| `{typography.button}` | 14px | 500 | 0 | CTA pill |
|
||
| `{typography.nav-link}` | 14px | 400 | 0 | Sidebar nav |
|
||
|
||
### Principles
|
||
- **Display weight stays at 300.** Cormorant Garamond Light is the editorial signature. Never bold display copy.
|
||
- **Subtle tracking on body.** Inter at +0.01em (applied globally on `body`).
|
||
- **Negative tracking on display.** Tighter as size grows (-0.01em → -0.03em).
|
||
- **Caption labels** are the editorial section marker — uppercase, 600, +0.08em.
|
||
|
||
## Layout
|
||
|
||
### Spacing & Container
|
||
- Base unit 4px; section rhythm 96px (`{spacing.section}`).
|
||
- Page padding: `px-8 py-10`. Max content width 1180px.
|
||
- Sidebar: 252px expanded, 76px collapsed.
|
||
|
||
### Whitespace Philosophy
|
||
Generous editorial pacing. Hero bands get large internal padding (64px) with a gradient orb in a corner; content cards sit 16–24px apart.
|
||
|
||
## Elevation & Depth
|
||
|
||
The system uses **hairline + soft drop**. Cards float above the canvas via a 1px hairline and a single subtle shadow tier (`shadow-sm`, deepening to `shadow-md` on hover). Atmospheric depth comes from the gradient orbs, not from layered surface steps.
|
||
|
||
| Level | Treatment | Use |
|
||
|---|---|---|
|
||
| Canvas | `{colors.canvas}` | App background, topbar |
|
||
| Card | `{colors.surface-card}` | Content cards, popovers |
|
||
| Hairline border | 1px `{colors.hairline}` | Card/input outlines |
|
||
| Soft drop | `shadow-sm` → `shadow-md` on hover | Card elevation |
|
||
| Gradient orb | blurred radial bloom, opacity 0.45–0.6 | Atmospheric depth only |
|
||
|
||
## Shapes
|
||
|
||
### Border Radius Scale (`--radius: 0.75rem` = 12px)
|
||
|
||
| Token | Approx | Use |
|
||
|---|---|---|
|
||
| `{rounded.md}` | ~10px | Form inputs |
|
||
| `{rounded.lg}` | 12px | Compact elements |
|
||
| `{rounded.xl}` | ~17px | Brand icon |
|
||
| `{rounded.2xl}` | ~22px | Feature/section cards |
|
||
| `{rounded.3xl}` | ~26px | Hero/placeholder bands |
|
||
| `{rounded.pill}` | 9999px | All buttons, nav items, badges |
|
||
| `{rounded.full}` | 9999px | Avatars, icon plates |
|
||
|
||
## Components
|
||
|
||
### App Shell
|
||
**`app-shell`** — `bg-background text-foreground`. Flex row: sidebar left, main column right (topbar + scrollable content at `px-8 py-10`).
|
||
|
||
### Sidebar
|
||
**`sidebar`** — `bg-sidebar`, border-right `{colors.hairline}`, width 252/76px. Brand block: gradient-orb brand icon + serif wordmark + uppercase `管理台` caption-label. Nav items are pill-shaped (44px); active uses `bg-sidebar-accent` with foreground text.
|
||
|
||
### Top Bar
|
||
**`topbar`** — `bg-background`, border-bottom hairline, height 81px. Right-aligned: Help outline pill, **theme toggle** (sun/moon), notification bell, user avatar pill — all hairline-outline style.
|
||
|
||
### Buttons (pills)
|
||
**`button-primary`** — ink pill (light) / off-white pill (dark), `{rounded.pill}`, height 40px (`size="lg"`).
|
||
**`button-outline`** — transparent pill, 1px `{colors.hairline-strong}` border, text ink; hover `bg-surface-strong`.
|
||
|
||
### Hero & Atmospheric
|
||
**`hero-band`** — `bg-canvas-soft`, `{rounded.3xl}`, hairline border, 64px padding, with 1–2 blurred gradient orbs (sky + lavender) behind editorial display copy, a caption-label eyebrow, and two CTAs.
|
||
**`gradient-orb`** — blurred radial bloom in one of the five pastel tokens; pure atmosphere, holds no content.
|
||
|
||
### Cards
|
||
**`feature-card` / `section-card`** — `bg-card`, `{rounded.2xl}`, hairline border, `shadow-sm`. Icon plate is a 40px `{colors.surface-strong}` circle with neutral foreground icon (no colored accent).
|
||
|
||
### Forms & Tags
|
||
**`text-input`** — `bg-background`, text ink, 1px `{colors.hairline-strong}` border, `{rounded.md}`; focus thickens border to ring.
|
||
**`badge-pill`** — `bg-surface-strong`, caption-label text, `{rounded.pill}`.
|
||
|
||
### Brand Elements
|
||
**`brand-icon`** / **`avatar`** — `{colors.primary}` base with a sky/lavender gradient-orb overlay, `{rounded.xl}` (icon) / `{rounded.full}` (avatar). The pastel orb replaces the old cyan→blue gradient.
|
||
|
||
## Theming
|
||
|
||
- Two themes from one palette: `.dark` toggles the variable set in `globals.css`. Dark is the default `<html className="dark">`.
|
||
- A `ThemeToggle` (sun/moon) in the topbar flips `documentElement.classList` and writes `localStorage.theme`.
|
||
- A no-flash inline script in `layout.tsx` applies the stored theme before paint (defaults to dark).
|
||
|
||
## Do's and Don'ts
|
||
|
||
### Do
|
||
- Reserve `{colors.primary}` (ink/off-white pill) for primary CTAs.
|
||
- Use Cormorant Garamond Light at weight 300 for every display headline. Never bold.
|
||
- Use Inter at +0.01em tracking for body.
|
||
- Use atmospheric gradient orbs (mint/peach/lavender/sky/rose) as decoration only.
|
||
- Use the pill shape for every CTA, nav item, and badge.
|
||
- Drive color from semantic tokens (`bg-card`, `text-ink`, `border-hairline`) so both themes work — never inline hex.
|
||
|
||
### Don't
|
||
- Don't reintroduce the cyan/blue dev-console accent or layered-navy surface stack.
|
||
- Don't bold display copy — it sits at weight 300.
|
||
- Don't use gradient orbs as button fills, text colors, or flat card backgrounds.
|
||
- Don't use sharp 0px or `{rounded.xl}` corners on CTAs — pill geometry is the brand button.
|
||
- Don't drop body Inter to weight 300 to match the serif — body stays at 400/500.
|
||
|
||
## Responsive Behavior
|
||
|
||
| Name | Width | Key Changes |
|
||
|---|---|---|
|
||
| Mobile | < 640px | Feature grid 1-up; sidebar collapsed; user name hidden; display sizes shrink via clamp |
|
||
| Tablet | 640–1024px | Feature grid 2-up |
|
||
| Desktop | 1024–1280px | Feature grid 3-up; full sidebar |
|
||
| Wide | > 1280px | Content caps at 1180px |
|
||
|
||
### Touch Targets
|
||
- Nav items 44px; primary buttons 40px (`size="lg"`).
|
||
- Sidebar collapses to 76px icon-only via toggle.
|
||
|
||
## Known Gaps
|
||
|
||
- Cormorant Garamond covers Latin only; CJK display headings fall back to the system serif/sans at weight 300. A bundled light CJK serif (e.g. a Noto Serif SC weight) is not yet wired in.
|
||
- Several inner pages (Components, History, Test, Workflow, Profile) currently use the shared `PlaceholderPage` editorial header and await real content.
|
||
- Animation timings (orb drift, hero entrance, theme cross-fade) out of scope.
|
||
- Form validation states beyond focus not yet documented.
|