The count stayed four. The people changed.
HTML for this example
Select the code and use your copy shortcut. Changes in this preview are temporary.
<!doctype html>
<html lang="en-US"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>The count stayed four. The people changed.</title><style>*{box-sizing:border-box}body{margin:0;background:#f3f4f2;color:#202c32;font:16px/1.5 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}main{max-width:1120px;margin:auto;padding:30px 28px 60px}header{margin-bottom:22px}.eyebrow{font-size:12px;letter-spacing:.14em;text-transform:uppercase;font-weight:750;color:#59676b}h1{font-size:clamp(25px,4vw,36px);line-height:1.15;letter-spacing:-.035em;margin:7px 0 10px}h2{font-size:21px;line-height:1.3;margin:0 0 16px}h3{font-size:17px;margin:0 0 10px}p{margin:8px 0 16px}label{display:block;font-size:14px;font-weight:650;margin:12px 0 5px}input,select,textarea,button{font:inherit}input,select,textarea{width:100%;min-width:0;border:1px solid #b6c0bf;border-radius:7px;background:#fff;color:#202c32;padding:10px 12px}textarea{resize:vertical;min-height:88px}input[type=checkbox]{width:18px;height:18px;vertical-align:middle;margin-right:8px}button{border:1px solid #b8c3c1;border-radius:7px;padding:10px 15px;background:#fff;color:#203632;font-weight:650;cursor:pointer}button.primary{background:#243f3c;color:white;border-color:#243f3c}button:hover{filter:brightness(.95)}button:disabled{opacity:.48;cursor:not-allowed}:focus-visible{outline:3px solid #b05814;outline-offset:3px}.buttons{display:flex;gap:8px;flex-wrap:wrap;margin-top:18px}.panel{background:#fff;border:1px solid #d7ddda;border-radius:12px;padding:24px}.split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.1fr);gap:22px}.row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}.result{background:#e4eee8;border:1px solid #c5d6ca;border-radius:12px;padding:28px}.big{font-size:clamp(32px,5vw,56px);font-weight:740;line-height:1.14;letter-spacing:-.04em;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.muted{color:#59686a;font-size:14px}.tag{display:inline-block;background:#edf0ed;border-radius:4px;padding:3px 8px;font-size:12px;font-weight:700}.error{color:#9d301b;font-weight:650;min-height:24px;margin-top:10px}.note{border-left:3px solid #9a794a;padding:10px 15px;background:#faf5e9;margin:18px 0}.below{margin-top:28px}.record{white-space:pre-wrap;overflow-wrap:anywhere;font:14px/1.65 ui-monospace,SFMono-Regular,Consolas,monospace;background:#f2f4f1;padding:18px;border-radius:8px}table{width:100%;border-collapse:collapse;font-size:14px}th,td{text-align:left;vertical-align:top;border-bottom:1px solid #dce1dd;padding:12px 10px;overflow-wrap:anywhere}th{color:#59686a;font-size:12px}a{color:#2c5a6a;overflow-wrap:anywhere}details{margin-top:20px}summary{cursor:pointer;font-weight:650}hr{border:0;border-top:1px solid #d6dcd6;margin:22px 0}[hidden]{display:none!important}ol,ul{padding-left:23px}.statline{display:flex;gap:20px;flex-wrap:wrap;margin:18px 0}.statline strong{display:block;font-size:24px}.inline{display:flex;align-items:center;gap:8px}.inline label{margin:0} .blank{min-height:36px;border-bottom:1px dashed #aebbb5;color:#66736d;font-size:13px;padding:8px 0}@media(max-width:720px){main{padding:22px 18px 45px}.split{grid-template-columns:1fr}.panel,.result{padding:20px}.row{gap:12px}th,td{padding:10px 6px}}@media(max-width:400px){main{padding:18px 14px 40px}.big{font-size:34px}button{padding:10px 12px}.row{grid-template-columns:1fr}.panel,.result{padding:17px}}.inline{flex-wrap:wrap}.metric{font-size:clamp(29px,4vw,44px);font-weight:750;line-height:1.2;letter-spacing:-.025em;font-variant-numeric:tabular-nums}.strip{display:flex;justify-content:space-between;align-items:center;gap:18px;flex-wrap:wrap}.caption{font-size:13px;color:#5d6b6f}.tag.warn{background:#f7e9ce;color:#795323}.tag.good{background:#e4ebe4;color:#344f3e}pre{margin:0;white-space:pre-wrap;overflow-wrap:anywhere}button[aria-pressed=true]{background:#304b51;color:white}.fieldNote{font-size:13px;color:#5e6a6d;margin:5px 0}.three{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px}.docHeader{display:flex;gap:24px;justify-content:space-between;align-items:start;flex-wrap:wrap;border-bottom:2px solid #445a58;padding-bottom:20px}.docHeader h2{margin:0}.money{font-variant-numeric:tabular-nums;white-space:nowrap}.statusBand{padding:17px 22px;border-radius:8px;background:#f5ead5;color:#654c2c;font-weight:650}.statusBand p{font-size:14px;font-weight:400;margin:5px 0 0}.blankLine{border-bottom:1px dashed #b6bfb8;min-height:38px}.evidenceRow{display:grid;grid-template-columns:1fr 1fr;gap:18px;border-top:1px solid #dce1dc;padding:18px 0}.checklabel{font-weight:400;margin:10px 0}.subtle{background:#f4f6f3}.downloadStatus{font-size:13px;min-height:22px;color:#49645a}@media(max-width:650px){.three{grid-template-columns:1fr}.evidenceRow{grid-template-columns:1fr}.metric{font-size:32px}}.movementSummary{background:#e4eceb;border:1px solid #c5d6d3;border-radius:12px;padding:26px;display:grid;grid-template-columns:1fr 1fr;gap:25px}.movementSummary .metric{margin-top:10px}.movementSummary p{font-size:14px;margin-top:12px}.rates{display:grid;grid-template-columns:1fr 1fr;gap:20px}.rates span,.rates small{display:block;font-size:13px}.rates strong{display:block;font-size:38px;line-height:1.3;margin:6px 0}.eventLine{display:grid;grid-template-columns:1fr .7fr 1fr;padding:16px 0;border-top:1px solid #d6deda;gap:12px}.idTokens{display:flex;gap:12px;flex-wrap:wrap}.idTokens span{border:1px solid #c8d4cf;padding:12px 18px;border-radius:7px;background:#f2f5f1;font:700 20px ui-monospace,Consolas,monospace}@media(max-width:760px){.movementSummary{grid-template-columns:1fr}.rates strong{font-size:32px}}@media(max-width:450px){.eventLine{grid-template-columns:1fr;gap:3px}}</style></head><body><main><header><div class="eyebrow">Fictional employee scope · September 1–30, 2026</div><h1>The count stayed four. The people changed.</h1></header>
<section class="movementSummary"><div><span class="eyebrow">Opening + hires − exits = closing</span><div class="metric" id="equation">4 + 2 − 2 = 4</div><p id="reconciliation">Closing identities reconcile with the event ledger.</p></div><div class="rates"><div><span>Separation rate</span><strong id="separation">50%</strong><small>2 exits ÷ endpoint average 4</small></div><div><span>Opening-cohort retention</span><strong id="retention">75%</strong><small>3 retained opening IDs ÷ 4</small></div></div></section>
<div class="buttons" role="group" aria-label="Movement view"><button id="opening" aria-pressed="false">Opening</button><button id="flow" aria-pressed="true">Period flow</button><button id="closing" aria-pressed="false">Closing</button><button id="cohort" aria-pressed="false">Opening cohort</button></div><section class="panel below"><h2 id="viewTitle">Four dated events, including both E05 events</h2><div id="view"></div></section>
<section class="panel below"><label for="scenario">Inspect a fictional record scenario</label><select id="scenario"><option value="complete">Complete September ledger</option><option value="missing">Missing E05 exit date</option><option value="duplicate">Duplicate hire event</option><option value="order">Exit before recorded join</option><option value="identity">Wrong closing identity, same count</option><option value="zero">Zero opening and closing headcount</option></select><div class="buttons"><button id="restore">Restore complete ledger</button></div><h3 class="below">Unresolved record exceptions</h3><div class="record" id="exceptions">None in the supplied complete fixture.</div><p class="muted">C01 is outside the declared employee scope; this is a dataset boundary, not a legal classification. No rehires/transfers are modeled. Actual organizational date and event policies need confirmation.</p></section>
<section class="panel below"><h2>Two distinct denominators</h2><p>Separation rate = period exits ÷ ((opening headcount + closing headcount) ÷ 2) × 100. The denominator is an endpoint average, not a daily average or BLS published rate.</p><p>Opening-cohort retention compares opening identities with closing identities. E06 is a later hire; E05 joined and exited during the month. Neither belongs in the retained opening cohort.</p><p class="muted">Closing ÷ opening totals and 100% minus separation rate do not compute this cohort's retention. This fixture gives no causes, morale finding, benchmark or employment recommendation.</p><a href="https://www.bls.gov/jlt/jltdef.htm">BLS stock and monthly flow definitions</a></section><footer class="muted below">Local review copy · Edits last only while this page is open.</footer></main><script data-app>
(()=>{
const $=id=>document.getElementById(id);const text=(id,v)=>$(id).textContent=String(v);const on=(id,type,fn)=>$(id).addEventListener(type,fn);const escapeHTML=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));const money=n=>'$'+(n/100).toFixed(2);const quantity=(s,integer=false)=>{s=String(s).trim();if(!(integer?/^[0-9]+$/:/^[0-9]+(?:[.][0-9]{1,2})?$/).test(s)||!Number.isFinite(+s)||+s>100000)throw Error('Enter a nonnegative '+(integer?'whole number':'number with at most two decimals')+' up to 100,000. Blank is missing.');return +s};const cents=s=>Math.round(quantity(s)*100);const download=(filename,content,type='text/plain')=>{const url=URL.createObjectURL(new Blob([content],{type})),a=document.createElement('a');a.href=url;a.download=filename;document.body.append(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),1000)};
const baseData={opening:['E01','E02','E03','E04'],closing:['E01','E03','E04','E06'],events:[{id:'J01',employee:'E05',type:'hire',date:'2026-09-05'},{id:'X01',employee:'E05',type:'exit',date:'2026-09-10'},{id:'J02',employee:'E06',type:'hire',date:'2026-09-20'},{id:'X02',employee:'E02',type:'exit',date:'2026-09-25'}]};let data=structuredClone(baseData),mode='flow';
function analyze(d){const issues=[],opening=new Set(d.opening),closing=new Set(d.closing),active=new Set(d.opening),eventIDs=new Set();if(opening.size!==d.opening.length||closing.size!==d.closing.length)issues.push('Duplicate employee ID in a snapshot.');for(const e of d.events){if(eventIDs.has(e.id))issues.push('Duplicate event ID '+e.id+'.');eventIDs.add(e.id);if(!/^2026-09-(0[1-9]|[12][0-9]|30)$/.test(e.date))issues.push(e.id+': date is missing or outside September 2026.');if(!['hire','exit'].includes(e.type))issues.push(e.id+': event type is unsupported.');if(!e.employee)issues.push(e.id+': employee ID missing.')}for(const e of [...d.events].filter(e=>/^2026-09-(0[1-9]|[12][0-9]|30)$/.test(e.date)).sort((a,b)=>a.date.localeCompare(b.date))){if(e.type==='hire'){if(active.has(e.employee))issues.push(e.id+': hire repeats an active employee.');active.add(e.employee)}if(e.type==='exit'){if(!active.has(e.employee))issues.push(e.id+': exit precedes a recorded join/opening membership.');active.delete(e.employee)}}if(active.size!==closing.size||[...active].some(x=>!closing.has(x)))issues.push('Closing identities do not match the supplied movement events.');const hires=d.events.filter(e=>e.type==='hire').length,exits=d.events.filter(e=>e.type==='exit').length,average=(opening.size+closing.size)/2,retained=[...opening].filter(x=>closing.has(x));return {issues,hires,exits,opening:[...opening],closing:[...closing],retained,average,separation:issues.length||average===0?null:exits/average*100,retention:issues.length||opening.size===0?null:retained.length/opening.size*100}}
function scenario(name){const d=structuredClone(baseData);if(name==='missing')d.events[1].date='';else if(name==='duplicate')d.events.push({...d.events[0]});else if(name==='order')d.events[1].date='2026-09-01';else if(name==='identity')d.closing=['E01','E02','E04','E06'];else if(name==='zero'){d.opening=[];d.closing=[];d.events=[]}else if(name!=='complete')throw Error('Unknown scenario');return d}
function render(){const r=analyze(data);text('equation',r.opening.length+' + '+r.hires+' − '+r.exits+' = '+(r.opening.length+r.hires-r.exits));text('reconciliation',r.issues.length?'Recorded closing count '+r.closing.length+'; final reconciliation is unconfirmed.':'Closing identities reconcile with the event ledger.');text('separation',r.separation===null?(r.issues.length?'Unconfirmed':'Not calculable'):r.separation+'%');text('retention',r.retention===null?(r.issues.length?'Unconfirmed':'Not calculable'):r.retention+'%');const small=$('separation').nextElementSibling;small.textContent=r.exits+' exits ÷ endpoint average '+r.average;$('retention').nextElementSibling.textContent=r.retained.length+' retained opening IDs ÷ '+r.opening.length;text('exceptions',r.issues.length?r.issues.join('\n'):'None in the supplied fixture.'+(r.average===0?' Zero average headcount: separation rate is not calculable.':''));for(const id of ['opening','flow','closing','cohort'])$(id).setAttribute('aria-pressed',String(mode===id));if(mode==='flow'){text('viewTitle','Period flow · every supplied hire and exit');$('view').innerHTML=data.events.length?data.events.map(e=>'<div class="eventLine"><strong>'+escapeHTML(e.id)+' · '+escapeHTML(e.employee)+'</strong><span>'+escapeHTML(e.type)+'</span><span>'+escapeHTML(e.date||'DATE MISSING')+'</span></div>').join(''):'<p>No supplied events.</p>'}else{const list=mode==='opening'?r.opening:mode==='closing'?r.closing:r.retained;text('viewTitle',mode==='cohort'?'Retained opening cohort · later hires excluded':mode==='opening'?'Opening membership · immediately before September 1':'Closing membership · after September 30');$('view').innerHTML='<div class="idTokens">'+list.map(id=>'<span>'+id+'</span>').join('')+'</div>'+(mode==='cohort'?'<p>Opening denominator: '+r.opening.join(', ')+'.<br>Retained IDs: '+(r.retained.join(', ')||'none')+'.</p>':'');if(!list.length)$('view').innerHTML='<p>No IDs in this supplied set.</p>'}}
for(const id of ['opening','flow','closing','cohort'])on(id,'click',()=>{mode=id;render()});on('scenario','change',()=>{data=scenario($('scenario').value);render()});on('restore','click',()=>{$('scenario').value='complete';data=scenario('complete');render()});window.app={analyze,scenario,baseData};render();
})();
</script></body></html>