Redesign CVMIS system
This commit is contained in:
+173
-37
@@ -1,65 +1,201 @@
|
||||
# Implementation Plan — The Clinical Record (v3)
|
||||
# Implementation Plan — GP System Dashboard Overhaul
|
||||
|
||||
## Project Overview
|
||||
|
||||
A premium portfolio CV presented as a clinical information system. The *structure* and *layout* come from GP software (EMIS Web, SystmOne) — patient banner, sidebar navigation, consultation journal, medications table, etc. — but the *execution* is **Clinical Luxury**: refined typography, layered shadows, generous spacing, premium fonts, atmospheric depth.
|
||||
|
||||
**This is NOT a faithful NHS clone.** It's a showcase portfolio that *evokes* clinical software while being distinctly beautiful.
|
||||
|
||||
**What's already done:** Data files (`src/data/*`), type system (`src/types/pmr.ts`), phase management (`App.tsx`), boot sequence, ECG animation, and design system foundation (Tailwind tokens, fonts, CSS variables).
|
||||
|
||||
**What this plan builds:** The visual layer from login screen through to the full PMR interface — every component rebuilt to Clinical Luxury quality with the new premium font, refined surfaces, and user-interactive login.
|
||||
Replace the "CareerRecord PMR" sidebar-nav + view-switching interface with a tile-based GP System dashboard. Reference design: `References/GPSystemconcept.html`.
|
||||
|
||||
## Quality Checks
|
||||
|
||||
Run after every task. All must pass before committing.
|
||||
- `npm run typecheck` — zero errors
|
||||
- `npm run lint` — pass (pre-existing AccessibilityContext warning OK)
|
||||
- `npm run build` — must succeed
|
||||
|
||||
```
|
||||
npm run typecheck
|
||||
npm run lint
|
||||
npm run build
|
||||
```
|
||||
## Important
|
||||
|
||||
## Reference Files
|
||||
**This file is for progress tracking only.** For implementation detail on any task, read the referenced file in `Ralph/refs/`. Do NOT bloat this file with implementation notes — keep it lean.
|
||||
|
||||
Each task references files in `Ralph/refs/`. Read the referenced file(s) for full design specs, implementation patterns, and code snippets. The ref files ARE the spec — do not duplicate their content here.
|
||||
|
||||
Always also read `Ralph/refs/ref-design-system.md` — it is the single source of truth for colors, typography, spacing, surfaces, and motion.
|
||||
|
||||
Also read `CLAUDE.md` for font setup instructions (Elvaro Grotesque and Blumir candidates in `Fonts/` directory).
|
||||
---
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] **Task 1: Design system foundation.** Tailwind config, CSS variables, font loading. *(Completed — see progress.txt)*
|
||||
### Phase 0: Foundation
|
||||
|
||||
- [x] **Task 1b: Boot sequence and ECG animation.** *(Completed and locked — do not modify)*
|
||||
#### Task 1: Update design tokens and Tailwind config
|
||||
> Detail: `Ralph/refs/ref-01-design-tokens.md`
|
||||
- [ ] Update CSS custom properties in `src/index.css` (new palette, shadows, layout vars)
|
||||
- [ ] Update `tailwind.config.js` (colors, shadows, borders, radius)
|
||||
- [ ] Keep boot/ECG/login tokens unchanged
|
||||
- [ ] Run quality checks
|
||||
|
||||
- [x] **Task 2: Set up premium font and update Tailwind config.** Read `CLAUDE.md` (Typography section) and `Ralph/refs/ref-design-system.md`. Load both candidate fonts from `Fonts/` directory (Elvaro Grotesque WOFF2 files and Blumir variable font WOFF2). Add `@font-face` declarations in `src/index.css`. Update Tailwind config to add `font-ui` family pointing to the chosen font (start with Elvaro, can be swapped later). Replace `font-inter` references in Tailwind config with `font-ui`. Ensure Geist Mono remains the monospace font. Keep Fira Code for boot/ECG phases only.
|
||||
#### Task 2: Create new data files and update types
|
||||
> Detail: `Ralph/refs/ref-02-data-types.md`
|
||||
- [ ] Create `src/data/profile.ts` (personal statement)
|
||||
- [ ] Create `src/data/tags.ts` (sidebar tags)
|
||||
- [ ] Create `src/data/alerts.ts` (sidebar alert flags)
|
||||
- [ ] Create `src/data/kpis.ts` (Latest Results metrics)
|
||||
- [ ] Create `src/data/skills.ts` (skills with medication frequency + years)
|
||||
- [ ] Update `src/types/pmr.ts` (new interfaces)
|
||||
- [ ] Run quality checks
|
||||
|
||||
- [x] **Task 3: Rebuild LoginScreen.** Read `Ralph/refs/ref-transition-login.md`. Key changes from prior version: (a) Typing speed is now **80ms/char** for username, **60ms/dot** for password — natural pace, not frantic. (b) After typing completes, the "Log In" button becomes **user-interactive** — the user clicks it to proceed. It is NOT auto-triggered. Button should have hover state, full opacity when ready, disabled/dimmed while typing. (c) Card shadow uses multi-layered shadow per design system. (d) Uses [UI font] for labels, Geist Mono for input fields. (e) `prefers-reduced-motion`: typing completes instantly, button is immediately interactive.
|
||||
#### Task 3: Update CLAUDE.md for new architecture
|
||||
- [x] Already completed during project setup (manual intervention 2026-02-13)
|
||||
|
||||
- [x] **Task 4: Rebuild PatientBanner.** Read `Ralph/refs/ref-banner-sidebar.md` (Patient Banner section). Full banner (80px) with surname-first format, demographic details, action buttons. Condensed banner (48px) via IntersectionObserver at 100px scroll. Mobile minimal banner with overflow menu. Uses [UI font] throughout. NHS Number tooltip: "GPhC Registration Number".
|
||||
### Phase 1: Core Layout
|
||||
|
||||
- [x] **Task 4b: Fix PatientBanner scroll condensation.** Read `Ralph/refs/ref-banner-sidebar.md` (Patient Banner section + Implementation Patterns). The full 3-row banner (80px — name/status, demographics, contact) never displays because the IntersectionObserver sentinel is broken. The sentinel (`absolute top-0` with `h-0`) is inside a React fragment next to the sticky header — it positions relative to the viewport, and the `-100px` rootMargin means it's immediately "not intersecting", so the banner always shows as condensed. Fix: ensure the sentinel is placed in the document flow ABOVE the scrollable content area (not absolute-positioned inside the banner fragment), so it's naturally visible on load and only scrolls out of view when the user scrolls 100px. Verify that on page load the full banner displays, and after scrolling 100px it smoothly condenses to the single-row 48px layout.
|
||||
#### Task 4: Build TopBar component
|
||||
> Detail: `Ralph/refs/ref-03-topbar-sidebar.md` (TopBar section)
|
||||
- [ ] Create `src/components/TopBar.tsx`
|
||||
- [ ] Brand section (icon + name + version tag)
|
||||
- [ ] Search bar (triggers command palette, not inline search)
|
||||
- [ ] Session info (mono font, pill badge)
|
||||
- [ ] Fixed position, 48px height, white bg, bottom border
|
||||
- [ ] Run quality checks
|
||||
|
||||
- [x] **Task 5: Rebuild ClinicalSidebar.** Read `Ralph/refs/ref-banner-sidebar.md` (Left Sidebar + Navigation sections). CV-friendly labels: Summary, Experience, Skills, Achievements, Projects, Education, Contact. 220px fixed width. Header branding, search input, navigation items with exact states (default/hover/active), separator line, footer with session info. Tablet mode: 56px icon-only. Keyboard shortcuts: Alt+1-7, arrow keys, "/" for search. URL hash routing.
|
||||
#### Task 5: Build new Sidebar — PersonHeader
|
||||
> Detail: `Ralph/refs/ref-03-topbar-sidebar.md` (Sidebar PersonHeader section)
|
||||
- [ ] Create `src/components/Sidebar.tsx`
|
||||
- [ ] Avatar circle (52px, teal gradient, initials)
|
||||
- [ ] Name, title, status badge with pulse dot
|
||||
- [ ] Details grid (GPhC, Education, Location, Phone, Email, Registered)
|
||||
- [ ] 272px width, light background, right border
|
||||
- [ ] Run quality checks
|
||||
|
||||
- [x] **Task 6: Rebuild PMRInterface layout + Breadcrumb.** Read `Ralph/refs/ref-banner-sidebar.md` (PMRInterface Layout + Breadcrumb sections). Fixed sidebar + sticky banner + scrollable content on `#F5F7FA`. Create `Breadcrumb.tsx`. Interface materialization animations (banner → sidebar → content stagger). View switching is INSTANT. Mobile: bottom nav bar. Update ViewId type if needed.
|
||||
#### Task 6: Build new Sidebar — Tags + Alerts
|
||||
> Detail: `Ralph/refs/ref-03-topbar-sidebar.md` (Tags and Alerts section)
|
||||
- [ ] Section title component (uppercase, divider line)
|
||||
- [ ] Tags section (flex wrap pills, color variants)
|
||||
- [ ] Alerts section (colored flag items with icons)
|
||||
- [ ] Run quality checks
|
||||
|
||||
- [x] **Task 7: Rebuild SummaryView + Clinical Alert.** Read `Ralph/refs/ref-summary-alert.md`. Clinical Alert with spring animation entrance, acknowledge → checkmark → collapse sequence. Summary cards: Demographics (full-width key-value), Active Problems (traffic lights), Current Skills quick table, Last Consultation preview. 2-column grid desktop, single column mobile. Navigation links to other views.
|
||||
#### Task 7: Build DashboardLayout and wire up App.tsx
|
||||
> Detail: `Ralph/refs/ref-04-dashboard-layout.md`
|
||||
- [ ] Create `src/components/DashboardLayout.tsx`
|
||||
- [ ] Three-zone layout: TopBar (fixed) + Sidebar (fixed) + Main (scrollable card grid)
|
||||
- [ ] Card grid: 2 columns desktop, 1 column <900px
|
||||
- [ ] Framer Motion entrance animations (topbar → sidebar → content)
|
||||
- [ ] Update App.tsx: replace PMRInterface with DashboardLayout in PMR phase
|
||||
- [ ] Verify boot → ECG → login → dashboard transition works
|
||||
- [ ] Run quality checks
|
||||
|
||||
- [x] **Task 8: Rebuild ConsultationsView.** Read `Ralph/refs/ref-consultations.md`. Reverse-chronological journal. Collapsed entries with date, org, role, key achievement. Expanded: H/E/P structure with coded entries. Height-only expand animation (no opacity fade). One expanded at a time. 3px left border color-coded by employer. Second clinical alert on first visit.
|
||||
### Phase 2: Dashboard Tiles
|
||||
|
||||
- [x] **Task 9: Rebuild MedicationsView.** Read `Ralph/refs/ref-medications.md`. Three category tabs (Active/Clinical/PRN). Semantic `<table>` with sortable columns. Expandable prescribing history in Geist Mono. Status dots with text labels. Mobile: card layout.
|
||||
#### Task 8: Build reusable Card component
|
||||
> Detail: `Ralph/refs/ref-05-card-and-top-tiles.md` (Card section)
|
||||
- [ ] Create `src/components/Card.tsx`
|
||||
- [ ] Base card styling (white, border, radius 8px, shadow-sm, hover shadow-md)
|
||||
- [ ] `full` variant (spans both grid columns)
|
||||
- [ ] CardHeader sub-component (dot + title + optional right text)
|
||||
- [ ] Run quality checks
|
||||
|
||||
- [x] **Task 10: Rebuild ProblemsView.** Read `Ralph/refs/ref-problems.md`. Two sections: Active Problems and Resolved Problems. Traffic light dots (8px, always with text labels). Code column in Geist Mono. Expandable rows with narrative + linked consultation navigation. Mobile: card layout.
|
||||
#### Task 9: Build PatientSummary tile
|
||||
> Detail: `Ralph/refs/ref-05-card-and-top-tiles.md` (PatientSummary section)
|
||||
- [ ] Create `src/components/tiles/PatientSummaryTile.tsx`
|
||||
- [ ] Full-width card, first in grid
|
||||
- [ ] Personal statement from `src/data/profile.ts`
|
||||
- [ ] Run quality checks
|
||||
|
||||
- [x] **Task 11: Rebuild InvestigationsView + DocumentsView.** Read `Ralph/refs/ref-investigations-documents.md`. Both views share the expandable-row pattern with tree-indented monospace content (box-drawing characters). Investigations: status badges (Complete/Ongoing/Live). Documents: type icons per document category. "View Results" button for PharMetrics only. Mobile: card layouts.
|
||||
#### Task 10: Build LatestResults tile
|
||||
> Detail: `Ralph/refs/ref-05-card-and-top-tiles.md` (LatestResults section)
|
||||
- [ ] Create `src/components/tiles/LatestResultsTile.tsx`
|
||||
- [ ] Half-width card, 2x2 metric grid
|
||||
- [ ] Four KPI metric cards with colored values
|
||||
- [ ] Data from `src/data/kpis.ts`
|
||||
- [ ] Run quality checks
|
||||
|
||||
- [x] **Task 12: Rebuild ReferralsView (Contact).** Read `Ralph/refs/ref-referrals.md`. Clinical referral form with priority radio buttons (Urgent/Routine/Two-Week Wait with tongue-in-cheek tooltips). Form validation, reference number generation, success state. Direct contact table below form.
|
||||
#### Task 11: Build CoreSkills tile ("Repeat Medications")
|
||||
> Detail: `Ralph/refs/ref-05-card-and-top-tiles.md` (CoreSkills section)
|
||||
- [ ] Create `src/components/tiles/CoreSkillsTile.tsx`
|
||||
- [ ] Half-width card, next to LatestResults
|
||||
- [ ] Skills listed as medications with frequency + years
|
||||
- [ ] Data from `src/data/skills.ts`
|
||||
- [ ] Run quality checks
|
||||
|
||||
- [x] **Task 13: Fuzzy search with fuse.js.** Read `Ralph/refs/ref-interactions.md` (Search section). Install fuse.js. Build search index from all content. Results dropdown grouped by section. Clicking a result navigates to section + expands matching item. Mobile: search at top of each view.
|
||||
#### Task 12: Build LastConsultation tile
|
||||
> Detail: `Ralph/refs/ref-06-bottom-tiles.md` (LastConsultation section)
|
||||
- [ ] Create `src/components/tiles/LastConsultationTile.tsx`
|
||||
- [ ] Full-width card
|
||||
- [ ] Header info row (Date, Org, Type, Band)
|
||||
- [ ] Role title + achievement bullet list
|
||||
- [ ] Data from first entry in `src/data/consultations.ts`
|
||||
- [ ] Run quality checks
|
||||
|
||||
- [x] **Task 14: Responsive design audit.** Read `Ralph/refs/ref-interactions.md` (Responsive Strategy section). Test all three breakpoints: Desktop (>1024px), Tablet (768-1024px), Mobile (<768px). Tables → card layouts on mobile. Bottom nav bar. Touch targets ≥48px.
|
||||
#### Task 13: Build CareerActivity tile
|
||||
> Detail: `Ralph/refs/ref-06-bottom-tiles.md` (CareerActivity section)
|
||||
- [ ] Create `src/components/tiles/CareerActivityTile.tsx`
|
||||
- [ ] Full-width card, two-column activity grid
|
||||
- [ ] Merge roles + projects + certs + education into timeline
|
||||
- [ ] Color-coded dots by entry type
|
||||
- [ ] Run quality checks
|
||||
|
||||
- [x] **Task 15: Accessibility audit + final polish.** Read `Ralph/refs/ref-interactions.md` (Accessibility section). Semantic HTML, ARIA attributes, focus management, keyboard navigation, screen reader announcements, `prefers-reduced-motion` support, WCAG 2.1 AA contrast. Final visual consistency pass.
|
||||
#### Task 14: Build Education tile
|
||||
> Detail: `Ralph/refs/ref-06-bottom-tiles.md` (Education section)
|
||||
- [ ] Create `src/components/tiles/EducationTile.tsx`
|
||||
- [ ] Full-width card, below Career Activity
|
||||
- [ ] Education entries from documents data
|
||||
- [ ] Run quality checks
|
||||
|
||||
#### Task 15: Build Projects tile
|
||||
> Detail: `Ralph/refs/ref-06-bottom-tiles.md` (Projects section)
|
||||
- [ ] Create `src/components/tiles/ProjectsTile.tsx`
|
||||
- [ ] Full-width card, prominent presentation
|
||||
- [ ] Status badges, project names, years, descriptions
|
||||
- [ ] Data from `src/data/investigations.ts`
|
||||
- [ ] Run quality checks
|
||||
|
||||
### Phase 3: Interactions
|
||||
|
||||
#### Task 16: Tile expansion system
|
||||
> Detail: `Ralph/refs/ref-07-interactions.md` (Tile Expansion section)
|
||||
- [ ] CareerActivity items expand to show full role detail
|
||||
- [ ] Projects items expand to show methodology, tech stack, results
|
||||
- [ ] CoreSkills items expand to show prescribing history
|
||||
- [ ] Height-only animation (200ms, no opacity fade)
|
||||
- [ ] Single-expand accordion
|
||||
- [ ] Keyboard: Enter/Space to expand, Escape to collapse
|
||||
- [ ] Run quality checks
|
||||
|
||||
#### Task 17: KPI flip card interaction
|
||||
> Detail: `Ralph/refs/ref-07-interactions.md` (KPI Flip section)
|
||||
- [ ] LatestResults metrics flip on click
|
||||
- [ ] Front: value + label. Back: explanation text
|
||||
- [ ] CSS perspective flip (400ms) or instant swap with reduced motion
|
||||
- [ ] One card flipped at a time
|
||||
- [ ] Run quality checks
|
||||
|
||||
#### Task 18: Build Command Palette
|
||||
> Detail: `Ralph/refs/ref-07-interactions.md` (Command Palette section)
|
||||
- [ ] Create `src/components/CommandPalette.tsx`
|
||||
- [ ] Ctrl+K trigger + search bar click trigger
|
||||
- [ ] Overlay with backdrop blur, ESC to close
|
||||
- [ ] Fuzzy search via fuse.js (adapt `src/lib/search.ts`)
|
||||
- [ ] Grouped results by section + Quick Actions
|
||||
- [ ] Keyboard navigation (arrows, Enter, Escape)
|
||||
- [ ] Run quality checks
|
||||
|
||||
### Phase 4: Polish
|
||||
|
||||
#### Task 19: Responsive design
|
||||
> Detail: `Ralph/refs/ref-08-polish.md` (Responsive section)
|
||||
- [ ] Desktop (>1024px): full sidebar + 2-column grid
|
||||
- [ ] Tablet (768–1024px): collapsed/hidden sidebar + adapted grid
|
||||
- [ ] Mobile (<768px): no sidebar, single-column tiles, simplified topbar
|
||||
- [ ] Touch-friendly targets (48px+)
|
||||
- [ ] Run quality checks
|
||||
|
||||
#### Task 20: Accessibility audit
|
||||
> Detail: `Ralph/refs/ref-08-polish.md` (Accessibility section)
|
||||
- [ ] Semantic HTML (header, nav, main, article, section)
|
||||
- [ ] Keyboard navigation (Tab, Enter/Space, Escape, Ctrl+K, arrows)
|
||||
- [ ] ARIA (expanded, controls, labels, live regions, dialog)
|
||||
- [ ] Focus management (trap in palette, visible rings, return focus)
|
||||
- [ ] `prefers-reduced-motion` on all animations
|
||||
- [ ] Color contrast verification
|
||||
- [ ] Run quality checks
|
||||
|
||||
#### Task 21: Clean up and final polish
|
||||
> Detail: `Ralph/refs/ref-08-polish.md` (Cleanup section)
|
||||
- [ ] Remove unused old components (PatientBanner, ClinicalSidebar, Breadcrumb, etc.)
|
||||
- [ ] Remove unused hooks (useScrollCondensation if unused)
|
||||
- [ ] Verify no dead imports
|
||||
- [ ] Final visual review against concept HTML
|
||||
- [ ] Run quality checks (clean build)
|
||||
|
||||
+84
-108
@@ -2,77 +2,58 @@
|
||||
|
||||
You are operating inside an automated loop. Each iteration you receive fresh context - you have NO memory of previous iterations. Your only persistence is the filesystem.
|
||||
|
||||
You are implementing **Design 7: The Clinical Record** — a Patient Medical Record (PMR) system that presents Andy's CV as a clinician would view a patient record. This is a visual redesign rebuilding existing components to achieve absolute thematic fidelity to real NHS clinical software.
|
||||
You are implementing **a GP System Dashboard** — a tile-based clinical record interface that presents Andy's CV as a GP surgery would display a patient record. The clinical metaphor lives in the structure (tiles as record sections, skills as "medications" with frequency, alerts, KPI metrics, career timeline) while the visual execution is modern and premium.
|
||||
|
||||
**The Concept:**
|
||||
The "patient" is Andy's career. Users navigate a genuine NHS clinical software interface (similar to EMIS Web, SystmOne, Vision) with a patient banner, sidebar navigation, consultation journal, medications table, clinical alerts, and a login sequence. The clinical metaphor lives in the LAYOUT and VISUAL PRESENTATION — the sidebar labels use CV-friendly terms (Experience, Skills, Achievements, Projects, Education, Contact) while each view is laid out like its clinical equivalent (consultation journal, medications table, problems list, etc.).
|
||||
|
||||
**IMPORTANT — Sidebar Label Convention:**
|
||||
The sidebar uses CV-intuitive labels, NOT clinical jargon. But each view's content is presented in the clinical format:
|
||||
- **Summary** → Patient summary layout
|
||||
- **Experience** (not "Consultations") → Consultation journal layout with History/Examination/Plan
|
||||
- **Skills** (not "Medications") → Medications table layout with dosages/frequency
|
||||
- **Achievements** (not "Problems") → Problems list layout with traffic lights
|
||||
- **Projects** (not "Investigations") → Investigation results layout
|
||||
- **Education** (not "Documents") → Attached documents layout
|
||||
- **Contact** (not "Referrals") → Referral form layout
|
||||
The "patient" is Andy's career. After a theatrical boot → ECG → login sequence, users see a dashboard with a light sidebar (person details, tags, alert flags) and a scrollable grid of tiles (Patient Summary, Latest Results, Repeat Medications/Skills, Last Consultation, Career Activity, Education, Projects). Tiles can be expanded for detail. A command palette (Ctrl+K) provides search. The reference design is `References/GPSystemconcept.html`.
|
||||
|
||||
## Your Task This Iteration
|
||||
|
||||
1. **Read the Design Guidance in the reference file** (REQUIRED for visual components): Each reference file in `Ralph/refs/` contains a "Design Guidance (from /frontend-design)" section at the bottom with pre-generated design direction, code patterns, and implementation details. You MUST read this section before writing code — it provides the aesthetic direction and code examples for the component. Do NOT invoke the `/frontend-design` skill at runtime — the guidance is already embedded in the ref files.
|
||||
1. **Read the reference file for your task** (REQUIRED): Each task in `IMPLEMENTATION_PLAN.md` references a detail file in `Ralph/refs/`. You MUST read this file before writing code — it provides the full specification, CSS values, data sources, and component structure.
|
||||
|
||||
2. **Read the plan**: Open `IMPLEMENTATION_PLAN.md` and find the highest-priority unchecked item (`- [ ]`). Items are listed in priority order - pick the first unchecked one.
|
||||
2. **Read the plan**: Open `IMPLEMENTATION_PLAN.md` and find the highest-priority unchecked item (`- [ ]`). Items are listed in dependency order — pick the first unchecked one. **The plan is for tracking only** — all implementation detail is in the referenced `Ralph/refs/` file.
|
||||
|
||||
3. **Read accumulated learnings**: Open `progress.txt` and read the "Codebase Patterns" section. This contains learnings from previous iterations about PMR design system, data architecture, animation approach, and clinical system authenticity.
|
||||
3. **Read accumulated learnings**: Open `progress.txt` and read the "Codebase Patterns" section AND the most recent manual intervention entry. These contain critical context about the architecture, established patterns, and decisions from previous iterations.
|
||||
|
||||
4. **Read guardrails**: Open `guardrails.md` and read ALL guardrails. These are hard rules you MUST follow. Key guardrails include:
|
||||
- Light-mode only (clinical systems don't have dark mode)
|
||||
- Instant view switching (no animations between views)
|
||||
- Proper semantic table markup for all data tables
|
||||
- Traffic lights must always have text labels
|
||||
- Exact NHS blue color (#005EB8)
|
||||
- ECG must end with flatline (not fade to white)
|
||||
- Login typing animation specifics
|
||||
- Consultation History/Examination/Plan format
|
||||
- Coded entries in [XXX000] format
|
||||
- Sidebar labels are CV-friendly (Experience, Skills, etc.), NOT clinical jargon
|
||||
- Light-mode only
|
||||
- Teal accent `#0D6E6E` (not NHS Blue) for interactive elements
|
||||
- 8px border-radius for cards (not 4px)
|
||||
- Three-tier shadow system (sm/md/lg)
|
||||
- Height-only tile expansion (no opacity fade)
|
||||
- Skills frequency: user-specified values (Data Analysis="Twice daily", etc.)
|
||||
- Sidebar contains ONLY PersonHeader + Tags + Alerts
|
||||
- Elvaro Grotesque font (not DM Sans, Inter, or Roboto)
|
||||
- Geist Mono for data/timestamps (not Fira Code in dashboard)
|
||||
|
||||
5. **Implement the item**: Complete the single task you selected. Keep changes focused - one task per iteration. Write production-quality React/TypeScript code that faithfully reproduces a clinical information system. This is a design showcase requiring absolute thematic fidelity.
|
||||
5. **Implement the item**: Complete the single task you selected. Keep changes focused — one task per iteration. Write production-quality React/TypeScript code.
|
||||
|
||||
**IMPORTANT — Ref files are the source of truth, not existing code.** The current codebase contains errors and legacy patterns from earlier iterations. Do NOT treat the existing component structure, layout, or behaviour as authoritative. If the existing code does not match what the ref file specifies, **rebuild the component from the ref spec** rather than patching around the existing implementation. The ref files define the target; the existing code is just a starting point that may need to be replaced entirely.
|
||||
**IMPORTANT — Ref files are the source of truth.** If existing code contradicts the ref file, rebuild from the ref spec.
|
||||
|
||||
6. **Run quality checks**: Execute the quality check commands listed in `IMPLEMENTATION_PLAN.md` under "Quality Checks". Fix any issues before proceeding.
|
||||
6. **Run quality checks**: Execute `npm run typecheck`, `npm run lint`, `npm run build`. Fix any issues before proceeding.
|
||||
|
||||
7. **Visual Review** (Tasks 1b-11 only — skip for non-visual tasks like Task 1, 12-15): After quality checks pass, verify your work visually in the browser using the Playwright MCP browser tools:
|
||||
7. **Visual Review** (for visual tasks): After quality checks pass, verify your work in the browser using Playwright MCP:
|
||||
a. Navigate to `http://localhost:5173` using `mcp__playwright__browser_navigate`.
|
||||
b. **First load only**: The app plays a boot→ECG→login→PMR sequence (~15s). Use `mcp__playwright__browser_wait_for` with `time: 15` then take a snapshot. On subsequent navigations, the app stays in PMR phase — no waiting needed.
|
||||
c. Navigate to the hash route for your task's view:
|
||||
- Task 1b (Boot/ECG): Refresh page, screenshot during boot sequence, then again during ECG animation
|
||||
- Task 2 (Login): Refresh page, wait ~8s (after boot+ECG), screenshot the login screen
|
||||
- Task 3 (Banner): Any PMR view — review the patient banner at top
|
||||
- Task 4 (Sidebar): Any PMR view — review left sidebar
|
||||
- Task 5 (Layout/Breadcrumb): Any PMR view — review overall composition
|
||||
- Task 6: `#summary` | Task 7: `#experience` | Task 8: `#skills`
|
||||
- Task 9: `#achievements` | Task 10: `#projects` then `#education` | Task 11: `#contact`
|
||||
d. Use `mcp__playwright__browser_snapshot` (accessibility tree) or `mcp__playwright__browser_take_screenshot` (visual) to capture the page, and compare against your reference file.
|
||||
e. Check specifically: colors match spec, correct font (Inter vs Geist Mono), proper spacing, `1px solid #E5E7EB` borders, 4px border-radius, layout alignment, NHS blue `#005EB8`.
|
||||
f. If discrepancies are found: fix them, re-run quality checks, take another screenshot to confirm.
|
||||
g. Note the visual review outcome in your progress.txt entry (step 10).
|
||||
b. **First load only**: The app plays boot→ECG→login (~15s). Use `mcp__playwright__browser_wait_for` with `time: 15`, then click the Log In button to reach the dashboard. On subsequent navigations, the app stays in dashboard phase.
|
||||
c. Take a screenshot and compare against `References/GPSystemconcept.html` (open it in a separate tab if needed).
|
||||
d. Check: colors match spec, correct font, proper spacing, borders, shadows, layout alignment, teal accent.
|
||||
e. Fix discrepancies, re-run quality checks, re-screenshot.
|
||||
f. Note the visual review outcome in progress.txt.
|
||||
|
||||
8. **Commit your changes**: Stage and commit all changes with a descriptive message referencing the task you completed.
|
||||
8. **Commit your changes**: Stage and commit all changes with a descriptive message referencing the task.
|
||||
|
||||
9. **Mark the item complete**: In `IMPLEMENTATION_PLAN.md`, change the item from `- [ ]` to `- [x]`.
|
||||
|
||||
10. **Update progress.txt**: Append to the "Iteration Log" section with:
|
||||
- Which task you completed
|
||||
- Any learnings or codebase patterns discovered (add to "Codebase Patterns" section)
|
||||
- Any issues encountered
|
||||
- Design decisions made (if visual component)
|
||||
- Visual review outcome (what was checked, any fixes made)
|
||||
- Which task you completed
|
||||
- Any learnings or codebase patterns discovered (add to "Codebase Patterns" section too)
|
||||
- Any issues encountered
|
||||
- Design decisions made
|
||||
- Visual review outcome
|
||||
|
||||
11. **Commit the progress update**: Stage and commit the updated `IMPLEMENTATION_PLAN.md` and `progress.txt`.
|
||||
|
||||
12. **Recommend model for next iteration**: Look at the NEXT unchecked task in `IMPLEMENTATION_PLAN.md` (the one after the task you just completed). Assess its complexity and output a model recommendation on its own line:
|
||||
12. **Recommend model for next iteration**: Look at the NEXT unchecked task. Output a model recommendation:
|
||||
|
||||
```
|
||||
<next-model>sonnet</next-model>
|
||||
@@ -84,86 +65,81 @@ The sidebar uses CV-intuitive labels, NOT clinical jargon. But each view's conte
|
||||
<next-model>opus</next-model>
|
||||
```
|
||||
|
||||
**Use this decision framework:**
|
||||
- **Use `sonnet`** for: configuration tasks, search/utility implementation, responsive fixes, accessibility audits, tasks with very prescriptive specs, tasks that are mostly wiring/plumbing
|
||||
- **Use `opus`** for: visual component rebuilds that invoke /frontend-design (design quality matters), complex animation work, tasks requiring strong aesthetic judgment, tasks where the previous iteration left issues that need creative problem-solving
|
||||
- **Default to `sonnet`** if unsure — it's cheaper and handles well-specified tasks fine
|
||||
- If there IS no next task (you just completed the last one), skip this step
|
||||
**Decision framework:**
|
||||
- **Use `sonnet`** for: configuration tasks, data files, simple wiring, accessibility audits, tasks with very prescriptive specs
|
||||
- **Use `opus`** for: visual component builds, complex animation work, tasks requiring aesthetic judgment, command palette, interaction design
|
||||
- **Default to `sonnet`** if unsure
|
||||
|
||||
13. **Determine if another iteration is needed**: Review your work and the codebase. The project needs another iteration if ANY of these are true:
|
||||
- Any task in the checklist is unchecked (`- [ ]`) or blocked (`- [B]`)
|
||||
- Quality checks would fail (run them to verify)
|
||||
- There are uncommitted changes
|
||||
- progress.txt has open questions or guidance for "next iteration"
|
||||
- The implementation doesn't fully satisfy the plan requirements
|
||||
- You have lingering doubts about correctness or completeness
|
||||
13. **Determine if another iteration is needed**: The project needs another iteration if ANY task is unchecked, quality checks fail, or there are uncommitted changes.
|
||||
|
||||
14. **Send completion signal ONLY if truly complete**: If and ONLY if the project definitely does NOT need another iteration — all tasks verified done, quality checks pass, no guidance for next iteration — output this exact signal on its own line:
|
||||
14. **Send completion signal ONLY if truly complete**: If ALL tasks are verified done, quality checks pass, and no further work is needed:
|
||||
|
||||
```
|
||||
<promise>COMPLETE</promise>
|
||||
```
|
||||
|
||||
DO NOT output this string if there's any chance another iteration is needed. When in doubt, do NOT send the promise — leave it for the next iteration to determine.
|
||||
DO NOT output this string if there's any chance another iteration is needed.
|
||||
|
||||
## Critical Rules
|
||||
|
||||
- **ALWAYS read the "Design Guidance" section in the ref file before writing visual component code** — do NOT invoke /frontend-design at runtime (it's pre-baked into the ref files)
|
||||
- **Do NOT invoke the /frontend-design skill** — the design guidance is already embedded in each ref file. Invoking it at runtime will consume your context and stall the iteration.
|
||||
- **ALWAYS visually review visual components (Tasks 1b-11) in the browser** — use Playwright MCP tools to screenshot and verify against the spec before committing
|
||||
- **ALWAYS read the ref file for your task before writing code**
|
||||
- **Only work on ONE task per iteration**
|
||||
- **Always read progress.txt AND guardrails.md before starting** — previous iterations may have left important context
|
||||
- **If a task is blocked or unclear**, document why in progress.txt and move to the next unchecked item
|
||||
- **Always read progress.txt AND guardrails.md before starting**
|
||||
- **Ref files are the spec — existing code is not**
|
||||
- **The plan file is for tracking only** — do not add detail to it
|
||||
- **Use TypeScript strictly** — no `any` types, proper interfaces
|
||||
- **Follow project structure** — components in `src/components/`, tiles in `src/components/tiles/`, data in `src/data/`
|
||||
- **Respect prefers-reduced-motion** — all animations must have instant fallbacks
|
||||
- **Keep commits atomic and well-described**
|
||||
- **If quality checks fail, fix the issues before committing**
|
||||
- **Ref files are the spec — existing code is not.** If the current implementation contradicts the ref file, rebuild from the ref spec. Do not preserve broken patterns just because they exist in the codebase.
|
||||
- **The visual quality bar is HIGH** — this must look like real clinical software
|
||||
- **Preserve clinical system authenticity** — instant navigation, proper tables, NHS blue, coded entries, traffic lights
|
||||
- **Sidebar labels are CV-friendly** — Experience (not Consultations), Skills (not Medications), etc.
|
||||
- **Use TypeScript strictly** — no `any` types, proper interfaces for all PMR data structures
|
||||
- **Follow the established project structure** — components in `src/components/`, data in `src/data/`, types in `src/types/`
|
||||
- **Respect prefers-reduced-motion** — animations must have instant fallbacks
|
||||
- **If quality checks fail, fix before committing**
|
||||
- **If a task is blocked**, document why in progress.txt and move to next
|
||||
|
||||
## Reference Files
|
||||
|
||||
Each task in the implementation plan references specific files in `Ralph/refs/`:
|
||||
- `Ralph/refs/ref-boot-ecg.md` — Boot sequence + ECG animation improvements
|
||||
- `Ralph/refs/ref-design-system.md` — Colors, typography, spacing, borders, motion
|
||||
- `Ralph/refs/ref-transition-login.md` — ECG flatline + login sequence
|
||||
- `Ralph/refs/ref-banner-sidebar.md` — Patient banner + sidebar + navigation
|
||||
- `Ralph/refs/ref-summary-alert.md` — Summary view + clinical alert
|
||||
- `Ralph/refs/ref-consultations.md` — Experience view (consultation journal layout)
|
||||
- `Ralph/refs/ref-medications.md` — Skills view (medications table layout)
|
||||
- `Ralph/refs/ref-problems.md` — Achievements view (problems list layout)
|
||||
- `Ralph/refs/ref-investigations-documents.md` — Projects + Education views
|
||||
- `Ralph/refs/ref-referrals.md` — Contact view (referral form layout)
|
||||
- `Ralph/refs/ref-interactions.md` — Interactions, responsive, accessibility
|
||||
Each task references a specific detail file in `Ralph/refs/`:
|
||||
|
||||
| Tasks | Reference File |
|
||||
|-------|---------------|
|
||||
| Task 1 | `Ralph/refs/ref-01-design-tokens.md` |
|
||||
| Task 2 | `Ralph/refs/ref-02-data-types.md` |
|
||||
| Tasks 4-6 | `Ralph/refs/ref-03-topbar-sidebar.md` |
|
||||
| Task 7 | `Ralph/refs/ref-04-dashboard-layout.md` |
|
||||
| Tasks 8-11 | `Ralph/refs/ref-05-card-and-top-tiles.md` |
|
||||
| Tasks 12-15 | `Ralph/refs/ref-06-bottom-tiles.md` |
|
||||
| Tasks 16-18 | `Ralph/refs/ref-07-interactions.md` |
|
||||
| Tasks 19-21 | `Ralph/refs/ref-08-polish.md` |
|
||||
|
||||
Also reference:
|
||||
- `References/GPSystemconcept.html` — Visual/structural target for the dashboard
|
||||
- `References/CV_v4.md` — Source CV content (roles, achievements, numbers, dates)
|
||||
- `CLAUDE.md` — Project architecture, design direction, styling conventions
|
||||
|
||||
Read ONLY the referenced file(s) for each task. Do NOT read goal.md directly.
|
||||
Read ONLY the referenced file(s) for your current task. Do not read all ref files at once.
|
||||
|
||||
## Design Document Highlights
|
||||
## Design Highlights
|
||||
|
||||
**Color Palette (Light-mode only):**
|
||||
- Main content: `#F5F7FA`
|
||||
- Cards: `#FFFFFF`
|
||||
- Sidebar: `#1E293B`
|
||||
- NHS blue: `#005EB8`
|
||||
- Green (active): `#22C55E`
|
||||
- Amber (alerts): `#F59E0B`
|
||||
- Background: `#F0F5F4` (warm sage)
|
||||
- Surface/cards: `#FFFFFF`
|
||||
- Sidebar: `#F7FAFA` (very light)
|
||||
- Accent: `#0D6E6E` (teal)
|
||||
- Borders: `#D4E0DE` (structural), `#E4EDEB` (cards)
|
||||
- Text: `#1A2B2A` (primary), `#5B7A78` (secondary), `#8DA8A5` (tertiary)
|
||||
- Status: `#059669` (success), `#D97706` (amber), `#DC2626` (alert)
|
||||
|
||||
**Typography:**
|
||||
- Inter for general text
|
||||
- Geist Mono for coded entries and data values
|
||||
- Elvaro Grotesque (`font-ui`) for UI text
|
||||
- Geist Mono (`font-geist`) for data, timestamps, coded entries
|
||||
- Fira Code for boot/ECG terminal only
|
||||
|
||||
**Layout:**
|
||||
- TopBar: fixed, 48px, white, bottom border
|
||||
- Sidebar: 272px, light, person header + tags + alerts
|
||||
- Main: scrollable card grid, 2 columns desktop, 1 column mobile
|
||||
- Cards: 8px radius, shadow-sm, border-light
|
||||
|
||||
**Key Interactions:**
|
||||
- Login sequence: typing username/password character-by-character
|
||||
- Clinical alert: slides down, acknowledges with checkmark → collapse
|
||||
- Consultation entries: expand/collapse with History/Examination/Plan
|
||||
- Medications table: sortable columns, expandable prescribing history
|
||||
- Sidebar: instant view switching, no animations
|
||||
|
||||
**Responsive Strategy:**
|
||||
- Desktop (>1024px): 220px sidebar with labels
|
||||
- Tablet (768-1024px): 56px icon-only sidebar
|
||||
- Mobile (<768px): Bottom navigation bar
|
||||
- Tile expansion: height-only animation, 200ms, accordion
|
||||
- KPI flip: CSS perspective 400ms, click to flip/unflip
|
||||
- Command palette: Ctrl+K, fuzzy search, keyboard navigation
|
||||
- Entrance: staggered topbar → sidebar → content
|
||||
|
||||
@@ -1,359 +0,0 @@
|
||||
# Reference: Boot Sequence + ECG Animation
|
||||
|
||||
> Covers the full pre-login flow: terminal boot → cursor transition → ECG heartbeat → name reveal → flatline. The flatline→login transition is covered in `ref-transition-login.md`.
|
||||
|
||||
---
|
||||
|
||||
## Current Architecture
|
||||
|
||||
Two components manage the pre-login flow:
|
||||
- `src/components/BootSequence.tsx` → terminal text animation, ends with blinking cursor
|
||||
- `src/components/ECGAnimation.tsx` → canvas-based heartbeat + name tracing + flatline + bg transition
|
||||
- `App.tsx` phases: `boot → ecg → login → pmr`
|
||||
|
||||
## What Needs to Change
|
||||
|
||||
### 1. Boot Sequence — Clean Up for Easy Config
|
||||
|
||||
**Problem:** Boot text lines are hardcoded as HTML strings with inline Tailwind classes. Adding/removing/reordering lines requires editing raw HTML. The `dangerouslySetInnerHTML` approach is fragile.
|
||||
|
||||
**Fix:** Refactor to a clean config-driven structure:
|
||||
```typescript
|
||||
// Example config structure — easy to customize
|
||||
const BOOT_CONFIG = {
|
||||
header: { text: 'CLINICAL TERMINAL v3.2.1', style: 'bright' },
|
||||
lines: [
|
||||
{ type: 'status', text: 'Initialising pharmacist profile...' },
|
||||
{ type: 'separator' },
|
||||
{ type: 'field', label: 'SYSTEM', value: 'NHS Norfolk & Waveney ICB' },
|
||||
{ type: 'field', label: 'USER', value: 'Andy Charlwood' },
|
||||
{ type: 'field', label: 'ROLE', value: 'Deputy Head of Population Health & Data Analysis' },
|
||||
{ type: 'field', label: 'LOCATION', value: 'Norwich, UK' },
|
||||
{ type: 'separator' },
|
||||
{ type: 'status', text: 'Loading modules...' },
|
||||
{ type: 'module', name: 'pharmacist_core.sys' },
|
||||
{ type: 'module', name: 'population_health.mod' },
|
||||
{ type: 'module', name: 'data_analytics.eng' },
|
||||
{ type: 'separator' },
|
||||
{ type: 'ready', text: 'READY — Rendering CV..' },
|
||||
],
|
||||
timing: { lineDelay: 220, holdAfterComplete: 400, fadeOutDuration: 800 },
|
||||
}
|
||||
```
|
||||
- Each line type maps to a React component (not raw HTML)
|
||||
- Colors remain: bright green `#00ff41`, dim green `#3a6b45`, cyan labels `#00e5ff`
|
||||
- Staggered reveal timing stays the same (220ms per line)
|
||||
- Font: Fira Code (this is the terminal phase, NOT the PMR — Fira Code is correct here)
|
||||
|
||||
### 2. Cursor → Dot Transition
|
||||
|
||||
**Problem:** The boot sequence ends with a blinking green block cursor (`.animate-blink`). The ECG animation starts with a glowing dot that appears at the far left of the screen. There's a visual disconnect — the cursor and dot don't connect.
|
||||
|
||||
**Fix:** The blinking cursor at the end of boot should smoothly transition INTO the ECG's glowing trace dot:
|
||||
- At end of boot, capture the cursor's screen position (x, y)
|
||||
- Pass this position to ECGAnimation via props
|
||||
- ECGAnimation starts with its glowing dot AT the cursor position
|
||||
- The cursor stops blinking and morphs: block cursor → circular glow (scale down width, increase glow, ~300ms)
|
||||
- The dot then begins moving rightward, drawing the flatline/heartbeat trace behind it
|
||||
- This means the ECG trace starts at the cursor position, NOT the far left edge
|
||||
|
||||
### 3. ECG Start Position
|
||||
|
||||
**Problem:** Currently the ECG trace starts at x=0 (far left of viewport). The cursor ends somewhere in the middle-left of the screen. This means the dot "teleports" from cursor position to the left edge.
|
||||
|
||||
**Fix:** The ECG animation should:
|
||||
- Accept a `startPosition: { x: number, y: number }` prop from the boot sequence
|
||||
- Begin the trace from that position
|
||||
- The first section of trace is a flat line from the cursor position rightward
|
||||
- Heartbeats begin after the first flat gap (same timing as now, just offset)
|
||||
- The viewport scroll logic already handles the "head" position — just shift the world-space origin
|
||||
|
||||
### 4. Text Reveal — Mask Technique
|
||||
|
||||
**Problem:** The current ECGAnimation.tsx reveals letters by stroking them with progressive alpha (`letterProgress > 0.3` → fade in). This looks like letters fading in, not like the ECG line IS the letter shape.
|
||||
|
||||
**Reference:** `ECGCombined.tsx` (Remotion version at project root) uses a superior technique:
|
||||
- The actual text characters are pre-rendered on the canvas (stroke-only, no fill)
|
||||
- A wipe mask follows the ECG trace head, revealing the text underneath
|
||||
- The ECG trace line IS the path that forms each letter shape (via the `getYAtX` function which returns letter Y values when in text region)
|
||||
- Connector lines between letters sit at the baseline
|
||||
- The neon glow filter applies to both the trace and revealed text
|
||||
|
||||
**What to adopt from ECGCombined.tsx:**
|
||||
- The mask-based text reveal approach (the trace unveils pre-rendered text)
|
||||
- The connector lines between letters at baseline
|
||||
- The neon glow SVG filters (or canvas equivalents)
|
||||
- The text mask brush that follows the trace head
|
||||
|
||||
**What to KEEP from current ECGAnimation.tsx:**
|
||||
- The character spacing (current `LETTER_W`, `LETTER_G`, `SPACE_W` values — preferred over ECGCombined.tsx spacing)
|
||||
- The heartbeat waveform shape (`generateHeartbeatPoints`)
|
||||
- The beat timing and amplitude escalation (0.3 → 0.55 → 0.85 → 1.0)
|
||||
- The canvas rendering approach (not SVG — canvas is correct for this performance-sensitive animation)
|
||||
- The viewport scrolling/camera logic
|
||||
- The flatline draw phase
|
||||
- The scanline and vignette effects
|
||||
- The background color transition to `#1E293B`
|
||||
|
||||
### 5. Text Rendering
|
||||
|
||||
- The name is still "ANDREW CHARLWOOD"
|
||||
- Letters are stroke-only (no fill) in neon green `#00ff41`
|
||||
- Each letter shape is defined by the `ECG_LETTERS` point arrays (keep these)
|
||||
- The trace line passes through each letter's shape points, making the ECG waveform form recognizable letter shapes
|
||||
- Between letters, the trace returns to baseline with short connector segments
|
||||
- Neon glow effect on both trace and revealed text
|
||||
|
||||
## Transition to Login
|
||||
|
||||
After the text is fully revealed and the flatline extends to the right edge, the flow continues as described in `ref-transition-login.md`:
|
||||
1. Name holds with glow (300ms)
|
||||
2. Glow fades, flatline extends right
|
||||
3. Canvas fades to black (200ms)
|
||||
4. Background transitions to dark blue-gray `#1E293B` (200ms)
|
||||
5. Login card materializes
|
||||
|
||||
## prefers-reduced-motion
|
||||
|
||||
With reduced motion enabled:
|
||||
- Boot text appears instantly (no stagger)
|
||||
- Cursor appears immediately
|
||||
- ECG animation is skipped entirely — transition straight from boot to login
|
||||
- Or: show the final frame (name fully revealed, flatline) as a static image for 1 second, then proceed to login
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Boot text renders correctly with all lines
|
||||
- [ ] Blinking cursor visible at end of boot
|
||||
- [ ] Cursor smoothly transitions to ECG dot (no teleport)
|
||||
- [ ] ECG trace starts from cursor position
|
||||
- [ ] Heartbeats render with increasing amplitude
|
||||
- [ ] Name letters revealed via mask technique (not alpha fade)
|
||||
- [ ] Connector lines between letters
|
||||
- [ ] Neon glow on trace and text
|
||||
- [ ] Flatline extends to right edge after name
|
||||
- [ ] Background transitions to `#1E293B`
|
||||
- [ ] Scanlines and vignette present
|
||||
- [ ] Reduced motion: instant/static
|
||||
- [ ] Mobile: scales correctly
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance (from /frontend-design)
|
||||
|
||||
> Pre-baked design direction. Do NOT invoke `/frontend-design` at runtime — this section contains the output.
|
||||
|
||||
### Aesthetic Direction: Authentic Clinical Terminal → Medical Monitor Realism
|
||||
|
||||
This isn't "medical-themed" design — this IS medical equipment interfaces. Two distinct phases:
|
||||
1. **Boot Terminal**: Authentic 1990s clinical system boot sequence (think legacy pharmacy dispensing systems, hospital terminal logins). CRT monitor aesthetic with phosphor green, scanlines, slight text glow.
|
||||
2. **ECG Monitor**: Hospital bedside cardiac monitor realism. The heartbeat isn't decorative — it's a functioning waveform that becomes letterforms through technical precision.
|
||||
|
||||
**Visual Signature**: The cursor-to-dot morphing transition. Most animations have discrete phases; this creates continuous material transformation — the blinking terminal cursor literally becomes the ECG trace point. It's the moment where "loading system" becomes "reading vital signs."
|
||||
|
||||
### Boot Sequence — Type-Safe Config
|
||||
|
||||
```typescript
|
||||
type BootLineType = 'header' | 'status' | 'separator' | 'field' | 'module' | 'ready'
|
||||
|
||||
interface BootLine {
|
||||
type: BootLineType
|
||||
text: string
|
||||
label?: string
|
||||
value?: string
|
||||
style?: 'bright' | 'dim' | 'cyan'
|
||||
}
|
||||
|
||||
interface BootConfig {
|
||||
header: string
|
||||
lines: BootLine[]
|
||||
timing: {
|
||||
lineDelay: number
|
||||
cursorBlinkInterval: number
|
||||
holdAfterComplete: number
|
||||
fadeOutDuration: number
|
||||
}
|
||||
colors: {
|
||||
bright: string
|
||||
dim: string
|
||||
cyan: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Component architecture:
|
||||
- `<BootLine>` — renders individual line types with appropriate styling
|
||||
- `<BootCursor>` — separate component for cursor with ref exposure for position capture
|
||||
- Config-driven rendering replaces hardcoded HTML
|
||||
|
||||
Example config:
|
||||
```typescript
|
||||
const BOOT_CONFIG: BootConfig = {
|
||||
header: 'CLINICAL TERMINAL v3.2.1',
|
||||
lines: [
|
||||
{ type: 'status', text: 'Initialising pharmacist profile...', style: 'dim' },
|
||||
{ type: 'separator', text: '---', style: 'dim' },
|
||||
{ type: 'field', label: 'SYSTEM', value: 'NHS Norfolk & Waveney ICB', style: 'cyan' },
|
||||
{ type: 'field', label: 'USER', value: 'Andy Charlwood', style: 'bright' },
|
||||
// ... etc
|
||||
],
|
||||
timing: { lineDelay: 220, cursorBlinkInterval: 530, holdAfterComplete: 400, fadeOutDuration: 800 },
|
||||
colors: { bright: '#00ff41', dim: '#3a6b45', cyan: '#00e5ff' }
|
||||
}
|
||||
```
|
||||
|
||||
Example BootLine component:
|
||||
```typescript
|
||||
function BootLine({ line }: { line: BootLine }) {
|
||||
const colors = BOOT_CONFIG.colors
|
||||
const color = line.style ? colors[line.style] : colors.dim
|
||||
|
||||
if (line.type === 'field') {
|
||||
return (
|
||||
<div className="font-mono text-sm leading-relaxed">
|
||||
<span style={{ color: colors.cyan }}>{line.label?.padEnd(9)}</span>
|
||||
<span style={{ color }}>{line.value}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (line.type === 'module') {
|
||||
return (
|
||||
<div className="font-mono text-sm leading-relaxed">
|
||||
<span className="font-bold" style={{ color: colors.bright }}>[OK]</span>
|
||||
{' '}
|
||||
<span style={{ color: colors.dim }}>{line.text}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ... other types
|
||||
}
|
||||
```
|
||||
|
||||
### Cursor → Dot Transition — Implementation
|
||||
|
||||
```typescript
|
||||
const [cursorPosition, setCursorPosition] = useState<{x: number, y: number} | null>(null)
|
||||
const cursorRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (cursorRef.current) {
|
||||
const rect = cursorRef.current.getBoundingClientRect()
|
||||
setCursorPosition({
|
||||
x: rect.left + rect.width / 2,
|
||||
y: rect.top + rect.height / 2
|
||||
})
|
||||
}
|
||||
}, [/* trigger when boot completes */])
|
||||
|
||||
// Pass to ECG:
|
||||
<ECGAnimation startPosition={cursorPosition} onComplete={...} />
|
||||
```
|
||||
|
||||
Morph animation: width 8px→0px, height 16px→6px, border-radius 0→50% (300ms ease-out). Simultaneously fade in ECG dot at same position. After morph complete, begin trace movement.
|
||||
|
||||
### Canvas Mask Reveal — Implementation
|
||||
|
||||
```javascript
|
||||
// Pre-render text to offscreen canvas
|
||||
const textCanvas = document.createElement('canvas')
|
||||
const textCtx = textCanvas.getContext('2d')
|
||||
textCtx.strokeStyle = lineColor
|
||||
textCtx.lineWidth = 1.5
|
||||
textCtx.font = `bold ${fontSize}px Arial`
|
||||
textLayout.forEach(item => {
|
||||
textCtx.strokeText(item.char, item.centerX, baselineY)
|
||||
})
|
||||
|
||||
// During animation loop:
|
||||
ctx.save()
|
||||
|
||||
// Create clipping path following trace head
|
||||
ctx.beginPath()
|
||||
ctx.rect(0, 0, headSX + 20, vh) // reveal up to head position + lead
|
||||
ctx.clip()
|
||||
|
||||
// Draw pre-rendered text through clip
|
||||
ctx.drawImage(textCanvas, -viewOff, 0)
|
||||
|
||||
ctx.restore()
|
||||
|
||||
// Feathered leading edge:
|
||||
const gradient = ctx.createLinearGradient(headSX - 30, 0, headSX, 0)
|
||||
gradient.addColorStop(0, 'rgba(0,255,65,0)')
|
||||
gradient.addColorStop(1, 'rgba(0,255,65,1)')
|
||||
```
|
||||
|
||||
### Connector Lines Between Letters
|
||||
|
||||
```javascript
|
||||
const connectors: {startX: number, endX: number}[] = []
|
||||
for (let i = 0; i < textLayout.length - 1; i++) {
|
||||
const curr = textLayout[i]
|
||||
const next = textLayout[i + 1]
|
||||
const endInset = CONNECTOR_INSETS[curr.char] || 0
|
||||
const startInset = CONNECTOR_INSETS[next.char] || 0
|
||||
connectors.push({
|
||||
startX: curr.endX - endInset,
|
||||
endX: next.startX + startInset
|
||||
})
|
||||
}
|
||||
|
||||
// During draw:
|
||||
connectors.forEach(conn => {
|
||||
if (headWX > conn.startX) {
|
||||
const connectorEndX = Math.min(conn.endX, headWX)
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(conn.startX - viewOff, baselineY)
|
||||
ctx.lineTo(connectorEndX - viewOff, baselineY)
|
||||
ctx.stroke()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Visual Enhancement Details
|
||||
|
||||
**CRT Scanlines** (boot phase):
|
||||
```css
|
||||
.boot-scanlines {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: repeating-linear-gradient(
|
||||
0deg,
|
||||
rgba(0, 0, 0, 0.15) 0px,
|
||||
transparent 1px,
|
||||
transparent 2px,
|
||||
rgba(0, 0, 0, 0.15) 3px
|
||||
);
|
||||
pointer-events: none;
|
||||
animation: scanline-drift 8s linear infinite;
|
||||
}
|
||||
```
|
||||
|
||||
**Phosphor Glow** (terminal text):
|
||||
```css
|
||||
.terminal-text {
|
||||
text-shadow:
|
||||
0 0 4px currentColor,
|
||||
0 0 8px currentColor,
|
||||
0 0 12px rgba(0, 255, 65, 0.3);
|
||||
}
|
||||
```
|
||||
|
||||
**ECG Neon Glow** (canvas — multi-layer):
|
||||
- Primary trace: 2px solid line
|
||||
- Glow layer 1: 6px @ 25% opacity + shadowBlur 14px
|
||||
- Glow layer 2: Inner 3px core for sharpness
|
||||
- Text: Same multi-layer glow approach
|
||||
|
||||
**Background Transition** (smooth RGB interpolation):
|
||||
```javascript
|
||||
const bgProgress = (elapsed - flatlineStartTime) / BG_TRANSITION
|
||||
ctx.fillStyle = `rgb(
|
||||
${Math.round(0 + (30 * bgProgress))},
|
||||
${Math.round(0 + (41 * bgProgress))},
|
||||
${Math.round(0 + (59 * bgProgress))}
|
||||
)` // black → #1E293B
|
||||
ctx.fillRect(0, 0, vw, vh)
|
||||
```
|
||||
+67
-42
@@ -5,8 +5,8 @@ Hard rules that MUST be followed in every iteration. Violating these will produc
|
||||
## Design Direction
|
||||
|
||||
### When: Making ANY aesthetic decision
|
||||
**Rule:** The direction is **Clinical Luxury** — the *structure* of clinical software (tables, status dots, coded entries, patient banner, sidebar) with *premium execution* (refined shadows, generous spacing, premium typography, atmospheric depth). This is NOT a faithful NHS clone.
|
||||
**Why:** The previous "clinical utilitarian" direction produced generic, flat output. The new direction keeps the clinical metaphor but makes it beautiful.
|
||||
**Rule:** The direction is **GP System Dashboard** — a tile-based clinical record system with a light, modern aesthetic. Teal accent (#0D6E6E), light sidebar (#F7FAFA), warm sage background (#F0F5F4), white card surfaces. The clinical metaphor lives in the STRUCTURE (tiles as "record sections", status indicators, medication-style skill entries, coded entries) — not in dark chrome or heavy clinical styling.
|
||||
**Why:** The previous dark-sidebar PMR interface is being replaced with the lighter, tile-based GP System concept (`References/GPSystemconcept.html`).
|
||||
|
||||
## Design System Guardrails
|
||||
|
||||
@@ -14,41 +14,66 @@ Hard rules that MUST be followed in every iteration. Violating these will produc
|
||||
**Rule:** Light-mode only. Do NOT add dark mode classes, `dark:` prefixes, or theme toggles.
|
||||
**Why:** The design direction is light-mode only.
|
||||
|
||||
### When: Setting border-radius on cards, inputs, or table elements
|
||||
**Rule:** Use 4px border-radius (`rounded` in Tailwind). The only exception is the LoginScreen card which uses 12px.
|
||||
**Why:** Clinical systems use minimal rounding. This precision is part of the Clinical Luxury feel.
|
||||
### When: Setting border-radius on cards and tiles
|
||||
**Rule:** Use 8px border-radius (var(--radius)) for cards and tiles. Use 6px (var(--radius-sm)) for inner elements (metric cards, activity items, tags). The only exception is the LoginScreen card which uses 12px, and the command palette which uses 12px.
|
||||
**Why:** The GP System concept uses 8px radius, slightly more rounded than the old 4px clinical style, reflecting the lighter aesthetic.
|
||||
|
||||
### When: Using monospace/code font
|
||||
**Rule:** Use Geist Mono (`font-family: 'Geist Mono', monospace`), NOT Fira Code, for coded entries, timestamps, clinical codes, and data values in the PMR interface. Fira Code is used in boot/ECG phases only.
|
||||
**Why:** Geist Mono is the specified monospace font for the PMR interface.
|
||||
**Rule:** Use Geist Mono (`font-family: 'Geist Mono', monospace`) for timestamps, session info, dates, GPhC number, and coded data values. Fira Code is used in boot/ECG phases only.
|
||||
**Why:** Geist Mono is the specified monospace font for the dashboard interface.
|
||||
|
||||
### When: Choosing the UI text font
|
||||
**Rule:** Use [UI font] — either Elvaro Grotesque or Blumir (see CLAUDE.md for setup). Do NOT use Inter, Roboto, or system defaults for the PMR interface.
|
||||
**Why:** Premium typography is the primary vehicle for the luxury feel. Generic fonts undermine the entire direction.
|
||||
**Rule:** Use Elvaro Grotesque (font-ui) as primary, Blumir (font-ui-alt) as alternative. Do NOT use Inter, Roboto, or system defaults. DM Sans appears in the concept HTML but is NOT the production font — use Elvaro Grotesque.
|
||||
**Why:** Premium typography is the primary vehicle for the luxury feel. The concept HTML uses DM Sans as a placeholder; the production build uses the licensed premium fonts.
|
||||
|
||||
### When: Adding shadows to cards or panels
|
||||
**Rule:** Use multi-layered shadows per the design system: `0 1px 2px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.03)`. Cards should feel like they float slightly above the content surface. Do NOT use flat, borderless cards or overly dramatic Material Design shadows.
|
||||
**Why:** Layered shadows create the subtle depth that distinguishes Clinical Luxury from both flat clinical software and generic SaaS.
|
||||
### When: Adding shadows to cards or tiles
|
||||
**Rule:** Use the three-tier shadow system:
|
||||
- `--shadow-sm`: `0 1px 2px rgba(26,43,42,0.05)` (default card state)
|
||||
- `--shadow-md`: `0 2px 8px rgba(26,43,42,0.08)` (hover / interactive)
|
||||
- `--shadow-lg`: `0 8px 32px rgba(26,43,42,0.12)` (command palette, overlays)
|
||||
**Why:** Shadows create depth hierarchy. sm=resting, md=interactive, lg=overlay.
|
||||
|
||||
### When: Styling borders
|
||||
**Rule:** All card and table borders must be `1px solid #E5E7EB` (gray-200). Keep borders on tables — they're authentically clinical.
|
||||
**Why:** Borders provide clinical texture. Combined with shadows, they create the luxury-clinical contrast.
|
||||
**Rule:** Use `1px solid var(--border-light)` (#E4EDEB) for card and tile borders. Use `1px solid var(--border)` (#D4E0DE) for structural borders (sidebar right edge, topbar bottom, section dividers).
|
||||
**Why:** Two-tier border system: lighter for cards, slightly stronger for structural elements.
|
||||
|
||||
## Sidebar Label Convention
|
||||
### When: Choosing accent/interactive colors
|
||||
**Rule:** Use teal `#0D6E6E` (var(--accent)) for interactive elements: links, active states, avatar gradient, dots, hover highlights. Hover: `#0A8080`. Accent-light: `rgba(10,128,128,0.08)` for subtle backgrounds.
|
||||
**Why:** Teal is the primary accent in the GP System concept. It replaces NHS Blue as the interactive color.
|
||||
|
||||
### When: Building or modifying sidebar navigation labels
|
||||
**Rule:** Labels MUST be CV-friendly: Summary, Experience, Skills, Achievements, Projects, Education, Contact. Do NOT use clinical jargon (Consultations, Medications, etc.) as labels. The clinical metaphor lives in the LAYOUT of each view, not the labels.
|
||||
**Why:** Non-clinical visitors should immediately understand what each section contains.
|
||||
### When: Using status colors
|
||||
**Rule:** Status colors: success=`#059669`, amber=`#D97706`, alert=`#DC2626`, purple=`#7C3AED` (education). Each with matching light/border variants. Always pair colored indicators with text labels.
|
||||
**Why:** Traffic light convention. Color-only indicators violate WCAG.
|
||||
|
||||
## Navigation Guardrails
|
||||
## Layout Guardrails
|
||||
|
||||
### When: Switching between sidebar views
|
||||
**Rule:** View switching must be INSTANT. No crossfade, no slide animation, no opacity transition.
|
||||
**Why:** Clinical systems use instant tab switching. This preserves the "application" feel.
|
||||
### When: Building the dashboard layout
|
||||
**Rule:** Three-zone layout: TopBar (fixed, 48px) + Sidebar (fixed left, 272px) + Main content (scrollable card grid). Main content has 24px-28px padding and card grid with 16px gap. Grid: 2 columns on desktop, 1 column below 900px.
|
||||
**Why:** Matches the GP System concept layout structure.
|
||||
|
||||
### When: Building navigation
|
||||
**Rule:** URL hash routing is required. Each view updates `window.location.hash`.
|
||||
**Why:** Direct linking to specific views is required.
|
||||
### When: Ordering tiles in the card grid
|
||||
**Rule:** Tile order: Patient Summary (full) → Latest Results (half) + Repeat Medications (half) → Last Consultation (full) → Career Activity (full) → Education (full) → Projects (full). Full-width tiles span both columns.
|
||||
**Why:** This ordering follows the concept layout with the user's addition of Patient Summary at the top.
|
||||
|
||||
## Sidebar Guardrails
|
||||
|
||||
### When: Building the sidebar
|
||||
**Rule:** Sidebar contains ONLY: PersonHeader (avatar, name, title, status, details) → Tags → Alerts/Highlights. Active Projects, Core Skills, and Education are in the MAIN CONTENT as tiles, NOT in the sidebar.
|
||||
**Why:** User explicitly requested moving Projects, Skills, and Education from sidebar to main dashboard tiles.
|
||||
|
||||
## Interaction Guardrails
|
||||
|
||||
### When: Expanding/collapsing tile content
|
||||
**Rule:** Height animation ONLY (200ms, ease-out). Do NOT fade opacity on content. Single-expand accordion — only one item expanded at a time within a tile.
|
||||
**Why:** Consistent expand/collapse behavior. Opacity fade was explicitly prohibited.
|
||||
|
||||
### When: Building the command palette
|
||||
**Rule:** Trigger via Ctrl+K or search bar click. Overlay with backdrop blur. ESC to close. Arrow key navigation. Fuzzy search via fuse.js.
|
||||
**Why:** Matches concept interaction pattern.
|
||||
|
||||
### When: Building KPI flip cards
|
||||
**Rule:** Click to flip metric card (front=value, back=explanation). 400ms CSS perspective flip or instant swap with reduced motion. Only one card flipped at a time.
|
||||
**Why:** User requested interactive KPI exploration with explanation text.
|
||||
|
||||
## Login Screen Guardrails
|
||||
|
||||
@@ -58,32 +83,32 @@ Hard rules that MUST be followed in every iteration. Violating these will produc
|
||||
|
||||
## Component Guardrails
|
||||
|
||||
### When: Expanding/collapsing entries
|
||||
**Rule:** Height animation ONLY (200ms, ease-out). Do NOT fade opacity on content.
|
||||
**Why:** The spec explicitly states content grows/shrinks without opacity change.
|
||||
|
||||
### When: Displaying traffic light status indicators
|
||||
**Rule:** Colored dots must ALWAYS have text labels. Never use color as the sole indicator.
|
||||
**Why:** WCAG — color cannot be the only means of communicating information.
|
||||
|
||||
### When: Rendering the clinical alert
|
||||
**Rule:** Use Framer Motion `type: "spring"` animation for entrance (not ease-out). Amber colors: bg `#FEF3C7`, left border `#F59E0B`, text `#92400E`.
|
||||
**Why:** Spring animation with slight overshoot makes alerts feel alive and demanding.
|
||||
### When: Writing table or list markup inside tiles
|
||||
**Rule:** Use semantic markup. Tables use `<table>`, `<thead>`, `<th scope="col">`, `<tbody>`, `<tr>`, `<td>`. Lists use `<ul>`/`<ol>` with `<li>`. No div-based tables.
|
||||
**Why:** Screen readers require native semantics.
|
||||
|
||||
### When: Writing table markup
|
||||
**Rule:** Use semantic `<table>`, `<thead>`, `<th scope="col">`, `<tbody>`, `<tr>`, `<td>`. No div-based tables.
|
||||
**Why:** Screen readers require native table semantics.
|
||||
### When: Using icons
|
||||
**Rule:** Use `lucide-react` icons only. No unicode symbols, no inline SVG copied from external sources. Exception: the concept's SVG icons should be converted to their lucide-react equivalents (e.g., concept's house icon → `Home` from lucide-react).
|
||||
**Why:** Consistent icon system, tree-shakeable, accessible.
|
||||
|
||||
## Data Guardrails
|
||||
|
||||
### When: Displaying CV content
|
||||
**Rule:** All data must come from `src/data/*.ts` files. Do NOT hardcode content in components or change any numbers/dates.
|
||||
**Why:** Data has been validated against CV_v4.md.
|
||||
**Rule:** All data must come from `src/data/*.ts` files. Do NOT hardcode content in components or change any numbers/dates. New data files (profile.ts, tags.ts, alerts.ts, kpis.ts, skills.ts) must be accurate to CV_v4.md.
|
||||
**Why:** Data has been validated against CV_v4.md. Single source of truth.
|
||||
|
||||
### When: Building the "Repeat Medications" (skills) tile
|
||||
**Rule:** Use the exact frequencies specified by the user: Data Analysis="Twice daily", Power BI="Once weekly", Python="Daily", SQL="Daily", JavaScript/TypeScript="When required". Include "years of experience" like "length of time on medication".
|
||||
**Why:** User explicitly specified these frequency values for the medication metaphor.
|
||||
|
||||
## Visual Review Guardrails
|
||||
|
||||
### When: Completing any visual task
|
||||
**Rule:** After quality checks, open `http://localhost:5173` via Playwright MCP tools (`mcp__playwright__browser_navigate`, `mcp__playwright__browser_take_screenshot`, `mcp__playwright__browser_snapshot`), take a screenshot, and compare against the ref file spec. Fix visual discrepancies. If browser tools are unavailable, note in progress.txt and proceed.
|
||||
**Rule:** After quality checks, open `http://localhost:5173` via Playwright MCP tools, take a screenshot, and compare against `References/GPSystemconcept.html`. Fix visual discrepancies. If browser tools are unavailable, note in progress.txt and proceed.
|
||||
**Why:** Code review alone cannot catch visual issues.
|
||||
|
||||
### When: Browser tools fail
|
||||
@@ -100,10 +125,10 @@ Hard rules that MUST be followed in every iteration. Violating these will produc
|
||||
**Rule:** All animations must respect `prefers-reduced-motion`. With reduced motion: all animations skip to final state instantly.
|
||||
**Why:** Accessibility requirement.
|
||||
|
||||
### When: Building visual components
|
||||
**Rule:** Each ref file in `Ralph/refs/` contains a "Design Guidance" section with design direction and code patterns. Read it BEFORE writing code. Do NOT invoke `/frontend-design` at runtime.
|
||||
**Why:** Design guidance is pre-baked to avoid context overflow.
|
||||
|
||||
### When: Running quality checks
|
||||
**Rule:** Run `npm run typecheck`, `npm run lint`, and `npm run build` after EVERY task. Fix all errors before committing.
|
||||
**Why:** Build failures compound across iterations.
|
||||
|
||||
### When: Referencing the concept design
|
||||
**Rule:** The reference design is `References/GPSystemconcept.html`. Open it in a browser or read the HTML to understand the visual target. The concept is the LAYOUT reference; production fonts and some colors differ (see font and color guardrails).
|
||||
**Why:** The concept HTML is the single source of truth for layout and spatial composition.
|
||||
|
||||
@@ -941,3 +941,81 @@ Do NOT invoke the `/frontend-design` skill at runtime — it was pre-run and the
|
||||
|
||||
**ALL TASKS COMPLETE** — Implementation plan fully checked off (Tasks 1-15)
|
||||
|
||||
## Manual Intervention — 2026-02-13
|
||||
### Reason: Complete redesign — replacing CareerRecord PMR with GP System Dashboard
|
||||
### Changes made:
|
||||
- **IMPLEMENTATION_PLAN.md**: Completely rewritten with 21 new tasks for GP System dashboard overhaul
|
||||
- **guardrails.md**: Completely rewritten for new design direction (teal palette, tile-based layout, 8px radius, new shadow system)
|
||||
- **progress.txt**: This intervention entry added
|
||||
- **CLAUDE.md**: Will be updated by Task 3 in the new plan (architecture, colors, components, styling)
|
||||
|
||||
### Previous plan status: 15/15 tasks completed (all checked off)
|
||||
### New plan: 21 tasks across 4 phases (Foundation → Core Layout → Dashboard Tiles → Interactions → Polish)
|
||||
|
||||
### What's being replaced:
|
||||
- `PatientBanner.tsx` → `TopBar.tsx` (white top bar with search and session info)
|
||||
- `ClinicalSidebar.tsx` → `Sidebar.tsx` (light background #F7FAFA, person header, tags, alerts only)
|
||||
- `PMRInterface.tsx` → `DashboardLayout.tsx` (topbar + sidebar + scrollable card grid)
|
||||
- All 7 `views/*.tsx` files → Dashboard tile components in `src/components/tiles/`
|
||||
- Color palette: dark sidebar (#1E293B) + NHS Blue (#005EB8) → light sidebar (#F7FAFA) + teal (#0D6E6E)
|
||||
- Navigation: sidebar-nav view-switching → single scrollable dashboard with expandable tiles
|
||||
- Patient banner scroll condensation → removed (no banner, just topbar)
|
||||
|
||||
### What's preserved:
|
||||
- Boot sequence (BootSequence.tsx) — LOCKED
|
||||
- ECG animation (ECGAnimation.tsx) — LOCKED
|
||||
- Login screen (LoginScreen.tsx) — unchanged
|
||||
- Font setup: Elvaro Grotesque (primary UI), Blumir (alt), Geist Mono (data), Fira Code (terminal only)
|
||||
- All data files in src/data/ — content unchanged, new data files added
|
||||
- fuse.js dependency — reused for command palette search
|
||||
- App.tsx phase management (boot → ecg → login → pmr) — pmr phase now renders DashboardLayout
|
||||
|
||||
### Tasks in new plan:
|
||||
Phase 0 — Foundation:
|
||||
1. Update design tokens + Tailwind config
|
||||
2. Create new data files + update types
|
||||
3. Update CLAUDE.md for new architecture
|
||||
|
||||
Phase 1 — Core Layout:
|
||||
4. Build TopBar component
|
||||
5. Build Sidebar — PersonHeader
|
||||
6. Build Sidebar — Tags, Alerts
|
||||
7. Build DashboardLayout + wire up App.tsx
|
||||
|
||||
Phase 2 — Dashboard Tiles:
|
||||
8. Build reusable Card component
|
||||
9. Build PatientSummary tile
|
||||
10. Build LatestResults tile
|
||||
11. Build CoreSkills tile ("Repeat Medications")
|
||||
12. Build LastConsultation tile
|
||||
13. Build CareerActivity tile
|
||||
14. Build Education tile
|
||||
15. Build Projects tile
|
||||
|
||||
Phase 3 — Interactions:
|
||||
16. Tile expansion system
|
||||
17. KPI flip card interaction
|
||||
18. Build Command Palette
|
||||
|
||||
Phase 4 — Polish:
|
||||
19. Responsive design
|
||||
20. Accessibility audit
|
||||
21. Clean up + final polish
|
||||
|
||||
### Context for next iteration:
|
||||
- The reference design is `References/GPSystemconcept.html` — READ THIS before starting any visual task
|
||||
- The old PMR components STILL EXIST in the codebase. Don't delete them yet — some expand/collapse patterns and data rendering can be reused inside tile expansion (Task 16). Cleanup happens in Task 21.
|
||||
- Login screen still transitions to `#1E293B` background. The new dashboard has `#F0F5F4` background. The LoginScreen.tsx may need a background color update, or the transition can be handled in DashboardLayout's entrance animation.
|
||||
- The concept HTML uses DM Sans font — this is a PLACEHOLDER. Production uses Elvaro Grotesque (font-ui). Do not switch to DM Sans.
|
||||
- The concept's command palette has a comprehensive data model — use it as reference for building the palette in Task 18.
|
||||
- Tile interactions (expansion, KPI flip) are in Phase 3. Tiles in Phase 2 should be built as static/display-only first, with data attributes or props that Phase 3 can hook into.
|
||||
|
||||
### New guardrails added:
|
||||
- Accent color: teal #0D6E6E (replacing NHS Blue #005EB8 as primary interactive color)
|
||||
- Border radius: 8px for cards (was 4px)
|
||||
- Shadow system: three-tier (sm/md/lg) replacing single pmr shadow
|
||||
- Sidebar: light background, PersonHeader + Tags + Alerts ONLY (projects, skills, education moved to tiles)
|
||||
- Layout: TopBar + Sidebar + Card Grid (replacing PatientBanner + ClinicalSidebar + view switching)
|
||||
- Tile ordering: Patient Summary → Latest Results + Core Skills → Last Consultation → Career Activity → Education → Projects
|
||||
- Skills frequency: user-specified values (Data Analysis=twice daily, etc.)
|
||||
|
||||
|
||||
@@ -1,542 +0,0 @@
|
||||
# Reference: Patient Banner + Sidebar + Navigation
|
||||
|
||||
> Extracted from goal.md — Patient Banner, Left Sidebar, and Navigation sections. These are the persistent UI chrome that defines the clinical system feel.
|
||||
|
||||
---
|
||||
|
||||
## Patient Banner (Persistent Top Chrome)
|
||||
|
||||
The patient banner is the most recognizable element of any PMR system. It spans the full viewport width above the main content area and provides constant demographic context.
|
||||
|
||||
### Full Banner (80px height, visible at top of page)
|
||||
|
||||
```
|
||||
+---------------------------------------------------------------------------+
|
||||
| CHARLWOOD, Andrew (Mr) Active (green dot) Open to opps. |
|
||||
| DOB: 14/02/1993 | NHS No: 2211810 | Norwich, NR1 |
|
||||
| 07795553088 | andy@charlwood.xyz [Download CV] [Email] [LinkedIn] |
|
||||
+---------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
### Content Mapping
|
||||
|
||||
| PMR Field | Actual Content | Notes |
|
||||
|-----------|---------------|-------|
|
||||
| Patient name | CHARLWOOD, Andrew (Mr) | Surname first, comma-separated — exactly as in clinical systems |
|
||||
| DOB | 14/02/1993 | DD/MM/YYYY format (UK clinical standard) |
|
||||
| NHS Number | 221 181 0 | Andy's GPhC registration number formatted like an NHS number (with spaces). Hover tooltip: "GPhC Registration Number" |
|
||||
| GP Practice | Self-Referred | Tongue-in-cheek — Andy referred himself to this record |
|
||||
| Address | Norwich, NR1 | Abbreviated postcode area |
|
||||
| Phone | 07795553088 | Clickable (tel: link) |
|
||||
| Email | andy@charlwood.xyz | Clickable (mailto: link) |
|
||||
| Status | Active (green dot) | Like the "registered" status in a PMR |
|
||||
| Badge | Open to opportunities | Styled as a clinical banner tag (blue background, white text, small pill shape) |
|
||||
|
||||
### Action Buttons (top right of banner)
|
||||
|
||||
| Button | PMR Equivalent | Action |
|
||||
|--------|---------------|--------|
|
||||
| Download CV | Print Summary | Downloads PDF version of CV |
|
||||
| Email | Send Letter | Opens mailto: link |
|
||||
| LinkedIn | External Link | Opens LinkedIn profile in new tab |
|
||||
|
||||
Buttons are styled as small outlined rectangles with NHS blue text and 1px NHS blue border, 4px radius. On hover: filled NHS blue background with white text.
|
||||
|
||||
### Condensed Banner (48px, sticky after scroll)
|
||||
|
||||
When the user scrolls past 100px of content, the banner smoothly condenses to show only the essential information on a single line:
|
||||
|
||||
```
|
||||
CHARLWOOD, Andrew (Mr) | NHS No: 2211810 | Active (green dot) [Download CV] [Email]
|
||||
```
|
||||
|
||||
The condensed banner sticks to the top of the viewport (`position: sticky`) with a `z-index` above the content area but below modals/alerts.
|
||||
|
||||
---
|
||||
|
||||
## Left Sidebar — Clinical Navigation
|
||||
|
||||
The sidebar replicates the dark navigation panel found in EMIS Web and similar clinical systems. It provides category-based access to different "record views."
|
||||
|
||||
**Width:** 220px (desktop), dark blue-gray (`#1E293B`) background.
|
||||
|
||||
### Navigation Items
|
||||
|
||||
**IMPORTANT:** Sidebar labels use CV-friendly terms, NOT clinical jargon. The clinical metaphor lives in the LAYOUT of each view, not the labels.
|
||||
|
||||
| Icon | Label | View Layout Style | Description |
|
||||
|------|-------|-------------------|-------------|
|
||||
| `ClipboardList` | Summary | Patient summary screen | Demographics, active items, current skills, recent role |
|
||||
| `FileText` | Experience | Consultation journal layout | Reverse-chronological journal of roles with H/E/P format |
|
||||
| `Pill` | Skills | Medications table layout | Skills table with proficiency dosages and frequency |
|
||||
| `AlertTriangle` | Achievements | Problems list layout | Challenges resolved and ongoing, with traffic lights |
|
||||
| `FlaskConical` | Projects | Investigation results layout | Project outcomes with status badges |
|
||||
| `FolderOpen` | Education | Attached documents layout | Certificates and qualifications |
|
||||
| `Send` | Contact | Referral form layout | Contact/message form styled as clinical referral |
|
||||
|
||||
### Styling
|
||||
|
||||
- Each item: 44px height, 16px left padding, icon (18px, `lucide-react`) + label in [UI font] 500, 14px
|
||||
- Default state: white text at 70% opacity, transparent background
|
||||
- Hover state: white text at 100% opacity, background `rgba(255,255,255,0.08)`
|
||||
- Active state: white text at 100%, NHS blue left border (3px), background `rgba(255,255,255,0.12)`, label in [UI font] 600
|
||||
- A thin horizontal separator line (`1px solid rgba(255,255,255,0.1)`) appears between "Summary" and "Consultations" (separating the overview from the detail views)
|
||||
|
||||
### Sidebar Footer
|
||||
|
||||
At the bottom of the sidebar, in small text ([UI font] 400, 11px, `#64748B`):
|
||||
```
|
||||
Session: A.CHARLWOOD
|
||||
Logged in: [current time]
|
||||
```
|
||||
This updates with the actual current time on mount, reinforcing the "logged in" metaphor.
|
||||
|
||||
### Sidebar Header
|
||||
|
||||
At the top, above the navigation items, a small logo or system name:
|
||||
```
|
||||
CareerRecord PMR
|
||||
v1.0.0
|
||||
```
|
||||
In [UI font] 500, 13px, white at 50% opacity. Styled like clinical system branding that appears in the top-left of the navigation.
|
||||
|
||||
---
|
||||
|
||||
## Navigation
|
||||
|
||||
### Primary Navigation: Left Sidebar
|
||||
|
||||
The sidebar is always visible on desktop — this is how clinical systems work. There is no floating nav, no hamburger menu on desktop, and no scroll-based navigation. The sidebar provides persistent, direct access to any record section.
|
||||
|
||||
### Keyboard Shortcuts
|
||||
|
||||
| Sidebar Item | View Layout | Shortcut |
|
||||
|-------------|-------------|----------|
|
||||
| Summary | Patient summary | `Alt+1` |
|
||||
| Experience | Consultation journal | `Alt+2` |
|
||||
| Skills | Medications table | `Alt+3` |
|
||||
| Achievements | Problems list | `Alt+4` |
|
||||
| Projects | Investigation results | `Alt+5` |
|
||||
| Education | Attached documents | `Alt+6` |
|
||||
| Contact | Referral form | `Alt+7` |
|
||||
|
||||
### URL Hash Routing
|
||||
|
||||
Each sidebar item updates the URL hash (`#summary`, `#experience`, `#skills`, `#achievements`, `#projects`, `#education`, `#contact`) for direct linking. On page load, the app reads the hash and navigates to the corresponding view.
|
||||
|
||||
### Breadcrumb
|
||||
|
||||
A breadcrumb appears at the top of the main content area:
|
||||
|
||||
```
|
||||
Patient Record > Consultations > Interim Head, Population Health
|
||||
```
|
||||
|
||||
The breadcrumb updates as the user navigates deeper (e.g., expanding a consultation). Clicking "Patient Record" returns to Summary. Clicking "Consultations" collapses any expanded entries and shows the full journal list. The breadcrumb is styled in [UI font] 400, 13px, gray-400, with chevron separators.
|
||||
|
||||
### Secondary Navigation: Within-View Interactions
|
||||
|
||||
- **Summary:** Clicking "View Full List" or "View Full Record" links navigates to the corresponding sidebar section.
|
||||
- **Consultations:** Expand/collapse individual entries. "Linked consultations" in Problems view can deep-link to specific consultation entries.
|
||||
- **Medications:** Category tabs (Active, Clinical, PRN) within the view. Click to expand prescribing history.
|
||||
- **Problems:** Click to expand. "Linked consultations" navigate to Consultations view.
|
||||
- **Investigations:** Click to expand results.
|
||||
- **Documents:** Click to expand preview.
|
||||
- **Referrals:** No sub-navigation.
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance
|
||||
|
||||
### Aesthetic Direction
|
||||
|
||||
**Clinical Luxury** — The patient banner and sidebar draw their *structure* from NHS clinical systems (PAS headers, EMIS Web navigation), but the *execution* is premium — refined typography, layered shadows, considered spacing. The clinical metaphor lives in the layout and conventions (surname-first, pipe separators, status dots); the luxury lives in the finish.
|
||||
|
||||
- **Tone**: Precise, information-dense, and refined. Generous whitespace, layered shadows, and premium typography elevate what would otherwise be institutional UI. The clinical conventions (data density, pipe separators, monospaced identifiers, surname-first, green status dot) provide authentic texture.
|
||||
- **Typography Discipline**:
|
||||
- [UI font] at 600 weight for the patient name — the anchor element
|
||||
- Geist Mono for structured identifiers (NHS Number, DOB) — monospaced data feels like it came from a database
|
||||
- [UI font] at normal weight for demographic text
|
||||
- The pipe character `|` as a data separator is a deliberate clinical convention
|
||||
|
||||
### Design System Tokens
|
||||
|
||||
| Token | Value | Usage |
|
||||
|-------|-------|-------|
|
||||
| NHS Blue | `#005EB8` | Primary accent, buttons, active states, borders |
|
||||
| Banner Background | `#334155` (slate-700) | Patient banner background — exact EMIS Web header shade |
|
||||
| Sidebar Background | `#1E293B` | Dark navigation panel |
|
||||
| Content Background | `#F5F7FA` | Main content area |
|
||||
| Border | `#E5E7EB` | 1px solid borders |
|
||||
| Border Radius | `4px` | All UI elements |
|
||||
| Green Status | `#22C55E` | Active status dot |
|
||||
| Font Text | [UI font] | All text content (Elvaro or Blumir — see CLAUDE.md) |
|
||||
| Font Data | `Geist Mono` | Monospaced identifiers |
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
1. **220px Sidebar Width**: Fixed, always visible on desktop. No hamburger menu. This is how clinical systems work — persistent direct access.
|
||||
|
||||
2. **Alt+1-7 Keyboard Shortcuts**: Each sidebar item has a keyboard shortcut for power users. Arrow key navigation and `/` for search focus.
|
||||
|
||||
3. **CV-Friendly Navigation Labels**: Not clinical jargon. The metaphor lives in the layout, not the labels:
|
||||
- Summary (ClipboardList icon)
|
||||
- Experience (FileText)
|
||||
- Skills (Pill)
|
||||
- Achievements (AlertTriangle)
|
||||
- Projects (FlaskConical)
|
||||
- Education (FolderOpen)
|
||||
- Contact (Send)
|
||||
|
||||
4. **Scroll-Triggered Banner Condensation**:
|
||||
- Full banner: 80px height with three rows (name, demographics, contact/actions)
|
||||
- Condensed: 48px sticky after 100px scroll, single line
|
||||
- 200ms smooth transition
|
||||
- IntersectionObserver for performance
|
||||
|
||||
5. **Navigation Item States**:
|
||||
- Default: white text at 70% opacity, transparent background
|
||||
- Hover: white text at 100%, background `rgba(255,255,255,0.08)`
|
||||
- Active: white text at 100%, 3px NHS blue left border, background `rgba(255,255,255,0.12)`, [UI font] 600 weight
|
||||
|
||||
6. **Interface Materialization Animations** (PMRInterface):
|
||||
- Patient banner slides down (200ms ease-out)
|
||||
- Sidebar slides from left (250ms ease-out, 50ms delay)
|
||||
- Content fades in (300ms, 100ms delay after sidebar)
|
||||
- View switching is INSTANT — no crossfade or slide between views
|
||||
|
||||
7. **Mobile Adaptations**:
|
||||
- Banner collapses to minimal: `CHARLWOOD, A (Mr) | 2211810 | dot`
|
||||
- Overflow menu for actions
|
||||
- Bottom nav bar (56px height with safe area padding)
|
||||
- Sidebar becomes icon-only (56px) with tooltips on tablet
|
||||
|
||||
### Implementation Patterns
|
||||
|
||||
#### PatientBanner Component Structure
|
||||
|
||||
```tsx
|
||||
// Main container with IntersectionObserver sentinel
|
||||
<>
|
||||
<div ref={sentinelRef} className="h-0 w-full absolute top-0" aria-hidden="true" />
|
||||
<header
|
||||
className={`
|
||||
sticky top-0 z-40 w-full
|
||||
bg-pmr-banner border-b border-slate-600
|
||||
transition-all duration-200 ease-out
|
||||
${shouldCondense ? 'h-12' : 'h-20'}
|
||||
`}
|
||||
role="banner"
|
||||
>
|
||||
{shouldCondense ? <CondensedBanner /> : <FullBanner />}
|
||||
</header>
|
||||
</>
|
||||
```
|
||||
|
||||
#### useScrollCondensation Hook
|
||||
|
||||
```tsx
|
||||
export function useScrollCondensation(options: UseScrollCondensationOptions = {}) {
|
||||
const { threshold = 100 } = options
|
||||
const [isCondensed, setIsCondensed] = useState(false)
|
||||
const sentinelRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const sentinel = sentinelRef.current
|
||||
if (!sentinel) return
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const [entry] = entries
|
||||
setIsCondensed(!entry.isIntersecting)
|
||||
},
|
||||
{
|
||||
rootMargin: `-${threshold}px 0px 0px 0px`,
|
||||
threshold: 0,
|
||||
}
|
||||
)
|
||||
|
||||
observer.observe(sentinel)
|
||||
return () => observer.disconnect()
|
||||
}, [threshold])
|
||||
|
||||
return { isCondensed, sentinelRef }
|
||||
}
|
||||
```
|
||||
|
||||
#### ClinicalSidebar Navigation Items
|
||||
|
||||
```tsx
|
||||
const navItems: NavItem[] = [
|
||||
{ id: 'summary', label: 'Summary', icon: <ClipboardList size={18} /> },
|
||||
{ id: 'consultations', label: 'Experience', icon: <FileText size={18} /> },
|
||||
{ id: 'medications', label: 'Skills', icon: <Pill size={18} /> },
|
||||
{ id: 'problems', label: 'Achievements', icon: <AlertTriangle size={18} /> },
|
||||
{ id: 'investigations', label: 'Projects', icon: <FlaskConical size={18} /> },
|
||||
{ id: 'documents', label: 'Education', icon: <FolderOpen size={18} /> },
|
||||
{ id: 'referrals', label: 'Contact', icon: <Send size={18} /> },
|
||||
]
|
||||
|
||||
// Item styling pattern
|
||||
<button
|
||||
className={`
|
||||
w-full h-[44px] px-4 flex items-center gap-3
|
||||
font-ui text-[14px] font-medium
|
||||
transition-all duration-150
|
||||
${isActive
|
||||
? 'text-white bg-white/[0.12] border-l-[3px] border-pmr-nhsblue font-semibold'
|
||||
: 'text-white/70 hover:text-white hover:bg-white/[0.08] border-l-[3px] border-transparent'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<span className="w-[18px] h-[18px]">{icon}</span>
|
||||
<span>{label}</span>
|
||||
</button>
|
||||
```
|
||||
|
||||
#### PMRInterface Layout
|
||||
|
||||
```tsx
|
||||
// Main layout structure
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
{/* Fixed sidebar */}
|
||||
<ClinicalSidebar
|
||||
activeView={activeView}
|
||||
onViewChange={handleViewChange}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
|
||||
{/* Main content area */}
|
||||
<div className="flex-1 flex flex-col min-w-0">
|
||||
{/* Sticky patient banner */}
|
||||
<PatientBanner isMobile={isMobile} isTablet={isTablet} />
|
||||
|
||||
{/* Scrollable content */}
|
||||
<main className="flex-1 overflow-y-auto bg-pmr-content p-6">
|
||||
{/* View content renders here */}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Action Button Pattern
|
||||
|
||||
```tsx
|
||||
// Outlined buttons that fill on hover
|
||||
<button
|
||||
className="
|
||||
px-3 py-1.5
|
||||
text-pmr-nhsblue text-sm font-medium
|
||||
border border-pmr-nhsblue rounded-[4px]
|
||||
transition-all duration-150
|
||||
hover:bg-pmr-nhsblue hover:text-white
|
||||
"
|
||||
>
|
||||
Download CV
|
||||
</button>
|
||||
```
|
||||
|
||||
### Mobile Considerations
|
||||
|
||||
- **Banner**: Shows only name (truncated), NHS number, and status dot
|
||||
- **Overflow Menu**: Three-dot menu reveals hidden actions (Download CV, Email, LinkedIn)
|
||||
- **Bottom Nav**: 56px fixed bottom bar with safe area padding for notched devices
|
||||
- **Touch Targets**: All interactive elements minimum 44px for accessibility
|
||||
|
||||
### Accessibility Requirements
|
||||
|
||||
- All navigation items keyboard accessible
|
||||
- Active state has visual indicator (NHS blue left border)
|
||||
- Reduced motion support: disable animations when `prefers-reduced-motion` is set
|
||||
- Focus visible states on all interactive elements
|
||||
- ARIA labels for icon-only buttons
|
||||
|
||||
---
|
||||
|
||||
## Additional Implementation Notes (from Agent Analysis)
|
||||
|
||||
### PatientBanner Component Refinements
|
||||
|
||||
#### Animation Improvements
|
||||
- Replace raw CSS `transition-all duration-200` with Framer Motion's `AnimatePresence` and `motion.div` for smoother layout animations
|
||||
- Enable cross-fade content between full and condensed banner states
|
||||
- Use `motion.div` with `initial`, `animate`, `exit` props for content swapping
|
||||
|
||||
#### Badge Styling
|
||||
- Current: `rounded-sm` — Change to true pill shape: `rounded-full` for "Open to opportunities" badge
|
||||
- Blue pill shape per NHS design system
|
||||
|
||||
#### NHS Number Tooltip
|
||||
- Replace native `title` attribute with custom styled tooltip
|
||||
- Use Framer Motion for controlled hover reveal
|
||||
- Tooltip text: "GPhC Registration Number"
|
||||
|
||||
#### Mobile Overflow Menu
|
||||
- Current: raw `useState` toggle with no animation
|
||||
- Use `AnimatePresence` for enter/exit animations
|
||||
- Three-dot menu button triggers slide-down panel
|
||||
|
||||
#### Action Button Hover States
|
||||
```tsx
|
||||
// Outlined buttons with NHS blue that fill on hover
|
||||
className="
|
||||
px-3 py-1.5
|
||||
text-[#005EB8] text-sm font-medium
|
||||
border border-[#005EB8] rounded-[4px]
|
||||
transition-all duration-150
|
||||
hover:bg-[#005EB8] hover:text-white
|
||||
"
|
||||
```
|
||||
|
||||
### ClinicalSidebar Keyboard Navigation
|
||||
|
||||
#### Alt+1-7 Shortcuts Implementation
|
||||
```tsx
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.altKey && e.key >= '1' && e.key <= '7') {
|
||||
const index = parseInt(e.key) - 1
|
||||
const view = navItems[index]
|
||||
if (view) onViewChange(view.id)
|
||||
}
|
||||
if (e.key === '/') {
|
||||
e.preventDefault()
|
||||
searchInputRef.current?.focus()
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
return () => window.removeEventListener('keydown', handleKeyDown)
|
||||
}, [onViewChange])
|
||||
```
|
||||
|
||||
#### Arrow Key Navigation
|
||||
- Up/Down arrows navigate between sidebar items
|
||||
- Focus trap within sidebar when using keyboard
|
||||
- Visual focus indicator matches hover state
|
||||
|
||||
### PMRInterface Layout Structure
|
||||
|
||||
#### Materialization Animation Sequence
|
||||
```tsx
|
||||
// Staggered entrance animations
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.05,
|
||||
delayChildren: 0.1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Patient banner: slides down (200ms ease-out)
|
||||
const bannerVariants = {
|
||||
hidden: { y: -80, opacity: 0 },
|
||||
visible: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
transition: { duration: 0.2, ease: 'easeOut' }
|
||||
}
|
||||
}
|
||||
|
||||
// Sidebar: slides from left (250ms ease-out, 50ms delay)
|
||||
const sidebarVariants = {
|
||||
hidden: { x: -220, opacity: 0 },
|
||||
visible: {
|
||||
x: 0,
|
||||
opacity: 1,
|
||||
transition: { duration: 0.25, ease: 'easeOut', delay: 0.05 }
|
||||
}
|
||||
}
|
||||
|
||||
// Content: fades in (300ms, 100ms delay after sidebar)
|
||||
const contentVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: { duration: 0.3, delay: 0.15 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### View Switching Performance
|
||||
- Views switch INSTANTLY — no crossfade or slide between views
|
||||
- Content updates immediately on hash change
|
||||
- No transition/animation between different view components
|
||||
- Only initial materialization has animation
|
||||
|
||||
### Breadcrumb Component Pattern
|
||||
|
||||
```tsx
|
||||
interface BreadcrumbProps {
|
||||
currentView: ViewId
|
||||
expandedItem?: { name: string; type: string }
|
||||
}
|
||||
|
||||
// View name mapping (CV-friendly names)
|
||||
const viewLabels: Record<ViewId, string> = {
|
||||
summary: 'Summary',
|
||||
consultations: 'Experience',
|
||||
medications: 'Skills',
|
||||
problems: 'Achievements',
|
||||
investigations: 'Projects',
|
||||
documents: 'Education',
|
||||
referrals: 'Contact'
|
||||
}
|
||||
|
||||
// Styling: [UI font] 400, 13px, gray-400
|
||||
// Chevron separators using Lucide ChevronRight
|
||||
// Clickable links navigate back
|
||||
```
|
||||
|
||||
### Mobile Bottom Navigation
|
||||
|
||||
```tsx
|
||||
// 56px height with safe area padding
|
||||
<div className="fixed bottom-0 left-0 right-0 h-14 bg-pmr-sidebar border-t border-slate-700 pb-safe">
|
||||
<div className="flex justify-around items-center h-full px-4">
|
||||
{navItems.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
className={`
|
||||
flex flex-col items-center gap-1
|
||||
${isActive ? 'text-pmr-nhsblue' : 'text-white/60'}
|
||||
`}
|
||||
>
|
||||
{item.icon}
|
||||
<span className="text-[10px]">{item.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### TypeScript Types Reference
|
||||
|
||||
```tsx
|
||||
// ViewId type for navigation
|
||||
export type ViewId =
|
||||
| 'summary'
|
||||
| 'consultations'
|
||||
| 'medications'
|
||||
| 'problems'
|
||||
| 'investigations'
|
||||
| 'documents'
|
||||
| 'referrals'
|
||||
|
||||
// Patient data structure
|
||||
export interface Patient {
|
||||
name: string // 'CHARLWOOD, Andrew (Mr)'
|
||||
displayName: string // 'Andrew Charlwood'
|
||||
dob: string // '14/02/1993'
|
||||
nhsNumber: string // '221 181 0'
|
||||
nhsNumberTooltip: string // 'GPhC Registration Number'
|
||||
address: string // 'Norwich, NR1'
|
||||
phone: string
|
||||
email: string
|
||||
linkedin: string
|
||||
status: 'Active' | 'Inactive'
|
||||
badge?: string // 'Open to opportunities'
|
||||
}
|
||||
```
|
||||
@@ -1,207 +0,0 @@
|
||||
# Reference: Consultations View (= Experience)
|
||||
|
||||
> Extracted from goal.md — Consultations View section. Each role is a "consultation entry" in a reverse-chronological journal.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This is the core content view and the most detailed section. Each role is a "consultation entry" in a reverse-chronological journal.
|
||||
|
||||
## Journal List Layout
|
||||
|
||||
Entries are stacked vertically, most recent at top. Each entry has a collapsed state and an expanded state.
|
||||
|
||||
### Collapsed Entry
|
||||
|
||||
```
|
||||
+------------------------------------------------------------------+
|
||||
| (green dot) 14 May 2025 | NHS Norfolk & Waveney ICB |
|
||||
| Interim Head, Population Health & Data Analysis |
|
||||
| Key: 14.6M efficiency programme identified and delivered |
|
||||
| [v Expand] |
|
||||
+------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
- Date in Geist Mono, 13px, gray-500 (left-aligned)
|
||||
- Organization in [UI font] 400, 13px, NHS blue
|
||||
- Role title in [UI font] 600, 15px, gray-900
|
||||
- Key coded entry: a single-line summary of the most notable achievement, prefixed with "Key:" in [UI font] 500, gray-500
|
||||
- Expand chevron button (right-aligned)
|
||||
- Status dot: green for current roles, gray for historical
|
||||
|
||||
### Expanded Entry (click to expand)
|
||||
|
||||
```
|
||||
+------------------------------------------------------------------+
|
||||
| (green dot) 14 May 2025 | NHS Norfolk & Waveney ICB [^ Close] |
|
||||
| Interim Head, Population Health & Data Analysis |
|
||||
| Duration: May 2025 - Nov 2025 |
|
||||
| |
|
||||
| HISTORY |
|
||||
| Returned to substantive Deputy Head role following |
|
||||
| commencement of ICB-wide organisational consultation. |
|
||||
| Led strategic delivery of population health initiatives |
|
||||
| and data-driven medicines optimisation across Norfolk & |
|
||||
| Waveney ICS, reporting to Associate Director of Pharmacy. |
|
||||
| |
|
||||
| EXAMINATION |
|
||||
| - Identified 14.6M efficiency programme through |
|
||||
| comprehensive data analysis |
|
||||
| - Built Python-based switching algorithm: 14,000 patients |
|
||||
| identified, 2.6M annual savings |
|
||||
| - Automated incentive scheme analysis: 50% reduction |
|
||||
| in targeted prescribing within 2 months |
|
||||
| |
|
||||
| PLAN |
|
||||
| - Achieved over-target performance by October 2025 |
|
||||
| - 2M on target for delivery this financial year |
|
||||
| - Presented to CMO bimonthly with evidence-based |
|
||||
| recommendations |
|
||||
| - Led transformation to patient-level SQL analytics |
|
||||
| |
|
||||
| CODED ENTRIES |
|
||||
| [EFF001] Efficiency programme: 14.6M identified |
|
||||
| [ALG001] Algorithm: 14,000 patients, 2.6M savings |
|
||||
| [AUT001] Automation: 50% prescribing reduction in 2mo |
|
||||
| [SQL001] Data transformation: practice->patient level |
|
||||
+------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
## The History / Examination / Plan Structure
|
||||
|
||||
This is a direct mapping from the clinical consultation format (SOAP notes) to career content:
|
||||
|
||||
| Clinical Term | CV Mapping | What Goes Here |
|
||||
|--------------|------------|----------------|
|
||||
| **History** | Context / Background | Why this role existed, what situation Andy walked into, reporting lines |
|
||||
| **Examination** | Analysis / Findings | What Andy discovered, built, or analyzed — the technical and analytical work |
|
||||
| **Plan** | Outcomes / Delivery | What was achieved, what impact was measured, what's ongoing |
|
||||
|
||||
Section headers ("HISTORY", "EXAMINATION", "PLAN") are styled in [UI font] 600, 12px, uppercase, letter-spacing 0.05em, gray-400 — styled like clinical consultation record section dividers.
|
||||
|
||||
## Coded Entries
|
||||
|
||||
At the bottom of each expanded consultation, "coded entries" appear — short-form tagged achievements with bracket codes. These mimic SNOMED CT / Read codes used in clinical systems. The codes are fictional but consistent (EFF = efficiency, ALG = algorithm, AUT = automation, SQL = data, etc.). Styled in Geist Mono, 12px, gray-500, with the code in brackets and the description after.
|
||||
|
||||
## Color Coding by Employer
|
||||
|
||||
Each consultation entry has a subtle left border (3px) indicating the employer:
|
||||
- NHS Norfolk & Waveney ICB: NHS blue (`#005EB8`)
|
||||
- Tesco PLC: Teal (`#00897B`)
|
||||
|
||||
This visual grouping helps the user quickly scan which organization each entry belongs to, without reading the text.
|
||||
|
||||
## Full Consultation Journal (all roles)
|
||||
|
||||
| Date | Organization | Role | Key Coded Entry |
|
||||
|------|-------------|------|-----------------|
|
||||
| May 2025 | NHS N&W ICB | Interim Head, Pop. Health & Data Analysis | [EFF001] 14.6M efficiency programme |
|
||||
| Jul 2024 | NHS N&W ICB | Deputy Head, Pop. Health & Data Analysis | [BUD001] 220M budget management |
|
||||
| May 2022 | NHS N&W ICB | High-Cost Drugs & Interface Pharmacist | [AUT002] Blueteq automation: 70% reduction |
|
||||
| Nov 2017 | Tesco PLC | Pharmacy Manager | [INN001] Asthma screening: ~1M national revenue |
|
||||
| Aug 2016 | Tesco PLC | Duty Pharmacy Manager | [REG001] GPhC registration commenced |
|
||||
|
||||
## Animation Behavior
|
||||
|
||||
- **Expand/collapse:** Height animation, 200ms, ease-out. No opacity fade — the content simply grows/shrinks.
|
||||
- Only one consultation can be expanded at a time. Expanding a new entry collapses the previous one.
|
||||
- The expand chevron rotates 180 degrees (pointing up when expanded).
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance
|
||||
|
||||
### Aesthetic Direction
|
||||
|
||||
**Clinical Luxury.** The structure and conventions of a clinical consultation journal — reverse-chronological entries, H/E/P format, coded entries, traffic-light status indicators — executed with premium refinement. Refined shadows, generous spacing, considered typography. Light-mode only.
|
||||
|
||||
**What makes this unforgettable:** The History / Examination / Plan structure maps perfectly to Context / Analysis / Outcome. A clinician seeing this will immediately recognize the consultation journal format. A recruiter gets highly structured, scannable career data. The accordion behavior, coded entries, and status indicators draw from clinical software patterns — but the execution feels polished and premium, not institutional.
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
1. **SOAP Notes Format (H/E/P Structure)**
|
||||
- Maps clinical consultation format to career content:
|
||||
- **History** → Context / Background (why the role existed, reporting lines)
|
||||
- **Examination** → Analysis / Findings (what was discovered, built, analyzed)
|
||||
- **Plan** → Outcomes / Delivery (what was achieved, impact measured)
|
||||
- Section headers styled as clinical system dividers: [UI font] 600, 12px, uppercase, letter-spacing 0.05em, gray-400
|
||||
|
||||
2. **Height-Only Expand Animation (200ms)**
|
||||
- No opacity fade on content—content simply grows/shrinks
|
||||
- Duration: 200ms with ease-out timing
|
||||
- Only one entry expanded at a time
|
||||
- Chevron rotates 180 degrees when expanded
|
||||
|
||||
3. **Color-Coded Left Border (3px)**
|
||||
- NHS Norfolk & Waveney ICB: NHS blue (`#005EB8`)
|
||||
- Tesco PLC: Teal (`#00897B`)
|
||||
|
||||
4. **Typography System**
|
||||
- [UI font] for text content (Elvaro or Blumir — see CLAUDE.md)
|
||||
- Geist Mono for dates, codes, timestamps
|
||||
- Border radius: 4px throughout
|
||||
- Borders: 1px solid #E5E7EB
|
||||
|
||||
### Implementation Patterns
|
||||
|
||||
**ConsultationEntry Type:**
|
||||
```typescript
|
||||
interface ConsultationEntry {
|
||||
id: string
|
||||
date: string // e.g., "14 May 2025"
|
||||
organization: string // e.g., "NHS Norfolk & Waveney ICB"
|
||||
role: string // e.g., "Interim Head, Population Health & Data Analysis"
|
||||
duration: string // e.g., "May 2025 - Nov 2025"
|
||||
isCurrent: boolean // Green dot if true, gray if false
|
||||
borderColor: '#005EB8' | '#00897B'
|
||||
keyAchievement: { code: string; description: string }
|
||||
history: string[] // Paragraphs for HISTORY section
|
||||
examination: string[] // Bullet points for EXAMINATION section
|
||||
plan: string[] // Bullet points for PLAN section
|
||||
codedEntries: { code: string; description: string }[]
|
||||
}
|
||||
```
|
||||
|
||||
**Animation Pattern:**
|
||||
```typescript
|
||||
// Height-only animation via Framer Motion
|
||||
<motion.div
|
||||
initial={false}
|
||||
animate={{ height: isExpanded ? 'auto' : 0 }}
|
||||
transition={{ duration: 0.2, ease: 'easeOut' }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
{/* Expanded content */}
|
||||
</motion.div>
|
||||
|
||||
// Chevron rotation
|
||||
<motion.div
|
||||
animate={{ rotate: isExpanded ? 180 : 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<ChevronDown />
|
||||
</motion.div>
|
||||
```
|
||||
|
||||
**Section Header Pattern:**
|
||||
```tsx
|
||||
<h4 className="font-sans text-xs font-semibold uppercase tracking-wider text-gray-400">
|
||||
History
|
||||
</h4>
|
||||
```
|
||||
|
||||
**Coded Entry Pattern:**
|
||||
```tsx
|
||||
<span className="font-mono text-xs text-gray-500">
|
||||
[EFF001] Efficiency programme: £14.6M identified
|
||||
</span>
|
||||
```
|
||||
|
||||
**Status Dot Pattern:**
|
||||
```tsx
|
||||
<div className={cn(
|
||||
"w-2 h-2 rounded-full",
|
||||
isCurrent ? "bg-green-500" : "bg-gray-400"
|
||||
)} />
|
||||
```
|
||||
@@ -1,158 +0,0 @@
|
||||
# Reference: Visual Design System
|
||||
|
||||
> The SINGLE SOURCE OF TRUTH for colors, typography, spacing, surfaces, and motion throughout the Clinical Record PMR. Follows the **Clinical Luxury** direction in CLAUDE.md.
|
||||
|
||||
---
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
A **premium portfolio** that uses the structure and metaphor of a GP clinical system — not a faithful NHS software clone. Real clinical systems (EMIS Web, SystmOne) are dense, border-heavy, and purely functional. We keep their *structure* (patient banner, sidebar navigation, record sections, tables, status indicators) but elevate the *execution* with refined typography, atmospheric depth, and considered whitespace.
|
||||
|
||||
The goal is contrast: clinical precision married to luxury refinement. The "wow" comes from recognizing the clinical metaphor while being surprised by how good it looks.
|
||||
|
||||
---
|
||||
|
||||
## Color Palette
|
||||
|
||||
**Light-mode only.** The metaphor demands it — clinical systems operate under bright consulting room lights. No dark mode.
|
||||
|
||||
**Backgrounds:**
|
||||
- Main content area: `#F5F7FA` — cool light gray base. Add atmospheric depth — a faint noise/grain texture overlay or a subtle warm tint — so the surface feels like quality paper, not a flat spreadsheet.
|
||||
- Card/panel surfaces: `#FFFFFF` — clean white. Cards float above the content surface via layered shadows (see Surfaces section).
|
||||
- Sidebar: `#1E293B` — dark blue-gray. The gravitas anchor — dark chrome that reads as serious software.
|
||||
- Patient banner: `#334155` — lighter blue-gray with white text. Subtle drop shadow below to separate from content.
|
||||
- Login screen background: `#1E293B` — same as sidebar. Carries through to PMR entrance seamlessly.
|
||||
|
||||
**Text:**
|
||||
- Primary: `#111827` (gray-900) — near-black for maximum readability
|
||||
- Secondary: `#6B7280` (gray-500) — labels, metadata, supporting text
|
||||
- Muted: `#94A3B8` (slate-400) — timestamps, tertiary info
|
||||
- On dark surfaces: `#FFFFFF` (white primary), `#94A3B8` (slate-400 secondary)
|
||||
|
||||
**Accent and status colors:**
|
||||
- **NHS Blue `#005EB8`** — THE accent color. Buttons, active nav states, links, interactive elements. The actual NHS brand blue — instantly recognizable, the strongest signal of the clinical metaphor. Use it confidently but not everywhere.
|
||||
- Green `#22C55E` — active/resolved/current states. Status dots, current role indicators.
|
||||
- Amber `#F59E0B` — alerts, in-progress items. The clinical alert banner background.
|
||||
- Red `#EF4444` — urgent/critical. Used very sparingly — only genuinely important items.
|
||||
- Gray `#6B7280` — inactive/historical items.
|
||||
|
||||
**Traffic light system (used throughout):**
|
||||
- Green dot: Active / Resolved / Current
|
||||
- Amber dot: In progress / Alert / Notable
|
||||
- Red dot: Urgent / Critical (rare)
|
||||
- Gray dot: Inactive / Historical
|
||||
- **Always paired with text labels.** Color is never the sole signifier (WCAG compliance).
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
See Claude.md for info on font choice. Typography carries the premium feel. The font choice must feel *designed* — intentional and distinctive — while reading cleanly at small clinical-system sizes (11-14px).
|
||||
|
||||
**Type scale (tight, clinical):**
|
||||
- Patient banner name: [UI font] 600, 20px
|
||||
- Patient banner details: [UI font] 400, 14px
|
||||
- Sidebar navigation labels: [UI font] 500, 14px, white
|
||||
- Section headings (main area): [UI font] 600, 15-18px
|
||||
- Consultation entry titles: [UI font] 600, 15-16px
|
||||
- Body text / descriptions: [UI font] 400, 13-14px, line-height 1.6
|
||||
- Table headers: [UI font] 600, 12-13px, uppercase, letter-spacing 0.03-0.05em
|
||||
- Table data cells: [UI font] 400, 13-14px
|
||||
- Labels / metadata: [UI font] 500, 11-12px
|
||||
- Coded entries / data values: Geist Mono 400, 12-13px
|
||||
- Clinical codes (SNOMED-style): Geist Mono 400, 11-12px, gray-400
|
||||
- Timestamps: Geist Mono 400, 11-12px
|
||||
- Alert banner text: [UI font] 500, 14px
|
||||
|
||||
**Hierarchy through weight, not size.** Use 400/500/600/700 weight variations within a narrow size range. Bold section headers, medium labels, regular body. This keeps the clinical density while creating clear, scannable hierarchy.
|
||||
|
||||
---
|
||||
|
||||
## Spacing and Layout
|
||||
|
||||
More generous than real clinical software. The clinical metaphor provides structure; the extra breathing room provides luxury.
|
||||
|
||||
- **Sidebar width:** 220px (fixed, desktop). Collapses to 56px (icon-only) on tablet.
|
||||
- **Patient banner height:** 80px (full), 48px (condensed/sticky)
|
||||
- **Main content max-width:** None — fills available space between sidebar and viewport edge.
|
||||
- **Main content padding:** 24px (desktop), 16px (mobile)
|
||||
- **Card padding:** 16-24px — more generous than real clinical systems. Content should breathe inside cards.
|
||||
- **Border radius:** 4px default for cards, inputs, buttons (clinical precision). 12px exception for the login card only.
|
||||
- **Table row height:** 40px
|
||||
- **Section spacing:** 24px between content blocks
|
||||
- **Base unit:** 4px grid — applied more generously than in real clinical systems
|
||||
|
||||
---
|
||||
|
||||
## Surfaces & Depth
|
||||
|
||||
Our biggest departure from real clinical software. Real systems are flat and border-heavy; we use **shadows and layering** for depth — while keeping borders where they're authentically clinical (tables, input fields).
|
||||
|
||||
**Cards:**
|
||||
- Border: `1px solid #E5E7EB` (keep the clinical border — it's authentic)
|
||||
- Shadow: Multi-layered — `0 1px 2px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.03)`. Gentle float, not Material Design dramatic.
|
||||
- Border-radius: `4px`
|
||||
- Hover: Cards may lift very slightly — 1-2px translateY + shadow deepens to `0 2px 4px rgba(0,0,0,0.06), 0 8px 16px rgba(0,0,0,0.04)`. Restrained, not bouncy.
|
||||
- Card headers: Light gray `#F9FAFB` background with `1px solid #E5E7EB` bottom border. Uppercase title in [UI font] 600, 12-13px. The most "clinical" element — keep it precise.
|
||||
|
||||
**Tables:**
|
||||
- Full `<table>` markup with styled headers — where clinical authenticity lives.
|
||||
- Table headers: `#F9FAFB` background, `1px solid #E5E7EB` borders.
|
||||
- Alternating rows: `#FFFFFF` / `#F9FAFB` — subtle but scannable.
|
||||
- Row hover: `#EFF6FF` background (blue tint).
|
||||
- Cell borders: `1px solid #E5E7EB` — keep full borders on tables. Authentic.
|
||||
|
||||
**Sidebar:**
|
||||
- Background: `#1E293B`
|
||||
- Right edge: `1px solid #334155` + optional very subtle glow/shadow where it meets the content area.
|
||||
- The sidebar should feel solid and authoritative against the lighter content.
|
||||
|
||||
**Patient banner:**
|
||||
- Background: `#334155`
|
||||
- Bottom: Subtle drop shadow `0 2px 8px rgba(0,0,0,0.12)` to separate from content below.
|
||||
- Bottom border: `1px solid #475569`
|
||||
|
||||
**Input fields:**
|
||||
- Border: `1px solid #D1D5DB`, `4px` radius, `#FFFFFF` background, `8px 12px` padding
|
||||
- Focus: NHS blue border + `box-shadow: 0 0 0 3px rgba(0,94,184,0.15)` — refined focus ring.
|
||||
|
||||
---
|
||||
|
||||
## Motion
|
||||
|
||||
Motion should feel **considered and premium** — never flashy, never gratuitous. Every animation has a purpose: to orient the user, to reward interaction, or to create a moment of polish.
|
||||
|
||||
**PMR entrance sequence (login → PMR transition):**
|
||||
- Patient banner slides down: 200ms, ease-out
|
||||
- Sidebar slides from left: 250ms, ease-out, 50ms delay
|
||||
- Content fades in: 300ms, 100ms delay after sidebar
|
||||
- The staggered materialization — the most impactful animation.
|
||||
|
||||
**Navigation switches:** Instant content swap. No crossfade, no slide. This preserves the "software application" feel — clinical systems switch tabs instantly.
|
||||
|
||||
**Expandable content:** Height-only animation, 200ms, `ease-out`. Content grows/shrinks — no opacity fade.
|
||||
|
||||
**Clinical alert entrance:** Spring animation (Framer Motion `type: "spring"`, moderate damping). The one element that *demands attention* — the spring overshoot is earned here.
|
||||
|
||||
**Alert acknowledge:** Warning icon cross-fades to green checkmark (200ms) → hold 200ms → alert height collapses (200ms ease-out).
|
||||
|
||||
**Hover states:** Subtle and immediate. Background-color transitions at 100ms. Card lifts are 1-2px max with shadow deepening. Think: OS-level responsiveness, not playful bouncing.
|
||||
|
||||
**Login typing:** Character-by-character reveal at a natural pace: 80ms/char for username, 60ms/dot for password. Cursor blink at 530ms. After typing completes, "Log In" button becomes interactive — user clicks to proceed (not auto-triggered).
|
||||
|
||||
**Patient banner condensation:** Height transition (200ms) from 80px → 48px as user scrolls past 100px. Buttery smooth, no jank.
|
||||
|
||||
**`prefers-reduced-motion`:** All animations skip to final state instantly. Typing completes immediately. Alert appears without slide. Expand/collapse is instant. No exceptions.
|
||||
|
||||
---
|
||||
|
||||
## What Makes This Design Distinctive
|
||||
|
||||
The design stands on **contrasts**:
|
||||
- Dark, serious sidebar next to warm, airy content
|
||||
- Small, precise monospace data in generous whitespace fields
|
||||
- NHS blue punching through an otherwise muted, restrained palette
|
||||
- Clinical structure (tables, status dots, coded entries) executed with luxury refinement (shadows, spacing, typography)
|
||||
- The boot → ECG → login theatrical sequence, then suddenly: a premium application
|
||||
|
||||
If any component could be dropped into a generic SaaS dashboard without looking out of place, it needs more character.
|
||||
@@ -1,163 +0,0 @@
|
||||
# Reference: Interactions, Responsive Design, and Accessibility
|
||||
|
||||
> Extracted from goal.md — Interactions, Responsive Strategy, and Accessibility sections.
|
||||
|
||||
---
|
||||
|
||||
## Interactions and Micro-interactions
|
||||
|
||||
### Sidebar Navigation
|
||||
- Clicking a sidebar item instantly swaps the main content area. No crossfade, no transition — just an immediate swap. This matches clinical system behavior exactly: navigation is instant.
|
||||
- The active sidebar item updates its left border (3px, NHS blue) and background tint simultaneously, with no animation (instant state change).
|
||||
|
||||
### Consultation Expand / Collapse
|
||||
- Clicking a consultation entry toggles between collapsed and expanded states.
|
||||
- The expand animation: height grows from 0 to content height over 200ms, ease-out. Content opacity transitions from 0 to 1 over the same duration.
|
||||
- Only one consultation can be expanded at a time. Expanding a new entry collapses the previous one.
|
||||
- The expand chevron rotates 180 degrees (pointing up when expanded).
|
||||
|
||||
### Medication Row Hover
|
||||
- Hovering a medication table row changes its background to `#EFF6FF` (subtle blue tint).
|
||||
- No transform, no elevation change. Just color.
|
||||
|
||||
### Table Column Sorting
|
||||
- Clicking a table column header sorts by that column. An arrow indicator (up/down) appears in the header.
|
||||
- Clicking the same header again reverses sort direction.
|
||||
- Sorting is instant (no animation on row reordering).
|
||||
|
||||
### Patient Banner Scroll Condensation
|
||||
- As the user scrolls past 100px of content, the patient banner smoothly transitions from full (80px) to condensed (48px) over 200ms.
|
||||
- The condensed banner shows only: name, NHS number, status dot, and action buttons.
|
||||
- Scrolling back to top restores the full banner.
|
||||
- Uses `position: sticky` with an `IntersectionObserver` to trigger the condensation.
|
||||
|
||||
### Alert Acknowledge
|
||||
- Clicking "Acknowledge" on a clinical alert:
|
||||
1. The warning icon cross-fades to a green checkmark (200ms)
|
||||
2. After a 200ms hold, the alert's height animates to 0 (200ms, ease-out)
|
||||
3. Content below shifts upward to fill the space (same 200ms timing)
|
||||
|
||||
### Search
|
||||
- A search input in the sidebar header ("Search record...") provides fuzzy matching across all PMR sections.
|
||||
- Typing shows a dropdown of results grouped by section (Consultations, Medications, Problems, etc.).
|
||||
- Each result shows the section icon, the matching text, and a relevance indicator.
|
||||
- Pressing Enter or clicking a result navigates to that section with the matching item highlighted/expanded.
|
||||
- Implementation: fuse.js for fuzzy search across a pre-built index of all content.
|
||||
|
||||
### Context Menus
|
||||
- Right-clicking (desktop) or long-pressing (mobile) on certain elements reveals a context menu:
|
||||
- On a consultation entry: "Expand", "Copy to clipboard", "View coded entries"
|
||||
- On a medication row: "View prescribing history", "Copy to clipboard"
|
||||
- On a problem entry: "View linked consultations", "Copy to clipboard"
|
||||
- Context menus styled: white background, `1px solid #E5E7EB` border, 4px radius, `box-shadow: 0 4px 12px rgba(0,0,0,0.1)`. Items in [UI font] 400, 14px, 36px row height.
|
||||
|
||||
### Login Screen Typing
|
||||
- The username types character-by-character at a natural reading pace (80ms per character).
|
||||
- The password dots appear at a deliberate pace (60ms per dot).
|
||||
- A blinking cursor appears in the active field (530ms blink interval).
|
||||
- After typing completes, the "Log In" button becomes clearly interactive (full opacity, hover state). The user clicks it to proceed — this is NOT auto-triggered.
|
||||
- On click, the button shows a brief pressed state before the interface materializes.
|
||||
|
||||
---
|
||||
|
||||
## Responsive Strategy
|
||||
|
||||
### Desktop (>1024px)
|
||||
The full PMR experience. This is the design's primary target — clinical systems are desktop applications.
|
||||
- Sidebar: 220px, always visible, dark blue-gray
|
||||
- Patient banner: full width, 80px height, condensing to 48px on scroll
|
||||
- Main content: fills remaining width (no max-width constraint)
|
||||
- Tables: full column display, alternating row colors, sort controls
|
||||
- Consultations: full History/Examination/Plan expanded view
|
||||
- Search: integrated in sidebar header
|
||||
|
||||
### Tablet (768-1024px)
|
||||
Sidebar collapses to icon-only mode (56px width). Hovering or tapping an icon shows the label as a tooltip.
|
||||
- Patient banner: condensed to single-line format always (no full/condensed toggle)
|
||||
- Main content: nearly full width
|
||||
- Tables: may horizontally scroll if columns exceed available width
|
||||
- Context menus: triggered by long-press instead of right-click
|
||||
|
||||
### Mobile (<768px)
|
||||
The sidebar becomes a **bottom navigation bar** with 7 icon buttons.
|
||||
|
||||
**Bottom nav layout:**
|
||||
```
|
||||
[Summary] [Consult] [Meds] [Problems] [Invest] [Docs] [Refer]
|
||||
```
|
||||
|
||||
Each icon from Lucide, 20px, with the active item highlighted in NHS blue with a label below. Height: 56px with safe area padding.
|
||||
|
||||
**Patient banner on mobile:** Minimal top bar: `CHARLWOOD, A (Mr) | 2211810 | (dot)` — action buttons collapse into "..." overflow menu.
|
||||
|
||||
**Content adaptations:**
|
||||
- Tables switch to card layout: each row becomes a small card with fields stacked vertically
|
||||
- Consultation entries: tap-to-expand pattern with larger tap targets (48px minimum height)
|
||||
- Medications: table becomes stacked card list
|
||||
- Referral form: full-width inputs, generous touch targets
|
||||
- Search: moves to top of each view as a search bar
|
||||
|
||||
**Back navigation:** Each view has a back arrow returning to Summary.
|
||||
|
||||
### Breakpoint Summary
|
||||
|
||||
| Element | Desktop (>1024) | Tablet (768-1024) | Mobile (<768) |
|
||||
|---------|-----------------|-------------------|---------------|
|
||||
| Sidebar | 220px, full labels | 56px, icons only | Bottom nav bar |
|
||||
| Patient banner | 80px full / 48px sticky | 48px always | Minimal top bar |
|
||||
| Tables | Full columns, horizontal | Scroll if needed | Card layout (stacked) |
|
||||
| Search | Sidebar header | Sidebar header | Top of each view |
|
||||
| Context menus | Right-click | Long-press | Long-press |
|
||||
|
||||
---
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Semantic HTML
|
||||
- Sidebar: `<nav role="navigation" aria-label="Clinical record navigation">` with `<ul>` and `<li>` items. Active item uses `aria-current="page"`.
|
||||
- Patient banner: `<header role="banner">` containing patient demographics.
|
||||
- Main content area: `<main>` element with `aria-label` matching the current view name.
|
||||
- Tables: Proper `<table>`, `<thead>`, `<th>`, `<tbody>`, `<tr>`, `<td>` markup. Column headers use `scope="col"`.
|
||||
- Consultation entries: `<article>` elements with `<button>` for expand/collapse, `aria-expanded` attribute.
|
||||
|
||||
### Keyboard Navigation
|
||||
- `Tab` moves between: sidebar items, patient banner buttons, main content interactive elements
|
||||
- `ArrowUp` / `ArrowDown` within the sidebar moves between navigation items (roving tabindex)
|
||||
- `Enter` / `Space` on sidebar items activates that view
|
||||
- `Enter` / `Space` on consultation entries toggles expand/collapse
|
||||
- `Alt+1` through `Alt+7` directly activates sidebar items
|
||||
- `Escape` closes expanded items, context menus, and search dropdown
|
||||
- Search input focusable with `/` key
|
||||
|
||||
### Screen Reader Experience
|
||||
1. After login, announces: "Patient Record for Charlwood, Andrew. Summary view."
|
||||
2. Clinical alert announced via `role="alert"`: full alert text
|
||||
3. Tables announced with column headers
|
||||
4. Expandable items announce expanded/collapsed state
|
||||
5. Breadcrumb uses `<nav aria-label="Breadcrumb">`
|
||||
|
||||
### Alert Accessibility
|
||||
- Uses `role="alert"` and `aria-live="assertive"`
|
||||
- Acknowledge button: `aria-label="Acknowledge clinical alert"`
|
||||
- Removal is smooth (element removes from accessibility tree)
|
||||
|
||||
### Focus Management
|
||||
- After login: focus moves to first sidebar item (Summary)
|
||||
- After navigating to new view: focus moves to first heading in main content
|
||||
- After expanding consultation: focus moves to HISTORY heading
|
||||
- After closing context menu: focus returns to trigger element
|
||||
- After acknowledging alert: focus moves to main content first interactive element
|
||||
|
||||
### Color and Contrast
|
||||
- All text meets WCAG 2.1 AA contrast requirements
|
||||
- Traffic lights never sole communicator — always with text labels
|
||||
- NHS blue on white: ~7.3:1 contrast ratio
|
||||
- Amber alert text on amber bg: ~5.8:1 contrast ratio
|
||||
|
||||
### Motion Preferences
|
||||
When `prefers-reduced-motion: reduce`:
|
||||
- Login typing completes instantly
|
||||
- Alert appears without slide
|
||||
- Expand/collapse is instant
|
||||
- Banner condensation is instant
|
||||
- Hover background-color changes remain
|
||||
@@ -1,277 +0,0 @@
|
||||
# Reference: Investigations View (= Projects) + Documents View (= Education)
|
||||
|
||||
> Extracted from goal.md — Investigations and Documents sections. Two simpler views that share the expandable-row pattern.
|
||||
|
||||
---
|
||||
|
||||
## Investigations View (= Projects)
|
||||
|
||||
Projects presented as diagnostic investigations — tests that were ordered, performed, and returned results.
|
||||
|
||||
### Investigation List
|
||||
|
||||
```
|
||||
+--[ Investigation Results ]----------------------------------------------+
|
||||
| Test Name | Requested | Status | Result |
|
||||
|------------------------------+-----------+----------+-------------------|
|
||||
| PharMetrics Interactive | 2024 | Complete | Live (green) |
|
||||
| Platform | | | |
|
||||
| Patient Switching Algorithm | 2025 | Complete | 14,000 pts found |
|
||||
| Blueteq Generator | 2023 | Complete | 70% reduction |
|
||||
| CD Monitoring System | 2024 | Complete | Population-scale |
|
||||
| Sankey Chart Analysis Tool | 2023 | Complete | Pathway audit |
|
||||
| Patient Pathway Analysis | 2024 | Ongoing | In development |
|
||||
+-------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
### Status Badges
|
||||
|
||||
Styled like laboratory result status indicators:
|
||||
- **Complete** (green dot): Investigation finished, results available
|
||||
- **Ongoing** (amber dot): Investigation still in progress
|
||||
- **Live** (pulsing green dot): Results are actively being used (for PharMetrics, which is a live URL)
|
||||
|
||||
### Expanded Investigation View
|
||||
|
||||
Clicking an investigation row reveals a detailed "results panel" below the row:
|
||||
|
||||
```
|
||||
PharMetrics Interactive Platform
|
||||
|-- Date Requested: 2024
|
||||
|-- Date Reported: 2024
|
||||
|-- Status: Complete - Live at medicines.charlwood.xyz
|
||||
|-- Requesting Clinician: A. Charlwood
|
||||
|-- Methodology:
|
||||
| Real-time medicines expenditure dashboard providing
|
||||
| actionable analytics for NHS decision-makers. Built with
|
||||
| Power BI and SQL, tracking expenditure across the 220M
|
||||
| prescribing budget.
|
||||
|-- Results:
|
||||
| - Real-time tracking of medicines expenditure
|
||||
| - Actionable analytics for budget holders
|
||||
| - Self-serve model for wider team
|
||||
|-- Tech Stack: Power BI, SQL, DAX
|
||||
|-- [View Results ->] (external link to medicines.charlwood.xyz)
|
||||
```
|
||||
|
||||
The expanded view uses a tree-like indented structure (with box-drawing characters in monospace) to present the investigation report. This mirrors how lab results and imaging reports appear in clinical systems — structured, indented, with labelled fields.
|
||||
|
||||
### "View Results" Link
|
||||
|
||||
For PharMetrics (the only project with a live URL), a "View Results" button appears styled as an NHS blue action button. For internal projects, this button is absent.
|
||||
|
||||
---
|
||||
|
||||
## Documents View (= Education & Certifications)
|
||||
|
||||
Education and certifications presented as attached documents in the patient record.
|
||||
|
||||
### Document List
|
||||
|
||||
```
|
||||
+--[ Attached Documents ]-------------------------------------------------+
|
||||
| Type | Document | Date | Source |
|
||||
|----------------+----------------------------------+---------+------------|
|
||||
| Certificate | MPharm (Hons) 2:1 | 2015 | UEA |
|
||||
| Registration | GPhC Pharmacist Registration | 2016 | GPhC |
|
||||
| Certificate | Mary Seacole Programme (78%) | 2018 | NHS LA |
|
||||
| Results | A-Levels: Maths A*, Chem B, | 2011 | Highworth |
|
||||
| | Politics C | | Grammar |
|
||||
| Research | Drug Delivery & Cocrystals | 2015 | UEA |
|
||||
| | (75.1% Distinction) | | |
|
||||
+-------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
### Document Type Icons
|
||||
|
||||
Small document icons from Lucide:
|
||||
- `FileText` for certificates
|
||||
- `Award` for registrations
|
||||
- `GraduationCap` for academic results
|
||||
- `FlaskConical` for research
|
||||
|
||||
### Expanded Document Preview
|
||||
|
||||
```
|
||||
MPharm (Hons) 2:1 - University of East Anglia
|
||||
|-- Type: Academic Qualification
|
||||
|-- Date Awarded: 2015
|
||||
|-- Institution: University of East Anglia, Norwich
|
||||
|-- Classification: Upper Second-Class Honours (2:1)
|
||||
|-- Duration: 2011 - 2015 (4 years)
|
||||
|-- Research: Drug delivery and cocrystals
|
||||
| Grade: 75.1% (Distinction)
|
||||
|-- Notes: MPharm is a 4-year integrated Master's degree
|
||||
required for pharmacist registration in the UK.
|
||||
```
|
||||
|
||||
The preview panel uses the same tree-indented structure as the Investigations expanded view, maintaining visual consistency across the PMR interface.
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance
|
||||
|
||||
### Aesthetic Direction
|
||||
|
||||
**Tone:** Clinical Luxury — the *structure* of clinical investigation results and attached documents (tables, status badges, expandable rows) executed with premium refinement. Borders provide authentic clinical structuring; layered shadows, generous spacing, and refined typography provide the luxury finish. Light-mode only.
|
||||
|
||||
**Differentiation:** The expanded-row tree-indented monospace structure using box-drawing characters is the signature element. It transforms a flat data table into something that reads like a lab report or radiology result — structured, indented, with labelled fields in `Geist Mono`. The pipe-and-branch characters (`├─`, `│`, `└─`) create a distinctly clinical aesthetic that no standard portfolio site would ever use.
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
#### ExpandableRow Component Pattern
|
||||
|
||||
Both views share an identical expand/collapse mechanic:
|
||||
|
||||
1. **Collapsed State:** Standard table row with hover feedback (slight background tint)
|
||||
2. **Expand Trigger:** Click anywhere on the row
|
||||
3. **Expanded State:** Full-width panel slides down below the row with `AnimatePresence`
|
||||
4. **Visual Connection:** Expanded panel has left border matching the row's status color
|
||||
5. **Tree Structure:** Expanded content uses box-drawing characters for clinical report aesthetic
|
||||
|
||||
**Status Badge System:**
|
||||
- **Complete** (green dot): `#10B981` background, used for finished investigations
|
||||
- **Ongoing** (amber dot): `#F59E0B` background, used for in-progress work
|
||||
- **Live** (pulsing green dot): `#10B981` with CSS pulse animation, used for active/live URLs
|
||||
|
||||
#### Typography & Spacing
|
||||
|
||||
- **Primary font:** [UI font] (text, labels, table headers — Elvaro or Blumir, see CLAUDE.md)
|
||||
- **Monospace font:** Geist Mono (tree-indented expanded content)
|
||||
- **Border radius:** 4px throughout
|
||||
- **Border color:** `#E5E7EB` (Tailwind gray-200)
|
||||
- **NHS Blue:** `#005EB8` (action buttons, links)
|
||||
- **Card shadow:** Multi-layered per design system
|
||||
|
||||
### Implementation Patterns
|
||||
|
||||
#### StatusBadge Component
|
||||
|
||||
```tsx
|
||||
interface StatusBadgeProps {
|
||||
status: 'complete' | 'ongoing' | 'live';
|
||||
label: string;
|
||||
}
|
||||
|
||||
const StatusBadge = ({ status, label }: StatusBadgeProps) => {
|
||||
const styles = {
|
||||
complete: 'bg-emerald-100 text-emerald-800 border-emerald-200',
|
||||
ongoing: 'bg-amber-100 text-amber-800 border-amber-200',
|
||||
live: 'bg-emerald-100 text-emerald-800 border-emerald-200 animate-pulse',
|
||||
};
|
||||
|
||||
const dotColors = {
|
||||
complete: 'bg-emerald-500',
|
||||
ongoing: 'bg-amber-500',
|
||||
live: 'bg-emerald-500 animate-ping',
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded text-xs font-medium border ${styles[status]}`}>
|
||||
<span className={`w-1.5 h-1.5 rounded-full ${dotColors[status]}`} />
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### Tree-Indented Content Structure
|
||||
|
||||
```tsx
|
||||
const TreeLine = ({ label, value, isLast = false }: TreeLineProps) => (
|
||||
<div className="font-mono text-sm text-gray-700">
|
||||
<span className="text-gray-400">{isLast ? '└─ ' : '├─ '}</span>
|
||||
<span className="text-gray-500">{label}:</span>
|
||||
<span className="ml-2">{value}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Usage in expanded view:
|
||||
<div className="bg-gray-50 border-l-4 border-emerald-400 pl-4 py-3">
|
||||
<TreeLine label="Date Requested" value="2024" />
|
||||
<TreeLine label="Status" value="Complete" />
|
||||
<TreeLine label="Methodology" value="Power BI dashboard..." isLast />
|
||||
</div>
|
||||
```
|
||||
|
||||
#### ExpandableRow with Framer Motion
|
||||
|
||||
```tsx
|
||||
const ExpandableRow = ({
|
||||
children,
|
||||
expandedContent,
|
||||
isExpanded,
|
||||
onToggle
|
||||
}: ExpandableRowProps) => {
|
||||
return (
|
||||
<>
|
||||
<tr
|
||||
onClick={onToggle}
|
||||
className="cursor-pointer hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
{children}
|
||||
</tr>
|
||||
<AnimatePresence>
|
||||
{isExpanded && (
|
||||
<motion.tr
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.2, ease: 'easeInOut' }}
|
||||
>
|
||||
<td colSpan={4} className="p-0 border-b">
|
||||
{expandedContent}
|
||||
</td>
|
||||
</motion.tr>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### Document Type Icons
|
||||
|
||||
```tsx
|
||||
import { FileText, Award, GraduationCap, FlaskConical } from 'lucide-react';
|
||||
|
||||
const documentIcons = {
|
||||
certificate: FileText,
|
||||
registration: Award,
|
||||
academic: GraduationCap,
|
||||
research: FlaskConical,
|
||||
};
|
||||
|
||||
const DocumentIcon = ({ type }: { type: keyof typeof documentIcons }) => {
|
||||
const Icon = documentIcons[type];
|
||||
return <Icon className="w-4 h-4 text-gray-500" />;
|
||||
};
|
||||
```
|
||||
|
||||
#### Mobile Card Layout
|
||||
|
||||
On mobile (<768px), both views switch to card layouts:
|
||||
|
||||
```tsx
|
||||
// Mobile: Card layout with vertical stacking
|
||||
<div className="md:hidden space-y-3">
|
||||
{investigations.map((inv) => (
|
||||
<div key={inv.id} className="bg-white rounded border p-4">
|
||||
{/* Card content */}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
// Desktop: Table layout
|
||||
<table className="hidden md:table w-full">
|
||||
{/* Table content */}
|
||||
</table>
|
||||
```
|
||||
|
||||
### Tech Stack Integration
|
||||
|
||||
- **React 18** with TypeScript strict mode
|
||||
- **Tailwind CSS** for all styling (no CSS-in-JS)
|
||||
- **Framer Motion 11** for expand/collapse animations
|
||||
- **Lucide React** for document type icons
|
||||
- **Geist Mono** font for tree-indented content (add to index.html)
|
||||
@@ -1,270 +0,0 @@
|
||||
# Reference: Medications View (= Skills)
|
||||
|
||||
> Extracted from goal.md — Medications View section. Skills presented as an active medications list.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Skills presented as an active medications list — the format every pharmacist and GP reads daily.
|
||||
|
||||
## Full Table Layout
|
||||
|
||||
```
|
||||
+--[ Active Medications ]-------------------------------------------------+
|
||||
| Drug Name | Dose | Frequency | Start | Status |
|
||||
|--------------------+-------+------------+----------+-------------------|
|
||||
| Python | 90% | Daily | 2017 | Active (green) |
|
||||
| SQL | 88% | Daily | 2017 | Active (green) |
|
||||
| Power BI | 92% | Daily | 2019 | Active (green) |
|
||||
| Data Analysis | 95% | Daily | 2016 | Active (green) |
|
||||
| JavaScript / TS | 70% | Weekly | 2020 | Active (green) |
|
||||
| Dashboard Dev | 88% | Weekly | 2019 | Active (green) |
|
||||
| Algorithm Design | 82% | Weekly | 2022 | Active (green) |
|
||||
| Data Pipelines | 80% | Weekly | 2022 | Active (green) |
|
||||
+-------------------------------------------------------------------------+
|
||||
|
||||
+--[ Clinical Medications ]-----------------------------------------------+
|
||||
| Drug Name | Dose | Frequency | Start | Status |
|
||||
|-------------------------+-------+------------+--------+----------------|
|
||||
| Medicines Optimisation | 95% | Daily | 2016 | Active (green) |
|
||||
| Pop. Health Analytics | 90% | Daily | 2022 | Active (green) |
|
||||
| NICE TA Implementation | 85% | Weekly | 2022 | Active (green) |
|
||||
| Health Economics | 80% | Monthly | 2023 | Active (green) |
|
||||
| Clinical Pathways | 82% | Weekly | 2022 | Active (green) |
|
||||
| CD Assurance | 88% | Weekly | 2024 | Active (green) |
|
||||
+-------------------------------------------------------------------------+
|
||||
|
||||
+--[ PRN (As Required) ]--------------------------------------------------+
|
||||
| Drug Name | Dose | Frequency | Start | Status |
|
||||
|-------------------------+-------+------------+--------+----------------|
|
||||
| Budget Management | 90% | As needed | 2024 | Active (green) |
|
||||
| Stakeholder Engagement | 88% | As needed | 2022 | Active (green) |
|
||||
| Pharma Negotiation | 85% | As needed | 2024 | Active (green) |
|
||||
| Team Development | 82% | As needed | 2017 | Active (green) |
|
||||
+-------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
## Column Definitions
|
||||
|
||||
| Column | PMR Meaning | CV Mapping |
|
||||
|--------|------------|------------|
|
||||
| Drug Name | Medication name | Skill name |
|
||||
| Dose | Dosage strength | Proficiency percentage |
|
||||
| Frequency | How often taken | How often the skill is used (Daily / Weekly / Monthly / As needed) |
|
||||
| Start | Date prescribed | Year Andy started using this skill (approximate) |
|
||||
| Status | Active / Stopped | Active (green dot) for current skills, Historical (gray dot) for deprecated skills |
|
||||
|
||||
## Medication Categories (tabs within the view)
|
||||
|
||||
Skills are grouped into three "medication types," mimicking how clinical systems separate regular, acute, and PRN medications:
|
||||
|
||||
- **Active Medications** = Technical skills (the "regular medications" — taken daily, core to function)
|
||||
- **Clinical Medications** = Healthcare domain skills (the specialist prescriptions)
|
||||
- **PRN (As Required)** = Strategic & leadership skills (used situationally, not daily)
|
||||
|
||||
## Table Styling
|
||||
|
||||
- Table headers: [UI font] 600, 13px, uppercase, gray-400, `#F9FAFB` background
|
||||
- Table rows: alternating `#FFFFFF` / `#F9FAFB` backgrounds
|
||||
- Row height: 40px
|
||||
- All borders: `1px solid #E5E7EB`
|
||||
- Hover state: row background changes to `#EFF6FF` (subtle blue tint)
|
||||
- Status dots: 6px circles, inline with status text
|
||||
|
||||
## Interaction — Prescribing History
|
||||
|
||||
Clicking any medication/skill row expands it downward to show a "prescribing history" — a mini-timeline of how the skill developed:
|
||||
|
||||
```
|
||||
Python | 90% | Daily | 2017 | Active (green)
|
||||
|-- Prescribing History:
|
||||
2017 Started: Self-taught for data analysis automation
|
||||
2019 Increased: Dashboard development, data pipeline work
|
||||
2022 Specialist use: Blueteq automation, Sankey analysis tools
|
||||
2024 Advanced: Switching algorithm (14,000 patients), CD monitoring
|
||||
2025 Current: Population-level analytics, incentive scheme automation
|
||||
```
|
||||
|
||||
The history entries are styled in Geist Mono, 12px, with year markers as bold anchors and descriptions in regular weight. This "prescribing history" shows skill progression in a format that clinicians understand intuitively.
|
||||
|
||||
## Sortable Columns
|
||||
|
||||
Table columns are sortable by clicking the header. Clicking "Dose" sorts by proficiency descending. Clicking "Start" sorts chronologically. A small sort indicator arrow appears in the active sort column header. Default sort: by category grouping.
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance
|
||||
|
||||
### Aesthetic Direction
|
||||
|
||||
**Clinical Luxury**
|
||||
|
||||
The medications-as-skills metaphor uses the *structure* of an active medications list — tabs, table layout, status indicators, prescribing history — but executed with premium refinement. Layered shadows on cards, generous spacing, refined typography. Light-mode only.
|
||||
|
||||
**Purpose:** Present 18 professional skills as an active medications list. Clinicians recognize the format; recruiters get navigable, information-dense content.
|
||||
|
||||
**Tone:** Precise, structured, refined. Tables and borders provide clinical authenticity; shadows, typography, and spacing provide the luxury finish. Clinical systems are designed for rapid information retrieval under time pressure — that same quality makes this an efficient skills display.
|
||||
|
||||
**Constraints:**
|
||||
- Light-mode ONLY
|
||||
- NHS blue `#005EB8` as the sole accent color
|
||||
- Border radius 4px for clinical elements
|
||||
- [UI font] for all text (Elvaro or Blumir — see CLAUDE.md), Geist Mono for prescribing history data
|
||||
- Borders `1px solid #E5E7EB` on tables
|
||||
- Card surfaces with multi-layered shadows per design system
|
||||
|
||||
**Differentiation:** The medications-as-skills mapping provides richer data than any traditional "skills list." Dose maps to proficiency, Frequency maps to usage patterns, Start maps to when the skill was acquired, and prescribing history shows the skill's evolution over time. Genuinely useful information architecture.
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
#### 1. Three Category Tabs
|
||||
- **"Active Medications"** (8 technical skills), **"Clinical Medications"** (6 healthcare domain skills), **"PRN (As Required)"** (4 strategic/leadership skills)
|
||||
- Active tab: white background + NHS blue (`#005EB8`) 2px bottom border
|
||||
- Inactive tabs: `#F9FAFB` background, gray text, hover brightens to white
|
||||
- Count badges show the number of items per category
|
||||
- Full ARIA `role="tablist"`, `role="tab"`, `aria-selected`, `aria-controls` semantics
|
||||
|
||||
#### 2. Semantic HTML Table
|
||||
- Proper `<table>`, `<thead>`, `<th scope="col">`, `<tbody>`, `<tr>`, `<td>` markup
|
||||
- Five columns: Drug Name, Dose, Frequency, Start, Status
|
||||
- Headers: [UI font] 600, 13px, uppercase, 0.03em tracking, `#F9FAFB` background
|
||||
- Row height: 40px
|
||||
- Alternating `#FFFFFF` / `#F9FAFB` row backgrounds via CSS `:nth-child(even)`
|
||||
- Hover state: `#EFF6FF` (subtle blue tint)
|
||||
- Status dots: 6px green circles inline with "Active" text
|
||||
- All borders: `1px solid #E5E7EB`
|
||||
|
||||
#### 3. Sortable Columns
|
||||
- Click any header to sort (ascending/descending toggle)
|
||||
- ChevronUp, ChevronDown, or ChevronsUpDown indicator in header
|
||||
- Sorting logic handles string, numeric (dose %), and date (year) columns
|
||||
- Default: no sort (original order preserved)
|
||||
|
||||
#### 4. Expandable Prescribing History
|
||||
- Click any row (or arrow at row end) to expand
|
||||
- Uses Framer Motion `<AnimatePresence>` for smooth height animation (0.2s)
|
||||
- History entries styled in Geist Mono 12px
|
||||
- Year markers bold, descriptions regular weight
|
||||
- Format: `2017 Started: Self-taught for data analysis automation`
|
||||
- Vertical timeline with connecting line on left
|
||||
|
||||
#### 5. Mobile: Card Layout
|
||||
- Below 640px: Table hidden, cards displayed
|
||||
- Each card is a bordered block with stacked key-value pairs
|
||||
- No horizontal scroll required
|
||||
- Same expandable history behavior
|
||||
|
||||
### Implementation Patterns / Code Snippets
|
||||
|
||||
#### Types
|
||||
```typescript
|
||||
interface MedicationEntry {
|
||||
drugName: string
|
||||
dose: string
|
||||
frequency: string
|
||||
start: string
|
||||
status: 'Active'
|
||||
prescribingHistory: PrescribingEvent[]
|
||||
}
|
||||
|
||||
interface PrescribingEvent {
|
||||
year: string
|
||||
label: string
|
||||
description: string
|
||||
}
|
||||
|
||||
type MedicationCategory = 'active' | 'clinical' | 'prn'
|
||||
```
|
||||
|
||||
#### Tab Implementation
|
||||
```typescript
|
||||
const tabs: { key: MedicationCategory; label: string }[] = [
|
||||
{ key: 'active', label: 'Active Medications' },
|
||||
{ key: 'clinical', label: 'Clinical Medications' },
|
||||
{ key: 'prn', label: 'PRN (As Required)' },
|
||||
]
|
||||
|
||||
{tabs.map(({ key, label }) => (
|
||||
<button
|
||||
key={key}
|
||||
role="tab"
|
||||
aria-selected={category === key}
|
||||
aria-controls={`${key}-panel`}
|
||||
onClick={() => setCategory(key)}
|
||||
className={cn(
|
||||
'px-4 py-2 font-inter text-sm font-medium transition-colors',
|
||||
category === key
|
||||
? 'bg-white text-[#005EB8] border-b-2 border-[#005EB8]'
|
||||
: 'bg-[#F9FAFB] text-gray-600 hover:bg-white'
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
```
|
||||
|
||||
#### Table Row with Expand
|
||||
```typescript
|
||||
<tr
|
||||
onClick={() => toggleExpanded(drugName)}
|
||||
className="h-[40px] border-b border-[#E5E7EB] cursor-pointer transition-colors hover:bg-[#EFF6FF]"
|
||||
>
|
||||
<td className="px-4 py-2 text-sm font-medium text-gray-900">
|
||||
{drugName}
|
||||
</td>
|
||||
{/* ... other cells ... */}
|
||||
</tr>
|
||||
|
||||
<AnimatePresence>
|
||||
{isExpanded && (
|
||||
<motion.tr
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<td colSpan={5} className="px-4 py-3 bg-[#F9FAFB]">
|
||||
<div className="font-mono text-xs space-y-1">
|
||||
{prescribingHistory.map((event) => (
|
||||
<div key={event.year} className="flex gap-3">
|
||||
<span className="font-bold text-gray-700">{event.year}</span>
|
||||
<span className="text-gray-600">{event.label}:</span>
|
||||
<span className="text-gray-500">{event.description}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
</motion.tr>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
```
|
||||
|
||||
#### Sort Indicator
|
||||
```typescript
|
||||
const SortIndicator = ({ column }: { column: SortColumn }) => {
|
||||
if (sort.column !== column) {
|
||||
return <ChevronsUpDown className="w-3.5 h-3.5 text-gray-400" />
|
||||
}
|
||||
return sort.direction === 'asc'
|
||||
? <ChevronUp className="w-3.5 h-3.5 text-[#005EB8]" />
|
||||
: <ChevronDown className="w-3.5 h-3.5 text-[#005EB8]" />
|
||||
}
|
||||
```
|
||||
|
||||
#### Status Dot
|
||||
```typescript
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-green-500" />
|
||||
<span className="text-sm text-gray-700">Active</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Tailwind Classes Summary
|
||||
- **Container:** `border border-[#E5E7EB] rounded`
|
||||
- **Table headers:** `bg-[#F9FAFB] text-xs font-semibold uppercase tracking-wide text-gray-600`
|
||||
- **Row hover:** `hover:bg-[#EFF6FF]`
|
||||
- **Alternating rows:** `even:bg-[#F9FAFB] bg-white`
|
||||
- **Tab active:** `bg-white text-[#005EB8] border-b-2 border-[#005EB8]`
|
||||
- **Tab inactive:** `bg-[#F9FAFB] text-gray-600 hover:bg-white`
|
||||
- **Mono text:** `font-mono text-xs`
|
||||
@@ -1,198 +0,0 @@
|
||||
# Reference: Problems View (= Achievements / Challenges)
|
||||
|
||||
> Extracted from goal.md — Problems View section. Career achievements framed as clinical problems that were identified, treated, and resolved.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The "Problems" list in a clinical record tracks diagnoses — conditions that were identified, treated, and either resolved or require ongoing management. This maps perfectly to career achievements: challenges that Andy identified and resolved.
|
||||
|
||||
## Two Sections: Active Problems and Resolved Problems
|
||||
|
||||
### Active Problems (current / ongoing)
|
||||
|
||||
```
|
||||
+--[ Active Problems ]----------------------------------------------------+
|
||||
| Status | Code | Problem | Since |
|
||||
|--------+-----------+--------------------------------------+------------|
|
||||
| AMB | [MGT001] | 220M prescribing budget oversight | Jul 2024 |
|
||||
| GRN | [TRN001] | Patient-level SQL transformation | 2025 |
|
||||
| AMB | [LEA001] | Team data literacy programme | Jul 2024 |
|
||||
+-------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
### Resolved Problems (past achievements)
|
||||
|
||||
```
|
||||
+--[ Resolved Problems ]--------------------------------------------------+
|
||||
| Status | Code | Problem | Resolved | Outcome |
|
||||
|--------+-----------+--------------------------------+-----------+------------------------------------------|
|
||||
| GRN | [EFF001] | Manual prescribing analysis | Oct 2025 | Python algorithm: 14,000 pts, 2.6M/yr |
|
||||
| | | inefficiency | | |
|
||||
| GRN | [EFF002] | 14.6M efficiency target | Oct 2025 | Over-target performance achieved |
|
||||
| GRN | [AUT001] | Blueteq form creation backlog | 2023 | 70% reduction, 200hrs saved |
|
||||
| GRN | [INN001] | Asthma screening scalability | 2019 | National rollout: ~300 branches, ~1M |
|
||||
| GRN | [AUT002] | Incentive scheme manual calc. | 2025 | Automated: 50% Rx reduction in 2 months |
|
||||
| GRN | [DAT001] | HCD spend tracking gaps | 2023 | Blueteq-secondary care data integration |
|
||||
| GRN | [VIS001] | Patient pathway opacity | 2023 | Sankey chart analysis tool |
|
||||
| GRN | [MON001] | Population opioid exposure | 2024 | CD monitoring system: OME tracking |
|
||||
| | | monitoring | | |
|
||||
+-------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
## Column Definitions
|
||||
|
||||
| Column | Meaning |
|
||||
|--------|---------|
|
||||
| Status | Traffic light: Green (resolved), Amber (in progress / active), Red (urgent — unused, reserved) |
|
||||
| Code | SNOMED-style reference code. Fictional but internally consistent. Formatted in Geist Mono. |
|
||||
| Problem | The challenge or opportunity Andy identified |
|
||||
| Resolved | Date or year the problem was resolved |
|
||||
| Outcome | Brief description of the resolution and its measurable impact |
|
||||
|
||||
## Expandable Rows
|
||||
|
||||
Each problem row can be expanded to show a full narrative: what the problem was, how Andy approached it, what tools/methods were used, and the quantified outcome. The expanded state also shows "linked consultations" — clicking a link navigates to the relevant entry in Consultations view.
|
||||
|
||||
## Traffic Light Status Indicators
|
||||
|
||||
Traffic lights are 8px circles with the status colors (green, amber, red, gray). They appear inline before the code column. This is exactly how clinical systems indicate problem severity/status — it's an immediately scannable visual language.
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance
|
||||
|
||||
### Aesthetic Direction
|
||||
|
||||
**Clinical Luxury** — The Problems view uses the clinical structure of a problem list (traffic lights, coded entries, expandable narratives) but executes with premium refinement. White card surfaces with layered shadows, generous padding, refined typography. The visual power comes from the *content structure* — traffic light dots and expandable narratives do the heavy lifting — while the luxury finish makes it feel polished and intentional.
|
||||
|
||||
The distinctiveness comes from the *concept itself* — framing career achievements as a Problem List is the creative act. The premium execution makes it memorable.
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
1. **Traffic Light Status Indicators (WCAG Critical)**
|
||||
- 8px circles: green (`#22C55E`) for resolved, amber (`#F59E0B`) for in-progress
|
||||
- **MUST ALWAYS be paired with text labels** — never dots alone (WCAG 1.4.1 requirement)
|
||||
- Each status shows both the colored dot AND the text label (e.g., "● Resolved", "● In Progress")
|
||||
- Implementation uses flexbox with gap-2 for dot-label pairing
|
||||
|
||||
2. **Typography System**
|
||||
- **[UI font]** for all body text, headers, and UI labels (Elvaro or Blumir — see CLAUDE.md)
|
||||
- **Geist Mono** for codes and dates — SNOMED-style codes like `[EFF001]`, `[MGT001]` must be monospace
|
||||
- Font sizes: 13px for table headers (uppercase, tracking-wider), 14px for body text
|
||||
- Header styling: `font-ui font-semibold text-xs uppercase tracking-wider text-gray-400`
|
||||
|
||||
3. **Color Palette (Locked)**
|
||||
- Light-mode ONLY
|
||||
- NHS Blue: `#005EB8` (Tailwind `text-pmr-nhsblue`) — used for links and accents
|
||||
- Borders: `1px solid #E5E7EB` (gray-200) — consistent table borders
|
||||
- Row hover: `#EFF6FF` (blue-50) — subtle highlight
|
||||
- Background: White cards on `#F5F7FA` (pmr-content) background with layered shadows per design system
|
||||
- Border radius: 4px for clinical elements
|
||||
|
||||
4. **Table Structure**
|
||||
- Semantic HTML: `<table>`, `<thead>`, `<th scope="col">`, `<tbody>`, `<tr>`, `<td>`
|
||||
- Two separate tables: Active Problems (4 columns) and Resolved Problems (6 columns)
|
||||
- Column widths fixed for Status (w-28), Code (w-28), Since/Resolved (w-28)
|
||||
- Alternating row backgrounds not used — clean white with hover state only
|
||||
|
||||
5. **Expandable Rows Pattern**
|
||||
- Chevron icon in rightmost column indicates expandability
|
||||
- Expanded content shows in a full-width sub-row below
|
||||
- Animation: height transition 200ms ease-out (respects prefers-reduced-motion)
|
||||
- Expanded background: `#F9FAFB` (gray-50) with narrative text and linked consultations
|
||||
|
||||
6. **Mobile Layout**
|
||||
- Card-based layout below breakpoint (isMobile from useBreakpoint hook)
|
||||
- Each problem becomes a rounded card with stacked information
|
||||
- Status and code on same line, problem description prominent
|
||||
- Expandable via button press, showing narrative and linked consultations
|
||||
|
||||
### Implementation Patterns
|
||||
|
||||
**TrafficLight Component (WCAG Compliant):**
|
||||
```tsx
|
||||
function TrafficLight({ status }: { status: ProblemStatus }) {
|
||||
const colorMap: Record<ProblemStatus, { bg: string; label: string }> = {
|
||||
Active: { bg: 'bg-green-500', label: 'Active' },
|
||||
'In Progress': { bg: 'bg-amber-500', label: 'In Progress' },
|
||||
Resolved: { bg: 'bg-green-500', label: 'Resolved' },
|
||||
}
|
||||
|
||||
const { bg, label } = colorMap[status]
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`w-2 h-2 rounded-full ${bg}`}
|
||||
aria-label={`Status: ${label}`}
|
||||
role="img"
|
||||
/>
|
||||
<span className="text-xs text-gray-600">{label}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Code Column (Geist Mono):**
|
||||
```tsx
|
||||
<td className="border border-gray-200 px-3 py-2.5">
|
||||
<span className="font-mono text-xs text-gray-500">[{problem.code}]</span>
|
||||
</td>
|
||||
```
|
||||
|
||||
**Row Hover Effect:**
|
||||
```tsx
|
||||
<tr className={`cursor-pointer hover:bg-blue-50 transition-colors ${
|
||||
isExpanded ? 'bg-blue-50' : ''
|
||||
}`}>
|
||||
```
|
||||
|
||||
**Expandable Row Animation:**
|
||||
```tsx
|
||||
<div
|
||||
style={{
|
||||
height: isExpanded ? contentHeight : 0,
|
||||
overflow: 'hidden',
|
||||
transition: prefersReducedMotion ? 'none' : 'height 200ms ease-out',
|
||||
}}
|
||||
>
|
||||
<div ref={contentRef} className="bg-gray-50 p-4">
|
||||
{/* Narrative content */}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Linked Consultations Navigation:**
|
||||
```tsx
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleLinkedClick(consultation.id)
|
||||
}}
|
||||
className="inline-flex items-center gap-1 text-xs text-pmr-nhsblue hover:underline"
|
||||
>
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
{consultation.organization} — {consultation.role}
|
||||
</button>
|
||||
```
|
||||
|
||||
### Mobile Card Layout
|
||||
|
||||
On mobile devices (`isMobile` from useBreakpoint hook), the table transforms into cards:
|
||||
- White background cards with `border border-gray-200 rounded`
|
||||
- Status dot + code on one line
|
||||
- Problem description as card title
|
||||
- Since/Resolved date below
|
||||
- Chevron indicates expandability
|
||||
- Expanded state shows narrative and linked consultations below
|
||||
|
||||
### Accessibility Requirements
|
||||
|
||||
1. **WCAG 1.4.1 Use of Color**: Never rely on color alone — traffic lights MUST have text labels
|
||||
2. **Semantic HTML**: Proper `<table>` structure with `<th scope="col">` for headers
|
||||
3. **ARIA**: `aria-expanded` on toggle buttons, `aria-label` on status dots
|
||||
4. **Motion**: Respect `prefers-reduced-motion` for expand/collapse animations
|
||||
5. **Focus management**: Linked consultation buttons are keyboard navigable
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
---
|
||||
|
||||
## Design Guidance
|
||||
|
||||
### Aesthetic Direction
|
||||
|
||||
**Tone: Clinical Luxury** — A contact form styled as a clinical referral, with the *structure* of an NHS referral form (priority levels, reference numbers, clinical fields) but executed with premium refinement. The humor comes from the deadpan application of clinical form conventions to a personal contact form. The beauty is in the precision of the grid, the crispness of the type hierarchy, refined inputs, and the tongue-in-cheek seriousness of it all.
|
||||
|
||||
**What makes it memorable**: The collision between clinical form structure and the fact that you are "referring" to a person's contact page. The pre-filled "patient" header, the priority radio buttons with their wry tooltips, the reference number generation — all of this is a joke delivered with a completely straight face, in a beautifully finished package.
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
**Priority Radio Buttons (urgent/routine/2-week-wait)**
|
||||
|
||||
| Priority | Color | Tooltip |
|
||||
|----------|-------|---------|
|
||||
| Urgent | Red (`#EF4444`) | "All enquiries are welcome, urgent or not." |
|
||||
| Routine | NHS Blue (`#005EB8`) | (default, no tooltip needed) |
|
||||
| Two-Week Wait | Amber (`#F59E0B`) | "NHS cancer referral pathway — this isn't that, but the spirit of promptness applies." |
|
||||
|
||||
Each priority option features a colored dot indicator and supports hover tooltips via a tooltip component pattern.
|
||||
|
||||
**Form Validation Patterns**
|
||||
|
||||
- Real-time validation on blur
|
||||
- Error messages appear below invalid fields in red (`text-red-600`)
|
||||
- Disabled submit button until required fields are valid
|
||||
- Required fields: Referrer Name, Referrer Email
|
||||
- Email validation uses standard pattern matching
|
||||
- Organization field is optional
|
||||
|
||||
**Design System Constraints (Locked)**
|
||||
|
||||
| Token | Value | Tailwind Class |
|
||||
|-------|-------|----------------|
|
||||
| NHS Blue | `#005EB8` | `text-pmr-nhsblue` / `bg-pmr-nhsblue` |
|
||||
| Card border | `1px solid #E5E7EB` | `border-pmr-border` |
|
||||
| Input border | `1px solid #D1D5DB` | `border-pmr-border-dark` |
|
||||
| Border radius | `4px` | `rounded` |
|
||||
| Label font | [UI font] 500, 13px, gray-600 | `font-ui font-medium text-sm text-gray-600` |
|
||||
| Mono font | Geist Mono | `font-mono` (reference numbers) |
|
||||
| Input padding | `8px 12px` | `py-2 px-3` |
|
||||
| Focus state | NHS blue border + glow | `focus:border-pmr-nhsblue focus:ring-2 focus:ring-pmr-nhsblue/15` |
|
||||
|
||||
### Implementation Patterns/Code Snippets
|
||||
|
||||
**Priority Option Component Pattern**
|
||||
|
||||
```tsx
|
||||
type Priority = 'urgent' | 'routine' | 'two-week-wait'
|
||||
|
||||
const dotColors: Record<Priority, string> = {
|
||||
urgent: 'bg-red-500',
|
||||
routine: 'bg-pmr-nhsblue',
|
||||
'two-week-wait': 'bg-amber-500',
|
||||
}
|
||||
|
||||
const labelColors: Record<Priority, string> = {
|
||||
urgent: 'text-red-600',
|
||||
routine: 'text-pmr-nhsblue',
|
||||
'two-week-wait': 'text-amber-600',
|
||||
}
|
||||
```
|
||||
|
||||
**Form Input Styling Pattern**
|
||||
|
||||
```tsx
|
||||
// Standard clinical form input
|
||||
<input
|
||||
className="w-full px-3 py-2 border border-pmr-border-dark rounded
|
||||
text-sm text-gray-900 placeholder-gray-400
|
||||
focus:outline-none focus:border-pmr-nhsblue
|
||||
focus:ring-2 focus:ring-pmr-nhsblue/15
|
||||
transition-all duration-200"
|
||||
/>
|
||||
|
||||
// Label styling
|
||||
<label className="block text-sm font-medium text-gray-600 mb-1.5 font-inter">
|
||||
Field Label
|
||||
</label>
|
||||
```
|
||||
|
||||
**Reference Number Generation**
|
||||
|
||||
```tsx
|
||||
function generateRefNumber(): string {
|
||||
const now = new Date()
|
||||
const year = now.getFullYear()
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(now.getDate()).padStart(2, '0')
|
||||
const seq = String(Math.floor(Math.random() * 999) + 1).padStart(3, '0')
|
||||
return `REF-${year}-${month}${day}-${seq}`
|
||||
}
|
||||
```
|
||||
|
||||
**Form State Management Pattern**
|
||||
|
||||
```tsx
|
||||
interface FormData {
|
||||
priority: Priority
|
||||
referrerName: string
|
||||
referrerEmail: string
|
||||
referrerOrg: string
|
||||
reason: string
|
||||
contactMethod: ContactMethod
|
||||
}
|
||||
|
||||
interface FormErrors {
|
||||
referrerName?: string
|
||||
referrerEmail?: string
|
||||
}
|
||||
|
||||
// Validation on submit
|
||||
const validate = (): boolean => {
|
||||
const errors: FormErrors = {}
|
||||
if (!formData.referrerName.trim()) {
|
||||
errors.referrerName = 'Referrer name is required'
|
||||
}
|
||||
if (!formData.referrerEmail.trim()) {
|
||||
errors.referrerEmail = 'Email is required'
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.referrerEmail)) {
|
||||
errors.referrerEmail = 'Please enter a valid email'
|
||||
}
|
||||
setErrors(errors)
|
||||
return Object.keys(errors).length === 0
|
||||
}
|
||||
```
|
||||
|
||||
**Success State Pattern**
|
||||
|
||||
```tsx
|
||||
// After form submission
|
||||
<div className="text-center py-8">
|
||||
<CheckCircle className="w-12 h-12 text-green-500 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-1">
|
||||
Referral sent successfully
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 font-mono mb-1">
|
||||
Reference: {refNumber}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
Expected response time: 24-48 hours
|
||||
</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Contact Method Radio Pattern**
|
||||
|
||||
```tsx
|
||||
type ContactMethod = 'email' | 'phone' | 'linkedin'
|
||||
|
||||
// Radio button with icon
|
||||
<div className="flex items-center gap-3 p-3 border border-pmr-border-dark rounded
|
||||
cursor-pointer hover:bg-gray-50 transition-colors">
|
||||
<input type="radio" className="sr-only" />
|
||||
<Mail className="w-4 h-4 text-gray-500" />
|
||||
<span className="text-sm text-gray-700">Email</span>
|
||||
</div>
|
||||
```
|
||||
@@ -1,309 +0,0 @@
|
||||
# Reference: Summary View + Clinical Alert
|
||||
|
||||
> Extracted from goal.md — Summary View and Clinical Alert sections. This is the landing view after login.
|
||||
|
||||
---
|
||||
|
||||
## Summary View
|
||||
|
||||
The landing view after login. This mimics the "Patient Summary" screen — the first screen a clinician sees when opening a patient record, showing the most important information at a glance.
|
||||
|
||||
**Layout:** A grid of summary cards arranged in a 2-column layout on desktop, single column on mobile. Each card has a header bar with the card title in [UI font] 600, 14px, uppercase, on a `#F9FAFB` background with `1px solid #E5E7EB` bottom border.
|
||||
|
||||
### Card 1: Patient Demographics (spans full width)
|
||||
|
||||
```
|
||||
+--[ Patient Demographics ]------------------------------------------+
|
||||
| Name: Andrew Charlwood Status: Active (dot) |
|
||||
| DOB: 14 February 1993 Location: Norwich, UK |
|
||||
| Registration: GPhC 2211810 Since: August 2016 |
|
||||
| Qualification: MPharm (Hons) 2:1 University: UEA, 2015 |
|
||||
+---------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
A two-column key-value table. Labels in [UI font] 500, 13px, gray-500. Values in [UI font] 400, 14px, gray-900. Labels right-aligned, values left-aligned — clinical form layout.
|
||||
|
||||
### Card 2: Active Problems (left column)
|
||||
|
||||
```
|
||||
+--[ Active Problems ]-----------------------------------------------+
|
||||
| (green dot) Deputy Head, Pop. Health & Data Analysis Jul 2024-Present |
|
||||
| NHS Norfolk & Waveney ICB |
|
||||
| (green dot) 220M prescribing budget management Ongoing |
|
||||
| (amber dot) Patient-level SQL analytics transformation In progress |
|
||||
+---------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
A list with green dots for active/current items, amber dots for in-progress items. Each entry has a title in [UI font] 500, 14px, and a date range or status in Geist Mono, 12px, right-aligned. Click an entry to navigate to the corresponding Consultation.
|
||||
|
||||
### Card 3: Current Medications — Quick View (right column)
|
||||
|
||||
```
|
||||
+--[ Current Medications (Quick View) ]-------------------------------+
|
||||
| Python | 90% | Daily | Active (green dot) |
|
||||
| SQL | 88% | Daily | Active (green dot) |
|
||||
| Power BI | 92% | Daily | Active (green dot) |
|
||||
| Data Analysis | 95% | Daily | Active (green dot) |
|
||||
| JS / TypeScript | 70% | Weekly | Active (green dot) |
|
||||
| [View Full List ->] |
|
||||
+---------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
A compact 4-column table showing the top 5 skills. "View Full List" links to the Medications view. Table headers are uppercase, 12px, gray-400. Table rows alternate between `#FFFFFF` and `#F9FAFB` backgrounds.
|
||||
|
||||
### Card 4: Last Consultation (spans full width)
|
||||
|
||||
```
|
||||
+--[ Last Consultation ]----------------------------------------------+
|
||||
| Date: May 2025 Clinician: A. Charlwood Location: NHS N&W ICB |
|
||||
| |
|
||||
| Interim Head, Population Health & Data Analysis |
|
||||
| Led strategic delivery of population health initiatives and |
|
||||
| data-driven medicines optimisation across Norfolk & Waveney ICS... |
|
||||
| [View Full Record ->] |
|
||||
+---------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
A preview of the most recent role, truncated to 2-3 lines. "View Full Record" navigates to Consultations with that entry expanded.
|
||||
|
||||
### Card 5: Alerts (full width, positioned above all other cards)
|
||||
|
||||
This is the Clinical Alert — see below.
|
||||
|
||||
---
|
||||
|
||||
## The Clinical Alert (Signature Interaction)
|
||||
|
||||
When the user first loads the Summary view (immediately after the login transition), a clinical alert banner slides down from beneath the patient banner.
|
||||
|
||||
### Alert Styling
|
||||
|
||||
```
|
||||
+--[ WARNING CLINICAL ALERT ]------------------------------------------+
|
||||
| WARNING ALERT: This patient has identified 14.6M in prescribing |
|
||||
| efficiency savings across Norfolk & Waveney ICS. |
|
||||
| [Acknowledge]|
|
||||
+----------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
- Background: amber (`#FEF3C7` — amber-100, light amber)
|
||||
- Left border: 4px solid `#F59E0B` (amber-500)
|
||||
- Warning icon: `AlertTriangle` from Lucide, amber-600
|
||||
- Text: [UI font] 500, 14px, `#92400E` (amber-800)
|
||||
- "Acknowledge" button: small outlined button, amber border and text
|
||||
|
||||
### Behavior
|
||||
|
||||
1. The alert slides down from beneath the patient banner with a spring animation (250ms, slight overshoot) after the PMR interface finishes materializing.
|
||||
2. It pushes the Summary content downward, so it's impossible to miss.
|
||||
3. Clicking "Acknowledge" triggers a brief animation: a green checkmark replaces the warning icon (200ms), then the alert collapses upward (200ms, ease-out) and is gone.
|
||||
4. The dismiss state is stored in React state (session-only) — refreshing the page shows the alert again.
|
||||
|
||||
### Why This Works
|
||||
|
||||
Clinical alerts are the mechanism that clinical systems use to put critical information in front of clinicians before they do anything else. They are the highest-priority information in the system. By framing Andy's most impressive metric ("14.6M") as a clinical alert, it gets the same treatment — it's the first thing the user reads, it demands acknowledgment, and its format gives the number institutional weight. This is not a boast in a paragraph; it's a system-generated alert based on data. The framing makes the achievement feel objective.
|
||||
|
||||
### Second Alert (on Consultations view)
|
||||
|
||||
When the user first navigates to Consultations, a secondary alert appears:
|
||||
|
||||
```
|
||||
WARNING NOTE: Patient has developed a Python-based switching algorithm
|
||||
identifying 14,000 patients for cost-effective medication alternatives.
|
||||
2.6M annual savings potential. Review recommended.
|
||||
```
|
||||
|
||||
This second alert reinforces the key technical achievement in clinical language. It appears only once (on first navigation to Consultations) and is dismissible with the same "Acknowledge" interaction.
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance
|
||||
|
||||
### Aesthetic Direction
|
||||
|
||||
**Clinical Luxury**
|
||||
|
||||
The Summary view and Clinical Alert use clinical structure (card-based summary, status dots, coded entries, alert banners) with premium execution. Key visual principles:
|
||||
|
||||
- **Light-mode ONLY**
|
||||
- **NHS blue (#005EB8)** — The accent color for headers and accents
|
||||
- **Card-based architecture** — All information lives in contained, bordered cards with layered shadows (per design system)
|
||||
- **Monospace for data** — Geist Mono for all coded entries, dates, and numerical values (clinical texture)
|
||||
- **Generous but structured** — More whitespace than a real clinical system. Cards have 16-24px padding. Content breathes.
|
||||
- **Status dots** — Green/amber/red traffic light indicators for at-a-glance status assessment
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
**1. Spring Animation for Alert Slide-Down**
|
||||
|
||||
The Clinical Alert uses a spring animation (Framer Motion `type: 'spring'`) rather than ease-out. This creates a subtle overshoot effect that feels "alive" — mimicking how real clinical alerts materialize in systems like EMIS or SystmOne.
|
||||
|
||||
```
|
||||
Initial state: y: -100%, opacity: 0
|
||||
Animate to: y: 0, opacity: 1
|
||||
type: 'spring', stiffness: 300, damping: 25
|
||||
```
|
||||
|
||||
**2. Acknowledge → Checkmark → Collapse Sequence**
|
||||
|
||||
The dismissal interaction follows a deliberate three-phase sequence:
|
||||
|
||||
1. **Acknowledge click** (0ms) — Button triggers dismissal state
|
||||
2. **Icon cross-fade** (200ms) — AlertTriangle fades out, CheckCircle fades in (green-600)
|
||||
3. **Hold beat** (200ms) — Checkmark holds briefly to confirm action completion
|
||||
4. **Height collapse** (200ms ease-out) — Alert height animates to 0, content slides up
|
||||
|
||||
This sequence transforms dismissal from a jarring disappearance into a satisfying confirmation action.
|
||||
|
||||
**3. Typography Hierarchy**
|
||||
|
||||
- **Card headers**: [UI font] 600, 14px, uppercase, letter-spacing-wide — creates clear section delineation
|
||||
- **Labels**: [UI font] 500, 13px, gray-500, right-aligned — clinical form layout
|
||||
- **Values**: [UI font] 400, 14px, gray-900, left-aligned — primary data focus
|
||||
- **Coded values**: Geist Mono, 12px — all dates, IDs, percentages, status codes
|
||||
|
||||
### Implementation Patterns
|
||||
|
||||
**ClinicalAlert Component**
|
||||
|
||||
```typescript
|
||||
// State machine for alert lifecycle
|
||||
type AlertState = 'visible' | 'acknowledging' | 'dismissing' | 'dismissed'
|
||||
|
||||
// Props interface
|
||||
interface ClinicalAlertProps {
|
||||
variant: 'warning' | 'note'
|
||||
icon: typeof AlertTriangle | typeof Info
|
||||
message: string
|
||||
onDismiss: () => void
|
||||
storageKey?: string // For session persistence
|
||||
}
|
||||
|
||||
// Animation variants
|
||||
const alertVariants = {
|
||||
hidden: { y: '-100%', opacity: 0 },
|
||||
visible: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
transition: { type: 'spring', stiffness: 300, damping: 25 }
|
||||
},
|
||||
exit: {
|
||||
height: 0,
|
||||
opacity: 0,
|
||||
transition: { duration: 0.2, ease: 'easeOut' }
|
||||
}
|
||||
}
|
||||
|
||||
const iconVariants = {
|
||||
warning: { scale: 1, opacity: 1 },
|
||||
acknowledged: {
|
||||
scale: [1, 1.1, 1],
|
||||
opacity: [1, 0],
|
||||
transition: { duration: 0.2 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**SummaryView Component**
|
||||
|
||||
```typescript
|
||||
// Grid layout structure
|
||||
const layoutConfig = {
|
||||
container: 'grid grid-cols-1 md:grid-cols-2 gap-4',
|
||||
demographics: 'col-span-full', // Spans both columns
|
||||
problems: 'col-span-1',
|
||||
medications: 'col-span-1',
|
||||
consultation: 'col-span-full'
|
||||
}
|
||||
|
||||
// Card header pattern
|
||||
const CardHeader = ({ title }: { title: string }) => (
|
||||
<div className="bg-[#F9FAFB] border-b border-[#E5E7EB] px-4 py-3">
|
||||
<h3 className="font-inter font-semibold text-sm uppercase tracking-wide">
|
||||
{title}
|
||||
</h3>
|
||||
</div>
|
||||
)
|
||||
|
||||
// Key-value row pattern (for Demographics)
|
||||
interface KeyValueRowProps {
|
||||
label: string
|
||||
value: string
|
||||
isMono?: boolean
|
||||
}
|
||||
|
||||
const KeyValueRow = ({ label, value, isMono }: KeyValueRowProps) => (
|
||||
<div className="grid grid-cols-[1fr_auto] gap-4 py-1">
|
||||
<span className="font-inter font-medium text-[13px] text-gray-500 text-right">
|
||||
{label}
|
||||
</span>
|
||||
<span className={`font-inter text-sm text-gray-900 text-left ${isMono ? 'font-geist-mono' : ''}`}>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
// Problem list pattern with traffic lights
|
||||
interface ProblemItemProps {
|
||||
status: 'active' | 'in-progress'
|
||||
title: string
|
||||
date: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const ProblemItem = ({ status, title, date, onClick }: ProblemItemProps) => (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className="flex items-center gap-3 py-2 hover:bg-gray-50 cursor-pointer transition-colors"
|
||||
>
|
||||
<div className={cn(
|
||||
'w-2 h-2 rounded-full',
|
||||
status === 'active' ? 'bg-green-500' : 'bg-amber-500'
|
||||
)} />
|
||||
<span className="flex-1 font-inter font-medium text-sm">{title}</span>
|
||||
<span className="font-geist-mono text-xs text-gray-500">{date}</span>
|
||||
</div>
|
||||
)
|
||||
```
|
||||
|
||||
**Animation Constants**
|
||||
|
||||
```typescript
|
||||
// Timing constants (ms)
|
||||
export const ANIMATION = {
|
||||
SPRING_DURATION: 250,
|
||||
ICON_CROSSFADE: 200,
|
||||
HOLD_BEAT: 200,
|
||||
COLLAPSE_DURATION: 200
|
||||
} as const
|
||||
|
||||
// Easing
|
||||
export const EASING = {
|
||||
spring: { type: 'spring', stiffness: 300, damping: 25 },
|
||||
easeOut: { ease: 'easeOut' }
|
||||
} as const
|
||||
```
|
||||
|
||||
### Color Palette
|
||||
|
||||
```css
|
||||
/* NHS System Colors */
|
||||
--nhs-blue: #005EB8;
|
||||
--nhs-light-blue: #41B6E6;
|
||||
|
||||
/* Alert Colors */
|
||||
--amber-100: #FEF3C7;
|
||||
--amber-500: #F59E0B;
|
||||
--amber-600: #D97706;
|
||||
--amber-800: #92400E;
|
||||
--green-500: #22C55E;
|
||||
--green-600: #16A34A;
|
||||
|
||||
/* UI Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-900: #111827;
|
||||
--border: #E5E7EB;
|
||||
```
|
||||
@@ -1,212 +0,0 @@
|
||||
# Reference: ECG Transition + Login Sequence
|
||||
|
||||
> Extracted from goal.md — ECG Transition section. This covers the flatline exit from the ECG animation and the immersive login sequence that bridges into the PMR interface.
|
||||
|
||||
---
|
||||
|
||||
## Starting Point
|
||||
|
||||
"ANDREW CHARLWOOD" is on screen in neon green (`#00ff41`) on black. The heartbeat trace is complete. The name is fully formed and glowing.
|
||||
|
||||
## Phase 1: The Flatline (600ms)
|
||||
|
||||
The neon green name holds for a beat (300ms). Then the glow around the letters begins to fade. Simultaneously, from the right edge of the name, a flatline trace extends rightward — a perfectly horizontal green line drawn at the baseline, extending across the remaining viewport width over 300ms. The visual reads as a patient monitor flatline. This is deliberate: the "patient" (the animation phase) is ending. A new record is about to open.
|
||||
|
||||
The flatline has a subtle audio-visual implication without actual sound — the green line is steady and unbroken, the glow around the name letters reduces to zero. The entire canvas is now: a fading green name with a horizontal flatline extending to the right edge. All on black.
|
||||
|
||||
## Phase 2: Screen Clear (400ms)
|
||||
|
||||
The entire canvas fades to black over 200ms (the name and flatline dissolve into darkness). Then, from black, the background transitions to a dark blue-gray (`#1E293B`) over 200ms. This is the color of a clinical system login screen — the dark institutional background that every NHS worker recognizes from their Monday morning.
|
||||
|
||||
## Phase 3: Login Sequence (user-paced)
|
||||
|
||||
A login panel materializes center-screen: a white card (320px wide, 12px border-radius, refined shadow) on the dark blue-gray background. The card contains:
|
||||
|
||||
- A small NHS-blue shield icon or generic clinical system logo at the top
|
||||
- **Username field**: Empty text input with label "Username". After 400ms, a cursor appears and types `A.CHARLWOOD` character by character at a natural reading pace (80ms per character, ~880ms total). The typing uses Geist Mono / monospace font.
|
||||
- **Password field**: After a 300ms pause, dots fill the password field at a deliberate pace (8 dots, 60ms each, ~480ms total).
|
||||
- **"Log In" button**: NHS blue (`#005EB8`), full width. After typing completes, the button becomes clearly available as a **user-interactive element**. The user clicks it to proceed. The button should have a visible hover state and feel like a natural call-to-action — this is the moment where the user "logs in" to the record.
|
||||
|
||||
**Important**: The login button is NOT auto-clicked. The user must click it. This creates a deliberate, satisfying interaction — the user is choosing to enter the record. On click, the button shows a brief pressed state (darkens slightly, 100ms), then...
|
||||
|
||||
## Phase 4: Interface Materialization (500ms)
|
||||
|
||||
The login card scales up slightly (103%) and fades out (200ms). As it fades, the full PMR interface fades in behind it:
|
||||
|
||||
1. **Patient banner** slides down from the top edge (200ms, ease-out)
|
||||
2. **Sidebar** slides in from the left edge (250ms, ease-out, starting 50ms after the banner)
|
||||
3. **Main content area** (Summary view) fades in (300ms, starting 100ms after sidebar begins)
|
||||
4. **Clinical alert banner** slides down from beneath the patient banner (250ms, spring easing, starting 200ms after main content appears)
|
||||
|
||||
## Phase 5: Final State
|
||||
|
||||
The full PMR interface is visible: patient banner at top, dark sidebar on left, Summary view in the main content area, and the clinical alert banner demanding attention. The user is now "logged in" to Andy's career record.
|
||||
|
||||
**Total transition duration:** ~2s for typing to complete, then user-paced (waits for button click), then ~500ms for interface materialization.
|
||||
|
||||
## Why This Works
|
||||
|
||||
The login sequence is the most immersive transition. Every NHS worker, every pharmacist, every GP recognizes the shape of a clinical login screen. This transition evokes that recognition — but executed with premium refinement rather than institutional austerity. The natural typing pace lets the user absorb what's happening. And the interactive login button is the pivotal moment: the user *chooses* to enter the record. That moment of agency makes the experience feel personal, not passive.
|
||||
|
||||
## Login Animation Implementation Notes
|
||||
|
||||
- Component mounts with dark blue-gray background
|
||||
- Login card fades in (Framer Motion, 200ms)
|
||||
- Username typing: `setInterval` adds one character per 80ms to a state string (~880ms total)
|
||||
- Password dots: `setInterval` adds one dot per 60ms (~480ms total)
|
||||
- After typing completes: button becomes interactive (opacity goes to 1, cursor: pointer)
|
||||
- **User clicks the "Log In" button** — this is NOT auto-triggered
|
||||
- On click: button shows pressed state (100ms), then `onComplete` callback fires
|
||||
- Typing respects `prefers-reduced-motion` — with reduced motion, full username and password appear instantly, button is immediately interactive
|
||||
- **Font: Geist Mono** for username/password fields (NOT Fira Code)
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance
|
||||
|
||||
### Aesthetic Direction: Clinical Luxury
|
||||
|
||||
The login card evokes the structure of a clinical system login — shield icon, two fields, a button — but executed with premium refinement. Clean white card with refined shadow, considered spacing, and the satisfying rhythm of credentials appearing at a natural pace. The recognition factor ("oh, this looks like a clinical login") is the creative hook; the premium finish is what makes it memorable.
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
1. **Active field focus ring**: NHS-blue border (`1px solid #005EB8`) on the currently active field, inactive fields shift to `#FAFAFA` background. Clinical login convention. Transition 150ms.
|
||||
2. **Refined card shadow**: Multi-layered shadow `0 1px 2px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.03)` — the card should feel like it floats above the dark background. Combined with `1px solid #E5E7EB` border.
|
||||
3. **Timer cleanup**: Track every `setInterval` and `setTimeout` via refs, clear all on unmount.
|
||||
4. **Consolidated active field state**: Single `activeField` state (`'username' | 'password' | 'done' | null`) instead of separate booleans. `'done'` state indicates typing is complete and button is ready.
|
||||
5. **Accessibility**: `role="status"` + `aria-label` on outer container. Cursor pipes `aria-hidden="true"`. Card entrance `scale: 0.98` (not 0).
|
||||
6. **User-initiated login**: After typing completes, the "Log In" button is clearly interactive. Hover state (slight darken), cursor: pointer, and the button should feel like an invitation to click. This is the one moment of user agency in the boot sequence — make it satisfying.
|
||||
7. **Natural typing pace**: 80ms/char for username, 60ms/dot for password. Deliberate and readable, not frantically fast.
|
||||
|
||||
### Implementation Pattern
|
||||
|
||||
```tsx
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
import { Shield } from 'lucide-react'
|
||||
|
||||
interface LoginScreenProps {
|
||||
onComplete: () => void
|
||||
}
|
||||
|
||||
// Key state
|
||||
const [username, setUsername] = useState('')
|
||||
const [passwordDots, setPasswordDots] = useState(0)
|
||||
const [showCursor, setShowCursor] = useState(true)
|
||||
const [activeField, setActiveField] = useState<'username' | 'password' | 'done' | null>('username')
|
||||
const [buttonPressed, setButtonPressed] = useState(false)
|
||||
const [isExiting, setIsExiting] = useState(false)
|
||||
const [typingComplete, setTypingComplete] = useState(false)
|
||||
|
||||
const fullUsername = 'A.CHARLWOOD'
|
||||
const passwordLength = 8
|
||||
```
|
||||
|
||||
Card structure:
|
||||
```tsx
|
||||
<motion.div
|
||||
className="bg-white"
|
||||
style={{
|
||||
width: '320px',
|
||||
padding: '32px',
|
||||
borderRadius: '12px',
|
||||
border: '1px solid #E5E7EB',
|
||||
boxShadow: '0 1px 2px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.03)',
|
||||
}}
|
||||
initial={{ opacity: 0, scale: 0.98 }}
|
||||
animate={isExiting ? { scale: 1.03, opacity: 0 } : { scale: 1, opacity: 1 }}
|
||||
transition={{ duration: 0.2, ease: 'easeOut' }}
|
||||
>
|
||||
```
|
||||
|
||||
Branding header:
|
||||
```tsx
|
||||
<div className="flex flex-col items-center" style={{ marginBottom: '28px' }}>
|
||||
<div style={{
|
||||
padding: '10px',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: 'rgba(0, 94, 184, 0.07)',
|
||||
marginBottom: '10px',
|
||||
}}>
|
||||
<Shield size={26} style={{ color: '#005EB8' }} strokeWidth={2.5} />
|
||||
</div>
|
||||
<span style={{
|
||||
fontFamily: "'[UI font]', system-ui, sans-serif",
|
||||
fontSize: '13px', fontWeight: 600,
|
||||
color: '#64748B', letterSpacing: '0.01em',
|
||||
}}>CareerRecord PMR</span>
|
||||
<span style={{
|
||||
fontFamily: "'[UI font]', system-ui, sans-serif",
|
||||
fontSize: '11px', fontWeight: 400,
|
||||
color: '#94A3B8', marginTop: '2px',
|
||||
}}>Clinical Information System</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
Input field pattern (username example):
|
||||
```tsx
|
||||
<div style={{
|
||||
width: '100%',
|
||||
padding: '9px 11px',
|
||||
fontFamily: "'Geist Mono', 'Fira Code', monospace",
|
||||
fontSize: '13px',
|
||||
backgroundColor: activeField === 'username' ? '#FFFFFF' : '#FAFAFA',
|
||||
border: activeField === 'username' ? '1px solid #005EB8' : '1px solid #E5E7EB',
|
||||
borderRadius: '4px',
|
||||
color: '#111827',
|
||||
minHeight: '38px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<span>{username}</span>
|
||||
{activeField === 'username' && (
|
||||
<span style={{ opacity: showCursor ? 1 : 0, color: '#005EB8' }} aria-hidden="true">|</span>
|
||||
)}
|
||||
</div>
|
||||
```
|
||||
|
||||
Login button (interactive — user clicks to proceed):
|
||||
```tsx
|
||||
<button
|
||||
onClick={typingComplete ? handleLogin : undefined}
|
||||
disabled={!typingComplete}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px 16px',
|
||||
fontFamily: "'[UI font]', system-ui, sans-serif",
|
||||
fontSize: '14px', fontWeight: 600,
|
||||
color: '#FFFFFF',
|
||||
backgroundColor: buttonPressed ? '#004494' : '#005EB8',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: typingComplete ? 'pointer' : 'default',
|
||||
opacity: typingComplete ? 1 : 0.6,
|
||||
transition: 'background-color 150ms, opacity 300ms',
|
||||
}}
|
||||
>Log In</button>
|
||||
```
|
||||
|
||||
Typing sequence (reduced motion branch):
|
||||
```tsx
|
||||
if (prefersReducedMotion) {
|
||||
setUsername(fullUsername)
|
||||
setPasswordDots(passwordLength)
|
||||
setActiveField('done')
|
||||
setTypingComplete(true)
|
||||
// Button is immediately available for user to click
|
||||
return
|
||||
}
|
||||
// Normal: username at 80ms/char, 300ms pause, password at 60ms/dot
|
||||
// After typing completes: setTypingComplete(true), button becomes interactive
|
||||
// User clicks "Log In" to proceed — no auto-click
|
||||
```
|
||||
|
||||
Footer:
|
||||
```tsx
|
||||
<div style={{ marginTop: '22px', paddingTop: '18px', borderTop: '1px solid #E5E7EB' }}>
|
||||
<p style={{
|
||||
fontFamily: "'[UI font]', system-ui, sans-serif",
|
||||
fontSize: '11px', color: '#94A3B8', textAlign: 'center',
|
||||
}}>Secure clinical system login</p>
|
||||
</div>
|
||||
```
|
||||
Reference in New Issue
Block a user