First replies • support review — Separate elapsed time to first human public reply from an unreplied ticket's current wait and from resolution; show the denominator behind…

HTML for this example

Select the code and use your copy shortcut. Changes in this preview are temporary.

<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>First replies • support review</title><style>*{box-sizing:border-box}body{margin:0;background:var(--bg,#f3f7fa);color:var(--ink,#203342);font:16px/1.55 system-ui,Segoe UI,sans-serif}main{max-width:1100px;margin:auto;padding:28px 30px 50px}h1{font-size:clamp(27px,3.5vw,40px);line-height:1.18;letter-spacing:-.035em;margin:8px 0 12px}h2{font-size:21px;line-height:1.35;margin:0 0 16px}h3{font-size:18px}.eyebrow{color:var(--accent,#176783);font-size:12px;letter-spacing:.12em;font-weight:800;text-transform:uppercase}.intro,.muted,small{color:#516272}.intro{margin:0 0 24px}.panel{padding:24px;border:1px solid var(--line,#d6e0e7);border-radius:12px;background:white;margin:18px 0}.grid{display:grid;grid-template-columns:1fr 1fr;gap:22px}.grid>*{min-width:0}.controls{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:16px 0}button,input,textarea,select{font:inherit}button{border:1px solid var(--accent,#176783);background:var(--accent,#176783);color:white;padding:10px 16px;min-height:44px;border-radius:7px;font-weight:700;cursor:pointer}button.secondary{background:white;color:var(--accent,#176783)}button[aria-pressed=true]{background:var(--tint,#e3f2f8);color:var(--ink,#203342);box-shadow:inset 0 0 0 2px var(--accent,#176783)}button:hover{filter:brightness(.96)}:focus-visible{outline:3px solid #d56b00;outline-offset:3px}label{display:block;margin:12px 0 5px;font-size:14px;font-weight:650}input:not([type=checkbox]),select,textarea{width:100%;padding:10px 12px;min-height:44px;border:1px solid #94a5b2;background:white;color:inherit;border-radius:7px}textarea{resize:vertical;min-height:120px}input[type=checkbox]{width:20px;height:20px;margin-right:9px}label.inline{display:flex;align-items:center;font-weight:500;gap:7px}.result{padding:24px;background:var(--tint,#e3f2f8);border:1px solid var(--line,#c8dce7);border-radius:12px}.big{font-size:clamp(30px,5vw,48px);font-weight:780;line-height:1.25;letter-spacing:-.035em;margin:10px 0}.trace{font:15px/1.7 ui-monospace,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}.error{color:#a12820;font-weight:650;min-height:24px}.table-wrap{overflow-x:auto}table{border-collapse:collapse;width:100%;font-size:15px}th,td{text-align:left;border-bottom:1px solid var(--line,#d6e0e7);padding:12px 9px;vertical-align:top}th{font-size:12px;text-transform:uppercase;letter-spacing:.03em;color:#516272}code,pre,.code{font:14px/1.65 ui-monospace,Consolas,monospace}pre{white-space:pre-wrap;overflow-wrap:anywhere;background:var(--tint,#eef3f7);padding:16px;border-radius:6px}details{border-top:1px solid var(--line,#d6e0e7);margin:24px 0;padding-top:16px}summary{cursor:pointer;font-weight:700;min-height:44px}footer{border-top:1px solid var(--line,#d6e0e7);margin-top:26px;padding-top:16px;font-size:13px;color:#516272}.bar{height:16px;background:#dce4ea;border-radius:20px;overflow:hidden;margin:12px 0}.bar span{display:block;height:100%;background:var(--accent,#176783)}[hidden]{display:none!important}@media(max-width:720px){main{padding:20px}.grid{grid-template-columns:1fr;gap:12px}.panel,.result{padding:20px}}@media(max-width:400px){main{padding:16px}.panel,.result{padding:16px}button{padding:10px 13px}} .metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin:20px 0}.metric{padding:20px;background:white;border:1px solid var(--line,#d6e0e7);border-radius:9px}.metric h2{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#516272;margin:0}.metric .big{font-size:clamp(25px,4vw,39px);margin:8px 0}.metric small{font-size:12px;display:block}.badge{display:inline-block;padding:4px 10px;background:var(--tint,#e3f2f8);border-radius:20px;font-size:13px;font-weight:700}.paper{background:white;padding:26px;border:1px solid var(--line,#d6e0e7)}.feedback{padding:18px;background:var(--tint,#e3f2f8);border-left:4px solid var(--accent,#176783)}.preview{white-space:pre-wrap;overflow-wrap:anywhere}.fact-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}.fact-grid article{padding:20px;background:white;border:1px solid var(--line,#d6e0e7)}.record-row{display:grid;grid-template-columns:70px 1fr 1fr 1fr;gap:12px;align-items:start;padding:16px 0;border-bottom:1px solid var(--line,#d6e0e7)}.record-row small{display:block}.record-row b{display:block}.export textarea{min-height:170px}@media(max-width:720px){.metrics{grid-template-columns:1fr 1fr}.fact-grid{grid-template-columns:1fr}.record-row{grid-template-columns:55px 1fr 1fr}.record-row .row-extra{grid-column:2/4}}@media(max-width:400px){.metrics{gap:10px}.metric{padding:14px}.metric .big{font-size:29px}.metric h2{font-size:11px}.paper{padding:18px}.record-row{gap:9px;font-size:14px}}
body{--accent:#285e91;--bg:#f2f5f9;--tint:#e4edf8;--ink:#24374d;--line:#d2ddea}.snapshot-line{display:flex;gap:14px;align-items:center;flex-wrap:wrap;color:#516272;font-size:13px}.metric.urgent{background:#fff4da;border-color:#e1ca91}.metric.urgent .big{color:#916314}.duration-rail{height:7px;background:#e6edf5;margin-top:8px;border-radius:8px;overflow:hidden}.duration-rail span{display:block;height:100%;background:#4a82b3}.panel{border-radius:9px}#scope-summary{font-size:22px}</style></head><body><main>
<header><span class="eyebrow">Email service review / October 13, 2026</span><h1>Separate the reply from the wait.</h1><p class="intro">Calendar minutes from ticket creation to the first human public reply.</p></header>
<div class="snapshot-line"><span class="badge" id="snapshot-label">Snapshot · 12:00 UTC</span><span>Four fictional tickets · no customer identities</span></div>
<section class="metrics" aria-label="Four distinct support measures"><article class="metric"><h2>Mean first reply</h2><p class="big" id="mean">23.33 min</p><small id="mean-basis">70 ÷ 3 · T1, T2, T4</small></article><article class="metric"><h2>Median first reply</h2><p class="big" id="median">20 min</p><small>Completed human replies only</small></article><article class="metric urgent"><h2>Awaiting first reply</h2><p class="big" id="awaiting">1 ticket</p><small id="wait-basis">T3 · waiting 120 min</small></article><article class="metric"><h2>Unresolved status</h2><p class="big" id="unresolved">3 tickets</p><small id="unresolved-basis">T1, T2, T3</small></article></section>
<nav class="controls" aria-label="Ticket scope"><button type="button" id="completed" class="secondary">Completed replies</button><button type="button" id="waiting" class="secondary">Awaiting first reply</button><button type="button" id="open" class="secondary">Unresolved</button><button type="button" id="all" class="secondary">All tickets</button></nav>
<section class="panel"><span class="eyebrow" id="scope-label"></span><h2 id="scope-summary">Completed first human replies · n = 3</h2><div id="records"></div><p id="scope-note" class="muted"></p></section>
<div class="grid"><section class="panel"><h2>Inspect a ticket’s endpoints</h2><div class="controls"><button type="button" id="ticket-T1" class="secondary">T1</button><button type="button" id="ticket-T2" class="secondary">T2</button><button type="button" id="ticket-T3" class="secondary">T3</button><button type="button" id="ticket-T4" class="secondary">T4</button></div><p id="detail" class="feedback" aria-live="polite"></p></section><section class="panel"><h2>Waiting-clock scenario</h2><label for="snapshot">Same-date snapshot time (UTC, HH:MM)</label><input id="snapshot" type="text" value="12:00" ><div class="controls"><button type="button" id="recalculate">Recalculate snapshot</button><button type="button" id="reset" class="secondary">Restore 12:00</button></div><p id="error" class="error" role="alert"></p><small>Source statuses and recorded events stay unchanged. A later clock increases an unfinished wait; it does not create a reply.</small></section></div>
<details><summary>Calculation trace and boundaries</summary><p>T1: 09:00 → 09:20 = 20 minutes. T2: 09:10 → 09:50 = 40 minutes; its 09:11 bot event does not stop this defined human-reply timer. T4: 10:30 → 10:40 = 10 minutes; its 10:45 solved event is a separate 15-minute endpoint.</p><p>Completed mean = (20 + 40 + 10) / 3; median of 10, 20, 40 = 20. T3’s missing first reply is not zero and never enters that mean. Raw arithmetic is retained; mean display rounds to two decimals.</p></details>
<footer>Original sanitized HTML snapshot, not a ticket-system connection or workbook export. No replies, status changes, live refresh, business-hours/SLA evaluation, agent ranking, or exact platform-metric parity is claimed. Maintained support records remain authoritative.</footer>
</main><script>const $=id=>document.getElementById(id);const on=(id,e,fn)=>$(id).addEventListener(e,fn);const txt=(id,v)=>$(id).textContent=v;const esc=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));const positive=x=>String(x).trim()!==''&&Number.isFinite(Number(x))&&Number(x)>0;const num=x=>Number(x.toPrecision(10)).toString();function enter(id,fn){on(id,'keydown',e=>{if(e.key==='Enter'&&!e.isComposing&&e.keyCode!==229){e.preventDefault();fn()}})}function selectText(id){$(id).focus();$(id).select()}
function clockMinute(s){if(!/^(?:[01]\d|2[0-3]):[0-5]\d$/.test(s))throw Error('Use a valid 24-hour time as HH:MM.');const [h,m]=s.split(':').map(Number);return h*60+m}
function whole(s,label){if(!/^\d+$/.test(String(s))||!Number.isSafeInteger(Number(s)))throw Error(label+' must be a nonnegative whole number.');return Number(s)}
function isoDay(s){if(!/^\d{4}-\d{2}-\d{2}$/.test(s))throw Error('Use a real calendar date as YYYY-MM-DD.');const d=new Date(s+'T00:00:00Z');if(!Number.isFinite(d.getTime())||d.toISOString().slice(0,10)!==s)throw Error('Use a real calendar date as YYYY-MM-DD.');return d.getTime()/86400000}


const tickets=[{id:'T1',created:'09:00',reply:'09:20',status:'Open'},{id:'T2',created:'09:10',reply:'09:50',bot:'09:11',status:'Pending customer'},{id:'T3',created:'10:00',reply:null,status:'New'},{id:'T4',created:'10:30',reply:'10:40',solved:'10:45',status:'Solved'}];let scope='completed',selected='T3',current=null;
function supportStats(rows,snapshot){const now=clockMinute(snapshot);const completed=[],waiting=[],unresolved=[];for(const t of rows){const created=clockMinute(t.created);if(created>now)throw Error('Snapshot precedes a supplied creation event.');if(t.reply!==null){const reply=clockMinute(t.reply);if(reply<created||reply>now)throw Error('Snapshot or reply sequence conflicts with supplied events.');completed.push({...t,duration:reply-created})}else waiting.push({...t,wait:now-created});if(t.solved&&(clockMinute(t.solved)>now||clockMinute(t.solved)<created))throw Error('Snapshot conflicts with the supplied solved event.');if(t.status!=='Solved')unresolved.push(t)}const durations=completed.map(t=>t.duration).sort((a,b)=>a-b),sum=durations.reduce((a,b)=>a+b,0),n=durations.length;return {completed,waiting,unresolved,sum,mean:n?sum/n:null,median:n?(n%2?durations[(n-1)/2]:(durations[n/2-1]+durations[n/2])/2):null}}
function inspect(){if(!current){txt('detail','No current validated snapshot.');return}const t=tickets.find(t=>t.id===selected);let text=t.id+' · '+t.status+'. Created '+t.created+' UTC. ';if(t.reply)text+='First human public reply '+t.reply+'; '+(clockMinute(t.reply)-clockMinute(t.created))+' calendar minutes.';else text+='No first human reply recorded; waiting '+current.waiting.find(w=>w.id===t.id).wait+' calendar minutes at '+$('snapshot').value+' UTC. Excluded from the completed-reply mean.';if(t.bot)text+=' Bot event '+t.bot+' is a different event.';if(t.solved)text+=' Solved '+t.solved+'; creation-to-solved '+(clockMinute(t.solved)-clockMinute(t.created))+' minutes, not first reply.';txt('detail',text);for(const x of tickets)$('ticket-'+x.id).setAttribute('aria-pressed',String(x.id===selected))}
function render(){if(!current)return;const rows=scope==='completed'?current.completed:scope==='waiting'?current.waiting:scope==='open'?current.unresolved:tickets;txt('scope-label',{completed:'Completed endpoint evidence',waiting:'Unfinished waits',open:'Status evidence',all:'Source record ledger'}[scope]);txt('scope-summary',{completed:'Completed first human replies',waiting:'Awaiting first human reply',open:'Unresolved statuses',all:'All supplied tickets'}[scope]+' · n = '+rows.length);$('records').innerHTML=rows.length?rows.map(t=>{const duration=t.reply?clockMinute(t.reply)-clockMinute(t.created):null,wait=duration===null?current.waiting.find(x=>x.id===t.id).wait:null;return '<article class="record-row"><b>'+t.id+'</b><div><small>Created</small><b>'+t.created+' UTC</b><small>'+esc(t.status)+'</small></div><div><small>First human reply</small><b>'+(t.reply?t.reply+' UTC':'Not recorded')+'</b></div><div class="row-extra"><small>'+(duration===null?'Unfinished wait':'Completed first reply')+'</small><b>'+(duration===null?wait:duration)+' min</b>'+(duration===null?'':'<div class="duration-rail"><span style="width:'+(duration/40*100)+'%"></span></div>')+'</div></article>'}).join(''):'<p>No records in this scope.</p>';txt('scope-note',scope==='waiting'?'Missing reply is not a zero-minute reply. These records are excluded from the completed mean.':scope==='open'?'An unresolved ticket may already have a human reply; status and reply completion are different fields.':'IDs and event endpoints determine inclusion; no business-time or SLA rule is supplied.');for(const s of ['completed','waiting','open','all'])$(s).setAttribute('aria-pressed',String(s===scope));inspect()}
function pending(){current=null;for(const n of ['mean','median','awaiting','unresolved'])txt(n,'Pending');for(const n of ['mean-basis','wait-basis','unresolved-basis','scope-note'])txt(n,'');txt('scope-summary','Snapshot input changed');$('records').innerHTML='';txt('detail','Recalculate the current snapshot.');txt('error','')}
function calculate(){try{current=supportStats(tickets,$('snapshot').value);txt('snapshot-label','Snapshot · October 13, 2026 · '+$('snapshot').value+' UTC');txt('mean',current.mean===null?'Not available':current.mean.toFixed(2)+' min');txt('median',current.median===null?'Not available':current.median+' min');txt('mean-basis',current.sum+' ÷ '+current.completed.length+' · '+current.completed.map(t=>t.id).join(', '));txt('awaiting',current.waiting.length+' '+(current.waiting.length===1?'ticket':'tickets'));txt('wait-basis',current.waiting.map(t=>t.id+' · waiting '+t.wait+' min').join('; ')||'No unfinished waits');txt('unresolved',current.unresolved.length+' tickets');txt('unresolved-basis',current.unresolved.map(t=>t.id).join(', ')||'None');txt('error','');render()}catch(e){pending();txt('scope-summary','No accepted snapshot');txt('snapshot-label','Snapshot not validated');txt('error',e.message)}}
for(const s of ['completed','waiting','open','all'])on(s,'click',()=>{scope=s;render()});for(const t of tickets)on('ticket-'+t.id,'click',()=>{selected=t.id;inspect()});on('snapshot','input',pending);on('recalculate','click',calculate);enter('snapshot',calculate);on('reset','click',()=>{$('snapshot').value='12:00';scope='completed';selected='T3';calculate()});calculate();
</script></body></html>