Compare commits

..

38 Commits

Author SHA1 Message Date
admin e13a073a6f Redesign CVMIS system 2 2026-02-13 16:42:45 +00:00
admin 000df670a3 Redesign CVMIS system 2026-02-13 16:42:23 +00:00
admin b9db2f5401 Update progress: Task 15 completed (Accessibility audit)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 01:42:59 +00:00
admin c3316b9c45 Task 15: Accessibility audit complete
- Sidebar: Replace <aside role="navigation"> with <nav> to avoid conflicting roles
- Sidebar search: Add combobox role, aria-expanded, aria-controls, aria-autocomplete
- Search results: Add listbox/option roles, group labels for screen reader navigation
- PMRInterface: Remove redundant role="main", fix aria-label to use CV-friendly labels
- Mobile search: Add aria-label and type="search" for proper semantics
- Breadcrumb: Add aria-current="page" to current item, aria-hidden on separators
- Clinical alert: Add aria-label="Acknowledge clinical alert" on button per spec
- Patient banner: Change focus:ring to focus-visible:ring on action buttons
- Patient banner: Add role="img" to StatusDot for aria-label accessibility
- Login screen: Change role="status" to role="dialog" with aria-modal
- Login screen: Add loginButtonRef with auto-focus when typing completes
- Login screen: Add focus-visible ring style to Log In button
- Medications tabs: Add id="tab-{id}" to tab buttons, fix aria-labelledby on panels
- Consultations: Wrap entries in <article> per semantic HTML spec
- Problems: Change TrafficLight dot from role="img" to aria-hidden (text label handles it)
- App: Add sr-only live region announcing "Patient Record for Charlwood, Andrew" on PMR entry
- Skip button: Add focus-visible ring for keyboard users

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 01:42:05 +00:00
admin b3ebff26bf Update progress: Task 14 completed (Responsive design audit) 2026-02-13 01:25:43 +00:00
admin 85ac1b879f Task 14: Responsive design audit complete 2026-02-13 01:25:07 +00:00
admin 4db3be0abb Update progress: Task 13 completed (Fuzzy search with fuse.js) 2026-02-13 01:21:19 +00:00
admin f96c6a99d1 Task 13: Implement fuzzy search with fuse.js
- Installed fuse.js for fuzzy search functionality
- Created src/lib/search.ts with buildSearchIndex and groupResultsBySection functions
- Search index includes all consultations, medications, problems, investigations, and documents
- Updated ClinicalSidebar to use fuse.js instead of simple filter
- Search results grouped by section (Experience, Skills, Achievements, Projects, Education)
- Section headers show icon and count
- Each result shows title and highlight text (truncated)
- Clicking a result navigates to the section and expands the matching item
- Minimum 2 characters required for search
- Top 10 results displayed
- Clean dropdown styling with hover states
- Integrates with AccessibilityContext to set expandedItem

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-13 01:20:08 +00:00
admin 7461a83b9d Update progress: Task 12 completed (ReferralsView rebuild) 2026-02-13 01:15:11 +00:00
admin b480b742c8 Task 12: Rebuild ReferralsView (Contact) with premium fonts and refined styling
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-13 01:14:25 +00:00
admin bfd17a3e80 Update progress: Task 11 completed (InvestigationsView + DocumentsView rebuild)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 01:09:56 +00:00
admin bba61f73b6 Task 11: Rebuild InvestigationsView + DocumentsView (Projects + Education)
- Replace CSS height transitions with Framer Motion AnimatePresence
- Add tree-indented monospace content with box-drawing characters
- Add StatusBadge pills (Complete/Ongoing/Live with pulse)
- Replace font-inter with font-ui, font-mono with font-geist
- Add multi-layered shadows (shadow-pmr), proper borders
- Add document type icons (FileText, Award, GraduationCap, FlaskConical)
- Color-coded left borders on expanded panels by status/type
- Alternating row backgrounds, hover:bg-[#EFF6FF]
- AccessibilityContext integration for breadcrumb updates
- Framer Motion chevron rotation, keyboard navigation
- Mobile card layouts with same animations
- prefers-reduced-motion support throughout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 01:08:57 +00:00
admin 8765470627 Update progress: Task 10 completed (ProblemsView rebuild) 2026-02-13 01:03:30 +00:00
admin 43aa836317 Task 10: Rebuild ProblemsView (Achievements view)
- Replaced all font-inter references with font-ui (Elvaro Grotesque)
- Updated font-mono to font-geist for codes and dates ([MGT001], Jul 2024, etc.)
- Changed hover colors from bg-blue-50 to bg-[#EFF6FF] (blue tint)
- Added shadow-pmr to both Active and Resolved Problems cards
- Switched from CSS transitions to Framer Motion for expand/collapse animations
  - AnimatePresence with height-only animation (no opacity fade per guardrail)
  - Chevron rotation via motion.div (180° when expanded)
  - prefersReducedMotion support (duration: 0)
- Updated font sizes: text-[13px] for headers, text-[14px] for body, text-xs for labels
- TrafficLight component now uses font-ui for text labels
- Added AccessibilityContext integration (setExpandedItem for breadcrumb)
- Mobile cards: added shadow-pmr, updated all font references to font-ui/font-geist
- Added focus-visible rings on linked consultation buttons

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-13 01:02:35 +00:00
admin f0cb6b924f Update progress: Task 9 completed (MedicationsView rebuild)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 00:57:26 +00:00
admin 06f0d658b0 Task 9: Rebuild MedicationsView (Skills view)
Rebuild medications/skills view from ref-medications.md spec with
Clinical Luxury design direction. Three category tabs with count
badges, semantic table with sortable columns, expandable prescribing
history with vertical timeline, and Framer Motion height animation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 00:56:35 +00:00
admin ad1ce81948 Update progress: Task 8 completed (ConsultationsView rebuild)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 00:52:08 +00:00
admin 2be346144c Task 8: Rebuild ConsultationsView (Experience view)
Rebuilt from ref-consultations.md spec with Clinical Luxury styling:
- Framer Motion height-only expand/collapse (no opacity fade)
- font-ui (Elvaro Grotesque) throughout, Geist Mono for dates/codes
- 3px left border color-coded by employer (NHS blue / Tesco teal)
- Multi-layered card shadows (shadow-pmr)
- Blue tint hover state (#EFF6FF)
- H/E/P section headers: uppercase, 12px, letter-spacing 0.05em
- Coded entries in Geist Mono with bracket codes
- Single-expand accordion behavior
- Chevron rotation via Framer Motion
- Proper font sizes per spec (13px body, 15px titles, 12px codes)
- Focus-visible ring on entry buttons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 00:51:23 +00:00
admin 1d8cb78143 Update progress: Task 7 completed (SummaryView + ClinicalAlert)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 00:47:02 +00:00
admin cd4aa1e240 Task 7: Rebuild SummaryView + ClinicalAlert
- ClinicalAlert: Framer Motion spring animation entrance, icon crossfade
  (AlertTriangle → CheckCircle), hold beat, height collapse sequence
- Demographics card: Full-width 2-column key-value layout with proper
  label alignment, monospace data values
- Active Problems card: Traffic light dots with text labels (guardrail)
- Quick Medications table: Semantic <table>, alternating rows, hover states
- Last Consultation card: Date in Geist Mono, NHS blue org, role preview
- All cards: font-ui (Elvaro Grotesque), multi-layered shadows, #E5E7EB borders
- Grid: 2-column desktop layout, single column mobile
- prefers-reduced-motion: instant alert, no animations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 00:46:14 +00:00
admin fd9dd7d00e Update progress: Task 6 completed (PMRInterface layout + Breadcrumb) 2026-02-13 00:40:31 +00:00
admin 8f6bfd0b5e Task 6: Rebuild PMRInterface layout + Breadcrumb
Changes made:
- Created Breadcrumb.tsx component with Patient Record > [View] > [Expanded Item] navigation
- Integrated Breadcrumb into PMRInterface (desktop/tablet only, not mobile)
- Breadcrumb receives currentView, expandedItem props and handles navigation callbacks
- Updated all font references from font-inter to font-ui (Elvaro Grotesque)
- Added shadow-pmr to default view placeholder card
- Mobile back button updated to use font-ui

Visual verification:
- Breadcrumb renders correctly with gray-400 text, chevron separators, 13px font size
- Navigation updates breadcrumb path correctly (tested Summary → Experience)
- Layout: fixed sidebar, sticky banner, scrollable content all working
- View switching is instant (no animation between views)
- Premium font (Elvaro Grotesque) rendering throughout interface

Quality checks: All passed (typecheck, lint, build — 396.39 KB bundle)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-13 00:39:41 +00:00
admin 803c4f8a48 Update progress: Task 5 completed (ClinicalSidebar rebuild)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 00:36:22 +00:00
admin 5533cded82 Task 5: Rebuild ClinicalSidebar with CV-friendly labels and premium font
- Replace clinical jargon labels with CV-friendly terms: Experience,
  Skills, Achievements, Projects, Education, Contact
- Replace all font-inter references with font-ui (Elvaro Grotesque)
- Fix Tailwind opacity syntax: bg-white/12 → bg-white/[0.12] etc.
- Add right edge border (border-r border-[#334155]) for sidebar depth
- Add focus-visible ring styles on all nav buttons
- Set explicit h-[44px] and font-[14px] per design spec
- Add border-transparent on inactive items to prevent layout shift
- Update footer text color to #64748B per spec
- Update MobileBottomNav labels to match sidebar convention
- Update PMRInterface viewLabels to CV-friendly names

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 00:35:43 +00:00
admin 86e0015393 Update progress: Task 4b completed (scroll condensation fix)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 00:31:03 +00:00
admin d16656b954 Task 4b: Fix PatientBanner scroll condensation
Root cause: sentinel element with `absolute top-0` inside PatientBanner was
positioned at viewport top, always triggering the IntersectionObserver's
-100px rootMargin threshold — banner was permanently stuck in condensed state.

Fix: Restructured PMRInterface layout from document-scroll to flex container
with explicit scroll container (`overflow-y-auto` on main). Lifted scroll
condensation logic to PMRInterface, passing `isCondensed` prop down to
PatientBanner. Replaced IntersectionObserver with scroll event listener on
the main element for reliable scroll position detection.

Key changes:
- PMRInterface: flex h-screen overflow-hidden layout (sidebar + content column)
- PatientBanner: accepts isCondensed prop, removed sticky/sentinel/hook
- ClinicalSidebar: h-full instead of h-screen sticky (parent handles sizing)
- useScrollCondensation: scroll event on container element via callback ref

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 00:30:23 +00:00
admin b7471c5cf8 Updated prompts 2026-02-13 00:20:25 +00:00
admin 5579e2741a Update progress: Task 4 completed (PatientBanner rebuild)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 00:16:29 +00:00
admin f75a6b9a5f Task 4: Rebuild PatientBanner with premium fonts, tooltip, and animations
- Replace font-inter with font-ui (Elvaro Grotesque) throughout banner
- Add custom NHSNumberWithTooltip with Framer Motion animated reveal
- Add AnimatePresence crossfade between full/condensed banner states
- Animate mobile overflow menu enter/exit
- Add SkipButton to App.tsx for boot/ECG phase skip
- Add shadow-pmr-banner, focus ring styles, prefers-reduced-motion support
- Fix mobile banner to use patient data instead of hardcoded values

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 00:16:20 +00:00
admin 8094f74800 Update Ralph loop: replace Claude in Chrome with Playwright MCP for visual review
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 00:11:50 +00:00
admin 4324f06186 Update progress: Task 3 completed (LoginScreen rebuild)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 23:45:09 +00:00
admin 5e1c96edfa Task 3: Rebuild LoginScreen with interactive login and premium font
- Typing speed: 80ms/char username, 60ms/dot password (was 30ms/20ms)
- Login button is now user-interactive (not auto-triggered)
- Button disabled/dimmed during typing, fully interactive after
- Hover state on button (darkens to #004D9F)
- Font changed from Inter to Elvaro Grotesque (var(--font-ui))
- Card shadow upgraded to multi-layered per design system
- Added 'done' activeField state for post-typing phase
- Proper timer cleanup via tracked timeout refs
- Reduced motion: typing instant, button immediately clickable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 23:44:33 +00:00
admin 556940c3c8 Update progress: Task 2 completed (premium font setup) 2026-02-12 23:41:17 +00:00
admin b8c1aedb5a Task 2: Set up premium font system (Elvaro Grotesque + Blumir)
Added @font-face declarations for both premium font candidates:
- Elvaro Grotesque: 7 weights (Light 300 → Black 900) from WOFF2/WOFF files
- Blumir: Variable font (100-700 weight range) from WOFF2/WOFF files

Updated Tailwind config:
- Added font-ui (Elvaro Grotesque) and font-ui-alt (Blumir) families
- Removed font-inter references (replaced with font-ui)
- Enhanced shadow tokens: pmr, pmr-hover, pmr-banner for Clinical Luxury depth
- Kept font-geist (Geist Mono) for data/timestamps, font-mono (Fira Code) for boot/ECG

Updated CSS variables and utility classes:
- --font-ui: Elvaro Grotesque
- --font-ui-alt: Blumir
- .pmr-theme now uses var(--font-ui) instead of var(--font-inter)

Fixed ESLint errors in ECGAnimation.tsx (viewOff/headSX should be const).

Quality checks: All passed (typecheck, lint, build). Font files bundled correctly.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-12 23:40:32 +00:00
admin 5a000d6457 Design direction changed from Clinical Utilitarian to Clinical Luxury, updated all plans etc 2026-02-12 23:31:17 +00:00
admin 3afadbdc73 Completed boot loading to ECG, to name written 2026-02-12 22:31:34 +00:00
admin 4eeeb05744 Ralph iteration 1: work in progress 2026-02-11 22:55:02 +00:00
admin 959f0e1842 Task 1b: Rebuild boot sequence and ECG animation
- Refactored BootSequence to config-driven architecture with type-safe line components
- Added cursor position capture and smooth cursor-to-dot morph transition
- Rebuilt ECGAnimation with mask-based text reveal technique
- Implemented connector lines between letters with per-character profiles
- ECG trace now starts from cursor position (no teleport)
- Added prefers-reduced-motion support for both phases
- Updated App.tsx to pass cursor position between components

Quality checks: typecheck ✓, lint ✓, build ✓
2026-02-11 22:54:44 +00:00
178 changed files with 29046 additions and 14295 deletions
+21 -1
View File
@@ -8,7 +8,27 @@
"Bash(start \"\" \"C:\\\\Users\\\\Andy\\\\Ralph Local\\\\Tasks\\\\cv-4-vitals-monitor\\\\4-vitals-monitor.html\")", "Bash(start \"\" \"C:\\\\Users\\\\Andy\\\\Ralph Local\\\\Tasks\\\\cv-4-vitals-monitor\\\\4-vitals-monitor.html\")",
"Bash(npx skills find:*)", "Bash(npx skills find:*)",
"WebSearch", "WebSearch",
"Bash(ls \"C:\\\\Users\\\\Andy\\\\Ralph Local\\\\Tasks\\\\New CV website\\\\designs\"\" 2>nul || echo \"Directory does not exist \")" "Bash(ls \"C:\\\\Users\\\\Andy\\\\Ralph Local\\\\Tasks\\\\New CV website\\\\designs\"\" 2>nul || echo \"Directory does not exist \")",
"Bash(npm run typecheck:*)",
"Bash(npm run dev:*)",
"Bash(npm run build:*)",
"Bash(dir:*)",
"mcp__playwright__browser_snapshot",
"mcp__playwright__browser_navigate",
"mcp__playwright__browser_take_screenshot",
"Bash(npm run lint:*)",
"Bash(curl:*)",
"mcp__playwright__browser_click",
"mcp__playwright__browser_wait_for",
"mcp__playwright__browser_evaluate",
"Bash(git add:*)",
"Bash(git commit -m \"$\\(cat <<''EOF''\nTask 4: Rebuild PatientBanner with premium fonts, tooltip, and animations\n\n- Replace font-inter with font-ui \\(Elvaro Grotesque\\) throughout banner\n- Add custom NHSNumberWithTooltip with Framer Motion animated reveal\n- Add AnimatePresence crossfade between full/condensed banner states\n- Animate mobile overflow menu enter/exit\n- Add SkipButton to App.tsx for boot/ECG phase skip\n- Add shadow-pmr-banner, focus ring styles, prefers-reduced-motion support\n- Fix mobile banner to use patient data instead of hardcoded values\n\nCo-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>\nEOF\n\\)\")",
"Bash(git commit:*)",
"Bash(ls:*)",
"Bash(tasklist:*)",
"Bash(npx -y serve -l 3333 .)",
"Bash(npx serve:*)",
"Bash(timeout /t 3 /nobreak)"
] ]
} }
} }
+5
View File
@@ -25,3 +25,8 @@ dist-ssr
# TypeScript # TypeScript
*.tsbuildinfo *.tsbuildinfo
#Playwrite Screenshots
*.png
nul
+278
View File
@@ -0,0 +1,278 @@
#!/usr/bin/env node
'use strict';
var chalk = require('chalk'),
os = require('os'),
httpServer = require('../lib/http-server'),
portfinder = require('portfinder'),
opener = require('opener'),
fs = require('fs'),
url = require('url');
var argv = require('minimist')(process.argv.slice(2), {
alias: {
tls: 'ssl'
}
});
var ifaces = os.networkInterfaces();
process.title = 'http-server';
if (argv.h || argv.help) {
console.log([
'usage: http-server [path] [options]',
'',
'options:',
' -p --port Port to use. If 0, look for open port. [8080]',
' -a Address to use [0.0.0.0]',
' -d Show directory listings [true]',
' -i Display autoIndex [true]',
' -g --gzip Serve gzip files when possible [false]',
' -b --brotli Serve brotli files when possible [false]',
' If both brotli and gzip are enabled, brotli takes precedence',
' -e --ext Default file extension if none supplied [none]',
' -s --silent Suppress log messages from output',
' --cors[=headers] Enable CORS via the "Access-Control-Allow-Origin" header',
' Optionally provide CORS headers list separated by commas',
' -o [path] Open browser window after starting the server.',
' Optionally provide a URL path to open the browser window to.',
' -c Cache time (max-age) in seconds [3600], e.g. -c10 for 10 seconds.',
' To disable caching, use -c-1.',
' -t Connections timeout in seconds [120], e.g. -t60 for 1 minute.',
' To disable timeout, use -t0',
' -U --utc Use UTC time format in log messages.',
' --log-ip Enable logging of the client\'s IP address',
'',
' -P --proxy Fallback proxy if the request cannot be resolved. e.g.: http://someurl.com',
' --proxy-options Pass options to proxy using nested dotted objects. e.g.: --proxy-options.secure false',
'',
' --username Username for basic authentication [none]',
' Can also be specified with the env variable NODE_HTTP_SERVER_USERNAME',
' --password Password for basic authentication [none]',
' Can also be specified with the env variable NODE_HTTP_SERVER_PASSWORD',
'',
' -S --tls --ssl Enable secure request serving with TLS/SSL (HTTPS)',
' -C --cert Path to TLS cert file (default: cert.pem)',
' -K --key Path to TLS key file (default: key.pem)',
'',
' -r --robots Respond to /robots.txt [User-agent: *\\nDisallow: /]',
' --no-dotfiles Do not show dotfiles',
' --mimetypes Path to a .types file for custom mimetype definition',
' -h --help Print this list and exit.',
' -v --version Print the version and exit.'
].join('\n'));
process.exit();
}
var port = argv.p || argv.port || parseInt(process.env.PORT, 10),
host = argv.a || '0.0.0.0',
tls = argv.S || argv.tls,
sslPassphrase = process.env.NODE_HTTP_SERVER_SSL_PASSPHRASE,
proxy = argv.P || argv.proxy,
proxyOptions = argv['proxy-options'],
utc = argv.U || argv.utc,
version = argv.v || argv.version,
logger;
var proxyOptionsBooleanProps = [
'ws', 'xfwd', 'secure', 'toProxy', 'prependPath', 'ignorePath', 'changeOrigin',
'preserveHeaderKeyCase', 'followRedirects', 'selfHandleResponse'
];
if (proxyOptions) {
Object.keys(proxyOptions).forEach(function (key) {
if (proxyOptionsBooleanProps.indexOf(key) > -1) {
proxyOptions[key] = proxyOptions[key].toLowerCase() === 'true';
}
});
}
if (!argv.s && !argv.silent) {
logger = {
info: console.log,
request: function (req, res, error) {
var date = utc ? new Date().toUTCString() : new Date();
var ip = argv['log-ip']
? req.headers['x-forwarded-for'] || '' + req.connection.remoteAddress
: '';
if (error) {
logger.info(
'[%s] %s "%s %s" Error (%s): "%s"',
date, ip, chalk.red(req.method), chalk.red(req.url),
chalk.red(error.status.toString()), chalk.red(error.message)
);
}
else {
logger.info(
'[%s] %s "%s %s" "%s"',
date, ip, chalk.cyan(req.method), chalk.cyan(req.url),
req.headers['user-agent']
);
}
}
};
}
else if (chalk) {
logger = {
info: function () {},
request: function () {}
};
}
if (version) {
logger.info('v' + require('../package.json').version);
process.exit();
}
if (!port) {
portfinder.basePort = 8080;
portfinder.getPort(function (err, port) {
if (err) { throw err; }
listen(port);
});
}
else {
listen(port);
}
function listen(port) {
var options = {
root: argv._[0],
cache: argv.c,
timeout: argv.t,
showDir: argv.d,
autoIndex: argv.i,
gzip: argv.g || argv.gzip,
brotli: argv.b || argv.brotli,
robots: argv.r || argv.robots,
ext: argv.e || argv.ext,
logFn: logger.request,
proxy: proxy,
proxyOptions: proxyOptions,
showDotfiles: argv.dotfiles,
mimetypes: argv.mimetypes,
username: argv.username || process.env.NODE_HTTP_SERVER_USERNAME,
password: argv.password || process.env.NODE_HTTP_SERVER_PASSWORD
};
if (argv.cors) {
options.cors = true;
if (typeof argv.cors === 'string') {
options.corsHeaders = argv.cors;
}
}
if (proxy) {
try {
new url.URL(proxy)
}
catch (err) {
logger.info(chalk.red('Error: Invalid proxy url'));
process.exit(1);
}
}
if (tls) {
options.https = {
cert: argv.C || argv.cert || 'cert.pem',
key: argv.K || argv.key || 'key.pem',
passphrase: sslPassphrase,
};
try {
fs.lstatSync(options.https.cert);
}
catch (err) {
logger.info(chalk.red('Error: Could not find certificate ' + options.https.cert));
process.exit(1);
}
try {
fs.lstatSync(options.https.key);
}
catch (err) {
logger.info(chalk.red('Error: Could not find private key ' + options.https.key));
process.exit(1);
}
}
var server = httpServer.createServer(options);
server.listen(port, host, function () {
var protocol = tls ? 'https://' : 'http://';
logger.info([
chalk.yellow('Starting up http-server, serving '),
chalk.cyan(server.root),
tls ? (chalk.yellow(' through') + chalk.cyan(' https')) : ''
].join(''));
logger.info([chalk.yellow('\nhttp-server version: '), chalk.cyan(require('../package.json').version)].join(''));
logger.info([
chalk.yellow('\nhttp-server settings: '),
([chalk.yellow('CORS: '), argv.cors ? chalk.cyan(argv.cors) : chalk.red('disabled')].join('')),
([chalk.yellow('Cache: '), argv.c ? (argv.c === '-1' ? chalk.red('disabled') : chalk.cyan(argv.c + ' seconds')) : chalk.cyan('3600 seconds')].join('')),
([chalk.yellow('Connection Timeout: '), argv.t === '0' ? chalk.red('disabled') : (argv.t ? chalk.cyan(argv.t + ' seconds') : chalk.cyan('120 seconds'))].join('')),
([chalk.yellow('Directory Listings: '), argv.d ? chalk.red('not visible') : chalk.cyan('visible')].join('')),
([chalk.yellow('AutoIndex: '), argv.i ? chalk.red('not visible') : chalk.cyan('visible')].join('')),
([chalk.yellow('Serve GZIP Files: '), argv.g || argv.gzip ? chalk.cyan('true') : chalk.red('false')].join('')),
([chalk.yellow('Serve Brotli Files: '), argv.b || argv.brotli ? chalk.cyan('true') : chalk.red('false')].join('')),
([chalk.yellow('Default File Extension: '), argv.e ? chalk.cyan(argv.e) : (argv.ext ? chalk.cyan(argv.ext) : chalk.red('none'))].join(''))
].join('\n'));
logger.info(chalk.yellow('\nAvailable on:'));
if (argv.a && host !== '0.0.0.0') {
logger.info(` ${protocol}${host}:${chalk.green(port.toString())}`);
} else {
Object.keys(ifaces).forEach(function (dev) {
ifaces[dev].forEach(function (details) {
if (details.family === 'IPv4') {
logger.info((' ' + protocol + details.address + ':' + chalk.green(port.toString())));
}
});
});
}
if (typeof proxy === 'string') {
if (proxyOptions) {
logger.info('Unhandled requests will be served from: ' + proxy + '. Options: ' + JSON.stringify(proxyOptions));
}
else {
logger.info('Unhandled requests will be served from: ' + proxy);
}
}
logger.info('Hit CTRL-C to stop the server');
if (argv.o) {
const openHost = host === '0.0.0.0' ? '127.0.0.1' : host;
let openUrl = `${protocol}${openHost}:${port}`;
if (typeof argv.o === 'string') {
openUrl += argv.o[0] === '/' ? argv.o : '/' + argv.o;
}
logger.info('Open: ' + openUrl);
opener(openUrl);
}
// Spacing before logs
if (!argv.s) logger.info();
});
}
if (process.platform === 'win32') {
require('readline').createInterface({
input: process.stdin,
output: process.stdout
}).on('SIGINT', function () {
process.emit('SIGINT');
});
}
process.on('SIGINT', function () {
logger.info(chalk.red('http-server stopped.'));
process.exit();
});
process.on('SIGTERM', function () {
logger.info(chalk.red('http-server stopped.'));
process.exit();
});
+27
View File
@@ -0,0 +1,27 @@
{
"iterations": [
{
"iteration": 1,
"startedAt": "2026-02-11T22:50:15.397Z",
"endedAt": "2026-02-11T22:55:02.081Z",
"durationMs": 283525,
"toolsUsed": {},
"filesModified": [
"Ralph/IMPLEMENTATION_PLAN.md",
"Ralph/progress.txt",
"src/App.tsx",
"src/components/BootSequence.tsx",
"src/components/ECGAnimation.tsx"
],
"exitCode": 0,
"completionDetected": false,
"errors": []
}
],
"totalDurationMs": 283525,
"struggleIndicators": {
"repeatedErrors": {},
"noProgressIterations": 0,
"shortIterations": 0
}
}
File diff suppressed because one or more lines are too long
-72
View File
@@ -1,72 +0,0 @@
# AGENTS.md
This file provides guidance to AI agents (OpenCode, Claude Code, etc.) when working with code in this repository.
## Project Overview
Interactive CV/portfolio website for Andy Charlwood with a distinctive loading experience: terminal boot sequence → ECG canvas animation with name tracing. Built as a React SPA with TypeScript and Vite.
## Commands
- `npm run dev` — Start dev server (localhost:5173)
- `npm run build` — TypeScript compile + Vite production build
- `npm run typecheck` — TypeScript type checking only (`tsc --noEmit`)
- `npm run lint` — ESLint
- `npm run preview` — Preview production build
No test framework is configured.
## Architecture
### Loading UI Flow
`App.tsx` manages a `Phase` state (`'boot'``'ecg'`). Each phase renders exclusively:
1. **BootSequence** — Terminal typing animation (~4s), green-on-black aesthetic
2. **ECGAnimation** — Canvas-based heartbeat animation (~5-6s) with letter tracing, background transitions from black to white
Total boot-to-ECG completion time must be ≤10 seconds.
### Key Patterns
- **Canvas ECG**: `ECGAnimation.tsx` does imperative canvas drawing with requestAnimationFrame — flatline → 3 heartbeats (40px→60px→100px) → letter tracing → exit.
### Path Aliases
`@/` maps to `./src/` (configured in both `vite.config.ts` and `tsconfig.json`).
### Styling
Tailwind CSS with custom design tokens in `tailwind.config.js`:
- **Colors**: teal `#00897B` (primary), coral `#FF6B6B` (accent), ECG palette (green/cyan/dim)
- **Fonts**: Plus Jakarta Sans (primary), Inter Tight (secondary), Fira Code (mono/terminal)
- **Breakpoints**: xs 480px, sm 640px, md 768px, lg 1024px, xl 1280px
### Type System
All data types live in `src/types/index.ts`. Strict TypeScript — no `any` types. One component per file with typed props interfaces.
## Guardrails
- Boot sequence text and colors must match `References/concept.html` exactly (CLINICAL TERMINAL v3.2.1 format).
- ECG animation timing/amplitudes/color transitions must match the concept reference.
- When writing components with visual styling or animations, invoke the `frontend-design` skill first.
## Available Skills
This project has access to the following agent skills in `.agents/skills/`:
- **frontend-design** — Use for any visual styling or animation work
## Project Structure
```
src/
├── components/ # One component per file (PascalCase)
├── hooks/ # Custom hooks (camelCase, use* prefix)
├── lib/ # Utility functions
├── types/ # TypeScript interfaces
├── App.tsx # Phase manager (root component)
└── index.css # Global styles + Tailwind directives
Ralph/ # Implementation plan, guardrails, progress tracking
References/ # Source content (concept.html, ECGVideo/)
```
+172 -28
View File
@@ -4,7 +4,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview ## Project Overview
Interactive CV/portfolio website for Andy Charlwood with a distinctive three-phase loading experience: terminal boot sequence → ECG canvas animation → main content. Built as a React SPA with TypeScript and Vite. Interactive CV/portfolio for Andy Charlwood, presented as a GP clinical record system. The concept: *what if a GP surgery's patient record system were redesigned by a luxury product studio?* The structure and metaphor of a real clinical system (tiles as record sections, status indicators, medication-style skill entries, alerts) — but elevated with refined typography, considered motion, and a modern light aesthetic.
**This is NOT a faithful NHS system clone.** It's a showcase portfolio that *evokes* the feel of clinical software while being distinctly beautiful. The clinical metaphor is the creative conceit; the execution should feel premium and contemporary.
Built as a React SPA with TypeScript and Vite.
**Reference design:** `References/GPSystemconcept.html` — the visual and structural target for the dashboard.
## Commands ## Commands
@@ -18,57 +24,195 @@ No test framework is configured.
## Architecture ## Architecture
### Three-Phase UI Flow ### Four-Phase UI Flow
`App.tsx` manages a `Phase` state (`'boot'``'ecg'``'content'`). Each phase renders exclusively: `App.tsx` manages a `Phase` state (`'boot'``'ecg'``'login'``'dashboard'`). Each phase renders exclusively:
1. **BootSequence** — Terminal typing animation (~4s), green-on-black aesthetic 1. **BootSequence** — Terminal typing animation (~4s), green-on-black aesthetic. Fira Code font, matrix-green palette. **Locked — do not change.**
2. **ECGAnimation** — Canvas-based heartbeat animation (~5-6s) with letter tracing, background transitions from black to white 2. **ECGAnimation** — Canvas-based heartbeat animation with mask-based letter tracing. Background transitions from black to `#1E293B`. **Locked — do not change.**
3. **Content**FloatingNav + all CV sections (Hero, Skills, Experience, Education, Projects, Contact, Footer) 3. **LoginScreen**Animated login card on dark background. Types credentials at a natural pace, then presents an interactive "Log In" button for the user to click. Login transitions to the dashboard.
4. **DashboardLayout** — The main portfolio experience: TopBar + Sidebar + scrollable tile-based dashboard.
Total boot-to-content time must be ≤10 seconds. ### Dashboard Layout (Post-Login)
The dashboard uses a three-zone layout:
```
┌─────────────────────────────────────────────────────┐
│ TopBar (fixed, 48px) — brand, search, session │
├──────────┬──────────────────────────────────────────┤
│ │ │
│ Sidebar │ Card Grid (scrollable) │
│ (272px) │ ┌─────────────────────────────────┐ │
│ │ │ Patient Summary (full width) │ │
│ Person │ ├────────────────┬────────────────┤ │
│ Header │ │ Latest Results │ Repeat Meds │ │
│ │ │ (KPIs) │ (Core Skills) │ │
│ Tags │ ├────────────────┴────────────────┤ │
│ │ │ Last Consultation (full width) │ │
│ Alerts │ ├─────────────────────────────────┤ │
│ │ │ Career Activity (full width) │ │
│ │ ├─────────────────────────────────┤ │
│ │ │ Education (full width) │ │
│ │ ├─────────────────────────────────┤ │
│ │ │ Projects (full width) │ │
│ │ └─────────────────────────────────┘ │
└──────────┴──────────────────────────────────────────┘
```
**No view switching.** The dashboard is a single scrollable page of tiles. Users scroll to see all sections. Detail drill-down happens by expanding tiles in-place (accordion pattern).
### Key Patterns ### Key Patterns
- **Scroll reveals**: `useScrollReveal` hook wraps IntersectionObserver with trigger-once semantics. Used by every content section. Never use scroll event listeners. - **Canvas ECG**: `ECGAnimation.tsx` does imperative canvas drawing with requestAnimationFrame — flatline → 3 heartbeats (40px→60px→100px) → mask-based letter tracing → exit. **Locked — do not change.**
- **Active nav tracking**: `useActiveSection` hook tracks which section is in viewport for FloatingNav highlighting. - **TopBar**: `TopBar.tsx` — fixed at top, brand + search trigger + session info. Search bar triggers Command Palette on click/Ctrl+K.
- **Staggered animations**: Components use index-based delays (`baseDelay + index * 100`) with Framer Motion. - **Sidebar**: `Sidebar.tsx` — light background, contains PersonHeader (avatar, name, title, status, details), Tags, and Alerts only. Skills, Projects, Education are in the main content tiles.
- **SVG skill circles**: `Skills.tsx` uses `strokeDashoffset = circumference * (1 - level / 100)` with `-90deg` rotation to start from 12 o'clock. - **Card Grid**: CSS Grid, 2 columns on desktop (gap 16px), 1 column on mobile. Tiles use a reusable `Card` component with consistent styling.
- **Canvas ECG**: `ECGAnimation.tsx` does imperative canvas drawing with requestAnimationFrame — flatline → 3 heartbeats (40px→60px→100px) → letter tracing → exit. - **Tile Expansion**: Career Activity items, Project items, and Skill items expand in-place with height-only animation (200ms, ease-out). Single-expand accordion — only one item open at a time.
- **KPI Flip Cards**: Latest Results metrics flip on click to show explanation text. CSS perspective transform, 400ms.
- **Command Palette**: Ctrl+K opens a Spotlight-style search overlay. Fuzzy search via fuse.js. Keyboard navigation (arrow keys, Enter, Escape).
- **Staggered entrance**: TopBar slides down → Sidebar slides from left → Content fades in. Quick (200-300ms).
- **Expandable content**: Height-only animation, 200ms ease-out. Content grows/shrinks — no opacity fade.
- **Responsive breakpoints**: Desktop (full sidebar + 2-col grid), Tablet (collapsed/hidden sidebar + 1-col), Mobile (no sidebar, stacked tiles).
### Path Aliases ### Path Aliases
`@/` maps to `./src/` (configured in both `vite.config.ts` and `tsconfig.json`). `@/` maps to `./src/` (configured in both `vite.config.ts` and `tsconfig.json`).
### Styling
Tailwind CSS with custom design tokens in `tailwind.config.js`:
- **Colors**: teal `#00897B` (primary), coral `#FF6B6B` (accent), ECG palette (green/cyan/dim)
- **Fonts**: Plus Jakarta Sans (primary), Inter Tight (secondary), Fira Code (mono/terminal)
- **Breakpoints**: xs 480px, sm 640px, md 768px, lg 1024px, xl 1280px
- Inline styles only for dynamic values that Tailwind can't express (e.g., computed `strokeDashoffset`).
### Type System ### Type System
All data types live in `src/types/index.ts`. Strict TypeScript — no `any` types. One component per file with typed props interfaces. All data types live in `src/types/index.ts` and `src/types/pmr.ts`. Strict TypeScript — no `any` types. One component per file with typed props interfaces.
## Design Direction: GP System Dashboard
The aesthetic direction is a **modern GP system dashboard** — the precision and information density of a medical records system, but with a light, contemporary, premium feel. Think: a healthcare SaaS product redesigned by a Swiss product studio.
### Tone
- **Precise, not cold.** Every element has a reason. Spacing is generous but intentional.
- **Light, not washed out.** Warm sage background, clean white surfaces, deliberate color accents.
- **Technical, not sterile.** Monospace data, status indicators, and coded entries create authentic texture.
- **Elegant, not decorative.** No gratuitous ornament. Beauty comes from proportion, contrast, and type.
### Typography
Typography is the primary vehicle for premium feel. Avoid generic system fonts.
- **UI / Body:**
- **Elvaro Grotesque** (primary, `font-ui`) — Modern grotesque sans-serif. 7 weights (300-900). Institutional credibility with premium feel. Slightly condensed proportions suit data-dense UI.
- **Blumir** (alternative, `font-ui-alt`) — Geometric-humanist hybrid. Variable font (100-700). More refined/luxurious feel.
- Both fonts sourced from Envato (licensed), stored in `Fonts/`. **Do not use Inter, Roboto, DM Sans, or system defaults.**
- Font files: Elvaro `Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-*.woff2`, Blumir `Fonts/blumir-font-family/WOFF/Blumir-VF.woff2`
- **Monospace / Data**: Geist Mono for timestamps, session info, GPhC number, dates, coded entries. Creates "technical texture."
- **Terminal phase**: Fira Code — locked, do not change.
- **Type scale**: Tight. Headings 15-18px, body 12.5-14px, labels 10-12px. Precision over drama.
- **Weight hierarchy**: Use weight (400/500/600/700) rather than size to establish hierarchy.
### Color Palette
The palette anchors on teal as the primary accent, with a light sidebar + warm content background.
- **Teal `#0D6E6E`** — Primary accent. Active states, links, avatar gradient, interactive elements. Hover: `#0A8080`. Light: `rgba(10,128,128,0.08)`.
- **Background `#F0F5F4`** — Warm sage. The content area feels organic, not flat gray.
- **Sidebar `#F7FAFA`** — Very light. Right border `#D4E0DE` separates from content.
- **TopBar `#FFFFFF`** — White surface. Bottom border `#D4E0DE`.
- **Cards `#FFFFFF`** — White with shadow-sm and border-light. Hover deepens to shadow-md.
- **Status colors**: Success `#059669`, Amber `#D97706`, Alert `#DC2626`, Purple `#7C3AED` — each with light bg and border variants. Always paired with text labels.
- **Text**: Primary `#1A2B2A`, Secondary `#5B7A78`, Tertiary `#8DA8A5`. Use full range for hierarchy.
- **Borders**: Structural `#D4E0DE`, Cards/inner `#E4EDEB`.
### Shadows & Depth
Three-tier shadow system for layered depth:
- **Cards (resting)**: `0 1px 2px rgba(26,43,42,0.05)` — gentle, always present.
- **Cards (hover/interactive)**: `0 2px 8px rgba(26,43,42,0.08)` — slightly lifted.
- **Overlays (command palette, modals)**: `0 8px 32px rgba(26,43,42,0.12)` — clearly elevated.
- **Hover states**: Shadow deepens + border color strengthens. Subtle, not dramatic.
### Motion
Motion should feel considered and premium, never flashy:
- **Entrance animations**: Dashboard materializes in sequence — TopBar slides down → Sidebar slides from left → Content fades in. Quick (200-300ms) with easing.
- **Login typing**: 80ms/char for username, 60ms/dot for password. Natural, readable pace. After typing completes, "Log In" button becomes interactive — user clicks to proceed.
- **Login transition**: On button click, card scales slightly and fades. Transition to dashboard layout.
- **Tile expansion**: Height-only animation, 200ms ease-out. Content grows/shrinks — no opacity fade.
- **KPI flip**: CSS perspective rotateY, 400ms ease-in-out. Click to flip, click to flip back.
- **Command palette**: Scale 0.97→1.0 + translateY entrance, 200ms. Backdrop fade.
- **Hover states**: Subtle, immediate. Border color shifts, shadow deepens. Think: OS-level responsiveness.
- **`prefers-reduced-motion`**: All animations skip to final state. No exceptions.
### Spatial Composition
- **Generous but structured.** Cards have 20px padding. Tile grid has 16px gap. Sections breathe.
- **Clear visual hierarchy.** Card headers: uppercase, small (12px), tracked-out, secondary color with colored dot indicator.
- **Two-column grid** on desktop, single column on mobile. Full-width tiles span both columns.
- **Sidebar sections** separated by thin divider titles (10px, uppercase, tertiary, with line extending right).
### What Makes It Memorable
The distinctiveness comes from the *clinical metaphor applied to a modern interface*:
- A light, professional sidebar with clinical-style person header and alert flags
- Skills presented as "Repeat Medications" with frequency dosing (twice daily, when required)
- KPI metrics that flip to reveal explanations, like interactive test results
- Career history as a clinical timeline with color-coded entry types
- The boot sequence → ECG → login flow is theatrical in a way that real clinical software never is
- Command palette (Ctrl+K) for searching records, like a clinical search tool
## Styling
Tailwind CSS with custom design tokens in `tailwind.config.js`:
- **Color tokens**: PMR-prefixed tokens (`pmr-accent`, `pmr-bg`, `pmr-surface`, `pmr-sidebar`, `pmr-text-primary`, etc.)
- **Fonts**: `font-ui` (Elvaro Grotesque), `font-ui-alt` (Blumir), `font-geist` (Geist Mono), `font-mono` (Fira Code for terminal)
- **Breakpoints**: xs 480px, sm 640px, md 768px, lg 1024px, xl 1280px
- **Border radius**: 8px default for cards/tiles (`var(--radius)`). 6px for inner elements (`var(--radius-sm)`). 12px exception for login card and command palette.
- **Shadows**: `shadow-sm`, `shadow-md`, `shadow-lg` tokens matching three-tier system.
- CSS custom properties in `index.css` for both boot/ECG phase tokens and dashboard phase tokens.
- Inline styles only for dynamic values that Tailwind can't express.
## Guardrails ## Guardrails
- Boot sequence text and colors must match `References/concept.html` exactly (CLINICAL TERMINAL v3.2.1 format). - **Boot sequence**: Text, colors, and timing must match `References/concept.html` exactly. **Do not modify.**
- ECG animation timing/amplitudes/color transitions must match the concept reference. - **ECG animation**: Timing, amplitudes, color transitions, and mask-based text reveal must match the concept reference. **Do not modify.**
- CV content sourced from `References/CV_v4.md` — roles, dates, and achievement numbers must be accurate. - **Reference design**: `References/GPSystemconcept.html` is the visual and structural target for the dashboard.
- Icons via `lucide-react`, not unicode symbols. - **CV content**: Sourced from `References/CV_v4.md` — roles, dates, and achievement numbers must be accurate.
- **Icons**: Via `lucide-react`, not unicode symbols.
- **Accessibility**: WCAG 2.1 AA compliance. Semantic HTML, ARIA attributes, keyboard navigation, `prefers-reduced-motion` support throughout. Status indicators always paired with text labels.
- **No generic aesthetics**: Every design decision should feel intentional. If a component could appear in any random SaaS template, it needs more character.
- **Fonts**: Elvaro Grotesque (primary) or Blumir (alt). Never Inter, Roboto, DM Sans, or system defaults. DM Sans appears in the concept HTML as a placeholder only.
## Project Structure ## Project Structure
``` ```
src/ src/
├── components/ # One component per file (PascalCase) ├── components/ # One component per file (PascalCase)
│ ├── tiles/ # Dashboard tile components (PatientSummaryTile, LatestResultsTile, etc.)
│ ├── views/ # Legacy PMR views (being replaced by tiles — may be referenced during transition)
│ ├── TopBar.tsx # Fixed top bar (brand, search trigger, session)
│ ├── Sidebar.tsx # Light sidebar (person header, tags, alerts)
│ ├── DashboardLayout.tsx # Main layout (topbar + sidebar + card grid)
│ ├── Card.tsx # Reusable card component with header
│ ├── CommandPalette.tsx # Ctrl+K search overlay
│ └── ... # Boot, ECG, Login (unchanged)
├── contexts/ # React contexts (AccessibilityContext)
├── data/ # Static data files
│ ├── patient.ts # Person details
│ ├── consultations.ts # Career roles (used in Last Consultation + Career Activity)
│ ├── medications.ts # Legacy skill data
│ ├── problems.ts # Achievements
│ ├── investigations.ts # Projects
│ ├── documents.ts # Education entries
│ ├── profile.ts # Personal statement
│ ├── tags.ts # Sidebar tags
│ ├── alerts.ts # Sidebar alert flags
│ ├── kpis.ts # KPI metrics for Latest Results
│ └── skills.ts # Skills with frequency/years (medication metaphor)
├── hooks/ # Custom hooks (camelCase, use* prefix) ├── hooks/ # Custom hooks (camelCase, use* prefix)
├── lib/ # Utility functions ├── lib/ # Utility functions (search.ts for fuse.js)
├── types/ # TypeScript interfaces ├── types/ # TypeScript interfaces (index.ts, pmr.ts)
├── App.tsx # Phase manager (root component) ├── App.tsx # Phase manager (root component)
└── index.css # Global styles + Tailwind directives └── index.css # Global styles + Tailwind directives
Ralph/ # Implementation plan, guardrails, progress tracking Ralph/ # Implementation plan, guardrails, progress tracking
References/ # Source content (concept.html, CV_v4.md, ECGVideo/) References/ # Source content (concept.html, GPSystemconcept.html, CV_v4.md)
``` ```
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More