# Isometric Landing — Use Case Narrative Design

> **Date:** 2026-04-16
> **Status:** Approved
> **Scope:** `frontend/src/components/landing/isometric/`

## Context

The landing page has an isometric 3D scene (Three.js / React Three Fiber) showing a contract lifecycle flow — 6 stages connected by animated tubes, with an InsightTicker cycling text. Currently the viz is purely decorative: the ticker, the 3D scene, and the prompt input are disconnected.

An external reference project (`enterprise-brain-1`) has 10 deeply researched use cases with rich narrative data. This spec describes how to weave those use cases into the isometric scene so it becomes a **passive briefing theatre** — the ticker narrates, the viz reacts, and the viewer sees Enterprise Brain's intelligence come alive before they ever type a question.

## Design Decisions

- **Landing only** — no question answering, no interaction with the prompt. This is a briefing, not an exploration hub.
- **Satellites as small 3D shapes** — same visual vocabulary as existing stage shapes (octahedron, box, tetrahedron, etc.), smaller, dimmed. NOT HTML overlays.
- **On the ground plane** — satellites sit at y=0 alongside their parent stage, offset in X/Z, no overlapping.
- **Spotlight, not cascade** — when the ticker advances, the relevant elements wake up and the rest dims. No chain-reaction ripple per line.
- **Flow shown via tubes** — active ticker lines light up the relevant connection tubes between stages, particles flow to show direction/causality.
- **Pill cards: active only** — inactive stages hide their pill cards. Active stages show enriched pill cards with UC insight data (tag, value, headline).
- **Shared ref, no new dependencies** — a mutable ref object bridges the ticker (main React tree) and the Three.js scene (separate React root). No Zustand, no event bus.
- **One-time cascade on load** — stages reveal first (existing animation), then satellites fade in together. After that, the ticker drives spotlight shifts.

## UC → Stage Mapping

| Stage | ID | Satellite UCs |
|---|---|---|
| BIDS | `bids` | UC-02 (Salami Slicing) |
| CONTRACTS | `contracts` | UC-00 (NCE Variation), UC-01 (Budget Bleed) |
| EARLY WARNINGS | `ew` | UC-03 (EW Response), UC-06 (Silence Alarm) |
| NCEs | `nces` | UC-05 (NCE Validity), UC-09 (Claim Patterns) |
| IMPLEMENTED | `implemented` | UC-04 (Coupled Risk), UC-08 (Cost of Delay) |
| REJECTED | `rejected` | UC-07 (Board Brief) |

10 satellites total across 6 stages.

## Ticker Lines (8)

Each line defines what it spotlights: parent stage(s), satellite(s), and flow tube(s).

| # | Ticker text | Stage | Satellites | Flow tubes |
|---|---|---|---|---|
| 1 | Afcons at 25% variation — 2x portfolio average | contracts | UC-00 | bids→contracts |
| 2 | 3 packages overspending with zero NCEs raised | contracts | UC-01 | contracts→ew |
| 3 | 7 small claims below £50K — classic salami pattern | bids | UC-02 | bids→contracts |
| 4 | 12 Early Warnings without Risk Reduction Meetings | ew | UC-03 | contracts→ew, ew→nces |
| 5 | 3 contractors behind programme with no EWs raised | ew | UC-06 | contracts→ew |
| 6 | £400K claim reducible to £220K via clause 63.7 | nces | UC-05 | ew→nces, nces→implemented |
| 7 | 6-week delay cascading to £24M — £780K/day true cost | implemented | UC-04, UC-08 | nces→implemented |
| 8 | £90M budget gap recoverable — board pack ready | ALL | UC-07, UC-09 | ALL |

Line 8 is the crescendo — everything glows briefly, then the loop resets.

## Animation Timeline

### Phase 1: Stage Reveal (0s–6.5s)
Existing staggered easeOutBack animation. Unchanged. Stages appear: bids (0.8s) → contracts (1.8s) → EW (2.8s) → NCEs (4.0s) → implemented + rejected (5.5s).

### Phase 2: Satellite Fade-In (7s–8.5s)
All 10 satellite shapes fade in together at ~25% opacity. Their parent connection tubes appear at the same time, very dim. No stagger — they all arrive as a quiet constellation.

### Phase 3: Ticker Spotlight Cycle (~10s onwards)
Ticker begins. Each line holds ~3.6s (3s display + 0.6s transition). On each line change:

1. **Spotlight ON** — the active stage glows (emissive ramp 0.3→0.8), its active satellite(s) wake up (glow, rotate faster, emissive pulse), relevant flow tubes brighten (opacity 0.06→0.22, particles speed up), enriched pill card fades in on the parent stage.
2. **Spotlight OFF (everything else)** — inactive stages dim further (emissive drops to ~0.15), inactive satellites stay at 25% opacity, inactive flow tubes stay very dim, pill cards hidden.
3. **Transition** — when the ticker advances, previous elements fade back (~600ms ease-out) while new elements wake up (~400ms ease-in). Brief overlap creates a smooth handoff, not a hard cut.

**Line 8 crescendo:** All stages + all satellites + all tubes glow simultaneously for ~3s, then everything dims back and the loop restarts from line 1.

**Full loop:** ~29s for 8 lines. First-time experience is ~39s (10s reveal + 29s cycle).

## State Architecture

### Shared Ref (bridge between React roots)

```js
bridgeRef = useRef({
  activeUcIds: [],        // which UC satellites are spotlighted
  activeStageIds: [],     // which main stages glow
  activeEdgeKeys: [],     // which flow tubes brighten (e.g. 'bids→contracts')
  insightData: null,      // { tag, tagColor, value, headline } for the enriched pill card
  tick: 0,                // bumped on each change — lets Html components detect updates
})
```

- Created in `IsometricBackground.jsx`
- Passed into `CanvasHost` → `LifecycleScene` → all 3D components
- Passed into `InsightTicker` via callback
- **Ticker writes** to the ref on each line change
- **3D components read** in `useFrame` (imperative, no re-renders)
- **EnrichedPillCard** (React/HTML via `<Html>`) checks `tick` via ~200ms `useEffect` interval to show/hide

### UC Satellite Data Shape

```js
{
  id: 'uc-00',
  label: 'NCE VARIATION',
  parentStageId: 'contracts',
  accent: '#5c83ff',           // from tokens.js palette
  insightTag: 'CRITICAL',
  insightTagColor: '#F06060',
  insightValue: '£93.2M',
  insightHeadline: 'Afcons CE exposure at 25% — 2x portfolio average',
  position: [-6.5, 0, 2.5],   // ground plane, offset from parent
  shape: 'octahedron',         // geometry type
}
```

### Ticker Line Data Shape

```js
{
  ucIds: ['uc-00'],
  stageIds: ['contracts'],
  edgeKeys: ['bids→contracts'],
  insightData: { tag: 'CRITICAL', tagColor: '#F06060', value: '£93.2M', headline: '...' },
  segments: [plain('Afcons at '), b('25% variation', C.red), ...],
}
```

## Spotlight Animations (useFrame Details)

### Main Stage — Active
- Emissive intensity: interpolate 0.3 → 0.8 over ~400ms
- Scale: no change (stays at 1.0)
- Ground ring opacity: 0.06 → 0.18

### Main Stage — Inactive
- Emissive intensity: interpolate down to 0.15 over ~600ms
- Ground ring opacity: 0.03

### Satellite — Active
- Emissive intensity: 0.1 → 0.6 with sine pulse (0.6 + sin(t*2)*0.15)
- Rotation speed: 0.003 → 0.012 rad/frame
- Opacity: 0.25 → 0.85
- Connection tube to parent: opacity 0.05 → 0.2, particle speed 2x

### Satellite — Dormant
- Emissive intensity: 0.1
- Rotation speed: 0.003 rad/frame (barely perceptible)
- Opacity: 0.25
- Connection tube: opacity 0.05

### Flow Tube — Active
- Tube opacity: 0.06 → 0.22
- Particle speed: 1x → 2x
- Particle scale: 1x → 1.4x

### Flow Tube — Inactive
- Tube opacity: 0.04 (slightly dimmer than current 0.06)
- Particle speed: 1x (unchanged)

### Enriched Pill Card — Active Stage
- Fades in via CSS animation (opacity 0→1, translateY 6px→0, 400ms ease)
- Shows: [TAG badge] + insightValue (hero number) + insightHeadline (1 line)
- Glass-morphism: rgba(10,16,29,0.85), backdrop-filter blur(12px), border 1px solid ${accent}35

### Enriched Pill Card — Inactive Stage
- Hidden (display: none or opacity 0)

## File Changes

### New files (3)

| File | Purpose |
|---|---|
| `isometric/useCaseData.js` | 10 UC satellite definitions + 8 ticker lines with spotlight config |
| `isometric/SatelliteShape.jsx` | Small 3D shape — reads bridgeRef in useFrame, dim/glow transitions, rotation, connection tube to parent |
| `isometric/EnrichedPillCard.jsx` | Replaces PillCard — shows/hides based on bridgeRef.tick, displays UC insight data when stage is active |

### Modified files (6)

| File | Changes |
|---|---|
| `isometric/lifecycleData.js` | Add `ucIds` array to each stage. Add `edgeKey` string to each flow edge. |
| `isometric/IsometricBackground.jsx` | Create bridgeRef, pass into CanvasHost (→ LifecycleScene) and InsightTicker. |
| `isometric/InsightTicker.jsx` | Replace hardcoded LINES with ticker lines from useCaseData.js. On each line change, write activeUcIds/activeStageIds/activeEdgeKeys/insightData to bridgeRef and bump tick. |
| `isometric/LifecycleStage.jsx` | Read bridgeRef in useFrame — dim/glow based on activeStageIds. Replace PillCard with EnrichedPillCard. |
| `isometric/LifecycleFlow.jsx` | Import UC satellite data, render SatelliteShape for each. Pass bridgeRef to all children. |
| `isometric/ConnectionTube.jsx` | Read bridgeRef in useFrame — brighten tube + speed particles when edge key is in activeEdgeKeys. |

### Minimally modified
- `LifecycleScene.jsx` — adds bridgeRef prop passthrough to LifecycleFlow; camera, lights, fog, stars, grid unchanged

### Unchanged files
- `StageViz3D.jsx` — existing stage shape components
- `SplitRing.jsx`, `SafeConnectionTube.jsx` — wrappers, unchanged
- `tokens.js` — color palette shared by satellites
- `KpiCardsOverlay.jsx` — remains unused

### No new dependencies
Zero. Shared mutable ref only.

## Verification

1. **Load the landing page** — watch for 40 seconds. Stages reveal (0–6.5s), satellites fade in (7–8.5s), ticker starts cycling (~10s). Each line spotlights the correct stage + satellite(s) + flow tubes. Inactive areas dim. Pill cards appear only on active stages with UC insight data.
2. **Line 8 crescendo** — all stages and satellites glow simultaneously, then dim back and the loop restarts.
3. **Flow tubes** — particles flow in the direction of causality when tubes are active. Inactive tubes are barely visible.
4. **No overlap** — satellite shapes sit on the ground plane near their parent, no visual overlap with each other or the main stages.
5. **Performance** — no frame drops. All bridgeRef reads are imperative in useFrame, no React re-renders in the Three.js root.
6. **Existing behavior preserved** — the prompt input and starter chips work exactly as before. Typing a question and submitting still navigates to /chat.
