<!-- source: openspec/changes/add-tactical-globe-hero/tasks.md -->

# Tasks — add-tactical-globe-hero

> Phased checklist for the executor. Lift exact math and tuning constants from
> `/tmp/globe-zip/openspec-globe/reference/globe-engine.reference.js`. The
> hero-integration shape (right-anchored absolute layer, no HUD, matchMedia gate
> at 1100 px) supersedes anything in `reference/hero.reference.html` — read
> `design.md` first.

## Phase 0 — baseline (documentation)

- [x] On clean `origin/main` (before any code), `pnpm install && pnpm -F web build`. Record `.next/static/chunks` gzipped total. Per Next 16 / Turbopack, per-route First Load JS is not printed; this proxy is good enough for `D-WB-N` documentation. Baseline recorded in PR body.

## Phase 1 — dependencies & scaffold

- [ ] Add `three` to `packages/ui` `dependencies` and `@types/three` to `devDependencies`. Run `pnpm install` to pick up.
- [ ] Add `"./marketing/globe": "./src/marketing/globe/index.ts"` to `packages/ui/package.json` `exports` map.
- [ ] Create `packages/ui/src/marketing/globe/` with empty `globe-engine.ts`, `globe-engine.types.ts`, `palette.ts`, `TacticalGlobe.tsx`, `index.ts`.
- [ ] Define `GlobeOptions` and `GlobePalette` in `globe-engine.types.ts` per design.md prop table.

## Phase 2 — palette constant

- [ ] In `palette.ts` define `GLOBE_PALETTE_HEX` const with `light` and `dark` variants. `light` is the only one indexed in v1; `dark` is stored as a forward hedge. Each entry carries a comment back-referencing the canonical hex in `packages/ui/src/styles/globals.css`.
- [ ] Implement `resolvePalette(surface: 'light' | 'dark'): GlobePalette` as a trivial index. **Do NOT add an oklch reader or converter** (G2 risk-reduction; see design.md).

## Phase 3 — engine geometry

- [ ] In `globe-engine.ts`, use named imports from `three` (`Scene, WebGLRenderer, PerspectiveCamera, Group, BufferGeometry, BufferAttribute, LineBasicMaterial, Line, Points, PointsMaterial, SphereGeometry, MeshBasicMaterial, Mesh, RingGeometry, DoubleSide, QuadraticBezierCurve3, Color, Clock, Vector3`), not `import * as THREE`.
- [ ] Port `latLonToVec3`, meridian / parallel builders, fibonacci point-field, city markers + hub halos, and quadratic-bezier arcs + travelling pulse dots from the reference. Lift exact math and tuning constants — do not re-derive.
- [ ] Encode the real geography (Chișinău + Bucharest hubs; London / Berlin / Paris / New York / Singapore spokes; arcs from hubs only).
- [ ] Verify no gradient, no shadow, radius-0 spirit holds (flat materials, opacity only).

## Phase 4 — engine lifecycle & motion

- [ ] Implement `init(canvas, opts)` / `setRunning(boolean)` / `resize()` / `dispose()`. **No `setOptions`** — design.md § Public surface.
- [ ] Delta-timed auto-spin; pointer-drag inertia with ±0.6 rad vertical clamp.
- [ ] Halo pulse + arc-dot travel; `prefers-reduced-motion` parks the RAF after one frame. Engine reads the media query at `init` and registers a `change` listener.
- [ ] Cap `devicePixelRatio` at 2 in `init` and `resize`.
- [ ] `dispose()` cancels RAF, removes media-query listener, disposes every geometry / material / texture, calls `renderer.dispose()`.

## Phase 5 — React wrapper

- [ ] `TacticalGlobe.tsx` (`'use client'`): canvas ref, mount → `engine.init`, unmount → `engine.dispose`.
- [ ] `ResizeObserver` on host → `engine.resize()`.
- [ ] `IntersectionObserver` on host → `engine.setRunning(visible)`.
- [ ] Forward pointer events to the engine; set cursor `grab` / `grabbing`.
- [ ] `aria-hidden="true"` on the canvas (decorative).
- [ ] Export `TacticalGlobe` from `index.ts` only. Engine, palette, types stay internal.

## Phase 6 — hero wiring

- [ ] New file `apps/web/src/components/home/hero-globe-background-c.tsx`:
  - [ ] `'use client'` directive.
  - [ ] `next/dynamic` import of `TacticalGlobe` with `{ ssr: false }` — mandatory (`THREE.WebGLRenderer` constructor breaks SSR on Cloudflare Workers).
  - [ ] `useState` + `useEffect` to set `enabled` based on `window.matchMedia('(min-width: 1100px) and (pointer: fine)').matches` after mount. Return `null` until enabled.
  - [ ] Stage `<div>` with `position: absolute; top: 0; bottom: 0; right: 0; left: auto; width: clamp(360px, 55vw, 720px); zIndex: 0;` and `aria-hidden="true"`.
  - [ ] Mount `<TacticalGlobe surface="light" accents="duo" secPerRot={60} density={24} tilt={23.4} markers particles autorotate />`.
  - [ ] **No HUD telemetry** (G3 strip).
- [ ] Edit `apps/web/src/app/[locale]/(marketing)/page.tsx`:
  - [ ] Wrap the existing `<HeroC />` in a new `<div style={{ position: 'relative' }}>` (or className-based equivalent).
  - [ ] Mount `<HeroGlobeBackgroundC />` as the FIRST child of that wrapper, BEFORE `<HeroC />`.
- [ ] Do NOT modify `apps/web/src/components/home/hero-c.tsx`.
- [ ] Confirm HeroC copy is unchanged and the type column LCP is server-rendered.

## Phase 7 — verification & PR

- [ ] Run the `verification.md` acceptance assertions locally.
- [ ] `pnpm -F @rapoport-studio/ui typecheck` passes.
- [ ] `pnpm -F web build` passes. Record post-implementation `.next/static/chunks` gzipped total. Compare to Phase 0 baseline; report delta in PR body. Confirm `/` route weight stays within the `D-WB-N` carve-out (~400 KB).
- [ ] Manual dev-server check:
  - [ ] Reduced-motion freezes the globe.
  - [ ] At < 1100 px or `pointer: coarse`, the globe is NOT in the DOM (Network panel shows no `three` chunk).
  - [ ] At ≥ 1100 px, the globe paints in the right band, no DOM-overlap with HeroC text glyphs.
  - [ ] No console errors; no CLS on the headline; scroll past hero confirms the globe is clipped to the hero band.
- [ ] Open PR on branch `feat/rap-1264/tactical-globe-hero`. Title `Tactical globe background for marketing hero` (no `RAP-1264:` prefix). Body references RAP-1264 plainly without `Closes` / `Fixes` / `Resolves` and lists deviations from the bundle reference HTML.
- [ ] At archive (separate PR after merge): append a dated amendment to `openspec/current/web.md` near the existing `### Hero-C (homepage hero rewrite)` section, with decision rows `D-GH-1` (text-free contract), `D-WB-N` (homepage budget carve-out + allowlist amendment), `D-GH-3` (cockpit-strip rationale).
