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

# Tactical blueprint globe for the marketing hero

> **Affects capability spec(s):** `openspec/current/web.md`
> **Linear issue:** [RAP-1264](https://linear.app/rapoport-studio-slr/issue/RAP-1264).

## Why

The marketing hero at `/` is type-only. `HeroC` carries a three-line lockup, a subhead, and two CTAs — and nothing else. That is correct for a studio whose system says the frame and the type do the talking, but it leaves one true thing unsaid: the studio operates as a network. Trilingual (EN · RO · RU), two home cities (Chișinău · Bucharest), specialists pulled in from across Europe and beyond. The page never renders that reach.

We add one load-bearing piece of motion that says *global, networked, engineered* — and stops there. Not a photoreal earth (the imagery rules forbid AI-slop realism, gradients, neon, bluish glow). A **wireframe blueprint globe**: latitude/longitude grid in blueprint blue, terracotta hubs sitting on the studio's actual coordinates, great-circle arcs connecting them, a faint point-field for depth. It reads as a drafting-table globe, not a hero stock asset. It is the tactical frame, made spherical and set turning.

This is a marketing-surface change only. No workspace, no deck, no new copy beyond what `HeroC` already ships.

## What Changes

- Add a framework-agnostic **`globe-engine`** module (raw `three`, no `@react-three/fiber`) that builds and animates the globe into a supplied `<canvas>`: lat/long wireframe, fibonacci point-field, terracotta city markers + halo rings on the two hubs, quadratic-bezier connection arcs with travelling pulse dots. Public surface: `init(canvas, opts)`, `setRunning(boolean)`, `resize()`, `dispose()`.
- Add a **`TacticalGlobe`** client component (`'use client'`) — a thin React wrapper that owns the canvas ref, the resize observer, the pointer-drag-with-inertia handler, the IntersectionObserver pause, and the engine lifecycle.
- Bind the globe palette to a `GLOBE_PALETTE_HEX` constant whose values mirror the canonical hex comments in `packages/ui/src/styles/globals.css`. No runtime `oklch()` reader, no colour converter — the marketing content surface ships single-mode (light); a dark variant is stored as a forward hedge but unused in v1.
- Add a **`HeroGlobeBackgroundC`** client wrapper in `apps/web` that loads `TacticalGlobe` via `next/dynamic({ ssr: false })` (mandatory: `THREE.WebGLRenderer` touches `document`/`window` and would crash the Cloudflare Workers SSR pass), gated by `window.matchMedia('(min-width: 1100px) and (pointer: fine)')` so mobile and touch viewports never download the `three` chunk.
- Mount the globe layer as the first child of a new `position: relative` wrapper around `<HeroC />` in `apps/web/src/app/[locale]/(marketing)/page.tsx`. The globe is `position: absolute` and right-anchored (`right: 0; left: auto; width: clamp(360px, 55vw, 720px)`), never under HeroC's text column. **HeroC source is not modified.**
- Honour `prefers-reduced-motion`: stop auto-rotation, freeze halo pulse and travelling arc dots, keep one static frame, park the RAF.

## Governance resolutions (taken with Pavel before code)

The bundle's prototype conflicted with three documented studio rules. Each was resolved explicitly:

- **G1 — `design-system.md:106` "No decorative imagery behind text. ... the canvas behind content is always flat."** Resolution: **strict text-free zone.** The globe stage origin sits to the right of HeroC's text column. A DOM-overlap verification step measures every text node bounding rect against the canvas rect and fails on any pixel intersection. Cited in `design.md`; no spec amendment.
- **G2 — `web.md:435` "Total page weight (homepage, gzipped): < 250 KB" + client-component allowlist (nav, sticky CTA, intake form, locale switcher).** `three` is ~150 KB gz; `dynamic({ ssr: false })` only removes it from the LCP critical path, not from total weight. Resolution: **pre-empted carve-out `D-WB-N`**. Homepage `/` gets a route-specific budget of ~400 KB gzip; all other marketing routes stay at the standard 250 KB. The `web.md` allowlist gains a `hero decorative background` category at archive.
- **G3 — `design-system.md:29 / 106` "No cockpit clichés — gauge dials, neon-on-black command-center styling, decorative telemetry, glow."** Resolution: **strip HUD; keep geometric motion.** No mono HUD telemetry div, no coordinates, no `OPEN`, no "drag to rotate" caption. Halo ring pulse on the two hubs + travelling dots along the arcs remain — motion-on-structure, not theatrical glow.

## Impact

- **Spec:** `openspec/current/web.md` — at archive, gains a decorative-background capability section under `### Hero-C (homepage hero rewrite)` plus three decision rows (`D-GH-1` text-free contract, `D-WB-N` homepage budget carve-out + allowlist amendment, `D-GH-3` cockpit-strip rationale).
- **Packages:** `packages/ui` gains a `marketing/globe/` folder + a new `"./marketing/globe"` exports entry + `three` runtime dep + `@types/three` dev dep. `apps/web` gains one new component (`hero-globe-background-c.tsx`) and one one-line wrap edit in `(marketing)/page.tsx`.
- **Surfaces:** marketing only (`rapoport.studio`). Globe never mounts on mobile / touch / < 1100 px viewports (matchMedia gate). Workspace and decks untouched.
- **Budget:** one `<canvas>`, ≤ ~1500 line-segments + 1400 points, single `requestAnimationFrame` loop, paused via `IntersectionObserver` when off-screen, capped at `devicePixelRatio = 2`. Homepage total weight stays within the `D-WB-N` carve-out (`/` ≤ 400 KB gzip).

## Out of scope

- Photoreal earth textures, day/night maps, atmospheric shaders, dotted-continent landmasses sampled from a bitmap — explicitly excluded; revisit only if a real engagement asks.
- The in-prototype **Tweaks panel** (surface / accents / speed / density sliders). That is a design-exploration tool; production ships fixed props with the chosen defaults. The tweak keys survive only as the component's prop names.
- The bundle prototype's **mono HUD telemetry** (coordinates, `OPEN` indicator, "drag to rotate" caption) — removed per G3.
- The bundle prototype's **viewport-fixed corner brackets** — `HeroC`'s existing `TacticalFrame` already carries the bracket grammar.
- Workspace (`app.rapoport.studio`) globe usage, deck usage, and the `StudioLogo` motion modes.
- Any new hero copy. `HeroC`'s lockup, subhead, and CTAs stay byte-identical.
- A runtime `oklch()` → sRGB converter. Hex constants only in v1; if a content-surface theme toggle ever lands, add a vetted converter then.
- Live theme-switch palette update. Marketing content surface is single-mode (light).
