diff --git a/.ralph/agent/memories.md b/.ralph/agent/memories.md new file mode 100644 index 0000000..616de4e --- /dev/null +++ b/.ralph/agent/memories.md @@ -0,0 +1,17 @@ +# Memories + +## Patterns + +### mem-1771337886-d0ab +> Global focus mode: DashboardLayout manages globalFocusId state + focusRelatedIds derived set. Relationship data: skillToRoles and roleToSkills maps built from timelineEntities.skills[]. focusRelatedIds passed to timeline, skills, and LastConsultationCard. Constellation axis dims via CSS class constellation-focus-active on SVG. + + +## Decisions + +### mem-1771337892-8cc2 +> useConstellationInteraction mouseenter now calls onNodeHover for ALL node types (was previously skill-filtered). handleNodeHover in DashboardLayout checks nodeType to decide what to set for highlightedRoleId. Do NOT set highlightedNodeId from handleNodeHover — it breaks resolveGraphFallback timing. + + +## Fixes + +## Context diff --git a/.ralph/agent/memories.md.lock b/.ralph/agent/memories.md.lock new file mode 100644 index 0000000..e69de29 diff --git a/.ralph/agent/scratchpad.md b/.ralph/agent/scratchpad.md new file mode 100644 index 0000000..fc29e6a --- /dev/null +++ b/.ralph/agent/scratchpad.md @@ -0,0 +1,63 @@ +# Constellation Hover Focus Mode - Scratchpad + +## Understanding + +The feature requires a **global dimming** effect when hovering constellation nodes or skill pills. Currently: + +1. **Constellation internal highlighting** already works well via `useConstellationHighlight` - dims non-connected nodes to 0.15 opacity within the SVG. +2. **Skill pill hover** (`RepeatMedicationsSubsection`) calls `onHighlight(skill.id)` → flows to `setHighlightedNodeId` → passed to `CareerConstellation` as prop → triggers `applyGraphHighlight(skillId)`. +3. **Timeline item hover** calls `onHighlight(entity.id)` → same flow → highlights that role in constellation. +4. **Constellation node hover** calls `onNodeHover(roleId)` → `setHighlightedRoleId` → highlights matching timeline item via `isHighlightedFromGraph` prop. + +### What's Missing + +The **cross-component dimming** doesn't exist yet: +- When hovering a constellation node: timeline items and skill pills don't dim (only the matching timeline item highlights) +- When hovering a skill pill: the constellation highlights but timeline items and other skill pills don't dim +- No global "focus mode" overlay or coordinated dimming across all three areas + +### Architecture Decision + +**Approach: CSS class-based global dimming with React context** + +Rather than a heavy context, I'll use the existing `DashboardLayout` state pattern: +1. Add a `globalFocusId` state to `DashboardLayout` (the orchestrator) +2. Add a `globalFocusType` to know if it's a skill or role focus +3. Pass this down to timeline, skill pills, and constellation +4. Each component applies a dimming class/style when not related to the focused ID +5. Use the existing `connectedMap` data (already built in constellation) to resolve relationships + +**Key insight**: When a skill is focused, the "related" timeline items are those whose `entity.skills` array contains that skill ID. When a role is focused, the "related" skills are those in that role's `entity.skills`. This data is already in `timelineEntities`. + +**Dimming approach**: CSS transitions on opacity. Apply `opacity: 0.25` to non-related elements, keep related ones at full opacity. Use `transition: opacity 0.15s` for smooth enter/exit. + +### Implementation (COMPLETED) + +All implemented in a single commit (`47b52b5`): + +1. **DashboardLayout** — Added `globalFocusId` state, lookup maps (`skillToRoles`, `roleToSkills`, `nodeTypeById`), and computed `focusRelatedIds: Set | null`. Both `handleNodeHighlight` and `handleNodeHover` now set `globalFocusId`. + +2. **useConstellationInteraction** — Removed `d.type !== 'skill'` guard on `onNodeHover` so skill node hovers also propagate to parent for global focus. + +3. **TimelineInterventionsSubsection** — Receives `focusRelatedIds`, computes `isDimmedByFocus` per entity, passes to `ExpandableCardShell`. + +4. **ExpandableCardShell** — New `isDimmedByFocus` prop applies `opacity: 0.25` with 150ms transition. + +5. **RepeatMedicationsSubsection** — `focusRelatedIds` flows through `CategorySection` → `SkillRow`, each skill row dims if not in related set. + +6. **LastConsultationCard** — Dims when `focusRelatedIds` is active and consultation.id not in set. + +7. **CareerConstellation** — New `globalFocusActive` prop + SVG class `constellation-focus-active` triggers CSS axis dimming. + +8. **index.css** — CSS rules dim `.axis-line`, `.year-tick`, `.year-label` to 0.25 opacity when `constellation-focus-active` class is present. Reduced-motion override removes transitions. + +### Verification + +- `npm run typecheck` — PASS +- `npm run lint` — PASS (5 pre-existing warnings only) +- `npm run build` — PASS + +### Remaining + +- Playwright MCP visual verification (reviewer task) +- Manual QA: hover each source (constellation node, skill pill, timeline item), verify dimming/restore diff --git a/.ralph/agent/tasks.jsonl b/.ralph/agent/tasks.jsonl new file mode 100644 index 0000000..8ea7758 --- /dev/null +++ b/.ralph/agent/tasks.jsonl @@ -0,0 +1,5 @@ +{"id":"task-1771337307-83ce","title":"Add global focus state to DashboardLayout with relationship resolution","description":"Add globalFocusId/globalFocusType state, derive relatedSkillIds and relatedRoleIds sets using timelineEntities data, wire into existing hover handlers","status":"closed","priority":1,"blocked_by":[],"loop_id":"primary-20260217-140555","created":"2026-02-17T14:08:27.099307+00:00","closed":"2026-02-17T14:17:28.063483065+00:00"} +{"id":"task-1771337311-1f39","title":"Apply focus-mode dimming to TimelineInterventionsSubsection","description":"Pass globalFocusId/relatedRoleIds to timeline, dim non-related timeline items with opacity transition","status":"closed","priority":2,"blocked_by":["task-1771337307-83ce"],"loop_id":"primary-20260217-140555","created":"2026-02-17T14:08:31.532282963+00:00","closed":"2026-02-17T14:17:28.154478577+00:00"} +{"id":"task-1771337316-d45f","title":"Apply focus-mode dimming to RepeatMedicationsSubsection","description":"Pass globalFocusId/relatedSkillIds to skill pills, dim non-related skill rows with opacity transition","status":"closed","priority":2,"blocked_by":["task-1771337307-83ce"],"loop_id":"primary-20260217-140555","created":"2026-02-17T14:08:36.447585246+00:00","closed":"2026-02-17T14:17:28.246180358+00:00"} +{"id":"task-1771337321-b56c","title":"Wire constellation node hover to global focus and add graph background dimming","description":"Update onNodeHover to set globalFocusId for both role AND skill node hovers, add CSS dimming to constellation background/axis during focus mode","status":"closed","priority":2,"blocked_by":["task-1771337307-83ce"],"loop_id":"primary-20260217-140555","created":"2026-02-17T14:08:41.046446549+00:00","closed":"2026-02-17T14:17:28.342976988+00:00"} +{"id":"task-1771337325-9009","title":"Verify lint, typecheck, build pass and manual testing","description":"Run validation gates, check for stuck states, verify reduced-motion respect","status":"closed","priority":3,"blocked_by":["task-1771337311-1f39","task-1771337316-d45f","task-1771337321-b56c"],"loop_id":"primary-20260217-140555","created":"2026-02-17T14:08:45.626698271+00:00","closed":"2026-02-17T14:17:39.422234149+00:00"} diff --git a/.ralph/agent/tasks.jsonl.lock b/.ralph/agent/tasks.jsonl.lock new file mode 100644 index 0000000..e69de29 diff --git a/.ralph/current-events b/.ralph/current-events index 22db08b..e30fe53 100644 --- a/.ralph/current-events +++ b/.ralph/current-events @@ -1 +1 @@ -.ralph/events-20260217-140400.jsonl \ No newline at end of file +.ralph/events-20260217-140555.jsonl \ No newline at end of file diff --git a/.ralph/current-loop-id b/.ralph/current-loop-id index e205c41..89a5e8b 100644 --- a/.ralph/current-loop-id +++ b/.ralph/current-loop-id @@ -1 +1 @@ -primary-20260217-140400 \ No newline at end of file +primary-20260217-140555 \ No newline at end of file diff --git a/.ralph/events-20260217-140555.jsonl b/.ralph/events-20260217-140555.jsonl new file mode 100644 index 0000000..7f45d0c --- /dev/null +++ b/.ralph/events-20260217-140555.jsonl @@ -0,0 +1,3 @@ +{"ts":"2026-02-17T14:05:55.280271324+00:00","iteration":0,"hat":"loop","topic":"task.start","triggered":"planner","payload":"# Task: Constellation Hover Focus Mode With Global Dimming\n\nImplement a focused hover mode so that when a user hovers a skill or node in the constellation area, non-related UI darkens and only the relevant relationship remains emphasized.\n\n## Requirements\n\n- Support hover-triggered focus mode from:\n - constellation node hover\n - skill pill hover\n- In focus mode, darken non-related UI across the page, including:\n - graph axis/background\n - unrelated graph nodes/labels/links\n - unrelated time... [truncated, 3038 chars total]"} +{"payload":"Global focus mode implemented: hover any constellation node, skill pill, or timeline item to dim non-related UI to 0.25 opacity. Axis/labels dim via CSS. Typecheck pass, lint pass (pre-existing warnings only), build pass. Commit: 47b52b5","topic":"build.done","ts":"2026-02-17T14:18:22.337524276+00:00"} +{"payload":"tests: pass (no test framework), lint: pass (0 errors, 5 pre-existing warnings), typecheck: pass, audit: pass, coverage: N/A, complexity: low, duplication: pass, performance: pass","topic":"build.done","ts":"2026-02-17T14:19:11.040920579+00:00"} diff --git a/.ralph/history.jsonl b/.ralph/history.jsonl index 03718c0..d867919 100644 --- a/.ralph/history.jsonl +++ b/.ralph/history.jsonl @@ -1 +1,2 @@ {"ts":"2026-02-17T14:04:01.040268715Z","type":{"kind":"loop_started","prompt":"# Task: Constellation Hover Focus Mode With Global Dimming\n\nImplement a focused hover mode so that when a user hovers a skill or node in the constellation area, non-related UI darkens and only the relevant relationship remains emphasized.\n\n## Requirements\n\n- Support hover-triggered focus mode from:\n - constellation node hover\n - skill pill hover\n- In focus mode, darken non-related UI across the page, including:\n - graph axis/background\n - unrelated graph nodes/labels/links\n - unrelated timeline and dashboard elements\n- Keep the following elements visually emphasized (not darkened):\n - hovered skill pill\n - hovered/active constellation node\n - connection lines between related node/skill items\n - timeline series item related to that skill/node\n- On hover exit, restore default appearance cleanly with no stuck state.\n- Preserve existing click behavior, keyboard behavior, and detail panel opening logic.\n- Respect reduced-motion preferences and existing accessibility patterns.\n\n## Likely Files To Update\n\n- `src/components/DashboardLayout.tsx`\n- `src/hooks/useConstellationInteraction.ts`\n- `src/hooks/useConstellationHighlight.ts`\n- `src/components/TimelineInterventionsSubsection.tsx`\n- `src/components/RepeatMedicationsSubsection.tsx`\n- `src/components/ExpandableCardShell.tsx`\n- `src/index.css`\n\nUpdate additional files only if necessary.\n\n## Success Criteria\n\nAll of the following must be true:\n\n- [ ] Hovering a constellation node enters focus mode with global dimming.\n- [ ] Hovering a skill pill enters focus mode with global dimming.\n- [ ] In focus mode, only the relevant node + relationship links + related timeline series item + active skill pill remain visually highlighted.\n- [ ] Graph axis/background visibly darken during focus mode.\n- [ ] Focus mode exits correctly on mouse leave with no lingering darkened state.\n- [ ] Existing interactions (role click, skill click, panel open, timeline expand/collapse) still work.\n- [ ] `npm run lint` passes.\n- [ ] `npm run typecheck` passes.\n- [ ] `npm run build` passes.\n- [ ] Playwright MCP evidence confirms behavior for both node-hover and skill-pill-hover scenarios.\n\n## Playwright MCP Verification\n\nReviewer must validate with Playwright MCP and record evidence in `.ralph/review.md`:\n\n- Run or confirm dev server at `http://localhost:5173`\n- Capture baseline screenshot before hover\n- Hover a constellation node and capture screenshot\n- Hover a skill pill and capture screenshot\n- In both hover screenshots, verify:\n - unrelated areas are darkened\n - related graph + timeline + skill elements remain emphasized\n\n## Constraints\n\n- Do not add new dependencies.\n- Follow existing TypeScript/React conventions and current styling system.\n- Keep changes focused to this feature only.\n- If a blocker repeats with identical evidence across 3 cycles, escalate in `.ralph/review.md` instead of forcing completion.\n\n## Status\n\nTrack progress in `.ralph/plan.md`, `.ralph/build.md`, and `.ralph/review.md`.\nWhen all success criteria are met, print `LOOP_COMPLETE`.\n"}} +{"ts":"2026-02-17T14:05:55.382060260Z","type":{"kind":"loop_started","prompt":"# Task: Constellation Hover Focus Mode With Global Dimming\n\nImplement a focused hover mode so that when a user hovers a skill or node in the constellation area, non-related UI darkens and only the relevant relationship remains emphasized.\n\n## Requirements\n\n- Support hover-triggered focus mode from:\n - constellation node hover\n - skill pill hover\n- In focus mode, darken non-related UI across the page, including:\n - graph axis/background\n - unrelated graph nodes/labels/links\n - unrelated timeline and dashboard elements\n- Keep the following elements visually emphasized (not darkened):\n - hovered skill pill\n - hovered/active constellation node\n - connection lines between related node/skill items\n - timeline series item related to that skill/node\n- On hover exit, restore default appearance cleanly with no stuck state.\n- Preserve existing click behavior, keyboard behavior, and detail panel opening logic.\n- Respect reduced-motion preferences and existing accessibility patterns.\n\n## Likely Files To Update\n\n- `src/components/DashboardLayout.tsx`\n- `src/hooks/useConstellationInteraction.ts`\n- `src/hooks/useConstellationHighlight.ts`\n- `src/components/TimelineInterventionsSubsection.tsx`\n- `src/components/RepeatMedicationsSubsection.tsx`\n- `src/components/ExpandableCardShell.tsx`\n- `src/index.css`\n\nUpdate additional files only if necessary.\n\n## Success Criteria\n\nAll of the following must be true:\n\n- [ ] Hovering a constellation node enters focus mode with global dimming.\n- [ ] Hovering a skill pill enters focus mode with global dimming.\n- [ ] In focus mode, only the relevant node + relationship links + related timeline series item + active skill pill remain visually highlighted.\n- [ ] Graph axis/background visibly darken during focus mode.\n- [ ] Focus mode exits correctly on mouse leave with no lingering darkened state.\n- [ ] Existing interactions (role click, skill click, panel open, timeline expand/collapse) still work.\n- [ ] `npm run lint` passes.\n- [ ] `npm run typecheck` passes.\n- [ ] `npm run build` passes.\n- [ ] Playwright MCP evidence confirms behavior for both node-hover and skill-pill-hover scenarios.\n\n## Playwright MCP Verification\n\nReviewer must validate with Playwright MCP and record evidence in `.ralph/review.md`:\n\n- Run or confirm dev server at `http://localhost:5173`\n- Capture baseline screenshot before hover\n- Hover a constellation node and capture screenshot\n- Hover a skill pill and capture screenshot\n- In both hover screenshots, verify:\n - unrelated areas are darkened\n - related graph + timeline + skill elements remain emphasized\n\n## Constraints\n\n- Do not add new dependencies.\n- Follow existing TypeScript/React conventions and current styling system.\n- Keep changes focused to this feature only.\n- If a blocker repeats with identical evidence across 3 cycles, escalate in `.ralph/review.md` instead of forcing completion.\n\n## Status\n\nTrack progress in `.ralph/plan.md`, `.ralph/build.md`, and `.ralph/review.md`.\nWhen all success criteria are met, print `LOOP_COMPLETE`.\n"}} diff --git a/.ralph/loop.lock b/.ralph/loop.lock index 17fec79..d2682f6 100644 --- a/.ralph/loop.lock +++ b/.ralph/loop.lock @@ -1,5 +1,5 @@ { - "pid": 2080966, - "started": "2026-02-17T14:04:00.931461003Z", + "pid": 2082303, + "started": "2026-02-17T14:05:55.274566523Z", "prompt": "# Task: Constellation Hover Focus Mode With Global Dimming\n\nImplement a focused hover mode so that ..." } \ No newline at end of file diff --git a/References/andy_charlwood_career_knowledge.md:Zone.Identifier b/References/andy_charlwood_career_knowledge.md:Zone.Identifier deleted file mode 100644 index d6c1ec6..0000000 Binary files a/References/andy_charlwood_career_knowledge.md:Zone.Identifier and /dev/null differ diff --git a/References/andy_charlwood_career_knowledge_dump.md:Zone.Identifier b/References/andy_charlwood_career_knowledge_dump.md:Zone.Identifier deleted file mode 100644 index d6c1ec6..0000000 Binary files a/References/andy_charlwood_career_knowledge_dump.md:Zone.Identifier and /dev/null differ diff --git a/d3chart.jpg b/d3chart.jpg deleted file mode 100644 index 847cfab..0000000 Binary files a/d3chart.jpg and /dev/null differ diff --git a/index.html b/index.html index 330d67b..56dc021 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,9 @@ - + - Andy Charlwood — MPharm | CV + CVMIS: Charlwood, Andrew diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..f5c8caf --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/thumbnails/ome.jpg b/public/thumbnails/ome.jpg new file mode 100644 index 0000000..f449935 Binary files /dev/null and b/public/thumbnails/ome.jpg differ diff --git a/src/components/PhoneCaptcha.tsx b/src/components/PhoneCaptcha.tsx new file mode 100644 index 0000000..2b68d2e --- /dev/null +++ b/src/components/PhoneCaptcha.tsx @@ -0,0 +1,156 @@ +import { useState, useCallback, useRef, useEffect } from 'react' + +interface PhoneCaptchaProps { + phone: string +} + +function generateChallenge() { + const a = Math.floor(Math.random() * 10) + 2 + const b = Math.floor(Math.random() * 8) + 1 + return { question: `${a} + ${b}`, answer: a + b } +} + +export function PhoneCaptcha({ phone }: PhoneCaptchaProps) { + const [state, setState] = useState<'masked' | 'challenge' | 'revealed'>('masked') + const [challenge, setChallenge] = useState(generateChallenge) + const [input, setInput] = useState('') + const [error, setError] = useState(false) + const inputRef = useRef(null) + + const maskedPhone = phone.slice(0, 2) + '\u2022\u2022\u2022 \u2022\u2022\u2022\u2022\u2022\u2022' + + useEffect(() => { + if (state === 'challenge') { + requestAnimationFrame(() => inputRef.current?.focus()) + } + }, [state]) + + const handleRevealClick = useCallback(() => { + setChallenge(generateChallenge()) + setInput('') + setError(false) + setState('challenge') + }, []) + + const handleSubmit = useCallback(() => { + const parsed = parseInt(input.trim(), 10) + if (parsed === challenge.answer) { + setState('revealed') + } else { + setError(true) + setTimeout(() => { + setError(false) + setChallenge(generateChallenge()) + setInput('') + }, 600) + } + }, [input, challenge.answer]) + + const handleDismiss = useCallback(() => { + setState('masked') + setInput('') + setError(false) + }, []) + + if (state === 'revealed') { + return ( + (e.currentTarget.style.textDecoration = 'underline')} + onMouseLeave={(e) => (e.currentTarget.style.textDecoration = 'none')} + > + {phone.replace(/(\d{5})(\d{6})/, '$1 $2')} + + ) + } + + if (state === 'challenge') { + return ( +
+ + {error ? 'Try again' : `${challenge.question} = ?`} + +
+ { setInput(e.target.value); setError(false) }} + onKeyDown={(e) => { + if (e.key === 'Enter') handleSubmit() + if (e.key === 'Escape') handleDismiss() + }} + style={{ + width: '36px', + padding: '3px 4px', + fontSize: '12px', + fontFamily: 'Geist Mono, monospace', + border: `1px solid ${error ? 'var(--alert, #e53935)' : 'var(--border)'}`, + borderRadius: '4px', + background: 'var(--surface)', + color: 'var(--text-primary)', + textAlign: 'center', + outline: 'none', + transition: 'border-color 150ms', + }} + aria-label={`Solve: ${challenge.question}`} + /> + +
+
+ ) + } + + return ( + + ) +} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 51f206a..cbadc73 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -11,7 +11,8 @@ import { Wrench, X, } from 'lucide-react' -import cvmisLogo from '../../cvmis-logo.svg' +import { CvmisLogo } from './CvmisLogo' +import { PhoneCaptcha } from './PhoneCaptcha' import { patient } from '@/data/patient' import { tags } from '@/data/tags' import { alerts } from '@/data/alerts' @@ -266,22 +267,14 @@ export default function Sidebar({ activeSection, onNavigate, onSearchClick }: Si
- CVMIS +
(null) + const [historicalOpen, setHistoricalOpen] = useState(false) const { openPanel } = useDetailPanel() + const visibleEntities = useMemo(() => timelineEntities.slice(0, VISIBLE_COUNT), []) + const historicalEntities = useMemo(() => timelineEntities.slice(VISIBLE_COUNT), []) + const consultationsById = useMemo( () => new Map(timelineConsultations.map((consultation) => [consultation.id, consultation])), [], @@ -284,9 +291,13 @@ export function TimelineInterventionsSubsection({ onNodeHighlight, highlightedRo openPanel({ type: 'career-role', consultation }) }, [consultationsById, openPanel]) + const historicalHasAnyFocusRelevance = focusRelatedIds !== null && focusRelatedIds !== undefined && + historicalEntities.some((e) => focusRelatedIds.has(e.id)) + const historicalDimmed = focusRelatedIds !== null && focusRelatedIds !== undefined && !historicalHasAnyFocusRelevance + return (
- {timelineEntities.map((entity) => ( + {visibleEntities.map((entity) => ( ))} + + {historicalEntities.length > 0 && ( +
+
setHistoricalOpen((prev) => !prev)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + setHistoricalOpen((prev) => !prev) + } + }} + aria-expanded={historicalOpen} + aria-label={`${historicalOpen ? 'Hide' : 'Show'} ${historicalEntities.length} historical entries`} + style={{ + display: 'flex', + alignItems: 'center', + gap: '8px', + padding: '6px 10px', + background: 'var(--bg-dashboard)', + borderRadius: 'var(--radius-sm)', + border: '1px solid var(--border-light)', + cursor: 'pointer', + transition: 'border-color 0.15s, box-shadow 0.15s', + }} + onMouseEnter={(e) => { + e.currentTarget.style.borderColor = 'rgba(0, 137, 123, 0.2)' + e.currentTarget.style.boxShadow = 'var(--shadow-md)' + }} + onMouseLeave={(e) => { + e.currentTarget.style.borderColor = 'var(--border-light)' + e.currentTarget.style.boxShadow = 'none' + }} + > + + + {historicalOpen ? 'Hide' : 'View'} historical entries ({historicalEntities.length}) + + +
+ + + {historicalOpen && ( + +
+ {historicalEntities.map((entity) => ( + handleToggle(entity.id)} + onViewFull={() => handleViewFull(entity)} + onHighlight={onNodeHighlight} + /> + ))} +
+
+ )} +
+
+ )}
) } diff --git a/src/components/constellation/CareerConstellation.tsx b/src/components/constellation/CareerConstellation.tsx index fa33e73..d308210 100644 --- a/src/components/constellation/CareerConstellation.tsx +++ b/src/components/constellation/CareerConstellation.tsx @@ -219,8 +219,6 @@ const CareerConstellation: React.FC = ({ resolveGraphFallback, resolveRoleFallback, dimensionsTrigger: dimensions.width + dimensions.height, - pauseForInteraction: animation.pauseForInteraction, - resumeAfterInteraction: animation.resumeAfterInteraction, }) // External highlight sync @@ -319,6 +317,7 @@ const CareerConstellation: React.FC = ({ {!prefersReducedMotion && ( void isMobile: boolean visible?: boolean @@ -9,7 +10,7 @@ interface PlayPauseButtonProps { } export const PlayPauseButton: React.FC = ({ - isPlaying, onToggle, isMobile, visible = true, containerRef, + isPlaying, isCompleted = false, onToggle, isMobile, visible = true, containerRef, }) => { const vw = typeof window !== 'undefined' ? window.innerWidth : 1024 const scale = vw >= 1440 ? 1.75 : vw >= 1280 ? 1.5 : vw >= 1080 ? 1.25 : 1 @@ -62,7 +63,7 @@ export const PlayPauseButton: React.FC = ({