# 05 — Responsive Component Plan

Stack: Next.js 16 App Router · TypeScript · Tailwind CSS v4 · Motion (scroll-linked islands only) · Zod (server-side validation).

## Principles

1. **Server Components by default.** Content, headings, lists, SVG diagrams, and schema render on the server. Client islands exist only where state or scroll position is required.
2. **Designed desktop-first, coded mobile-first.** Each component has an intentional small-screen composition — not a squeezed desktop layout.
3. **No layout shift.** Fonts via `next/font` with metric-matched fallbacks; media boxes reserve aspect ratio; reveals animate `transform`/`opacity` only.
4. **Motion is optional.** Everything is readable with JS disabled and with `prefers-reduced-motion`.
5. **Content never lives in components.** Components receive typed props from `src/lib/content.ts`.

## Breakpoints

| Name | Min width | Layout intent |
|---|---|---|
| base | 0 | Single column, 20px gutters, stacked CTAs, vertical diagrams |
| `sm` | 640px | Two-up chips and cards, inline CTAs |
| `md` | 768px | Two-column splits begin (panels, FAQ, footer) |
| `lg` | 1024px | Full desktop navigation, sticky diagrams, horizontal signal map |
| `xl` | 1280px | Display type reaches full scale, asymmetric 7/5 splits |
| `2xl` | 1536px | Container caps at 1360px; more air, no new layout |

## Client islands and budgets

| Island | Why client | Motion lib? | Notes |
|---|---|---|---|
| `Navbar` (+ `MegaMenu`, `MobileNav`) | Menu state, scroll state, focus management | No (CSS transitions) | Hover-intent 120ms; Escape closes; focus returns to trigger |
| `RevealObserver` | One IntersectionObserver for every `[data-reveal]` | No | Mounted once in root layout; failsafe shows content after 3s |
| `HeroBackdrop` | ~70px parallax on the hero photograph | Yes (`useScroll`) | Static under reduced motion |
| `AmbientVideo` | Play/pause by visibility, reduced motion, Save-Data | No | `muted playsInline loop preload="none"` |
| `GrowthSystem` | Active stage + scroll-linked path | Yes | Stage content SSR'd; node click scrolls to stage |
| `MetricPanel` | Model toggle + metric selection | No (CSS crossfade) | Tabs pattern with arrow-key support |
| `ProcessTimeline` | Active step + progress line | Yes (`useScroll`) | Step list is a real `nav` with anchors |
| `GrowthDiagnostic` | Answers → category | No | Pure scoring in `lib/diagnostic.ts` (unit-tested) |
| `MagneticButton` | Pointer tracking | No (rAF + transform) | Fine pointers only; disabled under reduced motion |
| `LeadForm` | `useActionState` + server action | No | Works without JS (progressive enhancement) |
| `ConsentManager` | Stored preference; loads analytics only after opt-in | No | Renders nothing unless an analytics ID is configured |

## Component inventory

### Primitives (`src/components/ui`)

| Component | Props (typed) | Responsive behaviour |
|---|---|---|
| `Container` | `size?: 'default' \| 'narrow' \| 'wide'` | Fluid inline padding `clamp(1.25rem, 4vw, 3rem)` |
| `Section` | `tone: 'paper' \| 'deep' \| 'night' \| 'surface'`, `id`, `labelledBy`, `spacing?` | Vertical rhythm scales `5rem → 9rem` |
| `SectionHeading` | `title`, `intro?`, `layout: 'split' \| 'stack'`, `as?`, `tone` | `split` = title 7 cols / intro 5 cols ≥ lg; stacked below. No label or index line above the title |
| `Button` / `ButtonLink` | `variant: 'primary' \| 'secondary' \| 'ghost'`, `tone`, `size`, `magnetic?`, `href` | Full-width < sm when `block` |
| `TextLink` | `href`, `children`, `tone` | 44px min hit area via padding |
| `Chip`, `Label`, `IllustrativeTag` | — | Wrap naturally |
| `Breadcrumbs` | `items: {name, href}[]` | Truncates middle crumbs < sm; emits BreadcrumbList JSON-LD |
| `JsonLd` | `data: object` | Escapes `<` to prevent script injection |
| `Logo` | `tone` | Mark-only below 360px |

### Global

| Component | Notes |
|---|---|
| `Navbar` | ≥ lg: inline items + dropdown triggers + CTA. < lg: logo, compact CTA (≥ sm), menu button. Background gains hairline + paper fill after 8px scroll. |
| `MegaMenu` | Services: 3 grouped columns + rail. Industries: 2 large cards. Full container width, anchored below header; closes on route change, outside click, Escape. |
| `MobileNav` | Full-height `dialog` sheet, `inert` on page behind, body scroll locked, accordion groups, CTA pinned. |
| `Footer` | 1 col → 2 cols (md) → 5 cols (lg). Legal row wraps. |

### Home (`src/components/home`)

| Component | Desktop (≥ lg) | Mobile (< lg) |
|---|---|---|
| `Hero` | ≥ xl: full-bleed 16:9 photograph fills the first screen; eyebrow, H1 (68–74px), sub-copy, CTAs, and disciplines in a 46rem column over a paper veil; signal map card docked over the photograph's lower edge. lg: 3:1 photograph band above the text, sub-copy + CTAs side by side | Photograph band first (4:3 phone, 16:9 from sm); H1 44px, CTAs stacked full width; `SignalMap` vertical variant below |
| `SignalMap` | Horizontal SVG: 5 channel inputs → 6 stages, Email loop, Analytics rail | Vertical SVG: channel chips row → stage column with loop on the right |
| `IndustryPanel` ×2 | Side by side, equal height; 16:9 photograph across the top edge (scales 3% on hover), glyph bottom-right | Stacked; 3:2 photograph on top |
| `GrowthSystem` | Sticky radial diagram (6 cols) + scrolling stages (6 cols) | Compact ring (non-sticky) + stages with a drawing left rail |
| `MetricPanel` | Metric index 5 cols + chart 7 cols, detail under index | Toggle → chart → horizontally scrollable metric chips → detail card |
| `ServiceSection` ×6 | Alternating text/photograph 6/5 splits, text centered on a 4:5 photograph with a numbered caption strip, hairline rows | Text then 4:3 photograph; deliverables single column |
| `SpecializationFlows` | Two diagrams side by side + table | Diagrams stacked (vertical flows); table becomes definition list per row |
| `PlatformMarquee` | Two full-bleed rows of pills (official mark + name) drifting in opposite directions, edges masked, paused on hover/focus | Same two rows; pills a little tighter. Static wrapped rows inside the container under reduced motion |
| `ProcessTimeline` | Sticky step index (3 cols) + steps (9 cols) with 3-column detail rows | Steps stacked with left rail; detail rows stacked |
| `WorkingTogether` | Home only: team review living photograph above (1536:620 at xl, 16:9 from md). Cadence board 5 cols + principles 7 cols | Principles first, then board |
| `GrowthDiagnostic` | Questions 7 cols + sticky result 5 cols | Questions, then result (auto-scrolls into view on completion, respecting reduced motion) |
| `ResourceCard` | Photographic cover (16:9; the feature cover takes the spare height), then format · category, title, dek, read time. Feature card spans 2 rows + 5 compact cards | Horizontal scroll-snap row of cards (with visible overflow cue) |
| `FAQ` | Heading + CTA 4 cols, accordion 8 cols | Stacked |
| `CTA` | Copy 7 cols over night; video fills right with veil | Copy full width; video as top band at 40% opacity |

### Inner-page templates (`src/components/templates`)

| Template | Sections |
|---|---|
| `ServicePage` | `PageHero` → Problem grid → Approach steps → Deliverables → SaaS/E-com applications → Process → Measurement framework (3 tiers) → FAQ → Related links → CTA |
| `IndustryPage` | `PageHero` (Higgsfield still) → Challenges → System diagram → Capability blocks → Metrics → Process → FAQ → CTA |
| `ArticlePage` | Breadcrumbs → header (category, title, dek, read time, date) → takeaways → body (typed blocks) with sticky TOC ≥ lg → contextual CTA → related |
| `LegalPage` | Narrow prose container, updated date |

## Accessibility contract per interactive component

| Component | Pattern |
|---|---|
| Dropdown triggers | `button[aria-expanded][aria-controls]`; panel is a labelled region; Escape closes and restores focus |
| Mobile nav | `dialog` with `aria-modal`, labelled title, focus trap, Escape |
| Metric toggle | `role="tablist"` with roving tabindex, arrow keys |
| Metric rows | Buttons with `aria-pressed`; detail region `aria-live="polite"` |
| Growth System nodes | Buttons labelled "Go to stage 02: Acquire"; stage articles have headings |
| Diagnostic | `fieldset` + `legend`, native radios styled as tiles, result `aria-live="polite"` |
| FAQ | Native `details`/`summary` |
| Charts/diagrams | `role="img"` + `aria-labelledby` title/desc, plus visible text equivalents |
| Videos | Decorative: `aria-hidden`, no controls needed (silent, ambient), pause on reduced motion |
| Lead form | Labels, `aria-describedby` for hints/errors, error summary focused on failed submit |

## Performance checklist

- Hero H1 is the LCP element; its reveal is CSS-only and starts on first paint.
- Posters: AVIF/WebP ≤ 120KB; videos: H.264 + VP9, no audio track, `preload="none"`, started after load.
- Higgsfield stills served through `next/image` with explicit `sizes`.
- Motion loaded only by the three scroll-linked islands (`LazyMotion` + `domAnimation`).
- All routes statically prerendered; OG images generated at build time.
- No third-party scripts by default. Analytics loads only after consent and only if configured.
