prompt: Ralph/PROMPT.md


prompt: Ralph/PROMPT.md


prompt: Ralph/PROMPT.md


prompt:
# Task: Sidebar-First Navigation Refactor (Remove Top Navbar/Subnav)

Refactor the dashboard so navigation is fully sidebar-driven, with clear recruiter-facing labels and robust responsive behavior. The current layout is still tied to an older navbar/subnav model and shows incorrect scroll behavior in the sidebar area.

## Context

Current implementation has separate top navigation (`TopBar`, `SubNav`) and a desktop-only sidebar. On upward scrolling in the sidebar, hidden space becomes visible in a way that implies layered layout offsets from the old top navbar/subnav structure.

## Requirements

- Remove top navbar/subnav from the rendered dashboard flow and migrate section navigation into the sidebar.
- Replace section labels with recruiter-facing content labels (no GP/internal metaphors as labels):
  - Overview
  - Projects
  - Experience
  - Education
  - Skills
- Keep iconography that can still evoke the GP-system metaphor, but labels must match actual portfolio content.
- Add a `Navigation` subheader area in the sidebar for section links.
- Keep a separate `My Data` area above `Navigation` in expanded sidebar mode.
- Ensure the sidebar no longer reveals hidden spacing/artifacts when scrolling upward.
- Implement mobile sidebar behavior (currently missing):
  - Sidebar is collapsed by default.
  - A hamburger control appears at the top and toggles expanded/collapsed state.
  - In collapsed mode, render a compact vertical rail with:
    - hamburger control at the top
    - the five section icons directly beneath for one-tap section jumping
  - In expanded mode, reveal full sidebar content:
    - `My Data` block
    - `Navigation` links with icon + text labels
    - tags, alerts, and highlights sections
- Preserve or improve accessibility:
  - Keyboard operable controls
  - Correct `aria-*` labels for menu toggle and navigation regions
  - Visible focus states
- Preserve smooth section scrolling/anchor behavior from navigation actions.

## Suggested GP-Metaphor Icon Mapping (labels remain recruiter-facing)

Use these concrete icon targets (or closest equivalents from existing icon library):

- Overview: `UserRound` (profile summary)
- Projects: `Pill` (interventions/medications metaphor)
- Experience: `Workflow` (pathway/Sankey metaphor)
- Education: `GraduationCap` (training/education)
- Skills: `Wrench` (capabilities/tools)

Label text must stay recruiter-facing:
- `Overview`, `Projects`, `Experience`, `Education`, `Skills`

## Likely Files In Scope

- `src/components/DashboardLayout.tsx`
- `src/components/Sidebar.tsx`
- `src/components/SubNav.tsx`
- `src/components/TopBar.tsx`
- `src/index.css`
- Any related hooks/types/styles needed for section activity and responsive state

## Success Criteria

All of the following must be true:

- [ ] No top navbar/subnav is rendered in the final dashboard layout.
- [ ] Sidebar contains the five required recruiter-facing nav labels under a `Navigation` subheader.
- [ ] Expanded sidebar includes a distinct `My Data` area above `Navigation`.
- [ ] Sidebar scrolling no longer exposes hidden top spacing/artifacts when scrolling upward.
- [ ] Desktop navigation from sidebar correctly jumps/scrolls to each section.
- [ ] On mobile, sidebar is collapsed by default with hamburger at top and five icon shortcuts visible.
- [ ] On mobile expand, sidebar shows `My Data`, full navigation links (icon + text), and tags/alerts/highlights.
- [ ] Navigation controls are keyboard accessible with appropriate ARIA semantics.
- [ ] `npm run lint` passes.
- [ ] `npm run typecheck` passes.
- [ ] `npm run build` passes.

## Constraints

- Use the existing project stack and conventions (TypeScript + React + current design language).
- Do not reintroduce GP-style labels like "Significant Interventions" or "Patient Summary" for the sidebar nav text.
- Keep changes focused on layout/navigation behavior; avoid unrelated refactors.

## Status

Track implementation progress in this file or `.ralph/plan.md`.
When all success criteria are met, print LOOP_COMPLETE.


prompt:
# Task: Patient Pathway Graph Stability + Unified Experience/Education Data Model

Refactor the patient-pathway style timeline/graph and related experience UI so interaction feels stable, data is consistent across all sections, and education is merged into the same primary timeline flow.

## Context

Current behavior has two major quality issues:
- Hovering graph-related content appears to trigger graph-wide motion/jiggle, implying unnecessary re-rendering or unstable layout state.
- Timeline dates shown in the graph do not match the dates shown in work-experience content.

The layout/content model is also split in ways that make consistency harder:
- Work and education data appear to be rendered through different pathways.
- Education is duplicated via a separate section beneath work experience.

## Requirements

- Fix interaction stability:
  - Hovering either a graph element OR its corresponding experience/education card must apply the same highlight behavior.
  - Hover should not cause global graph jiggle/repositioning.
- Diagnose and resolve date mismatch root cause:
  - Determine whether mismatch is from render logic, duplicated data sources, or both.
  - Deliver a fix so graph timeline dates match displayed card dates.
- Create one source of truth for timeline entities (career + education):
  - Include fields for full title, shortened graph label, date range, description/details, and skills list.
  - Use this canonical dataset to drive graph nodes/edges and card rendering.
- Skills integration:
  - Aggregate skills from canonical entities.
  - Feed the highest-frequency skills into sidebar tags.
- Experience/Education presentation update:
  - Remove the standalone work-experience subheader and existing role pill treatment.
  - In the unified timeline list, career entries show a `Career Intervention` pill.
  - Education entries remain in the same overall list/component flow but are visually right-aligned.
  - Education entries include an `Education Intervention` pill inside each card.
  - Remove the separate education section that currently sits below work experience.

## Likely Files In Scope

- `src/data/*` (or equivalent canonical data files)
- `src/types/*` (shared timeline entity typing)
- `src/components/*` for graph, timeline cards, sidebar tags, and experience/education sections
- Any related hooks/utilities managing hover state, mapping, and aggregation

## Success Criteria

All of the following must be true:

- [ ] Hovering on graph items and corresponding cards produces the same highlight outcome.
- [ ] Hover interactions do not cause full-graph jitter/repositioning artifacts.
- [ ] Graph dates and card dates are consistent for every timeline entry.
- [ ] A single canonical dataset powers both graph rendering and experience/education card content.
- [ ] Each timeline entry supports title + short graph label + skills + date fields needed by all consumers.
- [ ] Sidebar tags are sourced from aggregated canonical skills (most frequent first).
- [ ] Career entries show `Career Intervention` pill treatment.
- [ ] Education entries are visually right-aligned and show `Education Intervention` pill treatment.
- [ ] Separate standalone education section below work experience is removed.
- [ ] `npm run lint` passes.
- [ ] `npm run typecheck` passes.
- [ ] `npm run build` passes.

## Constraints

- Use existing stack/patterns (TypeScript + React + current project conventions).
- Keep changes focused on graph/timeline/data consistency and the requested UI restructuring.
- Do not introduce unrelated visual/system-wide refactors.

## Status

Track implementation progress in this file or `.ralph/plan.md`.
When all success criteria are met, print LOOP_COMPLETE.


prompt:
# Task: D3 Career Constellation Remediation (Hover, Timeline Parity, Visual Alignment)

Implement a full remediation of the career constellation chart and its linked timeline UI so interactions are reliable, timeline semantics are correct, and styling aligns with the rest of the site typography/tokens.

## Context

Recent chart refresh work did not fully resolve key issues:
- Hover highlighting is still not consistently activating on chart nodes.
- Timeline behavior in the chart is now more broken versus the work-experience timeline.
- Styling in the chart layer is not fully aligned with the main design system (including font token consistency).

The implementation should be grounded in the current codebase and preserve existing UX intent where possible.

## Requirements

- Fix hover interaction reliability in the D3 chart:
  - Ensure node hover consistently triggers graph highlighting on desktop.
  - Preserve touch behavior (tap-to-pin and clear interactions).
  - Preserve keyboard accessibility interactions.
- Remove interaction-layer conflicts:
  - Resolve any pointer interception between invisible accessibility overlays and SVG node hit targets.
  - Ensure focus-only controls do not break pointer hover behavior.
- Correct timeline data/semantic parity:
  - Ensure constellation role nodes map to the intended work-experience scope.
  - Prevent unintended education entries from being treated as role nodes unless explicitly intended.
  - Align ordering semantics between the chart timeline and work-experience timeline.
- Stabilize highlight state behavior:
  - Ensure graph highlight state and linked timeline card highlighting remain coherent when hovering roles vs skills.
  - Avoid reset/flicker edge cases on mouseleave/blur transitions.
- Align chart styling with site design system:
  - Use canonical font tokens consistently (UI vs mono usage should match the broader app).
  - Remove or replace invalid/undefined font token usage impacting timeline/chart-adjacent components.
  - Keep visual treatment consistent with existing dashboard cards/tokens (no unrelated redesign).
- Keep architecture maintainable:
  - Clarify data exports for timeline consumers (career-only, education-only, combined) where needed.
  - Avoid duplicate or dead timeline component paths if they create inconsistency.

## Validation Requirements

Run and pass:
- `npm run lint`
- `npm run typecheck`
- `npm run build`

Also perform manual behavioral checks and record concise notes in `.ralph/review.md`:
- Desktop hover on role nodes and skill nodes.
- Cross-highlight behavior between chart and timeline cards.
- Touch/coarse-pointer behavior (tap-to-pin and clear).
- Keyboard focus navigation and activation behavior.
- Timeline order parity sanity-check against work-experience content.

## Likely Files In Scope

- `src/components/CareerConstellation.tsx`
- `src/components/DashboardLayout.tsx`
- `src/components/TimelineInterventionsSubsection.tsx`
- `src/components/WorkExperienceSubsection.tsx` (if retained, removed, or reintegrated)
- `src/data/timeline.ts`
- `src/data/constellation.ts`
- `src/index.css`
- Related types in `src/types/pmr.ts` if needed

## Success Criteria

All of the following must be true:
- [ ] Constellation hover highlighting works reliably with pointer input.
- [ ] Accessibility/focus affordances remain functional without breaking pointer interactions.
- [ ] Timeline/role mapping in the chart is semantically correct and aligned with work-experience content.
- [ ] Highlight synchronization between chart and timeline cards behaves predictably.
- [ ] Font/token usage in chart and timeline-adjacent components is consistent with the app's design tokens.
- [ ] Any legacy/duplicate timeline path that causes divergence is resolved or clearly justified.
- [ ] `npm run lint` passes.
- [ ] `npm run typecheck` passes.
- [ ] `npm run build` passes.
- [ ] Reviewer records manual verification outcomes in `.ralph/review.md`.

## Constraints

- Use the existing TypeScript + React + Vite stack and project conventions.
- Keep changes scoped to constellation/timeline correctness and visual consistency.
- Do not introduce broad unrelated refactors.
- Prioritize correctness and maintainability over cosmetic novelty.

## Status

Track progress in `.ralph/plan.md` and keep it updated.
When all success criteria are met, print `LOOP_COMPLETE`.


prompt:
# Task: CareerConstellation Overhaul

Refactor, visually improve, and add chronological animation to the CareerConstellation D3 force chart — the centrepiece of the portfolio's Patient Pathway section.

## Requirements

### Phase 1 — Refactor the Monolith

Decompose `src/components/CareerConstellation.tsx` (1102 lines) into focused modules:

```
src/components/constellation/
  CareerConstellation.tsx          -- Orchestrator (< 300 lines)
  MobileAccordion.tsx              -- Mobile tap-to-expand accordion
  ConstellationLegend.tsx          -- Domain legend with node counts
  AccessibleNodeOverlay.tsx        -- Keyboard navigation button overlay
  constants.ts                     -- All magic numbers as named exports
  types.ts                         -- SimNode, SimLink, LayoutParams, local interfaces

src/hooks/
  useForceSimulation.ts            -- D3 simulation lifecycle (setup, forces, tick, cleanup)
  useConstellationHighlight.ts     -- applyGraphHighlight + connectedMap + highlight refs
  useConstellationInteraction.ts   -- Mouse/touch/pin handlers, callback refs
```

- [ ] Constants extracted (forces, sizes, opacities, durations)
- [ ] Types extracted (SimNode, SimLink, LayoutParams)
- [ ] MobileAccordion extracted as standalone component
- [ ] ConstellationLegend extracted
- [ ] AccessibleNodeOverlay extracted
- [ ] useForceSimulation hook created
- [ ] useConstellationHighlight hook created
- [ ] useConstellationInteraction hook created
- [ ] Orchestrator composed from hooks + sub-components (< 300 lines)
- [ ] All existing behaviour preserved (hover, click, tap, keyboard, mobile, detail panel)
- [ ] `npm run lint && npm run typecheck && npm run build` passes

### Phase 2 — Visual Improvements

Enhance the chart aesthetics while maintaining the PMR design language:

**Links:**
- [ ] Strength-weighted stroke width at rest: `0.5 + strength * 1.5` (range 0.5–2px)
- [ ] Domain-colored at rest (very low opacity: `0.08 + strength * 0.12`)
- [ ] Improved bezier curves: offset control point by vertical distance (`cx = (sx+tx)/2 + (ty-sy)*0.15`)
- [ ] On highlight: width `1 + strength * 2`, domain color at higher opacity

**Skill nodes:**
- [ ] Thin domain-colored stroke at rest (`stroke-width: 1, stroke-opacity: 0.4`)
- [ ] Size encoding by connected role count: `baseRadius + roleCount * 0.8`
- [ ] On highlight: subtle glow filter (feGaussianBlur, 2–3px stdDeviation, domain color)

**Role nodes:**
- [ ] Fill gradient: left-to-right from orgColor@0.08 to orgColor@0.18
- [ ] On highlight: fill-opacity 0.25, stroke-width 2, shadow-md filter

**Entry animation (mount, replaced by over-time animation in Phase 3):**
- [ ] Timeline guides fade in (200ms)
- [ ] Role nodes slide in from left along connectors (staggered 80ms, 300ms each)
- [ ] Skill nodes scale up from 0 (staggered 30ms, 250ms each)
- [ ] Links draw on via stroke-dashoffset (after source+target visible)
- [ ] Skipped entirely when `prefers-reduced-motion`

**Legend:**
- [ ] Domain node counts displayed: "Technical (8) · Clinical (6) · Leadership (7)"

### Phase 3 — Over-Time Animation

Build the constellation chronologically from 2009 to present:

**Data changes:**
- [ ] Modify `buildConstellationData()` in `src/data/timeline.ts` to include education entities
- [ ] Education entities appear as nodes on the timeline (use `type: 'role'` with education styling, or add `type: 'education'`)
- [ ] Update `src/types/pmr.ts` if new node types are needed
- [ ] Timeline order (oldest first): A-Levels (2009) → MPharm (2011) → Pre-Reg (2015) → Duty Manager (2016) → Pharmacy Manager (2017) → High Cost Drugs (2022) → Deputy Head (2024) → Interim Head (2025)

**Animation architecture:**
- [ ] Create `useTimelineAnimation` hook in `src/hooks/`
- [ ] All nodes present in simulation from start but hidden (opacity: 0) — stable positions, no layout jitter
- [ ] Reveal chronologically: each role/education entity appears, then its skills animate in
- [ ] Skills already visible from earlier roles just get new links (reinforcement pulse: scale 1.3x → 1.0x over 350ms)
- [ ] Uses requestAnimationFrame + timestamp scheduler (not setTimeout chains)
- [ ] Animation state machine in refs: IDLE → PLAYING → PAUSED → HOLDING → RESETTING → loop back to PLAYING
- [ ] Auto-plays on load (after force simulation settles)
- [ ] Loops continuously: hold 3s at end → fade all 400ms → pause 200ms → restart

**Visual effects during reveal:**
- [ ] Role/education nodes scale from 0 with ease-out-back
- [ ] New skill nodes scale from 0 with ease-out
- [ ] Links draw on via stroke-dashoffset animation
- [ ] Year indicator overlay (top-left of SVG, monospace font, var(--text-tertiary))

**Accessibility:**
- [ ] `prefers-reduced-motion`: skip animation entirely, show final state immediately
- [ ] Play/pause button with appropriate aria-label

### Phase 4 — Animation + Interaction Integration

Wire the animation to the existing highlight system:

- [ ] Hover/tap pauses animation, applies highlight normally (on visible nodes only)
- [ ] Highlight only operates on revealed nodes — unrevealed nodes stay at opacity 0
- [ ] Multiplicative opacity: animation visibility (0 or target) × highlight emphasis (1.0 or 0.15)
- [ ] Resume animation 800ms after last interaction ends (mouseout / background tap)
- [ ] Explicit pause via button stays paused until user clicks play again
- [ ] Play/pause toggle button (bottom-right of SVG area, subtle styling, larger touch target on mobile)
- [ ] Mobile accordion works during paused state
- [ ] Keyboard navigation works during paused state
- [ ] Click → detail panel works during paused state

## Success Criteria

All of the following must be true for LOOP_COMPLETE:

- [ ] `npm run lint && npm run typecheck && npm run build` passes with zero errors
- [ ] CareerConstellation orchestrator is < 300 lines
- [ ] Education entities (A-Levels, MPharm) appear in the constellation
- [ ] Animation auto-plays on load and loops continuously
- [ ] Network builds chronologically from 2009 through to present
- [ ] Skills accumulate visually — existing skills get new links, not duplicated
- [ ] Hover/tap pauses animation and shows highlight on visible nodes
- [ ] Animation resumes after 800ms of no interaction
- [ ] Play/pause button visible and functional
- [ ] Existing interactions preserved: click → detail panel, keyboard nav, mobile accordion
- [ ] `prefers-reduced-motion` shows final state immediately with no animation
- [ ] Links show domain colors and strength-weighted width at rest
- [ ] No TypeScript `any` types introduced
- [ ] No dead code or commented-out blocks

## Constraints

- TypeScript strict mode (`noUnusedLocals`, `noUnusedParameters`)
- Path alias: `@/*` → `src/*`
- Styling: Tailwind utilities + CSS custom properties for design tokens
- D3 v6 (already installed)
- Framer Motion for non-D3 animations; respect `prefers-reduced-motion`
- Design tokens: Primary teal #00897B, Accent coral #FF6B6B, PMR greens/teals/greys
- Font tokens: `--font-ui` (Elvaro), `--font-geist-mono` (monospace), `--font-primary` / `--font-secondary`
- No automated tests — quality gates are lint + typecheck + build
- D3 patterns: reference `.claude/skills/d3-visualization/` for force layout examples

## Key Architecture Decisions

1. **"All nodes hidden" for animation** — every node participates in the force simulation from the start (positions are stable). Reveal via opacity transitions only. Do NOT dynamically add/remove nodes from the simulation.

2. **Ref-based animation state** — the animation state machine lives in refs (not React state) to avoid re-renders in the rAF loop. Only sync to React state for UI controls (play/pause button).

3. **Multiplicative opacity model** — animation controls visibility (0 or target), highlight controls emphasis (1.0 or 0.15). Final opacity = animation × highlight. This prevents the two systems from conflicting.

4. **Imperative D3 + React hybrid** — D3 manages SVG rendering and force simulation imperatively via refs. React manages keyboard overlay buttons and UI controls. Follow the existing pattern in the codebase.

## Status

Track progress here. Mark items complete as you go.
When ALL success criteria are met, print LOOP_COMPLETE.


prompt:
# Task: CareerConstellation Overhaul

Refactor, visually improve, and add chronological animation to the CareerConstellation D3 force chart — the centrepiece of the portfolio's Patient Pathway section.

## Requirements

### Phase 1 — Refactor the Monolith

Decompose `src/components/CareerConstellation.tsx` (1102 lines) into focused modules:

```
src/components/constellation/
  CareerConstellation.tsx          -- Orchestrator (< 300 lines)
  MobileAccordion.tsx              -- Mobile tap-to-expand accordion
  ConstellationLegend.tsx          -- Domain legend with node counts
  AccessibleNodeOverlay.tsx        -- Keyboard navigation button overlay
  constants.ts                     -- All magic numbers as named exports
  types.ts                         -- SimNode, SimLink, LayoutParams, local interfaces

src/hooks/
  useForceSimulation.ts            -- D3 simulation lifecycle (setup, forces, tick, cleanup)
  useConstellationHighlight.ts     -- applyGraphHighlight + connectedMap + highlight refs
  useConstellationInteraction.ts   -- Mouse/touch/pin handlers, callback refs
```

- [ ] Constants extracted (forces, sizes, opacities, durations)
- [ ] Types extracted (SimNode, SimLink, LayoutParams)
- [ ] MobileAccordion extracted as standalone component
- [ ] ConstellationLegend extracted
- [ ] AccessibleNodeOverlay extracted
- [ ] useForceSimulation hook created
- [ ] useConstellationHighlight hook created
- [ ] useConstellationInteraction hook created
- [ ] Orchestrator composed from hooks + sub-components (< 300 lines)
- [ ] All existing behaviour preserved (hover, click, tap, keyboard, mobile, detail panel)
- [ ] `npm run lint && npm run typecheck && npm run build` passes

### Phase 2 — Visual Improvements

Enhance the chart aesthetics while maintaining the PMR design language:

**Links:**
- [ ] Strength-weighted stroke width at rest: `0.5 + strength * 1.5` (range 0.5–2px)
- [ ] Domain-colored at rest (very low opacity: `0.08 + strength * 0.12`)
- [ ] Improved bezier curves: offset control point by vertical distance (`cx = (sx+tx)/2 + (ty-sy)*0.15`)
- [ ] On highlight: width `1 + strength * 2`, domain color at higher opacity

**Skill nodes:**
- [ ] Thin domain-colored stroke at rest (`stroke-width: 1, stroke-opacity: 0.4`)
- [ ] Size encoding by connected role count: `baseRadius + roleCount * 0.8`
- [ ] On highlight: subtle glow filter (feGaussianBlur, 2–3px stdDeviation, domain color)

**Role nodes:**
- [ ] Fill gradient: left-to-right from orgColor@0.08 to orgColor@0.18
- [ ] On highlight: fill-opacity 0.25, stroke-width 2, shadow-md filter

**Entry animation (mount, replaced by over-time animation in Phase 3):**
- [ ] Timeline guides fade in (200ms)
- [ ] Role nodes slide in from left along connectors (staggered 80ms, 300ms each)
- [ ] Skill nodes scale up from 0 (staggered 30ms, 250ms each)
- [ ] Links draw on via stroke-dashoffset (after source+target visible)
- [ ] Skipped entirely when `prefers-reduced-motion`

**Legend:**
- [ ] Domain node counts displayed: "Technical (8) · Clinical (6) · Leadership (7)"

### Phase 3 — Over-Time Animation

Build the constellation chronologically from 2009 to present:

**Data changes:**
- [ ] Modify `buildConstellationData()` in `src/data/timeline.ts` to include education entities
- [ ] Education entities appear as nodes on the timeline (use `type: 'role'` with education styling, or add `type: 'education'`)
- [ ] Update `src/types/pmr.ts` if new node types are needed
- [ ] Timeline order (oldest first): A-Levels (2009) → MPharm (2011) → Pre-Reg (2015) → Duty Manager (2016) → Pharmacy Manager (2017) → High Cost Drugs (2022) → Deputy Head (2024) → Interim Head (2025)

**Animation architecture:**
- [ ] Create `useTimelineAnimation` hook in `src/hooks/`
- [ ] All nodes present in simulation from start but hidden (opacity: 0) — stable positions, no layout jitter
- [ ] Reveal chronologically: each role/education entity appears, then its skills animate in
- [ ] Skills already visible from earlier roles just get new links (reinforcement pulse: scale 1.3x → 1.0x over 350ms)
- [ ] Uses requestAnimationFrame + timestamp scheduler (not setTimeout chains)
- [ ] Animation state machine in refs: IDLE → PLAYING → PAUSED → HOLDING → RESETTING → loop back to PLAYING
- [ ] Auto-plays on load (after force simulation settles)
- [ ] Loops continuously: hold 3s at end → fade all 400ms → pause 200ms → restart

**Visual effects during reveal:**
- [ ] Role/education nodes scale from 0 with ease-out-back
- [ ] New skill nodes scale from 0 with ease-out
- [ ] Links draw on via stroke-dashoffset animation
- [ ] Year indicator overlay (top-left of SVG, monospace font, var(--text-tertiary))

**Accessibility:**
- [ ] `prefers-reduced-motion`: skip animation entirely, show final state immediately
- [ ] Play/pause button with appropriate aria-label

### Phase 4 — Animation + Interaction Integration

Wire the animation to the existing highlight system:

- [ ] Hover/tap pauses animation, applies highlight normally (on visible nodes only)
- [ ] Highlight only operates on revealed nodes — unrevealed nodes stay at opacity 0
- [ ] Multiplicative opacity: animation visibility (0 or target) × highlight emphasis (1.0 or 0.15)
- [ ] Resume animation 800ms after last interaction ends (mouseout / background tap)
- [ ] Explicit pause via button stays paused until user clicks play again
- [ ] Play/pause toggle button (bottom-right of SVG area, subtle styling, larger touch target on mobile)
- [ ] Mobile accordion works during paused state
- [ ] Keyboard navigation works during paused state
- [ ] Click → detail panel works during paused state

## Success Criteria

All of the following must be true for LOOP_COMPLETE:

- [ ] `npm run lint && npm run typecheck && npm run build` passes with zero errors
- [ ] CareerConstellation orchestrator is < 300 lines
- [ ] Education entities (A-Levels, MPharm) appear in the constellation
- [ ] Animation auto-plays on load and loops continuously
- [ ] Network builds chronologically from 2009 through to present
- [ ] Skills accumulate visually — existing skills get new links, not duplicated
- [ ] Hover/tap pauses animation and shows highlight on visible nodes
- [ ] Animation resumes after 800ms of no interaction
- [ ] Play/pause button visible and functional
- [ ] Existing interactions preserved: click → detail panel, keyboard nav, mobile accordion
- [ ] `prefers-reduced-motion` shows final state immediately with no animation
- [ ] Links show domain colors and strength-weighted width at rest
- [ ] No TypeScript `any` types introduced
- [ ] No dead code or commented-out blocks

## Constraints

- TypeScript strict mode (`noUnusedLocals`, `noUnusedParameters`)
- Path alias: `@/*` → `src/*`
- Styling: Tailwind utilities + CSS custom properties for design tokens
- D3 v6 (already installed)
- Framer Motion for non-D3 animations; respect `prefers-reduced-motion`
- Design tokens: Primary teal #00897B, Accent coral #FF6B6B, PMR greens/teals/greys
- Font tokens: `--font-ui` (Elvaro), `--font-geist-mono` (monospace), `--font-primary` / `--font-secondary`
- No automated tests — quality gates are lint + typecheck + build
- D3 patterns: reference `.claude/skills/d3-visualization/` for force layout examples

## Key Architecture Decisions

1. **"All nodes hidden" for animation** — every node participates in the force simulation from the start (positions are stable). Reveal via opacity transitions only. Do NOT dynamically add/remove nodes from the simulation.

2. **Ref-based animation state** — the animation state machine lives in refs (not React state) to avoid re-renders in the rAF loop. Only sync to React state for UI controls (play/pause button).

3. **Multiplicative opacity model** — animation controls visibility (0 or target), highlight controls emphasis (1.0 or 0.15). Final opacity = animation × highlight. This prevents the two systems from conflicting.

4. **Imperative D3 + React hybrid** — D3 manages SVG rendering and force simulation imperatively via refs. React manages keyboard overlay buttons and UI controls. Follow the existing pattern in the codebase.

## Status

Track progress here. Mark items complete as you go.
When ALL success criteria are met, print LOOP_COMPLETE.


prompt:
# Task: Career Constellation Chart & Layout Polish

Visual polish and layout adjustments to the career constellation chart, sidebar, and repeat medications section. 12 discrete changes across 10 files.

## Requirements

### 1. Reduce link opacity (`src/components/constellation/constants.ts`)
- Lower `LINK_BASE_OPACITY` from `0.08` → `0.04`
- Lower `LINK_STRENGTH_OPACITY_FACTOR` from `0.12` → `0.06`
- Makes skill connection lines subtler so job pills are visually clearer

### 2. White background on hovered job pill (`src/hooks/useConstellationHighlight.ts`)
- When a role/education node is the `activeNodeId`, override its `.node-circle` fill to `#FFFFFF` with `fill-opacity: 1`
- Currently uses a gradient fill with `fill-opacity: 0.25` — make it solid white, fully opaque

### 3. Move legend to top of chart + increase font size (`src/components/constellation/ConstellationLegend.tsx`)
- Position legend as absolutely-positioned overlay at the **top** of the chart container (not below the SVG)
- Increase font size from `10px` to `12px` to match work node label text size
- Separate the "Hover to explore connections" text from the legend — see item 12

### 4. Move year labels to right side of chart (`src/hooks/useForceSimulation.ts`)
- Keep the current node layout unchanged (roles, skills, timeline line stay where they are)
- Move year label text elements to the right edge of the chart: position at `width - sidePadding`, `text-anchor: 'end'`

### 5. Change chart fonts to dashboard style (`src/hooks/useForceSimulation.ts`)
- Year labels: change `font-family` from `var(--font-geist-mono)` to `var(--font-ui)`
- Year indicator (animation): same font change

### 6. Reverse pathway column split to 40/60 (`src/index.css`)
- Change `.pathway-columns` grid from `minmax(0, 1.3fr) minmax(0, 1fr)` to `minmax(0, 2fr) minmax(0, 3fr)`
- This gives 40% to work experience text and 60% to the graph

### 7. Sidebar: collapses to icon rail when patient summary scrolls out of view (`src/components/Sidebar.tsx` + `src/components/DashboardLayout.tsx`)
- Sidebar already starts expanded on desktop — no change needed there
- Add IntersectionObserver on the PatientSummaryTile element in DashboardLayout
- When PatientSummaryTile scrolls out of view, pass a `forceCollapsed` prop to Sidebar
- Sidebar collapses to icon rail (same as current mobile rail behaviour with nav buttons + hamburger menu)
- When PatientSummaryTile scrolls back into view, re-expand the sidebar
- Only applies on desktop (≥1024px) — mobile behaviour unchanged

### 8. Change pathway stacking breakpoint from 1024px to 768px (`src/index.css`)
- The `.pathway-columns` two-column layout currently triggers at `min-width: 1024px`
- Change this to `min-width: 768px` so the graph sits beside text on tablets too
- Sidebar breakpoint remains at 1024px (this only affects pathway columns)
- Also update `.pathway-graph-sticky` responsive rule to match the `768px` breakpoint

### 9. Repeat medications: 3-column layout (`src/components/RepeatMedicationsSubsection.tsx`)
- Render all 3 category sections (Technical, Healthcare Domain, Strategic & Leadership) side-by-side
- Use CSS grid: `grid-template-columns: repeat(3, 1fr)` on `md` (768px+) screens
- Stack vertically on mobile (<768px)
- Remove the `marginTop` between categories when in grid mode (they'll be in columns)

### 10. Skills hover → chart highlight (verify only)
- `RepeatMedicationsSubsection` already calls `onNodeHighlight` on hover
- This flows through `DashboardLayout` → `highlightedNodeId` → `CareerConstellation` → `useConstellationHighlight`
- Verify this interaction works end-to-end. If it does, no code change needed.

### 11. Play/pause button: left edge of chart, visible only when chart is in view (`src/components/constellation/PlayPauseButton.tsx` + `src/components/constellation/CareerConstellation.tsx`)
- Move button to the far-left edge of the chart container (not bottom-right)
- Use IntersectionObserver on the chart container to track if chart is visible
- When chart is in viewport: show button at left edge, vertically centered
- When chart scrolls out of view: hide the button
- Increase base opacity from 0.6 to 0.85
- Add slightly stronger border and subtle box-shadow for visibility

### 12. "Hover to explore connections" text — more visible, top-left above year indicator (`src/components/constellation/ConstellationLegend.tsx` or `src/components/constellation/CareerConstellation.tsx`)
- Separate this text from the legend dot items
- Position at the top-left of the chart, above the year indicator text
- Increase opacity from 0.7 to 1
- Increase font size (match or approach the legend font size)
- On touch devices, show "Tap to explore connections" instead

## Success Criteria

All of the following must be true:
- [ ] `npm run lint` passes with zero errors
- [ ] `npm run typecheck` passes with zero errors
- [ ] `npm run build` completes successfully
- [ ] Link opacity constants lowered (LINK_BASE_OPACITY=0.04, LINK_STRENGTH_OPACITY_FACTOR=0.06)
- [ ] Hovered role/education node gets white fill (#FFFFFF, fill-opacity 1)
- [ ] Legend positioned at top of chart with 12px font size
- [ ] Year labels positioned at right edge of chart with `var(--font-ui)` font
- [ ] Pathway columns use 40/60 split (2fr/3fr)
- [ ] Sidebar collapses to icon rail when patient summary scrolls out of view (desktop only)
- [ ] Pathway columns go side-by-side at 768px (not 1024px)
- [ ] Repeat medications renders 3 categories in grid columns on md+ screens
- [ ] Play/pause button on left edge of chart, hidden when chart not in view
- [ ] "Hover to explore" text at top-left of chart, full opacity, larger font

## Constraints

- TypeScript strict mode — `noUnusedLocals`, `noUnusedParameters` enforced
- Path alias: `@/*` → `src/*`
- Styling: Tailwind utility classes + inline `CSSProperties` for dynamic/theme values
- Animations: Framer Motion; respects `prefers-reduced-motion`
- Design tokens: Primary teal `#00897B`, Accent coral `#FF6B6B`
- Font tokens: `--font-ui` (Elvaro Grotesque), `--font-geist-mono` (Geist Mono)
- Do not break existing hover/click/keyboard interactions on the constellation
- Do not alter the D3 force simulation physics or node positioning logic (except year labels)
- Preserve existing mobile behaviour unless explicitly changed (items 8, 9)

## Files to Modify

1. `src/components/constellation/constants.ts`
2. `src/hooks/useConstellationHighlight.ts`
3. `src/components/constellation/ConstellationLegend.tsx`
4. `src/hooks/useForceSimulation.ts`
5. `src/index.css`
6. `src/components/Sidebar.tsx`
7. `src/components/DashboardLayout.tsx`
8. `src/components/RepeatMedicationsSubsection.tsx`
9. `src/components/constellation/PlayPauseButton.tsx`
10. `src/components/constellation/CareerConstellation.tsx`

## Status

Track progress here. Mark items complete as you go.
When all success criteria are met, print LOOP_COMPLETE.

- [ ] Item 1: Link opacity
- [ ] Item 2: White hover pill
- [ ] Item 3: Legend top position
- [ ] Item 4: Year labels right
- [ ] Item 5: Font change
- [ ] Item 6: Column split 40/60
- [ ] Item 7: Sidebar scroll collapse
- [ ] Item 8: Stacking breakpoint 768px
- [ ] Item 9: Medications 3-column
- [ ] Item 10: Skills hover verify
- [ ] Item 11: Play/pause button
- [ ] Item 12: Hover text visibility