refactor: decompose CareerConstellation monolith into focused modules

Break 1102-line CareerConstellation.tsx into:
- constellation/constants.ts: sizing, opacity, domain color tokens
- constellation/types.ts: SimNode, SimLink, LayoutParams interfaces
- hooks/useForceSimulation.ts: D3 simulation lifecycle
- hooks/useConstellationHighlight.ts: highlight/dim logic
- hooks/useConstellationInteraction.ts: mouse/touch/pin handlers
- constellation/MobileAccordion.tsx: tap-to-expand role details
- constellation/ConstellationLegend.tsx: domain legend
- constellation/AccessibleNodeOverlay.tsx: keyboard navigation buttons
- constellation/CareerConstellation.tsx: 288-line orchestrator

All existing behaviour preserved. Quality gates pass.
This commit is contained in:
2026-02-16 14:06:41 +00:00
parent b34ecb89e2
commit 65b265733e
11 changed files with 1324 additions and 1102 deletions
@@ -0,0 +1,53 @@
import React from 'react'
import { supportsCoarsePointer } from './constants'
interface ConstellationLegendProps {
isTouch: boolean
}
export const ConstellationLegend: React.FC<ConstellationLegendProps> = ({ isTouch }) => {
const items = [
{ label: 'Technical', color: 'var(--accent)' },
{ label: 'Clinical', color: 'var(--success)' },
{ label: 'Leadership', color: 'var(--amber)' },
]
return (
<div
style={{
display: 'flex',
flexWrap: 'wrap',
alignItems: 'center',
gap: '12px',
padding: '6px 12px',
fontFamily: 'var(--font-geist-mono)',
fontSize: '10px',
color: 'var(--text-tertiary)',
lineHeight: '24px',
}}
>
{items.map((item, i) => (
<React.Fragment key={item.label}>
{i > 0 && (
<span style={{ color: 'var(--border)', userSelect: 'none' }} aria-hidden="true">·</span>
)}
<span style={{ display: 'inline-flex', alignItems: 'center', gap: '5px' }}>
<span
style={{
display: 'inline-block',
width: '6px',
height: '6px',
borderRadius: '50%',
backgroundColor: item.color,
flexShrink: 0,
}}
/>
{item.label}
</span>
</React.Fragment>
))}
<span style={{ color: 'var(--border)', userSelect: 'none' }} aria-hidden="true">·</span>
<span style={{ opacity: 0.7 }}>{isTouch || supportsCoarsePointer ? 'Tap to explore connections' : 'Hover to explore connections'}</span>
</div>
)
}