| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Digoxin Dosing Demo</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600;9..144,700&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@500&display=swap" rel="stylesheet"> |
| <style> |
| :root{ |
| --bg:#0f1417; --panel:#161d22; --panel-2:#1d262c; --line:#2a363d; |
| --ink:#e8eef0; --ink-soft:#9fb0b6; --accent:#3fb7a4; --accent-deep:#2a8a7c; |
| --warn:#e0a03f; --danger:#d96b5b; --ok:#5cc08e; |
| } |
| *{box-sizing:border-box} |
| body{ |
| margin:0; background:var(--bg); |
| background-image:radial-gradient(circle at 15% 0%,rgba(63,183,164,.07),transparent 45%); |
| color:var(--ink); font-family:"IBM Plex Sans",sans-serif; line-height:1.5; |
| padding:32px 16px; |
| } |
| .wrap{max-width:880px;margin:0 auto} |
| header{margin-bottom:24px} |
| .kicker{font-family:"IBM Plex Mono",monospace;font-size:12px;letter-spacing:.18em; |
| text-transform:uppercase;color:var(--accent)} |
| h1{font-family:"Fraunces",serif;font-weight:600;font-size:38px;margin:.15em 0 .1em;letter-spacing:-.01em} |
| .sub{color:var(--ink-soft);font-size:15px;max-width:60ch} |
| .demo-flag{display:inline-block;margin-top:12px;background:rgba(217,107,91,.13); |
| border:1px solid rgba(217,107,91,.4);color:var(--danger); |
| font-size:12px;font-family:"IBM Plex Mono",monospace;padding:6px 11px;border-radius:6px} |
| .grid{display:grid;grid-template-columns:1fr 1fr;gap:14px} |
| @media(max-width:680px){.grid{grid-template-columns:1fr}} |
| .card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:22px} |
| .card h2{font-family:"Fraunces",serif;font-weight:600;font-size:20px;margin:0 0 16px} |
| label{display:block;font-size:13px;color:var(--ink-soft);margin:0 0 5px;font-weight:500} |
| .field{margin-bottom:14px} |
| input,select{width:100%;background:var(--panel-2);border:1px solid var(--line); |
| color:var(--ink);border-radius:8px;padding:10px 12px;font-size:15px;font-family:inherit} |
| input:focus,select:focus{outline:none;border-color:var(--accent)} |
| .row2{display:grid;grid-template-columns:1fr 1fr;gap:10px} |
| .seg{display:flex;gap:6px;flex-wrap:wrap} |
| .seg button{flex:1;min-width:64px;background:var(--panel-2);border:1px solid var(--line); |
| color:var(--ink-soft);padding:9px 6px;border-radius:8px;cursor:pointer;font-size:13px;font-family:inherit} |
| .seg button.on{background:var(--accent-deep);border-color:var(--accent);color:#fff} |
| .calc{width:100%;margin-top:6px;background:var(--accent);color:#06201c;border:none; |
| padding:14px;border-radius:10px;font-size:16px;font-weight:600;cursor:pointer;font-family:inherit; |
| letter-spacing:.02em} |
| .calc:hover{background:#4fd0bb} |
| #out{margin-top:18px} |
| .result-head{font-family:"Fraunces",serif;font-size:15px;color:var(--ink-soft);margin-bottom:4px} |
| .dose-big{font-family:"Fraunces",serif;font-weight:700;font-size:34px;color:var(--accent);line-height:1.1} |
| .dose-unit{font-size:16px;color:var(--ink-soft);font-weight:400} |
| .block{border-top:1px solid var(--line);padding:16px 0} |
| .block:first-child{border-top:none;padding-top:0} |
| .pk{font-family:"IBM Plex Mono",monospace;font-size:12.5px;color:var(--ink-soft); |
| background:var(--panel-2);padding:12px;border-radius:8px;margin-top:10px;white-space:pre-wrap} |
| .note{display:flex;gap:9px;align-items:flex-start;font-size:13.5px;margin:7px 0;color:var(--ink)} |
| .note .dot{flex:none;width:7px;height:7px;border-radius:50%;margin-top:7px;background:var(--accent)} |
| .note.warn .dot{background:var(--warn)} .note.danger .dot{background:var(--danger)} |
| .placeholder{color:var(--ink-soft);font-size:14px;text-align:center;padding:30px 10px} |
| footer{margin-top:26px;font-size:12px;color:var(--ink-soft);line-height:1.7} |
| footer code{font-family:"IBM Plex Mono",monospace;color:var(--accent)} |
| </style> |
| </head> |
| <body> |
| <div class="wrap"> |
| <header> |
| <div class="kicker">Pharmacokinetic Demo</div> |
| <h1>Digoxin Dosing</h1> |
| <p class="sub">Estimates a steady-state maintenance dose (Jusko / Koup-Jusko 1-compartment model) and a weight-based loading dose, with administration notes.</p> |
| <div class="demo-flag">DEMONSTRATION ONLY — NOT FOR CLINICAL USE</div> |
| </header> |
|
|
| <div class="grid"> |
| <div class="card"> |
| <h2>Patient</h2> |
| <div class="row2"> |
| <div class="field"><label>Age (years)</label><input id="age" type="number" value="68"></div> |
| <div class="field"><label>Weight (kg)</label><input id="wt" type="number" value="80"></div> |
| </div> |
| <div class="row2"> |
| <div class="field"><label>Height (cm)</label><input id="ht" type="number" value="170"></div> |
| <div class="field"><label>S. creatinine (mg/dL)</label><input id="scr" type="number" step="0.1" value="1.0"></div> |
| </div> |
| <div class="field"> |
| <label>Sex</label> |
| <div class="seg" id="sex"><button data-v="m" class="on">Male</button><button data-v="f">Female</button></div> |
| </div> |
| <div class="field"> |
| <label>Indication</label> |
| <div class="seg" id="ind"><button data-v="hf" class="on">Heart failure</button><button data-v="af">Atrial fib.</button><button data-v="both">Both</button></div> |
| </div> |
| <div class="field"> |
| <label>Dosage form</label> |
| <div class="seg" id="form"><button data-v="tab" class="on">Tablet</button><button data-v="iv">IV</button><button data-v="elx">Elixir</button></div> |
| </div> |
| <div class="field"> |
| <label>Target serum level (ng/mL)</label> |
| <input id="target" type="number" step="0.1" value="0.7"> |
| </div> |
| <button class="calc" onclick="calc()">Calculate dose</button> |
| </div> |
|
|
| <div class="card"> |
| <h2>Result</h2> |
| <div id="out"><div class="placeholder">Enter patient data and press Calculate.</div></div> |
| </div> |
| </div> |
|
|
| <footer> |
| Maintenance: Jusko Vd = 226 + [(298·CrCl)/(29.1+CrCl)], steady-state dose = (Css·Cl·τ)/F. CrCl by Cockcroft-Gault; lean weight by Devine. Bioavailability F: tablet 0.75, IV 1.0, elixir 0.8. Loading: weight-based (PO ~10 mcg/kg, IV ~8–12 mcg/kg lean weight). Therapeutic range 0.5–2 ng/mL; ≤0.9 ng/mL preferred.<br> |
| To host: create a HuggingFace Space (SDK = <code>Static</code>) and upload this file as <code>index.html</code>.<br> |
| Not valid for dialysis, acute/unstable renal function, or interacting drugs (amiodarone, verapamil, quinidine, macrolides). Demonstration only. |
| </footer> |
| </div> |
|
|
| <script> |
| const pick=(id)=>{const el=document.getElementById(id); |
| el.querySelectorAll('button').forEach(b=>b.onclick=()=>{ |
| el.querySelectorAll('button').forEach(x=>x.classList.remove('on'));b.classList.add('on');});}; |
| ['sex','ind','form'].forEach(pick); |
| const sel=(id)=>document.getElementById(id).querySelector('.on').dataset.v; |
| const round=(n,d=4)=>Math.round(n*Math.pow(10,d))/Math.pow(10,d); |
| |
| const strengths=[0.0625,0.125,0.1875,0.25,0.375,0.5]; |
| function nearest(mg){return strengths.reduce((a,b)=>Math.abs(b-mg)<Math.abs(a-mg)?b:a);} |
| |
| function calc(){ |
| const age=+age_v('age'), wt=+age_v('wt'), ht=+age_v('ht'), scr=+age_v('scr'), target=+age_v('target'); |
| const sex=sel('sex'), ind=sel('ind'), form=sel('form'); |
| const out=document.getElementById('out'); |
| if(!(age>0&&wt>0&&ht>0&&scr>0&&target>0)){out.innerHTML='<div class="placeholder">Please fill every field with a positive number.</div>';return;} |
| |
| |
| const inch=ht/2.54; |
| let ibw=(sex==='m'?50:45.5)+2.3*Math.max(0,(inch-60)); |
| ibw=round(ibw,1); |
| const dosingWt=Math.min(wt,ibw); |
| |
| |
| let crcl=((140-age)*dosingWt)/(72*scr); if(sex==='f') crcl*=0.85; |
| crcl=Math.max(0,round(crcl,1)); |
| |
| |
| const vd=round(226+(298*crcl)/(29.1+crcl),1); |
| |
| const F={tab:0.75,iv:1.0,elx:0.8}[form]; |
| const nonRenal=(ind==='hf')?20:40; |
| const clMlMin=1.303*crcl+nonRenal; |
| const clLh=round(clMlMin*60/1000,3); |
| const tau=24; |
| |
| |
| const doseMcg=(target*clLh*tau)/F; |
| const doseMg=round(doseMcg/1000,4); |
| const practical=nearest(doseMg); |
| |
| |
| const ldPerKg=(form==='iv')?10:12; |
| const ldTotal=round(ibw*ldPerKg/1000,3); |
| const showLoad=(ind!=='hf'); |
| |
| |
| let notes=[]; |
| if(crcl<30) notes.push(['danger','CrCl below 30 mL/min — high accumulation risk. Model not validated here; expect a reduced dose and closer monitoring.']); |
| else if(crcl<60) notes.push(['warn','Reduced renal function (CrCl '+crcl+' mL/min) — digoxin is renally cleared, so dose runs lower.']); |
| if(wt<ibw*0.9 || wt>ibw*1.3) notes.push(['warn','Actual weight differs from ideal; lean weight ('+ibw+' kg) used for dosing.']); |
| if(target>0.9) notes.push(['warn','Target above 0.9 ng/mL. Current guidance prefers ≤0.9 ng/mL for heart failure.']); |
| notes.push(['','Narrow therapeutic index. Check a serum level ≥6 h post-dose; steady state takes ~1 week (longer if renal function reduced).']); |
| if(form==='iv') notes.push(['','IV form: oral dose × 1.25 ≈ IV equivalent when switching.']); |
| if(ind!=='hf') notes.push(['','Loading splits as ½ now, then ¼ every 6–8 h ×2; reassess rate and toxicity between doses.']); |
| |
| let html=''; |
| html+='<div class="block"><div class="result-head">Maintenance dose</div>'+ |
| '<div class="dose-big">'+practical+' <span class="dose-unit">mg / day</span></div>'+ |
| '<div style="font-size:13px;color:var(--ink-soft);margin-top:4px">nearest available strength · model estimate '+doseMg+' mg/day</div></div>'; |
| |
| if(showLoad){ |
| html+='<div class="block"><div class="result-head">Loading dose (total)</div>'+ |
| '<div style="font-size:22px;font-family:Fraunces,serif;font-weight:600;color:var(--ok)">'+ldTotal+' <span class="dose-unit">mg</span></div>'+ |
| '<div style="font-size:13px;color:var(--ink-soft);margin-top:4px">'+(form==='iv'?'IV':'oral')+', lean-weight based · give as ½ + ¼ + ¼</div></div>'; |
| } else { |
| html+='<div class="block"><div class="result-head">Loading dose</div>'+ |
| '<div style="font-size:15px;color:var(--ink)">Not routinely used for heart failure (per ACC/AHA).</div></div>'; |
| } |
| |
| html+='<div class="block"><div class="result-head">Administration notes</div>'; |
| notes.forEach(n=>html+='<div class="note '+(n[0])+'"><span class="dot"></span><span>'+n[1]+'</span></div>'); |
| html+='</div>'; |
| |
| html+='<div class="block"><div class="result-head">Derived values</div><div class="pk">'+ |
| 'Lean (Devine) weight : '+ibw+' kg\n'+ |
| 'Dosing weight : '+dosingWt+' kg\n'+ |
| 'CrCl (Cockcroft-G.) : '+crcl+' mL/min\n'+ |
| 'Volume of dist. (Vd) : '+vd+' L\n'+ |
| 'Clearance : '+clLh+' L/h\n'+ |
| 'Bioavailability (F) : '+F+ |
| '</div></div>'; |
| |
| out.innerHTML=html; |
| } |
| function age_v(id){return document.getElementById(id).value;} |
| </script> |
| </body> |
| </html> |
|
|