const MARCH_PAWS = [
{ k:'M', label:'Massive Hemorrhage', groups:[
['Control', ['Tourniquet applied','2nd TQ side-by-side','Junctional TQ','Wound packed (hemostatic)','Pressure dressing','iTClamp','Direct pressure']],
['Adjuncts', ['TXA 2g IV/IO','TQ reassessed (<2h)','TQ converted','TQ time marked on skin']] ]},
{ k:'A', label:'Airway', groups:[
['Basic', ['Chin-lift / jaw-thrust','NPA placed','Recovery position','Sit-up & lean-forward','Suction']],
['Advanced', ['i-gel / SGA placed','Surgical cric','ETT placed','Airway burn → early definitive airway']] ]},
{ k:'R', label:'Respiration', groups:[
['Chest', ['Chest seal R','Chest seal L','Seal burped','Needle decomp R (5th ICS AAL)','Needle decomp L (5th ICS AAL)','Finger thoracostomy']],
['Support', ['O₂ administered','SpO₂ monitored','BVM / vent','Occlusive dressing checked']] ]},
{ k:'C', label:'Circulation', groups:[
['Access', ['IV 18g established','IO humeral','IO sternal','IO tibial','2nd line']],
['Resus', ['Whole blood started','LTOWB / FWB from team','Plasma / PRBC','Calcium 1g IV after 1st unit','Hypotensive resus — radial pulse / SBP ~100','Pelvic binder','Shock assessed (radial pulse, mental status)']] ]},
{ k:'H', label:'Hypothermia / Head', groups:[
['Hypothermia', ['Remove wet / burned clothing','HPMK applied','Ready-Heat blanket','Insulate from ground','Warm fluids / blood','Core temp monitored']],
['Head', ['TBI screen (MACE 2)','C-spine immobilized','Eye shield (rigid)','Pupils checked','Head elevated 30°']] ]},
{ k:'P', label:'Pain', groups:[
['Triple option', ['Acetaminophen 1 g PO + Meloxicam 15 mg PO','Fentanyl OTFC 800 mcg','Ketamine IV/IO 20–30 mg','Ketamine IN/IM 50–100 mg','Ondansetron 4 mg ODT/IV','Naloxone 0.4 mg ready']] ]},
{ k:'A2', label:'Antibiotics', groups:[
['Open wound', ['Moxifloxacin 400 mg PO','Ertapenem 1 g IV/IM','Cefotetan 2 g IV/IM (alt)','Allergy checked']] ]},
{ k:'W', label:'Wounds', groups:[
['Wound care', ['Wounds dressed','Burns covered dry sterile','Wound irrigated','Evisceration covered moist','Impaled object stabilized','Amputation stump dressed']] ]},
{ k:'S', label:'Splinting', groups:[
['Splint', ['SAM splint applied','Traction splint','Pelvic binder','Fracture immobilized','Distal CSM checked']] ]},
{ k:'D', label:'Document & Reassess', groups:[
['DD1380', ['DD 1380 started','MIST sent','Vitals q15 min','Re-MARCH complete','Handover briefed']] ]},
];
function ChipGroup({ items, prefix, list, toggle }) {
return <div className="chips">{items.map(it=>{ const id=prefix+':'+it; return <div key={id} className={'chip '+(list.includes(id)?'active':'')} onClick={()=>toggle(id)}>{it}</div>; })}</div>;
}
function parseHHMM(t){ if(!t) return NaN; const s=String(t).replace(':',''); const h=+s.slice(0,2), mm=+s.slice(2,4); return isNaN(h)||isNaN(mm)?NaN:h*60+mm; }
const fmtHM = (min)=>{ const mm=((min%1440)+1440)%1440; return String(Math.floor(mm/60)).padStart(2,'0')+String(mm%60).padStart(2,'0'); };
function TqTimers({ tourniquets, now }) {
const act = Object.entries(tourniquets||{}).filter(([,t])=>t.active);
if (!act.length) return <div style={{padding:'10px 14px',fontFamily:'var(--mono)',fontSize:'11px',color:'var(--fg-3)'}}>No tourniquet on. Mark one on the injury map to start conversion timers.</div>;
const nowMin = now.getHours()*60+now.getMinutes();
return <div className="tqt">{act.map(([k,t])=>{
const side={rArm:'R ARM',lArm:'L ARM',rLeg:'R LEG',lLeg:'L LEG'}[k];
const t0=parseHHMM(t.time); const el=isNaN(t0)?null:((nowMin-t0+1440)%1440);
const st = el==null?'amber': el<90?'green': el<120?'amber': el<360?'red':'black';
const msg = el==null?'Set application time in TQ panel': el<120?`Conversion window open · attempt if no shock, no amputation, wound packable`: el<360?`>2 h — conversion window passed. Reassess; convert only with surgical capability`: `>6 h — DO NOT CONVERT. Leave on, document.`;
return <div key={k} className={'tqt-c st-'+st}>
<div className="h"><b>{side}</b><span>{t.type||'TQ'}{t.position?' · '+t.position:''}</span></div>
<div className="el">{el==null?'—':`${Math.floor(el/60)}:${String(el%60).padStart(2,'0')}`}<small>elapsed · on @{t.time||'----'}</small></div>
<div className="bar"><i style={{width:`${Math.min(100,(el||0)/360*100)}%`}}></i></div>
<div className="ln"><span>Convert by (2 h)</span><b>{isNaN(t0)?'—':fmtHM(t0+120)}</b></div>
<div className="ln"><span>Hard limit (6 h)</span><b>{isNaN(t0)?'—':fmtHM(t0+360)}</b></div>
<div className="msg">{msg}</div>
</div>; })}</div>;
}
const PAIN_RUNGS = [
{ k:'mild', st:'green', w:'Mild–moderate · still fighting', dr:['Acetaminophen 1 g PO q8h','Meloxicam 15 mg PO daily','(TCCC Combat Wound Med Pack)'] },
{ k:'moderate', st:'amber', w:'Moderate–severe · NOT in shock, no resp distress', dr:['Fentanyl OTFC 800 mcg transbuccal','repeat ×1 at 15 min','or Ketamine 20–30 mg IV/IO · 50–100 mg IN/IM'] },
{ k:'severe', st:'red', w:'Severe · in shock or respiratory distress', dr:['Ketamine 20–30 mg IV/IO slow push','repeat q20 min to nystagmus','or 50–100 mg IN/IM q20–30 min','Naloxone 0.4 mg IV/IM/IN for opioid resp depression'] },
];
function PainLadder({ pain, setPain, avpu, lastVital }) {
const shock = lastVital && (+lastVital.sbp<90 || +lastVital.hr>120);
return <div className="proto">
<div className="ladder">{PAIN_RUNGS.map(r=><div key={r.k} className={'rung st-'+r.st+(pain.rung===r.k?' on':'')} onClick={()=>setPain({...pain,rung:pain.rung===r.k?'':r.k})}>
<div className="n">{r.k}</div><div className="w">{r.w}</div><div className="dr">{r.dr.map((d,i)=><div key={i}>{i===0?<b>{d}</b>:d}</div>)}</div></div>)}</div>
<div className="out" style={{'--st': shock?'var(--crit)':'var(--op)'}}><span className="k">Guidance</span>
{shock ? 'Latest vitals suggest shock (SBP <90 or HR >120): avoid opioids — ketamine is the analgesic of choice. Disarm the casualty.' : 'No shock on latest vitals. Casualty able to fight → CWMP. Otherwise OTFC or ketamine; disarm before dosing. Monitor SpO₂ and AVPU after any opioid/ketamine.'}
{avpu && avpu!=='A' && ' AVPU '+avpu+' — reassess airway before sedating drugs.'}</div>
</div>;
}
function Antibiotics({ abx, setAbx, allergies }) {
const t = (k)=>setAbx({...abx,[k]:!abx[k]});
const pcn = /pcn|penicillin|cephal|beta/i.test(allergies||'');
const rows = [
['moxi','Moxifloxacin 400 mg PO daily','Able to take PO. All open combat wounds. Start ASAP.'],
['erta','Ertapenem 1 g IV/IM daily','Unable to take PO, penetrating abdominal / shock / unconscious.'],
['cefo','Cefotetan 2 g IV/IM q12h','Alternative if ertapenem unavailable.'],
];
return <div className="proto">
{pcn && <div className="out" style={{'--st':'var(--crit)'}}><span className="k">Allergy flag</span>Listed allergy "{allergies}" — beta-lactam class caution for ertapenem / cefotetan.</div>}
{rows.map(([k,tt,d])=><div key={k} className={'row'+(abx[k]?' on':'')+(k!=='moxi'&&pcn?' crit':'')} onClick={()=>t(k)}><div><div className="t">{tt}</div><div className="d">{d}</div></div><div className="ck">{abx[k]?'✓':''}</div></div>)}
</div>;
}
const TBI_Q = [
['loc','Loss of consciousness','Any period unresponsive after the event'],
['aoc','Alteration of consciousness','Dazed, confused, "saw stars"'],
['pta','Post-traumatic amnesia','Cannot recall event or period after'],
['headache','Worsening headache','Progressive or severe'],
['vomit','Repeated vomiting','≥2 episodes'],
['seizure','Seizure',''],
['pupils','Unequal pupils',''],
['weak','Weakness / numbness',''],
['vision','Double vision / slurred speech',''],
['gcs','Deteriorating GCS / AVPU',''],
];
function TbiScreen({ tbi, setTbi }) {
const t=(k)=>setTbi({...tbi,[k]:!tbi[k]});
const red = ['headache','vomit','seizure','pupils','weak','vision','gcs'].some(k=>tbi[k]);
const conc = ['loc','aoc','pta'].some(k=>tbi[k]);
const res = red ? ['RED FLAG — urgent evac, neuro checks q15 min, head up 30°, SBP ≥110, SpO₂ ≥94, avoid hypoxia/hypotension','var(--crit)']
: conc ? ['Concussion (mTBI) — remove from duty, 24 h mandatory rest, MACE 2 at role 1, no return to duty until symptom-free','var(--amber)']
: ['No concussion criteria met — re-screen if symptoms develop','var(--op)'];
return <div className="proto">
<div className="out" style={{'--st':res[1]}}><span className="k">MACE 2 screen result</span>{res[0]}</div>
{TBI_Q.map(([k,tt,d],i)=><div key={k} className={'row'+(tbi[k]?' on':'')+(i>=3?' crit':'')} onClick={()=>t(k)}><div><div className="t">{i>=3?'⚑ ':''}{tt}</div>{d&&<div className="d">{d}</div>}</div><div className="ck">{tbi[k]?'✓':''}</div></div>)}
</div>;
}
function Hypothermia({ lastVital, interventions }) {
const done = ['H:Remove wet / burned clothing','H:HPMK applied','H:Ready-Heat blanket','H:Insulate from ground','H:Warm fluids / blood','H:Core temp monitored'].filter(k=>interventions.includes(k)).length;
const shock = lastVital && +lastVital.sbp<100;
return <div className="proto" style={{paddingTop:0}}>
<div className="out" style={{'--st': done>=4?'var(--op)':'var(--amber)'}}><span className="k">HPMK protocol · {done}/6 measures</span>
Every trauma casualty is hypothermic until proven otherwise. {shock?'Casualty is in shock — hypothermia accelerates coagulopathy; prioritise active warming now. ':''}Minimise exposure, get off the ground, active heat over torso, warm every fluid, cover the head. Trauma triad: hypothermia · acidosis · coagulopathy.</div>
</div>;
}
function MarchTab({ mb, upd, now }) {
const interventions = mb.interventions||[];
const toggle=(id)=>upd('interventions', p=>p.includes(id)?p.filter(x=>x!==id):[...p,id]);
const lv = (mb.vitals||[])[mb.vitals.length-1];
const [open,setOpen] = React.useState({});
return <div className="scrollable march">
{MARCH_PAWS.map(sec=>{ const n=sec.groups.flatMap(g=>g[1]).filter(it=>interventions.includes(sec.k+':'+it)).length; const isOpen = open[sec.k] !== false;
return <div className="panel" key={sec.k}>
<div className="panel-h" style={{cursor:'pointer'}} onClick={()=>setOpen(o=>({...o,[sec.k]:!isOpen}))}><span className="ltr">{sec.k.replace('2','')}</span><b>{sec.label}</b><span className="done">{n?n+' done':''} {isOpen?'▾':'▸'}</span></div>
{isOpen && <div className="panel-body">
{sec.groups.map(([g,items])=><React.Fragment key={g}><div className="sub">{g}</div><ChipGroup items={items} prefix={sec.k} list={interventions} toggle={toggle}/></React.Fragment>)}
{sec.k==='M' && <div style={{marginTop:10}}><div className="sub">TQ conversion timers</div><TqTimers tourniquets={mb.tourniquets} now={now}/></div>}
{sec.k==='H' && <Hypothermia lastVital={lv} interventions={interventions}/>}
{sec.k==='H' && <><div className="sub">TBI · MACE 2 quick screen</div><TbiScreen tbi={mb.tbi||{}} setTbi={v=>upd('tbi',v)}/></>}
{sec.k==='P' && <PainLadder pain={mb.pain||{}} setPain={v=>upd('pain',v)} avpu={mb.avpu} lastVital={lv}/>}
{sec.k==='A2' && <Antibiotics abx={mb.abx||{}} setAbx={v=>upd('abx',v)} allergies={mb.allergies}/>}
</div>}
</div>; })}
</div>;
}
const NINE_DEFAULT = { l1:'', l2:'', l2cs:'', l4:'A', l6:'N', l7:'C', l8:'A', l9:'' };
function NineLine({ team, nine, setNine }) {
const n = {...NINE_DEFAULT, ...nine};
const set=(k)=>(e)=>setNine({...n,[k]:e.target.value});
const cas = team.filter(mb=>mb.evac);
const by = (k)=>cas.filter(mb=>mb.evac===k).length;
const litter = cas.filter(mb=>mb.carry ? mb.carry==='litter' : (mb.evac==='urgent' || Object.values(mb.tourniquets||{}).some(t=>t.active) || (mb.avpu&&mb.avpu!=='A'))).length;
const amb = cas.length - litter;
const l3 = [by('urgent')&&`${by('urgent')}A`, by('priority')&&`${by('priority')}C`, by('routine')&&`${by('routine')}D`].filter(Boolean).join(' ')||'—';
const l5 = [litter&&`${litter}L`, amb&&`${amb}A`].filter(Boolean).join(' ')||'—';
const L4={A:'A — None',B:'B — Hoist',C:'C — Extraction equipment',D:'D — Ventilator'}, L6={N:'N — No enemy',P:'P — Possible enemy',E:'E — Enemy in area (caution)',X:'X — Armed escort required'};
const L7={A:'A — Panels',B:'B — Pyrotechnic',C:'C — Smoke',D:'D — None',E:'E — Other'}, L8={A:'A — US Military',B:'B — US Civilian',C:'C — Non-US Military',D:'D — Non-US Civilian',E:'E — EPW'};
const txt = `LINE 1  ${n.l1||'____'}\nLINE 2  ${n.l2||'____'} / ${n.l2cs||'____'}\nLINE 3  ${l3}\nLINE 4  ${L4[n.l4]}\nLINE 5  ${l5}\nLINE 6  ${L6[n.l6]}\nLINE 7  ${L7[n.l7]}\nLINE 8  ${L8[n.l8]}\nLINE 9  ${n.l9||'____'}\n\nMIST to follow on approach.`;
const sel=(k,opts)=><select value={n[k]} onChange={set(k)}>{Object.entries(opts).map(([v,l])=><option key={v} value={v}>{l}</option>)}</select>;
return <div className="scrollable"><div className="nine">
<div className="ln"><b>1</b><span className="k">Location</span><input value={n.l1} onChange={set('l1')} placeholder="MGRS grid e.g. 42S WD 12345 67890"/></div>
<div className="ln"><b>2</b><span className="k">Freq · C/S</span><div style={{display:'flex',gap:6}}><input value={n.l2} onChange={set('l2')} placeholder="Freq"/><input value={n.l2cs} onChange={set('l2cs')} placeholder="Call sign"/></div></div>
<div className="ln"><b>3</b><span className="k">Precedence</span><span className="auto">{l3}<small>auto · A urgent · C priority · D routine</small></span></div>
<div className="ln"><b>4</b><span className="k">Equipment</span>{sel('l4',L4)}</div>
<div className="ln"><b>5</b><span className="k">Litter / Amb</span><span className="auto">{l5}<small>auto from evac, TQ, AVPU</small></span></div>
<div className="ln"><b>6</b><span className="k">Security</span>{sel('l6',L6)}</div>
<div className="ln"><b>7</b><span className="k">Marking</span>{sel('l7',L7)}</div>
<div className="ln"><b>8</b><span className="k">Nationality</span>{sel('l8',L8)}</div>
<div className="ln"><b>9</b><span className="k">NBC / Terrain</span><input value={n.l9} onChange={set('l9')} placeholder="None / terrain description"/></div>
<pre>{txt}</pre>
<div style={{display:'flex',gap:6}}><button className="mini-btn" onClick={()=>navigator.clipboard&&navigator.clipboard.writeText(txt)}>⧉ Copy 9-Line</button><span style={{flex:1}}></span><span className="muted mono tiny">{cas.length} casualties flagged for evac</span></div>
</div></div>;
}
const CAN_GIVE = (donor, rec) => { const [dA,dR]=[donor.replace(/[+-]/,''),donor.slice(-1)], [rA,rR]=[rec.replace(/[+-]/,''),rec.slice(-1)]; const abo = dA==='O'||dA===rA||rA==='AB'; const rh = dR==='-'||rR==='+'; return abo&&rh; };
function TeamBloodBank({ team, mb, onSelect }) {
const donors = team.filter(x=>x.id!==mb.id).map(x=>({...x, ok: CAN_GIVE(x.bloodType, mb.bloodType), st: memberStatus(x)}));
const n = donors.filter(d=>d.ok && d.st==='green').length;
return <div className="panel"><div className="panel-h"><span className="tick"></span><b>Walking Blood Bank</b><span className="right">{n} compatible &amp; uninjured for {mb.bloodType}</span></div>
<div className="wbb">{donors.map(d=><div key={d.id} className={'d'+(d.ok?' ok':'')+(d.st!=='green'?' self':'')} onClick={()=>onSelect(d.id)} title={d.name}>
<div className="r">{d.role} · {d.name.split(',')[0]}</div><div className="bt">{d.bloodType}</div><div className="tag">{d.st!=='green'?'casualty':d.ok?(d.bloodType==='O-'?'universal':'compatible'):'incompatible'}</div></div>)}</div>
<div className="compat-key"><span className="k"><span className="sw" style={{background:'var(--op)'}}></span>Can donate</span><span className="k"><span className="sw" style={{background:'var(--fg-3)'}}></span>Incompatible / casualty</span><span style={{marginLeft:'auto'}}>Prefer LTOWB · pre-screened donors · Ca²⁺ after 1st unit</span></div>
</div>;
}
Object.assign(window, { MarchTab, NineLine, TeamBloodBank, TqTimers, parseHHMM, MARCH_PAWS, CAN_GIVE });