diff --git a/.playwright-mcp/http-server b/.playwright-mcp/http-server
new file mode 100644
index 0000000..7c597fa
--- /dev/null
+++ b/.playwright-mcp/http-server
@@ -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();
+});
diff --git a/Ralph/GP System concept.html b/Ralph/GP System concept.html
new file mode 100644
index 0000000..7934d60
--- /dev/null
+++ b/Ralph/GP System concept.html
@@ -0,0 +1,1711 @@
+
+
+
+Practitioner Record System — CHARLWOOD, Andrew
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
£220M
+
Budget Oversight
+
NHS prescribing
+
+
+
£14.6M
+
Efficiency Savings
+
Identified & tracked
+
+
+
9+
+
Years in NHS
+
Since 2016
+
+
+
12
+
Team Size Led
+
Cross-functional
+
+
+
+
+
+
+
+
+
+
+
+
Advanced SQL Certification
+
Q2 2026
+
+
Pending
+
+
+
+
+
Cloud Architecture Training
+
Q3 2026
+
+
Scheduled
+
+
+
+
+
Leadership Programme
+
2027
+
+
Planned
+
+
+
+
+
+
+
+
+
Interim Head, Population Health & Data Analysis
+
+ - Led a cross-functional team of 12 across data, analytics, and population health workstreams
+ - Oversaw £220M prescribing budget with full analytical accountability and reporting to ICB board
+ - Identified £14.6M in efficiency savings through data-driven prescribing interventions
+ - Designed and deployed Power BI dashboards used by 200+ clinicians and commissioners
+ - Spearheaded SQL analytics transformation, migrating legacy Access databases to modern data stack
+ - Established team data literacy programme, upskilling 30+ non-technical staff in data interpretation
+
+
+
+
+
+
+
+
+
+
+
Interim Head, Population Health & Data Analysis
+
NHS Norfolk & Waveney ICB
+
2024 – 2025
+
+
+
+
+
+
£220M Prescribing Budget Oversight
+
Lead analyst & budget owner
+
2024
+
+
+
+
+
+
Senior Data Analyst — Medicines Optimisation
+
NHS Norfolk & Waveney ICB
+
2021 – 2024
+
+
+
+
+
+
SQL Analytics Transformation
+
Legacy migration project lead
+
2025
+
+
+
+
+
+
Power BI Data Analyst Associate
+
Microsoft Certified
+
2023
+
+
+
+
+
+
Prescribing Data Pharmacist
+
NHS Norwich CCG
+
2018 – 2021
+
+
+
+
+
+
Clinical Pharmacy Diploma
+
Professional development
+
2019
+
+
+
+
+
+
Community Pharmacist
+
Boots UK
+
2016 – 2018
+
+
+
+
+
+
MPharm (Hons) — 2:1
+
University of East Anglia
+
2011 – 2015
+
+
+
+
+
+
GPhC Registration
+
General Pharmaceutical Council
+
August 2016
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ESC
+
+
Experience
+
+
+
+
Interim Head, Population Health & Data Analysis
+
NHS Norfolk & Waveney ICB · 2024–2025
+
+
+
+
+
+
Senior Data Analyst — Medicines Optimisation
+
NHS Norfolk & Waveney ICB · 2021–2024
+
+
+
+
+
+
Prescribing Data Pharmacist
+
NHS Norwich CCG · 2018–2021
+
+
+
+
+
+
Community Pharmacist
+
Boots UK · 2016–2018
+
+
Core Skills
+
+
+
+
Data Analysis — 95%
+
Primary expertise · NHS population data
+
+
+
+
+
+
Power BI — 92%
+
Dashboard design & deployment
+
+
+
+
+
+
Python — 90%
+
Data pipelines, automation, analytics
+
+
+
+
+
+
SQL — 88%
+
Advanced queries, database migration
+
+
+
+
+
+
JavaScript / TypeScript — 70%
+
Web development & tooling
+
+
Active Projects
+
+
+
+
£220M Prescribing Budget
+
Budget oversight & analytical accountability · 2024
+
+
+
+
+
+
SQL Analytics Transformation
+
Legacy migration to modern data stack · 2025
+
+
+
+
+
+
Team Data Literacy Programme
+
Upskilling 30+ non-technical staff · 2024
+
+
Achievements
+
+
+
+
£14.6M Efficiency Savings Identified
+
Data-driven prescribing interventions
+
+
+
+
+
+
£220M Budget Oversight
+
Full analytical accountability to ICB board
+
+
+
+
+
+
Power BI Dashboards for 200+ Users
+
Clinicians & commissioners across ICB
+
+
+
+
+
+
Team of 12 Led
+
Cross-functional data & population health
+
+
Education
+
+
+
+
MPharm (Hons) — 2:1
+
University of East Anglia · 2011–2015
+
+
+
+
+
+
GPhC Registration
+
General Pharmaceutical Council · August 2016
+
+
+
+
+
+
Power BI Data Analyst Associate
+
Microsoft Certified · 2023
+
+
+
+
+
+
Clinical Pharmacy Diploma
+
Professional development · 2019
+
+
Quick Actions
+
+
+
+
Download CV
+
Export as PDF
+
+
+
+
+
+
Send Email
+
andy@charlwood.xyz
+
+
+
+
+
+
View LinkedIn
+
Professional profile
+
+
+
+
+
+
View Projects
+
GitHub & portfolio
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Ralph/refs/ref-01-design-tokens.md b/Ralph/refs/ref-01-design-tokens.md
new file mode 100644
index 0000000..e5abb68
--- /dev/null
+++ b/Ralph/refs/ref-01-design-tokens.md
@@ -0,0 +1,99 @@
+# Reference: Task 1 — Design Tokens and Tailwind Config
+
+## Overview
+
+Update the design system from the dark-sidebar NHS Blue palette to the GP System concept's light teal palette. The concept reference is `References/GPSystemconcept.html`.
+
+## CSS Custom Properties (`src/index.css`)
+
+Add/update these variables in the PMR section (keep boot/ECG/login variables unchanged):
+
+```css
+/* GP System Dashboard tokens */
+--bg: #F0F5F4;
+--surface: #FFFFFF;
+--sidebar-bg: #F7FAFA;
+--text-primary: #1A2B2A;
+--text-secondary: #5B7A78;
+--text-tertiary: #8DA8A5;
+--accent: #0D6E6E;
+--accent-hover: #0A8080;
+--accent-light: rgba(10,128,128,0.08);
+--accent-border: rgba(10,128,128,0.18);
+--amber: #D97706;
+--amber-light: rgba(217,119,6,0.08);
+--amber-border: rgba(217,119,6,0.18);
+--success: #059669;
+--success-light: rgba(5,150,105,0.08);
+--success-border: rgba(5,150,105,0.18);
+--alert: #DC2626;
+--alert-light: rgba(220,38,38,0.08);
+--alert-border: rgba(220,38,38,0.18);
+--border: #D4E0DE;
+--border-light: #E4EDEB;
+--sidebar-width: 272px;
+--topbar-height: 48px;
+--radius: 8px;
+--radius-sm: 6px;
+--shadow-sm: 0 1px 2px rgba(26,43,42,0.05);
+--shadow-md: 0 2px 8px rgba(26,43,42,0.08);
+--shadow-lg: 0 8px 32px rgba(26,43,42,0.12);
+--font-body: var(--font-ui);
+--font-mono: 'Geist Mono', 'Fira Code', monospace;
+```
+
+## Tailwind Config (`tailwind.config.js`)
+
+Update the `extend` section:
+
+### Colors
+```js
+colors: {
+ 'pmr-bg': '#F0F5F4',
+ 'pmr-surface': '#FFFFFF',
+ 'pmr-sidebar': '#F7FAFA',
+ 'pmr-accent': '#0D6E6E',
+ 'pmr-accent-hover': '#0A8080',
+ 'pmr-text-primary': '#1A2B2A',
+ 'pmr-text-secondary': '#5B7A78',
+ 'pmr-text-tertiary': '#8DA8A5',
+ 'pmr-border': '#D4E0DE',
+ 'pmr-border-light': '#E4EDEB',
+ 'pmr-success': '#059669',
+ 'pmr-amber': '#D97706',
+ 'pmr-alert': '#DC2626',
+ 'pmr-purple': '#7C3AED',
+ // Keep pmr-nhsblue for backward compat during transition
+ 'pmr-nhsblue': '#005EB8',
+ // Keep pmr-content as fallback
+ 'pmr-content': '#F0F5F4',
+}
+```
+
+### Shadows
+```js
+boxShadow: {
+ 'pmr-sm': '0 1px 2px rgba(26,43,42,0.05)',
+ 'pmr-md': '0 2px 8px rgba(26,43,42,0.08)',
+ 'pmr-lg': '0 8px 32px rgba(26,43,42,0.12)',
+ // Keep old pmr shadow as alias during transition
+ 'pmr': '0 1px 2px rgba(26,43,42,0.05)',
+}
+```
+
+### Border Radius
+```js
+borderRadius: {
+ 'card': '8px', // was 4px — now 8px per concept
+ 'card-sm': '6px', // inner elements
+ 'login': '12px', // login card exception
+}
+```
+
+## What NOT to Change
+
+- Boot phase variables (`--matrix-*`, `--terminal-*`)
+- ECG phase variables
+- Login phase background (`#1E293B` — handled by transition)
+- Font declarations (Elvaro, Blumir, Geist Mono, Fira Code already set up correctly)
+- Breakpoint values
diff --git a/Ralph/refs/ref-02-data-types.md b/Ralph/refs/ref-02-data-types.md
new file mode 100644
index 0000000..a00def8
--- /dev/null
+++ b/Ralph/refs/ref-02-data-types.md
@@ -0,0 +1,203 @@
+# Reference: Task 2 — Data Files and Types
+
+## Overview
+
+Create new data files for dashboard-specific content and update the type system. All CV content must match `References/CV_v4.md` exactly.
+
+## New Data Files
+
+### `src/data/profile.ts`
+
+```typescript
+export const personalStatement = `Healthcare leader combining clinical pharmacy expertise with proficiency in Python, SQL, and data analytics, self-taught over the past decade through a drive to find root causes in data and build the most efficient solutions to complex problems. Currently leading population health analytics for NHS Norfolk & Waveney ICB, serving a population of 1.2 million. Experienced in working with messy, real-world prescribing data at scale to deliver actionable insights—from financial scenario modelling and pharmaceutical rebate negotiation to algorithm design and population-level pathway development. Proven track record of identifying and prioritising efficiency programmes worth £14.6M+ through automated, data-driven analysis. Skilled at translating complex clinical, financial, and analytical requirements into clear recommendations for executive stakeholders.`
+```
+
+### `src/data/tags.ts`
+
+```typescript
+import type { Tag } from '@/types/pmr'
+
+export const tags: Tag[] = [
+ { label: 'Pharmacist', colorVariant: 'teal' },
+ { label: 'Data Lead', colorVariant: 'teal' },
+ { label: 'NHS', colorVariant: 'teal' },
+ { label: 'Population Health', colorVariant: 'amber' },
+ { label: 'BI & Analytics', colorVariant: 'green' },
+]
+```
+
+### `src/data/alerts.ts`
+
+```typescript
+import type { Alert } from '@/types/pmr'
+
+export const alerts: Alert[] = [
+ {
+ message: '£14.6M SAVINGS IDENTIFIED',
+ severity: 'alert',
+ icon: 'AlertTriangle', // lucide-react icon name
+ },
+ {
+ message: '£220M BUDGET OVERSIGHT',
+ severity: 'amber',
+ icon: 'AlertCircle', // lucide-react icon name
+ },
+]
+```
+
+### `src/data/kpis.ts`
+
+```typescript
+import type { KPI } from '@/types/pmr'
+
+export const kpis: KPI[] = [
+ {
+ id: 'budget',
+ value: '£220M',
+ label: 'Budget Oversight',
+ sub: 'NHS prescribing',
+ colorVariant: 'green',
+ explanation: 'Managed the ICB\'s total prescribing budget with sophisticated forecasting models identifying cost pressures and enabling proactive financial planning across Norfolk & Waveney.',
+ },
+ {
+ id: 'savings',
+ value: '£14.6M',
+ label: 'Efficiency Savings',
+ sub: 'Identified & tracked',
+ colorVariant: 'amber',
+ explanation: 'Identified and prioritised a £14.6M efficiency programme through comprehensive data analysis; achieved over-target performance through targeted, evidence-based interventions across the integrated care system.',
+ },
+ {
+ id: 'years',
+ value: '9+',
+ label: 'Years in NHS',
+ sub: 'Since 2016',
+ colorVariant: 'teal',
+ explanation: 'Continuous NHS service since August 2016, progressing from community pharmacy through prescribing data analysis to system-level population health data leadership.',
+ },
+ {
+ id: 'team',
+ value: '12',
+ label: 'Team Size Led',
+ sub: 'Cross-functional',
+ colorVariant: 'green',
+ explanation: 'Led a cross-functional team of 12 spanning data analysts, population health specialists, and pharmacists across data, analytics, and population health workstreams.',
+ },
+]
+```
+
+### `src/data/skills.ts`
+
+Skills presented as "medications" with frequency (user-specified values) and years of experience.
+
+```typescript
+import type { SkillMedication } from '@/types/pmr'
+
+export const skills: SkillMedication[] = [
+ {
+ id: 'data-analysis',
+ name: 'Data Analysis',
+ frequency: 'Twice daily',
+ startYear: 2016,
+ yearsOfExperience: 9,
+ proficiency: 95,
+ category: 'Technical',
+ status: 'Active',
+ icon: 'BarChart3',
+ },
+ {
+ id: 'python',
+ name: 'Python',
+ frequency: 'Daily',
+ startYear: 2019,
+ yearsOfExperience: 6,
+ proficiency: 90,
+ category: 'Technical',
+ status: 'Active',
+ icon: 'Code2',
+ },
+ {
+ id: 'sql',
+ name: 'SQL',
+ frequency: 'Daily',
+ startYear: 2018,
+ yearsOfExperience: 7,
+ proficiency: 88,
+ category: 'Technical',
+ status: 'Active',
+ icon: 'Database',
+ },
+ {
+ id: 'power-bi',
+ name: 'Power BI',
+ frequency: 'Once weekly',
+ startYear: 2020,
+ yearsOfExperience: 5,
+ proficiency: 92,
+ category: 'Technical',
+ status: 'Active',
+ icon: 'PieChart',
+ },
+ {
+ id: 'javascript-typescript',
+ name: 'JavaScript / TypeScript',
+ frequency: 'When required',
+ startYear: 2022,
+ yearsOfExperience: 3,
+ proficiency: 70,
+ category: 'Technical',
+ status: 'Active',
+ icon: 'FileCode2',
+ },
+]
+```
+
+Note: Additional domain/leadership skills can be added later. Start with the 5 technical skills the user specified frequencies for.
+
+## Type Updates (`src/types/pmr.ts`)
+
+Add these interfaces (keep all existing types):
+
+```typescript
+export interface Tag {
+ label: string
+ colorVariant: 'teal' | 'amber' | 'green'
+}
+
+export interface Alert {
+ message: string
+ severity: 'alert' | 'amber'
+ icon: string
+}
+
+export interface KPI {
+ id: string
+ value: string
+ label: string
+ sub: string
+ colorVariant: 'green' | 'amber' | 'teal'
+ explanation: string
+}
+
+export interface SkillMedication {
+ id: string
+ name: string
+ frequency: string
+ startYear: number
+ yearsOfExperience: number
+ proficiency: number
+ category: 'Technical' | 'Domain' | 'Leadership'
+ status: 'Active' | 'Historical'
+ icon: string
+}
+```
+
+## Existing Data — No Changes
+
+These files remain untouched:
+- `src/data/patient.ts`
+- `src/data/consultations.ts`
+- `src/data/medications.ts`
+- `src/data/problems.ts`
+- `src/data/investigations.ts`
+- `src/data/documents.ts`
diff --git a/Ralph/refs/ref-03-topbar-sidebar.md b/Ralph/refs/ref-03-topbar-sidebar.md
new file mode 100644
index 0000000..25c65f2
--- /dev/null
+++ b/Ralph/refs/ref-03-topbar-sidebar.md
@@ -0,0 +1,147 @@
+# Reference: Tasks 4-6 — TopBar and Sidebar
+
+## Concept Reference
+
+All specs below are derived from `References/GPSystemconcept.html`. Open it in a browser for visual reference.
+
+---
+
+## Task 4: TopBar Component
+
+### File: `src/components/TopBar.tsx`
+
+### Structure
+```
+┌─────────────────────────────────────────────────────────────┐
+│ [🏠] Headhunt Medical Center Remote │ [🔍 Search... Ctrl+K] │ Dr. A.CHARLWOOD · Active Session · 12:23 [Ctrl+K] │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Specs
+
+**Container:**
+- `position: fixed`, `top: 0`, `left: 0`, `right: 0`
+- `height: var(--topbar-height)` (48px)
+- `background: var(--surface)` (#FFFFFF)
+- `border-bottom: 1px solid var(--border)` (#D4E0DE)
+- `display: flex`, `align-items: center`, `justify-content: space-between`
+- `padding: 0 20px`
+- `z-index: 100`
+
+**Brand (left):**
+- `Home` icon from lucide-react (18px, accent color)
+- Text: "Headhunt Medical Center" — 13px, font-ui, 600 weight, text-primary
+- Version badge: "Remote" — 11px, 400 weight, text-tertiary, margin-left 2px
+
+**Search bar (center):**
+- Wrapper: `max-width: 560px`, `min-width: 400px`
+- Container: `height: 42px`, `border: 1.5px solid var(--border)`, `border-radius: var(--radius)` (8px), `padding: 0 14px`, white bg
+- Search icon (16px, tertiary) + input + "Ctrl+K" kbd badge
+- Input: 13px, font-body, placeholder "Search records, experience, skills... (Ctrl+K)"
+- Hover: `border-color: var(--accent-border)`
+- Focus: `border-color: var(--accent)`, `box-shadow: 0 0 0 3px rgba(13,110,110,0.12)`
+- **On click/focus: opens Command Palette** (Task 18). Does NOT do inline search.
+- Kbd badge: mono font, 10px, tertiary, bg: var(--bg), border, padding 2px 6px, radius 4px
+
+**Session info (right):**
+- Text: "Dr. A.CHARLWOOD · Active Session · [time]" — 12px, text-secondary
+- Session pill: mono 11px, tertiary, `background: var(--accent-light)`, `padding: 3px 10px`, radius 4px, `border: 1px solid var(--accent-border)`
+- Ctrl+K shortcut badge (same style as search bar badge)
+
+**Responsive:**
+- Mobile (<768px): hide center search bar. Show only brand + session info (or hamburger).
+- Tablet: search bar may shrink.
+
+---
+
+## Task 5: Sidebar — PersonHeader
+
+### File: `src/components/Sidebar.tsx`
+
+### Overall Sidebar Container
+- `width: var(--sidebar-width)` (272px)
+- `min-width: var(--sidebar-width)`
+- `background: var(--sidebar-bg)` (#F7FAFA)
+- `border-right: 1px solid var(--border)` (#D4E0DE)
+- `overflow-y: auto`, custom scrollbar (4px width, transparent track, border-colored thumb)
+- `padding: 20px 16px`
+- `display: flex`, `flex-direction: column`, `gap: 2px`
+
+### PersonHeader Section
+Bordered below: `border-bottom: 2px solid var(--accent)`, `padding-bottom: 16px`, `margin-bottom: 6px`
+
+**Avatar:**
+- 52px × 52px circle
+- `background: linear-gradient(135deg, var(--accent), #0A8080)`
+- White text "AC", 700 weight, 18px, centered
+- `box-shadow: 0 2px 8px rgba(13,110,110,0.25)`
+- `margin-bottom: 12px`
+
+**Name:**
+- "CHARLWOOD, Andrew"
+- 15px, 700 weight, text-primary, `letter-spacing: -0.01em`
+
+**Title:**
+- "Pharmacy Data Technologist"
+- 11.5px, mono font, 400 weight, text-secondary
+- `margin-top: 2px`
+
+**Status badge:**
+- Inline-flex, gap 5px
+- `margin-top: 8px`
+- 11px, 500 weight, success color (#059669)
+- `background: var(--success-light)`, `border: 1px solid var(--success-border)`
+- `padding: 3px 9px`, `border-radius: 20px` (pill)
+- Animated dot: 6px circle, success color, `animation: pulse 2s infinite` (opacity 1→0.4→1)
+- Text: "Open to Opportunities"
+
+**Details grid:**
+- `display: grid`, `grid-template-columns: 1fr`, `gap: 6px`, `margin-top: 12px`
+- Each row: `display: flex`, `justify-content: space-between`, `align-items: center`, 11.5px, `padding: 2px 0`
+- Label: text-tertiary, 400 weight
+- Value: text-primary, 500 weight, text-align right
+- GPhC No. value: mono font, 11px, `letter-spacing: 0.12em` → "2211810"
+- Education value: "MPharm 2.1 (Hons)"
+- Location: "Norwich, Norfolk"
+- Phone: link in accent color, `text-decoration: none`, underline on hover → "07795 553 088"
+- Email: link → "andy@charlwood.xyz"
+- Registered: "August 2016"
+
+**Data source:** `src/data/patient.ts`
+
+---
+
+## Task 6: Sidebar — Tags + Alerts
+
+### Section Title Component
+Reusable within sidebar. Used for "Tags", "Alerts / Highlights", and any future sections.
+
+- `font-size: 10px`, `font-weight: 600`, `text-transform: uppercase`, `letter-spacing: 0.08em`
+- Color: text-tertiary
+- `margin-bottom: 10px`
+- Flex row with `::after` pseudo-element: `flex: 1`, `height: 1px`, `background: var(--border-light)`, `gap: 6px`
+
+### Tags Section
+- Container: `display: flex`, `flex-wrap: wrap`, `gap: 5px`
+- Each tag: 10.5px, 500 weight, `padding: 3px 8px`, `border-radius: 4px`, inline-flex, `line-height: 1.3`
+- **Color variants:**
+ - `teal`: `background: var(--accent-light)`, `color: var(--accent)`, `border: 1px solid var(--accent-border)`
+ - `amber`: `background: var(--amber-light)`, `color: var(--amber)`, `border: 1px solid var(--amber-border)`
+ - `green`: `background: var(--success-light)`, `color: var(--success)`, `border: 1px solid var(--success-border)`
+- **Data source:** `src/data/tags.ts`
+
+### Alerts / Highlights Section
+- Container: `display: flex`, `flex-direction: column`, `gap: 6px`
+- Each flag item: `display: flex`, `align-items: center`, `gap: 8px`
+ - 11px, 700 weight, `padding: 7px 10px`, `border-radius: var(--radius-sm)` (6px), `letter-spacing: 0.02em`
+- **Alert variant** (red):
+ - `background: var(--alert-light)`, `color: var(--alert)`, `border: 1px solid var(--alert-border)`
+ - Icon: `AlertTriangle` from lucide-react (14px, 2.5 stroke-width)
+- **Amber variant:**
+ - `background: var(--amber-light)`, `color: var(--amber)`, `border: 1px solid var(--amber-border)`
+ - Icon: `AlertCircle` from lucide-react (14px, 2.5 stroke-width)
+- Icon container: 16px square, flex center, flex-shrink-0
+- **Data source:** `src/data/alerts.ts`
+
+### Section Padding
+Each sidebar section: `padding: 14px 0 6px`
diff --git a/Ralph/refs/ref-04-dashboard-layout.md b/Ralph/refs/ref-04-dashboard-layout.md
new file mode 100644
index 0000000..9cc553d
--- /dev/null
+++ b/Ralph/refs/ref-04-dashboard-layout.md
@@ -0,0 +1,136 @@
+# Reference: Task 7 — DashboardLayout
+
+## Overview
+
+Create the main layout component that replaces `PMRInterface.tsx`. This is the container that houses TopBar, Sidebar, and the scrollable card grid of tiles.
+
+## File: `src/components/DashboardLayout.tsx`
+
+### Layout Structure
+
+```
+┌────────────────────────────────────────────────────┐
+│ TopBar (fixed, z-100, height: 48px) │
+├──────────┬─────────────────────────────────────────┤
+│ │ │
+│ Sidebar │ — scrollable card grid │
+│ (272px) │ padding: 24px 28px 40px │
+│ fixed │ │
+│ │ grid: 1fr 1fr, gap: 16px │
+│ │ │
+│ │ [PatientSummary — full] │
+│ │ [LatestResults] [CoreSkills] │
+│ │ [LastConsultation — full] │
+│ │ [CareerActivity — full] │
+│ │ [Education — full] │
+│ │ [Projects — full] │
+│ │ │
+└──────────┴─────────────────────────────────────────┘
+```
+
+### CSS Layout
+
+```
+.layout {
+ display: flex;
+ margin-top: var(--topbar-height); /* 48px */
+ height: calc(100vh - var(--topbar-height));
+}
+
+.sidebar {
+ /* See ref-03-topbar-sidebar.md for sidebar specs */
+ width: var(--sidebar-width);
+ min-width: var(--sidebar-width);
+ /* ... */
+}
+
+.main {
+ flex: 1;
+ overflow-y: auto;
+ padding: 24px 28px 40px;
+}
+
+.card-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 16px;
+}
+
+@media (max-width: 900px) {
+ .card-grid {
+ grid-template-columns: 1fr;
+ }
+}
+```
+
+Use Tailwind classes for all of this — the CSS above is for reference only.
+
+### Framer Motion Entrance Animations
+
+Staggered entrance when dashboard first renders (after login):
+
+1. **TopBar**: slides down from `-48px`, 200ms ease-out
+2. **Sidebar**: slides from `-272px` left, 250ms ease-out, 50ms delay
+3. **Main content**: fades in (opacity 0→1), 300ms, 150ms delay
+
+```typescript
+const topbarVariants = {
+ hidden: { y: -48, opacity: 0 },
+ visible: { y: 0, opacity: 1, transition: { duration: 0.2, ease: 'easeOut' } }
+}
+
+const sidebarVariants = {
+ hidden: { x: -272, opacity: 0 },
+ visible: { x: 0, opacity: 1, transition: { duration: 0.25, ease: 'easeOut', delay: 0.05 } }
+}
+
+const contentVariants = {
+ hidden: { opacity: 0 },
+ visible: { opacity: 1, transition: { duration: 0.3, delay: 0.15 } }
+}
+```
+
+With `prefers-reduced-motion`: all durations → 0, no delays.
+
+### Tile Ordering in Grid
+
+The card grid renders tiles in this order:
+1. `PatientSummaryTile` — `grid-column: 1 / -1` (full width)
+2. `LatestResultsTile` — single column (left)
+3. `CoreSkillsTile` — single column (right)
+4. `LastConsultationTile` — `grid-column: 1 / -1` (full width)
+5. `CareerActivityTile` — `grid-column: 1 / -1` (full width)
+6. `EducationTile` — `grid-column: 1 / -1` (full width)
+7. `ProjectsTile` — `grid-column: 1 / -1` (full width)
+
+### App.tsx Wiring
+
+In `src/App.tsx`, the PMR phase currently renders ``. Change it to render ``.
+
+```typescript
+// In App.tsx phase switch:
+case 'pmr':
+ return
+```
+
+Keep all other phases (boot, ecg, login) unchanged. The SkipButton that skips to login should still work.
+
+### Scrollbar Styling
+
+Main content area scrollbar (matches concept):
+- Width: 6px
+- Track: transparent
+- Thumb: var(--border) (#D4E0DE), border-radius 3px
+
+### Command Palette Integration
+
+The DashboardLayout should render the `CommandPalette` component (from Task 18) at the layout level, so it overlays the entire dashboard when triggered. For now (Task 7), just add a placeholder comment or empty div where it will go. The TopBar search bar's click handler should be wired to open the palette (but the palette itself comes in Task 18).
+
+### Background Color Transition
+
+The login screen has background `#1E293B`. The dashboard has background `#F0F5F4`. This transition should happen smoothly. Options:
+1. The DashboardLayout entrance animation covers the transition (content fades in over the dark background, replacing it)
+2. A brief CSS transition on the body/root background color
+3. Handle it in App.tsx with a state-based background
+
+The simplest approach is option 1 — the dashboard's entrance animation effectively replaces the dark login background with the light dashboard.
diff --git a/Ralph/refs/ref-05-card-and-top-tiles.md b/Ralph/refs/ref-05-card-and-top-tiles.md
new file mode 100644
index 0000000..afb2989
--- /dev/null
+++ b/Ralph/refs/ref-05-card-and-top-tiles.md
@@ -0,0 +1,144 @@
+# Reference: Tasks 8-11 — Card Component and Top Tiles
+
+## Task 8: Reusable Card Component
+
+### File: `src/components/Card.tsx`
+
+### Base Card
+```typescript
+interface CardProps {
+ children: React.ReactNode
+ full?: boolean // spans both grid columns
+ className?: string
+}
+```
+
+**Styling:**
+- `background: var(--surface)` (#FFFFFF)
+- `border: 1px solid var(--border-light)` (#E4EDEB)
+- `border-radius: var(--radius)` (8px)
+- `padding: 20px`
+- `box-shadow: var(--shadow-sm)` (0 1px 2px rgba(26,43,42,0.05))
+- Hover: `box-shadow: var(--shadow-md)`, `border-color: var(--border)` (#D4E0DE)
+- `transition: box-shadow 0.2s, border-color 0.2s`
+- Full variant: `grid-column: 1 / -1`
+
+### CardHeader Sub-component
+```typescript
+interface CardHeaderProps {
+ dotColor: 'teal' | 'amber' | 'green' | 'alert' | 'purple'
+ title: string
+ rightText?: string
+}
+```
+
+**Styling:**
+- `display: flex`, `align-items: center`, `gap: 8px`, `margin-bottom: 16px`
+- Dot: 8px circle, `border-radius: 50%`, flex-shrink-0
+ - teal: `#0D6E6E`, amber: `#D97706`, green: `#059669`, alert: `#DC2626`, purple: `#7C3AED`
+- Title: 12px, 600 weight, uppercase, `letter-spacing: 0.06em`, text-secondary (#5B7A78)
+- Right text (optional): 10px, 400 weight, normal case, no tracking, text-tertiary, mono font, `margin-left: auto`
+
+---
+
+## Task 9: PatientSummary Tile
+
+### File: `src/components/tiles/PatientSummaryTile.tsx`
+
+**Layout:** Full-width card, first in grid.
+
+**Content:**
+- CardHeader: teal dot + "PATIENT SUMMARY"
+- Body: personal statement text from `src/data/profile.ts`
+- Typography: 13px, font-ui, `line-height: 1.6` (leading-relaxed), text-primary
+- No interactive elements — read-only
+
+**Data:** `import { personalStatement } from '@/data/profile'`
+
+This is a simple tile. No expansion, no interactivity.
+
+---
+
+## Task 10: LatestResults Tile
+
+### File: `src/components/tiles/LatestResultsTile.tsx`
+
+**Layout:** Half-width card (single grid column). Sits in the LEFT column.
+
+**Content:**
+- CardHeader: teal dot + "LATEST RESULTS" + right text "Updated May 2025"
+- 2×2 metric grid inside
+
+**Metric Grid:**
+- `display: grid`, `grid-template-columns: 1fr 1fr`, `gap: 12px`
+
+**Each Metric Card:**
+- `padding: 14px`, `border-radius: var(--radius-sm)` (6px)
+- `border: 1px solid var(--border-light)`, `background: var(--bg)` (#F0F5F4)
+- Value: 22px, 700 weight, `letter-spacing: -0.02em`, `line-height: 1.2`
+ - Color by variant: green=#059669, amber=#D97706, teal=#0D6E6E
+- Label: 11px, text-secondary, 500 weight, `margin-top: 3px`
+- Sub: 10px, text-tertiary, mono font, `margin-top: 4px`
+
+**Data:** `import { kpis } from '@/data/kpis'`
+
+**KPI flip prep:** Each metric card should accept a `data-kpi-id` or an `onClick` prop placeholder — Task 17 will add the flip interaction. For now, render as static display.
+
+**Values:**
+| Value | Label | Sub | Color |
+|-------|-------|-----|-------|
+| £220M | Budget Oversight | NHS prescribing | green |
+| £14.6M | Efficiency Savings | Identified & tracked | amber |
+| 9+ | Years in NHS | Since 2016 | teal |
+| 12 | Team Size Led | Cross-functional | green |
+
+---
+
+## Task 11: CoreSkills Tile ("Repeat Medications")
+
+### File: `src/components/tiles/CoreSkillsTile.tsx`
+
+**Layout:** Half-width card (single grid column). Sits in the RIGHT column, next to LatestResults.
+
+**Content:**
+- CardHeader: amber dot + "REPEAT MEDICATIONS"
+- Vertical list of skill items, `gap: 10px`
+
+**Each Skill Item:**
+Matches the concept's `.dev-item` pattern:
+- `display: flex`, `align-items: center`, `gap: 10px`
+- 12.5px font, `padding: 10px 12px`
+- `background: var(--bg)` (#F0F5F4), `border-radius: var(--radius-sm)` (6px)
+- `border: 1px solid var(--border-light)`
+
+**Item structure:**
+- **Icon container** (28px square, 6px radius):
+ - `background: var(--accent-light)`, `color: var(--accent)` (teal)
+ - Lucide icon inside (14px): `BarChart3` for Data Analysis, `Code2` for Python, `Database` for SQL, `PieChart` for Power BI, `FileCode2` for JS/TS
+- **Text block** (flex: 1):
+ - Name: 600 weight, text-primary (e.g., "Data Analysis")
+ - Frequency + years: 11px, text-tertiary, mono font (e.g., "Twice daily · Since 2016 · 9 yrs")
+- **Optional status badge**: 10px, 500 weight, pill shape (padding 3px 8px, border-radius 20px), flex-shrink-0
+ - Could show proficiency or "Active" status
+
+**Medication metaphor format:**
+```
+[📊] Data Analysis Active
+ Twice daily · Since 2016 · 9 yrs
+
+[💻] Python Active
+ Daily · Since 2019 · 6 yrs
+
+[🗄️] SQL Active
+ Daily · Since 2018 · 7 yrs
+
+[📈] Power BI Active
+ Once weekly · Since 2020 · 5 yrs
+
+[📝] JavaScript / TypeScript Active
+ When required · Since 2022 · 3 yrs
+```
+
+**Data:** `import { skills } from '@/data/skills'`
+
+**Expansion prep:** Each item should accept an onClick prop placeholder — Task 16 will add expansion to show prescribing history (from existing medications data).
diff --git a/Ralph/refs/ref-06-bottom-tiles.md b/Ralph/refs/ref-06-bottom-tiles.md
new file mode 100644
index 0000000..a2b3282
--- /dev/null
+++ b/Ralph/refs/ref-06-bottom-tiles.md
@@ -0,0 +1,204 @@
+# Reference: Tasks 12-15 — Bottom Tiles
+
+## Task 12: LastConsultation Tile
+
+### File: `src/components/tiles/LastConsultationTile.tsx`
+
+**Layout:** Full-width card.
+
+**Content:**
+- CardHeader: green dot + "LAST CONSULTATION" + right text "Most recent role"
+
+**Header info row:**
+- `display: flex`, `flex-wrap: wrap`, `gap: 16px`
+- `margin-bottom: 14px`, `padding-bottom: 14px`, `border-bottom: 1px solid var(--border-light)`
+- Each field:
+ - Label: 10px, uppercase, `letter-spacing: 0.06em`, text-tertiary
+ - Value: 11.5px, 600 weight, text-primary
+
+| Label | Value |
+|-------|-------|
+| Date | May 2025 |
+| Organisation | NHS Norfolk & Waveney ICB |
+| Type | Permanent · Full-time |
+| Band | 8a |
+
+**Role title:**
+- "Interim Head, Population Health & Data Analysis"
+- 13.5px, 600 weight, `color: var(--accent)` (#0D6E6E)
+- `margin-bottom: 12px`
+
+**Bullet list:**
+- `list-style: none`, flex column, `gap: 7px`
+- Each bullet: 12.5px, text-primary, `padding-left: 16px`, `line-height: 1.5`
+- Pseudo `::before`: 5px circle, accent color (#0D6E6E), `opacity: 0.5`, positioned left at top 7px
+
+**Bullets** (from first consultation's examination array):
+- Led a cross-functional team of 12 across data, analytics, and population health workstreams
+- Oversaw £220M prescribing budget with full analytical accountability and reporting to ICB board
+- Identified £14.6M in efficiency savings through data-driven prescribing interventions
+- Designed and deployed Power BI dashboards used by 200+ clinicians and commissioners
+- Spearheaded SQL analytics transformation, migrating legacy Access databases to modern data stack
+- Established team data literacy programme, upskilling 30+ non-technical staff in data interpretation
+
+**Data:** `import { consultations } from '@/data/consultations'` — use `consultations[0]` (the most recent).
+
+Map consultation fields:
+- date → Date field
+- organization → Organisation field
+- role → Role title
+- examination array → Bullet points
+
+---
+
+## Task 13: CareerActivity Tile
+
+### File: `src/components/tiles/CareerActivityTile.tsx`
+
+**Layout:** Full-width card.
+
+**Content:**
+- CardHeader: teal dot + "CAREER ACTIVITY" + right text "Full timeline"
+
+**Activity grid:**
+- `display: grid`, `grid-template-columns: 1fr 1fr`, `gap: 10px`
+- Below 900px: `grid-template-columns: 1fr` (single column)
+
+**Each activity item:**
+- `display: flex`, `gap: 10px`
+- `padding: 10px 12px`
+- `background: var(--bg)` (#F0F5F4)
+- `border-radius: var(--radius-sm)` (6px)
+- `border: 1px solid var(--border-light)`
+- 12px font
+- `transition: border-color 0.15s`
+- Hover: `border-color: var(--accent-border)`
+
+**Dot (left):**
+- 8px circle, flex-shrink-0, `margin-top: 2px` (aligns with text)
+- Color by type:
+ - Role: teal (#0D6E6E)
+ - Project: amber (#D97706)
+ - Certification: green (#059669)
+ - Education: purple (#7C3AED)
+
+**Content (right):**
+- Title: 600 weight, text-primary, `line-height: 1.3`
+- Meta: 11px, text-secondary, `margin-top: 2px`
+- Date: 10px, mono font, text-tertiary, `margin-top: 3px`
+
+**Building the timeline data:**
+
+Merge entries from multiple data sources, sorted newest-first:
+
+```typescript
+type ActivityType = 'role' | 'project' | 'cert' | 'edu'
+
+interface ActivityEntry {
+ id: string
+ type: ActivityType
+ title: string
+ meta: string
+ date: string
+ sortYear: number // for sorting
+}
+```
+
+Sources:
+1. `consultations` → type "role": title=role, meta=organization, date=duration
+2. `investigations` (selected key ones) → type "project": title=name, meta=short description, date=year
+3. `documents` where type='Certificate' → type "cert": title=title, meta=source, date=date
+4. `documents` where type='Results' (MPharm) → type "edu": title=title, meta=source, date=date
+
+Match the concept HTML entries:
+| Type | Title | Meta | Date |
+|------|-------|------|------|
+| role | Interim Head, Population Health & Data Analysis | NHS Norfolk & Waveney ICB | 2024 – 2025 |
+| project | £220M Prescribing Budget Oversight | Lead analyst & budget owner | 2024 |
+| role | Senior Data Analyst — Medicines Optimisation | NHS Norfolk & Waveney ICB | 2021 – 2024 |
+| project | SQL Analytics Transformation | Legacy migration project lead | 2025 |
+| cert | Power BI Data Analyst Associate | Microsoft Certified | 2023 |
+| role | Prescribing Data Pharmacist | NHS Norwich CCG | 2018 – 2021 |
+| cert | Clinical Pharmacy Diploma | Professional development | 2019 |
+| role | Community Pharmacist | Boots UK | 2016 – 2018 |
+| edu | MPharm (Hons) — 2:1 | University of East Anglia | 2011 – 2015 |
+| cert | GPhC Registration | General Pharmaceutical Council | August 2016 |
+
+**Expansion prep:** Activity items should accept onClick for Task 16 (expand to show full role/project detail).
+
+---
+
+## Task 14: Education Tile
+
+### File: `src/components/tiles/EducationTile.tsx`
+
+**Layout:** Full-width card, below Career Activity.
+
+**Content:**
+- CardHeader: purple dot (#7C3AED) + "EDUCATION"
+
+**Education entries:**
+Vertical stack of education items.
+
+Each item:
+- `padding: 7px 10px`
+- `background: var(--surface)` (#FFFFFF)
+- `border: 1px solid var(--border-light)`
+- `border-radius: var(--radius-sm)` (6px)
+- 11.5px, text-primary
+
+Structure:
+- Degree name: 600 weight, `display: block`
+- Detail: text-secondary, 11px, `margin-top: 2px`
+
+**Entries** (from CV):
+| Degree | Detail |
+|--------|--------|
+| MPharm (Hons) — 2:1 | University of East Anglia · 2015 |
+| NHS Leadership Academy — Mary Seacole Programme | 2018 · 78% |
+| A-Levels: Mathematics (A*), Chemistry (B), Politics (C) | Highworth Grammar School · 2009–2011 |
+
+**Data:** Filter `src/data/documents.ts` for education entries, or hardcode from CV since the documents data may not have all education entries.
+
+Note: The concept HTML only shows the MPharm entry. But the CV has more education. Include all CV education entries.
+
+---
+
+## Task 15: Projects Tile
+
+### File: `src/components/tiles/ProjectsTile.tsx`
+
+**Layout:** Full-width card, prominent position.
+
+**Content:**
+- CardHeader: amber dot + "ACTIVE PROJECTS"
+
+**Project entries:**
+Vertical list, styled as interactive items.
+
+Each project:
+- `display: flex`, `align-items: flex-start`, `gap: 8px`
+- `padding: 7px 10px`
+- `background: var(--surface)`, `border: 1px solid var(--border-light)`
+- `border-radius: var(--radius-sm)` (6px)
+- 11.5px, text-primary
+- Hover: `border-color: var(--accent-border)`
+- `transition: border-color 0.15s`
+
+Structure:
+- **Status dot** (7px circle, flex-shrink-0, `margin-top: 4px`):
+ - Complete: success (#059669)
+ - Ongoing: accent (#0D6E6E)
+ - Live: success with pulse animation
+- **Project name**: text-primary, flex 1
+- **Year badge**: 10px, mono font, text-tertiary, `margin-left: auto`, flex-shrink-0
+
+**Data:** `import { investigations } from '@/data/investigations'`
+
+Map investigations to projects:
+- name → Project name
+- status → dot color
+- requestedYear → Year badge
+- resultSummary → Available for expansion (Task 16)
+
+**Expansion prep:** Each item should accept onClick for Task 16 (expand to show methodology, tech stack, results).
diff --git a/Ralph/refs/ref-07-interactions.md b/Ralph/refs/ref-07-interactions.md
new file mode 100644
index 0000000..3256ad9
--- /dev/null
+++ b/Ralph/refs/ref-07-interactions.md
@@ -0,0 +1,248 @@
+# Reference: Tasks 16-18 — Interactions
+
+## Task 16: Tile Expansion System
+
+### Overview
+
+Three tiles have expandable items: CareerActivity (roles), Projects, and CoreSkills. Clicking an item expands it in-place to reveal detail, like expanding a clinical record entry.
+
+### Expansion Pattern (consistent across all tiles)
+
+**Animation:**
+- Framer Motion `AnimatePresence` + `motion.div`
+- Height-only animation: 200ms, ease-out
+- **No opacity fade on content** (guardrail)
+- `overflow: hidden` on the animated container
+
+```typescript
+
+ {isExpanded && (
+
+ {/* expanded content */}
+
+ )}
+
+```
+
+**Behavior:**
+- Single-expand accordion: only one item expanded at a time within each tile
+- Click expanded item again to collapse
+- Click different item: collapses current, expands new
+- State: `expandedItemId: string | null` in each tile component
+
+**Keyboard:**
+- Enter/Space: toggle expand/collapse
+- Escape: collapse current item
+- `aria-expanded` on each clickable item
+
+**Visual:**
+- Expanded content has slightly different background (`var(--bg)` or subtle border-left)
+- Colored left border on expanded panel (accent color for roles, amber for projects, teal for skills)
+- Content padding: 12-16px
+
+### CareerActivity Expansion (roles)
+
+When a role-type activity item is expanded:
+- Show full role details from corresponding consultation entry
+- Structure: role title, organization, date range
+- Achievement bullets (examination array from consultation)
+- Coded entries if available
+- Match expanded content to `consultations` data by mapping activity item to consultation
+
+### Projects Expansion
+
+When a project item is expanded:
+- Show from investigation data:
+ - Methodology
+ - Tech stack (as tags or inline list)
+ - Results (bulleted)
+ - External URL link if available ("View Results" button)
+
+### CoreSkills Expansion
+
+When a skill item is expanded:
+- Show "prescribing history" — a timeline of skill development
+- Source: Can use the existing `medications` data which has `prescribingHistory` entries
+- Format: vertical timeline with year markers and descriptions
+ - Timeline dots: accent color, 6px, with connecting line
+ - Year: mono font, 12px, semibold
+ - Description: 12px, regular
+
+---
+
+## Task 17: KPI Flip Cards
+
+### Overview
+
+In the LatestResults tile, each metric card can be clicked to "flip" and reveal an explanation of that KPI.
+
+### Flip Animation
+
+**CSS Perspective approach:**
+```css
+.metric-card {
+ perspective: 1000px;
+ cursor: pointer;
+}
+
+.metric-card-inner {
+ transition: transform 0.4s ease-in-out;
+ transform-style: preserve-3d;
+ position: relative;
+}
+
+.metric-card-inner.flipped {
+ transform: rotateY(180deg);
+}
+
+.metric-card-front,
+.metric-card-back {
+ backface-visibility: hidden;
+ position: absolute;
+ inset: 0;
+}
+
+.metric-card-back {
+ transform: rotateY(180deg);
+}
+```
+
+Or use Framer Motion `animate={{ rotateY: isFlipped ? 180 : 0 }}` with `perspective` on parent.
+
+**Behavior:**
+- Click to flip front → back
+- Click again to flip back → front
+- Only one card flipped at a time (clicking another card flips the current one back)
+- State: `flippedCardId: string | null` in LatestResultsTile
+
+**Front face:** Current metric display (value + label + sub) — same as Task 10.
+
+**Back face:**
+- `background: var(--accent-light)` (subtle teal tint)
+- `padding: 14px`
+- Text: 12px, text-secondary, `line-height: 1.5`
+- The explanation text from KPI data's `explanation` field
+
+**Reduced motion:**
+- No 3D flip animation
+- Instant content swap (front → back)
+- Could use a simple crossfade or just replace content immediately
+
+**Keyboard:**
+- Enter/Space to flip
+- Each metric card should be `tabIndex={0}` with appropriate `aria-label`
+
+**KPI Explanations** (from `src/data/kpis.ts`):
+- £220M: Budget management with forecasting models
+- £14.6M: Efficiency programme through data analysis
+- 9+ Years: NHS service progression since 2016
+- 12: Cross-functional team leadership
+
+---
+
+## Task 18: Command Palette
+
+### File: `src/components/CommandPalette.tsx`
+
+### Trigger
+- **Ctrl+K** (global `keydown` listener on `document`)
+- **Click** on TopBar search bar (or focus on search input)
+- The TopBar search input does NOT do inline search — it opens the palette
+
+### Overlay
+- `position: fixed`, `inset: 0`
+- `background: rgba(26,43,42,0.45)`
+- `backdrop-filter: blur(4px)`
+- `z-index: 1000`
+- Fade in: `opacity: 0 → 1`, `visibility: hidden → visible`, 200ms transition
+- Click overlay (outside modal) to close
+
+### Palette Modal
+- `width: 580px`, `max-height: 520px`
+- `background: var(--surface)` (#FFFFFF)
+- `border-radius: 12px`
+- `box-shadow: 0 20px 60px rgba(26,43,42,0.2), 0 0 0 1px rgba(26,43,42,0.08)`
+- `overflow: hidden`
+- Entrance: `transform: scale(0.97) translateY(-8px)` → `scale(1) translateY(0)`, 200ms cubic-bezier
+
+### Search Input
+- Flex row: search icon (18px, accent) + input + "ESC" hint badge
+- `padding: 14px 18px`, `border-bottom: 1px solid var(--border-light)`
+- Input: 15px, font-body, placeholder "Search records, experience, skills..."
+- ESC badge: mono 10px, tertiary, bg var(--bg), border, padding 2px 7px, radius 4px
+
+### Results Area
+- `overflow-y: auto`, `padding: 8px`, `flex: 1`
+- Custom scrollbar (4px)
+
+### Result Sections
+Section label: 10px, 600 weight, uppercase, `letter-spacing: 0.08em`, text-tertiary, `padding: 8px 10px 5px`
+
+### Result Items
+- `display: flex`, `align-items: center`, `gap: 10px`
+- `padding: 9px 10px`, `border-radius: var(--radius-sm)` (6px)
+- `cursor: pointer`, `transition: background 0.1s`
+- 13px, text-primary
+- Hover/selected: `background: var(--accent-light)`
+- Selected also gets: `outline: 1.5px solid var(--accent-border)`
+
+**Item structure:**
+- Icon container: 28px square, 6px radius, colored bg per section
+ - Experience: teal
+ - Core Skills: green
+ - Active Projects: amber
+ - Achievements: amber
+ - Education: purple
+ - Quick Actions: teal
+- Text: title (500 weight) + subtitle (11px, tertiary, truncated)
+- Optional badge: 10px, mono, tertiary
+
+### Fuzzy Search
+
+Adapt existing `src/lib/search.ts` (fuse.js integration):
+- Rebuild search index to include new data (skills from skills.ts, KPIs, etc.)
+- `threshold: 0.3`, weighted keys (title: 2, content: 1)
+- `minMatchCharLength: 2`
+- Group results by section
+- Highlight matching text in titles using `` with accent-light background
+
+### Keyboard Navigation
+- **Arrow Down/Up**: move selection through results
+- **Enter**: select highlighted result (navigate to section or trigger action)
+- **Escape**: close palette
+- `selectedIndex` state tracks which result is highlighted
+- Auto-scroll highlighted result into view
+
+### Quick Actions Section
+| Title | Subtitle | Action |
+|-------|----------|--------|
+| Download CV | Export as PDF | Trigger download |
+| Send Email | andy@charlwood.xyz | `mailto:` link |
+| View LinkedIn | Professional profile | External link |
+| View Projects | GitHub & portfolio | External link |
+
+### Footer
+- `display: flex`, `gap: 12px`
+- `padding: 10px 18px`, `border-top: 1px solid var(--border-light)`
+- 11px, text-tertiary
+- Keyboard hints: `↑ ↓ Navigate`, `Enter Select`, `Esc Close`
+- Each key in `` styled element
+
+### Reduced Motion
+- No scale/translate entrance animation
+- Instant show/hide (opacity only, or immediate)
+
+### State Management
+```typescript
+const [isOpen, setIsOpen] = useState(false)
+const [query, setQuery] = useState('')
+const [selectedIndex, setSelectedIndex] = useState(-1)
+```
+
+Render the palette at the DashboardLayout level so it overlays everything.
diff --git a/Ralph/refs/ref-08-polish.md b/Ralph/refs/ref-08-polish.md
new file mode 100644
index 0000000..0335374
--- /dev/null
+++ b/Ralph/refs/ref-08-polish.md
@@ -0,0 +1,156 @@
+# Reference: Tasks 19-21 — Polish
+
+## Task 19: Responsive Design
+
+### Desktop (>1024px)
+- Full sidebar (272px) + TopBar + 2-column card grid
+- All tiles at full spec (as designed in Tasks 8-15)
+- Command palette at 580px width
+
+### Tablet (768–1024px)
+- Sidebar: collapse to icon-only (56px) or hide entirely with toggle
+- TopBar: full, but search bar may shrink (reduce min-width)
+- Card grid: can stay 2-column if space permits, or switch to 1-column
+- Activity grid inside CareerActivity tile: switch to 1-column
+
+### Mobile (<768px)
+- Sidebar: hidden entirely (off-canvas or removed)
+- TopBar: simplified — brand text may truncate, hide search bar center section
+- Navigation: consider a hamburger menu or bottom nav for key actions
+- Card grid: single column
+- All tiles stack vertically (full-width)
+- Metric grid in LatestResults: stays 2x2 (compact enough)
+- Activity grid in CareerActivity: single column
+- Touch targets: all clickable elements 48px+ minimum
+- Command palette: full-width with reduced padding
+
+### Breakpoint Strategy
+Use Tailwind responsive prefixes:
+- `lg:` for desktop (>1024px)
+- `md:` for tablet (>768px)
+- Default styles for mobile-first
+
+### Key responsive classes:
+```
+/* Card grid */
+grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-[16px]
+
+/* Sidebar visibility */
+hidden lg:flex lg:flex-col
+
+/* TopBar search */
+hidden md:block
+
+/* Activity grid */
+grid grid-cols-1 md:grid-cols-2
+
+/* Sidebar width */
+lg:w-[272px] lg:min-w-[272px]
+```
+
+---
+
+## Task 20: Accessibility Audit
+
+### Semantic HTML
+| Element | Tag | Notes |
+|---------|-----|-------|
+| TopBar | `` | Fixed at top |
+| Sidebar | `