Cleaned up repo with old files, and ammended logo size in sidebar/mobile overview
This commit is contained in:
@@ -569,5 +569,3 @@ export function BootSequence({ onComplete }: BootSequenceProps) {
|
||||
)
|
||||
}
|
||||
|
||||
export type { BootConfig, BootLine, BootLineType }
|
||||
export { BOOT_CONFIG }
|
||||
|
||||
@@ -28,9 +28,9 @@ const FAN_RIGHT_STAGGER_S = 0 // stagger delay for right pill (s)
|
||||
const TOTAL_ANIMATION_MS = FAN_DELAY_AFTER_RISE_MS + FAN_DURATION_S * 1000
|
||||
|
||||
// Overlap blend: multiply blend on fanning capsules (used by US-005)
|
||||
export const OVERLAY_BLEND_START_PROGRESS = 0.2 // fan progress at which blend fades in
|
||||
export const OVERLAP_BLEND_MAX_OPACITY = 0.3 // max blend opacity (20%)
|
||||
export const OVERLAP_BLEND_TRANSITION_DURATION_S = FAN_DURATION_S * (1 - OVERLAY_BLEND_START_PROGRESS)
|
||||
const OVERLAY_BLEND_START_PROGRESS = 0.2 // fan progress at which blend fades in
|
||||
const OVERLAP_BLEND_MAX_OPACITY = 0.3 // max blend opacity (20%)
|
||||
const OVERLAP_BLEND_TRANSITION_DURATION_S = FAN_DURATION_S * (1 - OVERLAY_BLEND_START_PROGRESS)
|
||||
|
||||
// Pivot point: bottom-center of the pill stack (in viewBox coords)
|
||||
const PX = 300
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
import { useState } from 'react'
|
||||
import { CardHeader } from './Card'
|
||||
import { useDetailPanel } from '@/contexts/DetailPanelContext'
|
||||
import { documents } from '@/data/documents'
|
||||
import { educationExtras } from '@/data/educationExtras'
|
||||
|
||||
export function EducationSubsection() {
|
||||
const { openPanel } = useDetailPanel()
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null)
|
||||
|
||||
const educationDocuments = [
|
||||
documents.find((d) => d.id === 'doc-mary-seacole')!,
|
||||
documents.find((d) => d.id === 'doc-mpharm')!,
|
||||
documents.find((d) => d.id === 'doc-alevels')!,
|
||||
]
|
||||
|
||||
const getExtras = (docId: string) =>
|
||||
educationExtras.find((e) => e.documentId === docId)
|
||||
|
||||
const getInlineDetails = (doc: (typeof educationDocuments)[0]) => {
|
||||
const extras = getExtras(doc.id)
|
||||
|
||||
switch (doc.id) {
|
||||
case 'doc-mpharm':
|
||||
return {
|
||||
title: 'MPharm (Hons) — 2:1',
|
||||
institution: 'University of East Anglia',
|
||||
year: '2011–2015',
|
||||
details: [
|
||||
`Research project: Drug delivery & cocrystals, 75.1% (Distinction)`,
|
||||
...(extras?.osceScore ? [`4th year OSCE: ${extras.osceScore}`] : []),
|
||||
],
|
||||
}
|
||||
case 'doc-mary-seacole':
|
||||
return {
|
||||
title: 'NHS Leadership Academy — Mary Seacole Programme',
|
||||
institution: 'NHS Leadership Academy',
|
||||
year: '2018',
|
||||
details: [
|
||||
`Programme score: 78%`,
|
||||
...(extras?.programmeDetail ? [extras.programmeDetail] : []),
|
||||
],
|
||||
}
|
||||
case 'doc-alevels':
|
||||
return {
|
||||
title: 'A-Levels',
|
||||
institution: 'Highworth Grammar School',
|
||||
year: '2009–2011',
|
||||
details: ['Mathematics (A*) · Chemistry (B) · Politics (C)'],
|
||||
}
|
||||
default:
|
||||
return {
|
||||
title: doc.title,
|
||||
institution: doc.institution,
|
||||
year: doc.date,
|
||||
details: doc.classification ? [doc.classification] : [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: '24px' }}>
|
||||
<CardHeader dotColor="purple" title="EDUCATION" />
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||
{educationDocuments.map((doc, index) => {
|
||||
const content = getInlineDetails(doc)
|
||||
const isHovered = hoveredIndex === index
|
||||
|
||||
return (
|
||||
<button
|
||||
key={doc.id}
|
||||
onClick={() => openPanel({ type: 'education', document: doc })}
|
||||
onMouseEnter={() => setHoveredIndex(index)}
|
||||
onMouseLeave={() => setHoveredIndex(null)}
|
||||
style={{
|
||||
padding: '12px 16px',
|
||||
background: 'var(--surface)',
|
||||
border: `1px solid ${isHovered ? 'var(--accent)' : 'var(--border-light)'}`,
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
fontSize: '13px',
|
||||
color: 'var(--text-primary)',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
transition: 'border-color 150ms ease-out, box-shadow 150ms ease-out',
|
||||
boxShadow: isHovered
|
||||
? '0 2px 8px rgba(26,43,42,0.08)'
|
||||
: '0 1px 2px rgba(26,43,42,0.05)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'baseline',
|
||||
gap: '12px',
|
||||
marginBottom: '4px',
|
||||
}}
|
||||
>
|
||||
<span style={{ fontWeight: 600, fontSize: '14px' }}>
|
||||
{content.title}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
fontFamily: 'var(--font-geist-mono)',
|
||||
fontSize: '11px',
|
||||
color: 'var(--text-tertiary)',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{content.year}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
color: 'var(--text-secondary)',
|
||||
fontSize: '12px',
|
||||
marginBottom: content.details.length > 0 ? '6px' : '0',
|
||||
}}
|
||||
>
|
||||
{content.institution}
|
||||
</div>
|
||||
{content.details.length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '2px',
|
||||
}}
|
||||
>
|
||||
{content.details.map((detail, i) => (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
color: 'var(--text-tertiary)',
|
||||
fontSize: '12px',
|
||||
fontFamily: 'var(--font-geist-mono)',
|
||||
}}
|
||||
>
|
||||
{detail}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -66,7 +66,7 @@ export function MobileOverviewHeader({ onSearchClick }: MobileOverviewHeaderProp
|
||||
>
|
||||
{/* Logo + Search row */}
|
||||
<div style={{ display: 'flex', alignItems: 'flex-end', gap: '6px', marginBottom: '12px' }}>
|
||||
<CvmisLogo cssHeight="40px" />
|
||||
<CvmisLogo cssHeight="50px" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSearchClick}
|
||||
|
||||
@@ -232,7 +232,7 @@ export default function Sidebar({ activeSection, onNavigate, onSearchClick }: Si
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<CvmisLogo cssHeight="48px" />
|
||||
<CvmisLogo cssHeight="50px" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSearchClick}
|
||||
@@ -240,7 +240,6 @@ export default function Sidebar({ activeSection, onNavigate, onSearchClick }: Si
|
||||
aria-label={sidebarCopy.searchAriaLabel}
|
||||
style={{
|
||||
width: '100%',
|
||||
minHeight: '44px',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
background: 'var(--surface)',
|
||||
@@ -253,7 +252,7 @@ export default function Sidebar({ activeSection, onNavigate, onSearchClick }: Si
|
||||
}}
|
||||
>
|
||||
<Search size={16} style={{ color: 'var(--text-tertiary)', flexShrink: 0 }} aria-hidden="true" />
|
||||
<span style={{ flex: 1, textAlign: 'left', fontSize: '13px' }}>
|
||||
<span style={{ flex: 1, textAlign: 'left', fontSize: '13px', padding: '5px 0' }}>
|
||||
{sidebarCopy.searchLabel}
|
||||
</span>
|
||||
<kbd
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Sizing
|
||||
export const MIN_HEIGHT = 400
|
||||
export const MOBILE_FALLBACK_HEIGHT = 520
|
||||
export const ROLE_WIDTH = 104
|
||||
export const ROLE_HEIGHT = 32
|
||||
export const ROLE_RX = 16
|
||||
@@ -50,12 +49,6 @@ export const SKILL_Y_OFFSET_STEP_MOBILE = 26
|
||||
export const SKILL_Y_GLOBAL_OFFSET_RATIO = -0.05
|
||||
export const SKILL_Y_CENTER_BLEND = 0.55
|
||||
export const SKILL_X_OVERLAP_MAX_RATIO = 1
|
||||
// Entry animation
|
||||
export const ENTRY_GUIDE_FADE_MS = 200
|
||||
export const ENTRY_ROLE_STAGGER_MS = 80
|
||||
export const ENTRY_ROLE_DURATION_MS = 300
|
||||
export const ENTRY_SKILL_STAGGER_MS = 30
|
||||
export const ENTRY_SKILL_DURATION_MS = 250
|
||||
|
||||
// Timeline animation
|
||||
export const ANIM_CHRONOLOGICAL_ENABLED = true
|
||||
@@ -66,8 +59,6 @@ export const ANIM_LINK_DRAW_MS = 600
|
||||
export const ANIM_LINK_STAGGER_MS = 200
|
||||
export const ANIM_REINFORCEMENT_MS = 700
|
||||
export const ANIM_STEP_GAP_MS = 1000
|
||||
export const ANIM_HOLD_MS = 15000
|
||||
export const ANIM_RESET_MS = 800
|
||||
export const ANIM_RESTART_DELAY_MS = 400
|
||||
|
||||
export const ANIM_SETTLE_ALPHA = 0.05
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { Alert } from '@/types/pmr'
|
||||
|
||||
export const alerts: Alert[] = [
|
||||
{
|
||||
message: '£14.6M SAVINGS IDENTIFIED',
|
||||
severity: 'alert',
|
||||
icon: 'AlertTriangle',
|
||||
},
|
||||
{
|
||||
message: '£215M BUDGET OVERSIGHT',
|
||||
severity: 'amber',
|
||||
icon: 'AlertCircle',
|
||||
},
|
||||
]
|
||||
@@ -495,17 +495,10 @@ export const timelineEntities: TimelineEntity[] = [...timelineEntitySeeds].sort(
|
||||
return b.dateRange.start.localeCompare(a.dateRange.start)
|
||||
})
|
||||
|
||||
export const timelineCareerEntities: TimelineEntity[] = timelineEntities.filter(
|
||||
const timelineCareerEntities: TimelineEntity[] = timelineEntities.filter(
|
||||
(entity) => entity.kind === 'career',
|
||||
)
|
||||
|
||||
export const timelineEducationEntities: TimelineEntity[] = timelineEntities.filter(
|
||||
(entity) => entity.kind === 'education',
|
||||
)
|
||||
|
||||
// Compatibility alias retained for downstream consumers that still import role entities.
|
||||
export const timelineRoleEntities = timelineCareerEntities
|
||||
|
||||
function mapTimelineToConsultation(entity: TimelineEntity): Consultation {
|
||||
const codedEntries: CodedEntry[] = entity.codedEntries ?? entity.details.map((detail, index) => ({
|
||||
code: `DET${String(index + 1).padStart(3, '0')}`,
|
||||
|
||||
+10
-21
@@ -5,8 +5,8 @@
|
||||
/* Premium UI fonts — Elvaro Grotesque (primary) */
|
||||
@font-face {
|
||||
font-family: 'Elvaro Grotesque';
|
||||
src: url('/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-Regular.woff2') format('woff2'),
|
||||
url('/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-Regular.woff') format('woff');
|
||||
src: url('/fonts/elvaro/TBJElvaro-Regular.woff2') format('woff2'),
|
||||
url('/fonts/elvaro/TBJElvaro-Regular.woff') format('woff');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
@font-face {
|
||||
font-family: 'Elvaro Grotesque';
|
||||
src: url('/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-Medium.woff2') format('woff2'),
|
||||
url('/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-Medium.woff') format('woff');
|
||||
src: url('/fonts/elvaro/TBJElvaro-Medium.woff2') format('woff2'),
|
||||
url('/fonts/elvaro/TBJElvaro-Medium.woff') format('woff');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
@@ -23,8 +23,8 @@
|
||||
|
||||
@font-face {
|
||||
font-family: 'Elvaro Grotesque';
|
||||
src: url('/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-SemiBold.woff2') format('woff2'),
|
||||
url('/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-SemiBold.woff') format('woff');
|
||||
src: url('/fonts/elvaro/TBJElvaro-SemiBold.woff2') format('woff2'),
|
||||
url('/fonts/elvaro/TBJElvaro-SemiBold.woff') format('woff');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
@@ -32,28 +32,18 @@
|
||||
|
||||
@font-face {
|
||||
font-family: 'Elvaro Grotesque';
|
||||
src: url('/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-Bold.woff2') format('woff2'),
|
||||
url('/Fonts/Elvaro Grotesque Sans Family/WOFF/TBJElvaro-Bold.woff') format('woff');
|
||||
src: url('/fonts/elvaro/TBJElvaro-Bold.woff2') format('woff2'),
|
||||
url('/fonts/elvaro/TBJElvaro-Bold.woff') format('woff');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Monospace — Interval Mono */
|
||||
@font-face {
|
||||
font-family: 'Interval Mono';
|
||||
src: url('/Fonts/IntervalMono/WOFF/TBJInterval-Regular.woff2') format('woff2'),
|
||||
url('/Fonts/IntervalMono/WOFF/TBJInterval-Regular.woff') format('woff');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Premium UI fonts — Blumir (alternative) */
|
||||
@font-face {
|
||||
font-family: 'Blumir';
|
||||
src: url('/Fonts/blumir-font-family/WOFF/Blumir-VF.woff2') format('woff2-variations'),
|
||||
url('/Fonts/blumir-font-family/WOFF/Blumir-VF.woff') format('woff-variations');
|
||||
src: url('/fonts/blumir/Blumir-VF.woff2') format('woff2-variations'),
|
||||
url('/fonts/blumir/Blumir-VF.woff') format('woff-variations');
|
||||
font-weight: 100 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
@@ -114,7 +104,6 @@
|
||||
--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-dashboard: 'Interval Mono', 'Fira Code', monospace;
|
||||
|
||||
/* Detail panel */
|
||||
--panel-narrow: 400px;
|
||||
|
||||
+2
-2
@@ -5,14 +5,14 @@ export interface ChatMessage {
|
||||
content: string
|
||||
}
|
||||
|
||||
export const LLM_MODEL = 'google/gemini-3-flash-preview'
|
||||
const LLM_MODEL = 'google/gemini-3-flash-preview'
|
||||
export const LLM_DISPLAY_NAME = 'Gemini 3 Flash'
|
||||
|
||||
export function isLLMAvailable(): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
export function buildSystemPrompt(): string {
|
||||
function buildSystemPrompt(): string {
|
||||
return LLM_SYSTEM_PROMPT
|
||||
}
|
||||
|
||||
|
||||
@@ -109,12 +109,6 @@ export interface Tag {
|
||||
colorVariant: 'teal' | 'amber' | 'green'
|
||||
}
|
||||
|
||||
export interface Alert {
|
||||
message: string
|
||||
severity: 'alert' | 'amber'
|
||||
icon: string
|
||||
}
|
||||
|
||||
export interface KPI {
|
||||
id: string
|
||||
value: string
|
||||
|
||||
Reference in New Issue
Block a user