const EMPTY_TQ = () => ({ rArm:{active:false,type:'',time:'',position:''}, lArm:{active:false,type:'',time:'',position:''}, rLeg:{active:false,type:'',time:'',position:''}, lLeg:{active:false,type:'',time:'',position:''} });
const BLANK_MEMBER = () => ({
status:'auto', ammo:'green', equip:'green',
service:'USA', unit:'ODA 0312', allergies:'NKDA', evac:'', triage:'', gender:'M', weight:85,
mechanism:[], acuteFindings:[], injuries:[], burnedRegions:[], tourniquets:EMPTY_TQ(),
vitals:[], draftVitals:{hr:'',sbp:'',dbp:'',rr:'',spo2:'',pain:''}, avpu:'A',
interventions:[], medsGiven:[], fluidsGiven:[], notes:'',
burn:{ formula:'parkland', burnTime:'', inhal:false, circ:false, uop:[], care:[] },
pain:{ rung:'', notes:'' }, abx:{}, tbi:{}, hypo:{},
});
const m = (o) => ({ ...BLANK_MEMBER(), ...o });
const ODA_ROSTER = [
m({ id:'18A',  role:'18A',  title:'Detachment Cdr',  name:'HALVORSEN, M.', last4:'2210', bloodType:'O+',  pos:6,  weight:88 }),
m({ id:'180A', role:'180A', title:'Asst Det Cdr',    name:'REYES, D.',     last4:'7731', bloodType:'A-',  pos:7,  weight:82, ammo:'amber' }),
m({ id:'18Z',  role:'18Z',  title:'Team Sergeant',   name:'BOUDREAUX, T.', last4:'0954', bloodType:'O-',  pos:5,  weight:94 }),
m({ id:'18F',  role:'18F',  title:'Intel Sgt',       name:'NAKAMURA, K.',  last4:'3318', bloodType:'B+',  pos:8,  weight:79 }),
m({ id:'18B1', role:'18B',  title:'Weapons Sgt',     name:'OKAFOR, J.',    last4:'4521', bloodType:'A+',  pos:1,  weight:90, evac:'urgent', triage:'immediate',
mechanism:['GSW','Blast'], acuteFindings:[], allergies:'PCN',
injuries:[{view:'front',x:78,y:295,type:'gsw'},{view:'front',x:130,y:200,type:'gsw'}],
tourniquets:{ ...EMPTY_TQ(), rLeg:{active:true,type:'CAT',time:'1342',position:'Proximal'} },
vitals:[{time:'1338',hr:'128',sbp:'92',dbp:'60',rr:'22',spo2:'94',pain:'8'},{time:'1345',hr:'118',sbp:'98',dbp:'64',rr:'20',spo2:'96',pain:'6'},{time:'1352',hr:'110',sbp:'104',dbp:'68',rr:'18',spo2:'97',pain:'4'}],
draftVitals:{hr:'110',sbp:'104',dbp:'68',rr:'18',spo2:'97',pain:'4'}, avpu:'V',
interventions:['M:Tourniquet applied','M:TXA 2g IV/IO','C:IV 18g established','C:Whole blood started','H:HPMK applied','P:Ketamine IV/IO 20–30 mg','A:Ertapenem 1 g IV/IM'],
medsGiven:[{id:1,time:'1343',name:'Fentanyl OTFC',dose:'800 mcg'},{id:2,time:'1346',name:'TXA',dose:'2 g IV'},{id:3,time:'1350',name:'Ketamine IV',dose:'30 mg'}],
fluidsGiven:[{id:1,time:'1348',type:'Whole Blood',vol:'500'}],
pain:{rung:'severe'}, abx:{erta:true},
notes:'1340: GSW R thigh + L flank. CAT prox R thigh; bleeding controlled. IV L AC 18g. TXA 2g. Permissive hypotension SBP 90+. HPMK on.' }),
m({ id:'18B2', role:'18B',  title:'Weapons Sgt',     name:'STRAND, E.',    last4:'6602', bloodType:'O+',  pos:12, weight:86, ammo:'red' }),
m({ id:'18C1', role:'18C',  title:'Engineer Sgt',    name:'DELACROIX, P.', last4:'1187', bloodType:'AB+', pos:2,  weight:84, evac:'priority', triage:'delayed',
mechanism:['IED','Burn','Blast'], injuries:[{view:'front',x:100,y:180,type:'shrap'}], burnedRegions:['f-rArm','f-chest','f-head'],
vitals:[{time:'1341',hr:'112',sbp:'118',dbp:'76',rr:'24',spo2:'95',pain:'9'},{time:'1355',hr:'104',sbp:'122',dbp:'78',rr:'22',spo2:'96',pain:'6'}],
draftVitals:{hr:'104',sbp:'122',dbp:'78',rr:'22',spo2:'96',pain:'6'},
interventions:['A:Sit-up & lean-forward','R:SpO₂ monitored','C:IV 18g established','H:Remove wet / burned clothing','H:HPMK applied','W:Burns covered dry sterile','P:Fentanyl OTFC 800 mcg'],
medsGiven:[{id:1,time:'1344',name:'Fentanyl OTFC',dose:'800 mcg'}], fluidsGiven:[{id:1,time:'1350',type:'LR',vol:'500'}],
burn:{ formula:'rule10', burnTime:'1336', inhal:true, circ:false, uop:[], care:['Stop','Airway','Assess','Wounds','Hypothermia'] },
pain:{rung:'moderate'}, equip:'amber',
notes:'1338: IED strike lead vic. Flash/flame burns face, R arm, ant chest. Singed nasal hair — airway watch.' }),
m({ id:'18C2', role:'18C',  title:'Engineer Sgt',    name:'MBEKI, S.',     last4:'8840', bloodType:'B-',  pos:11, weight:91 }),
m({ id:'18D1', role:'18D',  title:'Medical Sgt',     name:'PHAM, R.',      last4:'5573', bloodType:'O-',  pos:4,  weight:80 }),
m({ id:'18D2', role:'18D',  title:'Medical Sgt',     name:'WHITLOCK, A.',  last4:'2296', bloodType:'A+',  pos:9,  weight:83 }),
m({ id:'18E1', role:'18E',  title:'Commo Sgt',       name:'CASTILLO, R.',  last4:'9015', bloodType:'O+',  pos:3,  weight:78, evac:'routine', triage:'minimal',
mechanism:['Blast'], avpu:'A',
vitals:[{time:'1350',hr:'96',sbp:'128',dbp:'82',rr:'16',spo2:'98',pain:'5'}], draftVitals:{hr:'96',sbp:'128',dbp:'82',rr:'16',spo2:'98',pain:'5'},
tbi:{ loc:true, aoc:true, pta:false, headache:true }, interventions:['H:TBI screen (MACE 2)','P:Acetaminophen 1 g PO + Meloxicam 15 mg PO'], pain:{rung:'mild'},
notes:'1339: ~3 m from IED blast, knocked down, dazed ~1 min. HA 5/10, no vomiting. Pupils equal.' }),
m({ id:'18E2', role:'18E',  title:'Commo Sgt',       name:'GRANT, L.',     last4:'4478', bloodType:'AB-', pos:10, weight:81, ammo:'amber' }),
];
function memberStatus(mb) {
if (mb.status && mb.status !== 'auto') return mb.status;
if (mb.triage === 'expectant') return 'black';
const tq = Object.values(mb.tourniquets||{}).some(t=>t.active);
if (mb.triage === 'immediate' || mb.evac === 'urgent' || tq || (mb.acuteFindings||[]).length) return 'red';
if ((mb.injuries||[]).length || (mb.burnedRegions||[]).length || mb.evac || mb.triage || (mb.tbi && Object.values(mb.tbi).some(Boolean))) return 'amber';
return 'green';
}
const STATUS_LABEL = { green:'Up', amber:'Wounded', red:'Urgent', black:'KIA/Exp' };
const STATUS_ORDER = ['green','amber','red','black'];
const RAG = ['green','amber','red'];
const cycle = (v, arr=RAG) => arr[(arr.indexOf(v)+1) % arr.length];
function Formation({ team, selectedId, onSelect, squads, dueMap = {} }) {
const rows = squads ? squads.map(sq => ({ name: sq.name.split(' · ')[0].replace('Platoon ', '').replace('Weapons Squad', 'WPNS').replace(' Squad', ' SQD'), members: sq.slots.map(i => team[i]).filter(Boolean) })) : [{ name: '', members: [...team].sort((a,b)=>a.pos-b.pos) }];
const maxN = Math.max(...rows.map(r => r.members.length), 1);
const W = 300, labW = squads ? 46 : 0, rowH = squads ? 30 : 34, r = squads ? 11 : 12;
const gap = Math.min(2.3 * r + 2, (W - labW - 2 * r - 6) / Math.max(1, maxN - 1));
return (
<div className="formation" title="Order of movement · one row per section">
<svg viewBox={`0 0 ${W} ${rows.length * rowH}`}>
{rows.map((row, ri) => { const cy = ri * rowH + rowH / 2; const x0 = labW + r + 4; return <g key={ri}>
{row.name && <text x="0" y={cy + 3} fontSize="8" fill="var(--fg-3)" fontFamily="var(--mono)" letterSpacing=".06em">{row.name.toUpperCase()}</text>}
<line x1={x0} y1={cy} x2={x0 + gap * Math.max(0, row.members.length - 1)} y2={cy} stroke="var(--line-2)" strokeWidth="1" strokeDasharray="2 3"/>
{row.members.map((mb, i) => { const st = memberStatus(mb); const x = x0 + i * gap; const sel = mb.id === selectedId; const due = !!dueMap[mb.id];
return <g key={mb.id} className={'st-' + st} onClick={() => onSelect(mb.id)} style={{ cursor: 'pointer' }}>
<title>{`${mb.role} ${mb.name || mb.id} · P${mb.pos} · ${STATUS_LABEL[st]}${due ? ' · ' + dueMap[mb.id] : ''}`}</title>
<circle cx={x} cy={cy} r={sel ? r + 1.5 : r} fill="var(--st)" stroke={sel ? 'var(--fg-0)' : due ? 'var(--crit)' : 'none'} strokeWidth={sel ? 1.5 : 1.2} strokeDasharray={!sel && due ? '2 2' : 'none'}/>
<text x={x} y={cy + 3} textAnchor="middle" fontSize={squads ? 7.5 : 9} fontWeight="700" fill={st === 'amber' || st === 'green' ? '#0c1410' : '#fff'} fontFamily="var(--mono)" pointerEvents="none">{squads ? mb.role.slice(0, 3) : mb.pos}</text>
</g>; })}
</g>; })}
</svg>
</div>
);
}
function MiniFeed({ mb }) {
const lv = (mb.vitals || [])[mb.vitals.length - 1];
const d = mb.draftVitals || {};
const hr = +(d.hr || (lv && lv.hr)) || 0, sbp = d.sbp || (lv && lv.sbp) || '', dbp = d.dbp || (lv && lv.dbp) || '', spo2 = d.spo2 || (lv && lv.spo2) || '';
const has = hr > 0 || sbp || spo2;
const cls = !has ? 'none' : (hr > 120 || hr < 50 || +sbp < 90 || +spo2 < 90) ? 'crit' : (hr > 100 || +sbp < 100 || +spo2 < 94) ? 'warn' : 'ok';
const beat = 'M0 7 L6 7 L8 5 L10 7 L13 7 L15 1 L17 13 L19 7 L23 7 L26 4.5 L29 7 L44 7';
const dur = hr > 0 ? (60 / hr) * 2 : 0;
return <span className={'feed ' + cls} title={has ? `HR ${hr || '—'} · BP ${sbp || '—'}/${dbp || '—'} · SpO₂ ${spo2 || '—'}${lv ? ' · @' + lv.time : ''}` : 'No vitals recorded'}>
<svg width="44" height="14" viewBox="0 0 44 14" aria-hidden="true">
{has && hr > 0
? <g style={{ animation: `ecgscroll ${dur.toFixed(2)}s linear infinite` }}><path d={beat} fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round"/><path d={beat} transform="translate(44 0)" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round"/></g>
: <line x1="0" y1="7" x2="44" y2="7" stroke="currentColor" strokeWidth="1" strokeDasharray={has ? 'none' : '2 3'}/>}
</svg>
<span className="feed-t">{has ? <>{hr || '—'}<i>·</i>{sbp || '—'}/{dbp || '—'}<i>·</i>{spo2 || '—'}%</> : 'no vitals'}</span>
</span>;
}
function ManTile({ mb, sel, onSelect, opts = {}, due }) {
const st = memberStatus(mb);
const tqN = Object.values(mb.tourniquets||{}).filter(t=>t.active).length;
const lv = (mb.vitals||[])[mb.vitals.length-1];
const evacC = mb.evac==='urgent'?'var(--crit)':mb.evac==='priority'?'var(--amber)':mb.evac==='routine'?'var(--op)':null;
const triC = mb.triage==='immediate'?'var(--crit)':mb.triage==='delayed'?'var(--amber)':mb.triage==='minimal'?'var(--op)':mb.triage==='expectant'?'#555':null;
return (
<div className={'man st-'+st+(sel?' sel':'')} onClick={()=>onSelect(mb.id)} data-screen-label={mb.role+' '+mb.name}>
{st==='red' && <span className="alert"></span>}
<div className="role">{mb.role}<span className="pos">P{mb.pos}</span></div>
<div className="nm">{mb.name || <span style={{color:'var(--fg-3)'}}>— unassigned —</span>}{opts.showLast4 !== false && mb.last4 && <span style={{color:'var(--fg-3)'}}> /{mb.last4}</span>}</div>
{opts.showFeed !== false && <MiniFeed mb={mb}/>}
<div className="meta">
<span className="pill blood">{mb.bloodType}</span>
{mb.triage ? <span className="pill hot" style={{background:triC}}>{mb.triage.slice(0,3)}</span> : <span className="pill">{STATUS_LABEL[st]}</span>}
{mb.evac && <span className="pill hot" style={{background:evacC, color: mb.evac==='priority'?'#1a1410':mb.evac==='routine'?'#0c1410':'#fff'}}>{mb.evac.slice(0,3)}</span>}
{tqN>0 && <span className="pill hot" style={{background:'var(--crit)'}}>TQ×{tqN}</span>}
</div>
<div className="foot">
{opts.showAmmo !== false && <><span className={'st-'+mb.ammo} title="Ammo">AMMO</span><span className={'st-'+mb.equip} title="Equipment">EQP</span></>}
<span className={due?'st-red':lv?'st-green':'st-black'} style={{flex:1.3}} title="Last vitals">{lv?lv.time:'— —'}</span>
</div>
{due && <span className="due">{due}</span>}
</div>
);
}
function TeamStrip({ team, selectedId, onSelect, opts = {}, dueMap = {}, profile, platoons, platoonId, onPlatoon, company = false, onCompany = null, onSelectIn = null, dueAll = {} }) {
const counts = STATUS_ORDER.reduce((a,k)=>({...a,[k]:0}),{});
team.forEach(mb=>counts[memberStatus(mb)]++);
const cas = team.filter(mb=>memberStatus(mb)!=='green').length;
const rank = { red:0, black:1, amber:2, green:3 };
const sortFn = (a,b)=> opts.sortBy==='status' ? (rank[memberStatus(a)]-rank[memberStatus(b)]) || a.pos-b.pos : opts.sortBy==='role' ? a.id.localeCompare(b.id) : a.pos-b.pos;
const sorted = [...team].sort(sortFn);
const squads = profile && profile.squads;
const cols = squads ? Math.max(...squads.map(s=>s.slots.length)) : team.length;
const pax = profile ? profile.paxLabel : `ODA · ${team.length} PAX`;
const [expanded, setExpanded] = React.useState({}); // sections start collapsed: names only, click to open
return (
<div className="team" data-screen-label="Team strip">
<div className="team-sum">
<div className="lbl">{pax} · <b style={{color:cas?'var(--crit)':'var(--op)'}}>{cas} CAS</b></div>
{platoons && platoons.length>1 && <div className="plt-bar">{onCompany && (()=>{ const all=platoons.flatMap(p=>p.team); const c={green:0,amber:0,red:0,black:0}; all.forEach(x=>c[memberStatus(x)]++); const pc=all.length-c.green; return <button key="__co" className={'plt co'+(company?' on':'')+(c.red?' st-red':pc?' st-amber':' st-green')} onClick={()=>onCompany(!company)} title={`Company roll-up · ${all.length} pax · ${pc} cas`}>CO<i>{pc?pc+' CAS':'UP'}</i></button>; })()}{platoons.map(p=>{ const c={green:0,amber:0,red:0,black:0}; p.team.forEach(x=>c[memberStatus(x)]++); const pc=p.team.length-c.green; return <button key={p.id} className={'plt'+(p.id===platoonId?' on':'')+(c.red?' st-red':pc?' st-amber':' st-green')} onClick={()=>onPlatoon(p.id)} title={`${p.name} · ${c.green} up · ${pc} cas`}>{p.name}<i>{pc?pc+' CAS':'UP'}</i></button>; })}</div>}
<div className="counts">
{STATUS_ORDER.map(k=><div key={k} className={'cnt st-'+k}><b>{counts[k]}</b><span>{k==='black'?'KIA':k.toUpperCase()}</span></div>)}
</div>
{opts.showFormation !== false && <><div className="lbl" style={{marginTop:2}}>Order of Movement{squads?' · by section':''}</div><Formation team={team} selectedId={selectedId} onSelect={onSelect} squads={squads} dueMap={dueMap}/></>}
</div>
{company && platoons ? <div className="team-grid squads all-closed company" style={{gridTemplateColumns:'minmax(0,1fr)'}}>
{platoons.map(p=>{ const members=p.team; const casL=members.filter(x=>memberStatus(x)!=='green').sort((a,b)=>({red:0,black:1,amber:2})[memberStatus(a)]-({red:0,black:1,amber:2})[memberStatus(b)]); const sqCas=casL.length; const sqRed=members.filter(x=>memberStatus(x)==='red').length; const sqDue=members.filter(x=>dueAll[x.id]).length; const lead=members.find(x=>/^(PL|OIC|18A|CO)$/.test(x.role))||members[0]; const medic=members.find(x=>/68W|SRM|18D|HM|Corpsman/i.test(x.role+' '+x.title)); const tqN=members.reduce((n,x)=>n+Object.values(x.tourniquets||{}).filter(t=>t.active).length,0); const urg=members.filter(x=>x.evac==='urgent').length, pri=members.filter(x=>x.evac==='priority').length, rou=members.filter(x=>x.evac==='routine').length;
return <div key={p.id} className={'sq-lbl closed co-row'+(p.id===platoonId?' cur':'')+(sqRed?' st-red':sqCas?' st-amber':' st-green')} onClick={()=>onPlatoon(p.id)} title={`Open ${p.name}`} role="button">
<span className="sq-chev">▸</span>
<span className="sq-name"><b>{p.name}</b><i>{members.length} pax · {members.length-sqCas} up{lead?' · '+lead.role+' '+(lead.name||'').split(',')[0]:''}{medic?' · '+medic.role+' '+(medic.name||'').split(',')[0]:''}</i></span>
<span className="co-evac" onClick={e=>e.stopPropagation()} title="9-Line precedence · A urgent · C priority · D routine">{urg>0&&<em className="st-red">{urg}A</em>}{pri>0&&<em className="st-amber">{pri}C</em>}{rou>0&&<em className="st-green">{rou}D</em>}{tqN>0&&<em className="st-red tq">TQ×{tqN}</em>}</span>
<span className="sq-cas" onClick={e=>e.stopPropagation()}>{casL.slice(0,4).map(x=>{ const sx=memberStatus(x); const tq=Object.values(x.tourniquets||{}).filter(t=>t.active).length; return <span key={x.id} className={'sq-c st-'+sx+(x.id===selectedId?' cur':'')+(dueAll[x.id]?' due':'')} onClick={()=>onSelectIn&&onSelectIn(p.id,x.id)} title={`${x.role} ${x.name||x.id} · ${STATUS_LABEL[sx]}${dueAll[x.id]?' · '+dueAll[x.id]:''}`}><b>{x.role}</b>{(x.name||x.id).split(',')[0]}{x.evac&&<em>{x.evac.slice(0,3)}</em>}{tq>0&&<em className="tq">TQ×{tq}</em>}<MiniFeed mb={x}/></span>; })}{casL.length>4&&<span className="sq-c more" onClick={()=>onPlatoon(p.id)}>+{casL.length-4}</span>}</span>
<span className="sq-sum">{sqRed>0&&<em className="st-red">{sqRed} urgent</em>}{sqCas-sqRed>0&&<em className="st-amber">{sqCas-sqRed} wounded</em>}{sqDue>0&&<em className="st-red due">{sqDue} due</em>}{!sqCas&&<em className="st-green">all up</em>}</span>
</div>; })}
</div>
: <div className={'team-grid'+(opts.compactStrip?' compact':'')+(squads?' squads':'')+(squads&&!squads.some((_,k)=>expanded[k])?' all-closed':'')} style={{gridTemplateColumns:`repeat(${cols},minmax(0,1fr))`}}>
{squads
? squads.map((sq,si)=>{ const members=sq.slots.map(i=>team[i]).filter(Boolean); const casL=members.filter(x=>memberStatus(x)!=='green'); const sqCas=casL.length; const sqRed=members.filter(x=>memberStatus(x)==='red').length; const open=!!expanded[si]; const sqDue=members.filter(x=>dueMap[x.id]).length; const lead=members[0]; return <React.Fragment key={sq.name}>
<div className={'sq-lbl'+(open?' open':' closed')+(sqRed?' st-red':sqCas?' st-amber':' st-green')} style={{gridColumn:`1 / span ${cols}`}} onClick={()=>setExpanded(c=>({...c,[si]:!open}))} title={open?'Click to collapse section':'Click to show members and live feeds'} role="button" aria-expanded={open}>
<span className="sq-chev">{open?'\u25be':'\u25b8'}</span>
<span className="sq-name"><b>{sq.name.split(' \u00b7 ')[0]}</b><i>{sq.name.split(' \u00b7 ')[1] ? sq.name.split(' \u00b7 ')[1]+' \u00b7 ' : ''}{members.length} pax · {members.length-sqCas} up{lead ? ' \u00b7 '+lead.role+' '+(lead.name||'').split(',')[0] : ''}</i></span>
{!open && <span className="sq-dots" onClick={e=>e.stopPropagation()}>{members.map(x=>{ const s=memberStatus(x); return <b key={x.id} className={'st-'+s+(x.id===selectedId?' cur':'')+(dueMap[x.id]?' due':'')} onClick={()=>onSelect(x.id)} title={`${x.role} ${x.name||x.id} \u00b7 ${STATUS_LABEL[s]}${dueMap[x.id]?' \u00b7 '+dueMap[x.id]:''}`}>{x.role}</b>; })}</span>}
{!open && casL.length>0 && <span className="sq-cas" onClick={e=>e.stopPropagation()}>{casL.slice(0,3).map(x=>{ const s=memberStatus(x); const tq=Object.values(x.tourniquets||{}).filter(t=>t.active).length; return <span key={x.id} className={'sq-c st-'+s+(x.id===selectedId?' cur':'')} onClick={()=>onSelect(x.id)}><b>{x.role}</b>{(x.name||x.id).split(',')[0]}{x.evac && <em>{x.evac.slice(0,3)}</em>}{tq>0 && <em className="tq">TQ×{tq}</em>}<MiniFeed mb={x}/></span>; })}{casL.length>3 && <span className="sq-c more">+{casL.length-3}</span>}</span>}
<span className="sq-sum">{sqRed>0 && <em className="st-red">{sqRed} urgent</em>}{sqCas-sqRed>0 && <em className="st-amber">{sqCas-sqRed} wounded</em>}{sqDue>0 && <em className="st-red due">{sqDue} due</em>}{!sqCas && <em className="st-green">all up</em>}</span>
</div>
{open && members.sort(sortFn).map(mb=><ManTile key={mb.id} mb={mb} sel={mb.id===selectedId} onSelect={onSelect} opts={opts} due={dueMap[mb.id]}/>)}
</React.Fragment>; })
: sorted.map(mb=><ManTile key={mb.id} mb={mb} sel={mb.id===selectedId} onSelect={onSelect} opts={opts} due={dueMap[mb.id]}/>)}
</div>}
</div>
);
}
const ODA_PROFILE = {
key: 'tccc360-oda-v1', brand: <><b>TCCC</b> · ODA TEAM VIEW · 360</>, appClass: 'oda',
unitShort: 'ODA', paxLabel: 'ODA · 12 PAX', boardTitle: 'ODA STATUS BOARD', leaderLbl: '18Z', medicLbl: '18D', leader: 'MSG T. BOUDREAUX', medic: 'SSG R. PHAM', opName: 'ENDURING SHIELD',
pax: 12, squads: null, billets: [['18A','Detachment Cdr'],['180A','Asst Det Cdr'],['18Z','Team Sergeant'],['18F','Intel Sgt'],['18B','Weapons Sgt'],['18B','Weapons Sgt'],['18C','Engineer Sgt'],['18C','Engineer Sgt'],['18D','Medical Sgt'],['18D','Medical Sgt'],['18E','Commo Sgt'],['18E','Commo Sgt']], blankUnit: '',
blankTitle: 'Blank 12-man ODA', blankDesc: 'Empty, fillable roster — 12 slots with MOS only (18A, 180A, 18Z, 18F, 2×18B, 2×18C, 2×18D, 2×18E). Enter names, last-4, blood types and weights in the casualty header.',
blankConfirm: 'Start a blank 12-man ODA? All names, blood types and casualty data will be cleared.', resetConfirm: 'Reset the whole team to the sample roster?',
teamName: 'ODA 0312', platoons: [{ id: 'ODA', name: 'ODA', team: ODA_ROSTER }], defaultPlatoon: 'ODA', defaultSelected: '18B1',
migrate: (team) => team.map(x => x.id === '18B1' && JSON.stringify(x.injuries) === '[{"view":"front","x":70,"y":130,"type":"gsw"},{"view":"front","x":132,"y":280,"type":"gsw"}]' ? { ...x, injuries: [{view:'front',x:78,y:295,type:'gsw'},{view:'front',x:130,y:200,type:'gsw'}] } : x),
};
Object.assign(window, { ODA_ROSTER, ODA_PROFILE, BLANK_MEMBER, EMPTY_TQ, memberStatus, STATUS_LABEL, STATUS_ORDER, RAG, cycle, TeamStrip });