Pre UX polish

This commit is contained in:
2026-02-18 00:23:35 +00:00
parent 836305e2a3
commit 62c0d2ea19
13 changed files with 262 additions and 376 deletions
+30 -12
View File
@@ -46,6 +46,7 @@ export function DashboardLayout() {
const isMobileNav = useIsMobileNav()
const chronologyRef = useRef<HTMLDivElement>(null)
const patientSummaryRef = useRef<HTMLDivElement>(null)
const constellationWrapperRef = useRef<HTMLDivElement>(null)
const activeSection = useActiveSection()
const { openPanel } = useDetailPanel()
const careerConsultationsById = useMemo(
@@ -94,18 +95,35 @@ export function DashboardLayout() {
return related
}, [globalFocusId, nodeTypeById, skillToRoles, roleToSkills])
// Signal constellation animation readiness when patient summary scrolls out of view
// Signal constellation animation readiness:
// Desktop (>=768): when patient summary scrolls out of view (graph is side-by-side)
// Mobile (<768): when the constellation itself scrolls into view (single-column layout)
useEffect(() => {
const el = patientSummaryRef.current
if (!el) return
const observer = new IntersectionObserver(
([entry]) => {
if (!entry.isIntersecting) setConstellationReady(true)
},
{ threshold: 0 },
)
observer.observe(el)
return () => observer.disconnect()
const isMobile = window.innerWidth < 768
if (isMobile) {
const el = constellationWrapperRef.current
if (!el) return
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) setConstellationReady(true)
},
{ threshold: 0.1 },
)
observer.observe(el)
return () => observer.disconnect()
} else {
const el = patientSummaryRef.current
if (!el) return
const observer = new IntersectionObserver(
([entry]) => {
if (!entry.isIntersecting) setConstellationReady(true)
},
{ threshold: 0 },
)
observer.observe(el)
return () => observer.disconnect()
}
}, [])
// Measure the chronology stream height so the constellation graph can match it
@@ -301,7 +319,7 @@ export function DashboardLayout() {
<TimelineInterventionsSubsection onNodeHighlight={handleNodeHighlight} highlightedRoleId={highlightedRoleId} focusRelatedIds={focusRelatedIds} />
</div>
</div>
<div className="pathway-graph-sticky">
<div ref={constellationWrapperRef} className="pathway-graph-sticky">
<CareerConstellation
onRoleClick={handleRoleClick}
onSkillClick={handleSkillClick}
+39 -14
View File
@@ -21,7 +21,7 @@ function ProjectItem({
onClick,
}: ProjectItemProps) {
const dotColor = PROJECT_STATUS_COLORS[project.status]
const isLive = project.status === 'Live'
const livePillLabel = project.demoUrl ? 'Live Demo' : project.externalUrl ? 'Live' : null
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
@@ -118,19 +118,44 @@ function ProjectItem({
gap: '8px',
}}
>
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
backgroundColor: dotColor,
flexShrink: 0,
marginTop: '4px',
animation: isLive ? 'pulse 2s infinite' : undefined,
}}
aria-hidden="true"
/>
<span style={{ flex: 1, fontWeight: 500 }}>{project.name}</span>
{!livePillLabel && (
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
backgroundColor: dotColor,
flexShrink: 0,
marginTop: '4px',
}}
aria-hidden="true"
/>
)}
<span style={{ flex: 1, fontWeight: 500, display: 'flex', alignItems: 'center', gap: '6px', flexWrap: 'wrap' }}>
{project.name}
{livePillLabel && (
<span
className="live-pill"
style={{
fontSize: '9px',
fontFamily: 'var(--font-geist-mono)',
fontWeight: 600,
letterSpacing: '0.05em',
textTransform: 'uppercase',
padding: '2px 7px',
borderRadius: '9999px',
background: 'rgba(34, 197, 94, 0.12)',
color: '#16a34a',
border: '1px solid rgba(34, 197, 94, 0.3)',
animation: 'live-pill-pulse 2s ease-in-out infinite',
whiteSpace: 'nowrap',
lineHeight: '1.4',
}}
>
{livePillLabel}
</span>
)}
</span>
<span
style={{
fontSize: '11px',
+18
View File
@@ -226,6 +226,18 @@ html {
}
}
/* Live pill pulse — gentle glow throb */
@keyframes live-pill-pulse {
0%, 100% {
opacity: 1;
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.2);
}
50% {
opacity: 0.8;
box-shadow: 0 0 6px 2px rgba(34, 197, 94, 0.15);
}
}
/* Login spinner */
@keyframes login-spin {
to { transform: rotate(360deg); }
@@ -681,6 +693,12 @@ textarea:focus-visible {
animation: none;
}
/* Static live pill */
.live-pill {
animation: none !important;
box-shadow: none !important;
}
/* Instant smooth scroll override */
html {
scroll-behavior: auto;