# Should You Sign Up for Cryonics? Interactive EV calculator

Below is an interactive expected-value calculator of signing up for cryonics with Monte Carlo simulation.

The model assumes you sign up now.

```widget[4rbB9zAtvY8nj4Kjm]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Should You Sign Up for Cryonics?</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,300;0,6..72,400;0,6..72,500;1,6..72,300;1,6..72,400&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,400&family=DM+Mono:wght@300;400&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstat/1.9.6/jstat.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script>
<style>
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
:root{--bg:#faf9f6;--text:#1c1917;--text-secondary:#78716c;--text-tertiary:#a8a29e;--border:#e7e5e4;--accent:#4a6fa5;--accent-light:#e8eef6;--positive:#3d7a5f;--negative:#a65d57;--serif:'Newsreader',Georgia,serif;--sans:'DM Sans',-apple-system,sans-serif;--mono:'DM Mono','SF Mono',monospace}
html{font-size:16px;-webkit-font-smoothing:antialiased}
body{font-family:var(--sans);font-weight:400;color:var(--text);background:var(--bg);line-height:1.6}
a{color:var(--text-secondary);text-decoration-color:var(--text-tertiary);text-underline-offset:2px}
a:hover{color:var(--text)}
.container{max-width:720px;margin:0 auto;padding:1rem 1.5rem}
header{margin-bottom:3rem}
h1{font-family:var(--serif);font-weight:400;font-size:2.25rem;line-height:1.15;letter-spacing:-.02em;margin-bottom:1rem}
.subtitle{font-size:.9375rem;color:var(--text-secondary);line-height:1.65;max-width:580px}

/* Sticky result */
.sticky-result{position:sticky;top:0;z-index:10;background:var(--bg);padding:1rem 0 .875rem;margin-bottom:2rem;border-bottom:1px solid transparent;transition:border-color .3s}
.sticky-result.scrolled{border-bottom-color:var(--border)}
.result-label{font-size:.6875rem;font-weight:500;letter-spacing:.06em;text-transform:uppercase;color:var(--text-tertiary);margin-bottom:.125rem}
.result-value{font-family:var(--mono);font-size:1.75rem;font-weight:300;letter-spacing:-.02em;transition:color .2s;margin-bottom:.625rem}
.result-value.positive{color:var(--positive)}.result-value.negative{color:var(--negative)}
.result-stats{display:flex;gap:1.5rem;flex-wrap:wrap}
.result-stat{display:flex;flex-direction:column}
.result-stat .label{font-size:.5625rem;font-weight:500;letter-spacing:.05em;text-transform:uppercase;color:var(--text-tertiary)}
.result-stat .val{font-family:var(--mono);font-size:.8125rem;color:var(--text-secondary);margin-top:.0625rem}
.result-stat .val.positive{color:var(--positive)}.result-stat .val.negative{color:var(--negative)}
.verdict{font-family:var(--serif);font-size:.8125rem;font-style:italic;color:var(--text-secondary);line-height:1.45;margin-top:.625rem}

.section{margin-bottom:2.5rem}
.section-title{font-family:var(--serif);font-weight:400;font-size:1.25rem;margin-bottom:.25rem}
.section-note{font-size:.8125rem;color:var(--text-tertiary);margin-bottom:1.25rem;line-height:1.55}
.param{margin-bottom:1.5rem}
.param-header{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:.375rem}
.param-label{font-size:.8125rem;font-weight:400;color:var(--text)}
.param-value{font-family:var(--mono);font-size:.8125rem;font-weight:400;color:var(--accent);min-width:4rem;text-align:right}
input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:2px;background:var(--border);outline:none;border-radius:1px;cursor:pointer}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:14px;height:14px;border-radius:50%;background:var(--accent);cursor:pointer;transition:transform .15s}
input[type=range]::-webkit-slider-thumb:hover{transform:scale(1.2)}
input[type=range]::-moz-range-thumb{width:14px;height:14px;border-radius:50%;background:var(--accent);cursor:pointer;border:none}
.param-hint{font-size:.6875rem;color:var(--text-tertiary);margin-top:.25rem;line-height:1.45}

/* Distribution input */
.dist-input{margin-bottom:1.5rem}
.dist-input .param-header{margin-bottom:.5rem}
.dist-canvas{width:100%;height:64px;cursor:crosshair;border-radius:2px}
.dist-track{position:relative;height:28px;margin-top:-2px;user-select:none;-webkit-user-select:none}
.dist-track-line{position:absolute;top:12px;left:0;right:0;height:2px;background:var(--border)}
.dist-handle{position:absolute;top:5px;width:16px;height:16px;border-radius:50%;background:var(--bg);border:2px solid var(--accent);cursor:grab;transform:translateX(-50%);transition:box-shadow .15s;z-index:2}
.dist-handle:hover,.dist-handle.dragging{box-shadow:0 0 0 4px rgba(74,111,165,.15)}
.dist-handle.median{border-radius:3px;width:14px;height:14px;top:6px;background:var(--accent);z-index:3}
.dist-labels{display:flex;justify-content:space-between;font-size:.625rem;font-family:var(--mono);color:var(--text-tertiary);margin-top:2px}

.chart-wrapper{margin:2rem 0}
.chart-wrapper canvas{width:100%!important;max-height:260px}
.chart-label{font-size:.6875rem;font-weight:500;letter-spacing:.05em;text-transform:uppercase;color:var(--text-tertiary);margin-bottom:.75rem}
.mc-status{font-size:.6875rem;color:var(--text-tertiary);font-family:var(--mono);margin-top:.25rem}

.decomposition{border-top:1px solid var(--border);padding-top:1.5rem;margin-top:1.5rem}
.decomp-row{display:flex;justify-content:space-between;align-items:baseline;padding:.375rem 0;font-size:.8125rem}
.decomp-row .decomp-label{color:var(--text-secondary)}.decomp-row .decomp-val{font-family:var(--mono);font-size:.8125rem;color:var(--text)}
.decomp-row.total{border-top:1px solid var(--border);margin-top:.375rem;padding-top:.75rem;font-weight:500}
.decomp-row.total .decomp-val{font-weight:500}
.decomp-header{font-size:.6875rem;font-weight:500;letter-spacing:.05em;text-transform:uppercase;color:var(--text-tertiary);padding:.75rem 0 .25rem}

footer{margin-top:4rem;padding-top:2rem;border-top:1px solid var(--border);margin-bottom:0;}
footer p{font-size:.75rem;color:var(--text-tertiary);line-height:1.6;margin-bottom:1rem;}
.ins-btn{font-family:var(--sans);font-size:.6875rem;font-weight:400;padding:.3125rem .625rem;border:1px solid var(--border);border-radius:3px;background:var(--bg);color:var(--text-secondary);cursor:pointer;transition:all .15s}
.ins-btn:hover{border-color:var(--accent);color:var(--accent)}
.ins-btn.active{border-color:var(--accent);color:var(--accent);background:var(--accent-light)}
@media(max-width:600px){.container{padding:2.5rem 1.25rem 4rem}h1{font-size:1.75rem}.result-value{font-size:1.375rem}}
</style>
</head>
<body>
<div class="container">
  <div class="sticky-result" id="stickyResult">
    <div class="result-label">Mean value in simulations</div>
    <div class="result-value" id="mainResult">$0</div>
    <div class="result-stats">
      <div class="result-stat"><span class="label">Median</span><span class="val" id="medianVal">$0</span></div>
      <div class="result-stat"><span class="label">5th</span><span class="val" id="p5Val">$0</span></div>
      <div class="result-stat"><span class="label">95th</span><span class="val" id="p95Val">$0</span></div>
      <div class="result-stat"><span class="label">P(+)</span><span class="val" id="pPositive">0%</span></div>
    </div>
    <div class="verdict" id="verdict"></div>
  </div>

  <!-- You -->
  <div class="section">
    <div class="section-title">You</div>
    <div class="param">
      <div class="param-header"><span class="param-label">Current age</span><span class="param-value" id="curageDisplay">25</span></div>
      <input type="range" id="curage" min="18" max="75" value="25" step="1">
      <div class="param-hint" id="curageHint"></div>
    </div>
  </div>

  <!-- Costs -->
  <div class="section">
    <div class="section-title">Costs</div>
    <div class="section-note">Annual cost = Alcor dues (age x $15/yr) + life insurance premium. If people stop dying from aging, you only lost the premiums paid.</div>
    <div class="param">
      <div class="param-header"><span class="param-label">Preservation face value</span><span class="param-value" id="faceValueDisplay">$230,000</span></div>
      <input type="range" id="faceValue" min="80000" max="350000" value="230000" step="5000">
      <div class="param-hint">Whole body: $200k + $20k CMS waiver + $10k international = $230k. Neuro: $80k + $20k + $10k = $110k. US/Canada members save the $10k surcharge. CMS waiver avoids $200/yr standby fee.</div>
    </div>
    <div class="param">
      <div class="param-header"><span class="param-label">Insurance type</span><span class="param-value" id="insTypeDisplay">Whole life</span></div>
      <div style="display:flex;gap:.375rem;margin-top:.375rem">
        <button class="ins-btn active" data-type="whole">Whole life</button>
        <button class="ins-btn" data-type="term">Limited term</button>
      </div>
      <div id="customTermRow" style="display:none;margin-top:.625rem">
        <div class="param-header"><span class="param-label">Term length (years)</span><span class="param-value" id="customTermDisplay">54</span></div>
        <input type="range" id="customTerm" min="5" max="80" value="54" step="1">
        <div class="param-hint">Defaults to years until median LEV. Cheaper, but lapses if you outlive the term.</div>
      </div>
    </div>
    <div class="param">
      <div class="param-header"><span class="param-label">Computed annual cost</span><span class="param-value" id="annualCostDisplay">$0</span></div>
      <div class="param-hint" id="annualCostHint"></div>
    </div>
  </div>

  <!-- Revival -->
  <div class="section">
    <div class="section-title">Revival</div>
    <div class="section-note">Most negative futures don't lead to revival. Conditioning on revival skews toward better worlds.</div>
    <div class="param">
      <div class="param-header"><span class="param-label">P(revival succeeds)</span><span class="param-value" id="probSuccDisplay">5%</span></div>
      <input type="range" id="probSucc" min="0.005" max="0.99" value="0.05" step="0.005">
      <div class="param-hint">Geometric mean of literature: ~13%. Metaculus: ~2-5%. Merkle: >85% conditional on nanotech + good preservation.</div>
    </div>
    <div class="param">
      <div class="param-header"><span class="param-label">P(preserved | attempt made)</span><span class="param-value" id="probPresDisplay">90%</span></div>
      <input type="range" id="probPres" min="0.40" max="1.00" value="0.90" step="0.01">
      <div class="param-hint">Conditional on an attempt being made. If not, the insurance payout (or its unused portion) can be directed elsewhere.</div>
    </div>
  </div>

  <!-- LEV -->
  <div class="section">
    <div class="section-title">Longevity Escape Velocity</div>
    <div class="section-note">After LEV, death from aging is negligible. If you survive to LEV, cryonics had no value and you only lost the premiums paid. Drag the handles to shape your belief.</div>
    <div id="levDist"></div>
  </div>

  <!-- Future life -->
  <div class="section">
    <div class="section-title">Life after revival</div>
    <div class="section-note">How long and how good? Your estimate of years already incorporates personal risk (accidents, etc.). Ongoing extinction risk is handled separately below.</div>
    <div id="yearsDist"></div>
    <div class="param">
      <div class="param-header"><span class="param-label">Value of a future life-year</span><span class="param-value" id="valYearDisplay">$50,000</span></div>
      <input type="range" id="valYear" min="10000" max="500000" value="50000" step="5000">
      <div class="param-hint">De facto standard: ~$50k/QALY. In a wealthier future you would likely assign more.</div>
    </div>
    <div class="param">
      <div class="param-header"><span class="param-label">Annual time discount</span><span class="param-value" id="timeDiscountDisplay">0.1%</span></div>
      <input type="range" id="timeDiscount" min="0" max="0.10" value="0.001" step="0.001">
      <div class="param-hint">Discounts future years. Years frozen are skipped. 0% = no discounting.</div>
    </div>
    <div class="param">
      <div class="param-header"><span class="param-label">Annual quality improvement</span><span class="param-value" id="qualityImpDisplay">0.1%</span></div>
      <input type="range" id="qualityImp" min="0" max="0.10" value="0.001" step="0.001">
      <div class="param-hint">Annual increase in the value of a life-year after revival. Life quality likely improves while you are frozen, which this does not account for.</div>
    </div>
  </div>

  <!-- Extinction -->
  <div class="section">
    <div class="section-title">Existential risk</div>
    <div class="section-note">Flows into: (1) you die in extinction before signup, (2) humanity goes extinct while you're frozen, (3) ongoing extinction risk shortens post-revival life.</div>
    <div class="param">
      <div class="param-header"><span class="param-label">P(extinction by 2100)</span><span class="param-value" id="xrisk2100Display">16.5%</span></div>
      <input type="range" id="xrisk2100" min="0.001" max="0.999" value="0.165" step="0.001">
    </div>
    <div id="xTimingDist"></div>
    <div class="param">
      <div class="param-header"><span class="param-label">Post-2100 annual extinction rate</span><span class="param-value" id="postXriskDisplay">0.002%</span></div>
      <input type="range" id="postXrisk" min="-6" max="-2" value="-4.7" step="0.1">
      <div class="param-hint">Log scale. Ongoing annual probability after this century.</div>
    </div>
  </div>

  <!-- Charts -->
  <div class="section">
    <div class="chart-label">Distribution of outcomes (log scale, 5,000 samples)</div>
    <div class="chart-wrapper"><canvas id="distChart"></canvas></div>
    <p class="mc-status" id="mcStatus">Running simulation...</p>
  </div>

  <div class="section">
    <div class="section-title">Decomposition (point estimate)</div>
    <div class="section-note">Uses distribution medians. The mean value in simulations above samples around these with uncertainty and will differ.</div>
    <div class="decomposition" id="decomposition"></div>
  </div>

  <footer>
    <p>Model based on <a href="https://niplav.site/considerations_on_cryonics" target="_blank"><em>Cryonics Cost-Benefit Analysis</em></a> by niplav (2019, updated 2025).
      Insurance from <a href="https://www.ssa.gov/oact/STATS/table4c6.html" target="_blank">SSA 2022 life table</a>.
		</p>
    <p>Not financial or medical advice.</p>
  </footer>
</div>

<script>
// === SSA 2022 LIFE TABLE (qx avg M/F) ===
const QX=[
0.005592,0.000445,0.000275,0.000223,0.000180,0.000151,0.000131,0.000118,0.000114,0.000114,
0.000119,0.000132,0.000156,0.000195,0.000254,0.000338,0.000449,0.000582,0.000711,0.000816,
0.000899,0.000968,0.001033,0.001098,0.001165,0.001238,0.001312,0.001391,0.001475,0.001572,
0.001660,0.001749,0.001843,0.001926,0.001990,0.002058,0.002149,0.002244,0.002344,0.002459,
0.002578,0.002702,0.002826,0.002964,0.003110,0.003264,0.003452,0.003689,0.003955,0.004241,
0.004537,0.004856,0.005228,0.005656,0.006147,0.006694,0.007272,0.007905,0.008589,0.009308,
0.010058,0.010854,0.011721,0.012626,0.013540,0.014458,0.015380,0.016373,0.017541,0.018886,
0.020427,0.022087,0.023978,0.026156,0.028657,0.031534,0.035140,0.038818,0.042841,0.047222,
0.052408,0.058057,0.064317,0.071402,0.079445,0.088683,0.098999,0.110673,0.123709,0.137537,
0.152755,0.169536,0.188208,0.208574,0.230173,0.252744,0.275644,0.299013,0.322158,0.344466,
0.367322,0.390629,0.414283,0.438166,0.462152,0.487462,0.514169,0.542352,0.572093,0.603478,
0.636599,0.671553,0.708442,0.747374,0.788125,0.827531,0.868907,0.912353,0.957970,1.000000
];
const MAXAGE=119,CURYEAR=2026;
function lifeExpAt(a){let s=0,p=1;for(let k=0;a+k<QX.length;k++){s+=p;p*=(1-QX[a+k])}return a+s}
function gompertzSurv(from,to){if(to<=from)return 1;let p=1;for(let a=Math.floor(from);a<Math.floor(to)&&a<QX.length;a++)p*=(1-QX[a]);return p}

// === INSURANCE PREMIUM ===
function insurancePremium(age,face,termYears,rate=0.04,load=1.25){
  const v=1/(1+rate),n=Math.min(termYears||999,MAXAGE-age);
  let A=0,aD=0,kp=1;
  for(let k=0;k<n&&age+k<QX.length;k++){
    aD+=Math.pow(v,k)*kp;A+=Math.pow(v,k+1)*kp*QX[age+k];kp*=(1-QX[age+k]);
  }
  return aD>0?(face*A/aD)*load:0;
}

// === EXTINCTION RISK ===
// Derives piecewise annual hazards from total risk + timing distribution CDF
function buildXrisk(p){
  // Timing CDF gives fraction of conditional risk by each breakpoint
  const f30=splitCdf(2030,p.xtQ10,p.xtMed,p.xtQ90,false);
  const f40=splitCdf(2040,p.xtQ10,p.xtMed,p.xtQ90,false);
  const r30=p.xrisk2100*f30;
  const r40=p.xrisk2100*f40;
  const r100=p.xrisk2100;
  const h1=1-Math.pow(Math.max(1e-15,1-r30),1/Math.max(1,2030-CURYEAR));
  const c40=r40>r30?(r40-r30)/(1-r30):0;
  const h2=1-Math.pow(Math.max(1e-15,1-c40),0.1);
  const c100=r100>r40?(r100-r40)/(1-r40):0;
  const h3=1-Math.pow(Math.max(1e-15,1-c100),1/60);
  return{h1,h2,h3,post:p.postXrisk};
}
function extinctionBetween(y1,y2,xr){
  if(y2<=y1)return 0;
  let surv=1;const bps=[2030,2040,2100];let c=Math.max(y1,CURYEAR);
  const hAt=y=>y<2030?xr.h1:y<2040?xr.h2:y<2100?xr.h3:xr.post;
  for(const bp of bps){if(c>=y2)break;const e=Math.min(y2,bp);if(e>c){surv*=Math.pow(1-hAt(c),e-c);c=e}}
  if(y2>Math.max(c,2100))surv*=Math.pow(1-xr.post,y2-Math.max(c,2100));
  return 1-surv;
}

// === FUTURE LIFE VALUE (survival-integrated geometric series) ===
function futureLifeValue(maxYrs,baseVal,disc,qual,annSurv){
  const rs=((1+qual)/(1+disc))*annSurv;
  if(rs<=0)return 0;
  if(Math.abs(rs-1)<1e-12)return baseVal*Math.min(maxYrs,1e15);
  if(rs<1)return baseVal*(1-Math.pow(rs,maxYrs))/(1-rs);
  return baseVal*maxYrs; // rs>=1: uncapped growth (rare)
}
function expectedLifeYears(maxYrs,annSurv){
  if(annSurv>=1)return maxYrs;if(annSurv<=0)return 0;
  return(1-Math.pow(annSurv,maxYrs))/(1-annSurv);
}

// === SPLIT-NORMAL (continuous PDF with 2/(sL+sR) normalization) ===
function splitSigmas(q10,med,q90,logScale){
  const t=v=>logScale?Math.log(Math.max(1e-10,v)):v;
  const m=t(med),lo=t(q10),hi=t(q90);
  return{sL:Math.max(0.01,(m-lo)/1.2816),sR:Math.max(0.01,(hi-m)/1.2816)};
}
function splitSample(q10,med,q90,logScale){
  const{sL,sR}=splitSigmas(q10,med,q90,logScale);
  const t=v=>logScale?Math.log(Math.max(1e-10,v)):v;
  const inv=v=>logScale?Math.exp(v):v;
  const m=t(med);
  // Weight by sL/(sL+sR) left, sR/(sL+sR) right — matches 2/(sL+sR) PDF
  if(Math.random()<sL/(sL+sR)){return inv(m-Math.abs(jStat.normal.sample(0,sL)))}
  else{return inv(m+Math.abs(jStat.normal.sample(0,sR)))}
}
function splitPdf(val,q10,med,q90,logScale){
  const t=v=>logScale?Math.log(Math.max(1e-10,v)):v;
  const{sL,sR}=splitSigmas(q10,med,q90,logScale);
  const m=t(med),x=t(val);
  const s=x<=m?sL:sR;
  const z=(x-m)/s;
  const pdf=(2/(sL+sR))*Math.exp(-0.5*z*z)/2.5066;
  return logScale?pdf/Math.max(1e-10,val):pdf;
}
// CDF of the split-normal (in natural space)
function splitCdf(val,q10,med,q90,logScale){
  const t=v=>logScale?Math.log(Math.max(1e-10,v)):v;
  const{sL,sR}=splitSigmas(q10,med,q90,logScale);
  const m=t(med),x=t(val);
  if(x<=m){
    return 2*sL/(sL+sR)*jStat.normal.cdf(x,m,sL);
  } else {
    return sL/(sL+sR)+2*sR/(sL+sR)*(jStat.normal.cdf(x,m,sR)-0.5);
  }
}

// === DISTRIBUTION INPUT WIDGET (Metaculus-style) ===
class DistInput{
  constructor(container,opts){
    this.opts=opts; // {label,min,max,q10,median,q90,logScale,unit,hint,onChange}
    this.q10=opts.q10;this.med=opts.median;this.q90=opts.q90;
    this.dragging=null;
    this.el=document.createElement('div');this.el.className='dist-input';
    // Header
    const hdr=document.createElement('div');hdr.className='param-header';
    const lbl=document.createElement('span');lbl.className='param-label';lbl.textContent=opts.label;
    this.valSpan=document.createElement('span');this.valSpan.className='param-value';
    hdr.appendChild(lbl);hdr.appendChild(this.valSpan);this.el.appendChild(hdr);
    // Canvas
    this.canvas=document.createElement('canvas');this.canvas.className='dist-canvas';this.canvas.height=64;
    this.el.appendChild(this.canvas);
    // Track
    this.track=document.createElement('div');this.track.className='dist-track';
    this.trackLine=document.createElement('div');this.trackLine.className='dist-track-line';this.track.appendChild(this.trackLine);
    this.hL=this._mkHandle('left');this.hM=this._mkHandle('median');this.hR=this._mkHandle('right');
    this.track.appendChild(this.hL);this.track.appendChild(this.hM);this.track.appendChild(this.hR);
    this.el.appendChild(this.track);
    // Labels
    this.lblRow=document.createElement('div');this.lblRow.className='dist-labels';
    this.lblL=document.createElement('span');this.lblM=document.createElement('span');this.lblR=document.createElement('span');
    this.lblRow.appendChild(this.lblL);this.lblRow.appendChild(this.lblM);this.lblRow.appendChild(this.lblR);
    this.el.appendChild(this.lblRow);
    // Hint
    if(opts.hint){const h=document.createElement('div');h.className='param-hint';h.textContent=opts.hint;this.el.appendChild(h)}
    container.appendChild(this.el);
    // Events
    this._bindEvents();
    requestAnimationFrame(()=>{this._resize();this._update()});
    new ResizeObserver(()=>{this._resize();this._update()}).observe(this.canvas);
  }
  _mkHandle(type){
    const h=document.createElement('div');
    h.className='dist-handle'+(type==='median'?' median':'');
    h.dataset.type=type;return h;
  }
  _resize(){this.canvas.width=this.canvas.offsetWidth;this.trackW=this.track.offsetWidth}
  _valToFrac(v){
    const{min,max,logScale}=this.opts;
    if(logScale)return(Math.log(v)-Math.log(min))/(Math.log(max)-Math.log(min));
    return(v-min)/(max-min);
  }
  _fracToVal(f){
    const{min,max,logScale}=this.opts;
    f=Math.max(0,Math.min(1,f));
    if(logScale)return Math.exp(Math.log(min)+f*(Math.log(max)-Math.log(min)));
    return min+f*(max-min);
  }
  _update(){
    // Position handles
    const w=this.trackW||this.track.offsetWidth;
    this.hL.style.left=(this._valToFrac(this.q10)*w)+'px';
    this.hM.style.left=(this._valToFrac(this.med)*w)+'px';
    this.hR.style.left=(this._valToFrac(this.q90)*w)+'px';
    // Labels
    const u=this.opts.unit||'';
    const f=this.opts.fmt||(v=>v>=1e6?(v/1e6).toFixed(0)+'M':v>=1e3?Math.round(v).toLocaleString():v.toFixed(0));
    this.lblL.textContent='10th: '+f(this.q10)+u;
    this.lblM.textContent='median: '+f(this.med)+u;
    this.lblR.textContent='90th: '+f(this.q90)+u;
    this.valSpan.textContent=f(this.med)+u;
    // PDF
    this._drawPdf();
    if(this.opts.onChange)this.opts.onChange(this.q10,this.med,this.q90);
  }
  sample(){return splitSample(this.q10,this.med,this.q90,this.opts.logScale)}
  _drawPdf(){
    // Canvas x-axis matches the handle track coordinate space.
    // For logScale, the track IS log space, so we need log-space density.
    // splitPdf with logScale=true returns natural-space density (includes 1/v Jacobian).
    // Multiply by v to get log-space density for display.
    const c=this.canvas,ctx=c.getContext('2d'),w=c.width,h=c.height;
    ctx.clearRect(0,0,w,h);if(w<10)return;
    const n=200,pts=[];let maxP=0;
    const ls=this.opts.logScale;
    for(let i=0;i<n;i++){
      const f=(i+0.5)/n,v=this._fracToVal(f);
      let p=splitPdf(v,this.q10,this.med,this.q90,ls);
      if(ls)p*=v; // cancel 1/v Jacobian → log-space density
      p=isFinite(p)?Math.max(0,p):0;
      pts.push({x:f*w,p});
      if(p>maxP)maxP=p;
    }
    if(maxP<=0)return;
    ctx.beginPath();ctx.moveTo(0,h);
    for(const pt of pts)ctx.lineTo(pt.x,h-pt.p/maxP*(h-4));
    ctx.lineTo(w,h);ctx.closePath();
    ctx.fillStyle='rgba(74,111,165,0.12)';ctx.fill();
    ctx.strokeStyle='rgba(74,111,165,0.4)';ctx.lineWidth=1.5;
    ctx.beginPath();
    for(let i=0;i<pts.length;i++){const pt=pts[i];i===0?ctx.moveTo(pt.x,h-pt.p/maxP*(h-4)):ctx.lineTo(pt.x,h-pt.p/maxP*(h-4))}
    ctx.stroke();
    for(const[val,dash]of[[this.q10,[3,3]],[this.med,[]],[this.q90,[3,3]]]){
      const x=this._valToFrac(val)*w;
      if(x>=0&&x<=w){ctx.beginPath();ctx.setLineDash(dash);ctx.strokeStyle='rgba(74,111,165,0.3)';ctx.lineWidth=1;
        ctx.moveTo(x,4);ctx.lineTo(x,h);ctx.stroke()}
    }
    ctx.setLineDash([]);
  }
  _bindEvents(){
    let dragOffsetL=0,dragOffsetR=0; // offsets from median at drag start
    const onDown=(e)=>{
      const t=e.target.closest('.dist-handle');if(!t)return;
      e.preventDefault();this.dragging=t.dataset.type;t.classList.add('dragging');
      // Store offsets for median drag (in fraction space)
      if(this.dragging==='median'){
        dragOffsetL=this._valToFrac(this.med)-this._valToFrac(this.q10);
        dragOffsetR=this._valToFrac(this.q90)-this._valToFrac(this.med);
      }
      const onMove=(ev)=>{
        const rect=this.track.getBoundingClientRect();
        const cx=(ev.touches?ev.touches[0]:ev).clientX;
        const f=Math.max(0,Math.min(1,(cx-rect.left)/rect.width));
        if(this.dragging==='left'){
          const v=this._fracToVal(Math.min(f,this._valToFrac(this.med)-0.01));
          this.q10=v;
        } else if(this.dragging==='median'){
          // Move all three, clamping to bounds
          const fL=Math.max(0,f-dragOffsetL);
          const fR=Math.min(1,f+dragOffsetR);
          this.q10=this._fracToVal(fL);
          this.med=this._fracToVal(f);
          this.q90=this._fracToVal(fR);
        } else {
          const v=this._fracToVal(Math.max(f,this._valToFrac(this.med)+0.01));
          this.q90=v;
        }
        this._update();
      };
      const onUp=()=>{
        document.querySelectorAll('.dist-handle.dragging').forEach(h=>h.classList.remove('dragging'));
        this.dragging=null;
        document.removeEventListener('mousemove',onMove);document.removeEventListener('mouseup',onUp);
        document.removeEventListener('touchmove',onMove);document.removeEventListener('touchend',onUp);
      };
      document.addEventListener('mousemove',onMove);document.addEventListener('mouseup',onUp);
      document.addEventListener('touchmove',onMove,{passive:false});document.addEventListener('touchend',onUp);
    };
    this.track.addEventListener('mousedown',onDown);this.track.addEventListener('touchstart',onDown,{passive:false});
  }
}

// === READ PARAMS ===
let levDist,yearsDist,xTimingDist,insType='whole';
function getParams(){
  const postXriskLog=+document.getElementById('postXrisk').value;
  return{
    curage:+document.getElementById('curage').value,
    faceValue:+document.getElementById('faceValue').value,
    probSucc:+document.getElementById('probSucc').value,
    probPres:+document.getElementById('probPres').value,
    levQ10:levDist.q10,levMed:levDist.med,levQ90:levDist.q90,
    yearsQ10:yearsDist.q10,yearsMed:yearsDist.med,yearsQ90:yearsDist.q90,
    valYear:+document.getElementById('valYear').value,
    timeDiscount:+document.getElementById('timeDiscount').value,
    qualityImp:+document.getElementById('qualityImp').value,
    xrisk2100:+document.getElementById('xrisk2100').value,
    xtQ10:xTimingDist.q10,xtMed:xTimingDist.med,xtQ90:xTimingDist.q90,
    postXrisk:Math.pow(10,postXriskLog),
    insType:insType,
    customTerm:+document.getElementById('customTerm').value
  };
}
function insTermForPoint(p){
  if(p.insType==='term')return p.customTerm;
  return null; // whole life
}
function insTermForMC(p,sampledLev){
  if(p.insType==='term')return p.customTerm;
  return null; // whole life
}

// === POINT ESTIMATE ===
function pointEstimate(p){
  const age=p.curage,deathAge=Math.floor(lifeExpAt(age));
  const levAge=age+(p.levMed-CURYEAR),deathYear=CURYEAR+(deathAge-age);
  const xr=buildXrisk(p);
  let probDieBeforeLev;
  if(age>=levAge){probDieBeforeLev=0}
  else{
    const bio=gompertzSurv(age,Math.min(levAge,MAXAGE));
    const xToLev=extinctionBetween(CURYEAR,p.levMed,xr);
    probDieBeforeLev=1-bio*(1-xToLev);
  }
  const alcorDues=age<18?60:age*15;
  const term=insTermForPoint(p);
  const insPrem=insurancePremium(age,p.faceValue,term);
  const yearsTilDeath=Math.max(0,deathAge-age);
  const yearsTilLev=Math.max(0,levAge-age);
  const yearsPaying=Math.min(yearsTilDeath,yearsTilLev);
  // Insurance premiums only paid during the term (or whole life = til death/LEV)
  const insYears=term?Math.min(term,yearsPaying):yearsPaying;
  // Dues paid until death or LEV
  const duesCost=alcorDues*yearsPaying;
  const insCost=insPrem*insYears;
  // If limited term and die AFTER term expires but BEFORE LEV: pay face value out of pocket
  const dieAfterTerm=term&&(yearsTilDeath>term)&&(yearsTilDeath<yearsTilLev);
  const outOfPocket=dieAfterTerm?p.faceValue:0;
  const totalCost=duesCost+insCost+outOfPocket;
  const annualCost=alcorDues+insPrem; // for display
  const revivalYear=Math.max(deathYear+50,p.levMed);
  const xFrozen=extinctionBetween(deathYear,revivalYear,xr);
  const yg=p.yearsMed,surv=1-xr.post;
  const expYrs=expectedLifeYears(yg,surv);
  const lv=futureLifeValue(yg,p.valYear,p.timeDiscount,p.qualityImp,surv);
  const ey=p.probPres*p.probSucc*(1-xFrozen)*expYrs;
  const elv=p.probPres*p.probSucc*(1-xFrozen)*lv;
  const benefit=elv*probDieBeforeLev;
  const value=benefit-totalCost;
  return{value,benefit,totalCost,annualCost,alcorDues,insPrem,probDieBeforeLev,xFrozen,surv,expYrs,ey,lv,yearsPaying,insYears,outOfPocket};
}

// === MONTE CARLO ===
function runMC(p,n=5000){
  const res=new Float64Array(n),age=p.curage,dues=age<18?60:age*15;
  for(let i=0;i<n;i++){
    const ps=Math.min(1,Math.max(0,jStat.lognormal.sample(Math.log(Math.max(.001,p.probSucc)),.6)));
    const pp=Math.min(1,Math.max(0,jStat.normal.sample(p.probPres,.08)));
    const vy=Math.max(1000,jStat.normal.sample(p.valYear,p.valYear*.4));
    const lev=splitSample(p.levQ10,p.levMed,p.levQ90,false); // LEV is linear
    const yg=splitSample(p.yearsQ10,p.yearsMed,p.yearsQ90,true); // years is log
    const xr2100=Math.min(.999,Math.max(.001,jStat.normal.sample(p.xrisk2100,p.xrisk2100*.3)));
    const pxr=Math.max(1e-8,jStat.lognormal.sample(Math.log(p.postXrisk),.5));
    const xr=buildXrisk({xrisk2100:xr2100,xtQ10:p.xtQ10,xtMed:p.xtMed,xtQ90:p.xtQ90,postXrisk:pxr});
    const da=Math.max(age+1,jStat.normal.sample(lifeExpAt(age),11));
    const la=age+(lev-CURYEAR);
    const dy=CURYEAR+(da-age);
    const term=insTermForMC(p,lev);
    const ins=insurancePremium(age,p.faceValue,term);
    const yp=Math.max(0,Math.min(da-age,la-age)); // years paying anything
    const insYrs=term?Math.min(term,yp):yp; // years paying insurance
    let cost=ins*insYrs+dues*yp;
    // Limited term: if die after term but before LEV, pay face value out of pocket
    const yearsTilDeath=da-age,yearsTilLev=la-age;
    if(term&&yearsTilDeath>term&&yearsTilDeath<yearsTilLev)cost+=p.faceValue;
    if(da>=la){res[i]=-cost}
    else{
      const rv=Math.max(dy+50,lev);
      const xf=extinctionBetween(dy,rv,xr);
      const surv=1-pxr;
      const lv=futureLifeValue(yg,vy,p.timeDiscount,p.qualityImp,surv);
      res[i]=pp*ps*(1-xf)*lv-cost;
    }
  }
  return res;
}

// === FORMATTING ===
function fmt(n){const a=Math.abs(n),s=n<0?'\u2212':'';
  if(a>=1e12)return s+'$'+(a/1e12).toFixed(1)+'T';
  if(a>=1e9)return s+'$'+(a/1e9).toFixed(1)+'B';
  if(a>=1e6)return s+'$'+(a/1e6).toFixed(1)+'M';
  if(a>=1e3)return s+'$'+Math.round(a).toLocaleString();
  return s+'$'+Math.round(a)}
function fmtPct(n){if(n>=.9999)return'~100%';if(n<.0001)return'<0.01%';if(n<.01)return(n*100).toFixed(2)+'%';return(n*100).toFixed(1)+'%'}
function fmtNum(n){if(n>=1e12)return(n/1e12).toFixed(1)+'T';if(n>=1e9)return(n/1e9).toFixed(1)+'B';if(n>=1e6)return(n/1e6).toFixed(0)+'M';if(n>=1e3)return Math.round(n).toLocaleString();return Math.round(n).toString()}
function fmtLog(n){if(n>=0.01)return(n*100).toFixed(1)+'%';if(n>=0.001)return(n*100).toFixed(2)+'%';return(n*1e6).toFixed(0)+' per M'}

// === CHART ===
let distChart;
function initChart(){
  distChart=new Chart(document.getElementById('distChart').getContext('2d'),{type:'bar',
    data:{labels:[],datasets:[{data:[],backgroundColor:[],borderWidth:0,borderRadius:1}]},
    options:{responsive:true,maintainAspectRatio:true,aspectRatio:2.8,animation:false,
      plugins:{legend:{display:false},tooltip:{
        titleFont:{family:"'DM Mono',monospace",size:11},bodyFont:{family:"'DM Sans',sans-serif",size:11},
        backgroundColor:'#1c1917',titleColor:'#a8a29e',bodyColor:'#faf9f6',cornerRadius:3,padding:8,
        callbacks:{title:i=>i[0]?.label||'',label:i=>i.raw+' samples'}}},
      scales:{x:{display:true,grid:{display:false},border:{display:false},
        ticks:{font:{family:"'DM Mono',monospace",size:9},color:'#a8a29e',maxRotation:0,autoSkip:true,maxTicksLimit:8}},
        y:{display:false}}}});
}
function updateDistChart(results){
  const lr=[];for(const v of results)lr.push(v===0?0:v>0?Math.log10(Math.max(1,v)):-Math.log10(Math.max(1,-v)));
  const mn=Math.min(...lr),mx=Math.max(...lr),nB=40,bw=(mx-mn)/nB||1;
  const bins=new Array(nB).fill(0),labels=[];
  for(let i=0;i<nB;i++){const c=mn+(i+.5)*bw;labels.push(c>=0?'$'+fmtNum(Math.pow(10,c)):'\u2212$'+fmtNum(Math.pow(10,-c)))}
  for(const v of lr)bins[Math.min(nB-1,Math.max(0,Math.floor((v-mn)/bw)))]++;
  distChart.data.labels=labels;distChart.data.datasets[0].data=bins;
  distChart.data.datasets[0].backgroundColor=bins.map((_,i)=>mn+(i+.5)*bw>=0?'rgba(61,122,95,.55)':'rgba(166,93,87,.45)');
  distChart.update('none');
}

// === DECOMPOSITION ===
function updateDecomp(p,est,mcMean){
  document.getElementById('decomposition').innerHTML=`
    <div class="decomp-header">Probabilities</div>
    <div class="decomp-row"><span class="decomp-label">Die before LEV</span><span class="decomp-val">${fmtPct(est.probDieBeforeLev)}</span></div>
    <div class="decomp-row"><span class="decomp-label">Preserved given attempt</span><span class="decomp-val">${fmtPct(p.probPres)}</span></div>
    <div class="decomp-row"><span class="decomp-label">Revival succeeds</span><span class="decomp-val">${fmtPct(p.probSucc)}</span></div>
    <div class="decomp-row"><span class="decomp-label">Civilization survives while frozen</span><span class="decomp-val">${fmtPct(1-est.xFrozen)}</span></div>
    <div class="decomp-header">Life after revival</div>
    <div class="decomp-row"><span class="decomp-label">Annual survival (ongoing extinction)</span><span class="decomp-val">${(est.surv*100).toFixed(4)}%</span></div>
    <div class="decomp-row"><span class="decomp-label">Expected years (survival-integrated)</span><span class="decomp-val">${fmtNum(est.expYrs)} yr</span></div>
    <div class="decomp-row"><span class="decomp-label">Discounted life value</span><span class="decomp-val">${fmt(est.lv)}</span></div>
    <div class="decomp-row"><span class="decomp-label">Gross benefit</span><span class="decomp-val">${fmt(est.benefit)}</span></div>
    <div class="decomp-header">Costs</div>
    <div class="decomp-row"><span class="decomp-label">Alcor dues</span><span class="decomp-val">$${est.alcorDues}/yr x ${Math.round(est.yearsPaying)} yr = ${fmt(est.alcorDues*est.yearsPaying)}</span></div>
    <div class="decomp-row"><span class="decomp-label">Insurance premium</span><span class="decomp-val">$${Math.round(est.insPrem)}/yr x ${Math.round(est.insYears)} yr = ${fmt(est.insPrem*est.insYears)}</span></div>
    ${est.outOfPocket>0?'<div class="decomp-row"><span class="decomp-label">Out-of-pocket (die after term)</span><span class="decomp-val">'+fmt(est.outOfPocket)+'</span></div>':''}
    <div class="decomp-row"><span class="decomp-label">Total cost</span><span class="decomp-val">${fmt(est.totalCost)}</span></div>
    <div class="decomp-row total"><span class="decomp-label">Point estimate</span><span class="decomp-val" style="color:${est.value>=0?'var(--positive)':'var(--negative)'}">${fmt(est.value)}</span></div>
    <div class="decomp-row total"><span class="decomp-label">Mean value in simulations</span><span class="decomp-val" style="color:${mcMean>=0?'var(--positive)':'var(--negative)'}">${fmt(mcMean)}</span></div>`;
}

// === DISPLAY ===
function updateDisplays(p,est){
  document.getElementById('curageDisplay').textContent=p.curage;
  document.getElementById('faceValueDisplay').textContent='$'+p.faceValue.toLocaleString();
  document.getElementById('probSuccDisplay').textContent=fmtPct(p.probSucc);
  document.getElementById('probPresDisplay').textContent=Math.round(p.probPres*100)+'%';
  document.getElementById('valYearDisplay').textContent='$'+p.valYear.toLocaleString();
  document.getElementById('timeDiscountDisplay').textContent=(p.timeDiscount*100).toFixed(1)+'%';
  document.getElementById('qualityImpDisplay').textContent=(p.qualityImp*100).toFixed(1)+'%';
  document.getElementById('xrisk2100Display').textContent=fmtPct(p.xrisk2100);
  document.getElementById('postXriskDisplay').textContent=fmtLog(p.postXrisk);
  document.getElementById('curageHint').textContent=
    "Life expectancy: "+Math.round(lifeExpAt(p.curage))+" | Alcor dues: "+p.curage+"x$15 = $"+(p.curage<18?60:p.curage*15)+"/yr";
  const insLabel=insType==='term'?document.getElementById('customTerm').value+'-yr term':'Whole life';
  document.getElementById('insTypeDisplay').textContent=insLabel;
  document.getElementById('annualCostDisplay').textContent='$'+Math.round(est.annualCost).toLocaleString()+'/yr';
  const termNote=insType==='term'?' | If you outlive the '+document.getElementById('customTerm').value+'-yr term, you owe $'+p.faceValue.toLocaleString()+' out of pocket':'';
  document.getElementById('annualCostHint').textContent=
    "Dues $"+est.alcorDues+" + insurance $"+Math.round(est.insPrem)+" ("+insLabel+")"+termNote;
}

// === MAIN ===
let mcTimeout,lastMcMean=0;
function update(){
  const p=getParams(),est=pointEstimate(p);
  updateDisplays(p,est);
  clearTimeout(mcTimeout);
  document.getElementById('mcStatus').textContent='Recalculating...';
  mcTimeout=setTimeout(()=>{
    const results=runMC(p,5000);
    const sorted=Array.from(results).sort((a,b)=>a-b);
    const mean=sorted.reduce((s,v)=>s+v,0)/sorted.length;
    lastMcMean=mean;
    const median=sorted[Math.floor(sorted.length*.5)];
    const p5=sorted[Math.floor(sorted.length*.05)];
    const p95=sorted[Math.floor(sorted.length*.95)];
    const pPos=sorted.filter(v=>v>0).length/sorted.length;
    const me=document.getElementById('mainResult');
    me.textContent=fmt(mean);me.className='result-value '+(mean>=0?'positive':'negative');
    const sv=(id,val)=>{const e=document.getElementById(id);e.textContent=fmt(val);e.className='val '+(val>=0?'positive':'negative')};
    sv('medianVal',median);sv('p5Val',p5);sv('p95Val',p95);
    document.getElementById('pPositive').textContent=Math.round(pPos*100)+'%';
    const ve=document.getElementById('verdict');
    if(mean>50000&&pPos>.3)ve.textContent="Under these beliefs, signing up appears net positive in expectation.";
    else if(mean>0)ve.textContent="Marginally positive in expectation. The median is likely negative (LEV arriving first), but the positive tail is large.";
    else ve.textContent="Under these beliefs, the expected value is negative.";
    updateDistChart(results);
    updateDecomp(p,est,mean);
    document.getElementById('mcStatus').textContent='5,000 samples';
  },300);
}

// === INIT ===
document.addEventListener('DOMContentLoaded',()=>{
  const yearsFmt=v=>v>=1e12?(v/1e12).toFixed(0)+'T':v>=1e9?(v/1e9).toFixed(0)+'B':v>=1e6?(v/1e6).toFixed(0)+'M':v>=1e3?Math.round(v).toLocaleString():Math.round(v).toString();
  yearsDist=new DistInput(document.getElementById('yearsDist'),{
    label:'Years if revived',min:10,max:1e15,q10:200,median:2800,q90:100000,
    logScale:true,hint:'Drag handles to shape the distribution. Log scale: 10 to 1 quadrillion years.',
    fmt:yearsFmt,unit:' yr',onChange:()=>update()
  });
  levDist=new DistInput(document.getElementById('levDist'),{
    label:'Year of LEV',min:2028,max:2300,q10:2040,median:2080,q90:2160,
    logScale:false,hint:'Drag handles. 10th/90th percentile and median for when aging is cured.',
    fmt:v=>Math.round(v).toString(),unit:'',onChange:()=>update()
  });
  xTimingDist=new DistInput(document.getElementById('xTimingDist'),{
    label:'If extinction by 2100, when?',min:2026,max:2100,q10:2027,median:2035,q90:2055,
    logScale:false,hint:'Conditional timing: given that extinction happens this century, when? Front-loaded = more near-term AI/bio risk.',
    fmt:v=>Math.round(v).toString(),unit:'',onChange:()=>update()
  });
  initChart();update();
  document.querySelectorAll('input[type=range]').forEach(el=>el.addEventListener('input',update));
  // Insurance type buttons
  document.querySelectorAll('.ins-btn').forEach(btn=>{
    btn.addEventListener('click',()=>{
      document.querySelectorAll('.ins-btn').forEach(b=>b.classList.remove('active'));
      btn.classList.add('active');
      insType=btn.dataset.type;
      const row=document.getElementById('customTermRow');
      if(insType==='term'){
        // Default term to years until LEV median
        const termToLev=Math.max(5,Math.round(levDist.med-CURYEAR));
        document.getElementById('customTerm').value=termToLev;
        document.getElementById('customTermDisplay').textContent=termToLev;
        row.style.display='block';
      } else {
        row.style.display='none';
      }
      update();
    });
  });
  document.getElementById('customTerm').addEventListener('input',()=>{
    document.getElementById('customTermDisplay').textContent=document.getElementById('customTerm').value;
    update();
  });
  const sr=document.getElementById('stickyResult');
  const sentinel=document.createElement('div');sentinel.style.height='1px';
  sr.parentNode.insertBefore(sentinel,sr);
  new IntersectionObserver(([e])=>sr.classList.toggle('scrolled',!e.isIntersecting)).observe(sentinel);
});
</script>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v8c78df7c7c0f484497ecbca7046644da1771523124516" integrity="sha512-8DS7rgIrAmghBFwoOTujcf6D9rXvH8xm8JQ1Ja01h9QX8EzXldiszufYa4IFfKdLUKTTrnSFXLDkUEOTrZQ8Qg==" data-cf-beacon='{"version":"2024.11.0","token":"43ed3f742c5d4ca5ba9e561f7cc30ace","r":1,"server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>

```

See also: [*You Only Live Twice*](https://www.lesswrong.com/posts/yKXKcyoBzWtECzXrE/you-only-live-twice), [*Cryonics Signup Guide*](https://www.lesswrong.com/s/weBHYgBXg9thEQNEe), [*Cryonics Calculator*](https://www.cryonicscalculator.com/).

*If you're in London and want to sign up for cryonics, we're hosting a* [*cryonics sign-up party*](https://partiful.com/e/S1zTdHG4Wjn4KYqihZky) *tomorrow.*