diff --git a/src/components/DetailPanel.tsx b/src/components/DetailPanel.tsx index cd35356..40ab83f 100644 --- a/src/components/DetailPanel.tsx +++ b/src/components/DetailPanel.tsx @@ -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 = { @@ -221,29 +222,7 @@ export function DetailPanel() { {content.type === 'skill' && } {content.type === 'skills-all' && } {content.type === 'education' && } - - {/* 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' && ( -
-

- Detail panel for: {content.type} -

-

- Content renderers will be implemented in subsequent user stories. -

-
- )} + {content.type === 'project' && } diff --git a/src/components/LoginScreen.tsx b/src/components/LoginScreen.tsx index 4cc6aad..c246cf5 100644 --- a/src/components/LoginScreen.tsx +++ b/src/components/LoginScreen.tsx @@ -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' diff --git a/src/components/detail/ProjectDetail.tsx b/src/components/detail/ProjectDetail.tsx new file mode 100644 index 0000000..f1ae215 --- /dev/null +++ b/src/components/detail/ProjectDetail.tsx @@ -0,0 +1,211 @@ +import { ExternalLink } from 'lucide-react' +import type { Investigation } from '@/types/pmr' + +interface ProjectDetailProps { + investigation: Investigation +} + +const statusColorMap: Record = { + Complete: '#059669', + Ongoing: '#D97706', + Live: '#0D6E6E', +} + +const statusBgMap: Record = { + 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 ( +
+ {/* Header: name + year + status */} +
+
+ + {investigation.requestedYear} + + + {investigation.status} + +
+
+ {investigation.requestingClinician} +
+
+ + {/* Methodology */} +
+

+ Methodology +

+

+ {investigation.methodology} +

+
+ + {/* Tech stack tags */} +
+

+ Tech Stack +

+
+ {investigation.techStack.map((tech) => ( + + {tech} + + ))} +
+
+ + {/* Results */} +
+

+ Results +

+
    + {investigation.results.map((result, index) => ( +
  • + {result} +
  • + ))} +
+
+ + {/* External link */} + {investigation.externalUrl && ( + { + e.currentTarget.style.backgroundColor = 'var(--accent-hover)' + }} + onMouseLeave={(e) => { + e.currentTarget.style.backgroundColor = 'var(--accent)' + }} + > + + View Live Project + + )} +
+ ) +} diff --git a/src/data/profile.ts b/src/data/profile.ts deleted file mode 100644 index d7d91ba..0000000 --- a/src/data/profile.ts +++ /dev/null @@ -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.` diff --git a/src/hooks/useBreakpoint.ts b/src/hooks/useBreakpoint.ts deleted file mode 100644 index 2735b3a..0000000 --- a/src/hooks/useBreakpoint.ts +++ /dev/null @@ -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(() => { - 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 -} diff --git a/src/index.css b/src/index.css index 58d1b8b..f809533 100644 --- a/src/index.css +++ b/src/index.css @@ -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; + } + }