diff --git a/client/src/data/pe-data.test.ts b/client/src/data/pe-data.test.ts new file mode 100644 index 0000000..1ffd0bb --- /dev/null +++ b/client/src/data/pe-data.test.ts @@ -0,0 +1,90 @@ +// ============================================================ +// PE_DATA parity counts — catches the class of bug where an LLM +// silently drops entries from a long clinical array during a port. +// Numbers captured 2026-04-24 against public/js/peGuide.js commit +// 313ba7f. If the vanilla source changes, update both files in the +// same commit. +// ============================================================ + +import { describe, expect, it } from 'vitest'; +import { PE_DATA, AGE_GROUP_ORDER, SYSTEM_ORDER } from './pe-data'; + +describe('PE_DATA shape', () => { + it('has the six expected age groups', () => { + expect(Object.keys(PE_DATA).sort()).toEqual([...AGE_GROUP_ORDER].sort()); + }); + + it.each([...AGE_GROUP_ORDER])('%s has all four systems', (age) => { + const group = PE_DATA[age]; + for (const s of SYSTEM_ORDER) { + expect(group[s]).toBeDefined(); + expect(group[s].overview.length).toBeGreaterThan(0); + expect(Array.isArray(group[s].components)).toBe(true); + expect(group[s].components.length).toBeGreaterThan(0); + } + }); + + it('component + abnormalHints + pearl + significance counts match vanilla', () => { + let componentCount = 0; + let pearlCount = 0; + let significanceCount = 0; + for (const age of AGE_GROUP_ORDER) { + for (const sys of SYSTEM_ORDER) { + const comps = PE_DATA[age][sys].components; + for (const c of comps) { + componentCount++; + if (c.pearl) pearlCount++; + if (c.significance) significanceCount++; + expect(Array.isArray(c.steps)).toBe(true); + expect(c.steps.length).toBeGreaterThan(0); + expect(Array.isArray(c.abnormalHints)).toBe(true); + } + } + } + // Locked against vanilla peGuide.js (2026-04-24): + // 103 components, 27 pearl, 23 significance. + expect(componentCount).toBe(103); + expect(pearlCount).toBe(27); + expect(significanceCount).toBe(23); + }); +}); + +// Per-age-group × per-system component counts — captured from the +// legacy file with: +// awk 'NR>=316 && NR<=1334' public/js/peGuide.js | +// awk '/^ [a-z]+: \{$/{age=$1} /^ [a-z]+: \{$/{sys=$1} +// /^ { name:/{c[age" "sys]++} END{for(k in c) print k" "c[k]}' | sort +// so any drift in the TS port surfaces as a failing test here. +describe('PE_DATA per-cell component counts', () => { + const EXPECTED: Record = { + 'newborn msk': 6, + 'newborn neuro': 6, + 'newborn resp': 2, + 'newborn cv': 2, + 'infant msk': 4, + 'infant neuro': 5, + 'infant resp': 2, + 'infant cv': 2, + 'toddler msk': 5, + 'toddler neuro': 7, + 'toddler resp': 2, + 'toddler cv': 2, + 'preschool msk': 5, + 'preschool neuro': 7, + 'preschool resp': 2, + 'preschool cv': 2, + 'school msk': 5, + 'school neuro': 7, + 'school resp': 3, + 'school cv': 2, + 'adolescent msk': 6, + 'adolescent neuro': 8, + 'adolescent resp': 6, + 'adolescent cv': 5, + }; + + it.each(Object.entries(EXPECTED))('%s matches', (key, expected) => { + const [age, sys] = key.split(' ') as ['newborn', 'msk']; + expect(PE_DATA[age][sys].components.length).toBe(expected); + }); +}); diff --git a/client/src/data/pe-data.ts b/client/src/data/pe-data.ts new file mode 100644 index 0000000..37b5070 --- /dev/null +++ b/client/src/data/pe-data.ts @@ -0,0 +1,1082 @@ +// ============================================================ +// PE-DATA — full age-group × system × component × step hierarchy +// driving the React Physical Exam Guide checklist. +// +// Ported VERBATIM from public/js/peGuide.js lines 316-1334 using +// awk+sed so every string, hint, and ordering is byte-identical to +// the vanilla source. The migration checkpoint specifically flags +// this data: "An LLM will sometimes 'simplify' a long array of +// numbers and silently break it — don't let that happen." +// +// Expected counts, asserted in client/src/data/pe-data.test.ts: +// • 6 age groups (newborn, infant, toddler, preschool, school, adolescent) +// • 4 systems each (msk, neuro, resp, cv) → 24 system blocks +// • 24 overview strings, 24 components arrays +// • 103 components with 103 abnormalHints arrays +// +// If any of those counts drift, something got lost in transit. +// Counts captured: 2026-04-24 against peGuide.js commit 313ba7f. +// ============================================================ + +export interface PeStep { + label: string; + method: string; + normal: string; +} + +export interface PeComponent { + name: string; + steps: PeStep[]; + abnormalHints: string[]; + // Optional narrative fields that some components carry in the vanilla + // source — `pearl` is a teaching note, `significance` is a clinical + // significance blurb (e.g. "Persistent tone asymmetry = CP red flag"). + pearl?: string; + significance?: string; +} + +export interface PeSystem { + overview: string; + components: PeComponent[]; +} + +export interface PeAgeGroup { + label: string; + msk: PeSystem; + neuro: PeSystem; + resp: PeSystem; + cv: PeSystem; +} + +export type PeData = Record; + +// Canonical orderings used by the viewer. Order matches the legacy +// UI pills exactly so user muscle memory carries over. +export const AGE_GROUP_ORDER = ['newborn', 'infant', 'toddler', 'preschool', 'school', 'adolescent'] as const; +export const SYSTEM_ORDER = ['msk', 'neuro', 'resp', 'cv'] as const; +export const SYSTEM_LABELS: Record<(typeof SYSTEM_ORDER)[number], string> = { + msk: 'Musculoskeletal', + neuro: 'Neuro', + resp: 'Respiratory', + cv: 'Cardiovascular', +}; + +export const PE_DATA: PeData = { + + newborn: { + label: 'Newborn (0–28 days)', + msk: { + overview: 'Exam done warm, quiet, undressed. Focus: birth injury, DDH, congenital anomaly. All steps symmetric.', + components: [ + { name: 'Resting posture', steps: [ + { label: 'Observe posture', method: 'Place supine and undisturbed for 30s', normal: 'Symmetric flexion at hips, knees, elbows' }, + { label: 'Hand position', method: 'Inspect resting hands', normal: 'Loosely fisted; opens intermittently' }, + { label: 'Symmetry', method: 'Compare left vs right side at rest', normal: 'Mirror-image posture' } + ], abnormalHints: ['Frog-leg (hypotonia)', 'Asymmetric arm (brachial plexus/clavicle fx)', 'Opisthotonos (CNS)', 'Persistent fisting with thumb in palm'] }, + + { name: 'Clavicles', steps: [ + { label: 'Palpate right clavicle', method: 'Trace from sternoclavicular joint to acromion with index finger', normal: 'Smooth, continuous, no step-off' }, + { label: 'Palpate left clavicle', method: 'Same technique on left side', normal: 'Smooth, continuous' }, + { label: 'Crepitus check', method: 'Light pressure along length of clavicle while gently abducting arm', normal: 'No crepitus, no pain response' } + ], abnormalHints: ['Palpable step-off or callus (fracture — LGA, shoulder dystocia)', 'Asymmetric Moro on affected side'] }, + + { name: 'Hips — DDH screen', steps: [ + { label: 'Thigh/gluteal folds', method: 'Undress completely; compare skin-fold symmetry', normal: 'Symmetric thigh and gluteal folds' }, + { label: 'Abduction', method: 'Flex hips 90°, abduct simultaneously', normal: 'Both hips abduct to ≥75° symmetrically' }, + { label: 'Barlow maneuver', method: 'Thumb on medial thigh, flex hip 90°, adduct, apply gentle posterior pressure', normal: 'No clunk or movement felt (negative)' }, + { label: 'Ortolani maneuver', method: 'From Barlow position: abduct and lift with fingers on greater trochanter', normal: 'No clunk as hip returns (negative)' }, + { label: 'Galeazzi sign', method: 'Knees flexed together with feet on table; compare knee heights', normal: 'Knees level — no leg-length discrepancy' } + ], abnormalHints: ['Palpable Ortolani clunk (dislocated, reducible)', 'Barlow clunk (dislocatable)', 'Limited abduction', 'Positive Galeazzi (shortened femur)'] }, + + { name: 'Spine and back', steps: [ + { label: 'Position prone', method: 'Turn infant prone, support chest', normal: 'Tolerates position, lifts head briefly' }, + { label: 'Palpate midline', method: 'Run finger from C-spine to coccyx', normal: 'Straight midline, no step-offs or gaps' }, + { label: 'Sacral inspection', method: 'Inspect sacral dimple if present; measure depth, distance from anus', normal: 'No dimple, or dimple <5mm deep and <2.5cm from anus' }, + { label: 'Cutaneous markers', method: 'Inspect midline skin from neck to coccyx', normal: 'No hair tuft, hemangioma, lipoma, or sinus tract' } + ], abnormalHints: ['Deep sacral dimple (>5mm) or >2.5cm from anus — imaging', 'Hair tuft, hemangioma, lipoma (occult dysraphism)', 'Palpable defect'] }, + + { name: 'Upper extremities', steps: [ + { label: 'Spontaneous movement', method: 'Observe both arms for 30s', normal: 'Symmetric antigravity movement' }, + { label: 'Digits', method: 'Count and inspect fingers both hands', normal: '5 digits each, no webbing or duplication' }, + { label: 'Palmar creases', method: 'Inspect palmar creases', normal: 'Normal triradiate creases' } + ], abnormalHints: ['Erb/Klumpke palsy (paucity of movement)', 'Polydactyly, syndactyly', 'Single transverse (simian) palmar crease'] }, + + { name: 'Lower extremities / feet', steps: [ + { label: 'Hip and knee range', method: 'Gently flex, extend, internally/externally rotate each', normal: 'Full symmetric range, no contracture' }, + { label: 'Foot alignment', method: 'Inspect resting foot position', normal: 'Midline or mildly adducted forefoot' }, + { label: 'Passive correction', method: 'Gently attempt to bring foot to neutral', normal: 'Fully correctable to neutral' }, + { label: 'Stroke test', method: 'Stroke lateral border of foot', normal: 'Foot dorsiflexes and everts reflexively' } + ], abnormalHints: ['Rigid clubfoot (non-correctable talipes equinovarus)', 'Fixed metatarsus adductus', 'Rocker-bottom foot (trisomy 18)', 'Calcaneovalgus'] } + ] + }, + neuro: { + overview: 'Primitive reflexes present and symmetric. Tone assessed passively and actively. Full exam 3–5 min on a quiet, fed infant.', + components: [ + { name: 'Alertness and behavior', steps: [ + { label: 'State cycling', method: 'Observe over 1–2 min for alert periods', normal: 'Alert periods with spontaneous eye opening' }, + { label: 'Response to voice', method: 'Speak softly near ear', normal: 'Quiets to voice or turns toward sound' }, + { label: 'Consolability', method: 'If crying, attempt to soothe with swaddling or voice', normal: 'Consolable within 1–2 min' } + ], abnormalHints: ['Lethargy', 'Jitteriness not stopped by passive flexion', 'Irritability unrelieved by feeding'] }, + + { name: 'Cranial nerves', steps: [ + { label: 'CN II — pupil response', method: 'Shine light in each eye', normal: 'Pupils equal, reactive to light, direct and consensual' }, + { label: 'CN II — blink to light', method: 'Bright light in the line of sight', normal: 'Reflex blink' }, + { label: 'CN VII — facial symmetry at rest', method: 'Observe resting face', normal: 'Symmetric nasolabial folds' }, + { label: 'CN VII — facial symmetry with cry', method: 'Note face during a cry', normal: 'Symmetric grimace' }, + { label: 'CN IX, X, XII — suck and swallow', method: 'Offer clean gloved finger, pacifier, or during feed', normal: 'Strong coordinated suck-swallow, no choking' } + ], abnormalHints: ['Asymmetric face (CN VII injury — usually forceps)', 'Poor suck or uncoordinated swallow', 'Fixed or unequal pupil'] }, + + { name: 'Tone — passive', steps: [ + { label: 'Pull-to-sit', method: 'Grasp hands/wrists, pull smoothly to sit', normal: 'Brief head lag at term; not dramatic' }, + { label: 'Ventral suspension', method: 'Suspend prone over hand at chest', normal: 'Head briefly lifts to horizontal; extremities flexed' }, + { label: 'Arm recoil', method: 'Extend both arms fully at elbows, release', normal: 'Rapid return to flexion' }, + { label: 'Popliteal angle', method: 'Hip flexed 90°, extend knee maximally', normal: '≤110°' } + ], abnormalHints: ['Marked head lag (hypotonia)', 'Slip-through on vertical suspension', 'Hypertonia / scissoring', 'Floppy limbs with no recoil'] }, + + { name: 'Spontaneous movement', steps: [ + { label: 'Observe limbs', method: 'Undisturbed over 1 min, note movement', normal: 'Symmetric antigravity movement of all 4 limbs' }, + { label: 'Quality of movement', method: 'Note smoothness vs jitteriness', normal: 'Smooth, mildly variable movements' } + ], abnormalHints: ['Paucity of movement in one limb', 'Coarse jitteriness', 'Clonic jerks', 'Tonic posturing'] }, + + { name: 'Primitive reflexes', steps: [ + { label: 'Moro', method: 'Support head; allow 30° head drop or loud clap', normal: 'Symmetric arm abduction then flexion, often cry' }, + { label: 'Rooting', method: 'Stroke cheek at corner of mouth', normal: 'Turns head toward stroke and opens mouth' }, + { label: 'Palmar grasp', method: 'Press into palm with finger', normal: 'Strong, symmetric finger flexion' }, + { label: 'Plantar grasp', method: 'Press ball of foot below toes', normal: 'Toes flex around finger symmetrically' }, + { label: 'Stepping', method: 'Hold upright with feet touching surface', normal: 'Alternating stepping movements' }, + { label: 'Tonic neck (fencing)', method: 'Turn head to one side with infant supine', normal: 'Same-side arm extends, opposite flexes (not obligate)' }, + { label: 'Galant', method: 'Stroke paravertebrally from shoulder to buttock, one side', normal: 'Trunk curves toward stroked side' } + ], abnormalHints: ['Absent or asymmetric Moro — CNS injury, brachial plexus, clavicle fx', 'Absent grasps — CNS depression', 'Obligate tonic neck is abnormal'] }, + + { name: 'Babinski (plantar response)', steps: [ + { label: 'Stroke lateral sole', method: 'Firm stroke from heel toward toes along lateral plantar border', normal: 'Up-going great toe with fanning (normal in newborn)' }, + { label: 'Symmetry check', method: 'Repeat opposite foot', normal: 'Symmetric response' } + ], abnormalHints: ['Asymmetric response is always abnormal'] } + ] + }, + resp: { + overview: 'Newborn respiratory transition — RDS, TTN, pneumonia, meconium aspiration dominate the differential. Normal RR ≤ 60. Grunting is an alarm sign.', + components: [ + { name: 'Inspection', + significance: 'Detects distress and localises cause. Silverman score quantifies retraction severity.', + pearl: 'Grunting is physiologic PEEP against a partially closed glottis — always a sign of significant lung pathology in a newborn. Never dismiss it as fussy breathing.', + steps: [ + { label: 'Respiratory rate', method: 'Count over full 60 s, quiet and undisturbed.', normal: '40–60 /min; tachypnea > 60' }, + { label: 'Work of breathing (Silverman)', method: 'Inspect for upper-chest retraction, lower-chest retraction, xiphoid retraction, nasal flaring, grunting. Score 0–2 for each.', normal: 'Total Silverman 0 — no distress' }, + { label: 'Audible sounds', method: 'Listen without stethoscope — stridor? grunting? wheeze across the room?', normal: 'Quiet respirations' }, + { label: 'Colour', method: 'Inspect trunk and mucous membranes for central cyanosis; acrocyanosis (blue hands/feet) is normal in the first days.', normal: 'Pink trunk and mucous membranes' }, + { label: 'Chest shape', method: 'Inspect AP:transverse diameter and symmetry.', normal: 'Slightly barrel-shaped is normal; symmetric' } + ], + abnormalHints: ['Grunting — RDS, pneumonia, sepsis, CHD', 'Retractions + tachypnea — RDS, TTN, pneumothorax', 'Central cyanosis — cyanotic CHD, severe lung disease, persistent pulmonary HTN', 'Asymmetric chest movement — pneumothorax, diaphragmatic hernia'] }, + { name: 'Auscultation', + significance: 'Short stethoscope time in neonates because they fuss easily — get the most important zones first.', + pearl: 'Listen at the axilla, not just the anterior chest — pneumothorax can sound normal anteriorly. Auscultate both axillae systematically.', + steps: [ + { label: 'Air entry — anterior', method: 'Listen bilaterally at the upper and lower anterior chest.', normal: 'Symmetric bilateral air entry' }, + { label: 'Air entry — axillary', method: 'Listen bilaterally at each axilla — this is the most sensitive area for detecting a small pneumothorax.', normal: 'Clear and symmetric' }, + { label: 'Adventitious sounds', method: 'Listen for transmitted upper-airway sounds, crackles (RDS, pneumonia), grunting sounds.', normal: 'No crackles, no wheeze, clear sounds' }, + { label: 'Inspiration:expiration ratio', method: 'Observe breath-sound timing.', normal: 'Inspiration > expiration in length' } + ], + abnormalHints: ['Asymmetric air entry — pneumothorax, diaphragmatic hernia, endobronchial intubation', 'Fine crackles — RDS, TTN, pneumonia', 'Absent breath sounds unilaterally — pneumothorax or selective intubation'] } + ] + }, + cv: { + overview: 'Neonatal CV exam screens for CHD — the window of presentation is short and some lesions (duct-dependent) decompensate within hours of birth. Pre/postductal saturations + femoral pulses are the two fastest screens.', + components: [ + { name: 'Inspection and pre/postductal saturations', + significance: 'Pre/postductal SpO₂ differential > 3% suggests a duct-dependent lesion or persistent pulmonary HTN. Universal CCHD screening uses this.', + pearl: 'Pulse ox on the right hand = preductal (proximal to PDA insertion). Foot = postductal. Both arms and both legs should match; a differential is a red flag for critical CHD.', + steps: [ + { label: 'Central cyanosis', method: 'Inspect tongue, lips, oral mucosa.', normal: 'Pink mucous membranes' }, + { label: 'Pre/postductal SpO₂', method: 'Measure SpO₂ in right hand (preductal) AND either foot (postductal). Baby must be ≥24 hr old for CCHD screening.', normal: 'Both ≥ 95% AND difference < 3%' }, + { label: 'Peripheral perfusion', method: 'Capillary refill on sternum; note mottling or distal cyanosis.', normal: 'Capillary refill < 2 s, warm pink extremities' }, + { label: 'Precordial activity', method: 'Inspect anterior chest for hyperactive precordium.', normal: 'Not visible or minimally visible' } + ], + abnormalHints: ['Central cyanosis with SpO₂ < 95% → cyanotic CHD workup (4-extremity BP, ECG, hyperoxia test, echo)', 'Differential > 3% (pre > post) → duct-dependent systemic flow (HLHS, coarctation, interrupted arch)', 'Preductal < postductal — persistent pulmonary HTN with reversed shunt'] }, + { name: 'Palpation and auscultation', + significance: 'Absent femoral pulses + arm-leg BP gradient = coarctation. Many CHD lesions manifest murmurs only after ductus closes (48–72 h).', + pearl: 'Always palpate femoral pulses before discharging any newborn. Absent femorals in a well-appearing baby can be the only finding in a ductal-dependent coarctation — catastrophic if missed.', + steps: [ + { label: 'Apex beat', method: 'Palpate at the 4th ICS left of sternum (apex is higher in newborns).', normal: 'Palpable at 4th ICS, mid-clavicular or just lateral' }, + { label: 'Femoral pulses', method: 'Palpate both femoral pulses at the mid-inguinal point while simultaneously feeling the right brachial pulse — detects delay.', normal: 'Present, symmetric, equal timing with brachial' }, + { label: 'Auscultate each cardiac area', method: 'Use pediatric diaphragm at each classic point; baby quiet if possible.', normal: 'S1 S2 crisp; physiologic flow murmur sometimes present in the first 24–48 h' }, + { label: 'Continuous murmur', method: 'Listen below the left clavicle for a continuous ("machinery") murmur of PDA.', normal: 'No continuous murmur after the first day of life in a term baby' }, + { label: 'Four-limb BP (if any concern)', method: 'Right arm, left arm, both legs.', normal: 'Within 10 mmHg across limbs' } + ], + abnormalHints: ['Absent femoral pulses — coarctation of the aorta (surgical emergency if duct-dependent)', 'Harsh holosystolic at LLSB — VSD', 'Continuous machinery murmur — PDA (expected in preterm; in term > 48 h is abnormal)', 'Gallop S3/S4 — heart failure', 'Single S2 — transposition, truncus, severe AS/PS'] } + ] + } + }, + + infant: { + label: 'Infant (1–12 months)', + msk: { + overview: 'Continued DDH screen through 6 months. Watch motor progression and symmetry of tone/movement.', + components: [ + { name: 'Hips — continued DDH screen', steps: [ + { label: 'Thigh-fold symmetry', method: 'Inspect with infant supine, thighs flexed', normal: 'Symmetric folds' }, + { label: 'Barlow/Ortolani (through 3mo)', method: 'As in newborn — thumb medial, flex 90°, adduct+push then abduct+lift', normal: 'Negative bilaterally' }, + { label: 'Abduction (any age)', method: 'Flex 90°, abduct simultaneously', normal: '≥70° symmetric abduction' }, + { label: 'Galeazzi', method: 'Knees flexed feet flat on exam table', normal: 'Knees equal height' } + ], abnormalHints: ['Clunk on Barlow/Ortolani (<3mo)', 'Limited abduction', 'Asymmetric folds', 'Positive Galeazzi'] }, + + { name: 'Gross-motor milestones (age-appropriate)', steps: [ + { label: 'Head control', method: 'Prone, pull-to-sit, held upright', normal: 'Age-appropriate: 2mo lifts head 45°; 4mo no head lag' }, + { label: 'Rolling', method: 'Observe on flat surface', normal: 'Rolls back-to-front by 5–6mo' }, + { label: 'Sitting', method: 'Place in sitting position', normal: 'Tripod sit by 6mo; sits without support by 7–8mo' }, + { label: 'Standing', method: 'Support under arms', normal: 'Bears weight by 6mo; pulls to stand by 9mo' } + ], abnormalHints: ['Milestone delay by ≥2mo', 'Loss of previously attained milestone', 'Asymmetric use of limbs'] }, + + { name: 'Spine', steps: [ + { label: 'Palpate seated', method: 'Run finger along spine with infant sitting or held upright', normal: 'Midline, no step-offs' }, + { label: 'Back curvature', method: 'Inspect sitting and lying', normal: 'Physiologic gentle kyphosis in early infancy; lumbar lordosis as sitting develops' } + ], abnormalHints: ['Fixed kyphoscoliosis', 'Sacral dimple with cutaneous marker', 'Step-off'] }, + + { name: 'Extremities', steps: [ + { label: 'Passive range', method: 'Through each major joint', normal: 'Full symmetric range' }, + { label: 'Joint inspection', method: 'Inspect for swelling, warmth, effusion', normal: 'No swelling, warmth, or effusion' }, + { label: 'Feet alignment', method: 'Observe standing if pulling up, else passive positioning', normal: 'Correctable or aligned feet; flexible' } + ], abnormalHints: ['Joint swelling/warmth (septic vs reactive arthritis)', 'Rigid clubfoot', 'Persistent asymmetric tone'] } + ] + }, + neuro: { + overview: 'Primitive reflexes fading on schedule; protective reflexes emerging; gross motor and tone interlinked.', + components: [ + { name: 'Alertness and social engagement', steps: [ + { label: 'Social smile', method: 'Face-to-face interaction', normal: 'Social smile by 6–8 weeks' }, + { label: 'Tracking', method: 'Move object across visual field', normal: '180° horizontal tracking by 3mo' }, + { label: 'Engagement', method: 'Face-to-face, voice, toys', normal: 'Age-appropriate reciprocal interaction' } + ], abnormalHints: ['No social smile by 3mo', 'Absent tracking past 3mo', 'Poor engagement (developmental concern)'] }, + + { name: 'Cranial nerves', steps: [ + { label: 'Pupils', method: 'Light response each eye', normal: 'Equal reactive' }, + { label: 'Eye alignment', method: 'Inspect with gaze forward and in all directions', normal: 'No strabismus past 4mo' }, + { label: 'Facial symmetry', method: 'Observe smile and cry', normal: 'Symmetric' }, + { label: 'Suck and swallow', method: 'Observe feeding', normal: 'Coordinated, no choking' } + ], abnormalHints: ['Persistent nystagmus', 'Strabismus past 4mo', 'Asymmetric face'] }, + + { name: 'Tone', steps: [ + { label: 'Pull-to-sit', method: 'Gently pull wrists/hands', normal: 'No head lag by 4mo' }, + { label: 'Ventral suspension', method: 'Support prone, observe', normal: 'Head above horizontal, extremities actively flexed (age-dependent)' }, + { label: 'Vertical suspension', method: 'Support under arms, lift', normal: 'Does not slip through hands' } + ], abnormalHints: ['Persistent head lag past 4mo (hypotonia)', 'Hypertonia / scissoring (UMN)', 'Slip-through on vertical suspension'] }, + + { name: 'Primitive reflex integration', steps: [ + { label: 'Moro', method: 'Head drop or clap', normal: 'Absent by 6mo' }, + { label: 'Palmar grasp', method: 'Press into palm', normal: 'Integrated by 5–6mo; replaced by voluntary grasp' }, + { label: 'Tonic neck', method: 'Turn head to side', normal: 'Absent by 6mo' }, + { label: 'Rooting', method: 'Stroke cheek', normal: 'Absent by 3–4mo' } + ], abnormalHints: ['Persistence of any primitive reflex past 6mo warrants eval (CP, CNS injury)'] }, + + { name: 'Protective and postural reflexes', steps: [ + { label: 'Parachute', method: 'Held prone, tilt head downward suddenly', normal: 'Symmetric arm extension protectively by 9mo (emerges 6–9mo)' }, + { label: 'Lateral propping', method: 'Seated infant, gentle tilt to one side', normal: 'Arm extends to catch by 6–7mo' }, + { label: 'Landau', method: 'Suspend prone', normal: 'Extends head, spine, legs by 6mo' } + ], abnormalHints: ['Absent parachute after 12mo (concerning)', 'Asymmetric lateral propping', 'Absent Landau'] } + ] + }, + resp: { + overview: 'Infant respiratory disease centers on bronchiolitis, reactive airways, and pneumonia. Normal RR ≤ 50 (< 2 mo: ≤ 60). Infants are obligate nose-breathers — nasal congestion alone can cause significant WOB.', + components: [ + { name: 'Inspection', + significance: 'Infant distress signs escalate fast. Nasal flaring, tracheal tug, head bobbing = significant WOB. Apnea in an infant < 2 mo is an emergency.', + pearl: 'A quiet infant with retractions is more worrying than a crying one — exhausted infants stop crying and become hypoxic silently.', + steps: [ + { label: 'Respiratory rate', method: 'Count over full 60 s while quiet.', normal: '< 2 mo: ≤ 60; 2–12 mo: ≤ 50' }, + { label: 'Work of breathing', method: 'Inspect nasal flaring, subcostal/intercostal/suprasternal retractions, tracheal tug, head-bobbing, accessory muscle use.', normal: 'No retractions, effortless breathing' }, + { label: 'Audible sounds', method: 'Stridor? Wheeze across the room? Grunting? Prolonged expiration?', normal: 'Quiet respirations' }, + { label: 'Colour and feeding history', method: 'Central cyanosis? Poor feeding (feeding is an effort marker in infants)?', normal: 'Pink, feeds well' }, + { label: 'Apnea observation', method: 'Watch for ≥ 20-s pauses or pauses < 20 s with bradycardia/cyanosis.', normal: 'No apneas' } + ], + abnormalHints: ['Grunting / persistent retractions — pneumonia, bronchiolitis, CHF', 'Wheeze — bronchiolitis (RSV), asthma, foreign body', 'Stridor — croup (6 mo–6 y), laryngomalacia (infant), foreign body', 'Apnea — bronchiolitis, sepsis, pertussis, seizure'] }, + { name: 'Auscultation', + significance: 'Infants have a thin chest wall — sounds transmit widely. Symmetry, wheeze, and crackles are the main findings.', + pearl: 'In bronchiolitis, the classical finding is widespread end-inspiratory fine crackles PLUS expiratory wheeze. Tachypnea + retractions in an RSV-season infant confirms.', + steps: [ + { label: 'Air entry — bilateral', method: 'Warm stethoscope; listen at anterior chest and both axillae, both sides.', normal: 'Symmetric air entry' }, + { label: 'Wheeze', method: 'Listen in expiration. Diffuse wheeze = lower airway; focal wheeze = foreign body or local obstruction.', normal: 'No wheeze' }, + { label: 'Crackles', method: 'Listen in late inspiration. Focal = pneumonia; diffuse fine = bronchiolitis.', normal: 'No crackles' }, + { label: 'Prolonged expiration', method: 'Note expiration:inspiration length ratio.', normal: 'Inspiration ≥ expiration' } + ], + abnormalHints: ['Focal crackles + fever — pneumonia', 'Diffuse wheeze + fine crackles in an RSV-season infant — bronchiolitis', 'Silent chest with extreme WOB — impending respiratory failure'] } + ] + }, + cv: { + overview: 'Most CHD manifests in the first year as pulmonary blood-flow changes and the ductus closes. Infant CV exam = growth review + inspection + femoral pulses + auscultation. A harsh pan-systolic LLSB murmur in a 6-week-old = VSD until proven otherwise.', + components: [ + { name: 'Inspection and functional assessment', + significance: 'Heart failure in infants presents as poor feeding, sweating during feeds (diaphoresis), tachypnea, and poor weight gain. These historical features predict bad exam findings.', + pearl: 'Ask "Does the baby sweat while feeding?" — infant CHF presents with diaphoresis on the forehead during feeds, well before peripheral edema appears.', + steps: [ + { label: 'Growth trajectory', method: 'Plot weight-for-age on WHO chart. Failure to thrive raises CHD concern.', normal: 'Tracking ≥ 10th percentile or stable on personal curve' }, + { label: 'Feeding history', method: 'Ask about feed duration, sweating with feeds, tachypnea with feeds, tiring easily.', normal: 'Feeds < 20 min, no diaphoresis, no tachypnea' }, + { label: 'Central cyanosis', method: 'Inspect tongue and oral mucosa.', normal: 'Pink' }, + { label: 'Clubbing', method: 'Inspect finger nail beds (subtle in infants).', normal: 'No clubbing' }, + { label: 'Peripheral perfusion', method: 'Cap refill, warmth of extremities.', normal: 'Cap refill < 2 s, warm extremities' } + ], + abnormalHints: ['Poor weight gain — consider CHF from L-to-R shunt (VSD, PDA, AVSD)', 'Diaphoresis with feeds — infant CHF', 'Tiring with feeds — significant CHD', 'Central cyanosis — cyanotic CHD (ToF, TGA, TA, etc.)'] }, + { name: 'Palpation and auscultation', + significance: 'Femoral pulses + 4-limb BP screen coarctation. A harsh holosystolic murmur at LLSB is almost always a VSD in this age.', + pearl: 'Listen over each of the 5 classic points as in the adult exam — the locations shift slightly with infant chest size but relative positions are the same. Also listen at the back: coarctation murmurs radiate there.', + steps: [ + { label: 'Apex beat', method: 'Palpate at 4th ICS mid-clavicular line.', normal: 'Palpable at 4th ICS in infants' }, + { label: 'Femoral pulses', method: 'Palpate bilaterally, simultaneously with right brachial.', normal: 'Present, equal, no delay vs brachial' }, + { label: 'Listen at each classic area (APTM)', method: 'See APTM diagram. Use pediatric stethoscope with both diaphragm and bell.', normal: 'S1 and S2 crisp, no murmur or physiologic only' }, + { label: 'Listen over the back (interscapular)', method: 'Check for radiation of coarctation murmurs.', normal: 'No radiating murmur' } + ], + abnormalHints: ['Harsh holosystolic LLSB murmur — VSD', 'Continuous "machinery" murmur below left clavicle — PDA', 'Systolic ejection at ULSB + fixed split S2 — ASD', 'Ejection murmur at ULSB + cyanosis — tetralogy of Fallot', 'Absent femorals + radio-femoral delay — coarctation', 'Gallop + tachycardia — heart failure'] } + ] + } + }, + + toddler: { + label: 'Toddler (1–3 years)', + msk: { + overview: 'Gait, physiologic alignment changes, in-toeing, joint range. Cooperation unpredictable — use play.', + components: [ + { name: 'Gait', steps: [ + { label: 'Base of support', method: 'Have child walk ~10 feet', normal: 'Wide-based initially, narrowing by 2y' }, + { label: 'Heel-strike', method: 'Observe foot contact pattern', normal: 'Heel-strike developing by 18mo, consistent by 2y' }, + { label: 'Arm swing', method: 'Observe reciprocal arm swing', normal: 'Reciprocal arm swing by 18mo' }, + { label: 'Symmetry', method: 'Watch both sides in stance and swing', normal: 'Symmetric stride length and cadence' } + ], abnormalHints: ['Toe-walking past 2y (idiopathic vs CP vs DMD)', 'Limp (Legg-Calvé-Perthes, transient synovitis, trauma)', 'Wide-based ataxia'] }, + + { name: 'Knee alignment', steps: [ + { label: 'Stand feet together', method: 'Feet/medial malleoli touching; inspect knees', normal: 'Genu varum resolving by 18–24mo; mild genu valgum common by 2–3y' }, + { label: 'Intercondylar or intermalleolar distance', method: 'Measure if alignment appears abnormal', normal: '<5cm intercondylar (varum) or <8cm intermalleolar (valgum)' }, + { label: 'Symmetry', method: 'Compare sides', normal: 'Symmetric' } + ], abnormalHints: ['Persistent varum past 2y', 'Severe valgum >8cm', 'Unilateral (Blount disease, rickets)'] }, + + { name: 'Feet alignment', steps: [ + { label: 'In-toeing / out-toeing', method: 'Observe foot angle during gait', normal: 'Mild in-toeing common (tibial torsion, femoral anteversion)' }, + { label: 'Arch during stance', method: 'Stand flat, then on tiptoes', normal: 'Flexible flat foot that forms arch on tiptoe' }, + { label: 'Heel alignment', method: 'View from behind standing', normal: 'Neutral or mildly valgus heel' } + ], abnormalHints: ['Rigid flat foot (tarsal coalition)', 'Fixed metatarsus adductus', 'Severe in-toeing >15°'] }, + + { name: 'Joint range', steps: [ + { label: 'Hips', method: 'Flex, abduct, rotate each hip', normal: 'Full symmetric range, no pain' }, + { label: 'Knees', method: 'Flex, extend; palpate for effusion', normal: 'Full range, no effusion, stable' }, + { label: 'Ankles', method: 'Dorsiflex, plantarflex, invert, evert', normal: 'Full range' }, + { label: 'Shoulders, elbows, wrists', method: 'Through full range each', normal: 'Full symmetric range' } + ], abnormalHints: ['Joint effusion (septic vs reactive)', 'Guarded or painful motion', 'Asymmetric limitation'] }, + + { name: 'Spine', steps: [ + { label: 'Inspect standing', method: 'View spine from behind', normal: 'Midline, no prominent curve' }, + { label: 'Palpate midline', method: 'Run finger along spinous processes', normal: 'Midline, no step-off, no tenderness' }, + { label: 'Shoulder/hip symmetry', method: 'Compare shoulder and hip heights', normal: 'Symmetric' } + ], abnormalHints: ['Fixed scoliosis', 'Abnormal kyphosis', 'Asymmetric shoulder/hip', 'Midline tenderness'] } + ] + }, + neuro: { + overview: 'Shifting to adult-pattern exam. Primitive reflexes should be absent. Use play-based techniques.', + components: [ + { name: 'Mental status and language', steps: [ + { label: 'Engagement', method: 'Interaction with examiner/parent', normal: 'Alert, interactive, age-appropriate' }, + { label: 'Language — expressive', method: 'Note spontaneous utterances', normal: '1y: 1–3 words; 2y: 2-word phrases; 3y: short sentences' }, + { label: 'Language — receptive', method: 'Ask to point to body parts or follow simple commands', normal: 'Follows age-appropriate commands' } + ], abnormalHints: ['Language regression (autism, epileptic encephalopathy)', 'Poor engagement', 'No 2-word phrases by 2y'] }, + + { name: 'Cranial nerves', steps: [ + { label: 'CN II — pupil response', method: 'Shine light each eye', normal: 'Pupils equal reactive' }, + { label: 'CN III, IV, VI — EOM', method: 'Follow toy in H pattern', normal: 'Full smooth tracking, no strabismus' }, + { label: 'CN V — facial sensation', method: 'Light touch on forehead, cheek, jaw', normal: 'Responds to touch, symmetric' }, + { label: 'CN VII — face', method: 'Elicit smile, watch eye closure during cry', normal: 'Symmetric face' }, + { label: 'CN IX, X, XII — mouth', method: 'Say "ahh"; stick out tongue', normal: 'Palate rises symmetrically; tongue midline' } + ], abnormalHints: ['Strabismus', 'Facial asymmetry', 'Tongue deviation', 'Absent palate elevation'] }, + + { name: 'Motor — tone and bulk', steps: [ + { label: 'Passive tone', method: 'Move each limb through full range', normal: 'Normal resistance throughout' }, + { label: 'Bulk inspection', method: 'Inspect muscle bulk of thighs, calves, glutes', normal: 'Symmetric, age-appropriate bulk' }, + { label: 'Contracture check', method: 'Test for heel-cord tightness, hamstring tightness', normal: 'No contractures' } + ], abnormalHints: ['Spasticity (especially catch in ankles)', 'Hypotonia', 'Calf pseudo-hypertrophy (DMD)'] }, + + { name: 'Motor — functional strength', steps: [ + { label: 'Rising from floor', method: 'Place flat on back; ask to stand up', normal: 'Rises without using hands to push off thighs (no Gowers)' }, + { label: 'Climbing stairs', method: 'Observe or history', normal: 'Climbs holding rail' }, + { label: 'Squatting/getting up', method: 'Encourage via play', normal: 'Squats and rises without help' } + ], abnormalHints: ['Gowers sign (proximal weakness — DMD)', 'Unable to climb stairs at age expected', 'Calf pain on walking (myositis)'] }, + + { name: 'Deep tendon reflexes', steps: [ + { label: 'Patellar', method: 'Sitting or supine with distraction (toy)', normal: '2+ symmetric' }, + { label: 'Biceps', method: 'Arm at rest, strike thumb on tendon', normal: '2+ symmetric' }, + { label: 'Achilles', method: 'With distraction', normal: '2+ symmetric' } + ], abnormalHints: ['Hyperreflexia or clonus (UMN, CP)', 'Absent reflexes (LMN, neuropathy)', 'Asymmetry'] }, + + { name: 'Coordination and gait', steps: [ + { label: 'Run', method: 'Watch run ~10 feet', normal: 'Runs with reciprocal arm swing by 2y' }, + { label: 'Stairs', method: 'Observe', normal: 'Climbs with rail' }, + { label: 'Kick ball', method: 'Place ball; observe kick', normal: 'Kicks by 2y' }, + { label: 'Ataxia check', method: 'Watch for fluency of movement', normal: 'Smooth, no tremor' } + ], abnormalHints: ['Ataxic gait', 'Intention tremor', 'Clumsiness beyond age'] }, + + { name: 'Plantar response', steps: [ + { label: 'Stroke lateral sole', method: 'Firm stroke from heel to toes', normal: 'Down-going great toe (plantar flexion) by age 2' } + ], abnormalHints: ['Up-going toe after age 2 = UMN sign (Babinski positive)'] } + ] + }, + resp: { + overview: 'Toddler respiratory disease: viral URIs, reactive airways, croup (6 mo–6 y classical age), foreign-body aspiration (age 1–3 is peak). Normal RR ≤ 40.', + components: [ + { name: 'Inspection', + pearl: 'Sudden onset of unilateral wheeze + choking history in a toddler = foreign body until proven otherwise. CXR in expiration (or decubitus) helps show the trapped air.', + steps: [ + { label: 'Respiratory rate', method: 'Count over full 60 s if possible.', normal: '≤ 40 /min' }, + { label: 'Work of breathing', method: 'Retractions, nasal flaring, tracheal tug.', normal: 'No retractions' }, + { label: 'Audible sounds', method: 'Stridor (croup), wheeze, barking cough.', normal: 'Quiet respirations' }, + { label: 'Drooling / posture', method: 'Tripod positioning, drooling (epiglottitis in unvaccinated child).', normal: 'No drooling, normal posture' } + ], + abnormalHints: ['Barking cough + stridor — croup', 'Drooling + tripod + toxic — epiglottitis (emergency)', 'Sudden unilateral wheeze — foreign body aspiration'] }, + { name: 'Auscultation', + steps: [ + { label: 'Air entry', method: 'Cooperation variable — listen quickly and systematically.', normal: 'Symmetric' }, + { label: 'Adventitious sounds', method: 'Wheeze, crackles, stridor at the neck.', normal: 'Clear lung fields' }, + { label: 'Unilateral findings', method: 'Focal wheeze, decreased air entry, or asymmetry — think foreign body or pneumonia.', normal: 'Symmetric bilateral' } + ], + abnormalHints: ['Unilateral decreased breath sounds + wheeze — foreign body', 'Focal crackles — pneumonia', 'Diffuse wheeze — asthma/RAD'] } + ] + }, + cv: { + overview: 'Most hemodynamically significant CHD has been detected by this age. Innocent murmurs peak here (Still\'s murmur, venous hum). The exam is adult-pattern but with smaller chest and less cooperation.', + components: [ + { name: 'Inspection and palpation', + pearl: 'Innocent murmurs are a normal finding in well toddlers — soft, systolic, at the LLSB, musical, and they change with position. Anything that doesn\'t fit that pattern deserves referral.', + steps: [ + { label: 'General appearance + growth', method: 'Happy, active, tracking growth.', normal: 'Normal growth and activity' }, + { label: 'Colour and clubbing', method: 'Inspect tongue, nail beds.', normal: 'Pink, no clubbing' }, + { label: 'Apex beat', method: 'Palpate at 5th ICS mid-clavicular line.', normal: 'Located at 5th ICS MCL, tapping quality' }, + { label: 'Peripheral pulses', method: 'Brachial + femoral, symmetric and simultaneous.', normal: 'Symmetric, no delay' } + ], + abnormalHints: ['Tiring with play, poor growth — missed CHD', 'Cyanosis + clubbing — cyanotic CHD', 'Absent femorals — coarctation'] }, + { name: 'Auscultation', + steps: [ + { label: 'All 5 classic points', method: 'See APTM diagram above — Aortic, Pulmonic, Erb\'s, Tricuspid, Mitral.', normal: 'Crisp S1, S2 with physiologic split at pulmonic area' }, + { label: 'Evaluate any murmur', method: 'Timing, location, radiation, grade. Apply the "7 S" innocent-murmur criteria.', normal: 'No murmur, or soft (≤ grade 2) innocent murmur' }, + { label: 'Change with position', method: 'Have toddler sit, stand, lie down — does the murmur change? Innocent murmurs typically disappear or soften with standing.', normal: 'Murmur (if any) changes with position' } + ], + abnormalHints: ['Harsh, loud (≥3/6), radiating, or diastolic murmur — not innocent, refer', 'Cyanosis + murmur — CHD workup', 'Fixed split S2 — ASD'] } + ] + } + }, + + preschool: { + label: 'Preschool (3–5 years)', + msk: { + overview: 'Functional gait maneuvers, scoliosis screen, resolving physiologic alignment.', + components: [ + { name: 'Gait — multiple patterns', steps: [ + { label: 'Normal gait', method: 'Walk ~15 feet barefoot', normal: 'Symmetric, smooth, reciprocal arm swing' }, + { label: 'Heel walking', method: 'Walk on heels only', normal: 'Able by 4y' }, + { label: 'Toe walking', method: 'Walk on toes only', normal: 'Able by 4y' }, + { label: 'Tandem walking', method: 'Heel-to-toe along a line for 5 steps', normal: 'Able by 4y with minimal deviation' }, + { label: 'Hopping', method: 'Hop on one foot', normal: '3–5 hops on preferred foot by 4y' } + ], abnormalHints: ['Persistent toe-walking', 'Asymmetric stance/stride', 'Difficulty with any pattern'] }, + + { name: 'Alignment', steps: [ + { label: 'Knee alignment', method: 'Stand feet together', normal: 'Mild residual valgus resolving; neutral by 6–7y' }, + { label: 'Foot arch', method: 'Standing, then tiptoe', normal: 'Arch forms on tiptoe (flexible flat foot)' }, + { label: 'Heel position', method: 'View from behind standing', normal: 'Neutral or mild valgus' } + ], abnormalHints: ['Persistent unilateral varum', 'Rigid flat foot (no arch on tiptoe)', 'Pes cavus'] }, + + { name: 'Scoliosis screen (Adam forward-bend)', steps: [ + { label: 'Position', method: 'Feet together, bend forward at waist, arms hanging, palms together', normal: 'Arms and head relaxed' }, + { label: 'View from behind', method: 'Examiner at same height; look along back', normal: 'Symmetric paraspinal contour' }, + { label: 'View from side', method: 'Side view for kyphosis', normal: 'Smooth thoracic curve' } + ], abnormalHints: ['Rib hump (thoracic scoliosis)', 'Lumbar prominence', 'Asymmetric scapular height'] }, + + { name: 'Joint range and stability', steps: [ + { label: 'Active range', method: 'Ask to perform full range at hips, knees, ankles, shoulders, elbows, wrists', normal: 'Full symmetric range, no pain' }, + { label: 'Knee stability', method: 'Palpate for effusion; assess stability if complaint', normal: 'Stable, no effusion' }, + { label: 'Carrying angle (elbows)', method: 'Arms at sides, palms forward', normal: 'Normal valgus carrying angle' } + ], abnormalHints: ['Joint hypermobility (Beighton score)', 'Effusion', 'Pain with motion', 'Valgus >15°'] }, + + { name: 'Feet', steps: [ + { label: 'Standing inspection', method: 'View from front and behind', normal: 'Symmetric feet, neutral heel, flexible flat feet common' }, + { label: 'Tiptoe', method: 'Stand on tiptoes', normal: 'Arch forms, symmetric' }, + { label: 'Pes planus vs cavus', method: 'Note arch height', normal: 'Flexible flat foot or mild arch' } + ], abnormalHints: ['Rigid flat foot', 'Pes cavus (Charcot-Marie-Tooth)', 'Pain'] } + ] + }, + neuro: { + overview: 'Formal pediatric neuro exam now feasible — most 4- and 5-year-olds cooperate with structured testing.', + components: [ + { name: 'Mental status and language', steps: [ + { label: 'Orientation (age-appropriate)', method: 'Ask name, age, where you are', normal: 'Knows name and age by 3y; location by 4–5y' }, + { label: 'Speech intelligibility', method: 'Listen to spontaneous speech', normal: 'Strangers understand by 4y' }, + { label: 'Receptive language', method: '2- and 3-step commands', normal: 'Follows 3-step commands by 4–5y' } + ], abnormalHints: ['Dysarthria', 'Expressive or receptive language delay', 'Inattention'] }, + + { name: 'Cranial nerves (II–XII)', steps: [ + { label: 'CN II — visual acuity', method: 'HOTV chart or pictures at 10ft, each eye', normal: '20/30 or better by 4y; 20/25 by 5y' }, + { label: 'CN II — visual fields', method: 'Confrontation with toys from periphery', normal: 'Full fields' }, + { label: 'CN II — pupils', method: 'Light response each eye', normal: 'PERRL' }, + { label: 'CN III, IV, VI — EOM', method: 'Follow toy in H pattern; include convergence', normal: 'Full EOM, convergence present' }, + { label: 'CN V — sensation', method: 'Light touch forehead, cheek, jaw each side', normal: 'Intact, symmetric' }, + { label: 'CN V — motor', method: 'Clench teeth, palpate masseter', normal: 'Symmetric strong bulk' }, + { label: 'CN VII — face', method: 'Smile, wrinkle forehead, close eyes, puff cheeks', normal: 'Symmetric movement of all regions' }, + { label: 'CN VIII — hearing', method: 'Finger rub each ear or whispered words', normal: 'Intact bilaterally' }, + { label: 'CN IX, X — palate', method: 'Open mouth, say "ahh"', normal: 'Palate rises symmetrically, uvula midline' }, + { label: 'CN XI — SCM/trapezius', method: 'Shrug shoulders, turn head against resistance', normal: 'Symmetric strength' }, + { label: 'CN XII — tongue', method: 'Stick tongue out, move side to side', normal: 'Midline, no atrophy, full movement' } + ], abnormalHints: ['Strabismus (amblyopia risk)', 'Facial weakness', 'Tongue deviation/fasciculations', 'Uvula off-midline'] }, + + { name: 'Motor — tone, bulk, strength', steps: [ + { label: 'Tone inspection', method: 'Passive range all four limbs', normal: 'Normal tone throughout' }, + { label: 'Bulk inspection', method: 'Observe muscle bulk symmetry', normal: 'Symmetric, age-appropriate' }, + { label: 'Strength — shoulder abduction', method: 'Arms out, push down against resistance', normal: '5/5 bilaterally' }, + { label: 'Strength — elbow flexion', method: 'Flex elbow against resistance', normal: '5/5 bilaterally' }, + { label: 'Strength — grip', method: 'Squeeze examiner\'s fingers', normal: '5/5 symmetric' }, + { label: 'Strength — hip flexion', method: 'Lift leg off table against resistance', normal: '5/5 bilaterally' }, + { label: 'Strength — knee extension', method: 'Straighten knee against resistance', normal: '5/5 bilaterally' }, + { label: 'Strength — dorsiflexion', method: 'Pull toes up against resistance', normal: '5/5 bilaterally' } + ], abnormalHints: ['Focal weakness', 'Gowers sign', 'Pseudohypertrophy (DMD)', 'Atrophy'] }, + + { name: 'Deep tendon reflexes', steps: [ + { label: 'Biceps', method: 'Thumb on biceps tendon, strike', normal: '2+ symmetric' }, + { label: 'Patellar', method: 'Knees hanging, strike patellar tendon', normal: '2+ symmetric' }, + { label: 'Achilles', method: 'Slight dorsiflexion, strike Achilles tendon', normal: '2+ symmetric' }, + { label: 'Plantar response', method: 'Stroke lateral sole heel-to-toes', normal: 'Down-going great toe' } + ], abnormalHints: ['Hyperreflexia or clonus (UMN)', 'Hyporeflexia (LMN)', 'Up-going plantar (Babinski — abnormal past 2y)', 'Asymmetry'] }, + + { name: 'Coordination', steps: [ + { label: 'Finger-to-nose', method: 'Touch examiner\'s finger then own nose, repeat', normal: 'Smooth, no dysmetria' }, + { label: 'Heel-to-shin', method: 'Run heel down opposite shin', normal: 'Smooth bilaterally' }, + { label: 'Rapid alternating movements', method: 'Tap palm with opposite hand alternating palm/back', normal: 'Rhythmic, symmetric' }, + { label: 'Tandem walk', method: 'Heel-to-toe for 5 steps', normal: 'Minimal deviation' } + ], abnormalHints: ['Dysmetria', 'Dysdiadochokinesia', 'Intention tremor', 'Ataxic tandem'] }, + + { name: 'Sensory', steps: [ + { label: 'Light touch — hands', method: 'Cotton wisp on palm/dorsum, eyes closed', normal: 'Feels each touch' }, + { label: 'Light touch — feet', method: 'Same on dorsum of foot bilaterally', normal: 'Feels each touch' } + ], abnormalHints: ['Focal sensory loss', 'Stocking-glove loss'] }, + + { name: 'Gait and Romberg', steps: [ + { label: 'Normal gait', method: 'Walk ~20 feet', normal: 'Smooth, symmetric' }, + { label: 'Heel walk', method: 'Walk on heels', normal: 'Able without difficulty' }, + { label: 'Toe walk', method: 'Walk on toes', normal: 'Able without difficulty' }, + { label: 'Tandem', method: 'Heel-to-toe', normal: 'Intact' }, + { label: 'Romberg (5y+)', method: 'Feet together, eyes closed, stand 10s', normal: 'Stable without sway' } + ], abnormalHints: ['Ataxic gait (cerebellar)', 'Romberg positive (dorsal column)', 'Circumduction (UMN)'] } + ] + }, + resp: { + overview: 'Adult-pattern but shorter. Cooperation better than toddler. RR ≤ 30. Common: asthma/RAD, pneumonia, URIs.', + components: [ + { name: 'Inspection', + steps: [ + { label: 'Respiratory rate', method: 'Count over full 60 s quietly.', normal: '≤ 30 /min' }, + { label: 'Work of breathing', method: 'Retractions, nasal flaring, accessory muscle use.', normal: 'Effortless breathing' }, + { label: 'Audible sounds', method: 'Wheeze, stridor, cough quality (barking = croup).', normal: 'Quiet' }, + { label: 'Chest shape', method: 'AP:transverse, hyperinflation signs.', normal: 'Not barrel-chested' } + ], + abnormalHints: ['Barrel chest — chronic asthma, cystic fibrosis', 'Retractions + wheeze — asthma exacerbation'] }, + { name: 'Auscultation', + steps: [ + { label: 'Systematic zones', method: 'Upper, mid, lower fields anteriorly and posteriorly; axillae bilaterally. Cooperative deep breaths through mouth.', normal: 'Symmetric vesicular sounds' }, + { label: 'Wheeze', method: 'Expiratory, diffuse (asthma) or focal (foreign body, rare at this age).', normal: 'No wheeze' }, + { label: 'Crackles', method: 'Focal = pneumonia; diffuse fine = interstitial disease (rare in kids).', normal: 'No crackles' } + ], + abnormalHints: ['Focal crackles + fever — pneumonia', 'Diffuse wheeze — asthma', 'Prolonged expiration with wheeze — lower airway obstruction'] } + ] + }, + cv: { + overview: 'Most CHD is detected by this age. Innocent murmurs peak in this range. Sports participation exams require thorough CV screening.', + components: [ + { name: 'Inspection and palpation', + steps: [ + { label: 'General and growth', method: 'Track on growth curve; activity tolerance.', normal: 'Normal growth, active' }, + { label: 'Apex beat', method: '5th ICS mid-clavicular line.', normal: 'Normal position and character' }, + { label: 'Peripheral pulses', method: 'Brachial + femoral simultaneously. BP in arm and leg if HTN.', normal: 'Symmetric, no delay' } + ], + abnormalHints: ['Absent femorals or arm-leg BP gradient — coarctation (always check in HTN screening)', 'Displaced apex — cardiomegaly'] }, + { name: 'Auscultation', + pearl: 'The 7 "S" criteria and the 5 classic innocent murmurs (see panel above) handle most murmurs you\'ll find in this age group. Still\'s murmur is the single most common.', + steps: [ + { label: 'All 5 classic points', method: 'Walk through A → P → E → T → M with diaphragm then bell.', normal: 'S1 S2 clear, physiologic S2 split at pulmonic, no added sounds' }, + { label: 'Any murmur', method: 'Characterise: timing, location, radiation, grade, character. Apply 7 S criteria + compare to innocent-murmur panel.', normal: 'No murmur, or innocent flow murmur meeting all 7 S criteria' }, + { label: 'Position change', method: 'Standing vs supine. Innocent murmurs typically soften or disappear on standing.', normal: 'Murmur (if any) changes with position' }, + { label: 'Sports screening extras (if applicable)', method: 'Screen for HOCM — murmur intensifies with Valsalva and standing (opposite of most).', normal: 'No murmur worsening on Valsalva' } + ], + abnormalHints: ['Murmur breaking any of the 7 S criteria — refer', 'Harsh systolic at LUSB + fixed split S2 — ASD', 'Murmur louder with Valsalva — HOCM (sports participation risk)', 'Diastolic murmur — always pathologic'] } + ] + } + }, + + school: { + label: 'School-age (6–11 years)', + msk: { + overview: 'Scoliosis screening peri-puberty, sports overuse injuries, resolving alignment.', + components: [ + { name: 'Scoliosis screen (forward-bend + scoliometer)', steps: [ + { label: 'Standing inspection', method: 'Shoulders and iliac crest heights', normal: 'Symmetric shoulder and pelvic heights' }, + { label: 'Forward bend (Adam test)', method: 'Feet together, bend forward at waist, arms hanging palms together', normal: 'Symmetric paraspinal contour' }, + { label: 'Rib hump', method: 'View tangentially from behind at level of curve', normal: 'No rib hump' }, + { label: 'Lumbar prominence', method: 'Same view at lumbar level', normal: 'No prominence' }, + { label: 'Scoliometer (if available)', method: 'Place scoliometer across rib hump, read angle of trunk rotation', normal: 'ATR < 5°; ≥7° → refer' } + ], abnormalHints: ['Rib hump', 'ATR ≥7°', 'Asymmetric shoulders or pelvis', 'Decompensation (plumb line offset)'] }, + + { name: 'Back and spine', steps: [ + { label: 'Posture inspection', method: 'Standing, view front/back/side', normal: 'Normal spinal curves; plumb line centered' }, + { label: 'Palpate spinous processes', method: 'From C2 to S1', normal: 'No tenderness, no step-off' }, + { label: 'Range of motion', method: 'Flex, extend, lateral bend, rotate', normal: 'Full painless range' } + ], abnormalHints: ['Midline tenderness', 'Step-off (spondylolisthesis)', 'Limited motion with pain'] }, + + { name: 'Alignment', steps: [ + { label: 'Knees', method: 'Feet together, inspect', normal: 'Neutral alignment by 6–7y' }, + { label: 'Feet', method: 'Stand, then tiptoes', normal: 'Medial arch present, symmetric' }, + { label: 'Leg lengths', method: 'Supine, measure ASIS to medial malleolus if asymmetric', normal: 'Equal within 1cm' } + ], abnormalHints: ['Residual valgum', 'Pes cavus', 'Leg-length discrepancy >1cm'] }, + + { name: 'Joint stability and sports exam (if active)', steps: [ + { label: 'Active range all joints', method: 'Through full range', normal: 'Full symmetric range, no pain or crepitus' }, + { label: 'Knee — Lachman (if sports-active)', method: 'Knee 20° flexion, stabilize femur, pull tibia forward', normal: 'Firm endpoint, no laxity' }, + { label: 'Knee — McMurray', method: 'Flexed knee, rotate tibia while extending', normal: 'No pain or click' }, + { label: 'Shoulder — impingement (Neer/Hawkins)', method: 'Passive shoulder flexion with arm in internal rotation', normal: 'No pain' }, + { label: 'Ankle stability', method: 'Anterior drawer and talar tilt', normal: 'No laxity' } + ], abnormalHints: ['ACL laxity (positive Lachman)', 'Meniscal click', 'Shoulder impingement', 'Ankle instability'] }, + + { name: 'Gait and functional movement', steps: [ + { label: 'Normal gait', method: 'Walk 20 feet', normal: 'Smooth, symmetric' }, + { label: 'Single-leg stance', method: 'Stand on one foot 10s each side', normal: 'Stable without Trendelenburg drop' }, + { label: 'Squat', method: 'Full squat and rise', normal: 'Full squat without pain or asymmetry' }, + { label: 'Hop on one foot', method: '5 hops each side', normal: 'Able and symmetric' } + ], abnormalHints: ['Trendelenburg sign (hip abductor weakness)', 'Antalgic gait', 'Asymmetric squat', 'Pain with hop'] } + ] + }, + neuro: { + overview: 'Adult-pattern six-component exam: mental status, CN, motor, reflexes, sensory, coordination/gait.', + components: [ + { name: 'Mental status', steps: [ + { label: 'Orientation', method: 'Name, age, school, city, day of week', normal: 'Oriented x 4' }, + { label: 'Attention', method: 'Count backward from 20; days of week backward', normal: 'Intact' }, + { label: '3-item recall', method: 'Ball-flag-tree; ask at 3 and 5 min', normal: '3/3 recall at 5 min' }, + { label: 'Language', method: 'Name common objects; repeat a sentence', normal: 'Fluent, no paraphasia' } + ], abnormalHints: ['Inattention (ADHD features)', 'Memory deficits', 'Word-finding difficulty', 'Perseveration'] }, + + { name: 'Cranial nerves (II–XII)', steps: [ + { label: 'CN II — acuity', method: 'Snellen at 20ft each eye with corrective lenses if worn', normal: '20/20 or baseline' }, + { label: 'CN II — fields', method: 'Confrontation, 4 quadrants each eye', normal: 'Full fields' }, + { label: 'CN II — fundoscopy (if indicated)', method: 'Direct ophthalmoscopy — disc, vessels, macula', normal: 'Sharp disc, normal cup-disc ratio, no papilledema' }, + { label: 'CN II, III — pupils', method: 'Direct and consensual light, accommodation', normal: 'PERRLA' }, + { label: 'CN III, IV, VI — EOM', method: 'Follow finger in H pattern; convergence', normal: 'Full EOM, no nystagmus, convergence intact' }, + { label: 'CN V — sensation', method: 'Light touch V1 (forehead), V2 (cheek), V3 (jaw) each side', normal: 'Intact, symmetric' }, + { label: 'CN V — motor', method: 'Clench teeth, palpate masseter/temporalis; jaw opening', normal: 'Symmetric strength' }, + { label: 'CN VII', method: 'Raise eyebrows, close eyes tight, smile/show teeth, puff cheeks', normal: 'Symmetric movement, all regions' }, + { label: 'CN VIII', method: 'Finger rub each ear; Weber/Rinne if deficit', normal: 'Hears bilaterally' }, + { label: 'CN IX, X', method: 'Palate elevation with "ahh"; uvula midline; voice quality', normal: 'Symmetric elevation, uvula midline, normal voice' }, + { label: 'CN XI', method: 'Shrug shoulders against resistance; head turn against resistance', normal: '5/5 SCM and trapezius' }, + { label: 'CN XII', method: 'Stick tongue out; side-to-side', normal: 'Midline, no atrophy or fasciculations' } + ], abnormalHints: ['Papilledema (increased ICP)', 'Focal cranial nerve deficit — any warrants workup', 'Tongue fasciculations (LMN/MND)'] }, + + { name: 'Motor — bulk, tone, strength', steps: [ + { label: 'Bulk inspection', method: 'Shoulders, thighs, calves, intrinsic hand muscles', normal: 'Symmetric, no atrophy' }, + { label: 'Tone', method: 'Passive range at elbows, wrists, knees, ankles', normal: 'Normal resistance throughout' }, + { label: 'Strength — deltoids', method: 'Shoulder abduction against resistance', normal: '5/5 bilaterally' }, + { label: 'Strength — biceps', method: 'Elbow flexion against resistance', normal: '5/5' }, + { label: 'Strength — triceps', method: 'Elbow extension against resistance', normal: '5/5' }, + { label: 'Strength — grip', method: 'Squeeze 2 fingers', normal: '5/5 symmetric' }, + { label: 'Strength — finger abduction', method: 'Spread fingers against resistance', normal: '5/5' }, + { label: 'Strength — hip flexion', method: 'Lift leg supine against resistance', normal: '5/5' }, + { label: 'Strength — knee extension', method: 'Straighten knee against resistance', normal: '5/5' }, + { label: 'Strength — dorsiflexion', method: 'Pull toes up against resistance', normal: '5/5' }, + { label: 'Strength — plantarflexion', method: 'Push foot down against resistance', normal: '5/5' } + ], abnormalHints: ['Focal weakness (localize)', 'Spasticity (UMN)', 'Atrophy', 'Fasciculations'] }, + + { name: 'Deep tendon reflexes', steps: [ + { label: 'Biceps (C5-C6)', method: 'Thumb on tendon, strike', normal: '2+ symmetric' }, + { label: 'Triceps (C7-C8)', method: 'Strike triceps tendon', normal: '2+ symmetric' }, + { label: 'Brachioradialis (C5-C6)', method: 'Strike distal radius', normal: '2+ symmetric' }, + { label: 'Patellar (L3-L4)', method: 'Knees hanging, strike tendon', normal: '2+ symmetric' }, + { label: 'Achilles (S1)', method: 'Slight dorsiflexion, strike tendon', normal: '2+ symmetric' }, + { label: 'Plantar response', method: 'Stroke lateral sole heel-to-toes', normal: 'Down-going bilaterally' }, + { label: 'Clonus', method: 'Rapid dorsiflexion at ankle', normal: 'No sustained clonus' } + ], abnormalHints: ['Hyperreflexia with clonus (UMN: stroke, MS, cord lesion)', 'Hyporeflexia (LMN, neuropathy, myopathy)', 'Asymmetry', 'Up-going Babinski', 'Sustained clonus'] }, + + { name: 'Sensory', steps: [ + { label: 'Light touch — upper', method: 'Cotton wisp dorsum of hands, eyes closed', normal: 'Intact, symmetric' }, + { label: 'Light touch — lower', method: 'Same on dorsum of feet', normal: 'Intact, symmetric' }, + { label: 'Pain — upper', method: 'Broken Q-tip or pin on hands', normal: 'Intact, symmetric' }, + { label: 'Pain — lower', method: 'Same on feet', normal: 'Intact, symmetric' }, + { label: 'Vibration', method: '128 Hz tuning fork at distal IP joint of great toes', normal: 'Feels vibration; counts down seconds' }, + { label: 'Proprioception', method: 'Move great toe up/down with eyes closed', normal: 'Identifies direction correctly' } + ], abnormalHints: ['Dermatomal loss (nerve root)', 'Stocking-glove (neuropathy)', 'Loss of vibration/proprioception (dorsal column — B12, tabes, MS)'] }, + + { name: 'Coordination', steps: [ + { label: 'Finger-nose-finger', method: 'Touch examiner finger then own nose, examiner moves target', normal: 'Smooth, accurate, no dysmetria' }, + { label: 'Heel-to-shin', method: 'Supine: heel down opposite shin', normal: 'Smooth, on-target' }, + { label: 'Rapid alternating movements', method: 'Supinate/pronate hand on knee rapidly', normal: 'Rhythmic, symmetric' }, + { label: 'Fine motor', method: 'Finger tapping (thumb to each finger in sequence)', normal: 'Rhythmic, accurate' } + ], abnormalHints: ['Dysmetria (past-pointing, overshoot)', 'Intention tremor', 'Dysdiadochokinesia'] }, + + { name: 'Gait and Romberg', steps: [ + { label: 'Normal gait', method: 'Walk 20 feet', normal: 'Narrow-based, smooth, reciprocal arm swing' }, + { label: 'Heel walk', method: 'Walk on heels', normal: 'Able without difficulty' }, + { label: 'Toe walk', method: 'Walk on toes', normal: 'Able without difficulty' }, + { label: 'Tandem', method: 'Heel-to-toe along a line', normal: 'Minimal deviation, 10+ steps' }, + { label: 'Romberg', method: 'Feet together, eyes closed, 30s', normal: 'Stable without fall or significant sway' } + ], abnormalHints: ['Wide-based (cerebellar)', 'Steppage (peripheral neuropathy)', 'Scissoring (UMN)', 'Romberg positive (dorsal column)', 'Circumduction'] } + ] + }, + resp: { + overview: 'Nearly adult-pattern. Exam the same as adolescent with slightly more flexibility in cooperation. RR ≤ 30 in younger school-age, ≤ 20 in older. Sports history relevant (exercise-induced asthma).', + components: [ + { name: 'Inspection', + steps: [ + { label: 'Respiratory rate', method: 'Count over 60 s.', normal: '≤ 30 (6–11 y)' }, + { label: 'Work of breathing', method: 'Retractions, accessory muscles.', normal: 'Effortless' }, + { label: 'Audible sounds', method: 'Listen for wheeze, stridor.', normal: 'Quiet' }, + { label: 'Chest shape', method: 'Barrel chest, pectus deformities.', normal: 'Normal shape' }, + { label: 'Clubbing', method: 'Schamroth window test.', normal: 'No clubbing' } + ], + abnormalHints: ['Clubbing — CF, chronic hypoxemia, bronchiectasis', 'Barrel chest — chronic asthma, CF'] }, + { name: 'Palpation and percussion', + steps: [ + { label: 'Tracheal position', method: 'Middle finger in suprasternal notch.', normal: 'Midline' }, + { label: 'Chest expansion', method: 'Hands laterally, thumbs meeting at spine. Deep breath.', normal: 'Symmetric 3–5 cm' }, + { label: 'Tactile fremitus', method: 'Ulnar side of hand; "ninety-nine". Compare sides.', normal: 'Symmetric' }, + { label: 'Percussion', method: 'Pleximeter + plexor technique. Compare sides.', normal: 'Resonant throughout' } + ], + abnormalHints: ['Deviated trachea — pneumothorax, effusion, collapse', 'Dull percussion — consolidation, effusion', 'Hyper-resonant — pneumothorax, hyperinflation'] }, + { name: 'Auscultation', + steps: [ + { label: 'Systematic zones', method: 'Six anterior + four lateral + six posterior zones, compare side-to-side.', normal: 'Symmetric vesicular sounds' }, + { label: 'Adventitious sounds', method: 'Wheeze, crackles, rhonchi, rub, stridor at neck. Use sounds library for reference.', normal: 'No added sounds' }, + { label: 'Cough re-listen', method: 'Secretions (rhonchi, coarse crackles) should clear; fibrosis crackles do not.', normal: 'Secretion-based sounds clear with cough' } + ], + abnormalHints: ['Focal crackles + fever — pneumonia', 'Diffuse fine crackles — early interstitial disease', 'Expiratory wheeze — asthma / RAD'] } + ] + }, + cv: { + overview: 'Nearly adult-pattern. Sports participation screening is a key indication in this age. HOCM screening (family history of sudden cardiac death, exertional syncope, murmur louder with Valsalva) is specifically relevant.', + components: [ + { name: 'Inspection and palpation', + pearl: 'For sports participation exams, always ask about exertional symptoms (syncope, chest pain, unexpected fatigue) AND family history of sudden cardiac death before age 50. Screening exam alone catches only ~3% of HOCM.', + steps: [ + { label: 'General and growth', method: 'Track on growth curve; review activity tolerance.', normal: 'Normal growth, age-appropriate activity' }, + { label: 'Colour and clubbing', method: 'Inspect mucous membranes and nail beds.', normal: 'Pink, no clubbing' }, + { label: 'Apex beat', method: 'Palpate at 5th ICS mid-clavicular line.', normal: 'Normal position, tapping character' }, + { label: 'Peripheral pulses', method: 'Simultaneous brachial + femoral.', normal: 'Symmetric, no delay' }, + { label: 'Blood pressure', method: 'Measure BP with appropriately sized cuff. If elevated, check both arms and one leg.', normal: 'Age-appropriate (< 120/80 roughly by 10+ years)' } + ], + abnormalHints: ['Exertional syncope — HOCM, arrhythmia, LQTS', 'BP differential — coarctation', 'Displaced apex — cardiomegaly'] }, + { name: 'Auscultation', + steps: [ + { label: 'All 5 classic points', method: 'See APTM diagram. A → P → E → T → M with diaphragm and bell.', normal: 'S1, S2 clear with physiologic split at P, no added sounds' }, + { label: 'Grade any murmur', method: 'Levine 1–6 (see scales above); characterise timing, location, radiation.', normal: 'No murmur, or innocent flow murmur meeting all 7 S criteria' }, + { label: 'Innocent vs pathologic', method: 'Apply 7 S criteria; compare to innocent-murmur panel.', normal: 'Innocent murmur (if present) clearly fits all 7 S features' }, + { label: 'Dynamic maneuvers', method: 'Standing: HOCM louder; most others soften. Valsalva: HOCM louder.', normal: 'Murmur (if any) softens on standing and Valsalva' } + ], + abnormalHints: ['Murmur louder with Valsalva / standing — HOCM (sports disqualification considerations)', 'Any diastolic murmur', 'Murmur ≥ grade 3, radiating, or with thrill'] } + ] + } + }, + + adolescent: { + label: 'Adolescent (12–21 years)', + msk: { + overview: 'Sports-related injuries, adolescent scoliosis, apophyseal overuse, hypermobility screening.', + components: [ + { name: 'Scoliosis screen', steps: [ + { label: 'Standing inspection', method: 'Patient undressed to waist (keep privacy); compare shoulders, iliac crests, scapular heights', normal: 'Symmetric shoulders and pelvis' }, + { label: 'Forward bend (Adam)', method: 'Feet together, bend forward, arms hanging palms together', normal: 'Symmetric paraspinal contour' }, + { label: 'Scoliometer', method: 'Place across thoracic and lumbar regions at maximum prominence', normal: 'ATR <5°; 5–6° monitor; ≥7° refer' }, + { label: 'Plumb line check', method: 'Drop plumb from C7; note where it falls', normal: 'Passes through gluteal cleft (compensated)' }, + { label: 'Leg lengths', method: 'Supine; ASIS to medial malleolus each side', normal: 'Within 1cm' } + ], abnormalHints: ['Rib/lumbar hump', 'ATR ≥7°', 'Decompensation (plumb off gluteal cleft)', 'Leg-length discrepancy driving apparent curve'] }, + + { name: 'Back pain evaluation (if complaint)', steps: [ + { label: 'Inspect and palpate', method: 'Spinous processes, paraspinal muscles, SI joints', normal: 'Non-tender' }, + { label: 'Range of motion', method: 'Flex, extend, lateral bend, rotate', normal: 'Full painless range' }, + { label: 'Single-leg hyperextension (stork)', method: 'Stand on one foot, extend back — each side', normal: 'No pain (negative for spondylolysis)' }, + { label: 'Straight-leg raise', method: 'Supine, lift straight leg to 70°+', normal: 'No radicular pain to 70°' }, + { label: 'SI joint tests', method: 'FABER, SI compression', normal: 'No pain' } + ], abnormalHints: ['Spondylolysis (positive stork test)', 'Radicular pain (disc herniation)', 'SI joint pathology', 'Inflammatory back pain pattern'] }, + + { name: 'Joint stability — sports-specific', steps: [ + { label: 'Knee — Lachman', method: 'Knee 20° flexion, stabilize femur, pull tibia anteriorly', normal: 'Firm endpoint, no laxity (ACL intact)' }, + { label: 'Knee — anterior drawer', method: 'Knee 90°, pull tibia forward', normal: 'No excess anterior translation' }, + { label: 'Knee — varus/valgus stress', method: 'Stress at 0 and 30° flexion', normal: 'No gap opening (LCL/MCL intact)' }, + { label: 'Knee — McMurray', method: 'Flex, rotate tibia while extending', normal: 'No pain or click' }, + { label: 'Shoulder — apprehension', method: 'Abduct and externally rotate', normal: 'No apprehension' }, + { label: 'Shoulder — Neer/Hawkins', method: 'Passive flexion with internal rotation', normal: 'No pain' }, + { label: 'Ankle — anterior drawer', method: 'Pull heel forward with tibia stabilized', normal: 'No laxity' }, + { label: 'Ankle — talar tilt', method: 'Invert heel with tibia stabilized', normal: 'No excess tilt' } + ], abnormalHints: ['ACL/PCL tear', 'MCL/LCL laxity', 'Meniscal injury', 'Shoulder instability/impingement', 'Ankle ligament laxity'] }, + + { name: 'Apophysitis and overuse screen', steps: [ + { label: 'Tibial tubercle', method: 'Palpate with knee flexed', normal: 'Non-tender' }, + { label: 'Calcaneal apophysis', method: 'Palpate posterior calcaneus', normal: 'Non-tender' }, + { label: 'Iliac apophyses', method: 'Palpate ASIS, AIIS, iliac crest', normal: 'Non-tender' }, + { label: 'Rotator cuff', method: 'Empty-can (Jobe) test', normal: 'No pain or weakness' } + ], abnormalHints: ['Osgood-Schlatter (tibial tubercle tender)', 'Sever (calcaneal tender)', 'Iliac apophysitis', 'Rotator cuff tendinopathy'] }, + + { name: 'Hypermobility screen (Beighton)', steps: [ + { label: 'Fifth finger extension', method: 'Passive extension of fifth MCP to >90°', normal: 'No hyperextension (1 pt each side if positive)' }, + { label: 'Thumb to forearm', method: 'Passive flexion of thumb to touch forearm', normal: 'Does not reach (1 pt each side if positive)' }, + { label: 'Elbow hyperextension', method: 'Hyperextension >10°', normal: 'No hyperextension (1 pt each side if positive)' }, + { label: 'Knee hyperextension', method: 'Hyperextension >10°', normal: 'No hyperextension (1 pt each side if positive)' }, + { label: 'Palms to floor', method: 'Feet together, bend forward, palms flat on floor with knees straight', normal: 'Cannot reach (1 pt if positive)' } + ], abnormalHints: ['Beighton ≥5/9 suggests hypermobility spectrum (hEDS workup if with other features)'] }, + + { name: 'Alignment and gait', steps: [ + { label: 'Standing alignment', method: 'View knees, feet', normal: 'Neutral alignment, medial arch' }, + { label: 'Normal gait', method: 'Walk 20 feet', normal: 'Symmetric, smooth' }, + { label: 'Functional movements', method: 'Squat, single-leg stance, hop', normal: 'Full symmetric function' } + ], abnormalHints: ['Antalgic gait', 'Trendelenburg', 'Asymmetric squat'] } + ] + }, + neuro: { + overview: 'Full adult-pattern neuro exam across six pillars: mental status, cranial nerves, motor, reflexes, sensory, coordination/gait. In adolescents, screen concussion sequelae if sports-active; frontal release signs must be absent.', + components: [ + { name: 'Mental status', + significance: 'Detects cognitive change (concussion, substance use, mood disorder, rare neurodegenerative disease).', + pearl: 'Attention precedes memory. A patient who can\'t attend (serial 7s, months backward) will fail memory even with intact hippocampus — distinguish before calling it a memory problem.', + steps: [ + { label: 'Orientation', method: 'Name, age, date, location, situation', normal: 'Oriented x 4' }, + { label: 'Attention', method: 'Count backward from 100 by 7s (serial 7s) or months of year backward', normal: 'Intact' }, + { label: 'Short-term memory', method: '3-item registration and recall at 5 min', normal: '3/3 recall' }, + { label: 'Language', method: 'Object naming; sentence repetition; reading; writing', normal: 'Fluent, no paraphasia, comprehends written and spoken' }, + { label: 'Executive function', method: 'Similarities (apple/orange); interpret proverb', normal: 'Abstract, age-appropriate' } + ], abnormalHints: ['Post-concussion cognitive changes', 'Mood or personality changes', 'Subtle executive dysfunction', 'Word-finding difficulty'] }, + + { name: 'Cranial nerves (II–XII, full formal exam)', + significance: 'Localises brainstem, base-of-skull, and specific nerve pathology. Subtle deficits (RAPD, mild facial weakness, Horner) are easily missed — exam discipline matters.', + pearl: 'The fastest screen for a CN deficit is asking the patient to speak, smile, look around, and swallow water. What\'s preserved in everyday function tells you what\'s likely intact — then examine formally to confirm and to catch the subtle.', + steps: [ + { label: 'CN I (if indicated)', method: 'Coffee or cinnamon each nostril separately', normal: 'Identifies both' }, + { label: 'CN II — acuity', method: 'Snellen at 20ft each eye; corrective lenses if worn', normal: '20/20 or baseline' }, + { label: 'CN II — fields', method: 'Confrontation, 4 quadrants each eye', normal: 'Full fields' }, + { label: 'CN II — fundoscopy', method: 'Direct ophthalmoscopy — disc, vessels, macula', normal: 'Sharp disc, normal cup/disc, no papilledema' }, + { label: 'CN II, III — pupils', method: 'Direct, consensual, swinging flashlight, accommodation', normal: 'PERRLA, no RAPD' }, + { label: 'CN III, IV, VI — EOM', method: 'H pattern, convergence, note nystagmus or ptosis', normal: 'Full conjugate movement, no nystagmus, convergence intact' }, + { label: 'CN V — sensation', method: 'Light touch V1, V2, V3 each side', normal: 'Intact, symmetric' }, + { label: 'CN V — motor', method: 'Clench jaw, palpate masseter/temporalis; lateral jaw movement', normal: 'Symmetric strength and bulk' }, + { label: 'CN V — corneal reflex (if indicated)', method: 'Cotton wisp to cornea', normal: 'Blinks bilaterally' }, + { label: 'CN VII', method: 'Wrinkle forehead, close eyes against resistance, smile/bare teeth, puff cheeks', normal: 'Symmetric all four movements' }, + { label: 'CN VIII — hearing', method: 'Finger rub each ear; Weber (midline) + Rinne (air > bone) if deficit', normal: 'Equal bilaterally' }, + { label: 'CN IX, X', method: 'Palate elevation with "ahh"; uvula midline; voice; gag (if indicated)', normal: 'Symmetric palate, uvula midline, normal voice' }, + { label: 'CN XI', method: 'Shoulder shrug and head turn against resistance', normal: '5/5 SCM and trapezius bilaterally' }, + { label: 'CN XII', method: 'Tongue protrusion, side to side; inspect for fasciculations/atrophy', normal: 'Midline, no atrophy or fasciculations, full movement' } + ], abnormalHints: ['Any focal cranial nerve deficit', 'Papilledema', 'RAPD', 'Nystagmus', 'Facial asymmetry', 'Tongue deviation'] }, + + { name: 'Motor — bulk, tone, strength', + significance: 'Localises lesion to UMN vs LMN vs muscle vs junction. Pattern of weakness (proximal vs distal, symmetric vs focal) narrows differential.', + pearl: 'Pronator drift is the most sensitive screen for subtle UMN weakness — a normal-feeling arm that drifts down with eyes closed still has corticospinal tract dysfunction. Always do it even when formal strength is 5/5.', + steps: [ + { label: 'Bulk inspection', method: 'Inspect shoulders, biceps, thighs, calves, dorsal interossei (between metacarpals) of hands.', normal: 'Symmetric bulk; no atrophy, no pseudohypertrophy' }, + { label: 'Tone — upper', method: 'Passive flex-extend elbow and pronate-supinate wrist at slow then quick speeds. Then pronator drift: arms outstretched, palms up, eyes closed for 10 s.', normal: 'Smooth passive range; no drift, no pronation of the outstretched hand' }, + { label: 'Tone — lower', method: 'Passive knee flexion-extension; quick ankle dorsiflexion to check for catch. Heel-slap test: roll thigh and watch for ankle swing.', normal: 'Normal resistance, no catch, symmetric' }, + { label: 'Strength — deltoid (C5)', method: 'Patient abducts both arms to 90°. Examiner pushes down on each arm just above the elbow while patient resists. Compare sides.', normal: 'Holds against full resistance — MRC 5/5 bilaterally' }, + { label: 'Strength — biceps (C5–C6)', method: 'Elbow flexed 90°, supinated. Examiner grasps wrist and pulls to extend while patient resists.', normal: 'Holds against full resistance — 5/5' }, + { label: 'Strength — triceps (C7)', method: 'Elbow flexed 90°. Examiner pushes wrist toward shoulder while patient extends against resistance.', normal: 'Extends against full resistance — 5/5' }, + { label: 'Strength — wrist extension (C6–C7)', method: 'Patient makes fist, extends wrist. Examiner pushes down on knuckles while patient holds wrist up.', normal: 'Holds against full resistance — 5/5' }, + { label: 'Strength — finger flexion / grip (C8)', method: 'Patient grips two of examiner\'s crossed fingers as hard as possible. Compare sides.', normal: 'Strong symmetric grip — 5/5' }, + { label: 'Strength — finger abduction (T1)', method: 'Patient spreads fingers wide. Examiner squeezes index and little fingers together while patient resists.', normal: 'Holds fingers apart — 5/5' }, + { label: 'Strength — hip flexion (L2–L3)', method: 'Supine. Patient lifts straight leg 30° off table. Examiner pushes down on thigh just above knee while patient resists.', normal: 'Holds thigh up against full resistance — 5/5' }, + { label: 'Strength — knee extension (L3–L4)', method: 'Sitting, knee 90°. Patient straightens knee while examiner pushes distal shin down.', normal: 'Extends against full resistance — 5/5' }, + { label: 'Strength — ankle dorsiflexion (L4–L5)', method: 'Patient pulls toes and foot up toward shin. Examiner pushes foot down at the dorsum.', normal: 'Holds dorsiflexion against full resistance — 5/5; preserved heel-walk' }, + { label: 'Strength — great toe extension (L5)', method: 'Patient extends great toe up while examiner pushes it down with thumb.', normal: 'Holds against full resistance — 5/5 (classic L5 test)' }, + { label: 'Strength — ankle plantarflexion (S1)', method: 'Patient pushes foot down against examiner\'s hand at the ball. OR ask patient to toe-walk 10 steps (more sensitive — unilateral plantarflexion weakness shows immediately).', normal: 'Full power; toe-walks symmetrically — 5/5' } + ], abnormalHints: ['Focal weakness → localise by myotome', 'Pronator drift (subtle UMN, always check even with 5/5)', 'Spasticity / catch (UMN)', 'Atrophy (LMN, disuse)', 'Fasciculations (MND, ALS)', 'Pseudohypertrophy of calves (DMD in a young male)'] }, + + { name: 'Deep tendon reflexes', + significance: 'Reflex pattern (increased, decreased, asymmetric) localises UMN vs LMN vs root vs peripheral nerve. Inexpensive and fast, but asymmetry is the most informative finding.', + pearl: 'A reinforced reflex is still a reflex. If you can\'t elicit it initially, use Jendrassik (teeth clench or pull interlocked fingers apart) to boost — absent reflexes without reinforcement aren\'t truly absent.', + steps: [ + { label: 'Biceps (C5–C6)', method: 'Patient\'s arm relaxed across lap. Examiner places thumb firmly on biceps tendon at the cubital fossa, strikes thumb with reflex hammer. Compare both sides sequentially.', normal: '2+ symmetric — visible contraction of biceps, slight elbow flexion' }, + { label: 'Brachioradialis (C5–C6)', method: 'Arm relaxed. Strike the distal radius about 3 cm proximal to the wrist, on its radial (thumb) side.', normal: '2+ symmetric — elbow flexion and slight forearm supination' }, + { label: 'Triceps (C7)', method: 'Support the patient\'s arm at the wrist with elbow at 90°. Strike the triceps tendon just above the olecranon.', normal: '2+ symmetric — triceps contraction, slight elbow extension' }, + { label: 'Finger flexors — Hoffmann sign', method: 'Grasp the middle finger\'s distal phalanx, flick it downward quickly and release. Watch the thumb and index finger.', normal: 'Negative — no thumb flexion, no index flexion (positive = corticospinal tract dysfunction)' }, + { label: 'Patellar (L3–L4)', method: 'Patient sits with knees hanging freely off the table. Strike the patellar tendon just below the patella.', normal: '2+ symmetric — quadriceps contraction with knee extension' }, + { label: 'Achilles (S1)', method: 'Patient\'s knee slightly flexed and leg externally rotated, or kneeling on a chair. Slightly dorsiflex the foot and strike the Achilles tendon.', normal: '2+ symmetric — plantar flexion of the foot' }, + { label: 'Plantar response (Babinski)', method: 'Stroke the lateral aspect of the sole firmly from the heel toward the little toe, then curve across the ball of the foot.', normal: 'Toes flex downward (plantar flexion, "down-going") bilaterally in anyone ≥ 2 years' }, + { label: 'Ankle clonus', method: 'Knee slightly bent. Support the shin with one hand, quickly and sharply dorsiflex the foot with the other, hold in dorsiflexion.', normal: '≤ 3 non-sustained beats is acceptable; sustained rhythmic oscillation = pathological clonus (UMN)' } + ], abnormalHints: ['Hyperreflexia + sustained clonus = UMN (MS, myelopathy, cord lesion, stroke)', 'Symmetric hyporeflexia = peripheral polyneuropathy, GBS, myopathy, hypothyroid, B12 deficiency', 'Asymmetric hyporeflexia = radiculopathy at that segment', 'Hoffmann positive = corticospinal tract dysfunction at cervical cord or above', 'Up-going Babinski after age 2 = UMN (always abnormal)'] }, + + { name: 'Sensory', steps: [ + { label: 'Light touch — upper', method: 'Cotton wisp, dorsum of hands, eyes closed', normal: 'Intact, symmetric' }, + { label: 'Light touch — lower', method: 'Dorsum of feet', normal: 'Intact, symmetric' }, + { label: 'Pain — upper', method: 'Broken Q-tip sharp end, hands', normal: 'Intact, symmetric' }, + { label: 'Pain — lower', method: 'Same on feet', normal: 'Intact, symmetric' }, + { label: 'Temperature (if indicated)', method: 'Cold tuning fork each area', normal: 'Intact' }, + { label: 'Vibration', method: '128 Hz tuning fork at distal IP of great toes; count seconds to fade', normal: 'Feels vibration; appropriate duration' }, + { label: 'Proprioception', method: 'Move great toe up/down with eyes closed', normal: 'Identifies direction correctly' }, + { label: 'Two-point discrimination (if indicated)', method: 'Blunt calipers on fingertip', normal: '<5mm on fingertip' }, + { label: 'Stereognosis (if indicated)', method: 'Identify coin/key in hand with eyes closed', normal: 'Correct identification' } + ], abnormalHints: ['Dermatomal loss (nerve root)', 'Stocking-glove loss (length-dependent neuropathy)', 'Dorsal column loss (B12, tabes, MS — positive Romberg, vibration loss)', 'Cortical deficit (astereognosis, impaired 2-pt)'] }, + + { name: 'Coordination', steps: [ + { label: 'Finger-nose-finger', method: 'Alternate examiner\'s finger and own nose; examiner moves target', normal: 'Smooth, accurate bilaterally' }, + { label: 'Heel-to-shin', method: 'Supine: heel down opposite shin and back', normal: 'Smooth, accurate' }, + { label: 'Rapid alternating (Dysdiadochokinesis)', method: 'Supinate/pronate hand rapidly on thigh', normal: 'Rhythmic, symmetric' }, + { label: 'Finger tapping', method: 'Thumb to each finger in sequence rapidly', normal: 'Rhythmic, smooth, symmetric' } + ], abnormalHints: ['Dysmetria (cerebellar)', 'Intention tremor', 'Dysdiadochokinesia', 'Decomposed movement'] }, + + { name: 'Gait and Romberg', steps: [ + { label: 'Normal gait', method: 'Walk 20 feet', normal: 'Narrow-based, smooth, reciprocal arm swing' }, + { label: 'Heel walk', method: 'Walk on heels only', normal: 'Able without difficulty' }, + { label: 'Toe walk', method: 'Walk on toes only', normal: 'Able without difficulty' }, + { label: 'Tandem', method: 'Heel-to-toe along a line, 10+ steps', normal: 'Minimal deviation' }, + { label: 'Romberg', method: 'Feet together, eyes open then closed, 30s', normal: 'Stable — no significant sway or fall with eyes closed' }, + { label: 'Single-leg stance', method: '10s each side, eyes open', normal: 'Stable without drift' } + ], abnormalHints: ['Ataxic (wide-based — cerebellar)', 'Steppage (peripheral neuropathy / foot drop)', 'Circumduction (UMN hemiparesis)', 'Scissoring', 'Romberg positive (dorsal column)'] }, + + { name: 'Frontal release / primitive reflexes', steps: [ + { label: 'Grasp reflex', method: 'Stroke palm', normal: 'Absent' }, + { label: 'Snout reflex', method: 'Tap upper lip', normal: 'No lip pucker' }, + { label: 'Glabellar tap', method: 'Tap between eyebrows — should habituate after 3–4 taps', normal: 'Habituates (no sustained blink)' }, + { label: 'Palmomental', method: 'Stroke thenar eminence', normal: 'No ipsilateral chin twitch' } + ], abnormalHints: ['Presence suggests frontal lobe pathology, neurodegenerative disease, or severe TBI — rare in adolescence but relevant in post-concussion workup'] } + ] + }, + resp: { + overview: 'Systematic respiratory exam: inspection → palpation → percussion → auscultation → special maneuvers. Always start from observation — rate, pattern, work of breathing, and audible sounds (stridor, grunting) can be diagnostic before the stethoscope touches the chest.', + components: [ + { name: 'Inspection — observation before touching', + significance: 'Detects respiratory distress and localises the level of airway compromise before any equipment is used. High yield: RR, WOB, audible sounds, chest shape, colour.', + pearl: 'Audible stridor at rest from across the room = upper-airway obstruction, often urgent. Grunting in an infant = significant distress — never dismiss as fussiness.', + steps: [ + { label: 'Respiratory rate', method: 'Count over a full 60 seconds (not 15×4) — children normally breathe irregularly. Count while the patient is calm, before any interaction.', normal: 'Within age-appropriate range (see scales card above)' }, + { label: 'Respiratory pattern', method: 'Observe depth, regularity, and inspiration:expiration ratio. Watch for prolonged expiration, paradoxical chest-abdominal movement, or apneas.', normal: 'Regular, I:E ratio ~1:2, no pauses > 10 s in an infant' }, + { label: 'Work of breathing', method: 'Inspect for nasal flaring, suprasternal/intercostal/subcostal retractions, accessory muscle use (SCM, abdominals), tripod positioning, head-bobbing in infants.', normal: 'No retractions; breathing effortless' }, + { label: 'Audible sounds (no stethoscope)', method: 'Listen at the bedside without the stethoscope. Grunting? Stridor? Wheezing audible across the room? Hoarse voice?', normal: 'No audible stridor, grunting, or wheeze' }, + { label: 'Chest shape and symmetry', method: 'Inspect from front and lateral. Note AP-to-transverse diameter, pectus excavatum/carinatum, chest wall asymmetry.', normal: 'AP:transverse ~1:2 (not barrel-chested); symmetric' }, + { label: 'Colour and perfusion', method: 'Inspect lips, tongue, nail beds for central cyanosis. Check peripheral perfusion (capillary refill, mottling).', normal: 'Pink, cap refill < 2 s, no cyanosis' }, + { label: 'Clubbing', method: 'Inspect fingernails: Schamroth sign (reverse a finger against its mirror — normal forms a diamond-shaped window, clubbed does not).', normal: 'Normal nail angle, Schamroth window present' } + ], + abnormalHints: ['Audible stridor — upper airway (croup, epiglottitis, foreign body, laryngomalacia)', 'Grunting in infant — significant distress', 'Tripod positioning, accessory muscle use — severe distress', 'Barrel chest — chronic air-trapping (asthma, CF)', 'Central cyanosis — significant hypoxemia', 'Clubbing in a child — cystic fibrosis, chronic hypoxemia, bronchiectasis, cyanotic CHD'] }, + + { name: 'Palpation', + significance: 'Localises pathology: consolidation increases tactile fremitus; pneumothorax/effusion decreases it. Trachea deviates AWAY from expanding lesions and TOWARD collapsing ones.', + pearl: 'Tracheal deviation is one of the fastest bedside clues to mediastinal shift — tension pneumothorax pushes it away, lobar collapse pulls it toward. Palpate with the middle finger in the suprasternal notch.', + steps: [ + { label: 'Tracheal position', method: 'Patient sitting upright, neck slightly extended. Place middle finger in the suprasternal notch, check equal distance to each SCM.', normal: 'Midline' }, + { label: 'Chest expansion — symmetry', method: 'Hands on lateral chest wall with thumbs meeting at the spine (posterior) or xiphoid (anterior). Patient takes a deep breath. Watch thumbs separate symmetrically.', normal: 'Symmetric 3–5 cm separation' }, + { label: 'Tactile fremitus', method: 'Ulnar surface of hand on chest wall. Ask patient to say "ninety-nine" repeatedly. Move hand systematically across each zone, comparing sides.', normal: 'Equal mild vibration bilaterally over lung fields' }, + { label: 'Chest wall tenderness', method: 'Palpate ribs, costochondral junctions, sternum, and intercostal spaces.', normal: 'No tenderness' }, + { label: 'Subcutaneous emphysema', method: 'Gentle palpation along clavicles, neck, chest wall.', normal: 'No crepitus under skin' } + ], + abnormalHints: ['Tracheal deviation — tension pneumothorax, large pleural effusion (away); upper lobe collapse (toward)', 'Asymmetric expansion — pneumothorax, large effusion, lobar collapse, phrenic palsy', 'Increased fremitus — consolidation (pneumonia), lobar pneumonia', 'Decreased/absent fremitus — pleural effusion, pneumothorax, obstruction', 'Costochondral tenderness — costochondritis, trauma', 'Subcutaneous emphysema — pneumothorax, tracheobronchial injury'] }, + + { name: 'Percussion', + significance: 'Differentiates air (hyper-resonant), fluid (dull), and consolidated lung (dull) without imaging. Well-performed percussion detects a pleural effusion > 300 mL or a pneumothorax with ~90% sensitivity.', + pearl: 'Pleximeter fingertip must be flat against the chest wall — lift other fingers off. The "feel" of a percussion note is as informative as the sound: dullness has a dense, reflected quality; hyper-resonance feels hollow and springy.', + steps: [ + { label: 'Technique', method: 'Place middle finger of non-dominant hand (pleximeter) flat on chest wall; strike distal IP joint with tip of dominant middle finger (plexor) using a quick wrist flick.', normal: 'N/A — technique step' }, + { label: 'Systematic zones', method: 'Percuss from apex to base, comparing side-to-side at each level. Include anterior, lateral (mid-axillary), and posterior fields.', normal: 'Resonant throughout lung fields' }, + { label: 'Cardiac dullness', method: 'Percuss from resonant lung toward the heart border. Left sternal border dullness starts at the 3rd–5th ICS.', normal: 'Dullness beginning at the expected cardiac border' }, + { label: 'Hepatic dullness', method: 'Right 5th–6th ICS mid-clavicular line transitions from resonant to dull.', normal: 'Liver edge dullness at expected level' }, + { label: 'Diaphragmatic excursion', method: 'Patient inhales fully then exhales fully; mark level of dullness at each end. Difference is diaphragm excursion.', normal: '3–5 cm excursion bilaterally' } + ], + abnormalHints: ['Hyper-resonant — pneumothorax, emphysematous bulla, severe asthma attack', 'Dull — consolidation, pleural effusion (stony dull), atelectasis, pleural thickening, large mass', 'Raised diaphragm (loss of excursion) — effusion, paralysis, subdiaphragmatic pathology'] }, + + { name: 'Auscultation — normal breath sounds', + significance: 'Breath sound quality varies by location. Bronchial sounds heard peripherally = consolidation; absent breath sounds = pneumothorax, effusion, obstruction.', + pearl: 'Always compare corresponding points side-to-side sequentially — your ear calibrates to "normal" one side and immediately hears asymmetry. Listen through a full respiratory cycle at each zone.', + steps: [ + { label: 'Technique', method: 'Diaphragm of stethoscope directly on skin (not over clothing). Patient breathes slowly and deeply through an open mouth.', normal: 'N/A — technique' }, + { label: 'Vesicular sounds (peripheral)', method: 'Listen over lung fields away from the sternum. Play the "Normal vesicular" sample above for reference.', normal: 'Soft, low-pitched, inspiration > expiration in length and loudness' }, + { label: 'Bronchovesicular (over main bronchi)', method: 'Listen at the 1st–2nd ICS anteriorly and between scapulae posteriorly.', normal: 'Intermediate pitch, inspiration = expiration' }, + { label: 'Bronchial (over trachea)', method: 'Listen directly over the manubrium or trachea.', normal: 'Harsh, high-pitched, expiration > inspiration' }, + { label: 'Systematic comparison', method: 'Six zones anteriorly (upper/mid/lower × L/R), four lateral, six posterior. Compare side-to-side at each zone.', normal: 'Symmetric breath sounds at every paired zone' } + ], + abnormalHints: ['Bronchial sounds heard peripherally — consolidation (pneumonia)', 'Absent/diminished breath sounds — pneumothorax, effusion, severe obstruction, obesity / muscular chest', 'Prolonged expiration — lower airway obstruction (asthma, bronchiolitis)'] }, + + { name: 'Auscultation — adventitious sounds', + significance: 'Adventitious (added) sounds are the key diagnostic finding. Timing (inspiratory vs expiratory vs biphasic), character (continuous vs discontinuous), and location are all informative.', + pearl: 'Ask the patient to cough and re-listen. Secretions (rhonchi, some coarse crackles) clear or change; fine crackles of fibrosis or early pneumonia do not. The cough test separates two differential groups in one maneuver.', + steps: [ + { label: 'Listen for wheeze', method: 'Continuous musical sounds, typically expiratory. Use the "Wheeze" sample for reference.', normal: 'No wheeze' }, + { label: 'Listen for crackles — fine', method: 'Short, high-pitched, discontinuous "Velcro" sounds. Typically end-inspiratory, bibasilar. Use the "Fine crackles" sample.', normal: 'No crackles' }, + { label: 'Listen for crackles — coarse', method: 'Longer, lower-pitched, louder than fine. Use the "Coarse crackles" sample.', normal: 'No crackles' }, + { label: 'Listen for rhonchi', method: 'Low-pitched, continuous, snore-like. Often change with cough. Use the "Rhonchi" sample.', normal: 'No rhonchi' }, + { label: 'Listen for pleural rub', method: 'Grating, creaky, biphasic, does NOT clear with cough. Use the "Pleural rub" sample.', normal: 'No pleural rub' }, + { label: 'Listen at the neck (for stridor)', method: 'Place stethoscope over the anterior neck. Stridor is loudest here and differentiates from wheeze (loudest over chest). Use the "Stridor" sample.', normal: 'No stridor' }, + { label: 'Listen for expiratory grunting (infants)', method: 'Often audible without a stethoscope at the bedside — short, low-pitched sound at the end of each expiration (glottal closure against exhaled air).', normal: 'No grunting' }, + { label: 'Cough re-listen', method: 'Have patient cough forcefully; re-listen to any abnormal area. Note if the sound clears or changes.', normal: 'Any secretion-based sound should clear or change with cough' } + ], + abnormalHints: ['Wheeze — asthma, bronchiolitis, foreign body (localised), anaphylaxis', 'Fine crackles — pulmonary edema, interstitial lung disease, early pneumonia', 'Coarse crackles — bronchitis, pneumonia, bronchiectasis, aspiration', 'Rhonchi — large-airway secretions', 'Pleural rub — pleurisy, PE, pneumonia with pleural involvement', 'Stridor — upper airway obstruction (croup, epiglottitis, FB)'] }, + + { name: 'Special maneuvers — transmitted voice sounds', + significance: 'Vocal resonance tests detect consolidation (increased transmission) and effusion/pneumothorax (decreased). Useful when auscultation suggests asymmetry.', + pearl: 'Whispered pectoriloquy is the most sensitive of the three — whispered words transmitted clearly through consolidated lung. If "one, two, three" whispered becomes clearly audible over one lung zone, there is consolidation underneath.', + steps: [ + { label: 'Bronchophony', method: 'Patient says "ninety-nine" in normal voice. Listen at each lung zone with the stethoscope.', normal: 'Muffled, indistinct sound' }, + { label: 'Egophony', method: 'Patient says "ee" continuously. Listen over any suspicious area.', normal: '"Ee" sounds like "ee" (no change)' }, + { label: 'Whispered pectoriloquy', method: 'Patient whispers "one, two, three" or "ninety-nine". Listen over each zone.', normal: 'Whisper is faint and indistinct' } + ], + abnormalHints: ['Bronchophony increased — consolidation', 'Egophony positive ("ee" → "A" / "ay") — consolidation, sometimes top of an effusion', 'Whispered pectoriloquy positive (whisper clearly audible) — consolidation'] } + ] + }, + cv: { + overview: 'Systematic cardiovascular exam: inspection → palpation → auscultation at the five classic points → peripheral vascular exam. Always palpate the apex BEFORE auscultating — knowing where the apex lies tells you where to put the stethoscope and flags cardiomegaly immediately.', + components: [ + { name: 'Inspection', + significance: 'Detects obvious precordial activity, chest-wall signs of congenital heart disease, and systemic markers (cyanosis, clubbing, dysmorphic features).', + pearl: 'Clubbing + central cyanosis in a well-appearing adolescent = cyanotic congenital heart disease until proven otherwise. Inspect the fingernails before reaching for the stethoscope.', + steps: [ + { label: 'General appearance', method: 'Observe body habitus, features suggesting syndromic CHD (Turner, Down, Marfan, Williams).', normal: 'No dysmorphic features, appropriate growth' }, + { label: 'Central cyanosis', method: 'Inspect lips, tongue, and oral mucosa for bluish discoloration.', normal: 'Pink oral mucosa, no cyanosis' }, + { label: 'Peripheral cyanosis / clubbing', method: 'Inspect nail beds; do Schamroth\'s window (oppose nails of 4th fingers — normally forms a diamond-shaped window).', normal: 'Pink nail beds, Schamroth window present' }, + { label: 'Precordial bulge', method: 'Inspect anterior chest wall tangentially for asymmetric prominence over the heart.', normal: 'Symmetric chest, no bulge' }, + { label: 'Visible apex beat', method: 'Inspect for a visible cardiac impulse at the 5th ICS mid-clavicular line.', normal: 'Apex may be visible in thin patients; should not be displaced' }, + { label: 'Neck veins (JVP)', method: 'Patient reclined 45°, head turned slightly left. Observe the right internal jugular pulsation; measure vertical height above the sternal angle.', normal: '≤ 4 cm above sternal angle (≤ 9 cm H₂O from right atrium)' } + ], + abnormalHints: ['Central cyanosis — right-to-left shunt, severe hypoxemia', 'Clubbing — cyanotic CHD, chronic hypoxemia', 'Precordial bulge — long-standing cardiomegaly (grew during skeletal growth)', 'Visible apex displaced lateral/inferior — cardiomegaly', 'Elevated JVP — right-heart failure, fluid overload, cardiac tamponade'] }, + + { name: 'Palpation', + significance: 'Localises the apex (confirms cardiac size), detects thrills (loud murmurs), and identifies a parasternal heave (RV hypertrophy).', + pearl: 'If you feel a thrill, the murmur is at least grade 4/6 — grade your murmur as ≥4 even if it sounds less impressive. Thrill = loud, palpable turbulence.', + steps: [ + { label: 'Apex beat — localise', method: 'Feel with the tips of the fingers at the 5th ICS mid-clavicular line. If not found, roll the patient to the left lateral decubitus position.', normal: 'Located at 5th ICS, mid-clavicular line, less than 2 cm in diameter' }, + { label: 'Apex character', method: 'Describe: tapping (normal), heaving (pressure overload, e.g. AS/HTN), thrusting (volume overload, e.g. AR/MR), dyskinetic (MI/aneurysm).', normal: 'Brief tapping quality' }, + { label: 'Parasternal heave', method: 'Place the heel of the hand along the left sternal border. Sustained outward movement with each systole = heave.', normal: 'No heave' }, + { label: 'Thrills', method: 'Use the palmar aspect of the hand at each of the 5 auscultation areas (A, P, E, T, M). A thrill = palpable turbulence.', normal: 'No thrills' }, + { label: 'Peripheral pulses — upper', method: 'Palpate radial pulses bilaterally, then brachial. Note rate, rhythm, volume, and symmetry.', normal: 'Symmetric 2+ pulses, regular rhythm, age-appropriate rate' }, + { label: 'Peripheral pulses — lower', method: 'Palpate femoral pulses. Compare to brachial — radio-femoral or brachio-femoral delay suggests coarctation of the aorta.', normal: 'Femoral pulses 2+ symmetric, no delay relative to radial' } + ], + abnormalHints: ['Apex displaced laterally/inferiorly — cardiomegaly', 'Heaving apex — pressure overload (AS, HTN)', 'Thrusting apex — volume overload (AR, MR)', 'Thrill over precordium — always pathological; at least grade 4/6 murmur', 'Parasternal heave — RV hypertrophy (pulmonary HTN, pulmonary stenosis, VSD with Eisenmenger)', 'Radio-femoral delay — coarctation of the aorta (always check in a hypertensive adolescent)'] }, + + { name: 'Auscultation — approach', + significance: 'Systematic technique ensures every relevant finding is detected. Listen at all 5 points, with both diaphragm and bell, in supine/sitting/left-lateral positions as needed.', + pearl: 'Time every murmur by simultaneously palpating the carotid pulse with the fingers of your free hand. Pulse = systole. Murmur heard during the pulse = systolic; in between pulses = diastolic.', + steps: [ + { label: 'Positioning', method: 'Patient supine, head of bed at 30°. Exam room quiet, patient relaxed. Warm the stethoscope first.', normal: 'N/A — technique' }, + { label: 'Diaphragm technique', method: 'Firm contact with skin. Detects HIGH-pitched sounds: S1, S2, systolic ejection murmurs, AR, MR.', normal: 'N/A — technique' }, + { label: 'Bell technique', method: 'Very light contact — enough to make a seal but not stretch the skin. Detects LOW-pitched sounds: S3, S4, mitral stenosis rumble.', normal: 'N/A — technique' }, + { label: 'Listen at each of the 5 points', method: 'A → P → E → T → M in order, each with diaphragm then bell. Spend a full cycle at each zone.', normal: 'S1 crisp, S2 clear (splits physiologically on inspiration at P), no added sounds, no murmur' }, + { label: 'Left lateral decubitus position', method: 'If apex murmur suspected. Roll patient to left side. Listen at the apex with the BELL for mitral stenosis rumble or S3/S4.', normal: 'No added sounds, no diastolic rumble' }, + { label: 'Sitting forward, held expiration', method: 'Patient leans forward, exhales fully, holds. Listen at left lower sternal border and Erb\'s point with the DIAPHRAGM for aortic regurgitation (soft early diastolic decrescendo).', normal: 'No early-diastolic murmur' } + ], + abnormalHints: ['Fixed split S2 (no change with respiration) — ASD', 'Loud S2 at pulmonic area — pulmonary HTN', 'S3 — volume overload, CHF (can be normal in young athletes)', 'S4 — stiff ventricle (HTN, HCM, ischemia)', 'Audible opening snap — mitral stenosis (rare in children)'] }, + + { name: 'Auscultation — heart sounds and murmurs', + significance: 'Characterising a murmur by timing, location, radiation, pitch, quality, and dynamic maneuvers narrows the differential.', + pearl: 'Innocent murmurs in children share 7 "S" features: Soft (≤ grade 2), Systolic, Short, Single (no added S3/S4), Small (localised, non-radiating), Sweet (musical), Sensitive to position/respiration (louder supine, softer standing). Anything breaking this pattern deserves workup.', + steps: [ + { label: 'S1', method: 'Listen at the apex (mitral). Coincides with the carotid pulse upstroke. Mitral + tricuspid closure.', normal: 'Single, crisp, single-component sound' }, + { label: 'S2', method: 'Listen at the pulmonic area in HELD INSPIRATION and HELD EXPIRATION. Note whether S2 splits physiologically (wider in inspiration, narrower/absent in expiration).', normal: 'Physiologic split (widens on inspiration, narrows on expiration)' }, + { label: 'S3 / S4 gallops', method: 'Bell at the apex in left lateral decubitus. S3 = early diastole (after S2), low-pitched. S4 = late diastole (just before S1).', normal: 'Absent in adults; S3 can be normal in young athletes under age 30' }, + { label: 'Identify murmur — timing', method: 'Time vs carotid pulse. Systolic (during pulse) vs diastolic (between pulses) vs continuous.', normal: 'No murmur, or only soft innocent flow murmur' }, + { label: 'Identify murmur — location + radiation', method: 'Where loudest? Does it radiate? AS → carotids. MR → axilla. Coarctation → back.', normal: 'N/A — characterise only if murmur present' }, + { label: 'Identify murmur — character', method: 'Crescendo-decrescendo (ejection) vs holosystolic (plateau) vs decrescendo early-diastolic (AR, PR) vs mid-diastolic rumble (MS, TS).', normal: 'N/A — characterise only if murmur present' }, + { label: 'Grade intensity', method: 'Levine 1–6 scale (see scales card above).', normal: 'No murmur, or grade ≤ 2 soft innocent flow murmur' }, + { label: 'Dynamic maneuvers', method: 'Standing: ↑HOCM, ↑MVP click (earlier). Squatting: opposite. Valsalva: ↑HOCM, most others decrease.', normal: 'No significant change with posture' } + ], + abnormalHints: ['Holosystolic murmur at apex → axilla — mitral regurgitation', 'Holosystolic at lower left sternal border (LLSB) — VSD, tricuspid regurgitation', 'Systolic ejection at upper right sternal border → carotids — aortic stenosis', 'Systolic ejection at upper left sternal border — pulmonary stenosis', 'Continuous "machinery" below left clavicle — PDA', 'Early diastolic at Erb\'s point, leaning forward — aortic regurgitation', 'Diastolic rumble at apex, bell in left-lateral — mitral stenosis', 'Fixed split S2 + systolic flow murmur — ASD'] }, + + { name: 'Peripheral vascular exam', + significance: 'Coarctation of the aorta hides until BP and pulses are checked in all four extremities. Differential diagnosis of a hypertensive adolescent should include this in the first 60 seconds.', + pearl: 'Four-limb BP measurement is mandatory in any adolescent with hypertension or a murmur. Upper-extremity BP > lower-extremity BP (or brachio-femoral delay) = coarctation until excluded.', + steps: [ + { label: 'Four-limb blood pressure', method: 'Measure BP in right arm, left arm, and at least one leg. Use appropriately sized cuff (bladder width 40% of limb circumference, length 80–100%).', normal: 'Arm BPs within 10 mmHg of each other; leg systolic within 20 mmHg of arm systolic (may be higher)' }, + { label: 'Radial pulses', method: 'Palpate both radials simultaneously — note any delay or asymmetry.', normal: 'Simultaneous, symmetric, 2+' }, + { label: 'Radio-femoral delay', method: 'Palpate radial and femoral simultaneously. Feel the femoral as clearly "after" the radial = delay.', normal: 'No delay' }, + { label: 'Femoral pulses', method: 'Palpate both femoral pulses at the mid-inguinal point. Compare amplitude to radials.', normal: 'Symmetric 2+ pulses, equal amplitude to radial' }, + { label: 'Dorsalis pedis + posterior tibialis', method: 'Palpate in both feet.', normal: '2+ pulses bilaterally' }, + { label: 'Capillary refill', method: 'Press and release the nail bed; time to normal colour.', normal: '< 2 sec' } + ], + abnormalHints: ['Asymmetric upper-extremity BP (> 10 mmHg) — subclavian stenosis or coarctation at the origin', 'Upper >> lower-extremity BP — coarctation of the aorta', 'Diminished or absent femoral pulses with brachio-femoral delay — coarctation', 'Bounding pulses with wide pulse pressure — AR, PDA, arteriovenous fistula, thyrotoxicosis, anemia', 'Weak thready pulses — low output state (heart failure, shock, hypovolemia)', 'Prolonged capillary refill — dehydration, shock, cold stress'] } + ] + } + } +}; diff --git a/client/src/pages/PeGuide.tsx b/client/src/pages/PeGuide.tsx index e1d2bb5..ee990b5 100644 --- a/client/src/pages/PeGuide.tsx +++ b/client/src/pages/PeGuide.tsx @@ -1,29 +1,37 @@ // ============================================================ -// PHYSICAL EXAM GUIDE — minimum-viable React port. +// PHYSICAL EXAM GUIDE — full React port. // -// What lands in this commit: -// • Scales reference — MRC, DTR, Plantar, Beighton, ATR, RR by age, -// SpO2, Silverman, Westley, murmur Levine grade, pulse amp, cap refill -// • APTM auscultation-points legend (5 classic cardiac points) -// • Innocent murmurs of childhood reference -// • Respiratory sounds library with audio playback -// • Cardiac sounds library with audio playback +// Renders: +// • Age-group + system pills (6 × 4 = 24 combinations) +// • System overview banner +// • CV system extras: APTM legend, cardiac sounds, innocent murmurs +// • Resp system extras: respiratory sounds library +// • Collapsible grading-scales reference (system-scoped) +// • Component checklist with per-step normal / abnormal / (unset) +// toggle, abnormal-hints hint list, pearl + significance callouts +// • Patient age / gender + model inputs +// • Generate Exam Report → POST /api/generate-pe-narrative // -// What is intentionally NOT in this commit: -// • PE_DATA — the ~1000-line age-group × system × component × step -// hierarchy. The migration checkpoint memory flags this as the -// class of data where an LLM silently drops entries; it belongs in -// its own session with per-entry counts verified against the -// vanilla source. The exam-step checklist + Generate-Exam-Report -// flow (POST /api/generate-pe-narrative) continues to live in the -// vanilla app at / until that session lands. -// • APTM SVG diagram — big inline SVG; the letter legend is preserved, -// the pictorial chest outline can follow without clinical risk. +// PE_DATA is the full hierarchy ported verbatim from vanilla +// peGuide.js (see client/src/data/pe-data.ts). Clinical reference +// libraries (scales, APTM, sound files) live in pe-guide.ts. // ============================================================ -import { useRef } from 'react'; +import { useMemo, useState } from 'react'; +import { useMutation } from '@tanstack/react-query'; +import { api } from '@/lib/api'; +import type { PeNarrativeOk } from '@/shared/types'; +import { + PE_DATA, + AGE_GROUP_ORDER, + SYSTEM_ORDER, + SYSTEM_LABELS, + type PeComponent, + type PeStep, +} from '@/data/pe-data'; import { SCALES, + SYSTEM_SCALES, APTM_LEGEND, INNOCENT_MURMURS, RESP_SOUNDS, @@ -33,18 +41,28 @@ import { } from '@/data/pe-guide'; const card = 'rounded-lg border border-border bg-card p-5 space-y-3'; -const btnPrimary = 'rounded-md bg-primary text-primary-foreground px-3 py-2 text-sm font-medium disabled:opacity-50'; +const pill = 'px-3 py-1.5 rounded-full text-xs font-medium border transition-colors cursor-pointer'; +const btnPrimary = 'rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50'; +const btnGhost = 'rounded-md border border-border bg-background px-3 py-2 text-sm font-medium hover:bg-muted disabled:opacity-50'; +const input = 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring'; + +type StepStatus = 'normal' | 'abnormal' | null; + +// Key used to identify a step in the status map across age-group / system. +function stepKey(age: string, sys: string, componentIdx: number, stepIdx: number) { + return `${age}/${sys}/${componentIdx}/${stepIdx}`; +} function ScaleCard({ id, scale }: { id: string; scale: ScaleDef }) { return ( -
-

{scale.title}

- +
+

{scale.title}

+
- {scale.rows.map(([label, desc], i) => ( + {scale.rows.map(([labelText, desc], i) => ( - - + + ))} @@ -54,119 +72,399 @@ function ScaleCard({ id, scale }: { id: string; scale: ScaleDef }) { } function SoundCard({ entry }: { entry: SoundEntry }) { - const ref = useRef(null); return ( -
-

{entry.title}

-
+ + ); +} + +function StepRow({ + step, + status, + onStatus, +}: { + step: PeStep; + status: StepStatus; + onStatus: (next: StepStatus) => void; +}) { + const base = 'text-xs font-medium px-2 py-1 rounded border'; + return ( +
+
+
{step.label}
+
+ Method: {step.method} +
+
+ Normal: {step.normal} +
+
+
+ + +
+
+ ); +} + +function ComponentCard({ + age, + sys, + idx, + comp, + getStatus, + setStatus, +}: { + age: string; + sys: string; + idx: number; + comp: PeComponent; + getStatus: (k: string) => StepStatus; + setStatus: (k: string, next: StepStatus) => void; +}) { + return ( +
+

{comp.name}

+
+ {comp.steps.map((step, si) => { + const k = stepKey(age, sys, idx, si); + return ( + setStatus(k, next)} + /> + ); + })} +
+ {comp.abnormalHints.length > 0 && ( +
+
Watch for
+
    + {comp.abnormalHints.map((h, hi) =>
  • {h}
  • )} +
+
+ )} + {comp.pearl && ( +
+ Pearl: {comp.pearl} +
+ )} + {comp.significance && ( +
+ Significance: {comp.significance} +
+ )} +
); } export default function PeGuide() { + const [age, setAge] = useState<(typeof AGE_GROUP_ORDER)[number]>('toddler'); + const [sys, setSys] = useState<(typeof SYSTEM_ORDER)[number]>('msk'); + const [patientAge, setPatientAge] = useState(''); + const [patientGender, setPatientGender] = useState(''); + const [format, setFormat] = useState<'narrative' | 'list'>('narrative'); + const [statusMap, setStatusMap] = useState>({}); + const [narrative, setNarrative] = useState(null); + + const group = PE_DATA[age]; + const section = group[sys]; + + const generate = useMutation({ + mutationFn: (body: unknown) => api.post('/api/generate-pe-narrative', body), + onSuccess: (data) => setNarrative(data.narrative), + onError: (e: Error) => setNarrative('Generation failed: ' + e.message), + }); + + const summary = useMemo(() => { + let normal = 0, abnormal = 0, notAssessed = 0; + section.components.forEach((c, ci) => + c.steps.forEach((_, si) => { + const k = stepKey(age, sys, ci, si); + const v = statusMap[k] ?? null; + if (v === 'normal') normal++; + else if (v === 'abnormal') abnormal++; + else notAssessed++; + }), + ); + return { normal, abnormal, notAssessed }; + }, [age, sys, section, statusMap]); + + function reset() { + // Only clear the current system's entries, not all state. + setStatusMap((prev) => { + const next = { ...prev }; + section.components.forEach((c, ci) => + c.steps.forEach((_, si) => { delete next[stepKey(age, sys, ci, si)]; }), + ); + return next; + }); + setNarrative(null); + } + + function setAllNormal() { + setStatusMap((prev) => { + const next = { ...prev }; + section.components.forEach((c, ci) => + c.steps.forEach((_, si) => { next[stepKey(age, sys, ci, si)] = 'normal'; }), + ); + return next; + }); + } + + function onGenerate() { + setNarrative(null); + const steps: Array<{ component: string; label: string; method: string; normal: string; status: StepStatus; note?: string }> = []; + section.components.forEach((c, ci) => + c.steps.forEach((st, si) => { + steps.push({ + component: c.name, + label: st.label, + method: st.method, + normal: st.normal, + status: statusMap[stepKey(age, sys, ci, si)] ?? null, + }); + }), + ); + generate.mutate({ + steps, + ageGroup: age, + system: sys, + patientAge: patientAge || undefined, + patientGender: patientGender || undefined, + format, + }); + } + + const totalAssessed = summary.normal + summary.abnormal; + return ( -
+

Physical Exam Guide

- Reference scales, the five cardiac auscultation points, innocent childhood murmurs, and sound libraries. + Age-group and system-specific exam checklist with abnormal-finding hints. Toggle normal / abnormal + on each step, then generate a narrative for your note.

-
-
Exam-step checklist lives in the legacy viewer
-

- The full age-group × system checklist and the Generate Exam Report flow still run in the legacy app. They port in a - dedicated session so the clinical content can be verified entry-for-entry against the vanilla source. -

- - Open checklist in legacy viewer - -
- - {/* Scales */} -
-

Grading scales & reference ranges

-
- {Object.entries(SCALES).map(([id, scale]) => ( - + {/* Age-group pills */} +
+
Age group
+
+ {AGE_GROUP_ORDER.map((g) => ( + ))}
+
+ + {/* System pills */} +
+
System
+
+ {SYSTEM_ORDER.map((s) => ( + + ))} +
+
+ + {/* Overview */} +
+

{group.label} — {SYSTEM_LABELS[sys]}

+

{section.overview}

- {/* APTM legend */} -
-

Cardiac auscultation — APTM (the five points)

-
- {APTM_LEGEND.map((a) => ( -
-
+ {/* System-specific references */} + {sys === 'cv' && ( +
+

Auscultation landmarks (APTM + Erb's)

+
+ {APTM_LEGEND.map((p) => ( +
- {a.letter} + {p.letter} +
+
+
{p.title}
+
{p.location}
+
Listen for: {p.listen}
+ {p.innocent &&
Innocent: {p.innocent}
}
-

{a.title}

-
Location: {a.location}
-
Listen for: {a.listen}
- {a.innocent && ( -
- Innocent: {a.innocent} -
- )} -
+ ))} +
+

Cardiac sounds library

+
+ {CARDIAC_SOUNDS.map((s) => )} +
+

Classic innocent murmurs

+
+ {INNOCENT_MURMURS.map((m) => ( +
+
{m.name}
+
Age: {m.age} · Location: {m.location}
+
Sound: {m.character}
+
Confirm innocent: {m.confirm}
+
+ ))} +
+
+ )} + + {sys === 'resp' && ( +
+

Respiratory sounds library

+
+ {RESP_SOUNDS.map((s) => )} +
+
+ )} + + {/* Grading scales (system-scoped, collapsible) */} + {SYSTEM_SCALES[sys] && SYSTEM_SCALES[sys].length > 0 && ( +
+ Grading scales & reference +
+ {SYSTEM_SCALES[sys].map((sk: string) => { + const scale = SCALES[sk]; + if (!scale) return null; + return ; + })} +
+
+ )} + + {/* Checklist */} +
+
+

Exam checklist

+
+ {summary.normal} normal + {summary.abnormal} abnormal + {summary.notAssessed} not assessed +
+
+
+ + +
+
+ {section.components.map((c, ci) => ( + statusMap[k] ?? null} + setStatus={(k, next) => setStatusMap((prev) => ({ ...prev, [k]: next }))} + /> ))}
- {/* Innocent murmurs */} -
-

Innocent murmurs of childhood

-
- {INNOCENT_MURMURS.map((m) => ( -
-

{m.name}

-
-
Age: {m.age}
-
Location: {m.location}
-
Character: {m.character}
-
Confirm benign: {m.confirm}
-
-
- ))} + {/* Generate narrative */} +
+

Generate Exam Report

+

Uses the statuses above + optional patient context.

+
+ setPatientAge(e.target.value)} + /> + setPatientGender(e.target.value)} + /> +
-
- - {/* Respiratory sounds */} -
-

Respiratory sounds library

-
- {RESP_SOUNDS.map((s) => )} -
-
- - {/* Cardiac sounds */} -
-

Cardiac sounds library

-
- {CARDIAC_SOUNDS.map((s) => )} +
+ + {totalAssessed === 0 && ( + Mark at least one step before generating. + )}
+ {narrative && ( +
+ {narrative} +
+ )}
); diff --git a/e2e/tests/peguide-react.spec.js b/e2e/tests/peguide-react.spec.js index 2521bcb..077e102 100644 --- a/e2e/tests/peguide-react.spec.js +++ b/e2e/tests/peguide-react.spec.js @@ -1,48 +1,84 @@ // ============================================================ -// PE GUIDE (React port) — smoke tests for the reference page. -// -// Covers the subset that landed in the first commit: scales, -// APTM legend, innocent murmurs, respiratory + cardiac sound -// libraries. The full PE_DATA exam-step checklist is still in -// the legacy viewer and linked to from the amber banner. +// PE GUIDE (React port) — smoke tests for the full checklist. +// Covers age-group + system pills, overview, scales, sound +// libraries, component checklist with status toggles, and the +// Generate Exam Report path (mocked via page.route). // ============================================================ const { test, expect, E2E_BASE } = require('../fixtures'); -async function openReactPeGuide(page) { +async function openPeGuide(page) { await page.goto(E2E_BASE + '/app/peguide'); - await page.waitForSelector('[data-testid^="scale-"]', { timeout: 15000 }); + await page.waitForSelector('[data-testid="pe-age-group-pills"]', { timeout: 15000 }); } -test.describe('React PE Guide — reference content renders', () => { +test.describe('React PE Guide — age-group × system navigation', () => { - test('all 12 scales render (MRC, DTR, Plantar, Beighton, ATR, RR, SpO2, Silverman, Westley, murmur Levine, pulse amp, cap refill)', async ({ authedPage: _, page }) => { - await openReactPeGuide(page); - const count = await page.locator('[data-testid^="scale-"]').count(); - expect(count).toBe(12); - }); - - test('APTM legend shows all 5 points (A, P, E, T, M)', async ({ authedPage: _, page }) => { - await openReactPeGuide(page); - for (const letter of ['a', 'p', 'e', 't', 'm']) { - await expect(page.locator('[data-testid="aptm-' + letter + '"]')).toBeVisible(); + test('all 6 age-group pills render', async ({ authedPage: _, page }) => { + await openPeGuide(page); + for (const age of ['newborn', 'infant', 'toddler', 'preschool', 'school', 'adolescent']) { + await expect(page.locator('[data-testid="pe-age-' + age + '"]')).toBeVisible(); } }); - test('sound libraries: 7 respiratory + 6 cardiac entries', async ({ authedPage: _, page }) => { - await openReactPeGuide(page); - const respiratoryKeys = ['normal', 'wheeze', 'stridor', 'finecrackles', 'coarsecrackles', 'rhonchi', 'pleuralrub']; - const cardiacKeys = ['normal', 'infant-normal', 'vsd', 'mvp', 'stills', 'functional']; - for (const k of respiratoryKeys) { - expect(await page.locator('[data-testid="sound-' + k + '"]').count()).toBeGreaterThan(0); - } - for (const k of cardiacKeys) { - expect(await page.locator('[data-testid="sound-' + k + '"]').count()).toBeGreaterThan(0); + test('all 4 system pills render', async ({ authedPage: _, page }) => { + await openPeGuide(page); + for (const sys of ['msk', 'neuro', 'resp', 'cv']) { + await expect(page.locator('[data-testid="pe-system-' + sys + '"]')).toBeVisible(); } }); - test('legacy-viewer link for exam-step checklist is present', async ({ authedPage: _, page }) => { - await openReactPeGuide(page); - await expect(page.getByText('Open checklist in legacy viewer')).toBeVisible(); + test('switching age group rewrites the overview banner', async ({ authedPage: _, page }) => { + await openPeGuide(page); + await page.click('[data-testid="pe-age-newborn"]'); + await expect(page.locator('[data-testid="pe-overview"]')).toContainText('Newborn'); + await page.click('[data-testid="pe-age-adolescent"]'); + await expect(page.locator('[data-testid="pe-overview"]')).toContainText('Adolescent'); + }); + + test('CV system shows APTM + cardiac sounds + innocent murmurs', async ({ authedPage: _, page }) => { + await openPeGuide(page); + await page.click('[data-testid="pe-system-cv"]'); + await expect(page.locator('[data-testid="pe-cv-aptm"]')).toBeVisible(); + await expect(page.locator('[data-testid="sound-normal"]').first()).toBeVisible(); + }); + + test('Resp system shows respiratory sound library', async ({ authedPage: _, page }) => { + await openPeGuide(page); + await page.click('[data-testid="pe-system-resp"]'); + await expect(page.locator('[data-testid="pe-resp-sounds"]')).toBeVisible(); + }); +}); + +test.describe('React PE Guide — checklist + generation', () => { + + test('mark-all-normal sets summary counts and enables Generate', async ({ authedPage: _, page }) => { + await openPeGuide(page); + await page.click('[data-testid="pe-age-toddler"]'); + await page.click('[data-testid="pe-system-msk"]'); + await page.click('[data-testid="btn-pe-all-normal"]'); + await expect(page.locator('[data-testid="pe-checklist"]')).toContainText('0 not assessed'); + await expect(page.locator('[data-testid="btn-pe-generate"]')).toBeEnabled(); + }); + + test('Generate Exam Report calls /api/generate-pe-narrative', async ({ authedPage: _, page }) => { + await page.route('**/api/generate-pe-narrative', (route) => + route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + success: true, + model: 'mock-model', + narrative: 'MOCK PE narrative from the React port.', + summary: { normal: 5, abnormal: 0, notAssessed: 0 }, + }), + }), + ); + await openPeGuide(page); + await page.click('[data-testid="pe-age-toddler"]'); + await page.click('[data-testid="pe-system-msk"]'); + await page.click('[data-testid="btn-pe-all-normal"]'); + await page.click('[data-testid="btn-pe-generate"]'); + await expect(page.locator('[data-testid="pe-narrative"]')).toContainText('MOCK PE narrative'); }); }); diff --git a/public/app/assets/index-9V57IPOu.css b/public/app/assets/index-9V57IPOu.css deleted file mode 100644 index 5e2af10..0000000 --- a/public/app/assets/index-9V57IPOu.css +++ /dev/null @@ -1,2 +0,0 @@ -/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-900:oklch(40.8% .123 38.172);--color-orange-950:oklch(26.6% .079 36.259);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-900:oklch(41.4% .112 45.904);--color-amber-950:oklch(27.9% .077 45.635);--color-green-50:oklch(98.2% .018 155.826);--color-green-300:oklch(87.1% .15 154.449);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-950:oklch(26.6% .065 152.934);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--radius:.5rem;--color-background:#fff;--color-foreground:#0f172a;--color-muted:#f1f5f9;--color-muted-foreground:#64748b;--color-card:#fff;--color-primary:#0f172a;--color-primary-foreground:#f8fafc;--color-destructive:#ef4343;--color-border:#e2e8f0;--color-input:#e2e8f0;--color-ring:#94a3b8}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.fixed{position:fixed}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.top-0{top:calc(var(--spacing) * 0)}.left-0{left:calc(var(--spacing) * 0)}.z-50{z-index:50}.col-span-2{grid-column:span 2/span 2}.col-span-3{grid-column:span 3/span 3}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-2{margin-left:calc(var(--spacing) * 2)}.block{display:block}.flex{display:flex}.grid{display:grid}.inline{display:inline}.inline-block{display:inline-block}.table{display:table}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.h-10{height:calc(var(--spacing) * 10)}.h-screen{height:100vh}.min-h-\[60px\]{min-height:60px}.min-h-\[100px\]{min-height:100px}.min-h-\[120px\]{min-height:120px}.min-h-\[160px\]{min-height:160px}.min-h-\[200px\]{min-height:200px}.min-h-\[220px\]{min-height:220px}.min-h-screen{min-height:100vh}.w-10{width:calc(var(--spacing) * 10)}.w-20{width:calc(var(--spacing) * 20)}.w-64{width:calc(var(--spacing) * 64)}.w-full{width:100%}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-\[140px\]{max-width:140px}.max-w-\[240px\]{max-width:240px}.max-w-full{max-width:100%}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[150px\]{min-width:150px}.min-w-\[180px\]{min-width:180px}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing) * 1)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:var(--radius)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-amber-300{border-color:var(--color-amber-300)}.border-border{border-color:var(--color-border)}.border-input{border-color:var(--color-input)}.border-primary{border-color:var(--color-primary)}.border-red-200{border-color:var(--color-red-200)}.border-l-orange-500{border-left-color:var(--color-orange-500)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-background{background-color:var(--color-background)}.bg-black\/40{background-color:#0006}@supports (color:color-mix(in lab, red, red)){.bg-black\/40{background-color:color-mix(in oklab, var(--color-black) 40%, transparent)}}.bg-card{background-color:var(--color-card)}.bg-destructive{background-color:var(--color-destructive)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-600{background-color:var(--color-green-600)}.bg-muted{background-color:var(--color-muted)}.bg-muted\/20{background-color:#f1f5f933}@supports (color:color-mix(in lab, red, red)){.bg-muted\/20{background-color:color-mix(in oklab, var(--color-muted) 20%, transparent)}}.bg-muted\/30{background-color:#f1f5f94d}@supports (color:color-mix(in lab, red, red)){.bg-muted\/30{background-color:color-mix(in oklab, var(--color-muted) 30%, transparent)}}.bg-muted\/40{background-color:#f1f5f966}@supports (color:color-mix(in lab, red, red)){.bg-muted\/40{background-color:color-mix(in oklab, var(--color-muted) 40%, transparent)}}.bg-orange-50{background-color:var(--color-orange-50)}.bg-primary{background-color:var(--color-primary)}.bg-primary\/5{background-color:#0f172a0d}@supports (color:color-mix(in lab, red, red)){.bg-primary\/5{background-color:color-mix(in oklab, var(--color-primary) 5%, transparent)}}.bg-primary\/10{background-color:#0f172a1a}@supports (color:color-mix(in lab, red, red)){.bg-primary\/10{background-color:color-mix(in oklab, var(--color-primary) 10%, transparent)}}.bg-red-50{background-color:var(--color-red-50)}.bg-white{background-color:var(--color-white)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-8{padding-block:calc(var(--spacing) * 8)}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-6{padding-left:calc(var(--spacing) * 6)}.pl-8{padding-left:calc(var(--spacing) * 8)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-600{color:var(--color-amber-600)}.text-amber-900{color:var(--color-amber-900)}.text-destructive{color:var(--color-destructive)}.text-foreground{color:var(--color-foreground)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-muted-foreground{color:var(--color-muted-foreground)}.text-orange-500{color:var(--color-orange-500)}.text-orange-900{color:var(--color-orange-900)}.text-primary{color:var(--color-primary)}.text-primary-foreground{color:var(--color-primary-foreground)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-white{color:var(--color-white)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.accent-orange-500{accent-color:var(--color-orange-500)}.accent-primary{accent-color:var(--color-primary)}.opacity-60{opacity:.6}.opacity-80{opacity:.8}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}.even\:bg-muted\/20:nth-child(2n){background-color:#f1f5f933}@supports (color:color-mix(in lab, red, red)){.even\:bg-muted\/20:nth-child(2n){background-color:color-mix(in oklab, var(--color-muted) 20%, transparent)}}@media (hover:hover){.hover\:bg-muted:hover{background-color:var(--color-muted)}.hover\:bg-muted\/40:hover{background-color:#f1f5f966}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/40:hover{background-color:color-mix(in oklab, var(--color-muted) 40%, transparent)}}.hover\:bg-muted\/50:hover{background-color:#f1f5f980}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/50:hover{background-color:color-mix(in oklab, var(--color-muted) 50%, transparent)}}.hover\:bg-muted\/60:hover{background-color:#f1f5f999}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/60:hover{background-color:color-mix(in oklab, var(--color-muted) 60%, transparent)}}.hover\:bg-muted\/80:hover{background-color:#f1f5f9cc}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/80:hover{background-color:color-mix(in oklab, var(--color-muted) 80%, transparent)}}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-ring:focus{--tw-ring-color:var(--color-ring)}.disabled\:opacity-50:disabled{opacity:.5}@media (width>=40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (width>=48rem){.md\:col-span-1{grid-column:span 1/span 1}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-\[1\.2fr_1fr_1fr_auto\]{grid-template-columns:1.2fr 1fr 1fr auto}.md\:items-end{align-items:flex-end}}@media (width>=64rem){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme:dark){.dark\:bg-amber-950\/30{background-color:#4619014d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-950\/30{background-color:color-mix(in oklab, var(--color-amber-950) 30%, transparent)}}.dark\:bg-green-950\/30{background-color:#032e154d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-green-950\/30{background-color:color-mix(in oklab, var(--color-green-950) 30%, transparent)}}.dark\:bg-orange-950\/30{background-color:#4413064d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-orange-950\/30{background-color:color-mix(in oklab, var(--color-orange-950) 30%, transparent)}}.dark\:bg-red-950\/30{background-color:#4608094d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-red-950\/30{background-color:color-mix(in oklab, var(--color-red-950) 30%, transparent)}}.dark\:text-amber-100{color:var(--color-amber-100)}.dark\:text-green-300{color:var(--color-green-300)}.dark\:text-orange-100{color:var(--color-orange-100)}.dark\:text-red-200{color:var(--color-red-200)}}}body{background:var(--color-background);color:var(--color-foreground);margin:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false} diff --git a/public/app/assets/index-DmvH7O2M.js b/public/app/assets/index-Bk7sqZ-P.js similarity index 74% rename from public/app/assets/index-DmvH7O2M.js rename to public/app/assets/index-Bk7sqZ-P.js index 0218267..c6e69b2 100644 --- a/public/app/assets/index-DmvH7O2M.js +++ b/public/app/assets/index-Bk7sqZ-P.js @@ -49,4 +49,4 @@ Please change the parent to 1&&e.reused===`ref`){a(n);continue}}}function bc(e,t){let n=e.seen.get(t);if(!n)throw Error(`Unprocessed schema. This is a bug in Zod.`);let r=t=>{let n=e.seen.get(t);if(n.ref===null)return;let i=n.def??n.schema,a={...i},o=n.ref;if(n.ref=null,o){r(o);let n=e.seen.get(o),s=n.schema;if(s.$ref&&(e.target===`draft-07`||e.target===`draft-04`||e.target===`openapi-3.0`)?(i.allOf=i.allOf??[],i.allOf.push(s)):Object.assign(i,s),Object.assign(i,a),t._zod.parent===o)for(let e in i)e===`$ref`||e===`allOf`||e in a||delete i[e];if(s.$ref&&n.def)for(let e in i)e===`$ref`||e===`allOf`||e in n.def&&JSON.stringify(i[e])===JSON.stringify(n.def[e])&&delete i[e]}let s=t._zod.parent;if(s&&s!==o){r(s);let t=e.seen.get(s);if(t?.schema.$ref&&(i.$ref=t.schema.$ref,t.def))for(let e in i)e===`$ref`||e===`allOf`||e in t.def&&JSON.stringify(i[e])===JSON.stringify(t.def[e])&&delete i[e]}e.override({zodSchema:t,jsonSchema:i,path:n.path??[]})};for(let t of[...e.seen.entries()].reverse())r(t[0]);let i={};if(e.target===`draft-2020-12`?i.$schema=`https://json-schema.org/draft/2020-12/schema`:e.target===`draft-07`?i.$schema=`http://json-schema.org/draft-07/schema#`:e.target===`draft-04`?i.$schema=`http://json-schema.org/draft-04/schema#`:e.target,e.external?.uri){let n=e.external.registry.get(t)?.id;if(!n)throw Error("Schema is missing an `id` property");i.$id=e.external.uri(n)}Object.assign(i,n.def??n.schema);let a=e.external?.defs??{};for(let t of e.seen.entries()){let e=t[1];e.def&&e.defId&&(a[e.defId]=e.def)}e.external||Object.keys(a).length>0&&(e.target===`draft-2020-12`?i.$defs=a:i.definitions=a);try{let n=JSON.parse(JSON.stringify(i));return Object.defineProperty(n,`~standard`,{value:{...t[`~standard`],jsonSchema:{input:Cc(t,`input`,e.processors),output:Cc(t,`output`,e.processors)}},enumerable:!1,writable:!1}),n}catch{throw Error(`Error converting schema to JSON.`)}}function xc(e,t){let n=t??{seen:new Set};if(n.seen.has(e))return!1;n.seen.add(e);let r=e._zod.def;if(r.type===`transform`)return!0;if(r.type===`array`)return xc(r.element,n);if(r.type===`set`)return xc(r.valueType,n);if(r.type===`lazy`)return xc(r.getter(),n);if(r.type===`promise`||r.type===`optional`||r.type===`nonoptional`||r.type===`nullable`||r.type===`readonly`||r.type===`default`||r.type===`prefault`)return xc(r.innerType,n);if(r.type===`intersection`)return xc(r.left,n)||xc(r.right,n);if(r.type===`record`||r.type===`map`)return xc(r.keyType,n)||xc(r.valueType,n);if(r.type===`pipe`)return xc(r.in,n)||xc(r.out,n);if(r.type===`object`){for(let e in r.shape)if(xc(r.shape[e],n))return!0;return!1}if(r.type===`union`){for(let e of r.options)if(xc(e,n))return!0;return!1}if(r.type===`tuple`){for(let e of r.items)if(xc(e,n))return!0;return!!(r.rest&&xc(r.rest,n))}return!1}var Sc=(e,t={})=>n=>{let r=_c({...n,processors:t});return vc(e,r),yc(r,e),bc(r,e)},Cc=(e,t,n={})=>r=>{let{libraryOptions:i,target:a}=r??{},o=_c({...i??{},target:a,io:t,processors:n});return vc(e,o),yc(o,e),bc(o,e)},wc={guid:`uuid`,url:`uri`,datetime:`date-time`,json_string:`json-string`,regex:``},Tc=(e,t,n,r)=>{let i=n;i.type=`string`;let{minimum:a,maximum:o,format:s,patterns:c,contentEncoding:l}=e._zod.bag;if(typeof a==`number`&&(i.minLength=a),typeof o==`number`&&(i.maxLength=o),s&&(i.format=wc[s]??s,i.format===``&&delete i.format,s===`time`&&delete i.format),l&&(i.contentEncoding=l),c&&c.size>0){let e=[...c];e.length===1?i.pattern=e[0].source:e.length>1&&(i.allOf=[...e.map(e=>({...t.target===`draft-07`||t.target===`draft-04`||t.target===`openapi-3.0`?{type:`string`}:{},pattern:e.source}))])}},Ec=(e,t,n,r)=>{n.not={}},Dc=(e,t,n,r)=>{let i=e._zod.def,a=ji(i.entries);a.every(e=>typeof e==`number`)&&(n.type=`number`),a.every(e=>typeof e==`string`)&&(n.type=`string`),n.enum=a},Oc=(e,t,n,r)=>{if(t.unrepresentable===`throw`)throw Error(`Custom types cannot be represented in JSON Schema`)},kc=(e,t,n,r)=>{if(t.unrepresentable===`throw`)throw Error(`Transforms cannot be represented in JSON Schema`)},Ac=(e,t,n,r)=>{let i=n,a=e._zod.def,{minimum:o,maximum:s}=e._zod.bag;typeof o==`number`&&(i.minItems=o),typeof s==`number`&&(i.maxItems=s),i.type=`array`,i.items=vc(a.element,t,{...r,path:[...r.path,`items`]})},jc=(e,t,n,r)=>{let i=n,a=e._zod.def;i.type=`object`,i.properties={};let o=a.shape;for(let e in o)i.properties[e]=vc(o[e],t,{...r,path:[...r.path,`properties`,e]});let s=new Set(Object.keys(o)),c=new Set([...s].filter(e=>{let n=a.shape[e]._zod;return t.io===`input`?n.optin===void 0:n.optout===void 0}));c.size>0&&(i.required=Array.from(c)),a.catchall?._zod.def.type===`never`?i.additionalProperties=!1:a.catchall?a.catchall&&(i.additionalProperties=vc(a.catchall,t,{...r,path:[...r.path,`additionalProperties`]})):t.io===`output`&&(i.additionalProperties=!1)},Mc=(e,t,n,r)=>{let i=e._zod.def,a=i.inclusive===!1,o=i.options.map((e,n)=>vc(e,t,{...r,path:[...r.path,a?`oneOf`:`anyOf`,n]}));a?n.oneOf=o:n.anyOf=o},Nc=(e,t,n,r)=>{let i=e._zod.def,a=vc(i.left,t,{...r,path:[...r.path,`allOf`,0]}),o=vc(i.right,t,{...r,path:[...r.path,`allOf`,1]}),s=e=>`allOf`in e&&Object.keys(e).length===1;n.allOf=[...s(a)?a.allOf:[a],...s(o)?o.allOf:[o]]},Pc=(e,t,n,r)=>{let i=e._zod.def,a=vc(i.innerType,t,r),o=t.seen.get(e);t.target===`openapi-3.0`?(o.ref=i.innerType,n.nullable=!0):n.anyOf=[a,{type:`null`}]},Fc=(e,t,n,r)=>{let i=e._zod.def;vc(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType},Ic=(e,t,n,r)=>{let i=e._zod.def;vc(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType,n.default=JSON.parse(JSON.stringify(i.defaultValue))},Lc=(e,t,n,r)=>{let i=e._zod.def;vc(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType,t.io===`input`&&(n._prefault=JSON.parse(JSON.stringify(i.defaultValue)))},Rc=(e,t,n,r)=>{let i=e._zod.def;vc(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType;let o;try{o=i.catchValue(void 0)}catch{throw Error(`Dynamic catch values are not supported in JSON Schema`)}n.default=o},U=(e,t,n,r)=>{let i=e._zod.def,a=t.io===`input`?i.in._zod.def.type===`transform`?i.out:i.in:i.out;vc(a,t,r);let o=t.seen.get(e);o.ref=a},zc=(e,t,n,r)=>{let i=e._zod.def;vc(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType,n.readOnly=!0},Bc=(e,t,n,r)=>{let i=e._zod.def;vc(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType},Vc=I(`ZodISODateTime`,(e,t)=>{Eo.init(e,t),fl.init(e,t)});function Hc(e){return Ks(Vc,e)}var Uc=I(`ZodISODate`,(e,t)=>{Do.init(e,t),fl.init(e,t)});function Wc(e){return qs(Uc,e)}var Gc=I(`ZodISOTime`,(e,t)=>{Oo.init(e,t),fl.init(e,t)});function Kc(e){return Js(Gc,e)}var qc=I(`ZodISODuration`,(e,t)=>{ko.init(e,t),fl.init(e,t)});function Jc(e){return Ys(qc,e)}var Yc=(e,t)=>{la.init(e,t),e.name=`ZodError`,Object.defineProperties(e,{format:{value:t=>fa(e,t)},flatten:{value:t=>da(e,t)},addIssue:{value:t=>{e.issues.push(t),e.message=JSON.stringify(e.issues,Mi,2)}},addIssues:{value:t=>{e.issues.push(...t),e.message=JSON.stringify(e.issues,Mi,2)}},isEmpty:{get(){return e.issues.length===0}}})};I(`ZodError`,Yc);var Xc=I(`ZodError`,Yc,{Parent:Error}),Zc=pa(Xc),Qc=ma(Xc),$c=ha(Xc),el=_a(Xc),tl=ya(Xc),nl=ba(Xc),rl=xa(Xc),il=Sa(Xc),al=Ca(Xc),ol=wa(Xc),sl=Ta(Xc),cl=Ea(Xc),W=I(`ZodType`,(e,t)=>(V.init(e,t),Object.assign(e[`~standard`],{jsonSchema:{input:Cc(e,`input`),output:Cc(e,`output`)}}),e.toJSONSchema=Sc(e,{}),e.def=t,e.type=t.type,Object.defineProperty(e,`_def`,{value:t}),e.check=(...n)=>e.clone(Li(t,{checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]}),{parent:!0}),e.with=e.check,e.clone=(t,n)=>qi(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.parse=(t,n)=>Zc(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>$c(e,t,n),e.parseAsync=async(t,n)=>Qc(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>el(e,t,n),e.spa=e.safeParseAsync,e.encode=(t,n)=>tl(e,t,n),e.decode=(t,n)=>nl(e,t,n),e.encodeAsync=async(t,n)=>rl(e,t,n),e.decodeAsync=async(t,n)=>il(e,t,n),e.safeEncode=(t,n)=>al(e,t,n),e.safeDecode=(t,n)=>ol(e,t,n),e.safeEncodeAsync=async(t,n)=>sl(e,t,n),e.safeDecodeAsync=async(t,n)=>cl(e,t,n),e.refine=(t,n)=>e.check(cu(t,n)),e.superRefine=t=>e.check(lu(t)),e.overwrite=t=>e.check(sc(t)),e.optional=()=>Wl(e),e.exactOptional=()=>Kl(e),e.nullable=()=>Jl(e),e.nullish=()=>Wl(Jl(e)),e.nonoptional=t=>eu(e,t),e.array=()=>Ll(e),e.or=t=>K([e,t]),e.and=t=>J(e,t),e.transform=t=>iu(e,Hl(t)),e.default=t=>Xl(e,t),e.prefault=t=>Ql(e,t),e.catch=t=>nu(e,t),e.pipe=t=>iu(e,t),e.readonly=()=>ou(e),e.describe=t=>{let n=e.clone();return Ss.add(n,{description:t}),n},Object.defineProperty(e,`description`,{get(){return Ss.get(e)?.description},configurable:!0}),e.meta=(...t)=>{if(t.length===0)return Ss.get(e);let n=e.clone();return Ss.add(n,t[0]),n},e.isOptional=()=>e.safeParse(void 0).success,e.isNullable=()=>e.safeParse(null).success,e.apply=t=>t(e),e)),ll=I(`_ZodString`,(e,t)=>{mo.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Tc(e,t,n,r);let n=e._zod.bag;e.format=n.format??null,e.minLength=n.minimum??null,e.maxLength=n.maximum??null,e.regex=(...t)=>e.check(tc(...t)),e.includes=(...t)=>e.check(ic(...t)),e.startsWith=(...t)=>e.check(ac(...t)),e.endsWith=(...t)=>e.check(oc(...t)),e.min=(...t)=>e.check($s(...t)),e.max=(...t)=>e.check(Qs(...t)),e.length=(...t)=>e.check(ec(...t)),e.nonempty=(...t)=>e.check($s(1,...t)),e.lowercase=t=>e.check(nc(t)),e.uppercase=t=>e.check(rc(t)),e.trim=()=>e.check(lc()),e.normalize=(...t)=>e.check(cc(...t)),e.toLowerCase=()=>e.check(uc()),e.toUpperCase=()=>e.check(dc()),e.slugify=()=>e.check(fc())}),ul=I(`ZodString`,(e,t)=>{mo.init(e,t),ll.init(e,t),e.email=t=>e.check(ws(pl,t)),e.url=t=>e.check(As(gl,t)),e.jwt=t=>e.check(Gs(jl,t)),e.emoji=t=>e.check(js(_l,t)),e.guid=t=>e.check(Ts(ml,t)),e.uuid=t=>e.check(Es(hl,t)),e.uuidv4=t=>e.check(Ds(hl,t)),e.uuidv6=t=>e.check(Os(hl,t)),e.uuidv7=t=>e.check(ks(hl,t)),e.nanoid=t=>e.check(Ms(vl,t)),e.guid=t=>e.check(Ts(ml,t)),e.cuid=t=>e.check(Ns(yl,t)),e.cuid2=t=>e.check(Ps(bl,t)),e.ulid=t=>e.check(Fs(xl,t)),e.base64=t=>e.check(Hs(Ol,t)),e.base64url=t=>e.check(Us(kl,t)),e.xid=t=>e.check(Is(Sl,t)),e.ksuid=t=>e.check(Ls(Cl,t)),e.ipv4=t=>e.check(Rs(wl,t)),e.ipv6=t=>e.check(zs(Tl,t)),e.cidrv4=t=>e.check(Bs(El,t)),e.cidrv6=t=>e.check(Vs(Dl,t)),e.e164=t=>e.check(Ws(Al,t)),e.datetime=t=>e.check(Hc(t)),e.date=t=>e.check(Wc(t)),e.time=t=>e.check(Kc(t)),e.duration=t=>e.check(Jc(t))});function dl(e){return Cs(ul,e)}var fl=I(`ZodStringFormat`,(e,t)=>{H.init(e,t),ll.init(e,t)}),pl=I(`ZodEmail`,(e,t)=>{_o.init(e,t),fl.init(e,t)}),ml=I(`ZodGUID`,(e,t)=>{ho.init(e,t),fl.init(e,t)}),hl=I(`ZodUUID`,(e,t)=>{go.init(e,t),fl.init(e,t)}),gl=I(`ZodURL`,(e,t)=>{vo.init(e,t),fl.init(e,t)}),_l=I(`ZodEmoji`,(e,t)=>{yo.init(e,t),fl.init(e,t)}),vl=I(`ZodNanoID`,(e,t)=>{bo.init(e,t),fl.init(e,t)}),yl=I(`ZodCUID`,(e,t)=>{xo.init(e,t),fl.init(e,t)}),bl=I(`ZodCUID2`,(e,t)=>{So.init(e,t),fl.init(e,t)}),xl=I(`ZodULID`,(e,t)=>{Co.init(e,t),fl.init(e,t)}),Sl=I(`ZodXID`,(e,t)=>{wo.init(e,t),fl.init(e,t)}),Cl=I(`ZodKSUID`,(e,t)=>{To.init(e,t),fl.init(e,t)}),wl=I(`ZodIPv4`,(e,t)=>{Ao.init(e,t),fl.init(e,t)}),Tl=I(`ZodIPv6`,(e,t)=>{jo.init(e,t),fl.init(e,t)}),El=I(`ZodCIDRv4`,(e,t)=>{Mo.init(e,t),fl.init(e,t)}),Dl=I(`ZodCIDRv6`,(e,t)=>{No.init(e,t),fl.init(e,t)}),Ol=I(`ZodBase64`,(e,t)=>{Fo.init(e,t),fl.init(e,t)}),kl=I(`ZodBase64URL`,(e,t)=>{Lo.init(e,t),fl.init(e,t)}),Al=I(`ZodE164`,(e,t)=>{Ro.init(e,t),fl.init(e,t)}),jl=I(`ZodJWT`,(e,t)=>{Bo.init(e,t),fl.init(e,t)}),Ml=I(`ZodUnknown`,(e,t)=>{Vo.init(e,t),W.init(e,t),e._zod.processJSONSchema=(e,t,n)=>void 0});function Nl(){return Xs(Ml)}var Pl=I(`ZodNever`,(e,t)=>{Ho.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Ec(e,t,n,r)});function Fl(e){return Zs(Pl,e)}var Il=I(`ZodArray`,(e,t)=>{Wo.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Ac(e,t,n,r),e.element=t.element,e.min=(t,n)=>e.check($s(t,n)),e.nonempty=t=>e.check($s(1,t)),e.max=(t,n)=>e.check(Qs(t,n)),e.length=(t,n)=>e.check(ec(t,n)),e.unwrap=()=>e.element});function Ll(e,t){return pc(Il,e,t)}var Rl=I(`ZodObject`,(e,t)=>{Yo.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>jc(e,t,n,r),R(e,`shape`,()=>t.shape),e.keyof=()=>Bl(Object.keys(e._zod.def.shape)),e.catchall=t=>e.clone({...e._zod.def,catchall:t}),e.passthrough=()=>e.clone({...e._zod.def,catchall:Nl()}),e.loose=()=>e.clone({...e._zod.def,catchall:Nl()}),e.strict=()=>e.clone({...e._zod.def,catchall:Fl()}),e.strip=()=>e.clone({...e._zod.def,catchall:void 0}),e.extend=t=>Zi(e,t),e.safeExtend=t=>Qi(e,t),e.merge=t=>$i(e,t),e.pick=t=>Yi(e,t),e.omit=t=>Xi(e,t),e.partial=(...t)=>ea(Ul,e,t[0]),e.required=(...t)=>ta($l,e,t[0])});function zl(e,t){return new Rl({type:`object`,shape:e??{},...z(t)})}var G=I(`ZodUnion`,(e,t)=>{Zo.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Mc(e,t,n,r),e.options=t.options});function K(e,t){return new G({type:`union`,options:e,...z(t)})}var q=I(`ZodIntersection`,(e,t)=>{Qo.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Nc(e,t,n,r)});function J(e,t){return new q({type:`intersection`,left:e,right:t})}var Y=I(`ZodEnum`,(e,t)=>{ts.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Dc(e,t,n,r),e.enum=t.entries,e.options=Object.values(t.entries);let n=new Set(Object.keys(t.entries));e.extract=(e,r)=>{let i={};for(let r of e)if(n.has(r))i[r]=t.entries[r];else throw Error(`Key ${r} not found in enum`);return new Y({...t,checks:[],...z(r),entries:i})},e.exclude=(e,r)=>{let i={...t.entries};for(let t of e)if(n.has(t))delete i[t];else throw Error(`Key ${t} not found in enum`);return new Y({...t,checks:[],...z(r),entries:i})}});function Bl(e,t){return new Y({type:`enum`,entries:Array.isArray(e)?Object.fromEntries(e.map(e=>[e,e])):e,...z(t)})}var Vl=I(`ZodTransform`,(e,t)=>{ns.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>kc(e,t,n,r),e._zod.parse=(n,r)=>{if(r.direction===`backward`)throw new Oi(e.constructor.name);n.addIssue=r=>{if(typeof r==`string`)n.issues.push(sa(r,n.value,t));else{let t=r;t.fatal&&(t.continue=!1),t.code??=`custom`,t.input??=n.value,t.inst??=e,n.issues.push(sa(t))}};let i=t.transform(n.value,n);return i instanceof Promise?i.then(e=>(n.value=e,n)):(n.value=i,n)}});function Hl(e){return new Vl({type:`transform`,transform:e})}var Ul=I(`ZodOptional`,(e,t)=>{is.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Bc(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Wl(e){return new Ul({type:`optional`,innerType:e})}var Gl=I(`ZodExactOptional`,(e,t)=>{as.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Bc(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Kl(e){return new Gl({type:`optional`,innerType:e})}var ql=I(`ZodNullable`,(e,t)=>{os.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Pc(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Jl(e){return new ql({type:`nullable`,innerType:e})}var Yl=I(`ZodDefault`,(e,t)=>{ss.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Ic(e,t,n,r),e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap});function Xl(e,t){return new Yl({type:`default`,innerType:e,get defaultValue(){return typeof t==`function`?t():Wi(t)}})}var Zl=I(`ZodPrefault`,(e,t)=>{ls.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Lc(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Ql(e,t){return new Zl({type:`prefault`,innerType:e,get defaultValue(){return typeof t==`function`?t():Wi(t)}})}var $l=I(`ZodNonOptional`,(e,t)=>{us.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Fc(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function eu(e,t){return new $l({type:`nonoptional`,innerType:e,...z(t)})}var tu=I(`ZodCatch`,(e,t)=>{fs.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Rc(e,t,n,r),e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap});function nu(e,t){return new tu({type:`catch`,innerType:e,catchValue:typeof t==`function`?t:()=>t})}var ru=I(`ZodPipe`,(e,t)=>{ps.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>U(e,t,n,r),e.in=t.in,e.out=t.out});function iu(e,t){return new ru({type:`pipe`,in:e,out:t})}var au=I(`ZodReadonly`,(e,t)=>{hs.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>zc(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function ou(e){return new au({type:`readonly`,innerType:e})}var su=I(`ZodCustom`,(e,t)=>{_s.init(e,t),W.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Oc(e,t,n,r)});function cu(e,t={}){return mc(su,e,t)}function lu(e){return hc(e)}var uu=dl().min(1),X=dl().optional(),du=dl().optional();zl({email:dl().email(),password:uu,turnstileToken:X,totpCode:X}),zl({email:dl().email(),password:dl().min(8,`Password must be at least 8 characters`),name:uu,turnstileToken:X}),zl({email:dl().email(),turnstileToken:X});var fu=zl({transcript:uu,patientAge:X,patientGender:X,model:du,setting:Bl([`outpatient`,`inpatient`]).optional(),physicianMemories:X}),pu=zl({transcript:uu,patientAge:X,patientGender:X,model:du,type:Bl([`full`,`subjective`]).optional(),additionalInstructions:X,physicianMemories:X}),mu=zl({patientAge:X,patientGender:X,chiefComplaint:uu,transcript:X,dictation:X,ros:X,physicalExam:X,diagnoses:X,physicianMemories:X,model:du});zl({currentDocument:uu,instructions:uu,sourceContext:X,model:du}),zl({steps:Ll(zl({component:X,label:uu,method:X,normal:X,status:Bl([`normal`,`abnormal`]).nullable().optional(),note:X})).min(1),ageGroup:X,system:X,patientAge:X,patientGender:X,model:du,format:Bl([`narrative`,`list`]).optional()});var hu=zl({location:dl().min(1).max(120),name:dl().min(1).max(120),number:dl().min(1).max(40),type:Bl([`extension`,`pager`]).optional(),notes:dl().max(500).optional()});function gu({ext:e}){return(0,A.jsxs)(`div`,{className:`flex items-center gap-3 px-4 py-2 border-b border-border`,children:[(0,A.jsxs)(`div`,{className:`flex-1`,children:[(0,A.jsx)(`div`,{className:`font-medium`,children:e.name}),(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:e.location})]}),(0,A.jsx)(`div`,{className:`font-mono text-sm`,children:e.number}),(0,A.jsx)(`div`,{className:`text-xs uppercase text-muted-foreground w-20 text-right`,children:e.type})]})}function _u({onDone:e}){let t=et(),[n,r]=(0,_.useState)({location:``,name:``,number:``,type:`extension`,notes:``}),[i,a]=(0,_.useState)(null),o=M({mutationFn:e=>F.post(`/api/extensions`,e),onSuccess:()=>{t.invalidateQueries({queryKey:[`extensions`]}),e()},onError:e=>a(e.message)});function s(e){e.preventDefault(),a(null);let t=hu.safeParse(n);if(!t.success){a(t.error.issues.map(e=>e.message).join(`, `));return}o.mutate(t.data)}let c=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`form`,{onSubmit:s,className:`space-y-3 p-4 bg-muted/40 rounded-lg border border-border`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,A.jsx)(`input`,{className:c,placeholder:`Location (e.g. Main Hospital)`,value:n.location,onChange:e=>r({...n,location:e.target.value})}),(0,A.jsx)(`input`,{className:c,placeholder:`Name / department`,value:n.name,onChange:e=>r({...n,name:e.target.value})}),(0,A.jsx)(`input`,{className:c,placeholder:`Number`,value:n.number,onChange:e=>r({...n,number:e.target.value})}),(0,A.jsxs)(`select`,{className:c,value:n.type,onChange:e=>r({...n,type:e.target.value}),children:[(0,A.jsx)(`option`,{value:`extension`,children:`Extension`}),(0,A.jsx)(`option`,{value:`pager`,children:`Pager`})]})]}),i&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:i}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`submit`,disabled:o.isPending,className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:o.isPending?`Saving…`:`Save`}),(0,A.jsx)(`button`,{type:`button`,onClick:e,className:`rounded-md border border-border px-4 py-2 text-sm`,children:`Cancel`})]})]})}function vu(){let[e,t]=(0,_.useState)(!1),{data:n,isLoading:r,error:i}=j({queryKey:[`extensions`],queryFn:()=>F.get(`/api/extensions`)});return(0,A.jsxs)(`div`,{className:`max-w-3xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{className:`flex items-center justify-between`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Pagers & Extensions`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Per-user directory. This React port is the migration proof-of-life.`})]}),!e&&(0,A.jsx)(`button`,{onClick:()=>t(!0),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium`,children:`+ Add`})]}),e&&(0,A.jsx)(_u,{onDone:()=>t(!1)}),r&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),i&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:i.message}),n&&n.items.length===0&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground italic py-8 text-center`,children:`No extensions yet. Click Add to create the first one.`}),n&&n.items.length>0&&(0,A.jsx)(`div`,{className:`rounded-lg border border-border overflow-hidden`,children:n.items.map(e=>(0,A.jsx)(gu,{ext:e},e.id))})]})}var yu=[{section:`Getting Started`,items:[{q:`What is Pediatric AI Scribe?`,a:`Pediatric AI Scribe is an AI-powered clinical documentation tool designed specifically for pediatric medicine. It helps physicians generate structured clinical notes from voice recordings or typed text, saving time on documentation so you can focus on patient care. It supports HPIs, SOAP notes, hospital courses, chart reviews, well visits, sick visits, developmental milestone assessments, and more.`},{q:`How do I create my first note?`,a:`The easiest way to start is with Live Encounter: Go to the Encounter tab Enter the patient's age and gender Click Start Recording and speak naturally during your patient encounter Click Stop when done — the audio is transcribed automatically Click Generate HPI to create a structured note Edit the note as needed, then Copy to paste into your EHR`},{q:`Can I type or paste notes instead of recording?`,a:`Yes. Every transcript box is editable. You can type directly, paste from another source, or combine typed text with a recording. The AI works with whatever text is in the transcript area when you click Generate.`},{q:`What types of notes can I generate?`,a:`HPI — from live encounters or dictation, with OLDCARTS structure SOAP Notes — full SOAP or subjective-only from dictation Hospital Course — from progress notes, in prose, day-by-day, or organ-system format Chart Review — summarize outpatient, subspecialty, or ED visits for precharting Well Visit — complete preventive care notes with SSHADESS, milestones, and vaccines Sick Visit — quick documentation with auto-suggested ROS and PE Milestone Assessment — developmental narrative from selected milestones`}]},{section:`AI & Models`,items:[{q:`What AI model should I use?`,a:`Each tab has a model selector dropdown. All available models have been tested and configured by your administrator for clinical documentation quality. They are routed through HIPAA-compliant providers with signed Business Associate Agreements (BAAs). All models are capable of generating accurate clinical notes. If you are unsure which to pick, start with the default. You can experiment with different models and see which output style you prefer — some may be faster, some more detailed, some more concise. You can choose a different model per tab depending on the task.`},{q:`Does the AI learn from my edits?`,a:`Yes. The app uses a correction tracking system inspired by Dragon Medical's adaptive learning. Here is how it works: When the AI generates a note, the original output is stored in memory You edit the note to match your preferred style — fix phrasing, add details, restructure sections When you click Save, the app detects what you changed and stores the correction On future notes, your past corrections are included as style hints so the AI adapts to your documentation preferences The more you use the app and save your edits, the better the AI gets at matching your style. You can view and manage your stored corrections in Settings > AI Corrections. Note: Corrections are applied as gentle suggestions, not strict rules. The AI prioritizes clinical accuracy over style matching.`},{q:`Can I customize the AI's prompts?`,a:`Administrators can edit all AI prompts from the Admin Panel > Settings > Prompts section. This lets you adjust the instructions the AI follows for each note type without changing any code. Changes take effect immediately.`},{q:`What does the "Refine" button do?`,a:`After generating a note, you can give the AI plain-language instructions to modify it. For example: "Make it shorter" "Add that the patient has a history of asthma" "Summarize the labs" "Change the assessment to include bronchiolitis" The AI references both its current output and your original source material (transcript, pasted notes, labs) when refining, so it can look up details from the original input.`}]},{section:`Voice & Transcription`,items:[{q:`How does voice transcription work?`,a:`When you stop recording, the audio is sent to a speech-to-text service that converts it to text. The app supports multiple transcription providers including Whisper, Deepgram, and Google Gemini. Your administrator configures which provider is used. You will see a blue status bar at the top while transcription is in progress. You can continue working on the page while it processes.`},{q:`What is Browser Whisper?`,a:`Browser Whisper runs the Whisper AI model entirely in your browser using WebAssembly. Your audio never leaves your device, making it the most private transcription option. You can enable it in Settings > Browser Whisper. It works offline and is HIPAA-safe since no data is transmitted. The tradeoff is that it is slower than cloud-based transcription and requires downloading the model (~40–240 MB) on first use.`},{q:`Can I use the app on my phone?`,a:`Yes. The app is a Progressive Web App (PWA) that works in any modern browser. On mobile: Open the app in Chrome or Safari Tap "Add to Home Screen" to install it as a standalone app Recording works in the foreground, but audio stops if you lock the screen or switch apps on iOS and most Android devices — this is a browser limitation, not specific to this app On desktop, recording continues normally when the browser is minimized or in the background.`},{q:`What happens if transcription fails?`,a:`If transcription fails, your audio is automatically backed up to the server for 24 hours. You can retry transcription from Settings > Audio Backups. If browser speech recognition was active during recording, the live transcript is preserved as a fallback.`},{q:`Can the AI read my notes aloud?`,a:`Yes. Click the Read button on any generated note to hear it spoken aloud. This uses text-to-speech (TTS) powered by Google, OpenAI, or ElevenLabs depending on your setup. You can choose your preferred voice in Settings > Voice Preferences.`}]},{section:`Saving & Export`,items:[{q:`Are my encounters saved?`,a:`You can save encounters using the Save button at the top of each tab. Saved encounters include the transcript, generated note, and patient label. You can reload them later using the Load button. Saved encounters are automatically deleted after 7 days (configurable by your administrator). This is intentional — the app is a documentation tool, not a medical record system. Copy your final notes to your EHR for permanent storage.`},{q:`How do I export notes?`,a:`Copy — one-click copy to clipboard, ready to paste into any EHR Nextcloud — export directly to your Nextcloud instance (configure in Settings) Documents — upload files to S3-compatible storage from Settings All generated text is plain text with no markdown formatting, designed to paste cleanly into any EHR system.`}]},{section:`Privacy & Security`,items:[{q:`Is my patient data safe?`,a:`The app is designed with clinical privacy in mind: All connections use HTTPS/TLS encryption Audio and encounter data are temporary — auto-deleted within hours or days No patient data is stored long-term on the server Every action is audit-logged (who accessed what, when) Two-factor authentication (2FA) and session management are available Browser Whisper keeps audio entirely on your device For HIPAA compliance, ensure your administrator has configured a BAA-covered AI provider (such as AWS Bedrock, Google Vertex AI, or Azure OpenAI).`},{q:`What is two-factor authentication (2FA)?`,a:`2FA adds an extra layer of security to your account. After entering your password, you also enter a 6-digit code from an authenticator app (like Google Authenticator or Authy). Enable it in Settings > Two-Factor Authentication.`},{q:`How do I manage my active sessions?`,a:`Go to Settings > Active Sessions to see all devices where you are logged in. You can revoke any session individually or click Revoke All Other Sessions to log out every other device. Your current session is highlighted and cannot be revoked from this screen — use the Logout button instead.`},{q:`What happens when I change my password?`,a:`When you change your password in Settings > Change Password, all other active sessions are automatically logged out for security. Only your current session remains active. The app also checks if your new password has appeared in known data breaches and warns you (but does not prevent you from using it).`}]},{section:`Well Visit & Sick Visit`,items:[{q:`How does the Well Visit tab work?`,a:`The Well Visit tab follows the AAP Bright Futures periodicity schedule. It includes: Visit by Age — recommended screenings, vaccines, and anticipatory guidance for each visit age Milestones — developmental milestone tracker from birth through 11 years across multiple domains SSHADESS — adolescent psychosocial assessment (Strengths, School, Home, Activities, Drugs, Emotions, Sexuality, Safety) for ages 12+ Visit Note — generates a complete well visit note combining ROS, PE, milestones, and SSHADESS data`},{q:`What do the WNL / Abnormal / Not Reviewed buttons do?`,a:`In the ROS and Physical Exam sections, each system has three options: WNL / Normal — within normal limits, no concerns Abnormal — a text box appears so you can describe the finding Not Reviewed / Not Examined — explicitly not assessed Use All WNL to quickly mark everything normal, then click individual systems to change specific ones. Use Clear to reset all selections.`}]},{section:`Learning Hub`,items:[{q:`What is the Learning Hub?`,a:`The Learning Hub is an educational platform integrated into the app. It contains articles, clinical pearls, quizzes, and slide presentations created by moderators and administrators. You can browse by category, search content, and take quizzes to test your knowledge.`},{q:`How do quizzes work?`,a:`Quizzes include multiple-choice, multi-select, and true/false questions. After submitting your answers, you see your score along with explanations for each question. Your past attempts and scores are tracked so you can monitor your progress over time.`},{q:`Can I create Learning Hub content?`,a:`Moderators and administrators can create content using the CMS tab. You can write articles manually, or use AI to generate content from a topic description, uploaded PDFs, or files from Nextcloud. The CMS also supports Marp-based slide presentations with PPTX export.`}]},{section:`Pediatric Calculators`,items:[{q:`What calculators are available?`,a:`The Calculators tab includes clinical tools commonly used in pediatric practice: Blood Pressure Percentile — AAP 2017 guidelines using the Rosner quantile spline regression method. Requires age, sex, height, and BP. Provides exact systolic and diastolic percentiles adjusted for height, with AAP classification (Normal, Elevated, Stage 1, Stage 2). Includes definitions of hypertension and hypotension. BMI Percentile — CDC 2000 growth reference with extended obesity classification (Class 1, 2, 3 using % of 95th percentile). Shows BMI chart with percentile curves. Growth Charts — Visual percentile curves (3rd through 97th) with your patient plotted. Includes Weight-for-Age, Length/Height-for-Age (with mid-parental height), Head Circumference, Weight-for-Length, and Fenton preterm charts. Bilirubin — AAP 2022 phototherapy threshold calculator and Bhutani hour-specific nomogram with risk zone classification. Includes Nelson Table 137.1 risk factors. Vital Signs by Age — Harriet Lane reference table for HR, RR, BP, and weight by age from preterm through 18 years. Includes quick formulas for estimated weight, minimum SBP, ETT size, and maintenance fluids. Body Surface Area — Mosteller formula for BSA calculation. Weight-Based Dosing — Dose per kg with frequency, max dose cap, and volume calculation from concentration.`},{q:`How accurate is the BP calculator?`,a:`The BP calculator uses the same Rosner quantile spline regression method as the Baylor College of Medicine reference calculator. It computes exact percentiles (1st-99th) based on your patient's age, sex, and height using published regression coefficients. This is the same methodology underlying the AAP 2017 normative tables. Results are height-adjusted and clinically accurate.`},{q:`What are the growth chart curves?`,a:`The growth charts display WHO/CDC percentile curves (3rd, 5th, 10th, 25th, 50th, 75th, 90th, 95th, 97th percentiles) with your patient's measurement plotted as a blue dot. The 50th percentile is shown as a bold green line. Shaded bands show the normal range between symmetric percentiles. For Length/Height-for-Age, you can optionally enter both parents' heights to see the mid-parental target height range plotted on the chart.`}]}];function bu({q:e,a:t}){let[n,r]=(0,_.useState)(!1);return(0,A.jsxs)(`div`,{className:`border-b border-border last:border-0`,children:[(0,A.jsxs)(`button`,{onClick:()=>r(!n),className:`w-full text-left py-3 px-4 flex items-center justify-between hover:bg-muted/40 transition-colors`,"aria-expanded":n,children:[(0,A.jsx)(`span`,{className:`font-medium text-sm`,children:e}),(0,A.jsx)(`span`,{className:`text-muted-foreground text-sm`,children:n?`−`:`+`})]}),n&&(0,A.jsx)(`div`,{className:`px-4 pb-4 text-sm text-muted-foreground leading-relaxed whitespace-pre-line`,children:t})]})}function xu(){return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-6`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Frequently Asked Questions`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Learn how Pediatric AI Scribe works and get the most out of it.`})]}),yu.map(e=>(0,A.jsxs)(`section`,{className:`rounded-lg border border-border overflow-hidden`,children:[(0,A.jsx)(`h2`,{className:`bg-muted/40 px-4 py-2 text-sm font-semibold`,children:e.section}),(0,A.jsx)(`div`,{className:`bg-card`,children:e.items.map(e=>(0,A.jsx)(bu,{q:e.q,a:e.a},e.q))})]},e.section))]})}function Su(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(`outpatient`),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(null),[u,d]=(0,_.useState)(null),f=M({mutationFn:e=>F.post(`/api/generate-hpi-dictation`,e),onSuccess:e=>l(e.hpi),onError:()=>l(null)});function p(t){t.preventDefault(),d(null);let r={transcript:o,patientAge:e,patientGender:n,setting:i},a=fu.safeParse(r);if(!a.success){d(a.error.issues.map(e=>e.message).join(`, `));return}l(null),f.mutate(a.data)}function m(){s(``),l(null),d(null)}let h=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Voice Dictation → HPI`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Dictate your narrative → AI restructures into polished HPI. Audio-capture UI is a follow-up; this minimal form supports typed/pasted transcripts.`})]}),(0,A.jsxs)(`form`,{onSubmit:p,className:`space-y-4`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Age`}),(0,A.jsx)(`input`,{className:h,placeholder:`e.g. 8 months`,value:e,onChange:e=>t(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Gender`}),(0,A.jsxs)(`select`,{className:h,value:n,onChange:e=>r(e.target.value),children:[(0,A.jsx)(`option`,{value:``,children:`Select`}),(0,A.jsx)(`option`,{children:`Male`}),(0,A.jsx)(`option`,{children:`Female`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Setting`}),(0,A.jsxs)(`select`,{className:h,value:i,onChange:e=>a(e.target.value),children:[(0,A.jsx)(`option`,{value:`outpatient`,children:`Outpatient`}),(0,A.jsx)(`option`,{value:`inpatient`,children:`Inpatient / Floors`})]})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Transcript / dictation`}),(0,A.jsx)(`button`,{type:`button`,onClick:m,className:`text-xs text-muted-foreground underline`,children:`Clear`})]}),(0,A.jsx)(`textarea`,{className:h+` min-h-[200px] font-mono text-sm`,placeholder:`Type or paste your dictation here, then click Generate.`,value:o,onChange:e=>s(e.target.value)})]}),u&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:u}),f.error&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:f.error.message}),(0,A.jsx)(`div`,{className:`flex gap-2`,children:(0,A.jsx)(`button`,{type:`submit`,disabled:f.isPending||!o.trim(),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:f.isPending?`Generating…`:`Generate HPI`})})]}),c&&(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border flex items-center justify-between bg-muted/40`,children:[(0,A.jsx)(`h2`,{className:`text-sm font-semibold`,children:`Generated HPI`}),(0,A.jsx)(`button`,{onClick:()=>navigator.clipboard.writeText(c),className:`text-xs text-muted-foreground underline`,children:`Copy`})]}),(0,A.jsx)(`div`,{className:`p-4 whitespace-pre-wrap text-sm`,children:c})]})]})}function Cu(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(`outpatient`),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(null),[u,d]=(0,_.useState)(null),f=M({mutationFn:e=>F.post(`/api/generate-hpi-encounter`,e),onSuccess:e=>l(e.hpi)});function p(t){t.preventDefault(),d(null);let r={transcript:o,patientAge:e,patientGender:n,setting:i},a=fu.safeParse(r);if(!a.success){d(a.error.issues.map(e=>e.message).join(`, `));return}l(null),f.mutate(a.data)}let m=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Live Encounter → HPI`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Record or paste an encounter transcript; generate a structured HPI.`})]}),(0,A.jsxs)(`form`,{onSubmit:p,className:`space-y-4`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Age`}),(0,A.jsx)(`input`,{className:m,placeholder:`e.g. 5 years`,value:e,onChange:e=>t(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Gender`}),(0,A.jsxs)(`select`,{className:m,value:n,onChange:e=>r(e.target.value),children:[(0,A.jsx)(`option`,{value:``,children:`Select`}),(0,A.jsx)(`option`,{children:`Male`}),(0,A.jsx)(`option`,{children:`Female`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Setting`}),(0,A.jsxs)(`select`,{className:m,value:i,onChange:e=>a(e.target.value),children:[(0,A.jsx)(`option`,{value:`outpatient`,children:`Outpatient`}),(0,A.jsx)(`option`,{value:`inpatient`,children:`Inpatient / Floors`})]})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Transcript`}),(0,A.jsx)(`textarea`,{className:m+` min-h-[220px] font-mono text-sm`,placeholder:`Type or paste an encounter transcript, then click Generate.`,value:o,onChange:e=>s(e.target.value)})]}),u&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:u}),f.error&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:f.error.message}),(0,A.jsx)(`button`,{type:`submit`,disabled:f.isPending||!o.trim(),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:f.isPending?`Generating…`:`Generate HPI`})]}),c&&(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border flex items-center justify-between bg-muted/40`,children:[(0,A.jsx)(`h2`,{className:`text-sm font-semibold`,children:`Generated HPI`}),(0,A.jsx)(`button`,{onClick:()=>navigator.clipboard.writeText(c),className:`text-xs text-muted-foreground underline`,children:`Copy`})]}),(0,A.jsx)(`div`,{className:`p-4 whitespace-pre-wrap text-sm`,children:c})]})]})}function wu(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(`full`),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(``),[u,d]=(0,_.useState)(null),[f,p]=(0,_.useState)(null),m=M({mutationFn:e=>F.post(`/api/generate-soap`,e),onSuccess:e=>d(e.soap)});function h(t){t.preventDefault(),p(null);let r={transcript:o,patientAge:e,patientGender:n,type:i,additionalInstructions:c},a=pu.safeParse(r);if(!a.success){p(a.error.issues.map(e=>e.message).join(`, `));return}d(null),m.mutate(a.data)}let g=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`SOAP Note`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Encounter transcript → full SOAP or subjective-only narrative.`})]}),(0,A.jsxs)(`form`,{onSubmit:h,className:`space-y-4`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Age`}),(0,A.jsx)(`input`,{className:g,placeholder:`e.g. 3 years`,value:e,onChange:e=>t(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Gender`}),(0,A.jsxs)(`select`,{className:g,value:n,onChange:e=>r(e.target.value),children:[(0,A.jsx)(`option`,{value:``,children:`Select`}),(0,A.jsx)(`option`,{children:`Male`}),(0,A.jsx)(`option`,{children:`Female`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Output type`}),(0,A.jsxs)(`select`,{className:g,value:i,onChange:e=>a(e.target.value),children:[(0,A.jsx)(`option`,{value:`full`,children:`Full SOAP`}),(0,A.jsx)(`option`,{value:`subjective`,children:`Subjective only`})]})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Transcript`}),(0,A.jsx)(`textarea`,{className:g+` min-h-[200px] font-mono text-sm`,placeholder:`Type or paste the encounter transcript.`,value:o,onChange:e=>s(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsxs)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:[`Additional instructions `,(0,A.jsx)(`span`,{className:`text-muted-foreground normal-case font-normal`,children:`(optional)`})]}),(0,A.jsx)(`textarea`,{className:g+` min-h-[60px] text-sm`,placeholder:`e.g., 'Include return precautions', 'Add differential for otitis media'`,value:c,onChange:e=>l(e.target.value)})]}),f&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:f}),m.error&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:m.error.message}),(0,A.jsx)(`button`,{type:`submit`,disabled:m.isPending||!o.trim(),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:m.isPending?`Generating…`:`Generate SOAP`})]}),u&&(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border flex items-center justify-between bg-muted/40`,children:[(0,A.jsx)(`h2`,{className:`text-sm font-semibold`,children:`Generated SOAP`}),(0,A.jsx)(`button`,{onClick:()=>navigator.clipboard.writeText(u),className:`text-xs text-muted-foreground underline`,children:`Copy`})]}),(0,A.jsx)(`div`,{className:`p-4 whitespace-pre-wrap text-sm`,children:u})]})]})}function Tu(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(``),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(null),[u,d]=(0,_.useState)(null),f=M({mutationFn:e=>F.post(`/api/sick-visit/note`,e),onSuccess:e=>l(e.note)});function p(t){t.preventDefault(),d(null);let r={patientAge:e,patientGender:n,chiefComplaint:i,transcript:o},a=mu.safeParse(r);if(!a.success){d(a.error.issues.map(e=>e.message).join(`, `));return}l(null),f.mutate(a.data)}let m=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Sick Visit`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Chief complaint + transcript → structured sick-visit note.`})]}),(0,A.jsxs)(`form`,{onSubmit:p,className:`space-y-4`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Age`}),(0,A.jsx)(`input`,{className:m,placeholder:`e.g. 4 years`,value:e,onChange:e=>t(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Gender`}),(0,A.jsxs)(`select`,{className:m,value:n,onChange:e=>r(e.target.value),children:[(0,A.jsx)(`option`,{value:``,children:`Select`}),(0,A.jsx)(`option`,{children:`Male`}),(0,A.jsx)(`option`,{children:`Female`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1 col-span-3 md:col-span-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Chief complaint`}),(0,A.jsx)(`input`,{className:m,placeholder:`e.g. Fever x 2 days`,value:i,onChange:e=>a(e.target.value)})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Transcript / dictation`}),(0,A.jsx)(`textarea`,{className:m+` min-h-[200px] font-mono text-sm`,placeholder:`Encounter narrative — transcribed or dictated.`,value:o,onChange:e=>s(e.target.value)})]}),u&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:u}),f.error&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:f.error.message}),(0,A.jsx)(`button`,{type:`submit`,disabled:f.isPending||!i.trim(),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:f.isPending?`Generating…`:`Generate Note`})]}),c&&(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border flex items-center justify-between bg-muted/40`,children:[(0,A.jsx)(`h2`,{className:`text-sm font-semibold`,children:`Sick Visit Note`}),(0,A.jsx)(`button`,{onClick:()=>navigator.clipboard.writeText(c),className:`text-xs text-muted-foreground underline`,children:`Copy`})]}),(0,A.jsx)(`div`,{className:`p-4 whitespace-pre-wrap text-sm`,children:c})]})]})}function Eu(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(``),[o,s]=(0,_.useState)(`floor`),[c,l]=(0,_.useState)(``),[u,d]=(0,_.useState)(`auto`),[f,p]=(0,_.useState)(``),[m,h]=(0,_.useState)(``),[g,v]=(0,_.useState)(``),[y,b]=(0,_.useState)(null),x=M({mutationFn:e=>F.post(`/api/generate-hospital-course`,e),onSuccess:e=>b({hospitalCourse:e.hospitalCourse,format:e.format||`auto`})});function S(t){t.preventDefault(),b(null);let r=m.split(/\n\s*\n/).map(e=>e.trim()).filter(Boolean).map((e,t)=>({date:`Day ${t+1}`,type:`Progress Note`,content:e}));x.mutate({notes:r,hAndP:f?{date:`Admission`,content:f}:void 0,patientAge:e,patientGender:n,pmh:i,setting:o,los:c?parseInt(c):void 0,formatPreference:u,additionalInstructions:g||void 0})}let C=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Hospital Course`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Progress notes + H&P → hospital course summary (prose, day-by-day, or organ-system format).`})]}),(0,A.jsxs)(`form`,{onSubmit:S,className:`space-y-4`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Age`}),(0,A.jsx)(`input`,{className:C,value:e,onChange:e=>t(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Gender`}),(0,A.jsxs)(`select`,{className:C,value:n,onChange:e=>r(e.target.value),children:[(0,A.jsx)(`option`,{value:``,children:`Select`}),(0,A.jsx)(`option`,{children:`Male`}),(0,A.jsx)(`option`,{children:`Female`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Setting`}),(0,A.jsxs)(`select`,{className:C,value:o,onChange:e=>s(e.target.value),children:[(0,A.jsx)(`option`,{value:`floor`,children:`Floor`}),(0,A.jsx)(`option`,{value:`picu`,children:`PICU`}),(0,A.jsx)(`option`,{value:`nicu`,children:`NICU`}),(0,A.jsx)(`option`,{value:`psych`,children:`Psych`})]})]})]}),(0,A.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1 col-span-2`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`PMH`}),(0,A.jsx)(`input`,{className:C,placeholder:`e.g. Asthma, hypothyroidism`,value:i,onChange:e=>a(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`LOS (days)`}),(0,A.jsx)(`input`,{className:C,type:`number`,value:c,onChange:e=>l(e.target.value)})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Format`}),(0,A.jsxs)(`select`,{className:C,value:u,onChange:e=>d(e.target.value),children:[(0,A.jsx)(`option`,{value:`auto`,children:`Auto (infer from setting + LOS)`}),(0,A.jsx)(`option`,{value:`prose`,children:`Prose summary`}),(0,A.jsx)(`option`,{value:`dayByDay`,children:`Day-by-day`}),(0,A.jsx)(`option`,{value:`organSystem`,children:`Organ-system (ICU)`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`H&P`}),(0,A.jsx)(`textarea`,{className:C+` min-h-[120px] font-mono text-sm`,value:f,onChange:e=>p(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsxs)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:[`Progress notes `,(0,A.jsx)(`span`,{className:`normal-case font-normal text-muted-foreground`,children:`(separate each note with a blank line)`})]}),(0,A.jsx)(`textarea`,{className:C+` min-h-[200px] font-mono text-sm`,value:m,onChange:e=>h(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Additional instructions`}),(0,A.jsx)(`textarea`,{className:C+` min-h-[60px] text-sm`,value:g,onChange:e=>v(e.target.value)})]}),x.error&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:x.error.message}),(0,A.jsx)(`button`,{type:`submit`,disabled:x.isPending||!m.trim(),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:x.isPending?`Generating…`:`Generate Hospital Course`})]}),y&&(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border flex items-center justify-between bg-muted/40`,children:[(0,A.jsxs)(`h2`,{className:`text-sm font-semibold`,children:[`Hospital Course `,(0,A.jsxs)(`span`,{className:`text-xs font-normal text-muted-foreground`,children:[`(`,y.format,`)`]})]}),(0,A.jsx)(`button`,{onClick:()=>navigator.clipboard.writeText(y.hospitalCourse),className:`text-xs text-muted-foreground underline`,children:`Copy`})]}),(0,A.jsx)(`div`,{className:`p-4 whitespace-pre-wrap text-sm`,children:y.hospitalCourse})]})]})}function Du(){return{date:``,content:``,labs:``}}function Ou(){let[e,t]=(0,_.useState)(`outpatient`),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(``),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)([Du()]),[u,d]=(0,_.useState)(``),[f,p]=(0,_.useState)(null),m=M({mutationFn:e=>F.post(`/api/generate-chart-review`,e),onSuccess:e=>p(e.review)});function h(e,t){l(n=>n.map((n,r)=>r===e?{...n,...t}:n))}function g(t){t.preventDefault(),p(null);let r=c.filter(e=>e.content.trim());m.mutate({type:e,patientAge:n,patientGender:i,pmh:o,visits:e===`outpatient`?r:void 0,subspecialty:e===`subspecialty`?r:void 0,edVisits:e===`ed`?r:void 0,additionalInstructions:u||void 0})}let v=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Chart Review`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Past visits → summary for pre-charting.`})]}),(0,A.jsxs)(`form`,{onSubmit:g,className:`space-y-4`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-4 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Review type`}),(0,A.jsxs)(`select`,{className:v,value:e,onChange:e=>t(e.target.value),children:[(0,A.jsx)(`option`,{value:`outpatient`,children:`Outpatient`}),(0,A.jsx)(`option`,{value:`subspecialty`,children:`Subspecialty`}),(0,A.jsx)(`option`,{value:`ed`,children:`ED`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Age`}),(0,A.jsx)(`input`,{className:v,value:n,onChange:e=>r(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Gender`}),(0,A.jsxs)(`select`,{className:v,value:i,onChange:e=>a(e.target.value),children:[(0,A.jsx)(`option`,{value:``,children:`Select`}),(0,A.jsx)(`option`,{children:`Male`}),(0,A.jsx)(`option`,{children:`Female`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`PMH`}),(0,A.jsx)(`input`,{className:v,value:o,onChange:e=>s(e.target.value)})]})]}),(0,A.jsxs)(`div`,{className:`space-y-3`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,A.jsx)(`span`,{className:`text-sm font-semibold`,children:`Visits`}),(0,A.jsx)(`button`,{type:`button`,onClick:()=>l(e=>[...e,Du()]),className:`text-xs rounded-md border border-border px-2 py-1`,children:`+ Add visit`})]}),c.map((e,t)=>(0,A.jsxs)(`div`,{className:`rounded-lg border border-border p-3 space-y-2 bg-card`,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,A.jsx)(`input`,{type:`date`,className:v+` max-w-xs`,value:e.date,onChange:e=>h(t,{date:e.target.value})}),c.length>1&&(0,A.jsx)(`button`,{type:`button`,onClick:()=>l(e=>e.filter((e,n)=>n!==t)),className:`text-xs text-destructive`,children:`Remove`})]}),(0,A.jsx)(`textarea`,{className:v+` min-h-[100px] font-mono text-sm`,placeholder:`Visit note content — paste here.`,value:e.content,onChange:e=>h(t,{content:e.target.value})}),(0,A.jsx)(`textarea`,{className:v+` min-h-[60px] font-mono text-xs`,placeholder:`Labs from this visit (optional)`,value:e.labs,onChange:e=>h(t,{labs:e.target.value})})]},t))]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Additional instructions`}),(0,A.jsx)(`textarea`,{className:v+` min-h-[60px] text-sm`,placeholder:`e.g. 'Focus on thyroid management', 'Highlight medication changes'`,value:u,onChange:e=>d(e.target.value)})]}),m.error&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:m.error.message}),(0,A.jsx)(`button`,{type:`submit`,disabled:m.isPending||!c.some(e=>e.content.trim()),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:m.isPending?`Generating…`:`Generate Chart Review`})]}),f&&(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border flex items-center justify-between bg-muted/40`,children:[(0,A.jsx)(`h2`,{className:`text-sm font-semibold`,children:`Chart Review`}),(0,A.jsx)(`button`,{onClick:()=>navigator.clipboard.writeText(f),className:`text-xs text-muted-foreground underline`,children:`Copy`})]}),(0,A.jsx)(`div`,{className:`p-4 whitespace-pre-wrap text-sm`,children:f})]})]})}function ku(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(``),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(``),[u,d]=(0,_.useState)(``),[f,p]=(0,_.useState)(``),[m,h]=(0,_.useState)(``),[g,v]=(0,_.useState)(``),[y,b]=(0,_.useState)(`full`),[x,S]=(0,_.useState)(null),C=M({mutationFn:e=>F.post(`/api/well-visit/note`,e),onSuccess:e=>S(e.note)});function w(t){t.preventDefault(),S(null),C.mutate({patientAge:e,patientGender:n,visitAge:i,vitals:o,measurements:c,parentConcerns:u,transcript:f,screenings:m,vaccines:g,noteStyle:y})}let T=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`;return(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Well Visit`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Preventive-care note generation. Milestones and SSHADESS sub-tabs land in a follow-up; this first port covers the Visit Note pane.`})]}),(0,A.jsxs)(`form`,{onSubmit:w,className:`space-y-4`,children:[(0,A.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Age`}),(0,A.jsx)(`input`,{className:T,value:e,onChange:e=>t(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Gender`}),(0,A.jsxs)(`select`,{className:T,value:n,onChange:e=>r(e.target.value),children:[(0,A.jsx)(`option`,{value:``,children:`Select`}),(0,A.jsx)(`option`,{children:`Male`}),(0,A.jsx)(`option`,{children:`Female`})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Visit age`}),(0,A.jsx)(`input`,{className:T,placeholder:`e.g. 6 months`,value:i,onChange:e=>a(e.target.value)})]})]}),(0,A.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Vital signs`}),(0,A.jsx)(`textarea`,{className:T+` min-h-[60px] font-mono text-xs`,value:o,onChange:e=>s(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Measurements / growth`}),(0,A.jsx)(`textarea`,{className:T+` min-h-[60px] font-mono text-xs`,value:c,onChange:e=>l(e.target.value)})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Parent / patient concerns`}),(0,A.jsx)(`textarea`,{className:T+` min-h-[60px] text-sm`,value:u,onChange:e=>d(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Transcript / dictation`}),(0,A.jsx)(`textarea`,{className:T+` min-h-[160px] font-mono text-sm`,value:f,onChange:e=>p(e.target.value)})]}),(0,A.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Screenings completed`}),(0,A.jsx)(`textarea`,{className:T+` min-h-[60px] text-xs`,value:m,onChange:e=>h(e.target.value)})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Immunizations today`}),(0,A.jsx)(`textarea`,{className:T+` min-h-[60px] text-xs`,value:g,onChange:e=>v(e.target.value)})]})]}),(0,A.jsxs)(`label`,{className:`flex flex-col gap-1`,children:[(0,A.jsx)(`span`,{className:`text-xs font-semibold uppercase tracking-wider text-muted-foreground`,children:`Note style`}),(0,A.jsxs)(`select`,{className:T,value:y,onChange:e=>b(e.target.value),children:[(0,A.jsx)(`option`,{value:`full`,children:`Full encounter note`}),(0,A.jsx)(`option`,{value:`short`,children:`Brief SOAP`})]})]}),C.error&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:C.error.message}),(0,A.jsx)(`button`,{type:`submit`,disabled:C.isPending||!e.trim()&&!i.trim(),className:`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,children:C.isPending?`Generating…`:`Generate Well Visit Note`})]}),x&&(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border flex items-center justify-between bg-muted/40`,children:[(0,A.jsx)(`h2`,{className:`text-sm font-semibold`,children:`Well Visit Note`}),(0,A.jsx)(`button`,{onClick:()=>navigator.clipboard.writeText(x),className:`text-xs text-muted-foreground underline`,children:`Copy`})]}),(0,A.jsx)(`div`,{className:`p-4 whitespace-pre-wrap text-sm`,children:x})]})]})}function Au(){let{data:e,isLoading:t,error:n}=j({queryKey:[`schedule-data`],queryFn:()=>F.get(`/api/schedule-data`)});if(t)return(0,A.jsx)(`div`,{className:`p-6 text-sm text-muted-foreground`,children:`Loading schedule…`});if(n)return(0,A.jsx)(`div`,{className:`p-6 text-sm text-destructive`,children:n.message});if(!e)return null;let r=e.visitAges.filter(t=>e.periodicity[t.id]?.vaccines?.length),i=[],a=new Set;return r.forEach(t=>{e.periodicity[t.id].vaccines.forEach(e=>{a.has(e.vaccine)||(a.add(e.vaccine),i.push(e.vaccine))})}),(0,A.jsxs)(`div`,{className:`max-w-full mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Vaccine Schedule`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`AAP/ACIP 2025 complete immunization schedule (0–18 years).`})]}),(0,A.jsx)(`div`,{className:`rounded-lg border border-border overflow-auto bg-card`,children:(0,A.jsxs)(`table`,{className:`text-xs`,children:[(0,A.jsx)(`thead`,{className:`sticky top-0 bg-muted`,children:(0,A.jsxs)(`tr`,{children:[(0,A.jsx)(`th`,{className:`text-left font-semibold px-3 py-2 border-b border-border min-w-[180px] sticky left-0 bg-muted`,children:`Vaccine`}),r.map(e=>(0,A.jsx)(`th`,{className:`px-2 py-2 border-b border-border text-center whitespace-nowrap`,children:e.label},e.id))]})}),(0,A.jsx)(`tbody`,{children:i.map(t=>(0,A.jsxs)(`tr`,{className:`even:bg-muted/20`,children:[(0,A.jsx)(`td`,{className:`px-3 py-2 border-b border-border font-medium sticky left-0 bg-card`,children:e.vaccineFullNames[t]||t}),r.map(n=>{let r=(e.periodicity[n.id].vaccines||[]).find(e=>e.vaccine===t);if(!r)return(0,A.jsx)(`td`,{className:`border-b border-border`},n.id);let i=typeof r.dose==`number`?`#`+r.dose:r.dose||`•`;return(0,A.jsx)(`td`,{className:`border-b border-border text-center bg-primary/10 font-mono text-[11px]`,title:r.notes||`${t} dose ${r.dose}`,children:i},n.id)})]},t))})]})}),(0,A.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Hover any filled cell for notes. Sources: AAP/Bright Futures (Feb 2025), CDC Child & Adolescent Immunization Schedule (2025).`})]})}function ju(){let{data:e,isLoading:t,error:n}=j({queryKey:[`schedule-data`],queryFn:()=>F.get(`/api/schedule-data`)});return t?(0,A.jsx)(`div`,{className:`p-6 text-sm text-muted-foreground`,children:`Loading…`}):n?(0,A.jsx)(`div`,{className:`p-6 text-sm text-destructive`,children:n.message}):e?(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Catch-Up Schedule`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`CDC 2025 catch-up immunization schedule — minimum ages and intervals per vaccine.`})]}),Object.entries(e.catchUpSchedule).map(([t,n])=>{let r=e.vaccineFullNames[t]||t,i=n.catchUpNotes?Array.isArray(n.catchUpNotes)?n.catchUpNotes:[n.catchUpNotes]:[];return(0,A.jsxs)(`section`,{className:`rounded-lg border border-border bg-card overflow-hidden`,children:[(0,A.jsxs)(`header`,{className:`px-4 py-2 border-b border-border bg-muted/40 flex items-center justify-between`,children:[(0,A.jsx)(`h2`,{className:`text-sm font-semibold`,children:r}),n.minimumAgeForDose1&&(0,A.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[`Min age dose 1: `,(0,A.jsx)(`strong`,{children:n.minimumAgeForDose1})]})]}),n.series&&n.series.length>0&&(0,A.jsxs)(`table`,{className:`w-full text-xs`,children:[(0,A.jsx)(`thead`,{className:`bg-muted/20`,children:(0,A.jsxs)(`tr`,{children:[(0,A.jsx)(`th`,{className:`text-left px-3 py-2`,children:`Dose`}),(0,A.jsx)(`th`,{className:`text-left px-3 py-2`,children:`Min age`}),(0,A.jsx)(`th`,{className:`text-left px-3 py-2`,children:`Min interval from prev`}),(0,A.jsx)(`th`,{className:`text-left px-3 py-2`,children:`Notes`})]})}),(0,A.jsx)(`tbody`,{children:n.series.map(e=>(0,A.jsxs)(`tr`,{className:`border-t border-border`,children:[(0,A.jsxs)(`td`,{className:`px-3 py-2 font-semibold`,children:[`Dose `,e.dose]}),(0,A.jsx)(`td`,{className:`px-3 py-2`,children:e.minimumAge||`—`}),(0,A.jsx)(`td`,{className:`px-3 py-2`,children:e.minimumIntervalToPrev||`—`}),(0,A.jsx)(`td`,{className:`px-3 py-2 text-muted-foreground`,children:e.notes||``})]},String(e.dose)))})]}),i.length>0&&(0,A.jsx)(`ul`,{className:`list-disc pl-8 py-2 text-xs text-muted-foreground space-y-1`,children:i.map((e,t)=>(0,A.jsx)(`li`,{children:e},t))})]},t)})]}):null}function Mu({open:e,title:t,body:n,confirmText:r=`Confirm`,cancelText:i=`Cancel`,danger:a=!1,requirePassword:o=!1,passwordPlaceholder:s=`Password`,onConfirm:c,onCancel:l,busy:u=!1}){let[d,f]=(0,_.useState)(``);if((0,_.useEffect)(()=>{e||f(``)},[e]),(0,_.useEffect)(()=>{function t(t){e&&t.key===`Escape`&&l()}return window.addEventListener(`keydown`,t),()=>window.removeEventListener(`keydown`,t)},[e,l]),!e)return null;let p=u||o&&!d;return(0,A.jsx)(`div`,{role:`dialog`,"aria-modal":`true`,"aria-labelledby":`confirm-modal-title`,className:`fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4`,onClick:l,children:(0,A.jsxs)(`div`,{className:`w-full max-w-sm rounded-lg border border-border bg-background p-5 shadow-lg space-y-3`,onClick:e=>e.stopPropagation(),children:[(0,A.jsx)(`h3`,{id:`confirm-modal-title`,className:`text-base font-semibold`,children:t}),n&&(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:n}),o&&(0,A.jsx)(`input`,{type:`password`,autoFocus:!0,value:d,onChange:e=>f(e.target.value),placeholder:s,className:`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`,onKeyDown:e=>{e.key===`Enter`&&!p&&c(d)}}),(0,A.jsxs)(`div`,{className:`flex justify-end gap-2 pt-1`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:l,className:`rounded-md border border-border px-3 py-2 text-sm`,"data-testid":`confirm-modal-cancel`,children:i}),(0,A.jsx)(`button`,{type:`button`,disabled:p,onClick:()=>c(o?d:void 0),className:`rounded-md px-3 py-2 text-sm font-medium text-white disabled:opacity-50 `+(a?`bg-destructive`:`bg-primary`),"data-testid":`confirm-modal-ok`,children:u?`Working…`:r})]})]})})}var Nu=`rounded-lg border border-border bg-card p-5 space-y-3`,Pu=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`,Fu=`rounded-md bg-primary text-primary-foreground px-3 py-2 text-sm font-medium disabled:opacity-50`,Iu=`rounded-md border border-border px-3 py-2 text-sm disabled:opacity-50`,Lu=`rounded-md bg-destructive text-white px-3 py-2 text-sm font-medium disabled:opacity-50`;function Ru({msg:e}){return e?(0,A.jsx)(`div`,{className:`text-sm `+(e.kind===`ok`?`text-green-600`:e.kind===`err`?`text-destructive`:`text-muted-foreground`),children:e.text}):null}function zu(e){let t=Math.floor((Date.now()-new Date(e).getTime())/1e3);return t<60?`just now`:t<3600?Math.floor(t/60)+`m ago`:t<86400?Math.floor(t/3600)+`h ago`:Math.floor(t/86400)+`d ago`}function Bu(){let e=et(),[t,n]=(0,_.useState)(``),[r,i]=(0,_.useState)(``),[a,o]=(0,_.useState)(``),[s,c]=(0,_.useState)(null),l=M({mutationFn:e=>F.post(`/api/auth/change-password`,e),onSuccess:t=>{c({text:t.message||`Password changed`,kind:`ok`}),n(``),i(``),o(``),e.invalidateQueries({queryKey:[`sessions`]}),t.passwordWarning&&setTimeout(()=>c({text:t.passwordWarning,kind:`info`}),2e3)},onError:e=>c({text:e.message,kind:`err`})});function u(e){if(e.preventDefault(),c(null),!t||!r)return c({text:`Fill in all fields`,kind:`err`});if(r.length<8)return c({text:`New password must be 8+ characters`,kind:`err`});if(r!==a)return c({text:`Passwords do not match`,kind:`err`});l.mutate({currentPassword:t,newPassword:r})}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`change-password-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Change Password`}),(0,A.jsxs)(`form`,{onSubmit:u,className:`space-y-2 max-w-sm`,children:[(0,A.jsx)(`input`,{type:`password`,className:Pu,placeholder:`Current password`,value:t,onChange:e=>n(e.target.value),"data-testid":`pw-current`}),(0,A.jsx)(`input`,{type:`password`,className:Pu,placeholder:`New password (8+ characters)`,minLength:8,value:r,onChange:e=>i(e.target.value),"data-testid":`pw-new`}),(0,A.jsx)(`input`,{type:`password`,className:Pu,placeholder:`Confirm new password`,minLength:8,value:a,onChange:e=>o(e.target.value),"data-testid":`pw-confirm`}),(0,A.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,A.jsx)(`button`,{type:`submit`,disabled:l.isPending,className:Fu,"data-testid":`btn-change-password`,children:l.isPending?`Changing…`:`Change Password`}),(0,A.jsx)(Ru,{msg:s})]})]})]})}function Vu({codes:e,onClose:t}){return(0,A.jsx)(`div`,{role:`dialog`,"aria-modal":`true`,className:`fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4`,children:(0,A.jsxs)(`div`,{className:`w-full max-w-md rounded-lg border border-border bg-background p-5 shadow-lg space-y-3`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Save your backup codes`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Each code can be used once if you lose access to your authenticator. They will not be shown again.`}),(0,A.jsx)(`pre`,{className:`bg-muted p-3 rounded text-sm font-mono whitespace-pre-wrap break-all`,children:e.join(` `)}),(0,A.jsxs)(`div`,{className:`flex justify-end gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:()=>navigator.clipboard?.writeText(e.join(` `)),className:Iu,children:`Copy`}),(0,A.jsx)(`button`,{type:`button`,onClick:t,className:Fu,children:`I've saved them`})]})]})})}function Hu({user:e}){let t=et(),n=e.totp_enabled===!0,[r,i]=(0,_.useState)(`idle`),[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(``),[l,u]=(0,_.useState)(``),[d,f]=(0,_.useState)(null),[p,m]=(0,_.useState)(!1),[h,g]=(0,_.useState)(null),{data:v}=j({queryKey:[`2fa-backup-count`],queryFn:()=>F.get(`/api/auth/2fa/backup-codes/count`),enabled:n}),y=M({mutationFn:()=>F.post(`/api/auth/setup-2fa`,{}),onSuccess:e=>{o(e),i(`setup`),g(null)},onError:e=>g({text:e.message,kind:`err`})}),b=M({mutationFn:e=>F.post(`/api/auth/verify-2fa`,{code:e}),onSuccess:e=>{i(`idle`),c(``),o(null),e.backupCodes&&e.backupCodes.length&&f(e.backupCodes),t.invalidateQueries({queryKey:[`auth-me`]}),t.invalidateQueries({queryKey:[`2fa-backup-count`]}),g({text:`2FA enabled`,kind:`ok`})},onError:e=>g({text:e.message||`Invalid code`,kind:`err`})}),x=M({mutationFn:e=>F.post(`/api/auth/disable-2fa`,{password:e}),onSuccess:()=>{i(`idle`),u(``),t.invalidateQueries({queryKey:[`auth-me`]}),t.invalidateQueries({queryKey:[`2fa-backup-count`]}),g({text:`2FA disabled`,kind:`info`})},onError:e=>g({text:e.message||`Failed`,kind:`err`})}),S=M({mutationFn:e=>F.post(`/api/auth/2fa/backup-codes`,{password:e}),onSuccess:e=>{m(!1),e.codes&&e.codes.length&&f(e.codes),t.invalidateQueries({queryKey:[`2fa-backup-count`]})},onError:e=>{m(!1),g({text:e.message||`Failed to regenerate codes`,kind:`err`})}});return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`2fa-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Two-Factor Authentication`}),(0,A.jsxs)(`p`,{className:`text-sm`,"data-testid":`2fa-status`,children:[`Status:`,` `,(0,A.jsx)(`span`,{className:n?`text-green-600 font-medium`:`text-destructive font-medium`,children:n?`✅ Enabled`:`❌ Not enabled`})]}),!n&&r===`idle`&&(0,A.jsx)(`button`,{type:`button`,className:Fu,onClick:()=>y.mutate(),disabled:y.isPending,"data-testid":`btn-setup-2fa`,children:y.isPending?`Setting up…`:`Enable 2FA`}),n&&r===`idle`&&(0,A.jsxs)(`div`,{className:`flex items-center gap-3 flex-wrap`,children:[(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive`,onClick:()=>i(`disabling`),"data-testid":`btn-disable-2fa`,children:`Disable 2FA`}),v&&(0,A.jsxs)(`span`,{className:`text-sm `+(v.remaining<=2?`text-orange-500`:`text-muted-foreground`),children:[v.remaining,` backup codes remaining.`]}),v&&(0,A.jsx)(`button`,{type:`button`,className:Iu,onClick:()=>m(!0),"data-testid":`btn-regen-backup-codes`,children:`Regenerate`})]}),r===`setup`&&a&&(0,A.jsxs)(`div`,{className:`space-y-3 p-3 bg-muted/40 rounded-md`,children:[(0,A.jsx)(`p`,{className:`text-sm`,children:`Scan this QR code with your authenticator app:`}),(0,A.jsx)(`img`,{src:a.qrCode,alt:`2FA QR code`,className:`bg-white p-2 rounded max-w-[240px]`}),(0,A.jsxs)(`p`,{className:`text-sm`,children:[`Or enter manually: `,(0,A.jsx)(`code`,{className:`bg-muted px-2 py-0.5 rounded text-xs`,children:a.secret})]}),(0,A.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,A.jsx)(`input`,{type:`text`,maxLength:6,className:Pu+` max-w-[140px] font-mono tracking-widest`,placeholder:`123456`,value:s,onChange:e=>c(e.target.value.replace(/\D/g,``)),"data-testid":`2fa-verify-code`}),(0,A.jsx)(`button`,{type:`button`,className:Fu,disabled:s.length!==6||b.isPending,onClick:()=>b.mutate(s),"data-testid":`btn-verify-2fa`,children:b.isPending?`Verifying…`:`Verify & Enable`}),(0,A.jsx)(`button`,{type:`button`,className:Iu,onClick:()=>{i(`idle`),o(null),c(``)},children:`Cancel`})]})]}),r===`disabling`&&(0,A.jsxs)(`div`,{className:`space-y-2 p-3 bg-muted/40 rounded-md max-w-sm`,children:[(0,A.jsx)(`label`,{className:`block text-sm font-medium`,children:`Enter your password to confirm:`}),(0,A.jsx)(`input`,{type:`password`,className:Pu,value:l,onChange:e=>u(e.target.value),placeholder:`Password`,"data-testid":`2fa-disable-password`}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,className:Lu,disabled:!l||x.isPending,onClick:()=>x.mutate(l),"data-testid":`btn-disable-2fa-confirm`,children:x.isPending?`Disabling…`:`Confirm Disable`}),(0,A.jsx)(`button`,{type:`button`,className:Iu,onClick:()=>{i(`idle`),u(``)},"data-testid":`btn-disable-2fa-cancel`,children:`Cancel`})]})]}),(0,A.jsx)(Ru,{msg:h}),(0,A.jsx)(Mu,{open:p,title:`Regenerate backup codes?`,body:`This invalidates your existing codes. Enter your current password to confirm.`,confirmText:`Regenerate`,danger:!0,requirePassword:!0,passwordPlaceholder:`Current password`,busy:S.isPending,onConfirm:e=>e&&S.mutate(e),onCancel:()=>m(!1)}),d&&(0,A.jsx)(Vu,{codes:d,onClose:()=>f(null)})]})}function Uu({s:e,isCurrent:t,onRevoke:n}){return(0,A.jsxs)(`div`,{className:`flex items-center justify-between gap-3 rounded-md border-2 px-3 py-2 `+(t?`border-primary bg-primary/5`:`border-border bg-muted/40`),"data-testid":`session-row-`+e.id,children:[(0,A.jsxs)(`div`,{className:`min-w-0`,children:[(0,A.jsxs)(`div`,{className:`text-sm font-medium truncate`,children:[e.device_label||`Unknown device`,t&&(0,A.jsx)(`span`,{className:`ml-2 text-xs text-primary font-medium`,children:`(this device)`})]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[e.ip_address||`—`,` · Created `,new Date(e.created_at).toLocaleDateString(),` · Active `,zu(e.last_activity)]})]}),!t&&(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:n,"data-testid":`btn-revoke-session-`+e.id,children:`Revoke`})]})}function Wu(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(null),{data:a,isLoading:o,error:s}=j({queryKey:[`sessions`],queryFn:()=>F.get(`/api/sessions`)}),c=M({mutationFn:e=>F.delete(`/api/sessions/`+e),onSuccess:()=>{i({text:`Session revoked`,kind:`info`}),e.invalidateQueries({queryKey:[`sessions`]})},onError:e=>i({text:e.message,kind:`err`})}),l=M({mutationFn:()=>F.delete(`/api/sessions`),onSuccess:t=>{i({text:`All other sessions revoked (`+(t.revoked||0)+` removed)`,kind:`info`}),e.invalidateQueries({queryKey:[`sessions`]})},onError:e=>i({text:e.message,kind:`err`})}),u=c.isPending||l.isPending;return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`sessions-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Active Sessions`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Devices where you are currently logged in. Revoke any session to immediately log that device out.`}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:()=>n({kind:`all`}),"data-testid":`btn-revoke-all-sessions`,children:`Revoke All Other Sessions`}),o&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading sessions…`}),s&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:`Could not load sessions.`}),(0,A.jsxs)(`div`,{className:`space-y-2`,children:[a?.sessions.map(e=>(0,A.jsx)(Uu,{s:e,isCurrent:e.id===a.currentSessionId,onRevoke:()=>n({kind:`one`,id:e.id})},e.id)),a&&a.sessions.length===0&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No active sessions found.`})]}),(0,A.jsx)(Ru,{msg:r}),(0,A.jsx)(Mu,{open:t?.kind===`one`,title:`Revoke this session?`,body:`That device will be logged out.`,confirmText:`Revoke`,danger:!0,busy:u,onConfirm:()=>{t?.kind===`one`&&c.mutate(t.id),n(null)},onCancel:()=>n(null)}),(0,A.jsx)(Mu,{open:t?.kind===`all`,title:`Revoke all other sessions?`,body:`All other devices will be logged out.`,confirmText:`Revoke All`,danger:!0,busy:u,onConfirm:()=>{l.mutate(),n(null)},onCancel:()=>n(null)})]})}function Z({user:e}){let t=et(),n=!!e.nextcloud_url,[r,i]=(0,_.useState)(e.nextcloud_url||``),[a,o]=(0,_.useState)(e.nextcloud_user||``),[s,c]=(0,_.useState)(``),[l,u]=(0,_.useState)(e.webdav_learning_path||``),[d,f]=(0,_.useState)(!1),[p,m]=(0,_.useState)(null),h=M({mutationFn:e=>F.post(`/api/nextcloud/connect`,e),onSuccess:e=>{m({text:e.message||`Connected`,kind:`ok`}),c(``),t.invalidateQueries({queryKey:[`auth-me`]})},onError:e=>m({text:e.message||`Connection failed`,kind:`err`})}),g=M({mutationFn:()=>F.post(`/api/nextcloud/disconnect`,{}),onSuccess:()=>{m({text:`Disconnected`,kind:`info`}),c(``),t.invalidateQueries({queryKey:[`auth-me`]})},onError:e=>m({text:e.message,kind:`err`})}),v=M({mutationFn:e=>F.post(`/api/user/webdav-path`,{path:e}),onSuccess:()=>{m({text:`Path saved`,kind:`ok`}),t.invalidateQueries({queryKey:[`auth-me`]})},onError:e=>m({text:e.message||`Failed to save`,kind:`err`})});function y(e){e.preventDefault(),m(null);let t=r.trim().replace(/\/+$/,``),n=a.trim(),i=s.trim();if(!t||!n||!i)return m({text:`Fill all Nextcloud fields`,kind:`err`});h.mutate({nextcloudUrl:t,username:n,appPassword:i})}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`nextcloud-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Nextcloud Integration`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Export generated documents to your Nextcloud.`}),(0,A.jsx)(`div`,{className:`text-sm`,"data-testid":`nc-status`,children:n?(0,A.jsxs)(A.Fragment,{children:[`✅ Connected to `,(0,A.jsx)(`strong`,{children:e.nextcloud_url}),` as `,e.nextcloud_user]}):(0,A.jsx)(A.Fragment,{children:`Not connected`})}),(0,A.jsxs)(`form`,{onSubmit:y,className:`space-y-2 max-w-md`,children:[(0,A.jsxs)(`label`,{className:`block text-sm`,children:[(0,A.jsx)(`span`,{className:`block text-xs font-semibold text-muted-foreground mb-1`,children:`Nextcloud URL`}),(0,A.jsx)(`input`,{type:`url`,className:Pu,value:r,onChange:e=>i(e.target.value),placeholder:`https://cloud.example.com`,"data-testid":`nc-url`})]}),(0,A.jsxs)(`label`,{className:`block text-sm`,children:[(0,A.jsx)(`span`,{className:`block text-xs font-semibold text-muted-foreground mb-1`,children:`Username`}),(0,A.jsx)(`input`,{type:`text`,className:Pu,value:a,onChange:e=>o(e.target.value),placeholder:`your-username`,"data-testid":`nc-user`})]}),(0,A.jsxs)(`label`,{className:`block text-sm`,children:[(0,A.jsx)(`span`,{className:`block text-xs font-semibold text-muted-foreground mb-1`,children:`App Password`}),(0,A.jsx)(`input`,{type:`password`,className:Pu,value:s,onChange:e=>c(e.target.value),placeholder:`Generate in Nextcloud → Settings → Security`,"data-testid":`nc-pass`}),(0,A.jsx)(`span`,{className:`block text-xs text-muted-foreground mt-1`,children:`Go to Nextcloud → Settings → Security → Create new app password`})]}),(0,A.jsxs)(`div`,{className:`flex gap-2 flex-wrap`,children:[(0,A.jsx)(`button`,{type:`submit`,className:Fu,disabled:h.isPending,"data-testid":`btn-nc-connect`,children:h.isPending?`Connecting…`:n?`Reconnect`:`Connect`}),n&&(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive`,onClick:()=>f(!0),"data-testid":`btn-nc-disconnect`,children:`Disconnect`})]})]}),n&&(0,A.jsx)(`div`,{className:`border-t border-border pt-3 space-y-2 max-w-md`,children:(0,A.jsxs)(`label`,{className:`block text-sm`,children:[(0,A.jsx)(`span`,{className:`block text-xs font-semibold text-muted-foreground mb-1`,children:`Learning Hub — Default Browse Path`}),(0,A.jsxs)(`span`,{className:`block text-xs text-muted-foreground mb-2`,children:[`Folder opened first when picking files for AI content generation (e.g. `,(0,A.jsx)(`code`,{children:`/Medical-Resources`}),`)`]}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`input`,{type:`text`,className:Pu,value:l,onChange:e=>u(e.target.value),placeholder:`/Medical-Resources`,"data-testid":`nc-webdav-path`}),(0,A.jsx)(`button`,{type:`button`,className:Fu,disabled:v.isPending,onClick:()=>v.mutate(l.trim()),"data-testid":`btn-nc-save-path`,children:v.isPending?`Saving…`:`Save Path`})]})]})}),(0,A.jsx)(Ru,{msg:p}),(0,A.jsx)(Mu,{open:d,title:`Disconnect Nextcloud?`,body:`Future exports will fail until you reconnect. Your stored credentials will be cleared.`,confirmText:`Disconnect`,danger:!0,busy:g.isPending,onConfirm:()=>{g.mutate(),f(!1)},onCancel:()=>f(!1)})]})}function Gu(e){return e<1024?e+` B`:e<1048576?Math.round(e/1024)+` KB`:(e/1048576).toFixed(1)+` MB`}function Ku({doc:e,onDownload:t,onDelete:n,downloading:r}){return(0,A.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-2 rounded-md bg-muted/40 border border-border`,"data-testid":`doc-row-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,A.jsx)(`div`,{className:`text-sm font-medium truncate`,children:e.filename}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[Gu(e.size_bytes),` · `,new Date(e.created_at).toLocaleDateString(),e.description?` · `+e.description:``]})]}),(0,A.jsx)(`button`,{type:`button`,className:Fu+` text-xs`,disabled:r,onClick:t,"data-testid":`btn-doc-download-`+e.id,children:r?`…`:`Download`}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:n,"data-testid":`btn-doc-delete-`+e.id,children:`Delete`})]})}function qu(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(``),[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(null),{data:l,isLoading:u,error:d}=j({queryKey:[`documents`],queryFn:()=>F.get(`/api/documents`)}),f=M({mutationFn:async e=>{let t=new FormData;t.append(`file`,e.file),t.append(`description`,e.description);let n=await fetch(`/api/documents/upload`,{method:`POST`,credentials:`include`,body:t}),r=(n.headers.get(`content-type`)||``).includes(`application/json`)?await n.json():null;if(!n.ok||r&&r.success===!1)throw Error(r&&r.error||n.statusText);return r},onSuccess:t=>{o({text:`Document uploaded: `+t.filename,kind:`ok`}),n(null),i(``),e.invalidateQueries({queryKey:[`documents`]})},onError:e=>o({text:`Upload failed: `+e.message,kind:`err`})}),p=M({mutationFn:e=>F.delete(`/api/documents/`+e),onSuccess:()=>{o({text:`Document deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`documents`]})},onError:e=>o({text:e.message||`Delete failed`,kind:`err`})}),m=M({mutationFn:e=>F.get(`/api/documents/`+e+`/download`),onSuccess:e=>{e.url?window.open(e.url,`_blank`,`noopener,noreferrer`):o({text:`Download failed`,kind:`err`})},onError:e=>o({text:e.message||`Download failed`,kind:`err`})});function h(e){if(e.preventDefault(),o(null),!t)return o({text:`Select a file first`,kind:`err`});f.mutate({file:t,description:r})}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`documents-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Documents`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Upload and manage documents via S3 storage (PDF, images, Word docs, text files). Max 10 MB per file.`}),u&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),d&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:`Failed to load documents.`}),l&&!l.s3_configured&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground italic`,children:`S3 storage not configured. Set S3_BUCKET in server environment.`}),l&&l.s3_configured&&(0,A.jsxs)(A.Fragment,{children:[(0,A.jsxs)(`form`,{onSubmit:h,className:`flex flex-wrap gap-2 items-center`,"data-testid":`doc-upload-area`,children:[(0,A.jsx)(`input`,{type:`file`,className:`text-sm`,accept:`.pdf,.jpg,.jpeg,.png,.gif,.doc,.docx,.txt,.csv`,onChange:e=>n(e.target.files?.[0]??null),"data-testid":`doc-file-input`}),(0,A.jsx)(`input`,{type:`text`,className:Pu+` max-w-xs`,placeholder:`Description (optional)`,value:r,onChange:e=>i(e.target.value),"data-testid":`doc-description`}),(0,A.jsx)(`button`,{type:`submit`,className:Fu,disabled:!t||f.isPending,"data-testid":`btn-doc-upload`,children:f.isPending?`Uploading…`:`Upload`})]}),(0,A.jsx)(`div`,{className:`space-y-2`,children:l.documents.length===0?(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No documents uploaded yet.`}):l.documents.map(e=>(0,A.jsx)(Ku,{doc:e,onDownload:()=>m.mutate(e.id),onDelete:()=>c(e),downloading:m.isPending&&m.variables===e.id},e.id))})]}),(0,A.jsx)(Ru,{msg:a}),(0,A.jsx)(Mu,{open:!!s,title:`Delete this document permanently?`,body:s?.filename,confirmText:`Delete`,danger:!0,busy:p.isPending,onConfirm:()=>{s&&p.mutate(s.id),c(null)},onCancel:()=>c(null)})]})}function Ju(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(``),[a,o]=(0,_.useState)(``),[s,c]=(0,_.useState)(!1),[l,u]=(0,_.useState)(!1),{data:d}=j({queryKey:[`voice-options`],queryFn:()=>F.get(`/api/user/preferences/options`)}),{data:f}=j({queryKey:[`voice-prefs`],queryFn:()=>F.get(`/api/user/preferences`)});!l&&f&&(i(f.stt_model||``),o(f.tts_voice||``),u(!0));let p=M({mutationFn:e=>F.post(`/api/user/preferences`,e),onSuccess:()=>{n({text:`Voice preferences saved`,kind:`ok`}),e.invalidateQueries({queryKey:[`voice-prefs`]})},onError:e=>n({text:e.message||`Save failed`,kind:`err`})});async function m(){n(null),c(!0);try{await F.post(`/api/user/preferences`,{tts_voice:a||null}),e.invalidateQueries({queryKey:[`voice-prefs`]});let t=`Hello, this is a preview of the `+(a||`server default`)+` voice. This is how your read-aloud feature will sound.`,n=await fetch(`/api/text-to-speech`,{method:`POST`,credentials:`include`,headers:{"Content-Type":`application/json`},body:JSON.stringify({text:t})});if(!n.ok)throw Error(`Preview failed`);let r=await n.blob(),i=URL.createObjectURL(r),o=new Audio(i);o.onended=()=>URL.revokeObjectURL(i),await o.play()}catch(e){n({text:`Preview failed: `+e.message,kind:`err`})}finally{c(!1)}}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`voice-preferences-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Voice Preferences`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Customize your speech-to-text model and text-to-speech voice. These settings apply to all recording and read-aloud features.`}),(0,A.jsxs)(`div`,{className:`space-y-2 max-w-md`,children:[(0,A.jsxs)(`label`,{className:`block text-sm`,children:[(0,A.jsx)(`span`,{className:`block text-xs font-semibold text-muted-foreground mb-1`,children:`Speech-to-Text Model (Transcription)`}),(0,A.jsxs)(`select`,{className:Pu,value:r,onChange:e=>i(e.target.value),"data-testid":`stt-model-select`,children:[(0,A.jsxs)(`option`,{value:``,children:[`Server default`,d?` (`+d.sttProvider+`)`:``]}),d?.sttModels.map(e=>(0,A.jsx)(`option`,{value:e.value,children:e.label},e.value))]})]}),(0,A.jsxs)(`label`,{className:`block text-sm`,children:[(0,A.jsx)(`span`,{className:`block text-xs font-semibold text-muted-foreground mb-1`,children:`Text-to-Speech Voice (Read Aloud)`}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsxs)(`select`,{className:Pu,value:a,onChange:e=>o(e.target.value),"data-testid":`tts-voice-select`,children:[(0,A.jsxs)(`option`,{value:``,children:[`Server default`,d?` (`+d.ttsProvider+`)`:``]}),d?.ttsVoices.map(e=>(0,A.jsx)(`option`,{value:e.value,children:e.label},e.value))]}),(0,A.jsx)(`button`,{type:`button`,className:Iu,disabled:s,onClick:m,"data-testid":`btn-preview-voice`,children:s?`Loading…`:`Preview`})]})]}),(0,A.jsx)(`button`,{type:`button`,className:Fu,disabled:p.isPending,onClick:()=>p.mutate({stt_model:r||null,tts_voice:a||null}),"data-testid":`btn-save-voice-prefs`,children:p.isPending?`Saving…`:`Save Voice Preferences`})]}),(0,A.jsx)(Ru,{msg:t})]})}var Yu=`ped_browser_whisper_enabled`,Xu=`ped_browser_whisper_model`,Zu=[{value:`Xenova/whisper-tiny.en`,label:`Tiny (~39MB) — fastest, ~2-3s`},{value:`Xenova/whisper-base.en`,label:`Base (~74MB) — balanced, ~3-5s`},{value:`Xenova/whisper-small.en`,label:`Small (~244MB) — best quality, ~6-10s`}];function Qu(e,t=``){try{return window.localStorage.getItem(e)??t}catch{return t}}function $u(e,t){try{window.localStorage.setItem(e,t)}catch{}}function ed(){let[e,t]=(0,_.useState)(Qu(Yu)===`true`),[n,r]=(0,_.useState)(Qu(Xu)||Zu[0].value);function i(e){t(e),$u(Yu,String(e))}function a(e){r(e),$u(Xu,e)}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`browser-whisper-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Browser Transcription (Local Whisper)`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Transcribes audio entirely in your browser — no audio sent to any server. Powered by OpenAI Whisper running in WebAssembly. Model is downloaded once and cached locally.`}),(0,A.jsxs)(`div`,{className:`flex items-center gap-3 flex-wrap`,children:[(0,A.jsx)(`label`,{className:`text-sm font-medium`,children:`Enable browser transcription:`}),(0,A.jsxs)(`label`,{className:`flex items-center gap-2 cursor-pointer`,children:[(0,A.jsx)(`input`,{type:`checkbox`,className:`accent-primary size-4`,checked:e,onChange:e=>i(e.target.checked),"data-testid":`browser-whisper-enabled`}),(0,A.jsx)(`span`,{className:`text-sm`,"data-testid":`browser-whisper-status`,children:e?`On — audio stays on device`:`Off`})]})]}),(0,A.jsxs)(`div`,{className:`flex items-center gap-3 flex-wrap`,children:[(0,A.jsx)(`label`,{className:`text-sm font-medium`,children:`Model:`}),(0,A.jsx)(`select`,{className:Pu+` max-w-md`,value:n,onChange:e=>a(e.target.value),"data-testid":`browser-whisper-model`,children:Zu.map(e=>(0,A.jsx)(`option`,{value:e.value,children:e.label},e.value))})]}),(0,A.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`When enabled, overrides server transcription. Falls back to server if browser transcription fails. The actual WASM download runs from the recording components — pre-download will light up when those port to React.`})]})}var td=`ped_web_speech_enabled`;function nd(){let[e,t]=(0,_.useState)(Qu(td)===`true`),[n,r]=(0,_.useState)(!1),i=typeof window<`u`&&(`webkitSpeechRecognition`in window||`SpeechRecognition`in window);function a(){$u(Yu,`false`),t(!0),$u(td,`true`)}function o(){t(!1),$u(td,`false`)}return(0,A.jsxs)(`section`,{className:Nu+` border-l-4 border-l-orange-500`,"data-testid":`web-speech-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Real-Time Streaming Transcription`}),(0,A.jsxs)(`div`,{className:`bg-orange-50 dark:bg-orange-950/30 p-3 rounded-md text-sm text-orange-900 dark:text-orange-100`,children:[(0,A.jsx)(`strong`,{children:`Privacy Warning:`}),` Uses your browser's built-in speech recognition, which `,(0,A.jsx)(`strong`,{children:`may send audio to cloud servers`}),` (Chrome/Edge send to Google). Only enable if you accept this trade-off for real-time transcription.`]}),(0,A.jsxs)(`p`,{className:`text-sm text-muted-foreground`,children:[`See words appear as you speak (streaming). Overrides browser and server transcription when enabled. `,(0,A.jsx)(`strong`,{children:`Not HIPAA-compliant`}),` in most browsers.`]}),(0,A.jsxs)(`div`,{className:`flex items-center gap-3 flex-wrap`,children:[(0,A.jsx)(`label`,{className:`text-sm font-medium`,children:`Enable real-time streaming:`}),(0,A.jsxs)(`label`,{className:`flex items-center gap-2 cursor-pointer`,children:[(0,A.jsx)(`input`,{type:`checkbox`,className:`accent-orange-500 size-4`,checked:e,disabled:!i,onChange:e=>{e.target.checked?r(!0):o()},"data-testid":`web-speech-enabled`}),(0,A.jsx)(`span`,{className:`text-sm`,"data-testid":`web-speech-status`,children:i?e?`On — real-time streaming`:`Off`:`Not supported in this browser`})]})]}),i&&(0,A.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Streaming integration lights up when the recording components port to React.`}),(0,A.jsx)(Mu,{open:n,title:`Enable real-time streaming transcription?`,body:`Your browser's speech recognition may send audio to cloud servers (e.g. Google). This is NOT HIPAA-compliant. Only enable if you understand and accept this privacy trade-off.`,confirmText:`Enable`,danger:!0,onConfirm:()=>{a(),r(!1)},onCancel:()=>r(!1)})]})}var rd=[{value:`physical_exam`,label:`Physical Exam Template`},{value:`ros`,label:`Review of Systems Template`},{value:`encounter_format`,label:`Encounter Note Format`},{value:`family_history`,label:`Family History Format`},{value:`assessment_plan`,label:`Assessment & Plan Format`},{value:`template_soap`,label:`SOAP Note Template`},{value:`template_hpi`,label:`HPI Template`},{value:`template_wellvisit`,label:`Well Visit Template`},{value:`template_sickvisit`,label:`Sick Visit Template`},{value:`custom`,label:`Custom`}],id=Object.fromEntries(rd.map(e=>[e.value,e.label.replace(/ Template$| Format$/,``)]));function ad(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(null),[a,o]=(0,_.useState)(rd[0].value),[s,c]=(0,_.useState)(``),[l,u]=(0,_.useState)(``),[d,f]=(0,_.useState)(null),{data:p}=j({queryKey:[`memories`],queryFn:()=>F.get(`/api/memories`)}),m=(p?.memories||[]).filter(e=>!e.category.startsWith(`correction_`)),h=M({mutationFn:e=>{let{id:t,...n}=e;return t?F.put(`/api/memories/`+t,n):F.post(`/api/memories`,n)},onSuccess:()=>{n({text:r?`Template updated`:`Template saved`,kind:`ok`}),i(null),c(``),u(``),e.invalidateQueries({queryKey:[`memories`]})},onError:e=>n({text:e.message||`Save failed`,kind:`err`})}),g=M({mutationFn:e=>F.delete(`/api/memories/`+e),onSuccess:()=>{n({text:`Template deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`memories`]})},onError:e=>n({text:e.message||`Delete failed`,kind:`err`})});function v(e){i(e.id),o(e.category),c(e.name),u(e.content)}function y(){i(null),c(``),u(``)}function b(e){if(e.preventDefault(),n(null),!s.trim())return n({text:`Enter a template name`,kind:`err`});if(!l.trim())return n({text:`Enter template content`,kind:`err`});h.mutate({id:r??void 0,category:a,name:s.trim(),content:l.trim()})}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`templates-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`My Templates`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Save reusable templates for physical exam, ROS, encounter format, etc. The AI will use these when generating notes.`}),(0,A.jsxs)(`form`,{onSubmit:b,className:`space-y-2`,children:[(0,A.jsxs)(`div`,{className:`flex gap-2 flex-wrap items-center`,children:[(0,A.jsx)(`select`,{className:Pu+` max-w-xs`,value:a,onChange:e=>o(e.target.value),"data-testid":`mem-category`,children:rd.map(e=>(0,A.jsx)(`option`,{value:e.value,children:e.label},e.value))}),(0,A.jsx)(`input`,{type:`text`,className:Pu+` flex-1 min-w-[150px]`,placeholder:`Template name (e.g. Normal PE)`,value:s,onChange:e=>c(e.target.value),"data-testid":`mem-name`})]}),(0,A.jsx)(`textarea`,{rows:5,className:Pu+` resize-y`,placeholder:`Paste your template here. Example: HEENT: Normocephalic, atraumatic. Eyes: PERRL…`,value:l,onChange:e=>u(e.target.value),"data-testid":`mem-content`}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`submit`,className:Fu,disabled:h.isPending,"data-testid":`btn-mem-save`,children:h.isPending?`Saving…`:r?`Update Template`:`Add Template`}),r!==null&&(0,A.jsx)(`button`,{type:`button`,className:Iu,onClick:y,children:`Cancel`})]})]}),(0,A.jsx)(`div`,{className:`space-y-2`,children:m.length===0?(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No templates saved yet. Add one above.`}):m.map(e=>(0,A.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-2 rounded-md bg-muted/40 border border-border`,"data-testid":`mem-row-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-2 text-sm`,children:[(0,A.jsx)(`span`,{className:`font-medium`,children:e.name}),(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground uppercase tracking-wide`,children:id[e.category]||e.category})]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground truncate`,children:[(e.content||``).slice(0,100).replace(/\n/g,` `),e.content&&e.content.length>100?`…`:``]})]}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-xs`,onClick:()=>v(e),"data-testid":`btn-mem-edit-`+e.id,children:`Edit`}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:()=>f(e),"data-testid":`btn-mem-delete-`+e.id,children:`Delete`})]},e.id))}),(0,A.jsx)(Ru,{msg:t}),(0,A.jsx)(Mu,{open:!!d,title:`Delete template "`+(d?.name||``)+`"?`,body:`This cannot be undone.`,confirmText:`Delete`,danger:!0,busy:g.isPending,onConfirm:()=>{d&&g.mutate(d.id),f(null)},onCancel:()=>f(null)})]})}var od={correction_soap:`SOAP Correction`,correction_hpi:`HPI Correction`,correction_encounter:`Encounter Correction`,correction_wellvisit:`Well Visit Correction`,correction_sickvisit:`Sick Visit Correction`};function sd(e){let t=e.indexOf(` -CORRECTED TO: `);return t===-1?{original:e.trim(),corrected:``}:{original:e.substring(0,t).replace(/^ORIGINAL:\s*/i,``).trim(),corrected:e.substring(t+15).trim()}}function cd(){let e=et(),[t,n]=(0,_.useState)({}),[r,i]=(0,_.useState)(null),[a,o]=(0,_.useState)(null),{data:s}=j({queryKey:[`memories`],queryFn:()=>F.get(`/api/memories`)}),c=(s?.memories||[]).filter(e=>e.category.startsWith(`correction_`)),l=M({mutationFn:e=>F.delete(`/api/memories/`+e),onSuccess:()=>{i({text:`Correction deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`memories`]})},onError:e=>i({text:e.message||`Delete failed`,kind:`err`})});return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`corrections-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`AI Learning (Corrections)`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`The AI automatically learns from your edits. When you modify AI-generated text and save, corrections are stored here and applied to future notes. Latest 20 per section.`}),(0,A.jsx)(`div`,{className:`space-y-2`,children:c.length===0?(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No corrections yet. Edit AI-generated notes and save to start learning.`}):c.map(e=>{let r=t[e.id],i=sd(e.content||``),a=e.created_at?new Date(e.created_at).toLocaleDateString():``;return(0,A.jsxs)(`div`,{className:`rounded-md bg-muted/40 border border-border overflow-hidden`,"data-testid":`corr-row-`+e.id,children:[(0,A.jsxs)(`button`,{type:`button`,className:`w-full flex items-center gap-2 px-3 py-2 text-left`,onClick:()=>n({...t,[e.id]:!r}),children:[(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:r?`▾`:`▸`}),(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground uppercase tracking-wide`,children:od[e.category]||e.category}),(0,A.jsx)(`span`,{className:`flex-1 text-sm truncate`,children:e.name}),(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:a}),(0,A.jsx)(`span`,{role:`button`,className:`text-destructive text-xs px-2`,onClick:t=>{t.stopPropagation(),o(e)},"data-testid":`btn-corr-delete-`+e.id,children:`Delete`})]}),r&&(0,A.jsxs)(`div`,{className:`px-3 py-2 text-sm space-y-2 border-t border-border`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs font-semibold uppercase text-destructive mb-1`,children:`Original (AI generated):`}),(0,A.jsx)(`div`,{className:`whitespace-pre-wrap text-muted-foreground`,children:i.original})]}),i.corrected&&(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs font-semibold uppercase text-green-600 mb-1`,children:`Corrected to:`}),(0,A.jsx)(`div`,{className:`whitespace-pre-wrap`,children:i.corrected})]})]})]},e.id)})}),(0,A.jsx)(Ru,{msg:r}),(0,A.jsx)(Mu,{open:!!a,title:`Delete this correction?`,body:`The AI will no longer apply this edit in future notes.`,confirmText:`Delete`,danger:!0,busy:l.isPending,onConfirm:()=>{a&&l.mutate(a.id),o(null)},onCancel:()=>o(null)})]})}function ld(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(null),{data:a,isLoading:o}=j({queryKey:[`audio-backups`],queryFn:()=>F.get(`/api/audio-backups`)}),s=M({mutationFn:e=>F.delete(`/api/audio-backups/`+e),onSuccess:()=>{n({text:`Backup deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`audio-backups`]})},onError:e=>n({text:e.message,kind:`err`})});function c(e){window.open(`/api/audio-backups/`+e+`/audio`,`_blank`,`noopener,noreferrer`)}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`audio-backups-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Audio Backups`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Recordings are automatically backed up on the server and kept for 24 hours. Retry flow ports with the recording components.`}),o&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),(0,A.jsxs)(`div`,{className:`space-y-2`,children:[a&&a.backups.length===0&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No audio backups.`}),a?.backups.map(e=>{let t=Math.round(e.size_bytes/1024),n=e.compressed_bytes?` (`+Math.round(e.compressed_bytes/1024)+` KB compressed)`:``;return(0,A.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-2 rounded-md bg-muted/40 border border-border`,"data-testid":`audio-backup-row-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,A.jsxs)(`div`,{className:`text-sm font-medium`,children:[e.module,` recording`]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[new Date(e.created_at).toLocaleString(),` · `,t,` KB`,n,` · `,zu(e.created_at)]})]}),(0,A.jsx)(`button`,{type:`button`,className:Fu+` text-xs`,onClick:()=>c(e.id),children:`Play`}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:()=>i(e),"data-testid":`btn-audio-delete-`+e.id,children:`Delete`})]},e.id)})]}),(0,A.jsx)(Ru,{msg:t}),(0,A.jsx)(Mu,{open:!!r,title:`Delete this audio backup?`,body:`This cannot be undone.`,confirmText:`Delete`,danger:!0,busy:s.isPending,onConfirm:()=>{r&&s.mutate(r.id),i(null)},onCancel:()=>i(null)})]})}function ud(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(null),{data:a,isLoading:o}=j({queryKey:[`saved-encounters`],queryFn:()=>F.get(`/api/encounters/saved`)}),s=M({mutationFn:e=>F.delete(`/api/encounters/saved/`+e),onSuccess:()=>{n({text:`Deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`saved-encounters`]})},onError:e=>n({text:e.message,kind:`err`})});return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`saved-encounters-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Saved Encounters`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Encounters are automatically deleted after 7 days per site policy. Resume action ports with the encounter components.`}),o&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),(0,A.jsxs)(`div`,{className:`space-y-2`,children:[a&&a.encounters.length===0&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No saved encounters.`}),a?.encounters.map(e=>{let t=new Date(e.updated_at).toLocaleDateString(),n=new Date(e.expires_at).toLocaleDateString(),r=(e.transcript_preview||``).slice(0,80);return(0,A.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-2 rounded-md bg-muted/40 border border-border`,"data-testid":`enc-row-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-2 text-sm`,children:[(0,A.jsx)(`span`,{className:`font-medium truncate`,children:e.label||`Untitled`}),(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground uppercase tracking-wide`,children:e.enc_type})]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground truncate`,children:[t,` · expires `,n,r?` · `+r+`…`:``]})]}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:()=>i(e),"data-testid":`btn-enc-delete-`+e.id,children:`Delete`})]},e.id)})]}),(0,A.jsx)(Ru,{msg:t}),(0,A.jsx)(Mu,{open:!!r,title:`Delete this saved encounter?`,body:r?.label||void 0,confirmText:`Delete`,danger:!0,busy:s.isPending,onConfirm:()=>{r&&s.mutate(r.id),i(null)},onCancel:()=>i(null)})]})}function dd(){return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`compliance-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Compliance & Usage`}),(0,A.jsxs)(`div`,{className:`text-sm space-y-2`,children:[(0,A.jsxs)(`p`,{children:[(0,A.jsx)(`strong`,{children:`AWS Bedrock`}),` is available with a Business Associate Agreement (BAA) for HIPAA-eligible workloads.`]}),(0,A.jsxs)(`ul`,{className:`list-disc pl-5 space-y-1 text-muted-foreground`,children:[(0,A.jsx)(`li`,{children:`All connections use HTTPS/TLS encryption`}),(0,A.jsx)(`li`,{children:`Authentication with optional 2FA`}),(0,A.jsx)(`li`,{children:`No patient data stored on server beyond session`}),(0,A.jsx)(`li`,{children:`AWS Bedrock supports BAA for HIPAA compliance`}),(0,A.jsx)(`li`,{children:`Azure OpenAI supports BAA for HIPAA compliance`})]}),(0,A.jsxs)(`p`,{children:[(0,A.jsx)(`strong`,{children:`Important:`}),` Check with your institution's guidelines and policies before use. This tool is not intended for production clinical use without proper organizational authorization and provider BAAs in place. Use with caution.`]})]})]})}function fd(){let{data:e,isLoading:t,error:n}=j({queryKey:[`auth-me`],queryFn:()=>F.get(`/api/auth/me`)}),r=e?.user.canLocalAuth!==!1;return(0,A.jsxs)(`div`,{className:`max-w-3xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Settings`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Account security, integrations, and personal templates. More sub-sections port over in follow-up commits.`})]}),t&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),n&&(0,A.jsxs)(`div`,{className:`text-sm text-destructive`,children:[`Could not load your account: `,n.message]}),e&&r&&(0,A.jsxs)(A.Fragment,{children:[(0,A.jsx)(Bu,{}),(0,A.jsx)(Hu,{user:e.user}),(0,A.jsx)(Wu,{})]}),e&&!r&&(0,A.jsxs)(`section`,{className:Nu,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Account managed by single sign-on`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Your password and two-factor authentication are managed by your identity provider.`})]}),e&&(0,A.jsx)(Z,{user:e.user}),e&&(0,A.jsx)(qu,{}),e&&(0,A.jsx)(Ju,{}),e&&(0,A.jsx)(ed,{}),e&&(0,A.jsx)(nd,{}),e&&(0,A.jsx)(ad,{}),e&&(0,A.jsx)(cd,{}),e&&(0,A.jsx)(ld,{}),e&&(0,A.jsx)(ud,{}),(0,A.jsx)(dd,{})]})}var pd=`rounded-lg border border-border bg-card p-5 space-y-3`,md=`px-3 py-1 rounded-full text-xs font-medium border transition-colors cursor-pointer`,hd=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`,gd=`rounded-md bg-primary text-primary-foreground px-3 py-2 text-sm font-medium disabled:opacity-50`,_d=`rounded-md border border-border px-3 py-2 text-sm disabled:opacity-50`;function vd(e){switch(e){case`quiz`:return`Quiz`;case`pearl`:return`Pearl`;case`presentation`:return`Slides`;default:return`Article`}}function yd({row:e,onOpen:t}){return(0,A.jsxs)(`button`,{type:`button`,onClick:t,className:`w-full text-left rounded-lg border border-border bg-card hover:bg-muted/60 p-4 transition-colors`,"data-testid":`lh-feed-item-`+e.slug,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-2 text-xs text-muted-foreground uppercase tracking-wide mb-1`,children:[(0,A.jsx)(`span`,{className:`font-semibold`,children:vd(e.content_type)}),e.category_name&&(0,A.jsxs)(`span`,{children:[`· `,e.category_name]}),e.question_count?(0,A.jsxs)(`span`,{children:[`· `,e.question_count,` Q`]}):null]}),(0,A.jsx)(`div`,{className:`text-sm font-semibold`,children:e.title}),e.subject&&(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground mt-0.5 truncate`,children:e.subject})]})}function Q({filter:e,query:t,onOpen:n}){let{data:r,isLoading:i,error:a}=j({queryKey:t?[`learning-search`,t]:e?[`learning-category`,e]:[`learning-feed`],queryFn:()=>t?F.get(`/api/learning/search?q=`+encodeURIComponent(t)):e?F.get(`/api/learning/category/`+encodeURIComponent(e)):F.get(`/api/learning/feed?limit=30`)});if(i)return(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`});if(a)return(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:a.message});let o=r?.content||[];return o.length===0?(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground italic py-4`,children:`No content found.`}):(0,A.jsx)(`div`,{className:`grid grid-cols-1 sm:grid-cols-2 gap-3`,"data-testid":`lh-feed`,children:o.map(e=>(0,A.jsx)(yd,{row:e,onOpen:()=>n(e.slug)},e.id))})}function bd(e){let t={};for(let n of e)t[n.id]={optionIds:new Set};return t}function xd({content:e,onReset:t}){let n=et(),[r,i]=(0,_.useState)(()=>bd(e.questions)),[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(null),l=M({mutationFn:e=>F.post(`/api/learning/submit-quiz`,e),onSuccess:t=>{o(t),n.invalidateQueries({queryKey:[`learning-content`,e.slug]})},onError:e=>c(e.message||`Submit failed`)});function u(t){t.preventDefault(),c(null);let n=e.questions.map(e=>{let t=r[e.id];return e.question_type===`multi`?{questionId:e.id,optionIds:Array.from(t?.optionIds||[])}:{questionId:e.id,optionId:t?.optionId??null}});l.mutate({contentId:e.id,answers:n})}function d(e,t){i(n=>({...n,[e.id]:{optionId:t,optionIds:new Set}}))}function f(e,t){i(n=>{let r=new Set(n[e.id]?.optionIds||[]);return r.has(t)?r.delete(t):r.add(t),{...n,[e.id]:{optionIds:r}}})}if(a){let n=a.percentage>=80?`bg-green-600`:a.percentage>=50?`bg-amber-500`:`bg-destructive`;return(0,A.jsxs)(`section`,{className:pd,"data-testid":`lh-quiz-results`,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Results`}),(0,A.jsxs)(`span`,{className:`px-2 py-0.5 rounded text-xs font-semibold text-white `+n,"data-testid":`lh-quiz-score`,children:[a.score,`/`,a.total,` (`,a.percentage,`%)`]})]}),(0,A.jsx)(`div`,{className:`space-y-3`,children:a.results.map((e,t)=>(0,A.jsxs)(`div`,{className:`rounded-md border border-border p-3 bg-muted/30`,children:[(0,A.jsxs)(`div`,{className:`text-sm font-medium`,children:[(0,A.jsx)(`span`,{className:e.isCorrect?`text-green-600`:`text-destructive`,children:e.isCorrect?`✓`:`✗`}),` `,`Q`,t+1,`: `,e.questionText]}),!e.isCorrect&&e.correctOptionText&&(0,A.jsxs)(`div`,{className:`text-xs text-green-700 mt-1`,children:[(0,A.jsx)(`strong`,{children:`Correct:`}),` `,e.correctOptionText]}),!e.isCorrect&&e.selectedExplanation&&(0,A.jsxs)(`div`,{className:`text-xs text-destructive mt-1`,children:[(0,A.jsx)(`strong`,{children:`Why incorrect:`}),` `,e.selectedExplanation]}),e.generalExplanation&&(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground mt-1`,children:e.generalExplanation})]},e.questionId))}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,className:_d,onClick:()=>{o(null),i(bd(e.questions))},children:`Retake`}),(0,A.jsx)(`button`,{type:`button`,className:gd,onClick:t,children:`Back to Feed`})]})]})}return(0,A.jsxs)(`section`,{className:pd,"data-testid":`lh-quiz`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Quiz`}),(0,A.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[e.questions.length,` question`,e.questions.length===1?``:`s`]})]}),(0,A.jsxs)(`form`,{onSubmit:u,className:`space-y-4`,children:[e.questions.map((e,t)=>{let n=e.question_type===`multi`,i=e.question_type===`true_false`?`True / False`:n?`Multiple Select`:`Single Choice`;return(0,A.jsxs)(`div`,{className:`rounded-md border border-border p-3 space-y-2 bg-muted/30`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between text-xs text-muted-foreground`,children:[(0,A.jsxs)(`span`,{className:`font-semibold`,children:[`Q`,t+1]}),(0,A.jsx)(`span`,{children:i})]}),(0,A.jsx)(`div`,{className:`text-sm font-medium`,children:e.question_text}),n&&(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground italic`,children:`Select all that apply`}),(0,A.jsx)(`div`,{className:`space-y-1`,children:e.options.map(t=>{let i=r[e.id],a=n?i?.optionIds.has(t.id)===!0:i?.optionId===t.id;return(0,A.jsxs)(`label`,{className:`flex items-start gap-2 text-sm cursor-pointer hover:bg-muted/50 rounded px-2 py-1`,children:[(0,A.jsx)(`input`,{type:n?`checkbox`:`radio`,name:`q-`+e.id,checked:a,onChange:()=>n?f(e,t.id):d(e,t.id),className:`mt-0.5`}),(0,A.jsx)(`span`,{children:t.option_text})]},t.id)})})]},e.id)}),s&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:s}),(0,A.jsx)(`button`,{type:`submit`,className:gd,disabled:l.isPending,"data-testid":`btn-lh-submit-quiz`,children:l.isPending?`Submitting…`:`Submit Answers`})]})]})}function Sd({slug:e,onBack:t}){let{data:n,isLoading:r,error:i}=j({queryKey:[`learning-content`,e],queryFn:()=>F.get(`/api/learning/content/`+encodeURIComponent(e))});if(r)return(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`});if(i)return(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:i.message});if(!n)return null;let a=n.content;return(0,A.jsxs)(`div`,{className:`space-y-4`,children:[(0,A.jsx)(`button`,{type:`button`,className:_d,onClick:t,"data-testid":`btn-lh-back`,children:`← Back to Feed`}),(0,A.jsxs)(`section`,{className:pd,"data-testid":`lh-viewer`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between gap-4`,children:[(0,A.jsx)(`h2`,{className:`text-xl font-semibold`,"data-testid":`lh-viewer-title`,children:a.title}),(0,A.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[vd(a.content_type),a.category_name?` · `+a.category_name:``,a.author_name?` · `+a.author_name:``]})]}),a.content_type===`presentation`?(0,A.jsxs)(`div`,{className:`text-center py-8 space-y-3 bg-muted/30 rounded-md`,children:[(0,A.jsx)(`div`,{className:`text-4xl`,children:`📊`}),(0,A.jsx)(`div`,{className:`text-sm font-medium`,children:a.title}),(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:`Slide rendering lives in the legacy viewer.`}),(0,A.jsx)(`a`,{href:`/#learning/`+encodeURIComponent(a.slug),className:gd+` inline-block`,rel:`noreferrer`,children:`Open in legacy viewer`})]}):(0,A.jsx)(`div`,{className:`whitespace-pre-wrap text-sm leading-relaxed`,"data-testid":`lh-viewer-body`,children:a.body||``})]}),a.progress&&a.progress.length>0&&(0,A.jsxs)(`section`,{className:pd,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Your past attempts`}),(0,A.jsx)(`div`,{className:`space-y-1 text-sm`,children:a.progress.map((e,t)=>{let n=e.total>0?Math.round(e.score/e.total*100):0,r=n>=70?`text-green-600`:`text-amber-600`;return(0,A.jsxs)(`div`,{className:`flex justify-between border-b border-border py-1`,children:[(0,A.jsx)(`span`,{children:new Date(e.completed_at).toLocaleDateString()}),(0,A.jsxs)(`span`,{className:`font-semibold `+r,children:[e.score,`/`,e.total,` (`,n,`%)`]})]},t)})})]}),a.questions&&a.questions.length>0&&(0,A.jsx)(xd,{content:a,onReset:t})]})}function Cd(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(null),{data:o}=j({queryKey:[`learning-categories`],queryFn:()=>F.get(`/api/learning/categories`)});return i?(0,A.jsx)(`div`,{className:`max-w-4xl mx-auto p-6`,children:(0,A.jsx)(Sd,{slug:i,onBack:()=>a(null)})}):(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Learning Hub`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Pediatric education, clinical pearls, and self-assessment quizzes.`})]}),(0,A.jsx)(`div`,{className:pd,children:(0,A.jsx)(`input`,{type:`search`,className:hd,placeholder:`Search topics, subjects…`,value:e,onChange:e=>t(e.target.value),"data-testid":`lh-search`})}),(0,A.jsxs)(`div`,{className:`flex flex-wrap gap-2`,"data-testid":`lh-categories`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:()=>r(``),className:md+(n===``?` bg-primary text-primary-foreground border-primary`:` bg-muted hover:bg-muted/80`),children:`All`}),o?.categories.map(e=>(0,A.jsx)(`button`,{type:`button`,onClick:()=>r(e.slug),className:md+(n===e.slug?` bg-primary text-primary-foreground border-primary`:` bg-muted hover:bg-muted/80`),"data-testid":`lh-cat-`+e.slug,children:e.name},e.id))]}),(0,A.jsx)(Q,{filter:n,query:e.trim(),onOpen:e=>a(e)})]})}var wd={mrc:{title:`MRC strength grade (0–5)`,icon:`fa-hand-fist`,rows:[[`5`,`Normal power — holds against full resistance`],[`4`,`Reduced — moves against gravity + some resistance`],[`3`,`Moves against gravity only (no added resistance)`],[`2`,`Full range with gravity eliminated (horizontal plane)`],[`1`,`Flicker / trace contraction, no joint movement`],[`0`,`No contraction`]]},dtr:{title:`Deep-tendon reflex grade (0–4+)`,icon:`fa-circle-dot`,rows:[[`0`,`Absent`],[`1+`,`Hypoactive — trace, only with reinforcement`],[`2+`,`Normal`],[`3+`,`Brisk — may still be normal in anxious patients`],[`4+`,`Hyperactive with sustained clonus — always abnormal`]]},plantar:{title:`Plantar response (Babinski)`,icon:`fa-shoe-prints`,rows:[[`Down-going`,`Normal in anyone ≥ 2 years`],[`Up-going`,`Normal < 2 years; abnormal after — UMN lesion`],[`Asymmetric`,`Always abnormal at any age`]]},beighton:{title:`Beighton hypermobility score (0–9)`,icon:`fa-hands`,rows:[[`≤ 3`,`Normal flexibility`],[`4`,`Borderline — consider in context`],[`≥ 5`,`Hypermobility spectrum; screen for hEDS if other features present`]]},atr:{title:`Scoliometer — angle of trunk rotation`,icon:`fa-ruler`,rows:[[`< 5°`,`Normal, no follow-up`],[`5–6°`,`Borderline — re-check at each visit`],[`≥ 7°`,`Refer for PA/lateral spine x-ray + orthopedic evaluation`]]},rr:{title:`Respiratory rate — upper limit by age (awake)`,icon:`fa-lungs`,rows:[[`Newborn`,`≤ 60 /min`],[`< 2 months`,`≤ 60 /min (WHO tachypnea cutoff)`],[`2–12 months`,`≤ 50 /min (WHO tachypnea cutoff)`],[`1–5 years`,`≤ 40 /min (WHO tachypnea cutoff)`],[`6–11 years`,`≤ 30 /min`],[`≥ 12 years`,`≤ 20 /min (adult pattern)`]]},spo2:{title:`Pulse oximetry (SpO₂) — at room air`,icon:`fa-heart-pulse`,rows:[[`≥ 95%`,`Normal`],[`92–94%`,`Mild hypoxemia — investigate cause`],[`< 92%`,`Moderate hypoxemia — supplemental O₂`],[`< 88%`,`Severe — urgent intervention; target ≥ 90% acutely`]]},silverman:{title:`Silverman–Andersen retraction score (neonatal, 0–10)`,icon:`fa-baby`,rows:[[`0`,`No respiratory distress`],[`1–3`,`Mild — close observation`],[`4–6`,`Moderate distress — consider CPAP / support`],[`7–10`,`Severe — imminent respiratory failure, intubate`]]},westley:{title:`Westley croup severity score`,icon:`fa-stethoscope`,rows:[[`≤ 2`,`Mild — home management, cool mist, oral dexamethasone`],[`3–5`,`Moderate — nebulised epinephrine + dexamethasone`],[`6–11`,`Severe — admit, continuous monitoring`],[`≥ 12`,`Impending respiratory failure — ICU / airway management`]]},murmurGrade:{title:`Heart-murmur grading (Levine 1–6)`,icon:`fa-wave-square`,rows:[[`1/6`,`Very faint — heard only with concentration`],[`2/6`,`Soft but readily heard`],[`3/6`,`Moderately loud, no thrill`],[`4/6`,`Loud WITH a palpable thrill`],[`5/6`,`Very loud; audible with stethoscope just off the chest`],[`6/6`,`Audible without the stethoscope touching the chest`]]},pulseAmp:{title:`Pulse amplitude grade (0–4)`,icon:`fa-heart-pulse`,rows:[[`0`,`Absent`],[`1+`,`Diminished, thready`],[`2+`,`Normal`],[`3+`,`Bounding`],[`4+`,`Bounding with visible pulsation (e.g., aortic regurgitation)`]]},capRefill:{title:`Capillary refill time`,icon:`fa-hand`,rows:[[`< 2 sec`,`Normal`],[`2–3 sec`,`Borderline — consider hydration / perfusion`],[`≥ 3 sec`,`Delayed — dehydration, shock, low cardiac output`]]}},Td=[{letter:`A`,color:`#dc2626`,title:`Aortic area`,location:`2nd ICS, right sternal border`,listen:`S2 (aortic component), aortic stenosis, aortic regurgitation`},{letter:`P`,color:`#2563eb`,title:`Pulmonic area`,location:`2nd ICS, left sternal border`,listen:`S2 (pulmonic component), pulmonic stenosis, PDA, physiologic split of S2`,innocent:`Pulmonary flow murmur (children, adolescents) — upper left sternal border`},{letter:`E`,color:`#059669`,title:`Erb's point`,location:`3rd ICS, left sternal border`,listen:`Aortic regurgitation (best here), transitional zone murmurs`,innocent:`Still's murmur classically radiates to Erb's / LLSB`},{letter:`T`,color:`#d97706`,title:`Tricuspid area`,location:`4th–5th ICS, lower left sternal border`,listen:`Tricuspid regurgitation, VSD, S3/S4, holosystolic murmurs`,innocent:`Still's murmur — vibratory, musical, age 3–7 y (loudest between LLSB and apex)`},{letter:`M`,color:`#7c3aed`,title:`Mitral area (apex)`,location:`5th ICS, mid-clavicular line`,listen:`S1, mitral regurgitation, mitral stenosis (with bell, left-lateral decubitus)`}],Ed=[{name:`Still's (vibratory) murmur`,age:`3–7 y (most common in children)`,location:`LLSB, radiating to apex`,character:`Low-frequency vibratory / musical systolic, grade 2–3/6, mid-systolic, "twanging-string" quality`,confirm:`Louder supine, softer or disappears on standing or Valsalva. No radiation to neck/back. Normal S2.`},{name:`Pulmonary flow murmur`,age:`School-age and adolescents, thin chest`,location:`Upper left sternal border (2nd–3rd ICS)`,character:`Soft blowing early systolic ejection, grade 1–2/6, higher-pitched`,confirm:`No ejection click. Physiologic split of S2. Louder supine, softer on standing. No radiation.`},{name:`Venous hum`,age:`Ages 3–8, disappears by adolescence`,location:`Supraclavicular or infraclavicular area, usually right`,character:`Soft continuous hum, louder in diastole. Only innocent continuous murmur.`,confirm:`Disappears when supine OR when jugular vein is gently compressed (key maneuver). Turning head to opposite side also alters it.`},{name:`Carotid bruit / supraclavicular bruit`,age:`Children and adolescents`,location:`Supraclavicular fossa, right > left; may radiate to carotid`,character:`Brief early systolic, grade 2–3/6, higher-pitched than Still's`,confirm:`Softer or disappears with hyperextension of the shoulders. Normal cardiac exam otherwise. No radiation below the clavicles.`},{name:`Peripheral pulmonary stenosis (PPS, neonatal)`,age:`Newborns and infants < 6–12 months`,location:`Upper LSB, radiates to BOTH axillae and the back`,character:`Soft systolic ejection murmur, grade 1–2/6`,confirm:`Typical age + radiation to back/axillae. Resolves by age 1 as branch pulmonary arteries grow. Persistence or louder grade warrants echo.`}],Dd=[{key:`normal`,src:`/audio/respiratory/normal-vesicular.ogg`,title:`Normal vesicular breath sounds`,where:`Peripheral lung fields`,features:`Soft, rustling. Inspiration louder and longer than expiration.`,clinical:`Baseline — deviation elsewhere is what you listen for.`},{key:`wheeze`,src:`/audio/respiratory/wheeze.ogg`,title:`Wheeze`,where:`Diffuse in asthma; localised in foreign body`,features:`Continuous, high-pitched, musical. Usually expiratory; biphasic if severe.`,clinical:`Lower-airway narrowing — asthma, bronchiolitis, foreign body, bronchomalacia. Silent chest in severe asthma is an ominous sign.`},{key:`stridor`,src:`/audio/respiratory/stridor.ogg`,title:`Stridor`,where:`Louder over neck than chest — upper airway`,features:`Continuous, high-pitched, harsh. Classically inspiratory (extrathoracic obstruction); biphasic if fixed.`,clinical:`Croup, epiglottitis, foreign body, laryngomalacia (infant). Distinguish from wheeze by auscultating the neck — stridor is loudest there.`},{key:`finecrackles`,src:`/audio/respiratory/crackles-fine.ogg`,title:`Fine (end-inspiratory) crackles`,where:`Bibasilar in pulmonary edema/fibrosis; focal in pneumonia`,features:`Discontinuous, brief, high-pitched. "Velcro" quality. Late inspiratory, do NOT clear with cough.`,clinical:`Alveolar opening — pulmonary fibrosis, pulmonary edema, early pneumonia, atelectasis.`},{key:`coarsecrackles`,src:`/audio/respiratory/crackles-coarse.ogg`,title:`Coarse crackles`,where:`Lower lobes; either side`,features:`Discontinuous, longer and louder than fine crackles. Lower-pitched. Can be early or late inspiratory; often clear partly with cough.`,clinical:`Secretions in larger airways — bronchitis, later pneumonia, bronchiectasis, aspiration.`},{key:`rhonchi`,src:`/audio/respiratory/rhonchi.ogg`,title:`Rhonchi`,where:`Central or anywhere with airway secretions`,features:`Continuous, low-pitched, snore-like. Typically expiratory. Clear or change with cough.`,clinical:`Large-airway secretions — bronchitis, pneumonia with large-airway involvement, cystic fibrosis, bronchiectasis.`},{key:`pleuralrub`,src:`/audio/respiratory/pleural-rub.ogg`,title:`Pleural friction rub`,where:`Focal, often lateral or posterior lower chest`,features:`Grating, creaky — "leather on leather". Biphasic (heard in inspiration and expiration). Does NOT clear with cough.`,clinical:`Pleural inflammation — pleuritis, pulmonary embolism, pneumonia with pleural involvement, viral pleurisy.`}],Od=[{key:`normal`,src:`/audio/cardiac/normal.ogg`,title:`Normal heart sounds (S1, S2)`,where:`All four classic auscultation points`,rate:`~61 bpm reference`,features:`"lub-dub": S1 (closure of mitral + tricuspid) louder at apex; S2 (closure of aortic + pulmonic) louder at base. Physiologic S2 split on inspiration.`,clinical:`Reference for rhythm, rate, and the normal S1–S2 interval. Listen for what's changed — not just what's added.`},{key:`infant-normal`,src:`/audio/cardiac/infant-normal.ogg`,title:`Infant normal heart sounds`,where:`Infant chest — rate will be higher than adult`,rate:`Pediatric reference (120–160 bpm range)`,features:`Same S1–S2 pattern, faster rate. Short diastole makes murmurs easier to miss — careful auscultation needed.`,clinical:`Reference for neonatal/infant rhythm. Any murmur in the first 72 h should prompt pre/postductal sat screening.`},{key:`vsd`,src:`/audio/cardiac/vsd.wav`,title:`Ventricular septal defect (VSD)`,where:`Lower left sternal border (4th ICS)`,features:`Harsh, blowing, holosystolic (pansystolic) murmur — plateau shape through all of systole. Often accompanied by a thrill if large.`,clinical:`Most common congenital heart defect. Small VSD: loud murmur, usually asymptomatic, may close spontaneously. Large VSD: softer murmur (less pressure gradient) but signs of heart failure, pulmonary hypertension.`},{key:`mvp`,src:`/audio/cardiac/mitral-prolapse.wav`,title:`Mitral valve prolapse (MVP) — click + late systolic murmur`,where:`Apex (5th ICS, mid-clavicular line)`,features:`Mid-systolic click followed by a late-systolic crescendo murmur. Timing of click changes with maneuvers: earlier with standing or Valsalva, later with squatting.`,clinical:`Often benign, especially in thin young women. Features suggesting need for echo: thickened/redundant leaflets, associated MR, symptoms (palpitations, chest pain), arrhythmias.`},{key:`stills`,src:`/audio/cardiac/stills-murmur.ogg`,title:`Still's murmur (innocent)`,where:`LLSB, radiating to apex`,rate:`Classic age 3–7 y (this recording is a toddler)`,features:`Low-frequency vibratory / musical systolic, grade 2–3/6, mid-systolic, "twanging-string" quality.`,clinical:`The most common innocent murmur of childhood. Louder supine, softer or disappears on standing or Valsalva. Normal S2. No radiation to neck or back. No workup needed when classic.`},{key:`functional`,src:`/audio/cardiac/functional-murmur.wav`,title:`Functional (innocent) murmur — adult female`,where:`Left sternal border, soft systolic`,features:`Soft systolic murmur in a structurally normal heart — often from increased cardiac output, thin chest wall, anemia, hyperthyroidism, or pregnancy.`,clinical:`Benign if it meets the 7 S criteria. Investigate if loud (≥3/6), holosystolic, diastolic, radiating, or with thrill / symptoms.`}],kd=`rounded-lg border border-border bg-card p-5 space-y-3`,Ad=`rounded-md bg-primary text-primary-foreground px-3 py-2 text-sm font-medium disabled:opacity-50`;function jd({id:e,scale:t}){return(0,A.jsxs)(`section`,{className:kd,"data-testid":`scale-`+e,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:t.title}),(0,A.jsx)(`table`,{className:`w-full text-sm`,children:(0,A.jsx)(`tbody`,{children:t.rows.map(([e,t],n)=>(0,A.jsxs)(`tr`,{className:`border-b border-border last:border-0`,children:[(0,A.jsx)(`td`,{className:`py-1.5 pr-3 font-mono font-semibold whitespace-nowrap`,children:e}),(0,A.jsx)(`td`,{className:`py-1.5 text-muted-foreground`,children:t})]},n))})})]})}function Md({entry:e}){let t=(0,_.useRef)(null);return(0,A.jsxs)(`section`,{className:kd,"data-testid":`sound-`+e.key,children:[(0,A.jsx)(`h4`,{className:`text-sm font-semibold`,children:e.title}),(0,A.jsxs)(`audio`,{ref:t,controls:!0,preload:`none`,className:`w-full`,children:[(0,A.jsx)(`source`,{src:e.src}),`Your browser does not support HTML5 audio.`]}),(0,A.jsxs)(`div`,{className:`text-xs space-y-1`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide`,children:`Where:`}),` `,e.where]}),e.rate&&(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide`,children:`Rate:`}),` `,e.rate]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide`,children:`Features:`}),` `,e.features]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide`,children:`Clinical:`}),` `,e.clinical]})]})]})}function $(){return(0,A.jsxs)(`div`,{className:`max-w-5xl mx-auto p-6 space-y-6`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Physical Exam Guide`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Reference scales, the five cardiac auscultation points, innocent childhood murmurs, and sound libraries.`})]}),(0,A.jsxs)(`section`,{className:`rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-4 text-sm space-y-2`,children:[(0,A.jsx)(`div`,{className:`font-semibold text-amber-900 dark:text-amber-100`,children:`Exam-step checklist lives in the legacy viewer`}),(0,A.jsx)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:`The full age-group × system checklist and the Generate Exam Report flow still run in the legacy app. They port in a dedicated session so the clinical content can be verified entry-for-entry against the vanilla source.`}),(0,A.jsx)(`a`,{href:`/#peGuide`,className:Ad+` inline-block`,children:`Open checklist in legacy viewer`})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Grading scales & reference ranges`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-4`,children:Object.entries(wd).map(([e,t])=>(0,A.jsx)(jd,{id:e,scale:t},e))})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Cardiac auscultation — APTM (the five points)`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-4`,children:Td.map(e=>(0,A.jsxs)(`div`,{className:kd,"data-testid":`aptm-`+e.letter.toLowerCase(),children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,A.jsx)(`div`,{className:`w-10 h-10 rounded-full flex items-center justify-center font-bold text-white`,style:{background:e.color},children:e.letter}),(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:e.title})]}),(0,A.jsxs)(`div`,{className:`text-sm text-muted-foreground`,children:[(0,A.jsx)(`strong`,{children:`Location:`}),` `,e.location]}),(0,A.jsxs)(`div`,{className:`text-sm text-muted-foreground`,children:[(0,A.jsx)(`strong`,{children:`Listen for:`}),` `,e.listen]}),e.innocent&&(0,A.jsxs)(`div`,{className:`text-xs text-green-700 dark:text-green-300 bg-green-50 dark:bg-green-950/30 rounded px-2 py-1`,children:[(0,A.jsx)(`strong`,{children:`Innocent:`}),` `,e.innocent]})]},e.letter))})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Innocent murmurs of childhood`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-4`,children:Ed.map(e=>(0,A.jsxs)(`div`,{className:kd,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:e.name}),(0,A.jsxs)(`div`,{className:`text-sm space-y-1`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide text-xs`,children:`Age:`}),` `,e.age]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide text-xs`,children:`Location:`}),` `,e.location]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide text-xs`,children:`Character:`}),` `,e.character]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold text-muted-foreground uppercase tracking-wide text-xs`,children:`Confirm benign:`}),` `,e.confirm]})]})]},e.name))})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Respiratory sounds library`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-4`,children:Dd.map(e=>(0,A.jsx)(Md,{entry:e},e.key))})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Cardiac sounds library`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-4`,children:Od.map(e=>(0,A.jsx)(Md,{entry:e},e.key))})]})]})}function Nd(e){if(e==null)return null;let t=String(e).toLowerCase().trim();if(!t)return null;if(/^[\d.]+$/.test(t)){let e=Number.parseFloat(t);return Number.isNaN(e)?null:e}let n=0,r=!1,i=t.match(/([\d.]+)\s*(?:years|year|yrs|yr|y)(?![a-z])/);i&&(n+=Number.parseFloat(i[1])*12,r=!0);let a=t.match(/([\d.]+)\s*(?:months|month|mos|mo|m)(?![a-z])/);a&&(n+=Number.parseFloat(a[1]),r=!0);let o=t.match(/([\d.]+)\s*(?:weeks|week|wks|wk|w)(?![a-z])/);o&&(n+=Number.parseFloat(o[1])*7/30.4375,r=!0);let s=t.match(/([\d.]+)\s*(?:days|day|d)(?![a-z])/);return s&&(n+=Number.parseFloat(s[1])/30.4375,r=!0),r?n:null}function Pd(e){if(e<1){let t=Math.round(e*30.4375);return`${t} day${t===1?``:`s`} (${e.toFixed(2)} mo)`}if(e<24)return`${Fd(e,1)} months`;let t=Math.floor(e/12),n=Math.round(e-t*12);return n===12&&(t+=1,n=0),`${t} yr${n?` ${n} mo`:``} (${Math.round(e)} mo total)`}function Fd(e,t){let n=10**t;return Math.round(e*n)/n}function Id(e){if(e==null||Number.isNaN(e)||e<0)return null;let t=e/12,n;n=e<12?.5*e+4:t<=5?2*t+8:3*t+7;let r;r=e<12?(e+9)/2:t<=5?2*(t+5):4*t;let i;return i=t<13?e<12?`APLS 0-12 mo`:t<=5?`APLS 1-5 yr`:`APLS 6-12 yr`:`Best Guess 5-14 yr`,{weight:Math.max(.3,Fd(t<13?n:r,1)),formulaLabel:i,all:{apls:Fd(n,1),bestGuess:Fd(r,1)}}}function Ld(e,t){return!Number.isFinite(e)||!Number.isFinite(t)||e<=0||t<=0?null:Math.sqrt(t*e/3600)}function Rd(e){let t=e.frequencyPerDay??1,n=e.maxSingleDoseMg??0,r=e.concentrationMgPerMl??0;if(!Number.isFinite(e.weightKg)||!Number.isFinite(e.dosePerKg)||!Number.isFinite(t)||e.weightKg<=0||e.dosePerKg<=0||t<=0)return null;let i=e.weightKg*e.dosePerKg,a=!1;return n>0&&i>n&&(i=n,a=!0),{singleDoseMg:i,dailyDoseMg:i*t,frequencyPerDay:t,capped:a,volumeMl:r>0?i/r:null}}function zd(e,t,n){if(!Number.isFinite(e)||!Number.isFinite(t)||!Number.isFinite(n)||e<1||e>4||t<1||t>5||n<1||n>6)return null;let r=e+t+n,i;return i=r<=8?`Severe (Coma)`:r<=12?`Moderate`:`Mild`,{total:r,severity:i}}var Bd=`rounded-lg border border-border bg-card p-5 space-y-3`,Vd=`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium`,Hd=`rounded-md border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted`,Ud=`block text-xs font-medium text-muted-foreground`,Wd=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring`,Gd=[{id:`neonatal`,label:`Neonatal`,icon:`👶`,summary:`GA classification, AGA/SGA/LGA, prematurity category (Fenton 2013 / WHO).`},{id:`airway`,label:`Airway / RSI`,icon:`💨`,summary:`ETT size + depth, RSI induction + paralytic dosing by weight.`},{id:`cardiac`,label:`Cardiac Arrest`,icon:`❤️`,summary:`PALS dosing (epinephrine, amiodarone, lidocaine), defibrillation J/kg.`},{id:`respiratory`,label:`Respiratory`,icon:`🫁`,summary:`Asthma, bronchiolitis, croup severity + dosing.`},{id:`ventilation`,label:`O₂ & Ventilation`,icon:`🌀`,summary:`NC / HFNC / CPAP / BiPAP flow + FiO₂ targets by age.`},{id:`seizure`,label:`Seizures`,icon:`🧠`,summary:`Benzodiazepine + second/third-line weight-based dosing.`},{id:`sepsis`,label:`Sepsis & Fever`,icon:`🦠`,summary:`Empirical antibiotics + fluid bolus dosing by weight.`},{id:`anaphylaxis`,label:`Anaphylaxis`,icon:`💉`,summary:`Epinephrine IM, IV infusion, steroid + antihistamine dosing.`},{id:`sedation`,label:`Sedation`,icon:`🛌`,summary:`Procedural sedation regimens — ketamine, propofol, midazolam.`},{id:`agitation`,label:`Agitation`,icon:`😤`,summary:`Weight-based haloperidol, olanzapine, lorazepam.`},{id:`antiemetics`,label:`Antiemetics`,icon:`💊`,summary:`Ondansetron, metoclopramide, promethazine dosing.`},{id:`antimicrobials`,label:`Antimicrobials`,icon:`🧫`,summary:`Common empirical regimens keyed to syndrome + weight.`},{id:`burns`,label:`Burns`,icon:`🔥`,summary:`TBSA % (Lund-Browder, Rule of Nines-children), Parkland fluids.`},{id:`toxicology`,label:`Toxicology`,icon:`☠️`,summary:`Common toxidromes + antidotes + decontamination windows.`},{id:`trauma`,label:`Trauma`,icon:`🩹`,summary:`PECARN, c-spine, blood-product dosing, TXA.`}];function Kd(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(`apls`),[i,a]=(0,_.useState)(``),o=Nd(e),s=o==null?null:Id(o),c=s?n===`bestguess`?s.all.bestGuess:s.all.apls:null,l=i.trim()||(c==null?``:String(c));function u(){t(``),r(`apls`),a(``)}return(0,A.jsxs)(`section`,{className:Bd,"data-testid":`bedside-weight-estimator`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Age → Weight Estimator`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Shared starting point for Bedside dosing. Uses the same APLS and Best Guess formulas as the legacy app.`})]}),(0,A.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-[1.2fr_1fr_1fr_auto] md:items-end`,children:[(0,A.jsxs)(`div`,{className:`space-y-1`,children:[(0,A.jsx)(`label`,{htmlFor:`bedside-react-age`,className:Ud,children:`Age`}),(0,A.jsx)(`input`,{id:`bedside-react-age`,value:e,onChange:e=>t(e.target.value),placeholder:`e.g. "18m", "3y", "2y5m"`,className:Wd,"data-testid":`bedside-age-input`})]}),(0,A.jsxs)(`div`,{className:`space-y-1`,children:[(0,A.jsx)(`label`,{htmlFor:`bedside-react-formula`,className:Ud,children:`Formula`}),(0,A.jsxs)(`select`,{id:`bedside-react-formula`,value:n,onChange:e=>{r(e.target.value),a(``)},className:Wd,"data-testid":`bedside-formula-select`,children:[(0,A.jsx)(`option`,{value:`apls`,children:`APLS`}),(0,A.jsx)(`option`,{value:`bestguess`,children:`Best Guess`})]})]}),(0,A.jsxs)(`div`,{className:`space-y-1`,children:[(0,A.jsx)(`label`,{htmlFor:`bedside-react-weight`,className:Ud,children:`Weight (kg)`}),(0,A.jsx)(`input`,{id:`bedside-react-weight`,type:`number`,min:`0.3`,step:`0.1`,value:l,onChange:e=>a(e.target.value),className:Wd,"data-testid":`bedside-weight-input`})]}),(0,A.jsx)(`button`,{type:`button`,onClick:u,className:Hd,children:`Clear`})]}),e.trim()&&o==null?(0,A.jsx)(`div`,{className:`rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700 dark:bg-red-950/30 dark:text-red-200`,children:`Could not parse age. Try "3y", "18 months", or "15 days".`}):null,s&&c!=null?(0,A.jsxs)(`div`,{className:`rounded-lg border border-border bg-muted/40 p-4 text-sm`,"data-testid":`bedside-estimate-result`,children:[(0,A.jsxs)(`div`,{className:`font-semibold`,children:[c,` kg estimated from `,Pd(o??0)]}),(0,A.jsxs)(`div`,{className:`text-muted-foreground`,children:[`APLS: `,s.all.apls,` kg · Best Guess: `,s.all.bestGuess,` kg. You can override the weight field.`]})]}):null]})}function qd({pill:e}){return(0,A.jsxs)(`section`,{className:Bd,"data-testid":`bedside-panel-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,A.jsx)(`span`,{className:`text-2xl`,"aria-hidden":!0,children:e.icon}),(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:e.label})]}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:e.summary}),(0,A.jsx)(`div`,{className:`rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm space-y-2`,children:(0,A.jsx)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:`Weight-based calculators for this module run in the legacy viewer while the clinical data is verified for a direct React port. Open the legacy Bedside tab to use the full dosing flow.`})}),(0,A.jsx)(`a`,{href:`/#bedside`,className:Vd+` inline-block`,children:`Open in legacy viewer`})]})}function Jd(){let[e,t]=(0,_.useState)(Gd[0].id),n=Gd.find(t=>t.id===e)??Gd[0];return(0,A.jsxs)(`div`,{className:`max-w-5xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Bedside`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Emergency and rapid-reference pediatric tools. Weight-based dosing throughout — always verify against institutional protocols.`})]}),(0,A.jsx)(`div`,{className:`flex flex-wrap gap-2`,"data-testid":`bedside-subnav`,children:Gd.map(n=>(0,A.jsxs)(`button`,{type:`button`,onClick:()=>t(n.id),className:`px-3 py-1.5 rounded-full text-xs font-medium border transition-colors `+(e===n.id?`bg-primary text-primary-foreground border-primary`:`bg-muted hover:bg-muted/80 border-border`),"data-testid":`bedside-pill-`+n.id,children:[(0,A.jsx)(`span`,{className:`mr-1`,"aria-hidden":!0,children:n.icon}),n.label]},n.id))}),(0,A.jsx)(Kd,{}),(0,A.jsx)(qd,{pill:n})]})}var Yd=`rounded-lg border border-border bg-card p-5 space-y-3`,Xd=`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium`,Zd=`rounded-md border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted`,Qd=`space-y-1`,$d=`block text-xs font-medium text-muted-foreground`,ef=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring`,tf=`rounded-lg border border-border bg-muted/40 p-4`,nf=`rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700 dark:bg-red-950/30 dark:text-red-200`,rf=[{id:`bp`,label:`BP Percentile`,summary:`AAP 2017 age/height/sex-adjusted BP percentiles (Rosner quantile splines).`,source:`AAP 2017 (Flynn) — Rosner splines`},{id:`bmi`,label:`BMI Percentile`,summary:`BMI-for-age (CDC 2000 z-score tables).`,source:`CDC 2000 LMS`},{id:`growth`,label:`Growth Charts`,summary:`WHO 0–2 y / CDC 2–20 y; Fenton 2013 preterm (weight, length, head).`,source:`WHO 2006 + CDC 2000 + Fenton 2013 LMS`},{id:`bili`,label:`Bilirubin`,summary:`AAP 2022 phototherapy + exchange thresholds and Bhutani nomogram risk zones.`,source:`AAP 2022 (Kemper) + Bhutani 1999`},{id:`vitals`,label:`Vital Signs`,summary:`Normal HR / RR / BP ranges by age.`,source:`PALS + AHA reference`},{id:`bsa`,label:`Body Surface Area`,summary:`Mosteller body surface area formula.`,source:`Mosteller 1987`,ported:!0},{id:`dose`,label:`Weight-Based Dosing`,summary:`Generic mg/kg dosing with optional max-dose cap and concentration conversion.`,source:`Legacy calculator formula`,ported:!0},{id:`resus`,label:`Resus Meds`,summary:`Code-cart dosing (epinephrine, amiodarone, atropine, etc.).`,source:`PALS`},{id:`gcs`,label:`GCS`,summary:`Child/adult and infant Glasgow Coma Scale variants.`,source:`Teasdale + pediatric modification`,ported:!0},{id:`equipment`,label:`Equipment`,summary:`ETT size, blade, NG, Foley, suction by age/weight.`,source:`PALS + Broselow cross-reference`}];function af(e){if(!e.trim())return null;let t=Number(e);return Number.isFinite(t)?t:null}function of({id:e,labelText:t,value:n,onChange:r,min:i,max:a,step:o=`0.1`,placeholder:s}){return(0,A.jsxs)(`div`,{className:Qd,children:[(0,A.jsx)(`label`,{htmlFor:e,className:$d,children:t}),(0,A.jsx)(`input`,{id:e,type:`number`,min:i,max:a,step:o,value:n,onChange:e=>r(e.target.value),placeholder:s,className:ef})]})}function sf(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(null),[o,s]=(0,_.useState)(``);function c(){let t=Ld(Number(e),Number(n));if(t==null){s(`Enter a valid weight and height.`),a(null);return}s(``),a(t)}function l(){t(``),r(``),a(null),s(``)}return(0,A.jsxs)(`section`,{className:Yd,"data-testid":`calc-panel-bsa`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Body Surface Area`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Mosteller formula: BSA (m2) = sqrt(height(cm) x weight(kg) / 3600).`}),(0,A.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2`,children:[(0,A.jsx)(of,{id:`react-bsa-weight`,labelText:`Weight (kg)`,value:e,onChange:t,min:`1`,max:`200`,placeholder:`20`}),(0,A.jsx)(of,{id:`react-bsa-height`,labelText:`Height (cm)`,value:n,onChange:r,min:`30`,max:`220`,placeholder:`110`})]}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:c,className:Xd,"data-testid":`calc-bsa-calculate`,children:`Calculate`}),(0,A.jsx)(`button`,{type:`button`,onClick:l,className:Zd,children:`Clear`})]}),o?(0,A.jsx)(`div`,{className:nf,children:o}):null,i==null?null:(0,A.jsxs)(`div`,{className:tf,"data-testid":`calc-bsa-result`,children:[(0,A.jsx)(`div`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Mosteller BSA`}),(0,A.jsxs)(`div`,{className:`text-2xl font-semibold`,children:[i.toFixed(3),` m²`]}),(0,A.jsxs)(`div`,{className:`text-sm text-muted-foreground`,children:[e,` kg, `,n,` cm`]})]})]})}function cf(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(`1`),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(``),[u,d]=(0,_.useState)(null),[f,p]=(0,_.useState)(``);function m(){let t=Rd({weightKg:Number(e),dosePerKg:Number(n),frequencyPerDay:Number(i),maxSingleDoseMg:af(o),concentrationMgPerMl:af(c)});if(t==null){p(`Enter a valid weight, mg/kg dose, and frequency.`),d(null);return}p(``),d(t)}function h(){t(``),r(``),a(`1`),s(``),l(``),d(null),p(``)}return(0,A.jsxs)(`section`,{className:Yd,"data-testid":`calc-panel-dose`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Weight-Based Dosing`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Generic mg/kg calculator. Always verify medication-specific dosing against formulary and local policy.`}),(0,A.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2 lg:grid-cols-3`,children:[(0,A.jsx)(of,{id:`react-dose-weight`,labelText:`Patient Weight (kg)`,value:e,onChange:t,min:`1`,max:`200`,placeholder:`15`}),(0,A.jsx)(of,{id:`react-dose-per-kg`,labelText:`Dose (mg/kg)`,value:n,onChange:r,min:`0.01`,step:`0.01`,placeholder:`10`}),(0,A.jsxs)(`div`,{className:Qd,children:[(0,A.jsx)(`label`,{htmlFor:`react-dose-frequency`,className:$d,children:`Frequency`}),(0,A.jsxs)(`select`,{id:`react-dose-frequency`,value:i,onChange:e=>a(e.target.value),className:ef,children:[(0,A.jsx)(`option`,{value:`1`,children:`Once daily`}),(0,A.jsx)(`option`,{value:`2`,children:`Twice daily (BID)`}),(0,A.jsx)(`option`,{value:`3`,children:`Three times daily (TID)`}),(0,A.jsx)(`option`,{value:`4`,children:`Four times daily (QID)`}),(0,A.jsx)(`option`,{value:`6`,children:`Every 4 hours (Q4H)`})]})]}),(0,A.jsx)(of,{id:`react-dose-max`,labelText:`Max single dose (mg, optional)`,value:o,onChange:s,min:`0`,step:`1`,placeholder:`500`}),(0,A.jsx)(of,{id:`react-dose-concentration`,labelText:`Concentration (mg/mL, optional)`,value:c,onChange:l,min:`0`,placeholder:`40`})]}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:m,className:Xd,"data-testid":`calc-dose-calculate`,children:`Calculate`}),(0,A.jsx)(`button`,{type:`button`,onClick:h,className:Zd,children:`Clear`})]}),f?(0,A.jsx)(`div`,{className:nf,children:f}):null,u==null?null:(0,A.jsx)(`div`,{className:tf,"data-testid":`calc-dose-result`,children:(0,A.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-3`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Single Dose`}),(0,A.jsxs)(`div`,{className:`text-xl font-semibold`,children:[u.singleDoseMg.toFixed(1),` mg`]}),u.capped?(0,A.jsx)(`div`,{className:`text-xs text-red-600`,children:`Capped at max dose`}):null]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Daily Total`}),(0,A.jsxs)(`div`,{className:`text-xl font-semibold`,children:[u.dailyDoseMg.toFixed(1),` mg/day`]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[`x `,u.frequencyPerDay,`/day`]})]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Volume`}),(0,A.jsx)(`div`,{className:`text-xl font-semibold`,children:u.volumeMl==null?`n/a`:`${u.volumeMl.toFixed(1)} mL`}),(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:`per dose`})]})]})})]})}var lf={child:{eye:[[`4`,`4 - Spontaneous`],[`3`,`3 - To speech`],[`2`,`2 - To pain`],[`1`,`1 - None`]],verbal:[[`5`,`5 - Oriented`],[`4`,`4 - Confused`],[`3`,`3 - Inappropriate words`],[`2`,`2 - Incomprehensible sounds`],[`1`,`1 - None`]],motor:[[`6`,`6 - Obeys commands`],[`5`,`5 - Localizes pain`],[`4`,`4 - Withdraws to pain`],[`3`,`3 - Abnormal flexion`],[`2`,`2 - Abnormal extension`],[`1`,`1 - None`]]},infant:{eye:[[`4`,`4 - Spontaneous`],[`3`,`3 - To speech/sound`],[`2`,`2 - To painful stimuli`],[`1`,`1 - None`]],verbal:[[`5`,`5 - Coos/babbles`],[`4`,`4 - Irritable cry`],[`3`,`3 - Cries to pain`],[`2`,`2 - Moans to pain`],[`1`,`1 - None`]],motor:[[`6`,`6 - Normal spontaneous movement`],[`5`,`5 - Withdraws to touch`],[`4`,`4 - Withdraws to pain`],[`3`,`3 - Abnormal flexion`],[`2`,`2 - Abnormal extension`],[`1`,`1 - None`]]}};function uf({id:e,labelText:t,value:n,options:r,onChange:i}){return(0,A.jsxs)(`div`,{className:Qd,children:[(0,A.jsx)(`label`,{htmlFor:e,className:$d,children:t}),(0,A.jsx)(`select`,{id:e,value:n,onChange:e=>i(e.target.value),className:ef,children:r.map(([e,t])=>(0,A.jsx)(`option`,{value:e,children:t},e))})]})}function df(){let[e,t]=(0,_.useState)(`child`),[n,r]=(0,_.useState)(`4`),[i,a]=(0,_.useState)(`5`),[o,s]=(0,_.useState)(`6`),c=zd(Number(n),Number(i),Number(o)),l=lf[e];function u(e){t(e),r(`4`),a(`5`),s(`6`)}return(0,A.jsxs)(`section`,{className:Yd,"data-testid":`calc-panel-gcs`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Glasgow Coma Scale`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Select responses to calculate child/adult or infant-modified GCS. Total score 3-15.`}),(0,A.jsxs)(`div`,{className:`flex flex-wrap gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:()=>u(`child`),className:`px-3 py-1.5 rounded-full text-xs font-medium border `+(e===`child`?`bg-primary text-primary-foreground border-primary`:`bg-muted border-border`),children:`Child / Adult`}),(0,A.jsx)(`button`,{type:`button`,onClick:()=>u(`infant`),className:`px-3 py-1.5 rounded-full text-xs font-medium border `+(e===`infant`?`bg-primary text-primary-foreground border-primary`:`bg-muted border-border`),children:`Infant`})]}),(0,A.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-3`,children:[(0,A.jsx)(uf,{id:`react-gcs-eye`,labelText:`Eye Opening`,value:n,options:l.eye,onChange:r}),(0,A.jsx)(uf,{id:`react-gcs-verbal`,labelText:`Verbal Response`,value:i,options:l.verbal,onChange:a}),(0,A.jsx)(uf,{id:`react-gcs-motor`,labelText:`Motor Response`,value:o,options:l.motor,onChange:s})]}),c==null?null:(0,A.jsxs)(`div`,{className:tf,"data-testid":`calc-gcs-result`,children:[(0,A.jsx)(`div`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:e===`infant`?`Infant-modified GCS`:`Child / adult GCS`}),(0,A.jsxs)(`div`,{className:`text-3xl font-semibold`,children:[`GCS: `,c.total,`/15`]}),(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:c.severity}),(0,A.jsx)(`div`,{className:`mt-2 text-xs text-muted-foreground`,children:`Interpretation: 13-15 Mild, 9-12 Moderate, 3-8 Severe/Coma.`})]})]})}function ff({pill:e}){return(0,A.jsxs)(`section`,{className:Yd,"data-testid":`calc-panel-`+e.id,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:e.label}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:e.summary}),(0,A.jsxs)(`div`,{className:`rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm space-y-2`,children:[(0,A.jsxs)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:[(0,A.jsx)(`strong`,{children:`Source of truth:`}),` `,e.source,`.`]}),(0,A.jsx)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:`This calculator runs in the legacy viewer. A React port is gated on capturing test vectors from the vanilla implementation so the numerical output can be verified byte-for-byte — the migration checkpoint specifically flags this class of data as the one an LLM is most likely to silently simplify.`})]}),(0,A.jsx)(`a`,{href:`/#calculators`,className:Xd+` inline-block`,children:`Open in legacy viewer`})]})}function pf({pill:e}){return e.id===`bsa`?(0,A.jsx)(sf,{}):e.id===`dose`?(0,A.jsx)(cf,{}):e.id===`gcs`?(0,A.jsx)(df,{}):(0,A.jsx)(ff,{pill:e})}function mf(){let[e,t]=(0,_.useState)(rf[0].id),n=rf.find(t=>t.id===e)??rf[0];return(0,A.jsxs)(`div`,{className:`max-w-5xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Calculators`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Pediatric calculators — BP percentiles, bilirubin thresholds, growth, dosing, equipment sizing. Simple pure-formula calculators run in React now; high-risk table-driven calculators remain legacy-gated until vectors are captured.`})]}),(0,A.jsx)(`div`,{className:`flex flex-wrap gap-2`,"data-testid":`calc-subnav`,children:rf.map(n=>(0,A.jsxs)(`button`,{type:`button`,onClick:()=>t(n.id),className:`px-3 py-1.5 rounded-full text-xs font-medium border transition-colors `+(e===n.id?`bg-primary text-primary-foreground border-primary`:`bg-muted hover:bg-muted/80 border-border`),"data-testid":`calc-pill-`+n.id,children:[n.label,n.ported?(0,A.jsx)(`span`,{className:`ml-1 text-[10px] opacity-80`,children:`React`}):null]},n.id))}),(0,A.jsx)(pf,{pill:n})]})}var hf=`rounded-lg border border-border bg-card p-5 space-y-3`,gf=`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium`;function _f(){let{data:e,isLoading:t}=j({queryKey:[`auth-me`],queryFn:()=>F.get(`/api/auth/me`),staleTime:5*6e4});return t?(0,A.jsx)(`div`,{className:`max-w-3xl mx-auto p-6`,children:(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Checking permissions…`})}):e?.user.role===`admin`?(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Admin Panel`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Registration, users, feature flags, OIDC, email templates, AI prompts, SMTP, saved encounters (site-wide), AI model management, TTS/STT provider selection.`})]}),(0,A.jsxs)(`section`,{className:hf,"data-testid":`admin-shell`,children:[(0,A.jsx)(`div`,{className:`rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm space-y-2`,children:(0,A.jsx)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:`Admin sub-sections still live in the legacy viewer. Each one (user management, feature flags, announcements, OIDC config, SMTP, AI prompts, model management, TTS/STT) touches production state immediately when saved — those controls port in discrete follow-up commits with their own tests before they go live in the React tree.`})}),(0,A.jsx)(`a`,{href:`/#admin`,className:gf+` inline-block`,children:`Open admin in legacy viewer`})]})]}):(0,A.jsx)(`div`,{className:`max-w-3xl mx-auto p-6`,children:(0,A.jsxs)(`section`,{className:hf,"data-testid":`admin-access-denied`,children:[(0,A.jsx)(`h1`,{className:`text-xl font-semibold`,children:`Admin only`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`This page is restricted to users with the admin role. If you believe this is a mistake, contact your site administrator.`})]})})}var vf=new Ze({defaultOptions:{queries:{staleTime:3e4,retry:1}}});function yf(){return(0,A.jsxs)(`div`,{className:`max-w-3xl mx-auto p-6 space-y-4`,children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Pediatric AI Scribe — React client`}),(0,A.jsxs)(`p`,{className:`text-sm text-muted-foreground`,children:[`This is the new React tree. The legacy vanilla-JS app still lives at`,` `,(0,A.jsx)(`a`,{href:`/`,className:`underline`,children:`/`}),`.`]}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Ported tabs so far:`}),(0,A.jsxs)(`ul`,{className:`list-disc pl-6 text-sm space-y-1`,children:[(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/encounter`,className:`underline`,children:`Encounter HPI`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/dictation`,className:`underline`,children:`Dictation HPI`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/soap`,className:`underline`,children:`SOAP Note`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/sickvisit`,className:`underline`,children:`Sick Visit`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/extensions`,className:`underline`,children:`Extensions & Pagers`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/faq`,className:`underline`,children:`FAQ`})})]})]})}function bf(){return(0,A.jsx)(tt,{client:vf,children:(0,A.jsx)(ti,{basename:`/app`,children:(0,A.jsx)(mr,{children:(0,A.jsxs)(fr,{element:(0,A.jsx)(Ei,{}),children:[(0,A.jsx)(fr,{path:`/`,element:(0,A.jsx)(yf,{})}),(0,A.jsx)(fr,{path:`/encounter`,element:(0,A.jsx)(Cu,{})}),(0,A.jsx)(fr,{path:`/dictation`,element:(0,A.jsx)(Su,{})}),(0,A.jsx)(fr,{path:`/soap`,element:(0,A.jsx)(wu,{})}),(0,A.jsx)(fr,{path:`/sickvisit`,element:(0,A.jsx)(Tu,{})}),(0,A.jsx)(fr,{path:`/hospital`,element:(0,A.jsx)(Eu,{})}),(0,A.jsx)(fr,{path:`/chart`,element:(0,A.jsx)(Ou,{})}),(0,A.jsx)(fr,{path:`/wellvisit`,element:(0,A.jsx)(ku,{})}),(0,A.jsx)(fr,{path:`/vaxschedule`,element:(0,A.jsx)(Au,{})}),(0,A.jsx)(fr,{path:`/catchup`,element:(0,A.jsx)(ju,{})}),(0,A.jsx)(fr,{path:`/extensions`,element:(0,A.jsx)(vu,{})}),(0,A.jsx)(fr,{path:`/settings`,element:(0,A.jsx)(fd,{})}),(0,A.jsx)(fr,{path:`/learning`,element:(0,A.jsx)(Cd,{})}),(0,A.jsx)(fr,{path:`/peguide`,element:(0,A.jsx)($,{})}),(0,A.jsx)(fr,{path:`/bedside`,element:(0,A.jsx)(Jd,{})}),(0,A.jsx)(fr,{path:`/calculators`,element:(0,A.jsx)(mf,{})}),(0,A.jsx)(fr,{path:`/admin`,element:(0,A.jsx)(_f,{})}),(0,A.jsx)(fr,{path:`/faq`,element:(0,A.jsx)(xu,{})}),(0,A.jsx)(fr,{path:`*`,element:(0,A.jsx)(ur,{to:`/`,replace:!0})})]})})})})}(0,v.createRoot)(document.getElementById(`root`)).render((0,A.jsx)(_.StrictMode,{children:(0,A.jsx)(bf,{})})); \ No newline at end of file +CORRECTED TO: `);return t===-1?{original:e.trim(),corrected:``}:{original:e.substring(0,t).replace(/^ORIGINAL:\s*/i,``).trim(),corrected:e.substring(t+15).trim()}}function cd(){let e=et(),[t,n]=(0,_.useState)({}),[r,i]=(0,_.useState)(null),[a,o]=(0,_.useState)(null),{data:s}=j({queryKey:[`memories`],queryFn:()=>F.get(`/api/memories`)}),c=(s?.memories||[]).filter(e=>e.category.startsWith(`correction_`)),l=M({mutationFn:e=>F.delete(`/api/memories/`+e),onSuccess:()=>{i({text:`Correction deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`memories`]})},onError:e=>i({text:e.message||`Delete failed`,kind:`err`})});return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`corrections-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`AI Learning (Corrections)`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`The AI automatically learns from your edits. When you modify AI-generated text and save, corrections are stored here and applied to future notes. Latest 20 per section.`}),(0,A.jsx)(`div`,{className:`space-y-2`,children:c.length===0?(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No corrections yet. Edit AI-generated notes and save to start learning.`}):c.map(e=>{let r=t[e.id],i=sd(e.content||``),a=e.created_at?new Date(e.created_at).toLocaleDateString():``;return(0,A.jsxs)(`div`,{className:`rounded-md bg-muted/40 border border-border overflow-hidden`,"data-testid":`corr-row-`+e.id,children:[(0,A.jsxs)(`button`,{type:`button`,className:`w-full flex items-center gap-2 px-3 py-2 text-left`,onClick:()=>n({...t,[e.id]:!r}),children:[(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:r?`▾`:`▸`}),(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground uppercase tracking-wide`,children:od[e.category]||e.category}),(0,A.jsx)(`span`,{className:`flex-1 text-sm truncate`,children:e.name}),(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:a}),(0,A.jsx)(`span`,{role:`button`,className:`text-destructive text-xs px-2`,onClick:t=>{t.stopPropagation(),o(e)},"data-testid":`btn-corr-delete-`+e.id,children:`Delete`})]}),r&&(0,A.jsxs)(`div`,{className:`px-3 py-2 text-sm space-y-2 border-t border-border`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs font-semibold uppercase text-destructive mb-1`,children:`Original (AI generated):`}),(0,A.jsx)(`div`,{className:`whitespace-pre-wrap text-muted-foreground`,children:i.original})]}),i.corrected&&(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs font-semibold uppercase text-green-600 mb-1`,children:`Corrected to:`}),(0,A.jsx)(`div`,{className:`whitespace-pre-wrap`,children:i.corrected})]})]})]},e.id)})}),(0,A.jsx)(Ru,{msg:r}),(0,A.jsx)(Mu,{open:!!a,title:`Delete this correction?`,body:`The AI will no longer apply this edit in future notes.`,confirmText:`Delete`,danger:!0,busy:l.isPending,onConfirm:()=>{a&&l.mutate(a.id),o(null)},onCancel:()=>o(null)})]})}function ld(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(null),{data:a,isLoading:o}=j({queryKey:[`audio-backups`],queryFn:()=>F.get(`/api/audio-backups`)}),s=M({mutationFn:e=>F.delete(`/api/audio-backups/`+e),onSuccess:()=>{n({text:`Backup deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`audio-backups`]})},onError:e=>n({text:e.message,kind:`err`})});function c(e){window.open(`/api/audio-backups/`+e+`/audio`,`_blank`,`noopener,noreferrer`)}return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`audio-backups-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Audio Backups`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Recordings are automatically backed up on the server and kept for 24 hours. Retry flow ports with the recording components.`}),o&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),(0,A.jsxs)(`div`,{className:`space-y-2`,children:[a&&a.backups.length===0&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No audio backups.`}),a?.backups.map(e=>{let t=Math.round(e.size_bytes/1024),n=e.compressed_bytes?` (`+Math.round(e.compressed_bytes/1024)+` KB compressed)`:``;return(0,A.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-2 rounded-md bg-muted/40 border border-border`,"data-testid":`audio-backup-row-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,A.jsxs)(`div`,{className:`text-sm font-medium`,children:[e.module,` recording`]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[new Date(e.created_at).toLocaleString(),` · `,t,` KB`,n,` · `,zu(e.created_at)]})]}),(0,A.jsx)(`button`,{type:`button`,className:Fu+` text-xs`,onClick:()=>c(e.id),children:`Play`}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:()=>i(e),"data-testid":`btn-audio-delete-`+e.id,children:`Delete`})]},e.id)})]}),(0,A.jsx)(Ru,{msg:t}),(0,A.jsx)(Mu,{open:!!r,title:`Delete this audio backup?`,body:`This cannot be undone.`,confirmText:`Delete`,danger:!0,busy:s.isPending,onConfirm:()=>{r&&s.mutate(r.id),i(null)},onCancel:()=>i(null)})]})}function ud(){let e=et(),[t,n]=(0,_.useState)(null),[r,i]=(0,_.useState)(null),{data:a,isLoading:o}=j({queryKey:[`saved-encounters`],queryFn:()=>F.get(`/api/encounters/saved`)}),s=M({mutationFn:e=>F.delete(`/api/encounters/saved/`+e),onSuccess:()=>{n({text:`Deleted`,kind:`info`}),e.invalidateQueries({queryKey:[`saved-encounters`]})},onError:e=>n({text:e.message,kind:`err`})});return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`saved-encounters-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Saved Encounters`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Encounters are automatically deleted after 7 days per site policy. Resume action ports with the encounter components.`}),o&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),(0,A.jsxs)(`div`,{className:`space-y-2`,children:[a&&a.encounters.length===0&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`No saved encounters.`}),a?.encounters.map(e=>{let t=new Date(e.updated_at).toLocaleDateString(),n=new Date(e.expires_at).toLocaleDateString(),r=(e.transcript_preview||``).slice(0,80);return(0,A.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-2 rounded-md bg-muted/40 border border-border`,"data-testid":`enc-row-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-2 text-sm`,children:[(0,A.jsx)(`span`,{className:`font-medium truncate`,children:e.label||`Untitled`}),(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground uppercase tracking-wide`,children:e.enc_type})]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground truncate`,children:[t,` · expires `,n,r?` · `+r+`…`:``]})]}),(0,A.jsx)(`button`,{type:`button`,className:Iu+` text-destructive text-xs`,onClick:()=>i(e),"data-testid":`btn-enc-delete-`+e.id,children:`Delete`})]},e.id)})]}),(0,A.jsx)(Ru,{msg:t}),(0,A.jsx)(Mu,{open:!!r,title:`Delete this saved encounter?`,body:r?.label||void 0,confirmText:`Delete`,danger:!0,busy:s.isPending,onConfirm:()=>{r&&s.mutate(r.id),i(null)},onCancel:()=>i(null)})]})}function dd(){return(0,A.jsxs)(`section`,{className:Nu,"data-testid":`compliance-section`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Compliance & Usage`}),(0,A.jsxs)(`div`,{className:`text-sm space-y-2`,children:[(0,A.jsxs)(`p`,{children:[(0,A.jsx)(`strong`,{children:`AWS Bedrock`}),` is available with a Business Associate Agreement (BAA) for HIPAA-eligible workloads.`]}),(0,A.jsxs)(`ul`,{className:`list-disc pl-5 space-y-1 text-muted-foreground`,children:[(0,A.jsx)(`li`,{children:`All connections use HTTPS/TLS encryption`}),(0,A.jsx)(`li`,{children:`Authentication with optional 2FA`}),(0,A.jsx)(`li`,{children:`No patient data stored on server beyond session`}),(0,A.jsx)(`li`,{children:`AWS Bedrock supports BAA for HIPAA compliance`}),(0,A.jsx)(`li`,{children:`Azure OpenAI supports BAA for HIPAA compliance`})]}),(0,A.jsxs)(`p`,{children:[(0,A.jsx)(`strong`,{children:`Important:`}),` Check with your institution's guidelines and policies before use. This tool is not intended for production clinical use without proper organizational authorization and provider BAAs in place. Use with caution.`]})]})]})}function fd(){let{data:e,isLoading:t,error:n}=j({queryKey:[`auth-me`],queryFn:()=>F.get(`/api/auth/me`)}),r=e?.user.canLocalAuth!==!1;return(0,A.jsxs)(`div`,{className:`max-w-3xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Settings`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Account security, integrations, and personal templates. More sub-sections port over in follow-up commits.`})]}),t&&(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`}),n&&(0,A.jsxs)(`div`,{className:`text-sm text-destructive`,children:[`Could not load your account: `,n.message]}),e&&r&&(0,A.jsxs)(A.Fragment,{children:[(0,A.jsx)(Bu,{}),(0,A.jsx)(Hu,{user:e.user}),(0,A.jsx)(Wu,{})]}),e&&!r&&(0,A.jsxs)(`section`,{className:Nu,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Account managed by single sign-on`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Your password and two-factor authentication are managed by your identity provider.`})]}),e&&(0,A.jsx)(Z,{user:e.user}),e&&(0,A.jsx)(qu,{}),e&&(0,A.jsx)(Ju,{}),e&&(0,A.jsx)(ed,{}),e&&(0,A.jsx)(nd,{}),e&&(0,A.jsx)(ad,{}),e&&(0,A.jsx)(cd,{}),e&&(0,A.jsx)(ld,{}),e&&(0,A.jsx)(ud,{}),(0,A.jsx)(dd,{})]})}var pd=`rounded-lg border border-border bg-card p-5 space-y-3`,md=`px-3 py-1 rounded-full text-xs font-medium border transition-colors cursor-pointer`,hd=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm`,gd=`rounded-md bg-primary text-primary-foreground px-3 py-2 text-sm font-medium disabled:opacity-50`,_d=`rounded-md border border-border px-3 py-2 text-sm disabled:opacity-50`;function vd(e){switch(e){case`quiz`:return`Quiz`;case`pearl`:return`Pearl`;case`presentation`:return`Slides`;default:return`Article`}}function yd({row:e,onOpen:t}){return(0,A.jsxs)(`button`,{type:`button`,onClick:t,className:`w-full text-left rounded-lg border border-border bg-card hover:bg-muted/60 p-4 transition-colors`,"data-testid":`lh-feed-item-`+e.slug,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-2 text-xs text-muted-foreground uppercase tracking-wide mb-1`,children:[(0,A.jsx)(`span`,{className:`font-semibold`,children:vd(e.content_type)}),e.category_name&&(0,A.jsxs)(`span`,{children:[`· `,e.category_name]}),e.question_count?(0,A.jsxs)(`span`,{children:[`· `,e.question_count,` Q`]}):null]}),(0,A.jsx)(`div`,{className:`text-sm font-semibold`,children:e.title}),e.subject&&(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground mt-0.5 truncate`,children:e.subject})]})}function Q({filter:e,query:t,onOpen:n}){let{data:r,isLoading:i,error:a}=j({queryKey:t?[`learning-search`,t]:e?[`learning-category`,e]:[`learning-feed`],queryFn:()=>t?F.get(`/api/learning/search?q=`+encodeURIComponent(t)):e?F.get(`/api/learning/category/`+encodeURIComponent(e)):F.get(`/api/learning/feed?limit=30`)});if(i)return(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`});if(a)return(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:a.message});let o=r?.content||[];return o.length===0?(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground italic py-4`,children:`No content found.`}):(0,A.jsx)(`div`,{className:`grid grid-cols-1 sm:grid-cols-2 gap-3`,"data-testid":`lh-feed`,children:o.map(e=>(0,A.jsx)(yd,{row:e,onOpen:()=>n(e.slug)},e.id))})}function bd(e){let t={};for(let n of e)t[n.id]={optionIds:new Set};return t}function xd({content:e,onReset:t}){let n=et(),[r,i]=(0,_.useState)(()=>bd(e.questions)),[a,o]=(0,_.useState)(null),[s,c]=(0,_.useState)(null),l=M({mutationFn:e=>F.post(`/api/learning/submit-quiz`,e),onSuccess:t=>{o(t),n.invalidateQueries({queryKey:[`learning-content`,e.slug]})},onError:e=>c(e.message||`Submit failed`)});function u(t){t.preventDefault(),c(null);let n=e.questions.map(e=>{let t=r[e.id];return e.question_type===`multi`?{questionId:e.id,optionIds:Array.from(t?.optionIds||[])}:{questionId:e.id,optionId:t?.optionId??null}});l.mutate({contentId:e.id,answers:n})}function d(e,t){i(n=>({...n,[e.id]:{optionId:t,optionIds:new Set}}))}function f(e,t){i(n=>{let r=new Set(n[e.id]?.optionIds||[]);return r.has(t)?r.delete(t):r.add(t),{...n,[e.id]:{optionIds:r}}})}if(a){let n=a.percentage>=80?`bg-green-600`:a.percentage>=50?`bg-amber-500`:`bg-destructive`;return(0,A.jsxs)(`section`,{className:pd,"data-testid":`lh-quiz-results`,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Results`}),(0,A.jsxs)(`span`,{className:`px-2 py-0.5 rounded text-xs font-semibold text-white `+n,"data-testid":`lh-quiz-score`,children:[a.score,`/`,a.total,` (`,a.percentage,`%)`]})]}),(0,A.jsx)(`div`,{className:`space-y-3`,children:a.results.map((e,t)=>(0,A.jsxs)(`div`,{className:`rounded-md border border-border p-3 bg-muted/30`,children:[(0,A.jsxs)(`div`,{className:`text-sm font-medium`,children:[(0,A.jsx)(`span`,{className:e.isCorrect?`text-green-600`:`text-destructive`,children:e.isCorrect?`✓`:`✗`}),` `,`Q`,t+1,`: `,e.questionText]}),!e.isCorrect&&e.correctOptionText&&(0,A.jsxs)(`div`,{className:`text-xs text-green-700 mt-1`,children:[(0,A.jsx)(`strong`,{children:`Correct:`}),` `,e.correctOptionText]}),!e.isCorrect&&e.selectedExplanation&&(0,A.jsxs)(`div`,{className:`text-xs text-destructive mt-1`,children:[(0,A.jsx)(`strong`,{children:`Why incorrect:`}),` `,e.selectedExplanation]}),e.generalExplanation&&(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground mt-1`,children:e.generalExplanation})]},e.questionId))}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,className:_d,onClick:()=>{o(null),i(bd(e.questions))},children:`Retake`}),(0,A.jsx)(`button`,{type:`button`,className:gd,onClick:t,children:`Back to Feed`})]})]})}return(0,A.jsxs)(`section`,{className:pd,"data-testid":`lh-quiz`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Quiz`}),(0,A.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[e.questions.length,` question`,e.questions.length===1?``:`s`]})]}),(0,A.jsxs)(`form`,{onSubmit:u,className:`space-y-4`,children:[e.questions.map((e,t)=>{let n=e.question_type===`multi`,i=e.question_type===`true_false`?`True / False`:n?`Multiple Select`:`Single Choice`;return(0,A.jsxs)(`div`,{className:`rounded-md border border-border p-3 space-y-2 bg-muted/30`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between text-xs text-muted-foreground`,children:[(0,A.jsxs)(`span`,{className:`font-semibold`,children:[`Q`,t+1]}),(0,A.jsx)(`span`,{children:i})]}),(0,A.jsx)(`div`,{className:`text-sm font-medium`,children:e.question_text}),n&&(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground italic`,children:`Select all that apply`}),(0,A.jsx)(`div`,{className:`space-y-1`,children:e.options.map(t=>{let i=r[e.id],a=n?i?.optionIds.has(t.id)===!0:i?.optionId===t.id;return(0,A.jsxs)(`label`,{className:`flex items-start gap-2 text-sm cursor-pointer hover:bg-muted/50 rounded px-2 py-1`,children:[(0,A.jsx)(`input`,{type:n?`checkbox`:`radio`,name:`q-`+e.id,checked:a,onChange:()=>n?f(e,t.id):d(e,t.id),className:`mt-0.5`}),(0,A.jsx)(`span`,{children:t.option_text})]},t.id)})})]},e.id)}),s&&(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:s}),(0,A.jsx)(`button`,{type:`submit`,className:gd,disabled:l.isPending,"data-testid":`btn-lh-submit-quiz`,children:l.isPending?`Submitting…`:`Submit Answers`})]})]})}function Sd({slug:e,onBack:t}){let{data:n,isLoading:r,error:i}=j({queryKey:[`learning-content`,e],queryFn:()=>F.get(`/api/learning/content/`+encodeURIComponent(e))});if(r)return(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Loading…`});if(i)return(0,A.jsx)(`div`,{className:`text-sm text-destructive`,children:i.message});if(!n)return null;let a=n.content;return(0,A.jsxs)(`div`,{className:`space-y-4`,children:[(0,A.jsx)(`button`,{type:`button`,className:_d,onClick:t,"data-testid":`btn-lh-back`,children:`← Back to Feed`}),(0,A.jsxs)(`section`,{className:pd,"data-testid":`lh-viewer`,children:[(0,A.jsxs)(`div`,{className:`flex items-center justify-between gap-4`,children:[(0,A.jsx)(`h2`,{className:`text-xl font-semibold`,"data-testid":`lh-viewer-title`,children:a.title}),(0,A.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[vd(a.content_type),a.category_name?` · `+a.category_name:``,a.author_name?` · `+a.author_name:``]})]}),a.content_type===`presentation`?(0,A.jsxs)(`div`,{className:`text-center py-8 space-y-3 bg-muted/30 rounded-md`,children:[(0,A.jsx)(`div`,{className:`text-4xl`,children:`📊`}),(0,A.jsx)(`div`,{className:`text-sm font-medium`,children:a.title}),(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:`Slide rendering lives in the legacy viewer.`}),(0,A.jsx)(`a`,{href:`/#learning/`+encodeURIComponent(a.slug),className:gd+` inline-block`,rel:`noreferrer`,children:`Open in legacy viewer`})]}):(0,A.jsx)(`div`,{className:`whitespace-pre-wrap text-sm leading-relaxed`,"data-testid":`lh-viewer-body`,children:a.body||``})]}),a.progress&&a.progress.length>0&&(0,A.jsxs)(`section`,{className:pd,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Your past attempts`}),(0,A.jsx)(`div`,{className:`space-y-1 text-sm`,children:a.progress.map((e,t)=>{let n=e.total>0?Math.round(e.score/e.total*100):0,r=n>=70?`text-green-600`:`text-amber-600`;return(0,A.jsxs)(`div`,{className:`flex justify-between border-b border-border py-1`,children:[(0,A.jsx)(`span`,{children:new Date(e.completed_at).toLocaleDateString()}),(0,A.jsxs)(`span`,{className:`font-semibold `+r,children:[e.score,`/`,e.total,` (`,n,`%)`]})]},t)})})]}),a.questions&&a.questions.length>0&&(0,A.jsx)(xd,{content:a,onReset:t})]})}function Cd(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(null),{data:o}=j({queryKey:[`learning-categories`],queryFn:()=>F.get(`/api/learning/categories`)});return i?(0,A.jsx)(`div`,{className:`max-w-4xl mx-auto p-6`,children:(0,A.jsx)(Sd,{slug:i,onBack:()=>a(null)})}):(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Learning Hub`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Pediatric education, clinical pearls, and self-assessment quizzes.`})]}),(0,A.jsx)(`div`,{className:pd,children:(0,A.jsx)(`input`,{type:`search`,className:hd,placeholder:`Search topics, subjects…`,value:e,onChange:e=>t(e.target.value),"data-testid":`lh-search`})}),(0,A.jsxs)(`div`,{className:`flex flex-wrap gap-2`,"data-testid":`lh-categories`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:()=>r(``),className:md+(n===``?` bg-primary text-primary-foreground border-primary`:` bg-muted hover:bg-muted/80`),children:`All`}),o?.categories.map(e=>(0,A.jsx)(`button`,{type:`button`,onClick:()=>r(e.slug),className:md+(n===e.slug?` bg-primary text-primary-foreground border-primary`:` bg-muted hover:bg-muted/80`),"data-testid":`lh-cat-`+e.slug,children:e.name},e.id))]}),(0,A.jsx)(Q,{filter:n,query:e.trim(),onOpen:e=>a(e)})]})}var wd=[`newborn`,`infant`,`toddler`,`preschool`,`school`,`adolescent`],Td=[`msk`,`neuro`,`resp`,`cv`],Ed={msk:`Musculoskeletal`,neuro:`Neuro`,resp:`Respiratory`,cv:`Cardiovascular`},Dd={newborn:{label:`Newborn (0–28 days)`,msk:{overview:`Exam done warm, quiet, undressed. Focus: birth injury, DDH, congenital anomaly. All steps symmetric.`,components:[{name:`Resting posture`,steps:[{label:`Observe posture`,method:`Place supine and undisturbed for 30s`,normal:`Symmetric flexion at hips, knees, elbows`},{label:`Hand position`,method:`Inspect resting hands`,normal:`Loosely fisted; opens intermittently`},{label:`Symmetry`,method:`Compare left vs right side at rest`,normal:`Mirror-image posture`}],abnormalHints:[`Frog-leg (hypotonia)`,`Asymmetric arm (brachial plexus/clavicle fx)`,`Opisthotonos (CNS)`,`Persistent fisting with thumb in palm`]},{name:`Clavicles`,steps:[{label:`Palpate right clavicle`,method:`Trace from sternoclavicular joint to acromion with index finger`,normal:`Smooth, continuous, no step-off`},{label:`Palpate left clavicle`,method:`Same technique on left side`,normal:`Smooth, continuous`},{label:`Crepitus check`,method:`Light pressure along length of clavicle while gently abducting arm`,normal:`No crepitus, no pain response`}],abnormalHints:[`Palpable step-off or callus (fracture — LGA, shoulder dystocia)`,`Asymmetric Moro on affected side`]},{name:`Hips — DDH screen`,steps:[{label:`Thigh/gluteal folds`,method:`Undress completely; compare skin-fold symmetry`,normal:`Symmetric thigh and gluteal folds`},{label:`Abduction`,method:`Flex hips 90°, abduct simultaneously`,normal:`Both hips abduct to ≥75° symmetrically`},{label:`Barlow maneuver`,method:`Thumb on medial thigh, flex hip 90°, adduct, apply gentle posterior pressure`,normal:`No clunk or movement felt (negative)`},{label:`Ortolani maneuver`,method:`From Barlow position: abduct and lift with fingers on greater trochanter`,normal:`No clunk as hip returns (negative)`},{label:`Galeazzi sign`,method:`Knees flexed together with feet on table; compare knee heights`,normal:`Knees level — no leg-length discrepancy`}],abnormalHints:[`Palpable Ortolani clunk (dislocated, reducible)`,`Barlow clunk (dislocatable)`,`Limited abduction`,`Positive Galeazzi (shortened femur)`]},{name:`Spine and back`,steps:[{label:`Position prone`,method:`Turn infant prone, support chest`,normal:`Tolerates position, lifts head briefly`},{label:`Palpate midline`,method:`Run finger from C-spine to coccyx`,normal:`Straight midline, no step-offs or gaps`},{label:`Sacral inspection`,method:`Inspect sacral dimple if present; measure depth, distance from anus`,normal:`No dimple, or dimple <5mm deep and <2.5cm from anus`},{label:`Cutaneous markers`,method:`Inspect midline skin from neck to coccyx`,normal:`No hair tuft, hemangioma, lipoma, or sinus tract`}],abnormalHints:[`Deep sacral dimple (>5mm) or >2.5cm from anus — imaging`,`Hair tuft, hemangioma, lipoma (occult dysraphism)`,`Palpable defect`]},{name:`Upper extremities`,steps:[{label:`Spontaneous movement`,method:`Observe both arms for 30s`,normal:`Symmetric antigravity movement`},{label:`Digits`,method:`Count and inspect fingers both hands`,normal:`5 digits each, no webbing or duplication`},{label:`Palmar creases`,method:`Inspect palmar creases`,normal:`Normal triradiate creases`}],abnormalHints:[`Erb/Klumpke palsy (paucity of movement)`,`Polydactyly, syndactyly`,`Single transverse (simian) palmar crease`]},{name:`Lower extremities / feet`,steps:[{label:`Hip and knee range`,method:`Gently flex, extend, internally/externally rotate each`,normal:`Full symmetric range, no contracture`},{label:`Foot alignment`,method:`Inspect resting foot position`,normal:`Midline or mildly adducted forefoot`},{label:`Passive correction`,method:`Gently attempt to bring foot to neutral`,normal:`Fully correctable to neutral`},{label:`Stroke test`,method:`Stroke lateral border of foot`,normal:`Foot dorsiflexes and everts reflexively`}],abnormalHints:[`Rigid clubfoot (non-correctable talipes equinovarus)`,`Fixed metatarsus adductus`,`Rocker-bottom foot (trisomy 18)`,`Calcaneovalgus`]}]},neuro:{overview:`Primitive reflexes present and symmetric. Tone assessed passively and actively. Full exam 3–5 min on a quiet, fed infant.`,components:[{name:`Alertness and behavior`,steps:[{label:`State cycling`,method:`Observe over 1–2 min for alert periods`,normal:`Alert periods with spontaneous eye opening`},{label:`Response to voice`,method:`Speak softly near ear`,normal:`Quiets to voice or turns toward sound`},{label:`Consolability`,method:`If crying, attempt to soothe with swaddling or voice`,normal:`Consolable within 1–2 min`}],abnormalHints:[`Lethargy`,`Jitteriness not stopped by passive flexion`,`Irritability unrelieved by feeding`]},{name:`Cranial nerves`,steps:[{label:`CN II — pupil response`,method:`Shine light in each eye`,normal:`Pupils equal, reactive to light, direct and consensual`},{label:`CN II — blink to light`,method:`Bright light in the line of sight`,normal:`Reflex blink`},{label:`CN VII — facial symmetry at rest`,method:`Observe resting face`,normal:`Symmetric nasolabial folds`},{label:`CN VII — facial symmetry with cry`,method:`Note face during a cry`,normal:`Symmetric grimace`},{label:`CN IX, X, XII — suck and swallow`,method:`Offer clean gloved finger, pacifier, or during feed`,normal:`Strong coordinated suck-swallow, no choking`}],abnormalHints:[`Asymmetric face (CN VII injury — usually forceps)`,`Poor suck or uncoordinated swallow`,`Fixed or unequal pupil`]},{name:`Tone — passive`,steps:[{label:`Pull-to-sit`,method:`Grasp hands/wrists, pull smoothly to sit`,normal:`Brief head lag at term; not dramatic`},{label:`Ventral suspension`,method:`Suspend prone over hand at chest`,normal:`Head briefly lifts to horizontal; extremities flexed`},{label:`Arm recoil`,method:`Extend both arms fully at elbows, release`,normal:`Rapid return to flexion`},{label:`Popliteal angle`,method:`Hip flexed 90°, extend knee maximally`,normal:`≤110°`}],abnormalHints:[`Marked head lag (hypotonia)`,`Slip-through on vertical suspension`,`Hypertonia / scissoring`,`Floppy limbs with no recoil`]},{name:`Spontaneous movement`,steps:[{label:`Observe limbs`,method:`Undisturbed over 1 min, note movement`,normal:`Symmetric antigravity movement of all 4 limbs`},{label:`Quality of movement`,method:`Note smoothness vs jitteriness`,normal:`Smooth, mildly variable movements`}],abnormalHints:[`Paucity of movement in one limb`,`Coarse jitteriness`,`Clonic jerks`,`Tonic posturing`]},{name:`Primitive reflexes`,steps:[{label:`Moro`,method:`Support head; allow 30° head drop or loud clap`,normal:`Symmetric arm abduction then flexion, often cry`},{label:`Rooting`,method:`Stroke cheek at corner of mouth`,normal:`Turns head toward stroke and opens mouth`},{label:`Palmar grasp`,method:`Press into palm with finger`,normal:`Strong, symmetric finger flexion`},{label:`Plantar grasp`,method:`Press ball of foot below toes`,normal:`Toes flex around finger symmetrically`},{label:`Stepping`,method:`Hold upright with feet touching surface`,normal:`Alternating stepping movements`},{label:`Tonic neck (fencing)`,method:`Turn head to one side with infant supine`,normal:`Same-side arm extends, opposite flexes (not obligate)`},{label:`Galant`,method:`Stroke paravertebrally from shoulder to buttock, one side`,normal:`Trunk curves toward stroked side`}],abnormalHints:[`Absent or asymmetric Moro — CNS injury, brachial plexus, clavicle fx`,`Absent grasps — CNS depression`,`Obligate tonic neck is abnormal`]},{name:`Babinski (plantar response)`,steps:[{label:`Stroke lateral sole`,method:`Firm stroke from heel toward toes along lateral plantar border`,normal:`Up-going great toe with fanning (normal in newborn)`},{label:`Symmetry check`,method:`Repeat opposite foot`,normal:`Symmetric response`}],abnormalHints:[`Asymmetric response is always abnormal`]}]},resp:{overview:`Newborn respiratory transition — RDS, TTN, pneumonia, meconium aspiration dominate the differential. Normal RR ≤ 60. Grunting is an alarm sign.`,components:[{name:`Inspection`,significance:`Detects distress and localises cause. Silverman score quantifies retraction severity.`,pearl:`Grunting is physiologic PEEP against a partially closed glottis — always a sign of significant lung pathology in a newborn. Never dismiss it as fussy breathing.`,steps:[{label:`Respiratory rate`,method:`Count over full 60 s, quiet and undisturbed.`,normal:`40–60 /min; tachypnea > 60`},{label:`Work of breathing (Silverman)`,method:`Inspect for upper-chest retraction, lower-chest retraction, xiphoid retraction, nasal flaring, grunting. Score 0–2 for each.`,normal:`Total Silverman 0 — no distress`},{label:`Audible sounds`,method:`Listen without stethoscope — stridor? grunting? wheeze across the room?`,normal:`Quiet respirations`},{label:`Colour`,method:`Inspect trunk and mucous membranes for central cyanosis; acrocyanosis (blue hands/feet) is normal in the first days.`,normal:`Pink trunk and mucous membranes`},{label:`Chest shape`,method:`Inspect AP:transverse diameter and symmetry.`,normal:`Slightly barrel-shaped is normal; symmetric`}],abnormalHints:[`Grunting — RDS, pneumonia, sepsis, CHD`,`Retractions + tachypnea — RDS, TTN, pneumothorax`,`Central cyanosis — cyanotic CHD, severe lung disease, persistent pulmonary HTN`,`Asymmetric chest movement — pneumothorax, diaphragmatic hernia`]},{name:`Auscultation`,significance:`Short stethoscope time in neonates because they fuss easily — get the most important zones first.`,pearl:`Listen at the axilla, not just the anterior chest — pneumothorax can sound normal anteriorly. Auscultate both axillae systematically.`,steps:[{label:`Air entry — anterior`,method:`Listen bilaterally at the upper and lower anterior chest.`,normal:`Symmetric bilateral air entry`},{label:`Air entry — axillary`,method:`Listen bilaterally at each axilla — this is the most sensitive area for detecting a small pneumothorax.`,normal:`Clear and symmetric`},{label:`Adventitious sounds`,method:`Listen for transmitted upper-airway sounds, crackles (RDS, pneumonia), grunting sounds.`,normal:`No crackles, no wheeze, clear sounds`},{label:`Inspiration:expiration ratio`,method:`Observe breath-sound timing.`,normal:`Inspiration > expiration in length`}],abnormalHints:[`Asymmetric air entry — pneumothorax, diaphragmatic hernia, endobronchial intubation`,`Fine crackles — RDS, TTN, pneumonia`,`Absent breath sounds unilaterally — pneumothorax or selective intubation`]}]},cv:{overview:`Neonatal CV exam screens for CHD — the window of presentation is short and some lesions (duct-dependent) decompensate within hours of birth. Pre/postductal saturations + femoral pulses are the two fastest screens.`,components:[{name:`Inspection and pre/postductal saturations`,significance:`Pre/postductal SpO₂ differential > 3% suggests a duct-dependent lesion or persistent pulmonary HTN. Universal CCHD screening uses this.`,pearl:`Pulse ox on the right hand = preductal (proximal to PDA insertion). Foot = postductal. Both arms and both legs should match; a differential is a red flag for critical CHD.`,steps:[{label:`Central cyanosis`,method:`Inspect tongue, lips, oral mucosa.`,normal:`Pink mucous membranes`},{label:`Pre/postductal SpO₂`,method:`Measure SpO₂ in right hand (preductal) AND either foot (postductal). Baby must be ≥24 hr old for CCHD screening.`,normal:`Both ≥ 95% AND difference < 3%`},{label:`Peripheral perfusion`,method:`Capillary refill on sternum; note mottling or distal cyanosis.`,normal:`Capillary refill < 2 s, warm pink extremities`},{label:`Precordial activity`,method:`Inspect anterior chest for hyperactive precordium.`,normal:`Not visible or minimally visible`}],abnormalHints:[`Central cyanosis with SpO₂ < 95% → cyanotic CHD workup (4-extremity BP, ECG, hyperoxia test, echo)`,`Differential > 3% (pre > post) → duct-dependent systemic flow (HLHS, coarctation, interrupted arch)`,`Preductal < postductal — persistent pulmonary HTN with reversed shunt`]},{name:`Palpation and auscultation`,significance:`Absent femoral pulses + arm-leg BP gradient = coarctation. Many CHD lesions manifest murmurs only after ductus closes (48–72 h).`,pearl:`Always palpate femoral pulses before discharging any newborn. Absent femorals in a well-appearing baby can be the only finding in a ductal-dependent coarctation — catastrophic if missed.`,steps:[{label:`Apex beat`,method:`Palpate at the 4th ICS left of sternum (apex is higher in newborns).`,normal:`Palpable at 4th ICS, mid-clavicular or just lateral`},{label:`Femoral pulses`,method:`Palpate both femoral pulses at the mid-inguinal point while simultaneously feeling the right brachial pulse — detects delay.`,normal:`Present, symmetric, equal timing with brachial`},{label:`Auscultate each cardiac area`,method:`Use pediatric diaphragm at each classic point; baby quiet if possible.`,normal:`S1 S2 crisp; physiologic flow murmur sometimes present in the first 24–48 h`},{label:`Continuous murmur`,method:`Listen below the left clavicle for a continuous ("machinery") murmur of PDA.`,normal:`No continuous murmur after the first day of life in a term baby`},{label:`Four-limb BP (if any concern)`,method:`Right arm, left arm, both legs.`,normal:`Within 10 mmHg across limbs`}],abnormalHints:[`Absent femoral pulses — coarctation of the aorta (surgical emergency if duct-dependent)`,`Harsh holosystolic at LLSB — VSD`,`Continuous machinery murmur — PDA (expected in preterm; in term > 48 h is abnormal)`,`Gallop S3/S4 — heart failure`,`Single S2 — transposition, truncus, severe AS/PS`]}]}},infant:{label:`Infant (1–12 months)`,msk:{overview:`Continued DDH screen through 6 months. Watch motor progression and symmetry of tone/movement.`,components:[{name:`Hips — continued DDH screen`,steps:[{label:`Thigh-fold symmetry`,method:`Inspect with infant supine, thighs flexed`,normal:`Symmetric folds`},{label:`Barlow/Ortolani (through 3mo)`,method:`As in newborn — thumb medial, flex 90°, adduct+push then abduct+lift`,normal:`Negative bilaterally`},{label:`Abduction (any age)`,method:`Flex 90°, abduct simultaneously`,normal:`≥70° symmetric abduction`},{label:`Galeazzi`,method:`Knees flexed feet flat on exam table`,normal:`Knees equal height`}],abnormalHints:[`Clunk on Barlow/Ortolani (<3mo)`,`Limited abduction`,`Asymmetric folds`,`Positive Galeazzi`]},{name:`Gross-motor milestones (age-appropriate)`,steps:[{label:`Head control`,method:`Prone, pull-to-sit, held upright`,normal:`Age-appropriate: 2mo lifts head 45°; 4mo no head lag`},{label:`Rolling`,method:`Observe on flat surface`,normal:`Rolls back-to-front by 5–6mo`},{label:`Sitting`,method:`Place in sitting position`,normal:`Tripod sit by 6mo; sits without support by 7–8mo`},{label:`Standing`,method:`Support under arms`,normal:`Bears weight by 6mo; pulls to stand by 9mo`}],abnormalHints:[`Milestone delay by ≥2mo`,`Loss of previously attained milestone`,`Asymmetric use of limbs`]},{name:`Spine`,steps:[{label:`Palpate seated`,method:`Run finger along spine with infant sitting or held upright`,normal:`Midline, no step-offs`},{label:`Back curvature`,method:`Inspect sitting and lying`,normal:`Physiologic gentle kyphosis in early infancy; lumbar lordosis as sitting develops`}],abnormalHints:[`Fixed kyphoscoliosis`,`Sacral dimple with cutaneous marker`,`Step-off`]},{name:`Extremities`,steps:[{label:`Passive range`,method:`Through each major joint`,normal:`Full symmetric range`},{label:`Joint inspection`,method:`Inspect for swelling, warmth, effusion`,normal:`No swelling, warmth, or effusion`},{label:`Feet alignment`,method:`Observe standing if pulling up, else passive positioning`,normal:`Correctable or aligned feet; flexible`}],abnormalHints:[`Joint swelling/warmth (septic vs reactive arthritis)`,`Rigid clubfoot`,`Persistent asymmetric tone`]}]},neuro:{overview:`Primitive reflexes fading on schedule; protective reflexes emerging; gross motor and tone interlinked.`,components:[{name:`Alertness and social engagement`,steps:[{label:`Social smile`,method:`Face-to-face interaction`,normal:`Social smile by 6–8 weeks`},{label:`Tracking`,method:`Move object across visual field`,normal:`180° horizontal tracking by 3mo`},{label:`Engagement`,method:`Face-to-face, voice, toys`,normal:`Age-appropriate reciprocal interaction`}],abnormalHints:[`No social smile by 3mo`,`Absent tracking past 3mo`,`Poor engagement (developmental concern)`]},{name:`Cranial nerves`,steps:[{label:`Pupils`,method:`Light response each eye`,normal:`Equal reactive`},{label:`Eye alignment`,method:`Inspect with gaze forward and in all directions`,normal:`No strabismus past 4mo`},{label:`Facial symmetry`,method:`Observe smile and cry`,normal:`Symmetric`},{label:`Suck and swallow`,method:`Observe feeding`,normal:`Coordinated, no choking`}],abnormalHints:[`Persistent nystagmus`,`Strabismus past 4mo`,`Asymmetric face`]},{name:`Tone`,steps:[{label:`Pull-to-sit`,method:`Gently pull wrists/hands`,normal:`No head lag by 4mo`},{label:`Ventral suspension`,method:`Support prone, observe`,normal:`Head above horizontal, extremities actively flexed (age-dependent)`},{label:`Vertical suspension`,method:`Support under arms, lift`,normal:`Does not slip through hands`}],abnormalHints:[`Persistent head lag past 4mo (hypotonia)`,`Hypertonia / scissoring (UMN)`,`Slip-through on vertical suspension`]},{name:`Primitive reflex integration`,steps:[{label:`Moro`,method:`Head drop or clap`,normal:`Absent by 6mo`},{label:`Palmar grasp`,method:`Press into palm`,normal:`Integrated by 5–6mo; replaced by voluntary grasp`},{label:`Tonic neck`,method:`Turn head to side`,normal:`Absent by 6mo`},{label:`Rooting`,method:`Stroke cheek`,normal:`Absent by 3–4mo`}],abnormalHints:[`Persistence of any primitive reflex past 6mo warrants eval (CP, CNS injury)`]},{name:`Protective and postural reflexes`,steps:[{label:`Parachute`,method:`Held prone, tilt head downward suddenly`,normal:`Symmetric arm extension protectively by 9mo (emerges 6–9mo)`},{label:`Lateral propping`,method:`Seated infant, gentle tilt to one side`,normal:`Arm extends to catch by 6–7mo`},{label:`Landau`,method:`Suspend prone`,normal:`Extends head, spine, legs by 6mo`}],abnormalHints:[`Absent parachute after 12mo (concerning)`,`Asymmetric lateral propping`,`Absent Landau`]}]},resp:{overview:`Infant respiratory disease centers on bronchiolitis, reactive airways, and pneumonia. Normal RR ≤ 50 (< 2 mo: ≤ 60). Infants are obligate nose-breathers — nasal congestion alone can cause significant WOB.`,components:[{name:`Inspection`,significance:`Infant distress signs escalate fast. Nasal flaring, tracheal tug, head bobbing = significant WOB. Apnea in an infant < 2 mo is an emergency.`,pearl:`A quiet infant with retractions is more worrying than a crying one — exhausted infants stop crying and become hypoxic silently.`,steps:[{label:`Respiratory rate`,method:`Count over full 60 s while quiet.`,normal:`< 2 mo: ≤ 60; 2–12 mo: ≤ 50`},{label:`Work of breathing`,method:`Inspect nasal flaring, subcostal/intercostal/suprasternal retractions, tracheal tug, head-bobbing, accessory muscle use.`,normal:`No retractions, effortless breathing`},{label:`Audible sounds`,method:`Stridor? Wheeze across the room? Grunting? Prolonged expiration?`,normal:`Quiet respirations`},{label:`Colour and feeding history`,method:`Central cyanosis? Poor feeding (feeding is an effort marker in infants)?`,normal:`Pink, feeds well`},{label:`Apnea observation`,method:`Watch for ≥ 20-s pauses or pauses < 20 s with bradycardia/cyanosis.`,normal:`No apneas`}],abnormalHints:[`Grunting / persistent retractions — pneumonia, bronchiolitis, CHF`,`Wheeze — bronchiolitis (RSV), asthma, foreign body`,`Stridor — croup (6 mo–6 y), laryngomalacia (infant), foreign body`,`Apnea — bronchiolitis, sepsis, pertussis, seizure`]},{name:`Auscultation`,significance:`Infants have a thin chest wall — sounds transmit widely. Symmetry, wheeze, and crackles are the main findings.`,pearl:`In bronchiolitis, the classical finding is widespread end-inspiratory fine crackles PLUS expiratory wheeze. Tachypnea + retractions in an RSV-season infant confirms.`,steps:[{label:`Air entry — bilateral`,method:`Warm stethoscope; listen at anterior chest and both axillae, both sides.`,normal:`Symmetric air entry`},{label:`Wheeze`,method:`Listen in expiration. Diffuse wheeze = lower airway; focal wheeze = foreign body or local obstruction.`,normal:`No wheeze`},{label:`Crackles`,method:`Listen in late inspiration. Focal = pneumonia; diffuse fine = bronchiolitis.`,normal:`No crackles`},{label:`Prolonged expiration`,method:`Note expiration:inspiration length ratio.`,normal:`Inspiration ≥ expiration`}],abnormalHints:[`Focal crackles + fever — pneumonia`,`Diffuse wheeze + fine crackles in an RSV-season infant — bronchiolitis`,`Silent chest with extreme WOB — impending respiratory failure`]}]},cv:{overview:`Most CHD manifests in the first year as pulmonary blood-flow changes and the ductus closes. Infant CV exam = growth review + inspection + femoral pulses + auscultation. A harsh pan-systolic LLSB murmur in a 6-week-old = VSD until proven otherwise.`,components:[{name:`Inspection and functional assessment`,significance:`Heart failure in infants presents as poor feeding, sweating during feeds (diaphoresis), tachypnea, and poor weight gain. These historical features predict bad exam findings.`,pearl:`Ask "Does the baby sweat while feeding?" — infant CHF presents with diaphoresis on the forehead during feeds, well before peripheral edema appears.`,steps:[{label:`Growth trajectory`,method:`Plot weight-for-age on WHO chart. Failure to thrive raises CHD concern.`,normal:`Tracking ≥ 10th percentile or stable on personal curve`},{label:`Feeding history`,method:`Ask about feed duration, sweating with feeds, tachypnea with feeds, tiring easily.`,normal:`Feeds < 20 min, no diaphoresis, no tachypnea`},{label:`Central cyanosis`,method:`Inspect tongue and oral mucosa.`,normal:`Pink`},{label:`Clubbing`,method:`Inspect finger nail beds (subtle in infants).`,normal:`No clubbing`},{label:`Peripheral perfusion`,method:`Cap refill, warmth of extremities.`,normal:`Cap refill < 2 s, warm extremities`}],abnormalHints:[`Poor weight gain — consider CHF from L-to-R shunt (VSD, PDA, AVSD)`,`Diaphoresis with feeds — infant CHF`,`Tiring with feeds — significant CHD`,`Central cyanosis — cyanotic CHD (ToF, TGA, TA, etc.)`]},{name:`Palpation and auscultation`,significance:`Femoral pulses + 4-limb BP screen coarctation. A harsh holosystolic murmur at LLSB is almost always a VSD in this age.`,pearl:`Listen over each of the 5 classic points as in the adult exam — the locations shift slightly with infant chest size but relative positions are the same. Also listen at the back: coarctation murmurs radiate there.`,steps:[{label:`Apex beat`,method:`Palpate at 4th ICS mid-clavicular line.`,normal:`Palpable at 4th ICS in infants`},{label:`Femoral pulses`,method:`Palpate bilaterally, simultaneously with right brachial.`,normal:`Present, equal, no delay vs brachial`},{label:`Listen at each classic area (APTM)`,method:`See APTM diagram. Use pediatric stethoscope with both diaphragm and bell.`,normal:`S1 and S2 crisp, no murmur or physiologic only`},{label:`Listen over the back (interscapular)`,method:`Check for radiation of coarctation murmurs.`,normal:`No radiating murmur`}],abnormalHints:[`Harsh holosystolic LLSB murmur — VSD`,`Continuous "machinery" murmur below left clavicle — PDA`,`Systolic ejection at ULSB + fixed split S2 — ASD`,`Ejection murmur at ULSB + cyanosis — tetralogy of Fallot`,`Absent femorals + radio-femoral delay — coarctation`,`Gallop + tachycardia — heart failure`]}]}},toddler:{label:`Toddler (1–3 years)`,msk:{overview:`Gait, physiologic alignment changes, in-toeing, joint range. Cooperation unpredictable — use play.`,components:[{name:`Gait`,steps:[{label:`Base of support`,method:`Have child walk ~10 feet`,normal:`Wide-based initially, narrowing by 2y`},{label:`Heel-strike`,method:`Observe foot contact pattern`,normal:`Heel-strike developing by 18mo, consistent by 2y`},{label:`Arm swing`,method:`Observe reciprocal arm swing`,normal:`Reciprocal arm swing by 18mo`},{label:`Symmetry`,method:`Watch both sides in stance and swing`,normal:`Symmetric stride length and cadence`}],abnormalHints:[`Toe-walking past 2y (idiopathic vs CP vs DMD)`,`Limp (Legg-Calvé-Perthes, transient synovitis, trauma)`,`Wide-based ataxia`]},{name:`Knee alignment`,steps:[{label:`Stand feet together`,method:`Feet/medial malleoli touching; inspect knees`,normal:`Genu varum resolving by 18–24mo; mild genu valgum common by 2–3y`},{label:`Intercondylar or intermalleolar distance`,method:`Measure if alignment appears abnormal`,normal:`<5cm intercondylar (varum) or <8cm intermalleolar (valgum)`},{label:`Symmetry`,method:`Compare sides`,normal:`Symmetric`}],abnormalHints:[`Persistent varum past 2y`,`Severe valgum >8cm`,`Unilateral (Blount disease, rickets)`]},{name:`Feet alignment`,steps:[{label:`In-toeing / out-toeing`,method:`Observe foot angle during gait`,normal:`Mild in-toeing common (tibial torsion, femoral anteversion)`},{label:`Arch during stance`,method:`Stand flat, then on tiptoes`,normal:`Flexible flat foot that forms arch on tiptoe`},{label:`Heel alignment`,method:`View from behind standing`,normal:`Neutral or mildly valgus heel`}],abnormalHints:[`Rigid flat foot (tarsal coalition)`,`Fixed metatarsus adductus`,`Severe in-toeing >15°`]},{name:`Joint range`,steps:[{label:`Hips`,method:`Flex, abduct, rotate each hip`,normal:`Full symmetric range, no pain`},{label:`Knees`,method:`Flex, extend; palpate for effusion`,normal:`Full range, no effusion, stable`},{label:`Ankles`,method:`Dorsiflex, plantarflex, invert, evert`,normal:`Full range`},{label:`Shoulders, elbows, wrists`,method:`Through full range each`,normal:`Full symmetric range`}],abnormalHints:[`Joint effusion (septic vs reactive)`,`Guarded or painful motion`,`Asymmetric limitation`]},{name:`Spine`,steps:[{label:`Inspect standing`,method:`View spine from behind`,normal:`Midline, no prominent curve`},{label:`Palpate midline`,method:`Run finger along spinous processes`,normal:`Midline, no step-off, no tenderness`},{label:`Shoulder/hip symmetry`,method:`Compare shoulder and hip heights`,normal:`Symmetric`}],abnormalHints:[`Fixed scoliosis`,`Abnormal kyphosis`,`Asymmetric shoulder/hip`,`Midline tenderness`]}]},neuro:{overview:`Shifting to adult-pattern exam. Primitive reflexes should be absent. Use play-based techniques.`,components:[{name:`Mental status and language`,steps:[{label:`Engagement`,method:`Interaction with examiner/parent`,normal:`Alert, interactive, age-appropriate`},{label:`Language — expressive`,method:`Note spontaneous utterances`,normal:`1y: 1–3 words; 2y: 2-word phrases; 3y: short sentences`},{label:`Language — receptive`,method:`Ask to point to body parts or follow simple commands`,normal:`Follows age-appropriate commands`}],abnormalHints:[`Language regression (autism, epileptic encephalopathy)`,`Poor engagement`,`No 2-word phrases by 2y`]},{name:`Cranial nerves`,steps:[{label:`CN II — pupil response`,method:`Shine light each eye`,normal:`Pupils equal reactive`},{label:`CN III, IV, VI — EOM`,method:`Follow toy in H pattern`,normal:`Full smooth tracking, no strabismus`},{label:`CN V — facial sensation`,method:`Light touch on forehead, cheek, jaw`,normal:`Responds to touch, symmetric`},{label:`CN VII — face`,method:`Elicit smile, watch eye closure during cry`,normal:`Symmetric face`},{label:`CN IX, X, XII — mouth`,method:`Say "ahh"; stick out tongue`,normal:`Palate rises symmetrically; tongue midline`}],abnormalHints:[`Strabismus`,`Facial asymmetry`,`Tongue deviation`,`Absent palate elevation`]},{name:`Motor — tone and bulk`,steps:[{label:`Passive tone`,method:`Move each limb through full range`,normal:`Normal resistance throughout`},{label:`Bulk inspection`,method:`Inspect muscle bulk of thighs, calves, glutes`,normal:`Symmetric, age-appropriate bulk`},{label:`Contracture check`,method:`Test for heel-cord tightness, hamstring tightness`,normal:`No contractures`}],abnormalHints:[`Spasticity (especially catch in ankles)`,`Hypotonia`,`Calf pseudo-hypertrophy (DMD)`]},{name:`Motor — functional strength`,steps:[{label:`Rising from floor`,method:`Place flat on back; ask to stand up`,normal:`Rises without using hands to push off thighs (no Gowers)`},{label:`Climbing stairs`,method:`Observe or history`,normal:`Climbs holding rail`},{label:`Squatting/getting up`,method:`Encourage via play`,normal:`Squats and rises without help`}],abnormalHints:[`Gowers sign (proximal weakness — DMD)`,`Unable to climb stairs at age expected`,`Calf pain on walking (myositis)`]},{name:`Deep tendon reflexes`,steps:[{label:`Patellar`,method:`Sitting or supine with distraction (toy)`,normal:`2+ symmetric`},{label:`Biceps`,method:`Arm at rest, strike thumb on tendon`,normal:`2+ symmetric`},{label:`Achilles`,method:`With distraction`,normal:`2+ symmetric`}],abnormalHints:[`Hyperreflexia or clonus (UMN, CP)`,`Absent reflexes (LMN, neuropathy)`,`Asymmetry`]},{name:`Coordination and gait`,steps:[{label:`Run`,method:`Watch run ~10 feet`,normal:`Runs with reciprocal arm swing by 2y`},{label:`Stairs`,method:`Observe`,normal:`Climbs with rail`},{label:`Kick ball`,method:`Place ball; observe kick`,normal:`Kicks by 2y`},{label:`Ataxia check`,method:`Watch for fluency of movement`,normal:`Smooth, no tremor`}],abnormalHints:[`Ataxic gait`,`Intention tremor`,`Clumsiness beyond age`]},{name:`Plantar response`,steps:[{label:`Stroke lateral sole`,method:`Firm stroke from heel to toes`,normal:`Down-going great toe (plantar flexion) by age 2`}],abnormalHints:[`Up-going toe after age 2 = UMN sign (Babinski positive)`]}]},resp:{overview:`Toddler respiratory disease: viral URIs, reactive airways, croup (6 mo–6 y classical age), foreign-body aspiration (age 1–3 is peak). Normal RR ≤ 40.`,components:[{name:`Inspection`,pearl:`Sudden onset of unilateral wheeze + choking history in a toddler = foreign body until proven otherwise. CXR in expiration (or decubitus) helps show the trapped air.`,steps:[{label:`Respiratory rate`,method:`Count over full 60 s if possible.`,normal:`≤ 40 /min`},{label:`Work of breathing`,method:`Retractions, nasal flaring, tracheal tug.`,normal:`No retractions`},{label:`Audible sounds`,method:`Stridor (croup), wheeze, barking cough.`,normal:`Quiet respirations`},{label:`Drooling / posture`,method:`Tripod positioning, drooling (epiglottitis in unvaccinated child).`,normal:`No drooling, normal posture`}],abnormalHints:[`Barking cough + stridor — croup`,`Drooling + tripod + toxic — epiglottitis (emergency)`,`Sudden unilateral wheeze — foreign body aspiration`]},{name:`Auscultation`,steps:[{label:`Air entry`,method:`Cooperation variable — listen quickly and systematically.`,normal:`Symmetric`},{label:`Adventitious sounds`,method:`Wheeze, crackles, stridor at the neck.`,normal:`Clear lung fields`},{label:`Unilateral findings`,method:`Focal wheeze, decreased air entry, or asymmetry — think foreign body or pneumonia.`,normal:`Symmetric bilateral`}],abnormalHints:[`Unilateral decreased breath sounds + wheeze — foreign body`,`Focal crackles — pneumonia`,`Diffuse wheeze — asthma/RAD`]}]},cv:{overview:`Most hemodynamically significant CHD has been detected by this age. Innocent murmurs peak here (Still's murmur, venous hum). The exam is adult-pattern but with smaller chest and less cooperation.`,components:[{name:`Inspection and palpation`,pearl:`Innocent murmurs are a normal finding in well toddlers — soft, systolic, at the LLSB, musical, and they change with position. Anything that doesn't fit that pattern deserves referral.`,steps:[{label:`General appearance + growth`,method:`Happy, active, tracking growth.`,normal:`Normal growth and activity`},{label:`Colour and clubbing`,method:`Inspect tongue, nail beds.`,normal:`Pink, no clubbing`},{label:`Apex beat`,method:`Palpate at 5th ICS mid-clavicular line.`,normal:`Located at 5th ICS MCL, tapping quality`},{label:`Peripheral pulses`,method:`Brachial + femoral, symmetric and simultaneous.`,normal:`Symmetric, no delay`}],abnormalHints:[`Tiring with play, poor growth — missed CHD`,`Cyanosis + clubbing — cyanotic CHD`,`Absent femorals — coarctation`]},{name:`Auscultation`,steps:[{label:`All 5 classic points`,method:`See APTM diagram above — Aortic, Pulmonic, Erb's, Tricuspid, Mitral.`,normal:`Crisp S1, S2 with physiologic split at pulmonic area`},{label:`Evaluate any murmur`,method:`Timing, location, radiation, grade. Apply the "7 S" innocent-murmur criteria.`,normal:`No murmur, or soft (≤ grade 2) innocent murmur`},{label:`Change with position`,method:`Have toddler sit, stand, lie down — does the murmur change? Innocent murmurs typically disappear or soften with standing.`,normal:`Murmur (if any) changes with position`}],abnormalHints:[`Harsh, loud (≥3/6), radiating, or diastolic murmur — not innocent, refer`,`Cyanosis + murmur — CHD workup`,`Fixed split S2 — ASD`]}]}},preschool:{label:`Preschool (3–5 years)`,msk:{overview:`Functional gait maneuvers, scoliosis screen, resolving physiologic alignment.`,components:[{name:`Gait — multiple patterns`,steps:[{label:`Normal gait`,method:`Walk ~15 feet barefoot`,normal:`Symmetric, smooth, reciprocal arm swing`},{label:`Heel walking`,method:`Walk on heels only`,normal:`Able by 4y`},{label:`Toe walking`,method:`Walk on toes only`,normal:`Able by 4y`},{label:`Tandem walking`,method:`Heel-to-toe along a line for 5 steps`,normal:`Able by 4y with minimal deviation`},{label:`Hopping`,method:`Hop on one foot`,normal:`3–5 hops on preferred foot by 4y`}],abnormalHints:[`Persistent toe-walking`,`Asymmetric stance/stride`,`Difficulty with any pattern`]},{name:`Alignment`,steps:[{label:`Knee alignment`,method:`Stand feet together`,normal:`Mild residual valgus resolving; neutral by 6–7y`},{label:`Foot arch`,method:`Standing, then tiptoe`,normal:`Arch forms on tiptoe (flexible flat foot)`},{label:`Heel position`,method:`View from behind standing`,normal:`Neutral or mild valgus`}],abnormalHints:[`Persistent unilateral varum`,`Rigid flat foot (no arch on tiptoe)`,`Pes cavus`]},{name:`Scoliosis screen (Adam forward-bend)`,steps:[{label:`Position`,method:`Feet together, bend forward at waist, arms hanging, palms together`,normal:`Arms and head relaxed`},{label:`View from behind`,method:`Examiner at same height; look along back`,normal:`Symmetric paraspinal contour`},{label:`View from side`,method:`Side view for kyphosis`,normal:`Smooth thoracic curve`}],abnormalHints:[`Rib hump (thoracic scoliosis)`,`Lumbar prominence`,`Asymmetric scapular height`]},{name:`Joint range and stability`,steps:[{label:`Active range`,method:`Ask to perform full range at hips, knees, ankles, shoulders, elbows, wrists`,normal:`Full symmetric range, no pain`},{label:`Knee stability`,method:`Palpate for effusion; assess stability if complaint`,normal:`Stable, no effusion`},{label:`Carrying angle (elbows)`,method:`Arms at sides, palms forward`,normal:`Normal valgus carrying angle`}],abnormalHints:[`Joint hypermobility (Beighton score)`,`Effusion`,`Pain with motion`,`Valgus >15°`]},{name:`Feet`,steps:[{label:`Standing inspection`,method:`View from front and behind`,normal:`Symmetric feet, neutral heel, flexible flat feet common`},{label:`Tiptoe`,method:`Stand on tiptoes`,normal:`Arch forms, symmetric`},{label:`Pes planus vs cavus`,method:`Note arch height`,normal:`Flexible flat foot or mild arch`}],abnormalHints:[`Rigid flat foot`,`Pes cavus (Charcot-Marie-Tooth)`,`Pain`]}]},neuro:{overview:`Formal pediatric neuro exam now feasible — most 4- and 5-year-olds cooperate with structured testing.`,components:[{name:`Mental status and language`,steps:[{label:`Orientation (age-appropriate)`,method:`Ask name, age, where you are`,normal:`Knows name and age by 3y; location by 4–5y`},{label:`Speech intelligibility`,method:`Listen to spontaneous speech`,normal:`Strangers understand by 4y`},{label:`Receptive language`,method:`2- and 3-step commands`,normal:`Follows 3-step commands by 4–5y`}],abnormalHints:[`Dysarthria`,`Expressive or receptive language delay`,`Inattention`]},{name:`Cranial nerves (II–XII)`,steps:[{label:`CN II — visual acuity`,method:`HOTV chart or pictures at 10ft, each eye`,normal:`20/30 or better by 4y; 20/25 by 5y`},{label:`CN II — visual fields`,method:`Confrontation with toys from periphery`,normal:`Full fields`},{label:`CN II — pupils`,method:`Light response each eye`,normal:`PERRL`},{label:`CN III, IV, VI — EOM`,method:`Follow toy in H pattern; include convergence`,normal:`Full EOM, convergence present`},{label:`CN V — sensation`,method:`Light touch forehead, cheek, jaw each side`,normal:`Intact, symmetric`},{label:`CN V — motor`,method:`Clench teeth, palpate masseter`,normal:`Symmetric strong bulk`},{label:`CN VII — face`,method:`Smile, wrinkle forehead, close eyes, puff cheeks`,normal:`Symmetric movement of all regions`},{label:`CN VIII — hearing`,method:`Finger rub each ear or whispered words`,normal:`Intact bilaterally`},{label:`CN IX, X — palate`,method:`Open mouth, say "ahh"`,normal:`Palate rises symmetrically, uvula midline`},{label:`CN XI — SCM/trapezius`,method:`Shrug shoulders, turn head against resistance`,normal:`Symmetric strength`},{label:`CN XII — tongue`,method:`Stick tongue out, move side to side`,normal:`Midline, no atrophy, full movement`}],abnormalHints:[`Strabismus (amblyopia risk)`,`Facial weakness`,`Tongue deviation/fasciculations`,`Uvula off-midline`]},{name:`Motor — tone, bulk, strength`,steps:[{label:`Tone inspection`,method:`Passive range all four limbs`,normal:`Normal tone throughout`},{label:`Bulk inspection`,method:`Observe muscle bulk symmetry`,normal:`Symmetric, age-appropriate`},{label:`Strength — shoulder abduction`,method:`Arms out, push down against resistance`,normal:`5/5 bilaterally`},{label:`Strength — elbow flexion`,method:`Flex elbow against resistance`,normal:`5/5 bilaterally`},{label:`Strength — grip`,method:`Squeeze examiner's fingers`,normal:`5/5 symmetric`},{label:`Strength — hip flexion`,method:`Lift leg off table against resistance`,normal:`5/5 bilaterally`},{label:`Strength — knee extension`,method:`Straighten knee against resistance`,normal:`5/5 bilaterally`},{label:`Strength — dorsiflexion`,method:`Pull toes up against resistance`,normal:`5/5 bilaterally`}],abnormalHints:[`Focal weakness`,`Gowers sign`,`Pseudohypertrophy (DMD)`,`Atrophy`]},{name:`Deep tendon reflexes`,steps:[{label:`Biceps`,method:`Thumb on biceps tendon, strike`,normal:`2+ symmetric`},{label:`Patellar`,method:`Knees hanging, strike patellar tendon`,normal:`2+ symmetric`},{label:`Achilles`,method:`Slight dorsiflexion, strike Achilles tendon`,normal:`2+ symmetric`},{label:`Plantar response`,method:`Stroke lateral sole heel-to-toes`,normal:`Down-going great toe`}],abnormalHints:[`Hyperreflexia or clonus (UMN)`,`Hyporeflexia (LMN)`,`Up-going plantar (Babinski — abnormal past 2y)`,`Asymmetry`]},{name:`Coordination`,steps:[{label:`Finger-to-nose`,method:`Touch examiner's finger then own nose, repeat`,normal:`Smooth, no dysmetria`},{label:`Heel-to-shin`,method:`Run heel down opposite shin`,normal:`Smooth bilaterally`},{label:`Rapid alternating movements`,method:`Tap palm with opposite hand alternating palm/back`,normal:`Rhythmic, symmetric`},{label:`Tandem walk`,method:`Heel-to-toe for 5 steps`,normal:`Minimal deviation`}],abnormalHints:[`Dysmetria`,`Dysdiadochokinesia`,`Intention tremor`,`Ataxic tandem`]},{name:`Sensory`,steps:[{label:`Light touch — hands`,method:`Cotton wisp on palm/dorsum, eyes closed`,normal:`Feels each touch`},{label:`Light touch — feet`,method:`Same on dorsum of foot bilaterally`,normal:`Feels each touch`}],abnormalHints:[`Focal sensory loss`,`Stocking-glove loss`]},{name:`Gait and Romberg`,steps:[{label:`Normal gait`,method:`Walk ~20 feet`,normal:`Smooth, symmetric`},{label:`Heel walk`,method:`Walk on heels`,normal:`Able without difficulty`},{label:`Toe walk`,method:`Walk on toes`,normal:`Able without difficulty`},{label:`Tandem`,method:`Heel-to-toe`,normal:`Intact`},{label:`Romberg (5y+)`,method:`Feet together, eyes closed, stand 10s`,normal:`Stable without sway`}],abnormalHints:[`Ataxic gait (cerebellar)`,`Romberg positive (dorsal column)`,`Circumduction (UMN)`]}]},resp:{overview:`Adult-pattern but shorter. Cooperation better than toddler. RR ≤ 30. Common: asthma/RAD, pneumonia, URIs.`,components:[{name:`Inspection`,steps:[{label:`Respiratory rate`,method:`Count over full 60 s quietly.`,normal:`≤ 30 /min`},{label:`Work of breathing`,method:`Retractions, nasal flaring, accessory muscle use.`,normal:`Effortless breathing`},{label:`Audible sounds`,method:`Wheeze, stridor, cough quality (barking = croup).`,normal:`Quiet`},{label:`Chest shape`,method:`AP:transverse, hyperinflation signs.`,normal:`Not barrel-chested`}],abnormalHints:[`Barrel chest — chronic asthma, cystic fibrosis`,`Retractions + wheeze — asthma exacerbation`]},{name:`Auscultation`,steps:[{label:`Systematic zones`,method:`Upper, mid, lower fields anteriorly and posteriorly; axillae bilaterally. Cooperative deep breaths through mouth.`,normal:`Symmetric vesicular sounds`},{label:`Wheeze`,method:`Expiratory, diffuse (asthma) or focal (foreign body, rare at this age).`,normal:`No wheeze`},{label:`Crackles`,method:`Focal = pneumonia; diffuse fine = interstitial disease (rare in kids).`,normal:`No crackles`}],abnormalHints:[`Focal crackles + fever — pneumonia`,`Diffuse wheeze — asthma`,`Prolonged expiration with wheeze — lower airway obstruction`]}]},cv:{overview:`Most CHD is detected by this age. Innocent murmurs peak in this range. Sports participation exams require thorough CV screening.`,components:[{name:`Inspection and palpation`,steps:[{label:`General and growth`,method:`Track on growth curve; activity tolerance.`,normal:`Normal growth, active`},{label:`Apex beat`,method:`5th ICS mid-clavicular line.`,normal:`Normal position and character`},{label:`Peripheral pulses`,method:`Brachial + femoral simultaneously. BP in arm and leg if HTN.`,normal:`Symmetric, no delay`}],abnormalHints:[`Absent femorals or arm-leg BP gradient — coarctation (always check in HTN screening)`,`Displaced apex — cardiomegaly`]},{name:`Auscultation`,pearl:`The 7 "S" criteria and the 5 classic innocent murmurs (see panel above) handle most murmurs you'll find in this age group. Still's murmur is the single most common.`,steps:[{label:`All 5 classic points`,method:`Walk through A → P → E → T → M with diaphragm then bell.`,normal:`S1 S2 clear, physiologic S2 split at pulmonic, no added sounds`},{label:`Any murmur`,method:`Characterise: timing, location, radiation, grade, character. Apply 7 S criteria + compare to innocent-murmur panel.`,normal:`No murmur, or innocent flow murmur meeting all 7 S criteria`},{label:`Position change`,method:`Standing vs supine. Innocent murmurs typically soften or disappear on standing.`,normal:`Murmur (if any) changes with position`},{label:`Sports screening extras (if applicable)`,method:`Screen for HOCM — murmur intensifies with Valsalva and standing (opposite of most).`,normal:`No murmur worsening on Valsalva`}],abnormalHints:[`Murmur breaking any of the 7 S criteria — refer`,`Harsh systolic at LUSB + fixed split S2 — ASD`,`Murmur louder with Valsalva — HOCM (sports participation risk)`,`Diastolic murmur — always pathologic`]}]}},school:{label:`School-age (6–11 years)`,msk:{overview:`Scoliosis screening peri-puberty, sports overuse injuries, resolving alignment.`,components:[{name:`Scoliosis screen (forward-bend + scoliometer)`,steps:[{label:`Standing inspection`,method:`Shoulders and iliac crest heights`,normal:`Symmetric shoulder and pelvic heights`},{label:`Forward bend (Adam test)`,method:`Feet together, bend forward at waist, arms hanging palms together`,normal:`Symmetric paraspinal contour`},{label:`Rib hump`,method:`View tangentially from behind at level of curve`,normal:`No rib hump`},{label:`Lumbar prominence`,method:`Same view at lumbar level`,normal:`No prominence`},{label:`Scoliometer (if available)`,method:`Place scoliometer across rib hump, read angle of trunk rotation`,normal:`ATR < 5°; ≥7° → refer`}],abnormalHints:[`Rib hump`,`ATR ≥7°`,`Asymmetric shoulders or pelvis`,`Decompensation (plumb line offset)`]},{name:`Back and spine`,steps:[{label:`Posture inspection`,method:`Standing, view front/back/side`,normal:`Normal spinal curves; plumb line centered`},{label:`Palpate spinous processes`,method:`From C2 to S1`,normal:`No tenderness, no step-off`},{label:`Range of motion`,method:`Flex, extend, lateral bend, rotate`,normal:`Full painless range`}],abnormalHints:[`Midline tenderness`,`Step-off (spondylolisthesis)`,`Limited motion with pain`]},{name:`Alignment`,steps:[{label:`Knees`,method:`Feet together, inspect`,normal:`Neutral alignment by 6–7y`},{label:`Feet`,method:`Stand, then tiptoes`,normal:`Medial arch present, symmetric`},{label:`Leg lengths`,method:`Supine, measure ASIS to medial malleolus if asymmetric`,normal:`Equal within 1cm`}],abnormalHints:[`Residual valgum`,`Pes cavus`,`Leg-length discrepancy >1cm`]},{name:`Joint stability and sports exam (if active)`,steps:[{label:`Active range all joints`,method:`Through full range`,normal:`Full symmetric range, no pain or crepitus`},{label:`Knee — Lachman (if sports-active)`,method:`Knee 20° flexion, stabilize femur, pull tibia forward`,normal:`Firm endpoint, no laxity`},{label:`Knee — McMurray`,method:`Flexed knee, rotate tibia while extending`,normal:`No pain or click`},{label:`Shoulder — impingement (Neer/Hawkins)`,method:`Passive shoulder flexion with arm in internal rotation`,normal:`No pain`},{label:`Ankle stability`,method:`Anterior drawer and talar tilt`,normal:`No laxity`}],abnormalHints:[`ACL laxity (positive Lachman)`,`Meniscal click`,`Shoulder impingement`,`Ankle instability`]},{name:`Gait and functional movement`,steps:[{label:`Normal gait`,method:`Walk 20 feet`,normal:`Smooth, symmetric`},{label:`Single-leg stance`,method:`Stand on one foot 10s each side`,normal:`Stable without Trendelenburg drop`},{label:`Squat`,method:`Full squat and rise`,normal:`Full squat without pain or asymmetry`},{label:`Hop on one foot`,method:`5 hops each side`,normal:`Able and symmetric`}],abnormalHints:[`Trendelenburg sign (hip abductor weakness)`,`Antalgic gait`,`Asymmetric squat`,`Pain with hop`]}]},neuro:{overview:`Adult-pattern six-component exam: mental status, CN, motor, reflexes, sensory, coordination/gait.`,components:[{name:`Mental status`,steps:[{label:`Orientation`,method:`Name, age, school, city, day of week`,normal:`Oriented x 4`},{label:`Attention`,method:`Count backward from 20; days of week backward`,normal:`Intact`},{label:`3-item recall`,method:`Ball-flag-tree; ask at 3 and 5 min`,normal:`3/3 recall at 5 min`},{label:`Language`,method:`Name common objects; repeat a sentence`,normal:`Fluent, no paraphasia`}],abnormalHints:[`Inattention (ADHD features)`,`Memory deficits`,`Word-finding difficulty`,`Perseveration`]},{name:`Cranial nerves (II–XII)`,steps:[{label:`CN II — acuity`,method:`Snellen at 20ft each eye with corrective lenses if worn`,normal:`20/20 or baseline`},{label:`CN II — fields`,method:`Confrontation, 4 quadrants each eye`,normal:`Full fields`},{label:`CN II — fundoscopy (if indicated)`,method:`Direct ophthalmoscopy — disc, vessels, macula`,normal:`Sharp disc, normal cup-disc ratio, no papilledema`},{label:`CN II, III — pupils`,method:`Direct and consensual light, accommodation`,normal:`PERRLA`},{label:`CN III, IV, VI — EOM`,method:`Follow finger in H pattern; convergence`,normal:`Full EOM, no nystagmus, convergence intact`},{label:`CN V — sensation`,method:`Light touch V1 (forehead), V2 (cheek), V3 (jaw) each side`,normal:`Intact, symmetric`},{label:`CN V — motor`,method:`Clench teeth, palpate masseter/temporalis; jaw opening`,normal:`Symmetric strength`},{label:`CN VII`,method:`Raise eyebrows, close eyes tight, smile/show teeth, puff cheeks`,normal:`Symmetric movement, all regions`},{label:`CN VIII`,method:`Finger rub each ear; Weber/Rinne if deficit`,normal:`Hears bilaterally`},{label:`CN IX, X`,method:`Palate elevation with "ahh"; uvula midline; voice quality`,normal:`Symmetric elevation, uvula midline, normal voice`},{label:`CN XI`,method:`Shrug shoulders against resistance; head turn against resistance`,normal:`5/5 SCM and trapezius`},{label:`CN XII`,method:`Stick tongue out; side-to-side`,normal:`Midline, no atrophy or fasciculations`}],abnormalHints:[`Papilledema (increased ICP)`,`Focal cranial nerve deficit — any warrants workup`,`Tongue fasciculations (LMN/MND)`]},{name:`Motor — bulk, tone, strength`,steps:[{label:`Bulk inspection`,method:`Shoulders, thighs, calves, intrinsic hand muscles`,normal:`Symmetric, no atrophy`},{label:`Tone`,method:`Passive range at elbows, wrists, knees, ankles`,normal:`Normal resistance throughout`},{label:`Strength — deltoids`,method:`Shoulder abduction against resistance`,normal:`5/5 bilaterally`},{label:`Strength — biceps`,method:`Elbow flexion against resistance`,normal:`5/5`},{label:`Strength — triceps`,method:`Elbow extension against resistance`,normal:`5/5`},{label:`Strength — grip`,method:`Squeeze 2 fingers`,normal:`5/5 symmetric`},{label:`Strength — finger abduction`,method:`Spread fingers against resistance`,normal:`5/5`},{label:`Strength — hip flexion`,method:`Lift leg supine against resistance`,normal:`5/5`},{label:`Strength — knee extension`,method:`Straighten knee against resistance`,normal:`5/5`},{label:`Strength — dorsiflexion`,method:`Pull toes up against resistance`,normal:`5/5`},{label:`Strength — plantarflexion`,method:`Push foot down against resistance`,normal:`5/5`}],abnormalHints:[`Focal weakness (localize)`,`Spasticity (UMN)`,`Atrophy`,`Fasciculations`]},{name:`Deep tendon reflexes`,steps:[{label:`Biceps (C5-C6)`,method:`Thumb on tendon, strike`,normal:`2+ symmetric`},{label:`Triceps (C7-C8)`,method:`Strike triceps tendon`,normal:`2+ symmetric`},{label:`Brachioradialis (C5-C6)`,method:`Strike distal radius`,normal:`2+ symmetric`},{label:`Patellar (L3-L4)`,method:`Knees hanging, strike tendon`,normal:`2+ symmetric`},{label:`Achilles (S1)`,method:`Slight dorsiflexion, strike tendon`,normal:`2+ symmetric`},{label:`Plantar response`,method:`Stroke lateral sole heel-to-toes`,normal:`Down-going bilaterally`},{label:`Clonus`,method:`Rapid dorsiflexion at ankle`,normal:`No sustained clonus`}],abnormalHints:[`Hyperreflexia with clonus (UMN: stroke, MS, cord lesion)`,`Hyporeflexia (LMN, neuropathy, myopathy)`,`Asymmetry`,`Up-going Babinski`,`Sustained clonus`]},{name:`Sensory`,steps:[{label:`Light touch — upper`,method:`Cotton wisp dorsum of hands, eyes closed`,normal:`Intact, symmetric`},{label:`Light touch — lower`,method:`Same on dorsum of feet`,normal:`Intact, symmetric`},{label:`Pain — upper`,method:`Broken Q-tip or pin on hands`,normal:`Intact, symmetric`},{label:`Pain — lower`,method:`Same on feet`,normal:`Intact, symmetric`},{label:`Vibration`,method:`128 Hz tuning fork at distal IP joint of great toes`,normal:`Feels vibration; counts down seconds`},{label:`Proprioception`,method:`Move great toe up/down with eyes closed`,normal:`Identifies direction correctly`}],abnormalHints:[`Dermatomal loss (nerve root)`,`Stocking-glove (neuropathy)`,`Loss of vibration/proprioception (dorsal column — B12, tabes, MS)`]},{name:`Coordination`,steps:[{label:`Finger-nose-finger`,method:`Touch examiner finger then own nose, examiner moves target`,normal:`Smooth, accurate, no dysmetria`},{label:`Heel-to-shin`,method:`Supine: heel down opposite shin`,normal:`Smooth, on-target`},{label:`Rapid alternating movements`,method:`Supinate/pronate hand on knee rapidly`,normal:`Rhythmic, symmetric`},{label:`Fine motor`,method:`Finger tapping (thumb to each finger in sequence)`,normal:`Rhythmic, accurate`}],abnormalHints:[`Dysmetria (past-pointing, overshoot)`,`Intention tremor`,`Dysdiadochokinesia`]},{name:`Gait and Romberg`,steps:[{label:`Normal gait`,method:`Walk 20 feet`,normal:`Narrow-based, smooth, reciprocal arm swing`},{label:`Heel walk`,method:`Walk on heels`,normal:`Able without difficulty`},{label:`Toe walk`,method:`Walk on toes`,normal:`Able without difficulty`},{label:`Tandem`,method:`Heel-to-toe along a line`,normal:`Minimal deviation, 10+ steps`},{label:`Romberg`,method:`Feet together, eyes closed, 30s`,normal:`Stable without fall or significant sway`}],abnormalHints:[`Wide-based (cerebellar)`,`Steppage (peripheral neuropathy)`,`Scissoring (UMN)`,`Romberg positive (dorsal column)`,`Circumduction`]}]},resp:{overview:`Nearly adult-pattern. Exam the same as adolescent with slightly more flexibility in cooperation. RR ≤ 30 in younger school-age, ≤ 20 in older. Sports history relevant (exercise-induced asthma).`,components:[{name:`Inspection`,steps:[{label:`Respiratory rate`,method:`Count over 60 s.`,normal:`≤ 30 (6–11 y)`},{label:`Work of breathing`,method:`Retractions, accessory muscles.`,normal:`Effortless`},{label:`Audible sounds`,method:`Listen for wheeze, stridor.`,normal:`Quiet`},{label:`Chest shape`,method:`Barrel chest, pectus deformities.`,normal:`Normal shape`},{label:`Clubbing`,method:`Schamroth window test.`,normal:`No clubbing`}],abnormalHints:[`Clubbing — CF, chronic hypoxemia, bronchiectasis`,`Barrel chest — chronic asthma, CF`]},{name:`Palpation and percussion`,steps:[{label:`Tracheal position`,method:`Middle finger in suprasternal notch.`,normal:`Midline`},{label:`Chest expansion`,method:`Hands laterally, thumbs meeting at spine. Deep breath.`,normal:`Symmetric 3–5 cm`},{label:`Tactile fremitus`,method:`Ulnar side of hand; "ninety-nine". Compare sides.`,normal:`Symmetric`},{label:`Percussion`,method:`Pleximeter + plexor technique. Compare sides.`,normal:`Resonant throughout`}],abnormalHints:[`Deviated trachea — pneumothorax, effusion, collapse`,`Dull percussion — consolidation, effusion`,`Hyper-resonant — pneumothorax, hyperinflation`]},{name:`Auscultation`,steps:[{label:`Systematic zones`,method:`Six anterior + four lateral + six posterior zones, compare side-to-side.`,normal:`Symmetric vesicular sounds`},{label:`Adventitious sounds`,method:`Wheeze, crackles, rhonchi, rub, stridor at neck. Use sounds library for reference.`,normal:`No added sounds`},{label:`Cough re-listen`,method:`Secretions (rhonchi, coarse crackles) should clear; fibrosis crackles do not.`,normal:`Secretion-based sounds clear with cough`}],abnormalHints:[`Focal crackles + fever — pneumonia`,`Diffuse fine crackles — early interstitial disease`,`Expiratory wheeze — asthma / RAD`]}]},cv:{overview:`Nearly adult-pattern. Sports participation screening is a key indication in this age. HOCM screening (family history of sudden cardiac death, exertional syncope, murmur louder with Valsalva) is specifically relevant.`,components:[{name:`Inspection and palpation`,pearl:`For sports participation exams, always ask about exertional symptoms (syncope, chest pain, unexpected fatigue) AND family history of sudden cardiac death before age 50. Screening exam alone catches only ~3% of HOCM.`,steps:[{label:`General and growth`,method:`Track on growth curve; review activity tolerance.`,normal:`Normal growth, age-appropriate activity`},{label:`Colour and clubbing`,method:`Inspect mucous membranes and nail beds.`,normal:`Pink, no clubbing`},{label:`Apex beat`,method:`Palpate at 5th ICS mid-clavicular line.`,normal:`Normal position, tapping character`},{label:`Peripheral pulses`,method:`Simultaneous brachial + femoral.`,normal:`Symmetric, no delay`},{label:`Blood pressure`,method:`Measure BP with appropriately sized cuff. If elevated, check both arms and one leg.`,normal:`Age-appropriate (< 120/80 roughly by 10+ years)`}],abnormalHints:[`Exertional syncope — HOCM, arrhythmia, LQTS`,`BP differential — coarctation`,`Displaced apex — cardiomegaly`]},{name:`Auscultation`,steps:[{label:`All 5 classic points`,method:`See APTM diagram. A → P → E → T → M with diaphragm and bell.`,normal:`S1, S2 clear with physiologic split at P, no added sounds`},{label:`Grade any murmur`,method:`Levine 1–6 (see scales above); characterise timing, location, radiation.`,normal:`No murmur, or innocent flow murmur meeting all 7 S criteria`},{label:`Innocent vs pathologic`,method:`Apply 7 S criteria; compare to innocent-murmur panel.`,normal:`Innocent murmur (if present) clearly fits all 7 S features`},{label:`Dynamic maneuvers`,method:`Standing: HOCM louder; most others soften. Valsalva: HOCM louder.`,normal:`Murmur (if any) softens on standing and Valsalva`}],abnormalHints:[`Murmur louder with Valsalva / standing — HOCM (sports disqualification considerations)`,`Any diastolic murmur`,`Murmur ≥ grade 3, radiating, or with thrill`]}]}},adolescent:{label:`Adolescent (12–21 years)`,msk:{overview:`Sports-related injuries, adolescent scoliosis, apophyseal overuse, hypermobility screening.`,components:[{name:`Scoliosis screen`,steps:[{label:`Standing inspection`,method:`Patient undressed to waist (keep privacy); compare shoulders, iliac crests, scapular heights`,normal:`Symmetric shoulders and pelvis`},{label:`Forward bend (Adam)`,method:`Feet together, bend forward, arms hanging palms together`,normal:`Symmetric paraspinal contour`},{label:`Scoliometer`,method:`Place across thoracic and lumbar regions at maximum prominence`,normal:`ATR <5°; 5–6° monitor; ≥7° refer`},{label:`Plumb line check`,method:`Drop plumb from C7; note where it falls`,normal:`Passes through gluteal cleft (compensated)`},{label:`Leg lengths`,method:`Supine; ASIS to medial malleolus each side`,normal:`Within 1cm`}],abnormalHints:[`Rib/lumbar hump`,`ATR ≥7°`,`Decompensation (plumb off gluteal cleft)`,`Leg-length discrepancy driving apparent curve`]},{name:`Back pain evaluation (if complaint)`,steps:[{label:`Inspect and palpate`,method:`Spinous processes, paraspinal muscles, SI joints`,normal:`Non-tender`},{label:`Range of motion`,method:`Flex, extend, lateral bend, rotate`,normal:`Full painless range`},{label:`Single-leg hyperextension (stork)`,method:`Stand on one foot, extend back — each side`,normal:`No pain (negative for spondylolysis)`},{label:`Straight-leg raise`,method:`Supine, lift straight leg to 70°+`,normal:`No radicular pain to 70°`},{label:`SI joint tests`,method:`FABER, SI compression`,normal:`No pain`}],abnormalHints:[`Spondylolysis (positive stork test)`,`Radicular pain (disc herniation)`,`SI joint pathology`,`Inflammatory back pain pattern`]},{name:`Joint stability — sports-specific`,steps:[{label:`Knee — Lachman`,method:`Knee 20° flexion, stabilize femur, pull tibia anteriorly`,normal:`Firm endpoint, no laxity (ACL intact)`},{label:`Knee — anterior drawer`,method:`Knee 90°, pull tibia forward`,normal:`No excess anterior translation`},{label:`Knee — varus/valgus stress`,method:`Stress at 0 and 30° flexion`,normal:`No gap opening (LCL/MCL intact)`},{label:`Knee — McMurray`,method:`Flex, rotate tibia while extending`,normal:`No pain or click`},{label:`Shoulder — apprehension`,method:`Abduct and externally rotate`,normal:`No apprehension`},{label:`Shoulder — Neer/Hawkins`,method:`Passive flexion with internal rotation`,normal:`No pain`},{label:`Ankle — anterior drawer`,method:`Pull heel forward with tibia stabilized`,normal:`No laxity`},{label:`Ankle — talar tilt`,method:`Invert heel with tibia stabilized`,normal:`No excess tilt`}],abnormalHints:[`ACL/PCL tear`,`MCL/LCL laxity`,`Meniscal injury`,`Shoulder instability/impingement`,`Ankle ligament laxity`]},{name:`Apophysitis and overuse screen`,steps:[{label:`Tibial tubercle`,method:`Palpate with knee flexed`,normal:`Non-tender`},{label:`Calcaneal apophysis`,method:`Palpate posterior calcaneus`,normal:`Non-tender`},{label:`Iliac apophyses`,method:`Palpate ASIS, AIIS, iliac crest`,normal:`Non-tender`},{label:`Rotator cuff`,method:`Empty-can (Jobe) test`,normal:`No pain or weakness`}],abnormalHints:[`Osgood-Schlatter (tibial tubercle tender)`,`Sever (calcaneal tender)`,`Iliac apophysitis`,`Rotator cuff tendinopathy`]},{name:`Hypermobility screen (Beighton)`,steps:[{label:`Fifth finger extension`,method:`Passive extension of fifth MCP to >90°`,normal:`No hyperextension (1 pt each side if positive)`},{label:`Thumb to forearm`,method:`Passive flexion of thumb to touch forearm`,normal:`Does not reach (1 pt each side if positive)`},{label:`Elbow hyperextension`,method:`Hyperextension >10°`,normal:`No hyperextension (1 pt each side if positive)`},{label:`Knee hyperextension`,method:`Hyperextension >10°`,normal:`No hyperextension (1 pt each side if positive)`},{label:`Palms to floor`,method:`Feet together, bend forward, palms flat on floor with knees straight`,normal:`Cannot reach (1 pt if positive)`}],abnormalHints:[`Beighton ≥5/9 suggests hypermobility spectrum (hEDS workup if with other features)`]},{name:`Alignment and gait`,steps:[{label:`Standing alignment`,method:`View knees, feet`,normal:`Neutral alignment, medial arch`},{label:`Normal gait`,method:`Walk 20 feet`,normal:`Symmetric, smooth`},{label:`Functional movements`,method:`Squat, single-leg stance, hop`,normal:`Full symmetric function`}],abnormalHints:[`Antalgic gait`,`Trendelenburg`,`Asymmetric squat`]}]},neuro:{overview:`Full adult-pattern neuro exam across six pillars: mental status, cranial nerves, motor, reflexes, sensory, coordination/gait. In adolescents, screen concussion sequelae if sports-active; frontal release signs must be absent.`,components:[{name:`Mental status`,significance:`Detects cognitive change (concussion, substance use, mood disorder, rare neurodegenerative disease).`,pearl:`Attention precedes memory. A patient who can't attend (serial 7s, months backward) will fail memory even with intact hippocampus — distinguish before calling it a memory problem.`,steps:[{label:`Orientation`,method:`Name, age, date, location, situation`,normal:`Oriented x 4`},{label:`Attention`,method:`Count backward from 100 by 7s (serial 7s) or months of year backward`,normal:`Intact`},{label:`Short-term memory`,method:`3-item registration and recall at 5 min`,normal:`3/3 recall`},{label:`Language`,method:`Object naming; sentence repetition; reading; writing`,normal:`Fluent, no paraphasia, comprehends written and spoken`},{label:`Executive function`,method:`Similarities (apple/orange); interpret proverb`,normal:`Abstract, age-appropriate`}],abnormalHints:[`Post-concussion cognitive changes`,`Mood or personality changes`,`Subtle executive dysfunction`,`Word-finding difficulty`]},{name:`Cranial nerves (II–XII, full formal exam)`,significance:`Localises brainstem, base-of-skull, and specific nerve pathology. Subtle deficits (RAPD, mild facial weakness, Horner) are easily missed — exam discipline matters.`,pearl:`The fastest screen for a CN deficit is asking the patient to speak, smile, look around, and swallow water. What's preserved in everyday function tells you what's likely intact — then examine formally to confirm and to catch the subtle.`,steps:[{label:`CN I (if indicated)`,method:`Coffee or cinnamon each nostril separately`,normal:`Identifies both`},{label:`CN II — acuity`,method:`Snellen at 20ft each eye; corrective lenses if worn`,normal:`20/20 or baseline`},{label:`CN II — fields`,method:`Confrontation, 4 quadrants each eye`,normal:`Full fields`},{label:`CN II — fundoscopy`,method:`Direct ophthalmoscopy — disc, vessels, macula`,normal:`Sharp disc, normal cup/disc, no papilledema`},{label:`CN II, III — pupils`,method:`Direct, consensual, swinging flashlight, accommodation`,normal:`PERRLA, no RAPD`},{label:`CN III, IV, VI — EOM`,method:`H pattern, convergence, note nystagmus or ptosis`,normal:`Full conjugate movement, no nystagmus, convergence intact`},{label:`CN V — sensation`,method:`Light touch V1, V2, V3 each side`,normal:`Intact, symmetric`},{label:`CN V — motor`,method:`Clench jaw, palpate masseter/temporalis; lateral jaw movement`,normal:`Symmetric strength and bulk`},{label:`CN V — corneal reflex (if indicated)`,method:`Cotton wisp to cornea`,normal:`Blinks bilaterally`},{label:`CN VII`,method:`Wrinkle forehead, close eyes against resistance, smile/bare teeth, puff cheeks`,normal:`Symmetric all four movements`},{label:`CN VIII — hearing`,method:`Finger rub each ear; Weber (midline) + Rinne (air > bone) if deficit`,normal:`Equal bilaterally`},{label:`CN IX, X`,method:`Palate elevation with "ahh"; uvula midline; voice; gag (if indicated)`,normal:`Symmetric palate, uvula midline, normal voice`},{label:`CN XI`,method:`Shoulder shrug and head turn against resistance`,normal:`5/5 SCM and trapezius bilaterally`},{label:`CN XII`,method:`Tongue protrusion, side to side; inspect for fasciculations/atrophy`,normal:`Midline, no atrophy or fasciculations, full movement`}],abnormalHints:[`Any focal cranial nerve deficit`,`Papilledema`,`RAPD`,`Nystagmus`,`Facial asymmetry`,`Tongue deviation`]},{name:`Motor — bulk, tone, strength`,significance:`Localises lesion to UMN vs LMN vs muscle vs junction. Pattern of weakness (proximal vs distal, symmetric vs focal) narrows differential.`,pearl:`Pronator drift is the most sensitive screen for subtle UMN weakness — a normal-feeling arm that drifts down with eyes closed still has corticospinal tract dysfunction. Always do it even when formal strength is 5/5.`,steps:[{label:`Bulk inspection`,method:`Inspect shoulders, biceps, thighs, calves, dorsal interossei (between metacarpals) of hands.`,normal:`Symmetric bulk; no atrophy, no pseudohypertrophy`},{label:`Tone — upper`,method:`Passive flex-extend elbow and pronate-supinate wrist at slow then quick speeds. Then pronator drift: arms outstretched, palms up, eyes closed for 10 s.`,normal:`Smooth passive range; no drift, no pronation of the outstretched hand`},{label:`Tone — lower`,method:`Passive knee flexion-extension; quick ankle dorsiflexion to check for catch. Heel-slap test: roll thigh and watch for ankle swing.`,normal:`Normal resistance, no catch, symmetric`},{label:`Strength — deltoid (C5)`,method:`Patient abducts both arms to 90°. Examiner pushes down on each arm just above the elbow while patient resists. Compare sides.`,normal:`Holds against full resistance — MRC 5/5 bilaterally`},{label:`Strength — biceps (C5–C6)`,method:`Elbow flexed 90°, supinated. Examiner grasps wrist and pulls to extend while patient resists.`,normal:`Holds against full resistance — 5/5`},{label:`Strength — triceps (C7)`,method:`Elbow flexed 90°. Examiner pushes wrist toward shoulder while patient extends against resistance.`,normal:`Extends against full resistance — 5/5`},{label:`Strength — wrist extension (C6–C7)`,method:`Patient makes fist, extends wrist. Examiner pushes down on knuckles while patient holds wrist up.`,normal:`Holds against full resistance — 5/5`},{label:`Strength — finger flexion / grip (C8)`,method:`Patient grips two of examiner's crossed fingers as hard as possible. Compare sides.`,normal:`Strong symmetric grip — 5/5`},{label:`Strength — finger abduction (T1)`,method:`Patient spreads fingers wide. Examiner squeezes index and little fingers together while patient resists.`,normal:`Holds fingers apart — 5/5`},{label:`Strength — hip flexion (L2–L3)`,method:`Supine. Patient lifts straight leg 30° off table. Examiner pushes down on thigh just above knee while patient resists.`,normal:`Holds thigh up against full resistance — 5/5`},{label:`Strength — knee extension (L3–L4)`,method:`Sitting, knee 90°. Patient straightens knee while examiner pushes distal shin down.`,normal:`Extends against full resistance — 5/5`},{label:`Strength — ankle dorsiflexion (L4–L5)`,method:`Patient pulls toes and foot up toward shin. Examiner pushes foot down at the dorsum.`,normal:`Holds dorsiflexion against full resistance — 5/5; preserved heel-walk`},{label:`Strength — great toe extension (L5)`,method:`Patient extends great toe up while examiner pushes it down with thumb.`,normal:`Holds against full resistance — 5/5 (classic L5 test)`},{label:`Strength — ankle plantarflexion (S1)`,method:`Patient pushes foot down against examiner's hand at the ball. OR ask patient to toe-walk 10 steps (more sensitive — unilateral plantarflexion weakness shows immediately).`,normal:`Full power; toe-walks symmetrically — 5/5`}],abnormalHints:[`Focal weakness → localise by myotome`,`Pronator drift (subtle UMN, always check even with 5/5)`,`Spasticity / catch (UMN)`,`Atrophy (LMN, disuse)`,`Fasciculations (MND, ALS)`,`Pseudohypertrophy of calves (DMD in a young male)`]},{name:`Deep tendon reflexes`,significance:`Reflex pattern (increased, decreased, asymmetric) localises UMN vs LMN vs root vs peripheral nerve. Inexpensive and fast, but asymmetry is the most informative finding.`,pearl:`A reinforced reflex is still a reflex. If you can't elicit it initially, use Jendrassik (teeth clench or pull interlocked fingers apart) to boost — absent reflexes without reinforcement aren't truly absent.`,steps:[{label:`Biceps (C5–C6)`,method:`Patient's arm relaxed across lap. Examiner places thumb firmly on biceps tendon at the cubital fossa, strikes thumb with reflex hammer. Compare both sides sequentially.`,normal:`2+ symmetric — visible contraction of biceps, slight elbow flexion`},{label:`Brachioradialis (C5–C6)`,method:`Arm relaxed. Strike the distal radius about 3 cm proximal to the wrist, on its radial (thumb) side.`,normal:`2+ symmetric — elbow flexion and slight forearm supination`},{label:`Triceps (C7)`,method:`Support the patient's arm at the wrist with elbow at 90°. Strike the triceps tendon just above the olecranon.`,normal:`2+ symmetric — triceps contraction, slight elbow extension`},{label:`Finger flexors — Hoffmann sign`,method:`Grasp the middle finger's distal phalanx, flick it downward quickly and release. Watch the thumb and index finger.`,normal:`Negative — no thumb flexion, no index flexion (positive = corticospinal tract dysfunction)`},{label:`Patellar (L3–L4)`,method:`Patient sits with knees hanging freely off the table. Strike the patellar tendon just below the patella.`,normal:`2+ symmetric — quadriceps contraction with knee extension`},{label:`Achilles (S1)`,method:`Patient's knee slightly flexed and leg externally rotated, or kneeling on a chair. Slightly dorsiflex the foot and strike the Achilles tendon.`,normal:`2+ symmetric — plantar flexion of the foot`},{label:`Plantar response (Babinski)`,method:`Stroke the lateral aspect of the sole firmly from the heel toward the little toe, then curve across the ball of the foot.`,normal:`Toes flex downward (plantar flexion, "down-going") bilaterally in anyone ≥ 2 years`},{label:`Ankle clonus`,method:`Knee slightly bent. Support the shin with one hand, quickly and sharply dorsiflex the foot with the other, hold in dorsiflexion.`,normal:`≤ 3 non-sustained beats is acceptable; sustained rhythmic oscillation = pathological clonus (UMN)`}],abnormalHints:[`Hyperreflexia + sustained clonus = UMN (MS, myelopathy, cord lesion, stroke)`,`Symmetric hyporeflexia = peripheral polyneuropathy, GBS, myopathy, hypothyroid, B12 deficiency`,`Asymmetric hyporeflexia = radiculopathy at that segment`,`Hoffmann positive = corticospinal tract dysfunction at cervical cord or above`,`Up-going Babinski after age 2 = UMN (always abnormal)`]},{name:`Sensory`,steps:[{label:`Light touch — upper`,method:`Cotton wisp, dorsum of hands, eyes closed`,normal:`Intact, symmetric`},{label:`Light touch — lower`,method:`Dorsum of feet`,normal:`Intact, symmetric`},{label:`Pain — upper`,method:`Broken Q-tip sharp end, hands`,normal:`Intact, symmetric`},{label:`Pain — lower`,method:`Same on feet`,normal:`Intact, symmetric`},{label:`Temperature (if indicated)`,method:`Cold tuning fork each area`,normal:`Intact`},{label:`Vibration`,method:`128 Hz tuning fork at distal IP of great toes; count seconds to fade`,normal:`Feels vibration; appropriate duration`},{label:`Proprioception`,method:`Move great toe up/down with eyes closed`,normal:`Identifies direction correctly`},{label:`Two-point discrimination (if indicated)`,method:`Blunt calipers on fingertip`,normal:`<5mm on fingertip`},{label:`Stereognosis (if indicated)`,method:`Identify coin/key in hand with eyes closed`,normal:`Correct identification`}],abnormalHints:[`Dermatomal loss (nerve root)`,`Stocking-glove loss (length-dependent neuropathy)`,`Dorsal column loss (B12, tabes, MS — positive Romberg, vibration loss)`,`Cortical deficit (astereognosis, impaired 2-pt)`]},{name:`Coordination`,steps:[{label:`Finger-nose-finger`,method:`Alternate examiner's finger and own nose; examiner moves target`,normal:`Smooth, accurate bilaterally`},{label:`Heel-to-shin`,method:`Supine: heel down opposite shin and back`,normal:`Smooth, accurate`},{label:`Rapid alternating (Dysdiadochokinesis)`,method:`Supinate/pronate hand rapidly on thigh`,normal:`Rhythmic, symmetric`},{label:`Finger tapping`,method:`Thumb to each finger in sequence rapidly`,normal:`Rhythmic, smooth, symmetric`}],abnormalHints:[`Dysmetria (cerebellar)`,`Intention tremor`,`Dysdiadochokinesia`,`Decomposed movement`]},{name:`Gait and Romberg`,steps:[{label:`Normal gait`,method:`Walk 20 feet`,normal:`Narrow-based, smooth, reciprocal arm swing`},{label:`Heel walk`,method:`Walk on heels only`,normal:`Able without difficulty`},{label:`Toe walk`,method:`Walk on toes only`,normal:`Able without difficulty`},{label:`Tandem`,method:`Heel-to-toe along a line, 10+ steps`,normal:`Minimal deviation`},{label:`Romberg`,method:`Feet together, eyes open then closed, 30s`,normal:`Stable — no significant sway or fall with eyes closed`},{label:`Single-leg stance`,method:`10s each side, eyes open`,normal:`Stable without drift`}],abnormalHints:[`Ataxic (wide-based — cerebellar)`,`Steppage (peripheral neuropathy / foot drop)`,`Circumduction (UMN hemiparesis)`,`Scissoring`,`Romberg positive (dorsal column)`]},{name:`Frontal release / primitive reflexes`,steps:[{label:`Grasp reflex`,method:`Stroke palm`,normal:`Absent`},{label:`Snout reflex`,method:`Tap upper lip`,normal:`No lip pucker`},{label:`Glabellar tap`,method:`Tap between eyebrows — should habituate after 3–4 taps`,normal:`Habituates (no sustained blink)`},{label:`Palmomental`,method:`Stroke thenar eminence`,normal:`No ipsilateral chin twitch`}],abnormalHints:[`Presence suggests frontal lobe pathology, neurodegenerative disease, or severe TBI — rare in adolescence but relevant in post-concussion workup`]}]},resp:{overview:`Systematic respiratory exam: inspection → palpation → percussion → auscultation → special maneuvers. Always start from observation — rate, pattern, work of breathing, and audible sounds (stridor, grunting) can be diagnostic before the stethoscope touches the chest.`,components:[{name:`Inspection — observation before touching`,significance:`Detects respiratory distress and localises the level of airway compromise before any equipment is used. High yield: RR, WOB, audible sounds, chest shape, colour.`,pearl:`Audible stridor at rest from across the room = upper-airway obstruction, often urgent. Grunting in an infant = significant distress — never dismiss as fussiness.`,steps:[{label:`Respiratory rate`,method:`Count over a full 60 seconds (not 15×4) — children normally breathe irregularly. Count while the patient is calm, before any interaction.`,normal:`Within age-appropriate range (see scales card above)`},{label:`Respiratory pattern`,method:`Observe depth, regularity, and inspiration:expiration ratio. Watch for prolonged expiration, paradoxical chest-abdominal movement, or apneas.`,normal:`Regular, I:E ratio ~1:2, no pauses > 10 s in an infant`},{label:`Work of breathing`,method:`Inspect for nasal flaring, suprasternal/intercostal/subcostal retractions, accessory muscle use (SCM, abdominals), tripod positioning, head-bobbing in infants.`,normal:`No retractions; breathing effortless`},{label:`Audible sounds (no stethoscope)`,method:`Listen at the bedside without the stethoscope. Grunting? Stridor? Wheezing audible across the room? Hoarse voice?`,normal:`No audible stridor, grunting, or wheeze`},{label:`Chest shape and symmetry`,method:`Inspect from front and lateral. Note AP-to-transverse diameter, pectus excavatum/carinatum, chest wall asymmetry.`,normal:`AP:transverse ~1:2 (not barrel-chested); symmetric`},{label:`Colour and perfusion`,method:`Inspect lips, tongue, nail beds for central cyanosis. Check peripheral perfusion (capillary refill, mottling).`,normal:`Pink, cap refill < 2 s, no cyanosis`},{label:`Clubbing`,method:`Inspect fingernails: Schamroth sign (reverse a finger against its mirror — normal forms a diamond-shaped window, clubbed does not).`,normal:`Normal nail angle, Schamroth window present`}],abnormalHints:[`Audible stridor — upper airway (croup, epiglottitis, foreign body, laryngomalacia)`,`Grunting in infant — significant distress`,`Tripod positioning, accessory muscle use — severe distress`,`Barrel chest — chronic air-trapping (asthma, CF)`,`Central cyanosis — significant hypoxemia`,`Clubbing in a child — cystic fibrosis, chronic hypoxemia, bronchiectasis, cyanotic CHD`]},{name:`Palpation`,significance:`Localises pathology: consolidation increases tactile fremitus; pneumothorax/effusion decreases it. Trachea deviates AWAY from expanding lesions and TOWARD collapsing ones.`,pearl:`Tracheal deviation is one of the fastest bedside clues to mediastinal shift — tension pneumothorax pushes it away, lobar collapse pulls it toward. Palpate with the middle finger in the suprasternal notch.`,steps:[{label:`Tracheal position`,method:`Patient sitting upright, neck slightly extended. Place middle finger in the suprasternal notch, check equal distance to each SCM.`,normal:`Midline`},{label:`Chest expansion — symmetry`,method:`Hands on lateral chest wall with thumbs meeting at the spine (posterior) or xiphoid (anterior). Patient takes a deep breath. Watch thumbs separate symmetrically.`,normal:`Symmetric 3–5 cm separation`},{label:`Tactile fremitus`,method:`Ulnar surface of hand on chest wall. Ask patient to say "ninety-nine" repeatedly. Move hand systematically across each zone, comparing sides.`,normal:`Equal mild vibration bilaterally over lung fields`},{label:`Chest wall tenderness`,method:`Palpate ribs, costochondral junctions, sternum, and intercostal spaces.`,normal:`No tenderness`},{label:`Subcutaneous emphysema`,method:`Gentle palpation along clavicles, neck, chest wall.`,normal:`No crepitus under skin`}],abnormalHints:[`Tracheal deviation — tension pneumothorax, large pleural effusion (away); upper lobe collapse (toward)`,`Asymmetric expansion — pneumothorax, large effusion, lobar collapse, phrenic palsy`,`Increased fremitus — consolidation (pneumonia), lobar pneumonia`,`Decreased/absent fremitus — pleural effusion, pneumothorax, obstruction`,`Costochondral tenderness — costochondritis, trauma`,`Subcutaneous emphysema — pneumothorax, tracheobronchial injury`]},{name:`Percussion`,significance:`Differentiates air (hyper-resonant), fluid (dull), and consolidated lung (dull) without imaging. Well-performed percussion detects a pleural effusion > 300 mL or a pneumothorax with ~90% sensitivity.`,pearl:`Pleximeter fingertip must be flat against the chest wall — lift other fingers off. The "feel" of a percussion note is as informative as the sound: dullness has a dense, reflected quality; hyper-resonance feels hollow and springy.`,steps:[{label:`Technique`,method:`Place middle finger of non-dominant hand (pleximeter) flat on chest wall; strike distal IP joint with tip of dominant middle finger (plexor) using a quick wrist flick.`,normal:`N/A — technique step`},{label:`Systematic zones`,method:`Percuss from apex to base, comparing side-to-side at each level. Include anterior, lateral (mid-axillary), and posterior fields.`,normal:`Resonant throughout lung fields`},{label:`Cardiac dullness`,method:`Percuss from resonant lung toward the heart border. Left sternal border dullness starts at the 3rd–5th ICS.`,normal:`Dullness beginning at the expected cardiac border`},{label:`Hepatic dullness`,method:`Right 5th–6th ICS mid-clavicular line transitions from resonant to dull.`,normal:`Liver edge dullness at expected level`},{label:`Diaphragmatic excursion`,method:`Patient inhales fully then exhales fully; mark level of dullness at each end. Difference is diaphragm excursion.`,normal:`3–5 cm excursion bilaterally`}],abnormalHints:[`Hyper-resonant — pneumothorax, emphysematous bulla, severe asthma attack`,`Dull — consolidation, pleural effusion (stony dull), atelectasis, pleural thickening, large mass`,`Raised diaphragm (loss of excursion) — effusion, paralysis, subdiaphragmatic pathology`]},{name:`Auscultation — normal breath sounds`,significance:`Breath sound quality varies by location. Bronchial sounds heard peripherally = consolidation; absent breath sounds = pneumothorax, effusion, obstruction.`,pearl:`Always compare corresponding points side-to-side sequentially — your ear calibrates to "normal" one side and immediately hears asymmetry. Listen through a full respiratory cycle at each zone.`,steps:[{label:`Technique`,method:`Diaphragm of stethoscope directly on skin (not over clothing). Patient breathes slowly and deeply through an open mouth.`,normal:`N/A — technique`},{label:`Vesicular sounds (peripheral)`,method:`Listen over lung fields away from the sternum. Play the "Normal vesicular" sample above for reference.`,normal:`Soft, low-pitched, inspiration > expiration in length and loudness`},{label:`Bronchovesicular (over main bronchi)`,method:`Listen at the 1st–2nd ICS anteriorly and between scapulae posteriorly.`,normal:`Intermediate pitch, inspiration = expiration`},{label:`Bronchial (over trachea)`,method:`Listen directly over the manubrium or trachea.`,normal:`Harsh, high-pitched, expiration > inspiration`},{label:`Systematic comparison`,method:`Six zones anteriorly (upper/mid/lower × L/R), four lateral, six posterior. Compare side-to-side at each zone.`,normal:`Symmetric breath sounds at every paired zone`}],abnormalHints:[`Bronchial sounds heard peripherally — consolidation (pneumonia)`,`Absent/diminished breath sounds — pneumothorax, effusion, severe obstruction, obesity / muscular chest`,`Prolonged expiration — lower airway obstruction (asthma, bronchiolitis)`]},{name:`Auscultation — adventitious sounds`,significance:`Adventitious (added) sounds are the key diagnostic finding. Timing (inspiratory vs expiratory vs biphasic), character (continuous vs discontinuous), and location are all informative.`,pearl:`Ask the patient to cough and re-listen. Secretions (rhonchi, some coarse crackles) clear or change; fine crackles of fibrosis or early pneumonia do not. The cough test separates two differential groups in one maneuver.`,steps:[{label:`Listen for wheeze`,method:`Continuous musical sounds, typically expiratory. Use the "Wheeze" sample for reference.`,normal:`No wheeze`},{label:`Listen for crackles — fine`,method:`Short, high-pitched, discontinuous "Velcro" sounds. Typically end-inspiratory, bibasilar. Use the "Fine crackles" sample.`,normal:`No crackles`},{label:`Listen for crackles — coarse`,method:`Longer, lower-pitched, louder than fine. Use the "Coarse crackles" sample.`,normal:`No crackles`},{label:`Listen for rhonchi`,method:`Low-pitched, continuous, snore-like. Often change with cough. Use the "Rhonchi" sample.`,normal:`No rhonchi`},{label:`Listen for pleural rub`,method:`Grating, creaky, biphasic, does NOT clear with cough. Use the "Pleural rub" sample.`,normal:`No pleural rub`},{label:`Listen at the neck (for stridor)`,method:`Place stethoscope over the anterior neck. Stridor is loudest here and differentiates from wheeze (loudest over chest). Use the "Stridor" sample.`,normal:`No stridor`},{label:`Listen for expiratory grunting (infants)`,method:`Often audible without a stethoscope at the bedside — short, low-pitched sound at the end of each expiration (glottal closure against exhaled air).`,normal:`No grunting`},{label:`Cough re-listen`,method:`Have patient cough forcefully; re-listen to any abnormal area. Note if the sound clears or changes.`,normal:`Any secretion-based sound should clear or change with cough`}],abnormalHints:[`Wheeze — asthma, bronchiolitis, foreign body (localised), anaphylaxis`,`Fine crackles — pulmonary edema, interstitial lung disease, early pneumonia`,`Coarse crackles — bronchitis, pneumonia, bronchiectasis, aspiration`,`Rhonchi — large-airway secretions`,`Pleural rub — pleurisy, PE, pneumonia with pleural involvement`,`Stridor — upper airway obstruction (croup, epiglottitis, FB)`]},{name:`Special maneuvers — transmitted voice sounds`,significance:`Vocal resonance tests detect consolidation (increased transmission) and effusion/pneumothorax (decreased). Useful when auscultation suggests asymmetry.`,pearl:`Whispered pectoriloquy is the most sensitive of the three — whispered words transmitted clearly through consolidated lung. If "one, two, three" whispered becomes clearly audible over one lung zone, there is consolidation underneath.`,steps:[{label:`Bronchophony`,method:`Patient says "ninety-nine" in normal voice. Listen at each lung zone with the stethoscope.`,normal:`Muffled, indistinct sound`},{label:`Egophony`,method:`Patient says "ee" continuously. Listen over any suspicious area.`,normal:`"Ee" sounds like "ee" (no change)`},{label:`Whispered pectoriloquy`,method:`Patient whispers "one, two, three" or "ninety-nine". Listen over each zone.`,normal:`Whisper is faint and indistinct`}],abnormalHints:[`Bronchophony increased — consolidation`,`Egophony positive ("ee" → "A" / "ay") — consolidation, sometimes top of an effusion`,`Whispered pectoriloquy positive (whisper clearly audible) — consolidation`]}]},cv:{overview:`Systematic cardiovascular exam: inspection → palpation → auscultation at the five classic points → peripheral vascular exam. Always palpate the apex BEFORE auscultating — knowing where the apex lies tells you where to put the stethoscope and flags cardiomegaly immediately.`,components:[{name:`Inspection`,significance:`Detects obvious precordial activity, chest-wall signs of congenital heart disease, and systemic markers (cyanosis, clubbing, dysmorphic features).`,pearl:`Clubbing + central cyanosis in a well-appearing adolescent = cyanotic congenital heart disease until proven otherwise. Inspect the fingernails before reaching for the stethoscope.`,steps:[{label:`General appearance`,method:`Observe body habitus, features suggesting syndromic CHD (Turner, Down, Marfan, Williams).`,normal:`No dysmorphic features, appropriate growth`},{label:`Central cyanosis`,method:`Inspect lips, tongue, and oral mucosa for bluish discoloration.`,normal:`Pink oral mucosa, no cyanosis`},{label:`Peripheral cyanosis / clubbing`,method:`Inspect nail beds; do Schamroth's window (oppose nails of 4th fingers — normally forms a diamond-shaped window).`,normal:`Pink nail beds, Schamroth window present`},{label:`Precordial bulge`,method:`Inspect anterior chest wall tangentially for asymmetric prominence over the heart.`,normal:`Symmetric chest, no bulge`},{label:`Visible apex beat`,method:`Inspect for a visible cardiac impulse at the 5th ICS mid-clavicular line.`,normal:`Apex may be visible in thin patients; should not be displaced`},{label:`Neck veins (JVP)`,method:`Patient reclined 45°, head turned slightly left. Observe the right internal jugular pulsation; measure vertical height above the sternal angle.`,normal:`≤ 4 cm above sternal angle (≤ 9 cm H₂O from right atrium)`}],abnormalHints:[`Central cyanosis — right-to-left shunt, severe hypoxemia`,`Clubbing — cyanotic CHD, chronic hypoxemia`,`Precordial bulge — long-standing cardiomegaly (grew during skeletal growth)`,`Visible apex displaced lateral/inferior — cardiomegaly`,`Elevated JVP — right-heart failure, fluid overload, cardiac tamponade`]},{name:`Palpation`,significance:`Localises the apex (confirms cardiac size), detects thrills (loud murmurs), and identifies a parasternal heave (RV hypertrophy).`,pearl:`If you feel a thrill, the murmur is at least grade 4/6 — grade your murmur as ≥4 even if it sounds less impressive. Thrill = loud, palpable turbulence.`,steps:[{label:`Apex beat — localise`,method:`Feel with the tips of the fingers at the 5th ICS mid-clavicular line. If not found, roll the patient to the left lateral decubitus position.`,normal:`Located at 5th ICS, mid-clavicular line, less than 2 cm in diameter`},{label:`Apex character`,method:`Describe: tapping (normal), heaving (pressure overload, e.g. AS/HTN), thrusting (volume overload, e.g. AR/MR), dyskinetic (MI/aneurysm).`,normal:`Brief tapping quality`},{label:`Parasternal heave`,method:`Place the heel of the hand along the left sternal border. Sustained outward movement with each systole = heave.`,normal:`No heave`},{label:`Thrills`,method:`Use the palmar aspect of the hand at each of the 5 auscultation areas (A, P, E, T, M). A thrill = palpable turbulence.`,normal:`No thrills`},{label:`Peripheral pulses — upper`,method:`Palpate radial pulses bilaterally, then brachial. Note rate, rhythm, volume, and symmetry.`,normal:`Symmetric 2+ pulses, regular rhythm, age-appropriate rate`},{label:`Peripheral pulses — lower`,method:`Palpate femoral pulses. Compare to brachial — radio-femoral or brachio-femoral delay suggests coarctation of the aorta.`,normal:`Femoral pulses 2+ symmetric, no delay relative to radial`}],abnormalHints:[`Apex displaced laterally/inferiorly — cardiomegaly`,`Heaving apex — pressure overload (AS, HTN)`,`Thrusting apex — volume overload (AR, MR)`,`Thrill over precordium — always pathological; at least grade 4/6 murmur`,`Parasternal heave — RV hypertrophy (pulmonary HTN, pulmonary stenosis, VSD with Eisenmenger)`,`Radio-femoral delay — coarctation of the aorta (always check in a hypertensive adolescent)`]},{name:`Auscultation — approach`,significance:`Systematic technique ensures every relevant finding is detected. Listen at all 5 points, with both diaphragm and bell, in supine/sitting/left-lateral positions as needed.`,pearl:`Time every murmur by simultaneously palpating the carotid pulse with the fingers of your free hand. Pulse = systole. Murmur heard during the pulse = systolic; in between pulses = diastolic.`,steps:[{label:`Positioning`,method:`Patient supine, head of bed at 30°. Exam room quiet, patient relaxed. Warm the stethoscope first.`,normal:`N/A — technique`},{label:`Diaphragm technique`,method:`Firm contact with skin. Detects HIGH-pitched sounds: S1, S2, systolic ejection murmurs, AR, MR.`,normal:`N/A — technique`},{label:`Bell technique`,method:`Very light contact — enough to make a seal but not stretch the skin. Detects LOW-pitched sounds: S3, S4, mitral stenosis rumble.`,normal:`N/A — technique`},{label:`Listen at each of the 5 points`,method:`A → P → E → T → M in order, each with diaphragm then bell. Spend a full cycle at each zone.`,normal:`S1 crisp, S2 clear (splits physiologically on inspiration at P), no added sounds, no murmur`},{label:`Left lateral decubitus position`,method:`If apex murmur suspected. Roll patient to left side. Listen at the apex with the BELL for mitral stenosis rumble or S3/S4.`,normal:`No added sounds, no diastolic rumble`},{label:`Sitting forward, held expiration`,method:`Patient leans forward, exhales fully, holds. Listen at left lower sternal border and Erb's point with the DIAPHRAGM for aortic regurgitation (soft early diastolic decrescendo).`,normal:`No early-diastolic murmur`}],abnormalHints:[`Fixed split S2 (no change with respiration) — ASD`,`Loud S2 at pulmonic area — pulmonary HTN`,`S3 — volume overload, CHF (can be normal in young athletes)`,`S4 — stiff ventricle (HTN, HCM, ischemia)`,`Audible opening snap — mitral stenosis (rare in children)`]},{name:`Auscultation — heart sounds and murmurs`,significance:`Characterising a murmur by timing, location, radiation, pitch, quality, and dynamic maneuvers narrows the differential.`,pearl:`Innocent murmurs in children share 7 "S" features: Soft (≤ grade 2), Systolic, Short, Single (no added S3/S4), Small (localised, non-radiating), Sweet (musical), Sensitive to position/respiration (louder supine, softer standing). Anything breaking this pattern deserves workup.`,steps:[{label:`S1`,method:`Listen at the apex (mitral). Coincides with the carotid pulse upstroke. Mitral + tricuspid closure.`,normal:`Single, crisp, single-component sound`},{label:`S2`,method:`Listen at the pulmonic area in HELD INSPIRATION and HELD EXPIRATION. Note whether S2 splits physiologically (wider in inspiration, narrower/absent in expiration).`,normal:`Physiologic split (widens on inspiration, narrows on expiration)`},{label:`S3 / S4 gallops`,method:`Bell at the apex in left lateral decubitus. S3 = early diastole (after S2), low-pitched. S4 = late diastole (just before S1).`,normal:`Absent in adults; S3 can be normal in young athletes under age 30`},{label:`Identify murmur — timing`,method:`Time vs carotid pulse. Systolic (during pulse) vs diastolic (between pulses) vs continuous.`,normal:`No murmur, or only soft innocent flow murmur`},{label:`Identify murmur — location + radiation`,method:`Where loudest? Does it radiate? AS → carotids. MR → axilla. Coarctation → back.`,normal:`N/A — characterise only if murmur present`},{label:`Identify murmur — character`,method:`Crescendo-decrescendo (ejection) vs holosystolic (plateau) vs decrescendo early-diastolic (AR, PR) vs mid-diastolic rumble (MS, TS).`,normal:`N/A — characterise only if murmur present`},{label:`Grade intensity`,method:`Levine 1–6 scale (see scales card above).`,normal:`No murmur, or grade ≤ 2 soft innocent flow murmur`},{label:`Dynamic maneuvers`,method:`Standing: ↑HOCM, ↑MVP click (earlier). Squatting: opposite. Valsalva: ↑HOCM, most others decrease.`,normal:`No significant change with posture`}],abnormalHints:[`Holosystolic murmur at apex → axilla — mitral regurgitation`,`Holosystolic at lower left sternal border (LLSB) — VSD, tricuspid regurgitation`,`Systolic ejection at upper right sternal border → carotids — aortic stenosis`,`Systolic ejection at upper left sternal border — pulmonary stenosis`,`Continuous "machinery" below left clavicle — PDA`,`Early diastolic at Erb's point, leaning forward — aortic regurgitation`,`Diastolic rumble at apex, bell in left-lateral — mitral stenosis`,`Fixed split S2 + systolic flow murmur — ASD`]},{name:`Peripheral vascular exam`,significance:`Coarctation of the aorta hides until BP and pulses are checked in all four extremities. Differential diagnosis of a hypertensive adolescent should include this in the first 60 seconds.`,pearl:`Four-limb BP measurement is mandatory in any adolescent with hypertension or a murmur. Upper-extremity BP > lower-extremity BP (or brachio-femoral delay) = coarctation until excluded.`,steps:[{label:`Four-limb blood pressure`,method:`Measure BP in right arm, left arm, and at least one leg. Use appropriately sized cuff (bladder width 40% of limb circumference, length 80–100%).`,normal:`Arm BPs within 10 mmHg of each other; leg systolic within 20 mmHg of arm systolic (may be higher)`},{label:`Radial pulses`,method:`Palpate both radials simultaneously — note any delay or asymmetry.`,normal:`Simultaneous, symmetric, 2+`},{label:`Radio-femoral delay`,method:`Palpate radial and femoral simultaneously. Feel the femoral as clearly "after" the radial = delay.`,normal:`No delay`},{label:`Femoral pulses`,method:`Palpate both femoral pulses at the mid-inguinal point. Compare amplitude to radials.`,normal:`Symmetric 2+ pulses, equal amplitude to radial`},{label:`Dorsalis pedis + posterior tibialis`,method:`Palpate in both feet.`,normal:`2+ pulses bilaterally`},{label:`Capillary refill`,method:`Press and release the nail bed; time to normal colour.`,normal:`< 2 sec`}],abnormalHints:[`Asymmetric upper-extremity BP (> 10 mmHg) — subclavian stenosis or coarctation at the origin`,`Upper >> lower-extremity BP — coarctation of the aorta`,`Diminished or absent femoral pulses with brachio-femoral delay — coarctation`,`Bounding pulses with wide pulse pressure — AR, PDA, arteriovenous fistula, thyrotoxicosis, anemia`,`Weak thready pulses — low output state (heart failure, shock, hypovolemia)`,`Prolonged capillary refill — dehydration, shock, cold stress`]}]}}},Od={mrc:{title:`MRC strength grade (0–5)`,icon:`fa-hand-fist`,rows:[[`5`,`Normal power — holds against full resistance`],[`4`,`Reduced — moves against gravity + some resistance`],[`3`,`Moves against gravity only (no added resistance)`],[`2`,`Full range with gravity eliminated (horizontal plane)`],[`1`,`Flicker / trace contraction, no joint movement`],[`0`,`No contraction`]]},dtr:{title:`Deep-tendon reflex grade (0–4+)`,icon:`fa-circle-dot`,rows:[[`0`,`Absent`],[`1+`,`Hypoactive — trace, only with reinforcement`],[`2+`,`Normal`],[`3+`,`Brisk — may still be normal in anxious patients`],[`4+`,`Hyperactive with sustained clonus — always abnormal`]]},plantar:{title:`Plantar response (Babinski)`,icon:`fa-shoe-prints`,rows:[[`Down-going`,`Normal in anyone ≥ 2 years`],[`Up-going`,`Normal < 2 years; abnormal after — UMN lesion`],[`Asymmetric`,`Always abnormal at any age`]]},beighton:{title:`Beighton hypermobility score (0–9)`,icon:`fa-hands`,rows:[[`≤ 3`,`Normal flexibility`],[`4`,`Borderline — consider in context`],[`≥ 5`,`Hypermobility spectrum; screen for hEDS if other features present`]]},atr:{title:`Scoliometer — angle of trunk rotation`,icon:`fa-ruler`,rows:[[`< 5°`,`Normal, no follow-up`],[`5–6°`,`Borderline — re-check at each visit`],[`≥ 7°`,`Refer for PA/lateral spine x-ray + orthopedic evaluation`]]},rr:{title:`Respiratory rate — upper limit by age (awake)`,icon:`fa-lungs`,rows:[[`Newborn`,`≤ 60 /min`],[`< 2 months`,`≤ 60 /min (WHO tachypnea cutoff)`],[`2–12 months`,`≤ 50 /min (WHO tachypnea cutoff)`],[`1–5 years`,`≤ 40 /min (WHO tachypnea cutoff)`],[`6–11 years`,`≤ 30 /min`],[`≥ 12 years`,`≤ 20 /min (adult pattern)`]]},spo2:{title:`Pulse oximetry (SpO₂) — at room air`,icon:`fa-heart-pulse`,rows:[[`≥ 95%`,`Normal`],[`92–94%`,`Mild hypoxemia — investigate cause`],[`< 92%`,`Moderate hypoxemia — supplemental O₂`],[`< 88%`,`Severe — urgent intervention; target ≥ 90% acutely`]]},silverman:{title:`Silverman–Andersen retraction score (neonatal, 0–10)`,icon:`fa-baby`,rows:[[`0`,`No respiratory distress`],[`1–3`,`Mild — close observation`],[`4–6`,`Moderate distress — consider CPAP / support`],[`7–10`,`Severe — imminent respiratory failure, intubate`]]},westley:{title:`Westley croup severity score`,icon:`fa-stethoscope`,rows:[[`≤ 2`,`Mild — home management, cool mist, oral dexamethasone`],[`3–5`,`Moderate — nebulised epinephrine + dexamethasone`],[`6–11`,`Severe — admit, continuous monitoring`],[`≥ 12`,`Impending respiratory failure — ICU / airway management`]]},murmurGrade:{title:`Heart-murmur grading (Levine 1–6)`,icon:`fa-wave-square`,rows:[[`1/6`,`Very faint — heard only with concentration`],[`2/6`,`Soft but readily heard`],[`3/6`,`Moderately loud, no thrill`],[`4/6`,`Loud WITH a palpable thrill`],[`5/6`,`Very loud; audible with stethoscope just off the chest`],[`6/6`,`Audible without the stethoscope touching the chest`]]},pulseAmp:{title:`Pulse amplitude grade (0–4)`,icon:`fa-heart-pulse`,rows:[[`0`,`Absent`],[`1+`,`Diminished, thready`],[`2+`,`Normal`],[`3+`,`Bounding`],[`4+`,`Bounding with visible pulsation (e.g., aortic regurgitation)`]]},capRefill:{title:`Capillary refill time`,icon:`fa-hand`,rows:[[`< 2 sec`,`Normal`],[`2–3 sec`,`Borderline — consider hydration / perfusion`],[`≥ 3 sec`,`Delayed — dehydration, shock, low cardiac output`]]}},kd={msk:[`atr`,`beighton`],neuro:[`mrc`,`dtr`,`plantar`],resp:[`rr`,`spo2`,`silverman`,`westley`],cv:[`murmurGrade`,`pulseAmp`,`capRefill`]},Ad=[{letter:`A`,color:`#dc2626`,title:`Aortic area`,location:`2nd ICS, right sternal border`,listen:`S2 (aortic component), aortic stenosis, aortic regurgitation`},{letter:`P`,color:`#2563eb`,title:`Pulmonic area`,location:`2nd ICS, left sternal border`,listen:`S2 (pulmonic component), pulmonic stenosis, PDA, physiologic split of S2`,innocent:`Pulmonary flow murmur (children, adolescents) — upper left sternal border`},{letter:`E`,color:`#059669`,title:`Erb's point`,location:`3rd ICS, left sternal border`,listen:`Aortic regurgitation (best here), transitional zone murmurs`,innocent:`Still's murmur classically radiates to Erb's / LLSB`},{letter:`T`,color:`#d97706`,title:`Tricuspid area`,location:`4th–5th ICS, lower left sternal border`,listen:`Tricuspid regurgitation, VSD, S3/S4, holosystolic murmurs`,innocent:`Still's murmur — vibratory, musical, age 3–7 y (loudest between LLSB and apex)`},{letter:`M`,color:`#7c3aed`,title:`Mitral area (apex)`,location:`5th ICS, mid-clavicular line`,listen:`S1, mitral regurgitation, mitral stenosis (with bell, left-lateral decubitus)`}],jd=[{name:`Still's (vibratory) murmur`,age:`3–7 y (most common in children)`,location:`LLSB, radiating to apex`,character:`Low-frequency vibratory / musical systolic, grade 2–3/6, mid-systolic, "twanging-string" quality`,confirm:`Louder supine, softer or disappears on standing or Valsalva. No radiation to neck/back. Normal S2.`},{name:`Pulmonary flow murmur`,age:`School-age and adolescents, thin chest`,location:`Upper left sternal border (2nd–3rd ICS)`,character:`Soft blowing early systolic ejection, grade 1–2/6, higher-pitched`,confirm:`No ejection click. Physiologic split of S2. Louder supine, softer on standing. No radiation.`},{name:`Venous hum`,age:`Ages 3–8, disappears by adolescence`,location:`Supraclavicular or infraclavicular area, usually right`,character:`Soft continuous hum, louder in diastole. Only innocent continuous murmur.`,confirm:`Disappears when supine OR when jugular vein is gently compressed (key maneuver). Turning head to opposite side also alters it.`},{name:`Carotid bruit / supraclavicular bruit`,age:`Children and adolescents`,location:`Supraclavicular fossa, right > left; may radiate to carotid`,character:`Brief early systolic, grade 2–3/6, higher-pitched than Still's`,confirm:`Softer or disappears with hyperextension of the shoulders. Normal cardiac exam otherwise. No radiation below the clavicles.`},{name:`Peripheral pulmonary stenosis (PPS, neonatal)`,age:`Newborns and infants < 6–12 months`,location:`Upper LSB, radiates to BOTH axillae and the back`,character:`Soft systolic ejection murmur, grade 1–2/6`,confirm:`Typical age + radiation to back/axillae. Resolves by age 1 as branch pulmonary arteries grow. Persistence or louder grade warrants echo.`}],Md=[{key:`normal`,src:`/audio/respiratory/normal-vesicular.ogg`,title:`Normal vesicular breath sounds`,where:`Peripheral lung fields`,features:`Soft, rustling. Inspiration louder and longer than expiration.`,clinical:`Baseline — deviation elsewhere is what you listen for.`},{key:`wheeze`,src:`/audio/respiratory/wheeze.ogg`,title:`Wheeze`,where:`Diffuse in asthma; localised in foreign body`,features:`Continuous, high-pitched, musical. Usually expiratory; biphasic if severe.`,clinical:`Lower-airway narrowing — asthma, bronchiolitis, foreign body, bronchomalacia. Silent chest in severe asthma is an ominous sign.`},{key:`stridor`,src:`/audio/respiratory/stridor.ogg`,title:`Stridor`,where:`Louder over neck than chest — upper airway`,features:`Continuous, high-pitched, harsh. Classically inspiratory (extrathoracic obstruction); biphasic if fixed.`,clinical:`Croup, epiglottitis, foreign body, laryngomalacia (infant). Distinguish from wheeze by auscultating the neck — stridor is loudest there.`},{key:`finecrackles`,src:`/audio/respiratory/crackles-fine.ogg`,title:`Fine (end-inspiratory) crackles`,where:`Bibasilar in pulmonary edema/fibrosis; focal in pneumonia`,features:`Discontinuous, brief, high-pitched. "Velcro" quality. Late inspiratory, do NOT clear with cough.`,clinical:`Alveolar opening — pulmonary fibrosis, pulmonary edema, early pneumonia, atelectasis.`},{key:`coarsecrackles`,src:`/audio/respiratory/crackles-coarse.ogg`,title:`Coarse crackles`,where:`Lower lobes; either side`,features:`Discontinuous, longer and louder than fine crackles. Lower-pitched. Can be early or late inspiratory; often clear partly with cough.`,clinical:`Secretions in larger airways — bronchitis, later pneumonia, bronchiectasis, aspiration.`},{key:`rhonchi`,src:`/audio/respiratory/rhonchi.ogg`,title:`Rhonchi`,where:`Central or anywhere with airway secretions`,features:`Continuous, low-pitched, snore-like. Typically expiratory. Clear or change with cough.`,clinical:`Large-airway secretions — bronchitis, pneumonia with large-airway involvement, cystic fibrosis, bronchiectasis.`},{key:`pleuralrub`,src:`/audio/respiratory/pleural-rub.ogg`,title:`Pleural friction rub`,where:`Focal, often lateral or posterior lower chest`,features:`Grating, creaky — "leather on leather". Biphasic (heard in inspiration and expiration). Does NOT clear with cough.`,clinical:`Pleural inflammation — pleuritis, pulmonary embolism, pneumonia with pleural involvement, viral pleurisy.`}],$=[{key:`normal`,src:`/audio/cardiac/normal.ogg`,title:`Normal heart sounds (S1, S2)`,where:`All four classic auscultation points`,rate:`~61 bpm reference`,features:`"lub-dub": S1 (closure of mitral + tricuspid) louder at apex; S2 (closure of aortic + pulmonic) louder at base. Physiologic S2 split on inspiration.`,clinical:`Reference for rhythm, rate, and the normal S1–S2 interval. Listen for what's changed — not just what's added.`},{key:`infant-normal`,src:`/audio/cardiac/infant-normal.ogg`,title:`Infant normal heart sounds`,where:`Infant chest — rate will be higher than adult`,rate:`Pediatric reference (120–160 bpm range)`,features:`Same S1–S2 pattern, faster rate. Short diastole makes murmurs easier to miss — careful auscultation needed.`,clinical:`Reference for neonatal/infant rhythm. Any murmur in the first 72 h should prompt pre/postductal sat screening.`},{key:`vsd`,src:`/audio/cardiac/vsd.wav`,title:`Ventricular septal defect (VSD)`,where:`Lower left sternal border (4th ICS)`,features:`Harsh, blowing, holosystolic (pansystolic) murmur — plateau shape through all of systole. Often accompanied by a thrill if large.`,clinical:`Most common congenital heart defect. Small VSD: loud murmur, usually asymptomatic, may close spontaneously. Large VSD: softer murmur (less pressure gradient) but signs of heart failure, pulmonary hypertension.`},{key:`mvp`,src:`/audio/cardiac/mitral-prolapse.wav`,title:`Mitral valve prolapse (MVP) — click + late systolic murmur`,where:`Apex (5th ICS, mid-clavicular line)`,features:`Mid-systolic click followed by a late-systolic crescendo murmur. Timing of click changes with maneuvers: earlier with standing or Valsalva, later with squatting.`,clinical:`Often benign, especially in thin young women. Features suggesting need for echo: thickened/redundant leaflets, associated MR, symptoms (palpitations, chest pain), arrhythmias.`},{key:`stills`,src:`/audio/cardiac/stills-murmur.ogg`,title:`Still's murmur (innocent)`,where:`LLSB, radiating to apex`,rate:`Classic age 3–7 y (this recording is a toddler)`,features:`Low-frequency vibratory / musical systolic, grade 2–3/6, mid-systolic, "twanging-string" quality.`,clinical:`The most common innocent murmur of childhood. Louder supine, softer or disappears on standing or Valsalva. Normal S2. No radiation to neck or back. No workup needed when classic.`},{key:`functional`,src:`/audio/cardiac/functional-murmur.wav`,title:`Functional (innocent) murmur — adult female`,where:`Left sternal border, soft systolic`,features:`Soft systolic murmur in a structurally normal heart — often from increased cardiac output, thin chest wall, anemia, hyperthyroidism, or pregnancy.`,clinical:`Benign if it meets the 7 S criteria. Investigate if loud (≥3/6), holosystolic, diastolic, radiating, or with thrill / symptoms.`}],Nd=`rounded-lg border border-border bg-card p-5 space-y-3`,Pd=`px-3 py-1.5 rounded-full text-xs font-medium border transition-colors cursor-pointer`,Fd=`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium disabled:opacity-50`,Id=`rounded-md border border-border bg-background px-3 py-2 text-sm font-medium hover:bg-muted disabled:opacity-50`,Ld=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring`;function Rd(e,t,n,r){return`${e}/${t}/${n}/${r}`}function zd({id:e,scale:t}){return(0,A.jsxs)(`section`,{className:`rounded-md border border-border bg-background p-3`,"data-testid":`scale-`+e,children:[(0,A.jsx)(`h4`,{className:`text-sm font-semibold mb-2`,children:t.title}),(0,A.jsx)(`table`,{className:`w-full text-xs`,children:(0,A.jsx)(`tbody`,{children:t.rows.map(([e,t],n)=>(0,A.jsxs)(`tr`,{className:`border-b border-border last:border-0`,children:[(0,A.jsx)(`td`,{className:`py-1 pr-3 font-mono font-semibold whitespace-nowrap`,children:e}),(0,A.jsx)(`td`,{className:`py-1 text-muted-foreground`,children:t})]},n))})})]})}function Bd({entry:e}){return(0,A.jsxs)(`div`,{className:`rounded-md border border-border bg-background p-3 space-y-2`,"data-testid":`sound-`+e.key,children:[(0,A.jsx)(`div`,{className:`text-sm font-semibold`,children:e.title}),(0,A.jsx)(`audio`,{controls:!0,preload:`none`,className:`w-full`,children:(0,A.jsx)(`source`,{src:e.src})}),(0,A.jsxs)(`div`,{className:`text-xs space-y-0.5 text-muted-foreground`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold`,children:`Where:`}),` `,e.where]}),e.rate&&(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold`,children:`Rate:`}),` `,e.rate]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold`,children:`Features:`}),` `,e.features]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`span`,{className:`font-semibold`,children:`Clinical:`}),` `,e.clinical]})]})]})}function Vd({step:e,status:t,onStatus:n}){let r=`text-xs font-medium px-2 py-1 rounded border`;return(0,A.jsxs)(`div`,{className:`flex items-start gap-2 py-2 border-b border-border last:border-0`,children:[(0,A.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,A.jsx)(`div`,{className:`text-sm font-medium`,children:e.label}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground mt-0.5`,children:[(0,A.jsx)(`span`,{className:`font-semibold uppercase tracking-wide`,children:`Method:`}),` `,e.method]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[(0,A.jsx)(`span`,{className:`font-semibold uppercase tracking-wide`,children:`Normal:`}),` `,e.normal]})]}),(0,A.jsxs)(`div`,{className:`flex flex-col sm:flex-row gap-1 flex-shrink-0`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:()=>n(t===`normal`?null:`normal`),className:r+` `+(t===`normal`?`bg-green-600 text-white border-green-600`:`border-green-600 text-green-700 hover:bg-green-50 dark:hover:bg-green-950/30`),children:`Normal`}),(0,A.jsx)(`button`,{type:`button`,onClick:()=>n(t===`abnormal`?null:`abnormal`),className:r+` `+(t===`abnormal`?`bg-destructive text-white border-destructive`:`border-destructive text-destructive hover:bg-red-50 dark:hover:bg-red-950/30`),children:`Abnormal`})]})]})}function Hd({age:e,sys:t,idx:n,comp:r,getStatus:i,setStatus:a}){return(0,A.jsxs)(`div`,{className:Nd,"data-testid":`pe-component-${e}-${t}-${n}`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:r.name}),(0,A.jsx)(`div`,{children:r.steps.map((r,o)=>{let s=Rd(e,t,n,o);return(0,A.jsx)(Vd,{step:r,status:i(s),onStatus:e=>a(s,e)},o)})}),r.abnormalHints.length>0&&(0,A.jsxs)(`div`,{className:`rounded-md bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-900 p-3`,children:[(0,A.jsx)(`div`,{className:`text-xs font-semibold uppercase tracking-wide text-destructive mb-1`,children:`Watch for`}),(0,A.jsx)(`ul`,{className:`list-disc pl-5 text-xs text-red-900 dark:text-red-200 space-y-0.5`,children:r.abnormalHints.map((e,t)=>(0,A.jsx)(`li`,{children:e},t))})]}),r.pearl&&(0,A.jsxs)(`div`,{className:`rounded-md bg-amber-50 dark:bg-amber-950/30 border border-amber-300 dark:border-amber-800 p-3 text-xs text-amber-900 dark:text-amber-100`,children:[(0,A.jsx)(`span`,{className:`font-semibold uppercase tracking-wide`,children:`Pearl:`}),` `,r.pearl]}),r.significance&&(0,A.jsxs)(`div`,{className:`rounded-md bg-sky-50 dark:bg-sky-950/30 border border-sky-200 dark:border-sky-900 p-3 text-xs text-sky-900 dark:text-sky-100`,children:[(0,A.jsx)(`span`,{className:`font-semibold uppercase tracking-wide`,children:`Significance:`}),` `,r.significance]})]})}function Ud(){let[e,t]=(0,_.useState)(`toddler`),[n,r]=(0,_.useState)(`msk`),[i,a]=(0,_.useState)(``),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(`narrative`),[u,d]=(0,_.useState)({}),[f,p]=(0,_.useState)(null),m=Dd[e],h=m[n],g=M({mutationFn:e=>F.post(`/api/generate-pe-narrative`,e),onSuccess:e=>p(e.narrative),onError:e=>p(`Generation failed: `+e.message)}),v=(0,_.useMemo)(()=>{let t=0,r=0,i=0;return h.components.forEach((a,o)=>a.steps.forEach((a,s)=>{let c=u[Rd(e,n,o,s)]??null;c===`normal`?t++:c===`abnormal`?r++:i++})),{normal:t,abnormal:r,notAssessed:i}},[e,n,h,u]);function y(){d(t=>{let r={...t};return h.components.forEach((t,i)=>t.steps.forEach((t,a)=>{delete r[Rd(e,n,i,a)]})),r}),p(null)}function b(){d(t=>{let r={...t};return h.components.forEach((t,i)=>t.steps.forEach((t,a)=>{r[Rd(e,n,i,a)]=`normal`})),r})}function x(){p(null);let t=[];h.components.forEach((r,i)=>r.steps.forEach((a,o)=>{t.push({component:r.name,label:a.label,method:a.method,normal:a.normal,status:u[Rd(e,n,i,o)]??null})})),g.mutate({steps:t,ageGroup:e,system:n,patientAge:i||void 0,patientGender:o||void 0,format:c})}let S=v.normal+v.abnormal;return(0,A.jsxs)(`div`,{className:`max-w-5xl mx-auto p-6 space-y-5`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Physical Exam Guide`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Age-group and system-specific exam checklist with abnormal-finding hints. Toggle normal / abnormal on each step, then generate a narrative for your note.`})]}),(0,A.jsxs)(`div`,{className:`space-y-2`,children:[(0,A.jsx)(`div`,{className:`text-xs font-semibold text-muted-foreground uppercase tracking-wide`,children:`Age group`}),(0,A.jsx)(`div`,{className:`flex flex-wrap gap-2`,"data-testid":`pe-age-group-pills`,children:wd.map(n=>(0,A.jsx)(`button`,{type:`button`,onClick:()=>t(n),className:Pd+(e===n?` bg-primary text-primary-foreground border-primary`:` bg-muted hover:bg-muted/80 border-border`),"data-testid":`pe-age-`+n,children:Dd[n].label},n))})]}),(0,A.jsxs)(`div`,{className:`space-y-2`,children:[(0,A.jsx)(`div`,{className:`text-xs font-semibold text-muted-foreground uppercase tracking-wide`,children:`System`}),(0,A.jsx)(`div`,{className:`flex flex-wrap gap-2`,"data-testid":`pe-system-pills`,children:Td.map(e=>(0,A.jsx)(`button`,{type:`button`,onClick:()=>r(e),className:Pd+(n===e?` bg-primary text-primary-foreground border-primary`:` bg-muted hover:bg-muted/80 border-border`),"data-testid":`pe-system-`+e,children:Ed[e]},e))})]}),(0,A.jsxs)(`section`,{className:Nd+` border-l-4 border-l-primary`,"data-testid":`pe-overview`,children:[(0,A.jsxs)(`h2`,{className:`text-lg font-semibold`,children:[m.label,` — `,Ed[n]]}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:h.overview})]}),n===`cv`&&(0,A.jsxs)(`section`,{className:Nd,"data-testid":`pe-cv-aptm`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Auscultation landmarks (APTM + Erb's)`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-3`,children:Ad.map(e=>(0,A.jsxs)(`div`,{className:`flex gap-3 items-start rounded-md border border-border p-3`,children:[(0,A.jsx)(`div`,{className:`w-8 h-8 rounded-full flex items-center justify-center font-bold text-white flex-shrink-0`,style:{background:e.color},children:e.letter}),(0,A.jsxs)(`div`,{className:`min-w-0 text-sm`,children:[(0,A.jsx)(`div`,{className:`font-semibold`,children:e.title}),(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:e.location}),(0,A.jsxs)(`div`,{className:`text-xs mt-1`,children:[(0,A.jsx)(`strong`,{children:`Listen for:`}),` `,e.listen]}),e.innocent&&(0,A.jsxs)(`div`,{className:`text-xs text-green-700 dark:text-green-300 mt-1`,children:[(0,A.jsx)(`em`,{children:`Innocent:`}),` `,e.innocent]})]})]},e.letter))}),(0,A.jsx)(`h3`,{className:`text-base font-semibold mt-3`,children:`Cardiac sounds library`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-3`,children:$.map(e=>(0,A.jsx)(Bd,{entry:e},e.key))}),(0,A.jsx)(`h3`,{className:`text-base font-semibold mt-3`,children:`Classic innocent murmurs`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-3`,children:jd.map(e=>(0,A.jsxs)(`div`,{className:`rounded-md border border-green-200 dark:border-green-900 bg-green-50 dark:bg-green-950/30 p-3 text-sm space-y-1`,children:[(0,A.jsx)(`div`,{className:`font-semibold`,children:e.name}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[`Age: `,e.age,` · Location: `,e.location]}),(0,A.jsxs)(`div`,{className:`text-xs`,children:[(0,A.jsx)(`strong`,{children:`Sound:`}),` `,e.character]}),(0,A.jsxs)(`div`,{className:`text-xs`,children:[(0,A.jsx)(`strong`,{children:`Confirm innocent:`}),` `,e.confirm]})]},e.name))})]}),n===`resp`&&(0,A.jsxs)(`section`,{className:Nd,"data-testid":`pe-resp-sounds`,children:[(0,A.jsx)(`h3`,{className:`text-base font-semibold`,children:`Respiratory sounds library`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-3`,children:Md.map(e=>(0,A.jsx)(Bd,{entry:e},e.key))})]}),kd[n]&&kd[n].length>0&&(0,A.jsxs)(`details`,{className:Nd,"data-testid":`pe-scales`,children:[(0,A.jsx)(`summary`,{className:`cursor-pointer font-semibold text-sm`,children:`Grading scales & reference`}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 gap-3 mt-3`,children:kd[n].map(e=>{let t=Od[e];return t?(0,A.jsx)(zd,{id:e,scale:t},e):null})})]}),(0,A.jsxs)(`section`,{className:`space-y-3`,"data-testid":`pe-checklist`,children:[(0,A.jsxs)(`div`,{className:`flex flex-wrap items-center justify-between gap-2`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Exam checklist`}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground flex items-center gap-3`,children:[(0,A.jsxs)(`span`,{className:`text-green-600`,children:[v.normal,` normal`]}),(0,A.jsxs)(`span`,{className:`text-destructive`,children:[v.abnormal,` abnormal`]}),(0,A.jsxs)(`span`,{children:[v.notAssessed,` not assessed`]})]})]}),(0,A.jsxs)(`div`,{className:`flex flex-wrap gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:b,className:Id,"data-testid":`btn-pe-all-normal`,children:`Mark all normal`}),(0,A.jsx)(`button`,{type:`button`,onClick:y,className:Id,"data-testid":`btn-pe-reset`,children:`Reset`})]}),(0,A.jsx)(`div`,{className:`grid grid-cols-1 gap-3`,children:h.components.map((t,r)=>(0,A.jsx)(Hd,{age:e,sys:n,idx:r,comp:t,getStatus:e=>u[e]??null,setStatus:(e,t)=>d(n=>({...n,[e]:t}))},r))})]}),(0,A.jsxs)(`section`,{className:Nd,"data-testid":`pe-generate`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Generate Exam Report`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Uses the statuses above + optional patient context.`}),(0,A.jsxs)(`div`,{className:`grid grid-cols-1 md:grid-cols-3 gap-3`,children:[(0,A.jsx)(`input`,{className:Ld,placeholder:`Patient age (e.g. 3y)`,value:i,onChange:e=>a(e.target.value)}),(0,A.jsx)(`input`,{className:Ld,placeholder:`Patient gender (optional)`,value:o,onChange:e=>s(e.target.value)}),(0,A.jsxs)(`select`,{className:Ld,value:c,onChange:e=>l(e.target.value),children:[(0,A.jsx)(`option`,{value:`narrative`,children:`Narrative`}),(0,A.jsx)(`option`,{value:`list`,children:`List`})]})]}),(0,A.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,A.jsx)(`button`,{type:`button`,className:Fd,onClick:x,disabled:g.isPending||S===0,"data-testid":`btn-pe-generate`,children:g.isPending?`Generating…`:`Generate Exam Report`}),S===0&&(0,A.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:`Mark at least one step before generating.`})]}),f&&(0,A.jsx)(`div`,{className:`rounded-md border border-border bg-muted/40 p-3 whitespace-pre-wrap text-sm`,"data-testid":`pe-narrative`,children:f})]})]})}function Wd(e){if(e==null)return null;let t=String(e).toLowerCase().trim();if(!t)return null;if(/^[\d.]+$/.test(t)){let e=Number.parseFloat(t);return Number.isNaN(e)?null:e}let n=0,r=!1,i=t.match(/([\d.]+)\s*(?:years|year|yrs|yr|y)(?![a-z])/);i&&(n+=Number.parseFloat(i[1])*12,r=!0);let a=t.match(/([\d.]+)\s*(?:months|month|mos|mo|m)(?![a-z])/);a&&(n+=Number.parseFloat(a[1]),r=!0);let o=t.match(/([\d.]+)\s*(?:weeks|week|wks|wk|w)(?![a-z])/);o&&(n+=Number.parseFloat(o[1])*7/30.4375,r=!0);let s=t.match(/([\d.]+)\s*(?:days|day|d)(?![a-z])/);return s&&(n+=Number.parseFloat(s[1])/30.4375,r=!0),r?n:null}function Gd(e){if(e<1){let t=Math.round(e*30.4375);return`${t} day${t===1?``:`s`} (${e.toFixed(2)} mo)`}if(e<24)return`${Kd(e,1)} months`;let t=Math.floor(e/12),n=Math.round(e-t*12);return n===12&&(t+=1,n=0),`${t} yr${n?` ${n} mo`:``} (${Math.round(e)} mo total)`}function Kd(e,t){let n=10**t;return Math.round(e*n)/n}function qd(e){if(e==null||Number.isNaN(e)||e<0)return null;let t=e/12,n;n=e<12?.5*e+4:t<=5?2*t+8:3*t+7;let r;r=e<12?(e+9)/2:t<=5?2*(t+5):4*t;let i;return i=t<13?e<12?`APLS 0-12 mo`:t<=5?`APLS 1-5 yr`:`APLS 6-12 yr`:`Best Guess 5-14 yr`,{weight:Math.max(.3,Kd(t<13?n:r,1)),formulaLabel:i,all:{apls:Kd(n,1),bestGuess:Kd(r,1)}}}function Jd(e,t){return!Number.isFinite(e)||!Number.isFinite(t)||e<=0||t<=0?null:Math.sqrt(t*e/3600)}function Yd(e){let t=e.frequencyPerDay??1,n=e.maxSingleDoseMg??0,r=e.concentrationMgPerMl??0;if(!Number.isFinite(e.weightKg)||!Number.isFinite(e.dosePerKg)||!Number.isFinite(t)||e.weightKg<=0||e.dosePerKg<=0||t<=0)return null;let i=e.weightKg*e.dosePerKg,a=!1;return n>0&&i>n&&(i=n,a=!0),{singleDoseMg:i,dailyDoseMg:i*t,frequencyPerDay:t,capped:a,volumeMl:r>0?i/r:null}}function Xd(e,t,n){if(!Number.isFinite(e)||!Number.isFinite(t)||!Number.isFinite(n)||e<1||e>4||t<1||t>5||n<1||n>6)return null;let r=e+t+n,i;return i=r<=8?`Severe (Coma)`:r<=12?`Moderate`:`Mild`,{total:r,severity:i}}var Zd=`rounded-lg border border-border bg-card p-5 space-y-3`,Qd=`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium`,$d=`rounded-md border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted`,ef=`block text-xs font-medium text-muted-foreground`,tf=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring`,nf=[{id:`neonatal`,label:`Neonatal`,icon:`👶`,summary:`GA classification, AGA/SGA/LGA, prematurity category (Fenton 2013 / WHO).`},{id:`airway`,label:`Airway / RSI`,icon:`💨`,summary:`ETT size + depth, RSI induction + paralytic dosing by weight.`},{id:`cardiac`,label:`Cardiac Arrest`,icon:`❤️`,summary:`PALS dosing (epinephrine, amiodarone, lidocaine), defibrillation J/kg.`},{id:`respiratory`,label:`Respiratory`,icon:`🫁`,summary:`Asthma, bronchiolitis, croup severity + dosing.`},{id:`ventilation`,label:`O₂ & Ventilation`,icon:`🌀`,summary:`NC / HFNC / CPAP / BiPAP flow + FiO₂ targets by age.`},{id:`seizure`,label:`Seizures`,icon:`🧠`,summary:`Benzodiazepine + second/third-line weight-based dosing.`},{id:`sepsis`,label:`Sepsis & Fever`,icon:`🦠`,summary:`Empirical antibiotics + fluid bolus dosing by weight.`},{id:`anaphylaxis`,label:`Anaphylaxis`,icon:`💉`,summary:`Epinephrine IM, IV infusion, steroid + antihistamine dosing.`},{id:`sedation`,label:`Sedation`,icon:`🛌`,summary:`Procedural sedation regimens — ketamine, propofol, midazolam.`},{id:`agitation`,label:`Agitation`,icon:`😤`,summary:`Weight-based haloperidol, olanzapine, lorazepam.`},{id:`antiemetics`,label:`Antiemetics`,icon:`💊`,summary:`Ondansetron, metoclopramide, promethazine dosing.`},{id:`antimicrobials`,label:`Antimicrobials`,icon:`🧫`,summary:`Common empirical regimens keyed to syndrome + weight.`},{id:`burns`,label:`Burns`,icon:`🔥`,summary:`TBSA % (Lund-Browder, Rule of Nines-children), Parkland fluids.`},{id:`toxicology`,label:`Toxicology`,icon:`☠️`,summary:`Common toxidromes + antidotes + decontamination windows.`},{id:`trauma`,label:`Trauma`,icon:`🩹`,summary:`PECARN, c-spine, blood-product dosing, TXA.`}];function rf(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(`apls`),[i,a]=(0,_.useState)(``),o=Wd(e),s=o==null?null:qd(o),c=s?n===`bestguess`?s.all.bestGuess:s.all.apls:null,l=i.trim()||(c==null?``:String(c));function u(){t(``),r(`apls`),a(``)}return(0,A.jsxs)(`section`,{className:Zd,"data-testid":`bedside-weight-estimator`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Age → Weight Estimator`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Shared starting point for Bedside dosing. Uses the same APLS and Best Guess formulas as the legacy app.`})]}),(0,A.jsxs)(`div`,{className:`grid gap-3 md:grid-cols-[1.2fr_1fr_1fr_auto] md:items-end`,children:[(0,A.jsxs)(`div`,{className:`space-y-1`,children:[(0,A.jsx)(`label`,{htmlFor:`bedside-react-age`,className:ef,children:`Age`}),(0,A.jsx)(`input`,{id:`bedside-react-age`,value:e,onChange:e=>t(e.target.value),placeholder:`e.g. "18m", "3y", "2y5m"`,className:tf,"data-testid":`bedside-age-input`})]}),(0,A.jsxs)(`div`,{className:`space-y-1`,children:[(0,A.jsx)(`label`,{htmlFor:`bedside-react-formula`,className:ef,children:`Formula`}),(0,A.jsxs)(`select`,{id:`bedside-react-formula`,value:n,onChange:e=>{r(e.target.value),a(``)},className:tf,"data-testid":`bedside-formula-select`,children:[(0,A.jsx)(`option`,{value:`apls`,children:`APLS`}),(0,A.jsx)(`option`,{value:`bestguess`,children:`Best Guess`})]})]}),(0,A.jsxs)(`div`,{className:`space-y-1`,children:[(0,A.jsx)(`label`,{htmlFor:`bedside-react-weight`,className:ef,children:`Weight (kg)`}),(0,A.jsx)(`input`,{id:`bedside-react-weight`,type:`number`,min:`0.3`,step:`0.1`,value:l,onChange:e=>a(e.target.value),className:tf,"data-testid":`bedside-weight-input`})]}),(0,A.jsx)(`button`,{type:`button`,onClick:u,className:$d,children:`Clear`})]}),e.trim()&&o==null?(0,A.jsx)(`div`,{className:`rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700 dark:bg-red-950/30 dark:text-red-200`,children:`Could not parse age. Try "3y", "18 months", or "15 days".`}):null,s&&c!=null?(0,A.jsxs)(`div`,{className:`rounded-lg border border-border bg-muted/40 p-4 text-sm`,"data-testid":`bedside-estimate-result`,children:[(0,A.jsxs)(`div`,{className:`font-semibold`,children:[c,` kg estimated from `,Gd(o??0)]}),(0,A.jsxs)(`div`,{className:`text-muted-foreground`,children:[`APLS: `,s.all.apls,` kg · Best Guess: `,s.all.bestGuess,` kg. You can override the weight field.`]})]}):null]})}function af({pill:e}){return(0,A.jsxs)(`section`,{className:Zd,"data-testid":`bedside-panel-`+e.id,children:[(0,A.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,A.jsx)(`span`,{className:`text-2xl`,"aria-hidden":!0,children:e.icon}),(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:e.label})]}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:e.summary}),(0,A.jsx)(`div`,{className:`rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm space-y-2`,children:(0,A.jsx)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:`Weight-based calculators for this module run in the legacy viewer while the clinical data is verified for a direct React port. Open the legacy Bedside tab to use the full dosing flow.`})}),(0,A.jsx)(`a`,{href:`/#bedside`,className:Qd+` inline-block`,children:`Open in legacy viewer`})]})}function of(){let[e,t]=(0,_.useState)(nf[0].id),n=nf.find(t=>t.id===e)??nf[0];return(0,A.jsxs)(`div`,{className:`max-w-5xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Bedside`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Emergency and rapid-reference pediatric tools. Weight-based dosing throughout — always verify against institutional protocols.`})]}),(0,A.jsx)(`div`,{className:`flex flex-wrap gap-2`,"data-testid":`bedside-subnav`,children:nf.map(n=>(0,A.jsxs)(`button`,{type:`button`,onClick:()=>t(n.id),className:`px-3 py-1.5 rounded-full text-xs font-medium border transition-colors `+(e===n.id?`bg-primary text-primary-foreground border-primary`:`bg-muted hover:bg-muted/80 border-border`),"data-testid":`bedside-pill-`+n.id,children:[(0,A.jsx)(`span`,{className:`mr-1`,"aria-hidden":!0,children:n.icon}),n.label]},n.id))}),(0,A.jsx)(rf,{}),(0,A.jsx)(af,{pill:n})]})}var sf=`rounded-lg border border-border bg-card p-5 space-y-3`,cf=`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium`,lf=`rounded-md border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted`,uf=`space-y-1`,df=`block text-xs font-medium text-muted-foreground`,ff=`w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring`,pf=`rounded-lg border border-border bg-muted/40 p-4`,mf=`rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700 dark:bg-red-950/30 dark:text-red-200`,hf=[{id:`bp`,label:`BP Percentile`,summary:`AAP 2017 age/height/sex-adjusted BP percentiles (Rosner quantile splines).`,source:`AAP 2017 (Flynn) — Rosner splines`},{id:`bmi`,label:`BMI Percentile`,summary:`BMI-for-age (CDC 2000 z-score tables).`,source:`CDC 2000 LMS`},{id:`growth`,label:`Growth Charts`,summary:`WHO 0–2 y / CDC 2–20 y; Fenton 2013 preterm (weight, length, head).`,source:`WHO 2006 + CDC 2000 + Fenton 2013 LMS`},{id:`bili`,label:`Bilirubin`,summary:`AAP 2022 phototherapy + exchange thresholds and Bhutani nomogram risk zones.`,source:`AAP 2022 (Kemper) + Bhutani 1999`},{id:`vitals`,label:`Vital Signs`,summary:`Normal HR / RR / BP ranges by age.`,source:`PALS + AHA reference`},{id:`bsa`,label:`Body Surface Area`,summary:`Mosteller body surface area formula.`,source:`Mosteller 1987`,ported:!0},{id:`dose`,label:`Weight-Based Dosing`,summary:`Generic mg/kg dosing with optional max-dose cap and concentration conversion.`,source:`Legacy calculator formula`,ported:!0},{id:`resus`,label:`Resus Meds`,summary:`Code-cart dosing (epinephrine, amiodarone, atropine, etc.).`,source:`PALS`},{id:`gcs`,label:`GCS`,summary:`Child/adult and infant Glasgow Coma Scale variants.`,source:`Teasdale + pediatric modification`,ported:!0},{id:`equipment`,label:`Equipment`,summary:`ETT size, blade, NG, Foley, suction by age/weight.`,source:`PALS + Broselow cross-reference`}];function gf(e){if(!e.trim())return null;let t=Number(e);return Number.isFinite(t)?t:null}function _f({id:e,labelText:t,value:n,onChange:r,min:i,max:a,step:o=`0.1`,placeholder:s}){return(0,A.jsxs)(`div`,{className:uf,children:[(0,A.jsx)(`label`,{htmlFor:e,className:df,children:t}),(0,A.jsx)(`input`,{id:e,type:`number`,min:i,max:a,step:o,value:n,onChange:e=>r(e.target.value),placeholder:s,className:ff})]})}function vf(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(null),[o,s]=(0,_.useState)(``);function c(){let t=Jd(Number(e),Number(n));if(t==null){s(`Enter a valid weight and height.`),a(null);return}s(``),a(t)}function l(){t(``),r(``),a(null),s(``)}return(0,A.jsxs)(`section`,{className:sf,"data-testid":`calc-panel-bsa`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Body Surface Area`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Mosteller formula: BSA (m2) = sqrt(height(cm) x weight(kg) / 3600).`}),(0,A.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2`,children:[(0,A.jsx)(_f,{id:`react-bsa-weight`,labelText:`Weight (kg)`,value:e,onChange:t,min:`1`,max:`200`,placeholder:`20`}),(0,A.jsx)(_f,{id:`react-bsa-height`,labelText:`Height (cm)`,value:n,onChange:r,min:`30`,max:`220`,placeholder:`110`})]}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:c,className:cf,"data-testid":`calc-bsa-calculate`,children:`Calculate`}),(0,A.jsx)(`button`,{type:`button`,onClick:l,className:lf,children:`Clear`})]}),o?(0,A.jsx)(`div`,{className:mf,children:o}):null,i==null?null:(0,A.jsxs)(`div`,{className:pf,"data-testid":`calc-bsa-result`,children:[(0,A.jsx)(`div`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Mosteller BSA`}),(0,A.jsxs)(`div`,{className:`text-2xl font-semibold`,children:[i.toFixed(3),` m²`]}),(0,A.jsxs)(`div`,{className:`text-sm text-muted-foreground`,children:[e,` kg, `,n,` cm`]})]})]})}function yf(){let[e,t]=(0,_.useState)(``),[n,r]=(0,_.useState)(``),[i,a]=(0,_.useState)(`1`),[o,s]=(0,_.useState)(``),[c,l]=(0,_.useState)(``),[u,d]=(0,_.useState)(null),[f,p]=(0,_.useState)(``);function m(){let t=Yd({weightKg:Number(e),dosePerKg:Number(n),frequencyPerDay:Number(i),maxSingleDoseMg:gf(o),concentrationMgPerMl:gf(c)});if(t==null){p(`Enter a valid weight, mg/kg dose, and frequency.`),d(null);return}p(``),d(t)}function h(){t(``),r(``),a(`1`),s(``),l(``),d(null),p(``)}return(0,A.jsxs)(`section`,{className:sf,"data-testid":`calc-panel-dose`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Weight-Based Dosing`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Generic mg/kg calculator. Always verify medication-specific dosing against formulary and local policy.`}),(0,A.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2 lg:grid-cols-3`,children:[(0,A.jsx)(_f,{id:`react-dose-weight`,labelText:`Patient Weight (kg)`,value:e,onChange:t,min:`1`,max:`200`,placeholder:`15`}),(0,A.jsx)(_f,{id:`react-dose-per-kg`,labelText:`Dose (mg/kg)`,value:n,onChange:r,min:`0.01`,step:`0.01`,placeholder:`10`}),(0,A.jsxs)(`div`,{className:uf,children:[(0,A.jsx)(`label`,{htmlFor:`react-dose-frequency`,className:df,children:`Frequency`}),(0,A.jsxs)(`select`,{id:`react-dose-frequency`,value:i,onChange:e=>a(e.target.value),className:ff,children:[(0,A.jsx)(`option`,{value:`1`,children:`Once daily`}),(0,A.jsx)(`option`,{value:`2`,children:`Twice daily (BID)`}),(0,A.jsx)(`option`,{value:`3`,children:`Three times daily (TID)`}),(0,A.jsx)(`option`,{value:`4`,children:`Four times daily (QID)`}),(0,A.jsx)(`option`,{value:`6`,children:`Every 4 hours (Q4H)`})]})]}),(0,A.jsx)(_f,{id:`react-dose-max`,labelText:`Max single dose (mg, optional)`,value:o,onChange:s,min:`0`,step:`1`,placeholder:`500`}),(0,A.jsx)(_f,{id:`react-dose-concentration`,labelText:`Concentration (mg/mL, optional)`,value:c,onChange:l,min:`0`,placeholder:`40`})]}),(0,A.jsxs)(`div`,{className:`flex gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:m,className:cf,"data-testid":`calc-dose-calculate`,children:`Calculate`}),(0,A.jsx)(`button`,{type:`button`,onClick:h,className:lf,children:`Clear`})]}),f?(0,A.jsx)(`div`,{className:mf,children:f}):null,u==null?null:(0,A.jsx)(`div`,{className:pf,"data-testid":`calc-dose-result`,children:(0,A.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-3`,children:[(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Single Dose`}),(0,A.jsxs)(`div`,{className:`text-xl font-semibold`,children:[u.singleDoseMg.toFixed(1),` mg`]}),u.capped?(0,A.jsx)(`div`,{className:`text-xs text-red-600`,children:`Capped at max dose`}):null]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Daily Total`}),(0,A.jsxs)(`div`,{className:`text-xl font-semibold`,children:[u.dailyDoseMg.toFixed(1),` mg/day`]}),(0,A.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[`x `,u.frequencyPerDay,`/day`]})]}),(0,A.jsxs)(`div`,{children:[(0,A.jsx)(`div`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:`Volume`}),(0,A.jsx)(`div`,{className:`text-xl font-semibold`,children:u.volumeMl==null?`n/a`:`${u.volumeMl.toFixed(1)} mL`}),(0,A.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:`per dose`})]})]})})]})}var bf={child:{eye:[[`4`,`4 - Spontaneous`],[`3`,`3 - To speech`],[`2`,`2 - To pain`],[`1`,`1 - None`]],verbal:[[`5`,`5 - Oriented`],[`4`,`4 - Confused`],[`3`,`3 - Inappropriate words`],[`2`,`2 - Incomprehensible sounds`],[`1`,`1 - None`]],motor:[[`6`,`6 - Obeys commands`],[`5`,`5 - Localizes pain`],[`4`,`4 - Withdraws to pain`],[`3`,`3 - Abnormal flexion`],[`2`,`2 - Abnormal extension`],[`1`,`1 - None`]]},infant:{eye:[[`4`,`4 - Spontaneous`],[`3`,`3 - To speech/sound`],[`2`,`2 - To painful stimuli`],[`1`,`1 - None`]],verbal:[[`5`,`5 - Coos/babbles`],[`4`,`4 - Irritable cry`],[`3`,`3 - Cries to pain`],[`2`,`2 - Moans to pain`],[`1`,`1 - None`]],motor:[[`6`,`6 - Normal spontaneous movement`],[`5`,`5 - Withdraws to touch`],[`4`,`4 - Withdraws to pain`],[`3`,`3 - Abnormal flexion`],[`2`,`2 - Abnormal extension`],[`1`,`1 - None`]]}};function xf({id:e,labelText:t,value:n,options:r,onChange:i}){return(0,A.jsxs)(`div`,{className:uf,children:[(0,A.jsx)(`label`,{htmlFor:e,className:df,children:t}),(0,A.jsx)(`select`,{id:e,value:n,onChange:e=>i(e.target.value),className:ff,children:r.map(([e,t])=>(0,A.jsx)(`option`,{value:e,children:t},e))})]})}function Sf(){let[e,t]=(0,_.useState)(`child`),[n,r]=(0,_.useState)(`4`),[i,a]=(0,_.useState)(`5`),[o,s]=(0,_.useState)(`6`),c=Xd(Number(n),Number(i),Number(o)),l=bf[e];function u(e){t(e),r(`4`),a(`5`),s(`6`)}return(0,A.jsxs)(`section`,{className:sf,"data-testid":`calc-panel-gcs`,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Glasgow Coma Scale`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Select responses to calculate child/adult or infant-modified GCS. Total score 3-15.`}),(0,A.jsxs)(`div`,{className:`flex flex-wrap gap-2`,children:[(0,A.jsx)(`button`,{type:`button`,onClick:()=>u(`child`),className:`px-3 py-1.5 rounded-full text-xs font-medium border `+(e===`child`?`bg-primary text-primary-foreground border-primary`:`bg-muted border-border`),children:`Child / Adult`}),(0,A.jsx)(`button`,{type:`button`,onClick:()=>u(`infant`),className:`px-3 py-1.5 rounded-full text-xs font-medium border `+(e===`infant`?`bg-primary text-primary-foreground border-primary`:`bg-muted border-border`),children:`Infant`})]}),(0,A.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-3`,children:[(0,A.jsx)(xf,{id:`react-gcs-eye`,labelText:`Eye Opening`,value:n,options:l.eye,onChange:r}),(0,A.jsx)(xf,{id:`react-gcs-verbal`,labelText:`Verbal Response`,value:i,options:l.verbal,onChange:a}),(0,A.jsx)(xf,{id:`react-gcs-motor`,labelText:`Motor Response`,value:o,options:l.motor,onChange:s})]}),c==null?null:(0,A.jsxs)(`div`,{className:pf,"data-testid":`calc-gcs-result`,children:[(0,A.jsx)(`div`,{className:`text-xs uppercase tracking-wide text-muted-foreground`,children:e===`infant`?`Infant-modified GCS`:`Child / adult GCS`}),(0,A.jsxs)(`div`,{className:`text-3xl font-semibold`,children:[`GCS: `,c.total,`/15`]}),(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:c.severity}),(0,A.jsx)(`div`,{className:`mt-2 text-xs text-muted-foreground`,children:`Interpretation: 13-15 Mild, 9-12 Moderate, 3-8 Severe/Coma.`})]})]})}function Cf({pill:e}){return(0,A.jsxs)(`section`,{className:sf,"data-testid":`calc-panel-`+e.id,children:[(0,A.jsx)(`h2`,{className:`text-lg font-semibold`,children:e.label}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:e.summary}),(0,A.jsxs)(`div`,{className:`rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm space-y-2`,children:[(0,A.jsxs)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:[(0,A.jsx)(`strong`,{children:`Source of truth:`}),` `,e.source,`.`]}),(0,A.jsx)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:`This calculator runs in the legacy viewer. A React port is gated on capturing test vectors from the vanilla implementation so the numerical output can be verified byte-for-byte — the migration checkpoint specifically flags this class of data as the one an LLM is most likely to silently simplify.`})]}),(0,A.jsx)(`a`,{href:`/#calculators`,className:cf+` inline-block`,children:`Open in legacy viewer`})]})}function wf({pill:e}){return e.id===`bsa`?(0,A.jsx)(vf,{}):e.id===`dose`?(0,A.jsx)(yf,{}):e.id===`gcs`?(0,A.jsx)(Sf,{}):(0,A.jsx)(Cf,{pill:e})}function Tf(){let[e,t]=(0,_.useState)(hf[0].id),n=hf.find(t=>t.id===e)??hf[0];return(0,A.jsxs)(`div`,{className:`max-w-5xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Calculators`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Pediatric calculators — BP percentiles, bilirubin thresholds, growth, dosing, equipment sizing. Simple pure-formula calculators run in React now; high-risk table-driven calculators remain legacy-gated until vectors are captured.`})]}),(0,A.jsx)(`div`,{className:`flex flex-wrap gap-2`,"data-testid":`calc-subnav`,children:hf.map(n=>(0,A.jsxs)(`button`,{type:`button`,onClick:()=>t(n.id),className:`px-3 py-1.5 rounded-full text-xs font-medium border transition-colors `+(e===n.id?`bg-primary text-primary-foreground border-primary`:`bg-muted hover:bg-muted/80 border-border`),"data-testid":`calc-pill-`+n.id,children:[n.label,n.ported?(0,A.jsx)(`span`,{className:`ml-1 text-[10px] opacity-80`,children:`React`}):null]},n.id))}),(0,A.jsx)(wf,{pill:n})]})}var Ef=`rounded-lg border border-border bg-card p-5 space-y-3`,Df=`rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium`;function Of(){let{data:e,isLoading:t}=j({queryKey:[`auth-me`],queryFn:()=>F.get(`/api/auth/me`),staleTime:5*6e4});return t?(0,A.jsx)(`div`,{className:`max-w-3xl mx-auto p-6`,children:(0,A.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:`Checking permissions…`})}):e?.user.role===`admin`?(0,A.jsxs)(`div`,{className:`max-w-4xl mx-auto p-6 space-y-4`,children:[(0,A.jsxs)(`header`,{children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Admin Panel`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Registration, users, feature flags, OIDC, email templates, AI prompts, SMTP, saved encounters (site-wide), AI model management, TTS/STT provider selection.`})]}),(0,A.jsxs)(`section`,{className:Ef,"data-testid":`admin-shell`,children:[(0,A.jsx)(`div`,{className:`rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm space-y-2`,children:(0,A.jsx)(`p`,{className:`text-amber-900 dark:text-amber-100`,children:`Admin sub-sections still live in the legacy viewer. Each one (user management, feature flags, announcements, OIDC config, SMTP, AI prompts, model management, TTS/STT) touches production state immediately when saved — those controls port in discrete follow-up commits with their own tests before they go live in the React tree.`})}),(0,A.jsx)(`a`,{href:`/#admin`,className:Df+` inline-block`,children:`Open admin in legacy viewer`})]})]}):(0,A.jsx)(`div`,{className:`max-w-3xl mx-auto p-6`,children:(0,A.jsxs)(`section`,{className:Ef,"data-testid":`admin-access-denied`,children:[(0,A.jsx)(`h1`,{className:`text-xl font-semibold`,children:`Admin only`}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`This page is restricted to users with the admin role. If you believe this is a mistake, contact your site administrator.`})]})})}var kf=new Ze({defaultOptions:{queries:{staleTime:3e4,retry:1}}});function Af(){return(0,A.jsxs)(`div`,{className:`max-w-3xl mx-auto p-6 space-y-4`,children:[(0,A.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Pediatric AI Scribe — React client`}),(0,A.jsxs)(`p`,{className:`text-sm text-muted-foreground`,children:[`This is the new React tree. The legacy vanilla-JS app still lives at`,` `,(0,A.jsx)(`a`,{href:`/`,className:`underline`,children:`/`}),`.`]}),(0,A.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Ported tabs so far:`}),(0,A.jsxs)(`ul`,{className:`list-disc pl-6 text-sm space-y-1`,children:[(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/encounter`,className:`underline`,children:`Encounter HPI`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/dictation`,className:`underline`,children:`Dictation HPI`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/soap`,className:`underline`,children:`SOAP Note`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/sickvisit`,className:`underline`,children:`Sick Visit`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/extensions`,className:`underline`,children:`Extensions & Pagers`})}),(0,A.jsx)(`li`,{children:(0,A.jsx)(ii,{to:`/faq`,className:`underline`,children:`FAQ`})})]})]})}function jf(){return(0,A.jsx)(tt,{client:kf,children:(0,A.jsx)(ti,{basename:`/app`,children:(0,A.jsx)(mr,{children:(0,A.jsxs)(fr,{element:(0,A.jsx)(Ei,{}),children:[(0,A.jsx)(fr,{path:`/`,element:(0,A.jsx)(Af,{})}),(0,A.jsx)(fr,{path:`/encounter`,element:(0,A.jsx)(Cu,{})}),(0,A.jsx)(fr,{path:`/dictation`,element:(0,A.jsx)(Su,{})}),(0,A.jsx)(fr,{path:`/soap`,element:(0,A.jsx)(wu,{})}),(0,A.jsx)(fr,{path:`/sickvisit`,element:(0,A.jsx)(Tu,{})}),(0,A.jsx)(fr,{path:`/hospital`,element:(0,A.jsx)(Eu,{})}),(0,A.jsx)(fr,{path:`/chart`,element:(0,A.jsx)(Ou,{})}),(0,A.jsx)(fr,{path:`/wellvisit`,element:(0,A.jsx)(ku,{})}),(0,A.jsx)(fr,{path:`/vaxschedule`,element:(0,A.jsx)(Au,{})}),(0,A.jsx)(fr,{path:`/catchup`,element:(0,A.jsx)(ju,{})}),(0,A.jsx)(fr,{path:`/extensions`,element:(0,A.jsx)(vu,{})}),(0,A.jsx)(fr,{path:`/settings`,element:(0,A.jsx)(fd,{})}),(0,A.jsx)(fr,{path:`/learning`,element:(0,A.jsx)(Cd,{})}),(0,A.jsx)(fr,{path:`/peguide`,element:(0,A.jsx)(Ud,{})}),(0,A.jsx)(fr,{path:`/bedside`,element:(0,A.jsx)(of,{})}),(0,A.jsx)(fr,{path:`/calculators`,element:(0,A.jsx)(Tf,{})}),(0,A.jsx)(fr,{path:`/admin`,element:(0,A.jsx)(Of,{})}),(0,A.jsx)(fr,{path:`/faq`,element:(0,A.jsx)(xu,{})}),(0,A.jsx)(fr,{path:`*`,element:(0,A.jsx)(ur,{to:`/`,replace:!0})})]})})})})}(0,v.createRoot)(document.getElementById(`root`)).render((0,A.jsx)(_.StrictMode,{children:(0,A.jsx)(jf,{})})); \ No newline at end of file diff --git a/public/app/assets/index-Cun0WMrQ.css b/public/app/assets/index-Cun0WMrQ.css new file mode 100644 index 0000000..38f43e8 --- /dev/null +++ b/public/app/assets/index-Cun0WMrQ.css @@ -0,0 +1,2 @@ +/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-900:oklch(40.8% .123 38.172);--color-orange-950:oklch(26.6% .079 36.259);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-amber-950:oklch(27.9% .077 45.635);--color-green-50:oklch(98.2% .018 155.826);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-900:oklch(39.3% .095 152.535);--color-green-950:oklch(26.6% .065 152.934);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-100:oklch(95.1% .026 236.824);--color-sky-200:oklch(90.1% .058 230.902);--color-sky-900:oklch(39.1% .09 240.876);--color-sky-950:oklch(29.3% .066 243.157);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--radius:.5rem;--color-background:#fff;--color-foreground:#0f172a;--color-muted:#f1f5f9;--color-muted-foreground:#64748b;--color-card:#fff;--color-primary:#0f172a;--color-primary-foreground:#f8fafc;--color-destructive:#ef4343;--color-border:#e2e8f0;--color-input:#e2e8f0;--color-ring:#94a3b8}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.collapse{visibility:collapse}.visible{visibility:visible}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.top-0{top:calc(var(--spacing) * 0)}.left-0{left:calc(var(--spacing) * 0)}.z-50{z-index:50}.col-span-2{grid-column:span 2/span 2}.col-span-3{grid-column:span 3/span 3}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-2{margin-left:calc(var(--spacing) * 2)}.block{display:block}.flex{display:flex}.grid{display:grid}.inline-block{display:inline-block}.table{display:table}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.h-8{height:calc(var(--spacing) * 8)}.h-screen{height:100vh}.min-h-\[60px\]{min-height:60px}.min-h-\[100px\]{min-height:100px}.min-h-\[120px\]{min-height:120px}.min-h-\[160px\]{min-height:160px}.min-h-\[200px\]{min-height:200px}.min-h-\[220px\]{min-height:220px}.min-h-screen{min-height:100vh}.w-8{width:calc(var(--spacing) * 8)}.w-20{width:calc(var(--spacing) * 20)}.w-64{width:calc(var(--spacing) * 64)}.w-full{width:100%}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-\[140px\]{max-width:140px}.max-w-\[240px\]{max-width:240px}.max-w-full{max-width:100%}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[150px\]{min-width:150px}.min-w-\[180px\]{min-width:180px}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing) * 1)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:var(--radius)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-amber-300{border-color:var(--color-amber-300)}.border-border{border-color:var(--color-border)}.border-destructive{border-color:var(--color-destructive)}.border-green-200{border-color:var(--color-green-200)}.border-green-600{border-color:var(--color-green-600)}.border-input{border-color:var(--color-input)}.border-primary{border-color:var(--color-primary)}.border-red-200{border-color:var(--color-red-200)}.border-sky-200{border-color:var(--color-sky-200)}.border-l-orange-500{border-left-color:var(--color-orange-500)}.border-l-primary{border-left-color:var(--color-primary)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-background{background-color:var(--color-background)}.bg-black\/40{background-color:#0006}@supports (color:color-mix(in lab, red, red)){.bg-black\/40{background-color:color-mix(in oklab, var(--color-black) 40%, transparent)}}.bg-card{background-color:var(--color-card)}.bg-destructive{background-color:var(--color-destructive)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-600{background-color:var(--color-green-600)}.bg-muted{background-color:var(--color-muted)}.bg-muted\/20{background-color:#f1f5f933}@supports (color:color-mix(in lab, red, red)){.bg-muted\/20{background-color:color-mix(in oklab, var(--color-muted) 20%, transparent)}}.bg-muted\/30{background-color:#f1f5f94d}@supports (color:color-mix(in lab, red, red)){.bg-muted\/30{background-color:color-mix(in oklab, var(--color-muted) 30%, transparent)}}.bg-muted\/40{background-color:#f1f5f966}@supports (color:color-mix(in lab, red, red)){.bg-muted\/40{background-color:color-mix(in oklab, var(--color-muted) 40%, transparent)}}.bg-orange-50{background-color:var(--color-orange-50)}.bg-primary{background-color:var(--color-primary)}.bg-primary\/5{background-color:#0f172a0d}@supports (color:color-mix(in lab, red, red)){.bg-primary\/5{background-color:color-mix(in oklab, var(--color-primary) 5%, transparent)}}.bg-primary\/10{background-color:#0f172a1a}@supports (color:color-mix(in lab, red, red)){.bg-primary\/10{background-color:color-mix(in oklab, var(--color-primary) 10%, transparent)}}.bg-red-50{background-color:var(--color-red-50)}.bg-sky-50{background-color:var(--color-sky-50)}.bg-white{background-color:var(--color-white)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-8{padding-block:calc(var(--spacing) * 8)}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-6{padding-left:calc(var(--spacing) * 6)}.pl-8{padding-left:calc(var(--spacing) * 8)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-600{color:var(--color-amber-600)}.text-amber-900{color:var(--color-amber-900)}.text-destructive{color:var(--color-destructive)}.text-foreground{color:var(--color-foreground)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-muted-foreground{color:var(--color-muted-foreground)}.text-orange-500{color:var(--color-orange-500)}.text-orange-900{color:var(--color-orange-900)}.text-primary{color:var(--color-primary)}.text-primary-foreground{color:var(--color-primary-foreground)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-900{color:var(--color-red-900)}.text-sky-900{color:var(--color-sky-900)}.text-white{color:var(--color-white)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.accent-orange-500{accent-color:var(--color-orange-500)}.accent-primary{accent-color:var(--color-primary)}.opacity-60{opacity:.6}.opacity-80{opacity:.8}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}.even\:bg-muted\/20:nth-child(2n){background-color:#f1f5f933}@supports (color:color-mix(in lab, red, red)){.even\:bg-muted\/20:nth-child(2n){background-color:color-mix(in oklab, var(--color-muted) 20%, transparent)}}@media (hover:hover){.hover\:bg-green-50:hover{background-color:var(--color-green-50)}.hover\:bg-muted:hover{background-color:var(--color-muted)}.hover\:bg-muted\/40:hover{background-color:#f1f5f966}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/40:hover{background-color:color-mix(in oklab, var(--color-muted) 40%, transparent)}}.hover\:bg-muted\/50:hover{background-color:#f1f5f980}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/50:hover{background-color:color-mix(in oklab, var(--color-muted) 50%, transparent)}}.hover\:bg-muted\/60:hover{background-color:#f1f5f999}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/60:hover{background-color:color-mix(in oklab, var(--color-muted) 60%, transparent)}}.hover\:bg-muted\/80:hover{background-color:#f1f5f9cc}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/80:hover{background-color:color-mix(in oklab, var(--color-muted) 80%, transparent)}}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-ring:focus{--tw-ring-color:var(--color-ring)}.disabled\:opacity-50:disabled{opacity:.5}@media (width>=40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}}@media (width>=48rem){.md\:col-span-1{grid-column:span 1/span 1}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-\[1\.2fr_1fr_1fr_auto\]{grid-template-columns:1.2fr 1fr 1fr auto}.md\:items-end{align-items:flex-end}}@media (width>=64rem){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (prefers-color-scheme:dark){.dark\:border-amber-800{border-color:var(--color-amber-800)}.dark\:border-green-900{border-color:var(--color-green-900)}.dark\:border-red-900{border-color:var(--color-red-900)}.dark\:border-sky-900{border-color:var(--color-sky-900)}.dark\:bg-amber-950\/30{background-color:#4619014d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-950\/30{background-color:color-mix(in oklab, var(--color-amber-950) 30%, transparent)}}.dark\:bg-green-950\/30{background-color:#032e154d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-green-950\/30{background-color:color-mix(in oklab, var(--color-green-950) 30%, transparent)}}.dark\:bg-orange-950\/30{background-color:#4413064d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-orange-950\/30{background-color:color-mix(in oklab, var(--color-orange-950) 30%, transparent)}}.dark\:bg-red-950\/30{background-color:#4608094d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-red-950\/30{background-color:color-mix(in oklab, var(--color-red-950) 30%, transparent)}}.dark\:bg-sky-950\/30{background-color:#052f4a4d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-sky-950\/30{background-color:color-mix(in oklab, var(--color-sky-950) 30%, transparent)}}.dark\:text-amber-100{color:var(--color-amber-100)}.dark\:text-green-300{color:var(--color-green-300)}.dark\:text-orange-100{color:var(--color-orange-100)}.dark\:text-red-200{color:var(--color-red-200)}.dark\:text-sky-100{color:var(--color-sky-100)}@media (hover:hover){.dark\:hover\:bg-green-950\/30:hover{background-color:#032e154d}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-green-950\/30:hover{background-color:color-mix(in oklab, var(--color-green-950) 30%, transparent)}}.dark\:hover\:bg-red-950\/30:hover{background-color:#4608094d}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-red-950\/30:hover{background-color:color-mix(in oklab, var(--color-red-950) 30%, transparent)}}}}}body{background:var(--color-background);color:var(--color-foreground);margin:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false} diff --git a/public/app/index.html b/public/app/index.html index 09e7623..365a358 100644 --- a/public/app/index.html +++ b/public/app/index.html @@ -5,8 +5,8 @@ client - - + +
{label}{desc}{labelText}{desc}