function DecisionLog({ log }) {
const ref = React.useRef(null);
React.useEffect(() => {
if (ref.current) ref.current.scrollTop = ref.current.scrollHeight;
}, [log.length]);
return (
<div className="panel decision-log">
<div className="panel-header">
<span className="marker">▮</span>
<span>Decision Log</span>
<span className="right">{log.length} ENTR{log.length === 1 ? "Y" : "IES"}</span>
</div>
<div className="panel-body" ref={ref}>
<div className="log-list">
{log.map((entry, i) =>
<div key={i} className="log-line">
<span className="log-time">T+{window.formatClock(entry.t)}</span>
<span className={`log-tag ${entry.tag}`}>{entry.tag}</span>
<span className={`log-msg ${entry.kind || ""}`}>{entry.msg}</span>
</div>
)}
</div>
</div>
</div>);
}
function OutcomePanel({ survival, criticalDone, criticalMissed, contraTaken, elapsedSec, paused, finished, onStart, onPause, onReset, onResetMission, onEvac, onExport }) {
const totalCrit = criticalDone.length + criticalMissed.length;
let cls = "";
if (survival >= 70) cls = "";else
if (survival >= 35) cls = "warn";else
cls = "danger";
return (
<div className="outcome-panel">
<div className="outcome-head">Survival Probability · Live</div>
<div className="survival-display">
<div className={`survival-num ${cls}`}>{survival}<span style={{ fontSize: 22, color: "var(--ink-faint)" }}>%</span></div>
<div className="survival-label">
{survival >= 70 ? "Likely Survives" : survival >= 35 ? "Guarded" : "Likely Dies"}
</div>
</div>
<div className="survival-bar">
<div className="survival-fill" style={{ width: `${survival}%` }} />
</div>
<div className="outcome-stats">
<div className="stat-tile">
<div className="lbl">Critical Actions</div>
<div className="val">{criticalDone.length}/{totalCrit}</div>
</div>
<div className="stat-tile">
<div className="lbl">Contraindications</div>
<div className="val" style={{ color: contraTaken.size > 0 ? "var(--danger)" : "var(--ink-bright)" }}>{contraTaken.size}</div>
</div>
<div className="stat-tile">
<div className="lbl">Elapsed</div>
<div className="val">{window.formatClock(elapsedSec)}</div>
</div>
<div className="stat-tile">
<div className="lbl">Status</div>
<div className="val" style={{ fontSize: 12, paddingTop: 4 }}>{finished ? "ENDED" : paused ? "PAUSED" : "RUNNING"}</div>
</div>
</div>
<div style={{ display: "flex", gap: 6, marginTop: 12 }}>
{!finished && (paused ?
<button className="debrief-btn primary" style={{ flex: 1 }} onClick={onStart}>
{elapsedSec === 0 ? "▶ START" : "▶ RESUME"}
</button> :
<button className="debrief-btn" style={{ flex: 1 }} onClick={onPause}>❚❚ PAUSE</button>)
}
<button className="debrief-btn" onClick={onReset} title="Reset just this patient">↺</button>
<button className="debrief-btn danger" onClick={() => {
if (window.confirm("Reset ALL patients and mission clock?\n\nThis wipes every patient's progress, the mission timer, and starts the scenario set from scratch.")) {
onResetMission && onResetMission();
}
}} title="Mission reset — wipe ALL patients and clock">↺↺</button>
{!finished && <button className="debrief-btn" onClick={onEvac} title="End scenario / EVAC">EVAC</button>}<button className="debrief-btn" onClick={onExport} title="Export a PDF patient report (treated or untreated)">⎙ PDF</button>
</div>
</div>);
}
function Debrief({ scenario, criticalDone, criticalMissed, contraTaken, log, survival, outcome, onReplay, onNext, onReturn, onExport }) {
const verdictCls = outcome === "alive" ? "alive" : outcome === "guarded" ? "guarded" : outcome === "dead" ? "dead" : survival >= 70 ? "alive" : survival >= 35 ? "guarded" : "dead";
const verdictText = verdictCls === "alive" ? "Casualty stabilized for evac" :
verdictCls === "guarded" ? "Casualty unstable — high risk in transport" :
"Casualty did not survive";
return (
<div className="debrief-backdrop">
<div className="debrief">
<div className="debrief-header">
<span className="title">Debrief · {scenario.name} / {scenario.last4}</span>
<span className={`verdict ${verdictCls}`}>{verdictText}</span>
</div>
<div className="debrief-body">
<div className="debrief-section">
<div className="sec-head">Survival Outcome</div>
<div style={{ display: "flex", alignItems: "baseline", gap: 14 }}>
<span style={{ fontSize: 48, color:
verdictCls === "alive" ? "var(--live)" :
verdictCls === "guarded" ? "var(--warn)" : "var(--danger)",
fontWeight: 600
}}>{survival}%</span>
<span style={{ color: "var(--ink-mute)" }}>
vs textbook ceiling <b style={{ color: "var(--ink)" }}>{scenario.expectedSurvival}%</b> &nbsp;·&nbsp;
untreated baseline <b style={{ color: "var(--ink)" }}>{scenario.baseSurvival}%</b>
</span>
</div>
</div>
<div className="debrief-section">
<div className="sec-head">Critical Actions · {criticalDone.length} of {criticalDone.length + criticalMissed.length}</div>
<div className="crit-list">
{scenario.criticalActions.map((a) => {
const wasDone = criticalDone.includes(a);
return (
<div key={a.id} className={`crit-row ${wasDone ? "done" : "missed"}`}>
<span className="check">{wasDone ? "✓" : "✗"}</span>
<span className="desc">[{a.category}] {a.label}</span>
<span className="time-est">Ideal ≤ T+{String(a.byMin).padStart(2, "0")}:00</span>
</div>);
})}
</div>
</div>
{contraTaken.size > 0 &&
<div className="debrief-section">
<div className="sec-head" style={{ color: "var(--danger)" }}>Contraindicated Actions Taken</div>
<div className="crit-list">
{[...contraTaken].map((id) => {
const contra = scenario.contraindications.find((c) => c.actionId === id);
return (
<div key={id} className="crit-row missed">
<span className="check">⚠</span>
<span className="desc">{contra?.message || id}</span>
<span className="time-est"></span>
</div>);
})}
</div>
</div>
}
{verdictCls === "dead" && criticalDone.length === 0 &&
<div className="debrief-section">
<div className="sec-head" style={{ color: "var(--danger)" }}>Why This Casualty Died — No Interventions Performed</div>
<div style={{ color: "var(--ink-mute)", fontSize: 11, lineHeight: 1.6, marginBottom: 10 }}>
No treatment was rendered. {scenario.name} died of {scenario.diagnosis} — left untreated, the injury followed its
natural course to cardiovascular collapse. The vitals trajectory was never interrupted: every minute without the
first intervention compounded the next, and the window for each life-saving measure closed in sequence.
</div>
<div className="sec-head">The Life-Saving Sequence — what would have saved {scenario.name}</div>
<div className="crit-list">
{[...scenario.criticalActions].sort((a, b) => a.byMin - b.byMin || b.weight - a.weight).map((a) =>
<div key={a.id} className="crit-row missed">
<span className="check">→</span>
<span className="desc">[{a.category}] {a.label} <b style={{ color: "var(--ink)" }}>· {a.weight} pts</b></span>
<span className="time-est">by T+{String(a.byMin).padStart(2, "0")}:00</span>
</div>
)}
</div>
</div>
}
<div className="debrief-section">
<div className="sec-head">Clinical Reasoning</div>
<div style={{ color: "var(--ink-mute)", fontSize: 11, lineHeight: 1.6 }}>
{generateReasoning(scenario, criticalDone, criticalMissed, contraTaken)}
</div>
</div>
</div>
<div className="debrief-actions">
{verdictCls === "dead" && onReturn && <button className="debrief-btn danger" onClick={onReturn}>↩ MAIN DASHBOARD</button>}<button className="debrief-btn" onClick={onReplay}>↺ REPLAY</button>
<button className="debrief-btn" onClick={onExport} title="Save this debrief as a PDF report">⎙ PDF REPORT</button><button className="debrief-btn primary" onClick={onNext}>NEXT PATIENT →</button>
</div>
</div>
</div>);
}
function generateReasoning(scenario, done, missed, contraTaken) {
const parts = [];
const topMissed = missed.sort((a, b) => b.weight - a.weight)[0];
if (topMissed) {
parts.push(`The highest-impact omission was: ${topMissed.label}. This action carries ${topMissed.weight} survival-weight points in this scenario.`);
}
if (contraTaken.size > 0) {
parts.push(`You triggered ${contraTaken.size} contraindication${contraTaken.size > 1 ? "s" : ""} — each costs survival probability and signals a clinical-decision gap. Review the warnings above.`);
}
if (done.length === scenario.criticalActions.length && contraTaken.size === 0) {
parts.push(`All critical actions completed without contraindication. Textbook execution.`);
} else if (done.length / scenario.criticalActions.length >= 0.7) {
parts.push(`MARCH sequence largely intact. Closing the gap on remaining items would push this casualty toward a survivable outcome.`);
} else {
parts.push(`MARCH discipline was incomplete. Walk through M→A→R→C→H on your next attempt — the priorities are sequenced for a reason.`);
}
return parts.join(" ");
}
function App() {
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"difficulty": "strict",
"hintMode": false,
"showAnswers": false,
"timeSpeed": 1,
"pulseSound": "beep",
"soundOn": true,
"theme": "dark",
"briefRate": 1,
"glassPanels": true,
"glassOpacity": 54,
"bgCondition": true
} /*EDITMODE-END*/;
const [t, setTweak] = window.useTweaks(TWEAK_DEFAULTS);
const [scenarioIdx, setScenarioIdx] = useState(0);
const [rosterOpen, setRosterOpen] = useState(false);
const [visited, setVisited] = useState([0]);
React.useEffect(() => {
setVisited((v) => v.includes(scenarioIdx) ? v : [...v, scenarioIdx]);
}, [scenarioIdx]);
const toggleRoster = () => {
setRosterOpen((o) => {
const opening = !o;
if (window.Sound) { window.Sound.init(); window.Sound.play(opening ? "confirm" : "pageClick"); }
return opening;
});
};
const [focusMode, setFocusMode] = useState(false);
const [openTabs, setOpenTabs] = useState({ vitals: true, brief: false, tx: false, log: false, outcome: true });
const [kia, setKia] = useState(() => new Set());
const [dismissedIds, setDismissedIds] = useState(() => new Set());
const toggleTab = (k) => {
if (focusMode) {
setFocusMode(false);
setOpenTabs((o) => ({ ...o, [k]: true, ...(k === "brief" ? { vitals: false } : {}) }));
if (window.Sound) { window.Sound.init(); window.Sound.play("confirm"); }
return;
}
setOpenTabs((o) => {
const v = !o[k];
const n = { ...o, [k]: v };
if (k === "brief") n.vitals = !v;   // brief pops up → vitals collapse · and back
if (window.Sound) { window.Sound.init(); window.Sound.play(v ? "confirm" : "pageClick"); }
return n;
});
};
const onBriefDone = React.useCallback(() => {
setOpenTabs((o) => o.brief ? { ...o, brief: false, vitals: true } : o);
}, []);
const toggleFocus = () => {
setFocusMode((f) => {
const on = !f;
if (window.Sound) { window.Sound.init(); window.Sound.play(on ? "confirm" : "pageClick"); }
return on;
});
};
const eff = focusMode
? { vitals: false, brief: false, tx: true, log: false, outcome: false }
: openTabs;
const rosterShown = focusMode ? false : rosterOpen;
React.useEffect(() => {
document.documentElement.dataset.theme = t.theme === "light" ? "light" : "dark";
}, [t.theme]);
React.useEffect(() => {
document.body.classList.toggle("glass", !!t.glassPanels);
if (window.PFC_BG) window.PFC_BG.setGlass(!!t.glassPanels);
}, [t.glassPanels]);
React.useEffect(() => {
document.body.style.setProperty("--glass-alpha", String((t.glassOpacity ?? 62) / 100));
}, [t.glassOpacity]);
const engine = window.useScenarioEngine({
scenarioIdx, setScenarioIdx,
timeSpeed: t.timeSpeed,
difficulty: t.difficulty,
showAnswers: t.showAnswers
});
const { state, dispatch } = engine;
const { scenario, vitals, taken, contraTaken, log, elapsedSec, paused, finished,
survival, criticalDone, criticalMissed, toast, outcome, nextHint,
tqStates, evacPriority, missionElapsedSec, missionRunning } = state;
const { performAction, start, pause, reset, resetMission, evac, next, prev, pick, setToast,
onTqApply, onTqConvert, setAVPU, setEvacPriority } = dispatch;
React.useEffect(() => {
if (!window.PFC_BG) return;
const dead = finished && outcome === "dead";
const level = !t.bgCondition ? "stable"
: state.arrested || dead || survival < 35 ? "critical"
: survival < 70 ? "urgent"
: "stable";
window.PFC_BG.setCondition(level, { hr: state.arrested ? 0 : (vitals.hr || 0) });
}, [survival, state.arrested, finished, outcome, vitals.hr, t.bgCondition]);
React.useEffect(() => {
if (!window.Sound) return;
window.Sound.setPulseMode(t.soundOn ? t.pulseSound : "off");
}, [t.pulseSound, t.soundOn]);
React.useEffect(() => {
if (!window.Sound) return;
if (!missionRunning || finished || !t.soundOn) {
window.Sound.stopMonitor();
return;
}
window.Sound.init();
window.Sound.startMonitor(() => ({ hr: vitals.hr, spo2: vitals.spo2, sys: vitals.sys }));
return () => window.Sound.stopMonitor();
}, [missionRunning, finished, t.soundOn, vitals.hr, vitals.spo2, vitals.sys]);
React.useEffect(() => {
if (!window.Sound) return;
if (state.arrested && t.soundOn) {
window.Sound.init();
window.Sound.startAsystole();
} else {
window.Sound.stopAsystole();
}
return () => { if (window.Sound) window.Sound.stopAsystole(); };
}, [state.arrested, t.soundOn]);
React.useEffect(() => {
const onKey = (e) => {
if (e.target.tagName === "INPUT") return;
if (e.key === "p" || e.key === "P") {paused ? start() : pause();}
if (e.key === "r" || e.key === "R") {reset();}
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [paused, start, pause, reset]);
React.useEffect(() => {
if (!finished) setDismissedIds((s) => { if (!s.has(scenario.id)) return s; const n = new Set(s); n.delete(scenario.id); return n; });
if (finished && outcome && outcome !== "dead") setKia((s) => { if (!s.has(scenario.id)) return s; const n = new Set(s); n.delete(scenario.id); return n; });
}, [finished, outcome, scenario.id]);
const exportReport = React.useCallback(() => {
document.body.classList.add("report-print");
if (window.Sound) window.Sound.play("pageClick");
const cleanup = () => document.body.classList.remove("report-print");
window.addEventListener("afterprint", cleanup, { once: true });
setTimeout(() => { window.print(); setTimeout(cleanup, 1200); }, 120);
}, []);
const recordTime = "T+" + window.formatClock(missionElapsedSec);
const patientTime = "P+" + window.formatClock(elapsedSec);
return (
<div className="app">
{/* TOP BAR */}
<div className="topbar" data-comment-anchor="b228c54f0a-div-227-7">
<span className="brand">
<span className="dot"></span>
<span>TCCC · PATIENT TREATMENT DASHBOARD</span>
</span>
<span className="sep"></span>
<span>Patient {scenarioIdx + 1} / {window.SCENARIOS.length}</span>
<span className="sep"></span>
<span style={{ color: "var(--ink)" }}>{scenario.callsign} · {scenario.name} {scenario.last4}</span>
<div className="right">
<button
className="topbar-btn tweaks-btn"
onClick={() => window.postMessage({ type: "__activate_edit_mode" }, "*")}
title="Open Tweaks panel">
⚙ TWEAKS
</button>
<span className="clock" title="Mission clock — runs across patient switches">{recordTime}</span>
<span className="clock-sub" title="Current patient elapsed">{patientTime}</span>
<span className={`pill ${paused ? "warn" : finished ? "danger" : "live"}`}>
{finished ? "ENDED" : paused ? "PAUSED" : "RUNNING"}
</span>
</div>
</div>
{/* PATIENT ROSTER */}
<div className={`roster-strip ${rosterShown ? "open" : "collapsed"}`} data-screen-label="Patient roster">
<button className="roster-head" onClick={toggleRoster} title={rosterShown ? "Collapse roster" : "Expand roster"}>
<span className="roster-caret">{rosterShown ? "\u25be" : "\u25b8"}</span>
<span className="roster-title">Patient Roster</span>
<span className="roster-count">{window.SCENARIOS.length} casualties</span>
{!rosterShown &&
<span className="roster-visited">
{visited.map((vi) =>
<span key={window.SCENARIOS[vi].id}
className={`roster-chip ${vi === scenarioIdx ? "active" : ""} ${kia.has(window.SCENARIOS[vi].id) ? "kia" : ""}`}
title={window.SCENARIOS[vi].diagnosis}
onClick={(e) => { e.stopPropagation(); pick(vi); if (window.Sound) window.Sound.play("pageClick"); }}>
{String(vi + 1).padStart(2, "0")} {window.SCENARIOS[vi].name.toUpperCase()}
</span>
)}
</span>
}
<span className="roster-hint">{rosterShown ? "Click a patient to load \u00b7 collapse \u25b4" : "Expand \u25be"}</span>
</button>
<div className="roster-grid-wrap">
<div className="roster-grid">
{window.SCENARIOS.map((s, i) =>
<button key={s.id}
className={`roster-pill ${i === scenarioIdx ? "active" : ""} ${kia.has(s.id) ? "kia" : ""}`}
title={s.diagnosis}
onClick={() => { pick(i); if (window.Sound) window.Sound.play("pageClick"); }}>
<span className="num">{String(i + 1).padStart(2, "0")}</span>
<span className="lname">{s.name.toUpperCase()}</span>
</button>
)}
</div>
</div>
</div>
{/* LEFT — vitals + casualty brief (collapsible tabs) */}
<div className="col-left" data-screen-label="Vitals and casualty brief">
<div className={`tab-shell shell-vitals ${eff.vitals ? "open" : "closed"}`}>
<button className="tab-bar" onClick={() => toggleTab("vitals")}>
<span className="marker">▮</span><span>Signs &amp; Symptoms</span>
<span className="tab-caret">{eff.vitals ? "\u25be" : "\u25b8"}</span>
</button>
<div className="tab-body">
<div className="vitals-panel">
<window.VitalsPanel
vitals={vitals}
takenAVPU={vitals.avpu}
onChangeAVPU={setAVPU}
elapsedSec={elapsedSec}
paused={paused} />
</div>
</div>
</div>
<div className={`tab-shell shell-brief ${eff.brief ? "open" : "closed"} ${!eff.brief ? "glow" : ""}`}>
<button className="tab-bar" onClick={() => toggleTab("brief")}>
<span className="marker">▮</span><span>Casualty Brief · DD-1380</span>
<span className="tab-caret">{eff.brief ? "\u25be" : "\u25b8"}</span>
</button>
<div className="tab-body">
<window.CasualtyInfoPanel
scenario={scenario}
briefRate={t.briefRate}
onBriefDone={onBriefDone} />
</div>
</div>
</div>
{/* CENTER — anatomical figure */}
<div className="col-center" data-screen-label="Casualty body view">
<window.BodyPanel
scenario={scenario}
taken={taken}
tqStates={tqStates}
elapsedSec={elapsedSec}
onTqApply={onTqApply}
onTqConvert={onTqConvert} />
</div>
{/* RIGHT — treatments + outcome (collapsible tabs) */}
<div className="col-right" data-screen-label="Treatments and outcome">
<div className={`tab-shell shell-tx ${eff.tx ? "open" : "closed"} ${!eff.tx ? "glow" : ""}`}>
<button className="tab-bar" onClick={() => toggleTab("tx")}>
<span className="marker">▮</span><span>Treatments</span>
<span className="tab-mini">TX · MEDS · DRIP · BLOOD · DECIDE · REF</span>
<span className="tab-caret">{eff.tx ? "\u25be" : "\u25b8"}</span>
</button>
<div className="tab-body">
<window.TreatmentsPanel
scenario={scenario}
taken={taken}
perform={performAction}
nextHint={nextHint}
hintMode={t.hintMode}
showAnswers={t.showAnswers}
evacPriority={evacPriority}
setEvacPriority={setEvacPriority} />
</div>
</div>
<div className={`tab-shell shell-outcome ${eff.outcome ? "open" : "closed"}`}>
<button className="tab-bar" onClick={() => toggleTab("outcome")}>
<span className="marker">▮</span><span>Survival Probability</span>
<span className="tab-caret">{eff.outcome ? "\u25be" : "\u25b8"}</span>
</button>
<div className="tab-body">
<OutcomePanel
survival={survival}
criticalDone={criticalDone}
criticalMissed={criticalMissed}
contraTaken={contraTaken}
elapsedSec={elapsedSec}
paused={paused}
finished={finished}
onStart={start}
onPause={pause}
onReset={reset}
onResetMission={resetMission}
onEvac={evac}
onExport={exportReport} />
</div>
</div>
</div>
{/* BOTTOM — decision log (collapsible) */}
<div className="log-row" data-screen-label="Decision log">
<div className={`tab-shell shell-log ${eff.log ? "open" : "closed"}`}>
<button className="tab-bar" onClick={() => toggleTab("log")}>
<span className="marker">▮</span><span>Decision Log</span>
<span className="tab-mini">{log.length} ENTR{log.length === 1 ? "Y" : "IES"}</span>
<span className="tab-caret">{eff.log ? "\u25be" : "\u25b8"}</span>
</button>
<div className="tab-body">
<DecisionLog log={log} />
</div>
</div>
</div>
{/* Hidden print report — revealed by @media print on export */}
<window.PrintReport
scenario={scenario}
vitals={vitals}
log={log}
survival={survival}
elapsedSec={elapsedSec}
missionElapsedSec={missionElapsedSec}
outcome={outcome}
finished={finished}
criticalDone={criticalDone}
criticalMissed={criticalMissed}
contraTaken={contraTaken}
evacPriority={evacPriority} />
{/* FOCUS MODE — mirrors the MUSIC pill, opposite corner */}
<button className={`focus-tgl ${focusMode ? "on" : ""}`} onClick={toggleFocus}
title="Focus mode — collapse everything except Treatments">
<span className="focus-ico">◎</span> FOCUS
</button>
{/* Toast */}
{toast &&
<div className={`toast ${toast.kind === "good" ? "good" : ""}`}>{toast.msg}</div>
}
{/* Debrief */}
{finished && !dismissedIds.has(scenario.id) &&
<Debrief
scenario={scenario}
criticalDone={criticalDone}
criticalMissed={criticalMissed}
contraTaken={contraTaken}
log={log}
survival={survival}
outcome={outcome}
onReplay={reset}
onExport={exportReport}
onReturn={() => {
setKia((s) => { const n = new Set(s); n.add(scenario.id); return n; });
setDismissedIds((s) => { const n = new Set(s); n.add(scenario.id); return n; });
if (window.Sound) window.Sound.play("pageClick");
}}
onNext={() => {next();}} />
}
{/* Tweaks panel */}
<window.TweaksPanel title="Tweaks">
<window.TweakSection label="Display">
<window.TweakRadio
label="Theme"
value={t.theme}
options={[
{ value: "dark", label: "Night" },
{ value: "light", label: "Day" }]
}
onChange={(v) => setTweak("theme", v)} />
</window.TweakSection>
<window.TweakSection label="Background · WebGL">
<window.TweakToggle
label="Glass panels"
value={t.glassPanels}
onChange={(v) => setTweak("glassPanels", v)} />
<window.TweakSlider
label="Glass opacity"
min={30} max={95} step={1}
unit="%"
value={t.glassOpacity ?? 62}
onChange={(v) => setTweak("glassOpacity", v)} />
<window.TweakToggle
label="Tint to patient acuity"
value={t.bgCondition !== false}
onChange={(v) => setTweak("bgCondition", v)} />
</window.TweakSection>
<window.TweakSection label="Difficulty">
<window.TweakRadio
label="Grading"
value={t.difficulty}
options={[
{ value: "lenient", label: "Lenient" },
{ value: "strict", label: "Strict" }]
}
onChange={(v) => setTweak("difficulty", v)} />
</window.TweakSection>
<window.TweakSection label="Coaching">
<window.TweakToggle
label="Hint next critical action"
value={t.hintMode}
onChange={(v) => setTweak("hintMode", v)} />
<window.TweakToggle
label="Show full answer key"
value={t.showAnswers}
onChange={(v) => setTweak("showAnswers", v)} />
</window.TweakSection>
<window.TweakSection label="Time">
<window.TweakSlider
label="Sim speed"
min={1} max={6} step={1}
unit="×"
value={t.timeSpeed}
onChange={(v) => setTweak("timeSpeed", v)} />
</window.TweakSection>
<window.TweakSection label="Audio">
<window.TweakToggle
label="Sound on"
value={t.soundOn}
onChange={(v) => setTweak("soundOn", v)} />
<window.TweakRadio
label="Pulse sound"
value={t.pulseSound}
options={[
{ value: "beep", label: "Beep" },
{ value: "lubdub", label: "Lub-Dub" },
{ value: "off", label: "Off" }]
}
onChange={(v) => setTweak("pulseSound", v)} />
</window.TweakSection>
<window.TweakSection label="Patient Brief">
<window.TweakSlider
label="Narration speed"
min={0.7} max={1.6} step={0.1}
unit="×"
value={t.briefRate}
onChange={(v) => setTweak("briefRate", v)} />
</window.TweakSection>
</window.TweaksPanel>
</div>);
}
window.App = App;
ReactDOM.createRoot(document.getElementById("root")).render(<App />);