const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"pulse": 80,
"sysBP": 104,
"diaBP": 68,
"resp": 18,
"spo2": 97,
"triage": "IMMEDIATE",
"evac": "URGENT",
"soundOn": false,
"soundMode": "beep",
"soundVolume": 0.55,
"patientName": "RODRIGUES, J.",
"patientRoster": "7004",
"patientUnit": "USA · A/843",
"patientSex": "M",
"patientWeight": 90,
"patientAllergies": "NKDA",
"patientBlood": "A+",
"cardOpacity": 16,
"cardBlur": 24,
"theme": "dark",
"glowOn": true
}/*EDITMODE-END*/;
const BLOOD_TYPES = ['A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-'];
const INNOVATIONS = [
{ org: 'Milrem Robotics', tag: 'Robotic CASEVAC', url: 'https://milremrobotics.com/product/themis-cargo-casevac/',
focus: 'THeMIS Cargo CASEVAC UGV — battle-proven casualty evacuation & resupply, fielded with Ukrainian forces at the point of injury',
caps: ['CASEVAC', 'Front-line resupply', '50 km/h', '8 h endurance'] },
{ org: 'ARX Robotics', tag: 'Autonomous MedEvac UGV', url: 'https://www.arx-robotics.com/gereon-rcs',
focus: 'GEREON RCS — modular UGV running logistics, MedEvac & recon; 30 delivered to Ukraine in 2025, including a 17-hour casualty evacuation under fire',
caps: ['MedEvac ×3 wounded', 'Logistics', 'Recon', '500 kg payload'] },
{ org: 'Near Earth Autonomy', tag: 'Aerial CASEVAC & Blood', url: 'https://www.nearearth.aero/',
focus: 'Firefly autonomy flying USMC tactical resupply; U.S. Army-selected (with LIFT Aircraft) for uncrewed blood delivery & golden-hour casualty evacuation',
caps: ['Golden-hour CASEVAC', 'Blood delivery', 'TRUAS resupply', 'Safe-landing autonomy'] },
{ org: 'TATRC · MEDRAS', tag: 'Autonomous Casualty Care', url: 'https://www.tatrc.org/www/divisions/medras/',
focus: 'U.S. Army medical robotics: VISTA drone-borne casualty detection & standoff vitals at Project Convergence 2025, BRACE robotic evacuation, and AI that fills the DD-1380 by itself',
caps: ['Casualty detection', 'Standoff vitals', 'Robotic evac', 'Auto-documentation'] },
{ org: 'DARPA Triage Challenge', tag: 'Autonomous Triage', url: 'https://www.darpa.mil/research/challenges/darpa-triage-challenge',
focus: 'Air + ground robot teams autonomously find casualties, read vitals and triage mass-casualty scenes — the $1.5 M finals run November 2026',
caps: ['Autonomous triage', 'Standoff vitals', 'UAS + UGV teams', 'MCI scenarios'] },
{ org: 'Zipline', tag: 'Medical Drone Logistics', url: 'https://www.flyzipline.com/',
focus: 'The longest-running medical drone network — blood & medicines delivered at national scale, now expanding across five countries with U.S. backing',
caps: ['Blood delivery', 'BVLOS at scale', 'Cold chain', 'MCI resupply'] },
{ org: 'Ghost Robotics', tag: 'Quadruped Q-UGV', url: 'https://www.ghostrobotics.io/',
focus: 'Vision 60 quadruped — new 6-DoF manipulator arm (Dec 2025) adds door-opening and equipment handling to a platform fielded with the USMC, Army & USAF',
caps: ['Q-UGV', 'Manipulator arm', 'All-weather', 'Sensor mule'] },
{ org: 'Skydio', tag: 'AI Tactical sUAS', url: 'https://www.skydio.com/',
focus: 'X10D — the U.S. Army\u2019s largest single-vendor sUAS buy (2,500+ aircraft, 2026); EW-hardened visual autonomy, Blue UAS cleared',
caps: ['AI navigation', 'EW resilience', 'Night ISR', 'Blue UAS'] },
{ org: 'Apptronik', tag: 'Humanoid · Logistics', url: 'https://apptronik.com/',
focus: 'Apollo 2 humanoid — modular legged / wheeled worker validated across logistics & manufacturing; the dual-use labor base military sustainment will draw on',
caps: ['Humanoid', 'Legged + wheeled', 'Logistics', 'Dual-use'] },
{ org: 'Agility Robotics', tag: 'Humanoid · Fielded', url: 'https://www.agilityrobotics.com/',
focus: 'Digit — the first humanoid earning revenue in production logistics (100,000+ totes moved); human-spaces mobility of litter-bearer class',
caps: ['Humanoid', 'Production deployments', 'Tote handling', 'Human spaces'] },
{ org: 'UTEP Aerospace Center', tag: 'Next-Gen UAS Frontier', url: 'https://www.utep.edu/aerospace/',
focus: 'Next-Gen UAS Frontier (2026): an 87 mi\u00b2 BVLOS-approved range and 11-aircraft fleet researching defense & disaster-response autonomy',
caps: ['Autonomy', 'Sensing & perception', 'Secure comms', 'BVLOS range', 'Cooperative / swarm'] },
{ org: 'Frontier Robotics', tag: 'Subsea Autonomy', url: 'https://www.frontierrobotics.ai/',
focus: 'Platform-agnostic subsea autonomy & 3-D perception for ROVs — vendor-neutral retrofit autonomy, now Imenco-backed',
caps: ['Stereo vision', 'Edge computing', '3D mapping', 'SLAM', 'Autonomous inspection'] },
{ org: 'Fly4Future', tag: 'Autonomous UAV Research', url: 'https://fly4future.com/',
focus: 'CTU Prague MRS spinoff — fully autonomous research drones; Eagle.One drone interceptor and an A2RL autonomous-racing finalist (2025)',
caps: ['AI training', 'Visual algorithms', 'Multi-robot', 'Swarm applications', 'Counter-UAS'] },
{ org: 'Frontline Robotics', tag: 'EW-Resilient Recon', url: 'https://www.frontline-robotics.tech/en',
focus: 'Ukrainian defence-tech, combat-proven: Zoom 3.0 EW-resilient recon (90\u00d7 zoom, AI visual-inertial nav) and Linza logistics drones',
caps: ['AI navigation', 'GNSS-denied ops', 'ISR missions', 'Logistics drops'] },
{ org: 'Epoch AI', tag: 'Capability Benchmarks', url: 'https://epoch.ai/',
focus: 'Tracking what autonomous robots can actually do — capability benchmarks & data for the 2026 robotics frontier',
caps: ['Industrial tasks', 'Household tasks', 'Capability evaluation'] },
{ org: 'Failory \u00b7 2026', tag: 'Startup Landscape', url: 'https://www.failory.com/',
focus: 'The drone & ground-robot startup landscape to watch in 2026',
caps: ['Open-source drone OS', 'Aerial intelligence', 'Delivery', 'Recon', 'Counter-drone'] },
];
const LITERATURE = [
{ title: 'Autonomous Platforms for Casualty Evacuation', src: 'HDIAC \u00b7 DoD state-of-the-art report', yr: '2024',
note: 'The reference survey of unmanned CASEVAC platforms, air and ground',
url: 'https://hdiac.dtic.mil/wp-content/uploads/2024/08/SOAR_HDIAC_Autonomous-Platforms-for-Casualty-Evacuation_8142024.pdf' },
{ title: 'Medical Implications of Emerging UAS in Military & Combat Environments \u2014 a Narrative Review', src: 'PubMed \u00b7 Military Medicine', yr: '2025',
note: 'What drone-saturated battlefields mean for casualty care doctrine',
url: 'https://pubmed.ncbi.nlm.nih.gov/40354101/' },
{ title: 'DART and MSAI Triumph at the DARPA Triage Challenge', src: 'DARPA', yr: '2025',
note: 'Results from the 2025 systems event \u2014 robots triaging simulated MCIs',
url: 'https://www.darpa.mil/news/2025/dart-msai-triumph-darpa-triage-challenge' },
{ title: 'VISTA Casualty Detection & Assessment at Project Convergence Capstone 5', src: 'U.S. Army \u00b7 TATRC', yr: '2025',
note: 'Drone-borne standoff casualty detection and vitals, field-tested',
url: 'https://www.army.mil/article/283268/tatrcs_vista_casualty_detection_and_assessment_systems_to_be_tested_at_project_convergence_capstone_5' },
{ title: 'Robots Take Center Stage in the DARPA Triage Challenge', src: 'IEEE Spectrum', yr: '2025',
note: 'Inside the courses, sensors and autonomy stacks of the triage robots',
url: 'https://spectrum.ieee.org/darpa-triage-challenge-robots' },
{ title: 'A Multi-Robot Platform for Robotic Triage Combining Onboard Sensing and Foundation Models', src: 'arXiv', yr: '2025',
note: 'Foundation-model triage across cooperating air & ground robots',
url: 'https://arxiv.org/abs/2512.08754' },
{ title: 'Autonomous Care, Robotics & Evacuation portfolio', src: 'MTEC', yr: 'ongoing',
note: 'The DoD medical-technology consortium\u2019s active robotics & evac programs',
url: 'https://mtec-sc.org/capabilities/Autonomous-Care-Robotics-and-Evacuation' },
{ title: 'Ukraine Deploys Ground Robots to Enhance Casualty Evacuation', src: 'Army Recognition', yr: '2025',
note: 'Nearly half of Ukrainian UGV missions are resupply or casualty evacuation',
url: 'https://www.armyrecognition.com/archives/archives-land-defense/defense-news-army-2025/ukraine-deploys-ground-robots-to-enhance-casualty-evacuation-as-the-battlefront-becomes-wider' },
{ title: 'Glimpses of Future Battlefield Medicine \u2014 Robotic Surgeons & Unmanned Vehicles', src: 'JMVH', yr: 'review',
note: 'Where tele-surgery and unmanned platforms take combat medicine next',
url: 'https://jmvh.org/article/glimpses-of-future-battlefield-medicine-the-proliferation-of-robotic-surgeons-and-unmanned-vehicles-and-technologies/' },
{ title: 'Chasing True AI Autonomy: From Legacy Mindsets to Battlefield Dominance', src: 'War on the Rocks', yr: '2025',
note: 'The doctrine argument for genuine autonomy over teleoperation',
url: 'https://warontherocks.com/2025/12/chasing-true-ai-autonomy-from-legacy-mindsets-to-battlefield-dominance/' },
];
const statusPulse = (p) =>
(p >= 60 && p <= 100) ? 'normal'
: ((p >= 50 && p < 60) || (p > 100 && p <= 120)) ? 'warn'
: 'crit';
const statusSys = (s) =>
(s >= 90 && s <= 130) ? 'normal'
: ((s >= 80 && s < 90) || (s > 130 && s <= 159)) ? 'warn'
: 'crit';
const statusDia = (d) =>
(d >= 60 && d <= 85) ? 'normal'
: ((d >= 50 && d < 60) || (d > 85 && d <= 99)) ? 'warn'
: 'crit';
const statusResp = (r) =>
(r >= 12 && r <= 20) ? 'normal'
: ((r >= 8 && r < 12) || (r > 20 && r <= 30)) ? 'warn'
: 'crit';
const statusSpO2 = (s) =>
(s >= 95) ? 'normal'
: (s >= 90) ? 'warn'
: 'crit';
const statusMAP = (m) =>
(m >= 65 && m <= 100) ? 'normal'
: ((m >= 60 && m < 65) || (m > 100 && m <= 110)) ? 'warn'
: 'crit';
const mapLabel = (m) => {
if (m < 60) return 'INADEQUATE';
if (m < 65) return 'MARGINAL';
if (m <= 100) return 'ADEQUATE PERFUSION';
if (m <= 110) return 'ELEVATED';
return 'CRITICAL HIGH';
};
const rank = { normal: 0, warn: 1, crit: 2 };
const worst = (...arr) => arr.reduce((a, b) => rank[a] >= rank[b] ? a : b);
const ECG_POINTS = [
[0.00,  0.00],
[0.04,  0.08],
[0.07,  0.13],   // P peak
[0.10,  0.06],
[0.14,  0.00],
[0.17,  0.00],
[0.185,-0.18],   // Q
[0.200, 1.10],   // R peak (sharp)
[0.215,-0.42],   // S
[0.235,-0.05],
[0.27,  0.00],
[0.37,  0.00],
[0.42,  0.22],   // T peak
[0.50,  0.04],
[0.55,  0.00],
[1.00,  0.00],
];
function ecgAt(t) {
for (let i = 0; i < ECG_POINTS.length - 1; i++) {
const [t0, y0] = ECG_POINTS[i];
const [t1, y1] = ECG_POINTS[i+1];
if (t >= t0 && t <= t1) {
const k = (t - t0) / (t1 - t0);
return y0 + (y1 - y0) * k;
}
}
return 0;
}
const HeartAudio = (() => {
let ctx = null;
let masterGain = null;
let enabled = false;
let volume = 0.55;
let toneHz = 880;
let mode = 'beep';
function ensure() {
if (!ctx) {
const AC = window.AudioContext || window.webkitAudioContext;
if (!AC) return null;
ctx = new AC();
masterGain = ctx.createGain();
masterGain.gain.value = volume;
masterGain.connect(ctx.destination);
}
if (ctx.state === 'suspended') ctx.resume();
return ctx;
}
function thump(when, { f0, f1, dur, vol }) {
if (!ctx || !masterGain || !enabled) return;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
const lp = ctx.createBiquadFilter();
osc.type = 'sine';
osc.frequency.setValueAtTime(f0, when);
osc.frequency.exponentialRampToValueAtTime(f1, when + dur * 0.7);
lp.type = 'lowpass';
lp.frequency.value = 220;
lp.Q.value = 0.6;
gain.gain.setValueAtTime(0, when);
gain.gain.linearRampToValueAtTime(vol, when + 0.008);
gain.gain.exponentialRampToValueAtTime(0.0008, when + dur);
osc.connect(lp).connect(gain).connect(masterGain);
osc.start(when);
osc.stop(when + dur + 0.02);
}
function playBeep() {
const c = ensure(); if (!c || !enabled) return;
const when = c.currentTime;
const dur = 0.055;
const osc = c.createOscillator();
const gain = c.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(toneHz, when);
gain.gain.setValueAtTime(0, when);
gain.gain.linearRampToValueAtTime(0.9, when + 0.004);
gain.gain.setValueAtTime(0.9, when + dur * 0.55);
gain.gain.exponentialRampToValueAtTime(0.0008, when + dur);
osc.connect(gain).connect(masterGain);
osc.start(when);
osc.stop(when + dur + 0.02);
}
function playLubDub() {
const c = ensure(); if (!c || !enabled) return;
const t1 = c.currentTime;
thump(t1, { f0: 95, f1: 28, dur: 0.13, vol: 0.85 });
thump(t1 + 0.14, { f0: 125, f1: 40, dur: 0.09, vol: 0.6 });
}
return {
setEnabled(v) { enabled = !!v; if (v) ensure(); },
setVolume(v) {
volume = Math.max(0, Math.min(1, v));
if (masterGain) masterGain.gain.value = volume;
},
setMode(m) { mode = (m === 'lubdub') ? 'lubdub' : 'beep'; },
setTone(hz) { toneHz = hz; },
isEnabled() { return enabled; },
beat() {
if (!enabled) return;
if (mode === 'lubdub') playLubDub();
else playBeep();
},
};
})();
function ECGTrace({ pulse, color, onBeat }) {
const canvasRef = React.useRef(null);
const stateRef = React.useRef({ x: 0, y: null, last: 0, raf: 0, beatPos: 0 });
const onBeatRef = React.useRef(onBeat);
React.useEffect(() => { onBeatRef.current = onBeat; }, [onBeat]);
const colorRef = React.useRef(color);
React.useEffect(() => { colorRef.current = color; }, [color]);
React.useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
let w = 0, h = 0;
const dpr = window.devicePixelRatio || 1;
const getBg = () =>
getComputedStyle(document.documentElement)
.getPropertyValue('--canvas-bg').trim() || '#07090a';
const resize = () => {
const r = canvas.getBoundingClientRect();
w = Math.max(1, Math.floor(r.width));
h = Math.max(1, Math.floor(r.height));
canvas.width = w * dpr;
canvas.height = h * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.fillStyle = getBg();
ctx.fillRect(0, 0, w, h);
stateRef.current.x = 0;
stateRef.current.y = h * 0.55;
};
resize();
const ro = new ResizeObserver(resize);
ro.observe(canvas);
const themeObs = new MutationObserver(() => {
ctx.fillStyle = getBg();
ctx.fillRect(0, 0, w, h);
stateRef.current.x = 0;
stateRef.current.y = h * 0.55;
});
themeObs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
const baseline = () => h * 0.55;
const amp = () => h * 0.40;
const beatSec = () => 60 / pulse;
const visibleBeats = 3;
const sweepPxPerSec = () => w / (visibleBeats * beatSec());
const THRESH_R = 0.20;
const crossed = (prev, next, thr) => {
if (next >= prev) return prev < thr && next >= thr;
return prev < thr || next >= thr;
};
const frame = (now) => {
const s = stateRef.current;
const dt = s.last ? Math.min(0.05, (now - s.last) / 1000) : 0;
s.last = now;
const dx = Math.max(0.5, sweepPxPerSec() * dt);
let newX = s.x + dx;
while (newX >= w) newX -= w;
const gap = Math.max(18, w * 0.045);
ctx.fillStyle = getBg();
if (newX + gap <= w) {
ctx.fillRect(newX, 0, gap, h);
} else {
ctx.fillRect(newX, 0, w - newX, h);
ctx.fillRect(0, 0, gap - (w - newX), h);
}
const newBeatPos = (s.beatPos + dt / beatSec()) % 1;
if (onBeatRef.current && crossed(s.beatPos, newBeatPos, THRESH_R)) {
onBeatRef.current();
}
s.beatPos = newBeatPos;
const targetY = baseline() - ecgAt(s.beatPos) * amp();
const stroke = colorRef.current || '#4ade80';
ctx.strokeStyle = stroke;
ctx.lineWidth = 1.6;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.shadowColor = stroke;
ctx.shadowBlur = 7;
if (newX > s.x) {
ctx.beginPath();
ctx.moveTo(s.x, s.y);
ctx.lineTo(newX, targetY);
ctx.stroke();
} else {
ctx.beginPath();
ctx.moveTo(s.x, s.y);
ctx.lineTo(w, targetY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, targetY);
ctx.lineTo(newX, targetY);
ctx.stroke();
}
ctx.shadowBlur = 0;
s.x = newX;
s.y = targetY;
s.raf = requestAnimationFrame(frame);
};
stateRef.current.raf = requestAnimationFrame(frame);
return () => {
cancelAnimationFrame(stateRef.current.raf);
ro.disconnect();
themeObs.disconnect();
};
}, [pulse]);
return <canvas ref={canvasRef} />;
}
const SoundOnIcon = () => (
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 6h2.5L9 3v10L5.5 10H3z" fill="currentColor" stroke="none" />
<path d="M11 5.5c.9 1 .9 4 0 5" />
<path d="M13 4c1.8 1.8 1.8 6.2 0 8" />
</svg>
);
const SoundOffIcon = () => (
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 6h2.5L9 3v10L5.5 10H3z" fill="currentColor" stroke="none" />
<path d="M11 6l4 4M15 6l-4 4" />
</svg>
);
function Steppers({ onUp, onDown }) {
return (
<div className="steppers" onClick={(e) => e.stopPropagation()}>
<button type="button" onClick={onUp} aria-label="increase">▲</button>
<button type="button" onClick={onDown} aria-label="decrease">▼</button>
</div>
);
}
function VitalTile({ k, value, suffix, status, min, max, step = 1, className = '', onChange, headerRight }) {
const inc = () => onChange(Math.min(max, value + step));
const dec = () => onChange(Math.max(min, value - step));
return (
<div className={`vital ${className}`} data-status={status}>
<div className="stack">
<div className="k">
<span>{k}</span>
{headerRight}
</div>
<div className="v">{value}{suffix && <sup>{suffix}</sup>}</div>
</div>
<Steppers onUp={inc} onDown={dec} />
</div>
);
}
function NumField({ value, min, max, onChange, width = '2.4ch' }) {
const clamp = (v) => Math.max(min, Math.min(max, v));
return (
<input
className="numfield"
style={{ width }}
value={value}
inputMode="numeric"
onChange={(e) => {
const n = parseInt(e.target.value || '0', 10);
if (!Number.isNaN(n)) onChange(clamp(n));
}}
onKeyDown={(e) => {
if (e.key === 'ArrowUp')   { e.preventDefault(); onChange(clamp(value + 1)); }
if (e.key === 'ArrowDown') { e.preventDefault(); onChange(clamp(value - 1)); }
}}
onWheel={(e) => {
if (document.activeElement !== e.currentTarget) return;
e.preventDefault();
onChange(clamp(value + (e.deltaY < 0 ? 1 : -1)));
}}
/>
);
}
function BPTile({ sys, dia, onSys, onDia }) {
const map = Math.round((sys + 2 * dia) / 3);
const sStatus = statusSys(sys);
const dStatus = statusDia(dia);
const bpStatus = worst(sStatus, dStatus);
const mStatus = statusMAP(map);
return (
<div className="vital bp-tile" data-status={bpStatus} data-map-status={mStatus}>
<div className="bp-grid">
<div className="bp-main">
<div className="k">BP · sys / dia · mmHg</div>
<div className="bp-fields">
<div className="bp-cell">
<NumField value={sys} min={60} max={220} onChange={onSys} />
<span className="bp-step" onClick={(e) => e.stopPropagation()}>
<button type="button" onClick={() => onSys(Math.min(220, sys + 1))} aria-label="sys up">▴</button>
<button type="button" onClick={() => onSys(Math.max(60, sys - 1))} aria-label="sys down">▾</button>
</span>
</div>
<span className="sep">/</span>
<div className="bp-cell">
<NumField value={dia} min={30} max={140} onChange={onDia} />
<span className="bp-step" onClick={(e) => e.stopPropagation()}>
<button type="button" onClick={() => onDia(Math.min(140, dia + 1))} aria-label="dia up">▴</button>
<button type="button" onClick={() => onDia(Math.max(30, dia - 1))} aria-label="dia down">▾</button>
</span>
</div>
</div>
</div>
<div className="bp-divider" />
<div className="bp-map">
<div className="k">MAP · derived</div>
<div className="v">{map}<sup>mmHg</sup></div>
<div className="status">{mapLabel(map)}</div>
</div>
</div>
</div>
);
}
const TRIAGE_OPTS = [
{ v: 'IMMEDIATE', label: 'IMM', cls: 'triage-imm' },
{ v: 'DELAYED',   label: 'DEL', cls: 'triage-del' },
{ v: 'MINIMAL',   label: 'MIN', cls: 'triage-min' },
{ v: 'EXPECTANT', label: 'EXP', cls: 'triage-exp' },
];
const EVAC_OPTS = [
{ v: 'URGENT',   cls: 'evac-urgent' },
{ v: 'PRIORITY', cls: 'evac-priority' },
{ v: 'ROUTINE',  cls: 'evac-routine' },
];
function SubjectCard({
name, roster, unit, sex, weight, allergies, blood,
evac, triage,
onEvac, onTriage, onBlood,
}) {
return (
<div className="subject-card">
<div className="sc-row sc-head">
<div className="sc-headinfo">
<div className="sc-name">{name}</div>
<div className="sc-meta">#{roster} · {unit}</div>
</div>
<button
type="button"
className="sc-blood"
onClick={onBlood}
title="Cycle blood type"
>{blood}</button>
</div>
<div className="sc-row sc-stats">
<span><b>{sex}</b></span>
<span className="sep">·</span>
<span><b>{weight}</b> kg</span>
<span className="sep">·</span>
<span title="Allergies">{allergies}</span>
</div>
<div className="sc-row sc-chiprow">
<div className="sc-k">Evac</div>
<div className="sc-chips">
{EVAC_OPTS.map(({ v, cls }) => (
<button
key={v} type="button"
className={`sc-chip ${cls} ${evac === v ? 'active' : ''}`}
onClick={() => onEvac(v)}
title={v}
>{v.slice(0, 3)}</button>
))}
</div>
</div>
<div className="sc-row sc-chiprow">
<div className="sc-k">Triage</div>
<div className="sc-chips">
{TRIAGE_OPTS.map(({ v, label, cls }) => (
<button
key={v} type="button"
className={`sc-chip ${cls} ${triage === v ? 'active' : ''}`}
onClick={() => onTriage(v)}
title={v}
>{label}</button>
))}
</div>
</div>
</div>
);
}
function useClock(startMins = 21 * 60 + 3) {
const [mins, setMins] = React.useState(startMins);
React.useEffect(() => {
const id = setInterval(() => setMins((m) => (m + 1) % (24 * 60)), 6000);
return () => clearInterval(id);
}, []);
const h = String(Math.floor(mins / 60)).padStart(2, '0');
const m = String(mins % 60).padStart(2, '0');
return `${h}:${m}`;
}
function App() {
const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
const zt = useClock();
React.useEffect(() => {
const beatMs = Math.round(60000 / t.pulse);
document.documentElement.style.setProperty('--beat-ms', beatMs + 'ms');
}, [t.pulse]);
React.useEffect(() => {
document.documentElement.style.setProperty('--card-opacity', (t.cardOpacity / 100).toFixed(3));
document.documentElement.style.setProperty('--card-blur', t.cardBlur + 'px');
}, [t.cardOpacity, t.cardBlur]);
React.useEffect(() => {
document.documentElement.setAttribute('data-theme', t.theme || 'dark');
}, [t.theme]);
const glowRef = React.useRef(null);
React.useEffect(() => {
if (!t.glowOn) return;
let raf = null, tx = window.innerWidth / 2, ty = window.innerHeight / 2;
const handler = (e) => {
tx = e.clientX; ty = e.clientY;
if (!raf) {
raf = requestAnimationFrame(() => {
document.documentElement.style.setProperty('--glow-x', tx + 'px');
document.documentElement.style.setProperty('--glow-y', ty + 'px');
raf = null;
});
}
};
window.addEventListener('mousemove', handler);
window.addEventListener('pointermove', handler);
return () => {
window.removeEventListener('mousemove', handler);
window.removeEventListener('pointermove', handler);
if (raf) cancelAnimationFrame(raf);
};
}, [t.glowOn]);
React.useEffect(() => {
HeartAudio.setEnabled(!!t.soundOn);
}, [t.soundOn]);
React.useEffect(() => {
HeartAudio.setVolume(t.soundVolume ?? 0.55);
}, [t.soundVolume]);
React.useEffect(() => {
HeartAudio.setMode(t.soundMode || 'beep');
}, [t.soundMode]);
React.useEffect(() => {
const viewer = document.querySelector('spline-viewer');
const loader = document.getElementById('loader');
if (!viewer || !loader) return;
const hide = () => {
loader.classList.add('hidden');
setTimeout(() => loader && loader.remove(), 700);
};
viewer.addEventListener('load', hide);
const fallback = setTimeout(hide, 8000);
const stripLogo = () => {
const root = viewer.shadowRoot;
if (!root) return;
const logo = root.getElementById('logo') || root.querySelector('#logo, a[href*="spline"], [part="logo"]');
if (logo) logo.remove();
if (!root.querySelector('#__hide_logo')) {
const s = document.createElement('style');
s.id = '__hide_logo';
s.textContent = `#logo, a[href*="spline"], [part="logo"] { display:none !important; visibility:hidden !important; opacity:0 !important; pointer-events:none !important; }`;
root.appendChild(s);
}
};
let n = 0;
const tick = setInterval(() => { stripLogo(); if (++n > 40) clearInterval(tick); }, 150);
viewer.addEventListener('load', () => {
stripLogo();
if (viewer.shadowRoot) {
new MutationObserver(stripLogo).observe(viewer.shadowRoot, { childList: true, subtree: true });
}
});
return () => { clearTimeout(fallback); clearInterval(tick); };
}, []);
const pStat = statusPulse(t.pulse);
const rStat = statusResp(t.resp);
const sStat = statusSpO2(t.spo2);
const map = Math.round((t.sysBP + 2 * t.diaBP) / 3);
const bpOverall = worst(statusSys(t.sysBP), statusDia(t.diaBP), statusMAP(map));
const overall = worst(pStat, rStat, sStat, bpOverall);
const statusColor = { normal: '#4ade80', warn: '#facc15', crit: '#ef4444' };
const ecgColor = statusColor[pStat];
const overallColor = statusColor[overall];
React.useEffect(() => {
document.documentElement.style.setProperty('--glow-color', overallColor);
}, [overallColor]);
React.useEffect(() => {
if (!window.PFC_BG) return;
window.PFC_BG.setGlass(true); // brighten the field so it reads behind the humanoid
const level = overall === 'crit' ? 'critical' : overall === 'warn' ? 'urgent' : 'stable';
window.PFC_BG.setCondition(level, { hr: t.pulse });
}, [overall, t.pulse]);
const toggleSound = () => {
const next = !t.soundOn;
if (next) {
HeartAudio.setEnabled(true);
HeartAudio.beat();
} else {
HeartAudio.setEnabled(false);
}
setTweak('soundOn', next);
};
const handleBeat = React.useCallback(() => {
HeartAudio.beat();
}, []);
const evacClass = t.evac.toLowerCase();
return (
<>
{t.glowOn && <div className="cursor-glow" aria-hidden="true" />}
<div className="frame">
{/* TOP BAR */}
<div className="topbar">
<div className="left">
<span className="beat-dot" aria-hidden="true" style={{ background: ecgColor, boxShadow: `0 0 8px ${ecgColor}` }}></span>
<span className="crumb"><b>HUMANOID</b> · UNIT 01</span>
<span className="sep"></span>
<span className="crumb optional">CASE <b>7004</b> · ROSTER HOO332</span>
<span className="sep optional"></span>
<span className="crumb">ZT <b>{zt}</b></span>
<button
type="button"
className="tweaks-btn"
onClick={() => window.postMessage({ type: '__activate_edit_mode' }, '*')}
title="Open Vitals Tweaks"
>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round">
<circle cx="5" cy="4" r="1.5" />
<line x1="5" y1="1" x2="5" y2="2.5" />
<line x1="5" y1="5.5" x2="5" y2="15" />
<circle cx="11" cy="8" r="1.5" />
<line x1="11" y1="1" x2="11" y2="6.5" />
<line x1="11" y1="9.5" x2="11" y2="15" />
</svg>
Vitals
</button>
</div>
<div className="right">
<span className="crumb" style={{ color: overall === 'crit' ? '#ef4444' : overall === 'warn' ? '#facc15' : 'var(--mint)' }}>
{overall === 'crit' ? 'CRITICAL' : overall === 'warn' ? 'GUARDED' : 'STABLE'}
</span>
<span className="sep"></span>
<span className={`badge ${evacClass}`}><span className="pip"></span> EVAC · {t.evac}</span>
</div>
</div>
{/* STAGE */}
<div className="stage">
<spline-viewer url={(typeof window !== 'undefined' && window.__resources && window.__resources.splineScene) || 'scene.splinecode'} loading-anim-type="none"></spline-viewer>
<div className="ticks" aria-hidden="true">
<span></span><span></span><span></span><span></span>
</div>
<div className="stage-corner-r">
<span className="live"><span className="pip" style={{ background: ecgColor, boxShadow: `0 0 8px ${ecgColor}` }}></span> TELEMETRY · STREAMING</span>
<span>LAT 34.8421° · LON 38.9956°</span>
<span>UPLINK · MESH · 142 ms</span>
</div>
<SubjectCard
name={t.patientName}
roster={t.patientRoster}
unit={t.patientUnit}
sex={t.patientSex}
weight={t.patientWeight}
allergies={t.patientAllergies}
blood={t.patientBlood}
evac={t.evac}
triage={t.triage}
onEvac={(v) => setTweak('evac', v)}
onTriage={(v) => setTweak('triage', v)}
onBlood={() => {
const i = BLOOD_TYPES.indexOf(t.patientBlood);
setTweak('patientBlood', BLOOD_TYPES[(i + 1) % BLOOD_TYPES.length]);
}}
/>
</div>
{/* PANEL */}
<div className="panel">
<div className="eyebrow"><span className="line"></span> Field Brief · 0001</div>
<h1>The battlefield<br/>has changed.</h1>
<div className="body">
<p>Drone-saturated skies deny MEDEVAC. The <em>golden hour</em> stretches into days. And the most highly trained medics on earth are left carrying casualties out on their backs.</p>
<p className="pull">It doesn't have to be this way.</p>
<p>Robotic teammates — humanoid litter-bearers, autonomous resupply mules, tethered ISR streaming vitals to a surgeon a continent away — close the gap between point of injury and definitive care.</p>
<p>They don't replace operators. They give them the <em>time</em>, the <em>supplies</em>, and the <em>reach</em> to keep a teammate breathing until exfil arrives.</p>
</div>
<div className="divider">
<span className="rule"></span><span>Closing Statement</span><span className="rule"></span>
</div>
<div className="close">
<div>
<div className="label">Status</div>
<div className="value mint">The technology exists.</div>
</div>
<div>
<div className="label">Timing</div>
<div className="value">The need is now.</div>
</div>
</div>
{/* INNOVATION FRONT — real programs that prove the brief */}
<div className="divider">
<span className="rule"></span><span>The Innovation Front</span><span className="rule"></span>
</div>
<p className="innov-lead">
These aren't concepts. Across autonomy, swarm, subsea and contested-EW
domains, the building blocks of a robotic medical-reach chain are
already fielded today.
</p>
<div className="innov-grid">
{INNOVATIONS.map((it, i) => (
<a className="innov-card" key={i} href={it.url} target="_blank" rel="noopener noreferrer"
title={'Visit ' + it.org}>
<div className="ic-head">
<span className="ic-org">{it.org} <span className="ic-ext" aria-hidden="true">↗</span></span>
<span className="ic-tag">{it.tag}</span>
</div>
<div className="ic-focus">{it.focus}</div>
<div className="ic-caps">
{it.caps.map((c, j) => <span className="ic-cap" key={j}>{c}</span>)}
</div>
</a>
))}
</div>
<div className="innov-foot">
16 programs · casualty evac · autonomous triage · aerial blood logistics · humanoids · swarm · subsea · EW-resilient ISR
</div>
{/* THE READING ROOM — current literature on autonomous medical robotics */}
<div className="divider">
<span className="rule"></span><span>The Reading Room · Current Literature</span><span className="rule"></span>
</div>
<p className="innov-lead">
The primary sources behind the brief — recent reports, trials and reviews on
autonomous triage, robotic casualty evacuation and drone medical logistics.
</p>
<div className="lit-list">
{LITERATURE.map((it, i) => (
<a className="lit-row" key={i} href={it.url} target="_blank" rel="noopener noreferrer">
<div className="lit-main">
<span className="lit-title">{it.title}</span>
<span className="lit-note">{it.note}</span>
</div>
<span className="lit-src">{it.src} · {it.yr} <span className="ic-ext" aria-hidden="true">↗</span></span>
</a>
))}
</div>
<footer className="spm-copyright" role="contentinfo">
<span><b>© 2026 Special Purpose LLC. All rights reserved.</b></span>
<span className="spm-disc">The 2025–2026 Special-Purpose-Medics suite is a trademarked training architecture of Special Purpose LLC.</span><span className="spm-disc">Original content, code &amp; design are proprietary; referenced clinical guidelines &amp; U.S. government works remain the property of their respective owners.</span>
<span className="spm-disc">For training &amp; educational use only — not medical advice.</span>
<span className="spm-disc">Contact · <a href="mailto:specialpurposemedic@gmail.com" style={{color:'inherit',textDecoration:'underline'}}>specialpurposemedic@gmail.com</a></span>
</footer>
</div>
{/* BOTTOM VITALS */}
<div className="bottom">
<VitalTile
k="Pulse"
value={t.pulse}
suffix="BPM"
status={pStat}
min={30} max={220}
className="pulse-tile"
onChange={(v) => setTweak('pulse', v)}
headerRight={
<button
type="button"
className={`audio-toggle ${t.soundOn ? 'on' : ''}`}
onClick={toggleSound}
aria-label={t.soundOn ? 'Mute heart sound' : 'Unmute heart sound'}
title={t.soundOn ? 'Mute heart sound' : 'Heart sound · click to enable'}
>
{t.soundOn ? <SoundOnIcon /> : <SoundOffIcon />}
</button>
}
/>
<BPTile
sys={t.sysBP} dia={t.diaBP}
onSys={(v) => setTweak('sysBP', v)}
onDia={(v) => setTweak('diaBP', v)}
/>
<VitalTile k="Resp" value={t.resp} suffix="/MIN"
status={rStat} min={4} max={60}
onChange={(v) => setTweak('resp', v)} />
<VitalTile k="SpO₂" value={t.spo2} suffix="%"
status={sStat} min={50} max={100}
onChange={(v) => setTweak('spo2', v)} />
<div className="ecg" data-status={pStat}>
<div className="k">ECG · Lead II</div>
<ECGTrace pulse={t.pulse} color={ecgColor} onBeat={handleBeat} />
<div className="bpm-tag">{t.pulse} bpm</div>
</div>
</div>
</div>
{/* TWEAKS PANEL */}
<TweaksPanel title="Vitals · Tweaks">
<TweakSection label="Live Vitals" />
<TweakSlider label="Pulse" value={t.pulse} min={30} max={220} unit=" bpm"
onChange={(v) => setTweak('pulse', v)} />
<TweakSlider label="BP · Sys" value={t.sysBP} min={60} max={220} unit=" mmHg"
onChange={(v) => setTweak('sysBP', v)} />
<TweakSlider label="BP · Dia" value={t.diaBP} min={30} max={140} unit=" mmHg"
onChange={(v) => setTweak('diaBP', v)} />
<TweakSlider label="Resp" value={t.resp} min={4} max={60} unit=" /min"
onChange={(v) => setTweak('resp', v)} />
<TweakSlider label="SpO₂" value={t.spo2} min={50} max={100} unit=" %"
onChange={(v) => setTweak('spo2', v)} />
<TweakSection label="Triage" />
<TweakSelect label="Triage cat." value={t.triage}
options={['IMMEDIATE', 'DELAYED', 'MINIMAL', 'EXPECTANT']}
onChange={(v) => setTweak('triage', v)} />
<TweakRadio label="Evac priority" value={t.evac}
options={['URGENT', 'PRIORITY', 'ROUTINE']}
onChange={(v) => setTweak('evac', v)} />
<TweakSection label="Display" />
<TweakRadio label="Theme" value={t.theme || 'dark'}
options={['dark', 'light']}
onChange={(v) => setTweak('theme', v)} />
<TweakToggle label="Gaze glow" value={!!t.glowOn}
onChange={(v) => setTweak('glowOn', v)} />
<TweakSection label="Card" />
<TweakSlider label="Opacity" value={t.cardOpacity}
min={0} max={100} unit=" %"
onChange={(v) => setTweak('cardOpacity', v)} />
<TweakSlider label="Blur" value={t.cardBlur}
min={0} max={50} unit=" px"
onChange={(v) => setTweak('cardBlur', v)} />
<TweakSection label="Patient" />
<TweakText label="Name" value={t.patientName}
onChange={(v) => setTweak('patientName', v)} />
<TweakText label="Roster #" value={t.patientRoster}
onChange={(v) => setTweak('patientRoster', v)} />
<TweakText label="Svc · Unit" value={t.patientUnit}
onChange={(v) => setTweak('patientUnit', v)} />
<TweakRadio label="Sex" value={t.patientSex}
options={['M', 'F']}
onChange={(v) => setTweak('patientSex', v)} />
<TweakSlider label="Weight" value={t.patientWeight}
min={20} max={200} unit=" kg"
onChange={(v) => setTweak('patientWeight', v)} />
<TweakSelect label="Blood" value={t.patientBlood}
options={BLOOD_TYPES}
onChange={(v) => setTweak('patientBlood', v)} />
<TweakText label="Allergies" value={t.patientAllergies}
onChange={(v) => setTweak('patientAllergies', v)} />
<TweakSection label="Heart sound" />
<TweakToggle label="Audio" value={t.soundOn}
onChange={(v) => { setTweak('soundOn', v); HeartAudio.setEnabled(v); if (v) HeartAudio.beat(); }} />
<TweakRadio label="Sound" value={t.soundMode || 'beep'}
options={['beep', 'lubdub']}
onChange={(v) => { setTweak('soundMode', v); HeartAudio.setMode(v); if (t.soundOn) HeartAudio.beat(); }} />
<TweakSlider label="Volume" value={Math.round((t.soundVolume ?? 0.55) * 100)}
min={0} max={100} unit=" %"
onChange={(v) => setTweak('soundVolume', v / 100)} />
<TweakSection label="Presets" />
<div style={{ display: 'flex', gap: 6 }}>
<TweakButton label="Normal" onClick={() => setTweak({ pulse: 78, sysBP: 118, diaBP: 76, resp: 14, spo2: 98, triage: 'DELAYED', evac: 'PRIORITY' })} />
<TweakButton label="Brady" onClick={() => setTweak({ pulse: 46, sysBP: 102, diaBP: 64, resp: 11, spo2: 96, triage: 'DELAYED', evac: 'PRIORITY' })} secondary />
<TweakButton label="Tachy" onClick={() => setTweak({ pulse: 132, sysBP: 96, diaBP: 60, resp: 24, spo2: 93, triage: 'IMMEDIATE', evac: 'URGENT' })} secondary />
<TweakButton label="Shock" onClick={() => setTweak({ pulse: 152, sysBP: 78, diaBP: 50, resp: 30, spo2: 86, triage: 'IMMEDIATE', evac: 'URGENT' })} secondary />
</div>
</TweaksPanel>
</>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);