US-032: Reduced motion audit, final cleanup, and visual review
- Add prefers-reduced-motion overrides for SubNav button transitions - Add prefers-reduced-motion overrides for smooth scroll behavior - Fix connection status dot/text transitions to respect reduced motion - Create ProjectDetail.tsx renderer and wire into DetailPanel - Remove placeholder fallback from DetailPanel (all types now covered) - Delete unused files: useBreakpoint.ts, profile.ts - Remove unused legacy --pmr-* CSS variables (18 properties) - Remove unused .pmr-theme CSS utility class
This commit is contained in:
@@ -9,6 +9,7 @@ import { ConsultationDetail } from './detail/ConsultationDetail'
|
||||
import { SkillDetail } from './detail/SkillDetail'
|
||||
import { SkillsAllDetail } from './detail/SkillsAllDetail'
|
||||
import { EducationDetail } from './detail/EducationDetail'
|
||||
import { ProjectDetail } from './detail/ProjectDetail'
|
||||
|
||||
// Width mapping from content type
|
||||
const widthMap: Record<DetailPanelContent['type'], 'narrow' | 'wide'> = {
|
||||
@@ -221,29 +222,7 @@ export function DetailPanel() {
|
||||
{content.type === 'skill' && <SkillDetail skill={content.skill} />}
|
||||
{content.type === 'skills-all' && <SkillsAllDetail category={content.category} />}
|
||||
{content.type === 'education' && <EducationDetail document={content.document} />}
|
||||
|
||||
{/* Other content types - placeholder for future stories */}
|
||||
{content.type !== 'kpi' &&
|
||||
content.type !== 'consultation' &&
|
||||
content.type !== 'career-role' &&
|
||||
content.type !== 'skill' &&
|
||||
content.type !== 'skills-all' &&
|
||||
content.type !== 'education' && (
|
||||
<div
|
||||
style={{
|
||||
fontFamily: 'var(--font-ui)',
|
||||
color: 'var(--text-secondary)',
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
Detail panel for: <strong>{content.type}</strong>
|
||||
</p>
|
||||
<p style={{ marginTop: '8px', fontSize: '12px' }}>
|
||||
Content renderers will be implemented in subsequent user stories.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{content.type === 'project' && <ProjectDetail investigation={content.investigation} />}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -370,7 +370,7 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
|
||||
height: '6px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: connectionState === 'connected' ? '#059669' : '#DC2626',
|
||||
transition: 'background-color 300ms ease',
|
||||
transition: prefersReducedMotion ? 'none' : 'background-color 300ms ease',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
@@ -379,7 +379,7 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
|
||||
fontFamily: "var(--font-geist-mono, 'Geist Mono', monospace)",
|
||||
fontSize: '10px',
|
||||
color: connectionState === 'connected' ? '#059669' : '#8DA8A5',
|
||||
transition: 'color 300ms ease',
|
||||
transition: prefersReducedMotion ? 'none' : 'color 300ms ease',
|
||||
}}
|
||||
>
|
||||
{connectionState === 'connected'
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
import type { Investigation } from '@/types/pmr'
|
||||
|
||||
interface ProjectDetailProps {
|
||||
investigation: Investigation
|
||||
}
|
||||
|
||||
const statusColorMap: Record<Investigation['status'], string> = {
|
||||
Complete: '#059669',
|
||||
Ongoing: '#D97706',
|
||||
Live: '#0D6E6E',
|
||||
}
|
||||
|
||||
const statusBgMap: Record<Investigation['status'], string> = {
|
||||
Complete: 'rgba(5,150,105,0.08)',
|
||||
Ongoing: 'rgba(217,119,6,0.08)',
|
||||
Live: 'rgba(10,128,128,0.08)',
|
||||
}
|
||||
|
||||
export function ProjectDetail({ investigation }: ProjectDetailProps) {
|
||||
const statusColor = statusColorMap[investigation.status]
|
||||
const statusBg = statusBgMap[investigation.status]
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
fontFamily: 'var(--font-ui)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '24px',
|
||||
}}
|
||||
>
|
||||
{/* Header: name + year + status */}
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
flexWrap: 'wrap',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '11px',
|
||||
fontFamily: 'var(--font-geist-mono)',
|
||||
color: 'var(--text-tertiary)',
|
||||
}}
|
||||
>
|
||||
{investigation.requestedYear}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
padding: '2px 8px',
|
||||
fontSize: '11px',
|
||||
fontWeight: 600,
|
||||
color: statusColor,
|
||||
backgroundColor: statusBg,
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
}}
|
||||
>
|
||||
{investigation.status}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
fontFamily: 'var(--font-geist-mono)',
|
||||
color: 'var(--text-tertiary)',
|
||||
}}
|
||||
>
|
||||
{investigation.requestingClinician}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Methodology */}
|
||||
<div>
|
||||
<h3
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
color: 'var(--text-secondary)',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
Methodology
|
||||
</h3>
|
||||
<p
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.6',
|
||||
color: 'var(--text-primary)',
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{investigation.methodology}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Tech stack tags */}
|
||||
<div>
|
||||
<h3
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
color: 'var(--text-secondary)',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
Tech Stack
|
||||
</h3>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
|
||||
{investigation.techStack.map((tech) => (
|
||||
<span
|
||||
key={tech}
|
||||
style={{
|
||||
padding: '3px 10px',
|
||||
fontSize: '11px',
|
||||
fontWeight: 500,
|
||||
fontFamily: 'var(--font-geist-mono)',
|
||||
color: 'var(--accent)',
|
||||
backgroundColor: 'var(--accent-light)',
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
border: '1px solid var(--accent-border)',
|
||||
}}
|
||||
>
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Results */}
|
||||
<div>
|
||||
<h3
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
color: 'var(--text-secondary)',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
Results
|
||||
</h3>
|
||||
<ul
|
||||
style={{
|
||||
margin: 0,
|
||||
paddingLeft: '20px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '8px',
|
||||
}}
|
||||
>
|
||||
{investigation.results.map((result, index) => (
|
||||
<li
|
||||
key={index}
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.6',
|
||||
color: 'var(--text-primary)',
|
||||
}}
|
||||
>
|
||||
{result}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* External link */}
|
||||
{investigation.externalUrl && (
|
||||
<a
|
||||
href={investigation.externalUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
padding: '8px 16px',
|
||||
fontSize: '13px',
|
||||
fontWeight: 600,
|
||||
fontFamily: 'var(--font-ui)',
|
||||
color: 'var(--surface)',
|
||||
backgroundColor: 'var(--accent)',
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
textDecoration: 'none',
|
||||
alignSelf: 'flex-start',
|
||||
transition: 'background-color 150ms',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'var(--accent-hover)'
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'var(--accent)'
|
||||
}}
|
||||
>
|
||||
<ExternalLink size={14} />
|
||||
View Live Project
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export const personalStatement = `Healthcare leader combining clinical pharmacy expertise with proficiency in Python, SQL, and data analytics, self-taught over the past decade through a drive to find root causes in data and build the most efficient solutions to complex problems. Currently leading population health analytics for NHS Norfolk & Waveney ICB, serving a population of 1.2 million. Experienced in working with messy, real-world prescribing data at scale to deliver actionable insights—from financial scenario modelling and pharmaceutical rebate negotiation to algorithm design and population-level pathway development. Proven track record of identifying and prioritising efficiency programmes worth £14.6M+ through automated, data-driven analysis. Skilled at translating complex clinical, financial, and analytical requirements into clear recommendations for executive stakeholders.`
|
||||
@@ -1,61 +0,0 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
type Breakpoint = 'mobile' | 'tablet' | 'desktop'
|
||||
|
||||
interface BreakpointState {
|
||||
breakpoint: Breakpoint
|
||||
isMobile: boolean
|
||||
isTablet: boolean
|
||||
isDesktop: boolean
|
||||
}
|
||||
|
||||
export function useBreakpoint(): BreakpointState {
|
||||
const [state, setState] = useState<BreakpointState>(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
return { breakpoint: 'desktop', isMobile: false, isTablet: false, isDesktop: true }
|
||||
}
|
||||
const width = window.innerWidth
|
||||
if (width < 768) {
|
||||
return { breakpoint: 'mobile', isMobile: true, isTablet: false, isDesktop: false }
|
||||
}
|
||||
if (width < 1024) {
|
||||
return { breakpoint: 'tablet', isMobile: false, isTablet: true, isDesktop: false }
|
||||
}
|
||||
return { breakpoint: 'desktop', isMobile: false, isTablet: false, isDesktop: true }
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
const width = window.innerWidth
|
||||
let breakpoint: Breakpoint
|
||||
let isMobile: boolean
|
||||
let isTablet: boolean
|
||||
let isDesktop: boolean
|
||||
|
||||
if (width < 768) {
|
||||
breakpoint = 'mobile'
|
||||
isMobile = true
|
||||
isTablet = false
|
||||
isDesktop = false
|
||||
} else if (width < 1024) {
|
||||
breakpoint = 'tablet'
|
||||
isMobile = false
|
||||
isTablet = true
|
||||
isDesktop = false
|
||||
} else {
|
||||
breakpoint = 'desktop'
|
||||
isMobile = false
|
||||
isTablet = false
|
||||
isDesktop = true
|
||||
}
|
||||
|
||||
setState({ breakpoint, isMobile, isTablet, isDesktop })
|
||||
}
|
||||
|
||||
handleResize()
|
||||
window.addEventListener('resize', handleResize)
|
||||
return () => window.removeEventListener('resize', handleResize)
|
||||
}, [])
|
||||
|
||||
return state
|
||||
}
|
||||
+11
-24
@@ -139,25 +139,6 @@
|
||||
--backdrop-blur: 4px;
|
||||
--backdrop-bg: rgba(26,43,42,0.15);
|
||||
|
||||
/* Legacy PMR tokens — kept for backward compat during transition (cleaned up in Task 21) */
|
||||
--pmr-content: #F0F5F4;
|
||||
--pmr-card: #FFFFFF;
|
||||
--pmr-sidebar: #F7FAFA;
|
||||
--pmr-banner: #334155;
|
||||
--pmr-nhs-blue: #005EB8;
|
||||
--pmr-green: #22C55E;
|
||||
--pmr-amber: #F59E0B;
|
||||
--pmr-red: #EF4444;
|
||||
--pmr-text-primary: #1A2B2A;
|
||||
--pmr-text-secondary: #5B7A78;
|
||||
--pmr-border: #D4E0DE;
|
||||
--pmr-border-dark: #D1D5DB;
|
||||
--pmr-selected: #EFF6FF;
|
||||
--pmr-alert-bg: #FEF3C7;
|
||||
--pmr-alert-border: #F59E0B;
|
||||
--pmr-alert-text: #92400E;
|
||||
--pmr-radius: 8px;
|
||||
--pmr-radius-login: 12px;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -193,11 +174,6 @@ body {
|
||||
.font-geist-mono {
|
||||
font-family: var(--font-geist-mono);
|
||||
}
|
||||
.pmr-theme {
|
||||
background-color: var(--bg-dashboard);
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-ui);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
@@ -435,4 +411,15 @@ textarea:focus-visible {
|
||||
animation: none;
|
||||
border-top-color: #0D6E6E;
|
||||
}
|
||||
|
||||
/* Instant SubNav transitions */
|
||||
.subnav-scroll button {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
/* Instant smooth scroll override */
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user