;(function () {
const { useState: useHS, useEffect: useHE, useRef: useHR } = React;
const PHASES = {
P:   { title: 'Atrial depolarization',     desc: 'SA node fires · impulse spreads across both atria' },
PR:  { title: 'AV nodal delay',            desc: 'Impulse held at the AV node while the atria empty' },
QRS: { title: 'Ventricular depolarization', desc: 'His bundle → bundle branches → Purkinje fibres' },
ST:  { title: 'Plateau',                   desc: 'Ventricles fully depolarized · contracting' },
T:   { title: 'Ventricular repolarization', desc: 'Ventricles reset for the next beat' },
TP:  { title: 'Electrical diastole',       desc: 'Heart at rest · chambers filling' },
};
const PHASE_ORDER = ['P', 'PR', 'QRS', 'ST', 'T', 'TP'];
function usePhase(hostRef, hr) {
const [phase, setPhase] = useHS('TP');
const barRef = useHR(null);
useHE(() => {
let raf, last = null;
const durs = window.PQRST_COMPUTE(hr);
const cum = []; let acc = 0;
for (const id of PHASE_ORDER) { acc += durs[id === 'PR' ? 'PRseg' : id]; cum.push([id, acc]); }
const loop = () => {
raf = requestAnimationFrame(loop);
const el = hostRef.current && hostRef.current.querySelector('.pqrst-scroller');
const a = el && el.getAnimations && el.getAnimations()[0];
if (!a || a.currentTime == null) return;
const dur = a.effect.getComputedTiming().duration || 1;
const tau = ((a.currentTime % dur) + dur) % dur / dur;
const tMs = tau * durs.RR;
let id = 'TP', start = 0;
for (const [pid, end] of cum) { if (tMs < end) { id = pid; break; } start = end; }
const seg = cum.find(c => c[0] === id);
const frac = seg ? (tMs - start) / (seg[1] - start) : 0;
if (barRef.current) barRef.current.style.setProperty('--frac', frac.toFixed(3));
if (id !== last) { last = id; setPhase(id); }
};
loop();
return () => cancelAnimationFrame(raf);
}, [hr]);
return [phase, barRef];
}
const LIMB = {
I:   { deg: 0,    pos: 'LA', neg: 'RA', note: '+LA · −RA' },
II:  { deg: 60,   pos: 'LL', neg: 'RA', note: '+LL · −RA' },
III: { deg: 120,  pos: 'LL', neg: 'LA', note: '+LL · −LA' },
aVR: { deg: -150, pos: 'RA', neg: null, note: '+RA vs. LA+LL' },
aVL: { deg: -30,  pos: 'LA', neg: null, note: '+LA vs. RA+LL' },
aVF: { deg: 90,   pos: 'LL', neg: null, note: '+LL vs. RA+LA' },
};
const LIMB_XY = { RA: [46, 62], LA: [174, 62], LL: [150, 214] };
const CHEST = {
V1: { xy: [92, 60],  note: '4th ICS · right sternal border' },
V2: { xy: [124, 58], note: '4th ICS · left sternal border' },
V3: { xy: [152, 68], note: 'Midway between V2 and V4' },
V4: { xy: [175, 86], note: '5th ICS · midclavicular line' },
V5: { xy: [194, 110], note: 'Anterior axillary line · level of V4' },
V6: { xy: [204, 138], note: 'Midaxillary line · level of V4' },
};
const HC = [118, 124]; // heart centre, frontal
const TC = [128, 120]; // heart centre, transverse
function LeadView({ lead }) {
const limb = LIMB[lead];
if (limb) {
const r = 78, th = limb.deg * Math.PI / 180;
const ex = HC[0] + r * Math.cos(th), ey = HC[1] + r * Math.sin(th);
const bx = HC[0] - r * 0.55 * Math.cos(th), by = HC[1] - r * 0.55 * Math.sin(th);
return (
<svg className="heart leadview" viewBox="0 0 220 240" role="img" aria-label={'Lead ' + lead + ' axis'}>
<path className="torso" d="M78,22 h64 v16 c30,6 44,20 46,46 v128 h-156 v-128 c2,-26 16,-40 46,-46 z" />
<g className="hex">
{[0, 30, 60, 90, 120, 150].map(d => { const t = d * Math.PI / 180; return <line key={d} x1={HC[0] - r * Math.cos(t)} y1={HC[1] - r * Math.sin(t)} x2={HC[0] + r * Math.cos(t)} y2={HC[1] + r * Math.sin(t)} />; })}
<circle cx={HC[0]} cy={HC[1]} r={r} />
</g>
<path className="mini-heart" d="M104,108 c-6,-10 -20,-6 -18,6 c2,10 14,18 22,26 c8,-8 20,-16 22,-26 c2,-12 -12,-16 -18,-6 z" />
<line className="axis" x1={bx} y1={by} x2={ex} y2={ey} markerEnd="url(#lv-arrow)" />
<defs><marker id="lv-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,0 L10,5 L0,10 z" /></marker></defs>
{Object.entries(LIMB_XY).map(([k, [x, y]]) => (
<g key={k} className={'elec' + (k === limb.pos ? ' pos' : k === limb.neg ? ' neg' : '')}>
<circle cx={x} cy={y} r="8" /><text x={x} y={y + 3.5}>{k}</text>
{k === limb.pos && <text className="sign" x={x + 11} y={y - 6}>+</text>}
{k === limb.neg && <text className="sign" x={x + 11} y={y - 6}>−</text>}
</g>
))}
<text className="deg" x="110" y="230">{(limb.deg > 0 ? '+' : '') + limb.deg + '°'} · frontal plane</text>
</svg>
);
}
const c = CHEST[lead];
if (!c) return null;
return (
<svg className="heart leadview" viewBox="0 0 220 240" role="img" aria-label={'Lead ' + lead + ' electrode'}>
<ellipse className="torso" cx="110" cy="128" rx="100" ry="78" />
<ellipse className="spine" cx="110" cy="192" rx="9" ry="7" />
<ellipse className="lung" cx="62" cy="130" rx="34" ry="52" />
<ellipse className="lung" cx="170" cy="140" rx="26" ry="46" />
<ellipse className="mini-heart" cx={TC[0]} cy={TC[1]} rx="34" ry="26" transform={`rotate(-30 ${TC[0]} ${TC[1]})`} />
<line className="axis" x1={TC[0]} y1={TC[1]} x2={c.xy[0]} y2={c.xy[1]} markerEnd="url(#lv-arrow2)" />
<defs><marker id="lv-arrow2" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,0 L10,5 L0,10 z" /></marker></defs>
{Object.entries(CHEST).map(([k, v]) => (
<g key={k} className={'elec' + (k === lead ? ' pos' : '')}>
<circle cx={v.xy[0]} cy={v.xy[1]} r="8" /><text x={v.xy[0]} y={v.xy[1] + 3.5}>{k}</text>
</g>
))}
<text className="deg" x="14" y="36" textAnchor="start">anterior</text>
<text className="deg" x="14" y="222" textAnchor="start">posterior</text>
<text className="deg" x="196" y="232" textAnchor="end">patient L →</text>
</svg>
);
}
function HeartWindow({ hostRef, hr, color, lead }) {
const [phase, barRef] = usePhase(hostRef, hr);
const [tab, setTab] = useHS('cond');
const first = useHR(true);
useHE(() => {
if (first.current) { first.current = false; return; }
if (lead) setTab('lead');
}, [lead]);
const showLead = !!lead && tab === 'lead';
const info = PHASES[phase];
const lv = window.PQRST_LEADS[lead];
const note = LIMB[lead]?.note || CHEST[lead]?.note;
return (
<div className="heart-win" data-phase={phase} style={{ '--h-active': color }}>
<div className="hw-head">
<div className="hw-tabs">
<button className={tab === 'cond' ? 'on' : ''} onClick={() => setTab('cond')}>Conduction</button>
{lead && <button className={tab === 'lead' ? 'on' : ''} onClick={() => setTab('lead')}>Lead {lead}</button>}
</div>
{!showLead && <span className="hw-phase">{phase}</span>}
</div>
{showLead ? <LeadView lead={lead} /> : (
<svg className="heart" viewBox="0 0 220 240" role="img" aria-label={info.title}>
<g className="atria">
<ellipse className="chamber" cx="68" cy="74" rx="38" ry="33" />
<ellipse className="chamber" cx="146" cy="72" rx="38" ry="31" />
<text x="68" y="78" className="lbl">RA</text>
<text x="146" y="76" className="lbl">LA</text>
</g>
<g className="ventricles">
<path className="chamber" d="M36,104 C38,160 84,196 112,222 L112,104 Z" />
<path className="chamber" d="M112,104 L112,222 C142,196 188,160 188,104 Z" />
<text x="74" y="150" className="lbl">RV</text>
<text x="150" y="150" className="lbl">LV</text>
</g>
<g className="cond">
<path className="tract" d="M56,50 C70,70 92,86 106,94" />
<path className="tract" d="M56,50 C50,70 70,92 104,98" />
<path className="his" d="M110,102 L112,126" />
<path className="rbb" d="M112,126 C108,150 100,176 92,196" />
<path className="lbb" d="M112,126 C124,142 138,166 148,192" />
<path className="purk" d="M92,196 C84,190 76,180 70,168 M92,196 C98,190 104,186 108,182 M148,192 C156,182 166,170 172,156 M148,192 C140,186 132,182 124,178 M140,168 C132,164 126,160 120,150" />
<circle className="node sa" cx="56" cy="50" r="5" />
<circle className="node av" cx="108" cy="99" r="5" />
</g>
<text x="46" y="38" className="tag">SA</text>
<text x="118" y="96" className="tag">AV</text>
</svg>
)}
{showLead ? (
<div className="hw-cap">
<div className="hw-title">{lv?.view}</div>
<div className="hw-desc">{note}</div>
</div>
) : (
<div className="hw-cap">
<div className="hw-title">{info.title}</div>
<div className="hw-desc">{info.desc}</div>
<div className="hw-bar" ref={barRef}><span></span></div>
<div className="hw-steps">
{PHASE_ORDER.map(p => <span key={p} className={p === phase ? 'on' : ''}>{p}</span>)}
</div>
</div>
)}
</div>
);
}
window.HeartWindow = HeartWindow;
})();