foundation-models/evidentia-public / evidentia /Cell_Rejuvenation_Slider_BioReal.html
foundation-models's picture
download
raw
47.8 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Partial Reprogramming — The Sweet Spot</title>
<style>
/* ---------- typography ---------- */
@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600;9..144,700&family=JetBrains+Mono:wght@400;500;600&family=Inter+Tight:wght@300;400;500;600;700&display=swap');
:root {
--bg-0: #050a14;
--bg-1: #0a1428;
--bg-2: #0f1d3a;
--ink: #f8fafc;
--ink-dim: #94a3b8;
--ink-faint: #475569;
--teal: #0ea5a4;
--teal-glow: #14d6d3;
--teal-deep: #0f766e;
--amber: #f59e0b;
--amber-glow: #fcd34d;
--red: #ef4444;
--red-deep: #b91c1c;
--grid: rgba(20, 214, 211, 0.06);
--hairline: rgba(148, 163, 184, 0.18);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
font-family: 'Inter Tight', sans-serif;
background: var(--bg-0);
color: var(--ink);
min-height: 100vh;
overflow-x: hidden;
overflow-y: auto;
/* atmospheric vignette */
background-image:
radial-gradient(ellipse at 50% 30%, rgba(14, 165, 164, 0.08) 0%, transparent 60%),
radial-gradient(ellipse at 80% 80%, rgba(245, 158, 11, 0.04) 0%, transparent 50%),
linear-gradient(180deg, var(--bg-1) 0%, var(--bg-0) 100%);
background-attachment: fixed;
}
/* ---------- grid backdrop ---------- */
body::before {
content: '';
position: fixed;
inset: 0;
background-image:
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px);
background-size: 40px 40px;
pointer-events: none;
z-index: 0;
mask-image: radial-gradient(ellipse at center, black 30%, transparent 80%);
-webkit-mask-image: radial-gradient(ellipse at center, black 30%, transparent 80%);
}
/* ---------- container ---------- */
.stage {
position: relative;
z-index: 1;
max-width: 1400px;
margin: 0 auto;
padding: 36px 48px 60px;
display: flex;
flex-direction: column;
gap: 24px;
}
/* ---------- header ---------- */
.header {
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 32px;
border-bottom: 1px solid var(--hairline);
padding-bottom: 20px;
}
.header-left .eyebrow {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
font-weight: 500;
letter-spacing: 0.28em;
color: var(--amber);
text-transform: uppercase;
margin-bottom: 8px;
}
.header-left h1 {
font-family: 'Fraunces', serif;
font-size: 38px;
font-weight: 600;
line-height: 1.05;
letter-spacing: -0.01em;
}
.header-left h1 em {
font-style: italic;
color: var(--teal-glow);
font-weight: 400;
}
.header-left .sub {
margin-top: 10px;
font-size: 14px;
color: var(--ink-dim);
max-width: 60ch;
line-height: 1.5;
}
.header-right {
text-align: right;
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--ink-faint);
letter-spacing: 0.15em;
text-transform: uppercase;
}
.header-right .meta-row {
margin-bottom: 4px;
display: flex;
justify-content: flex-end;
gap: 12px;
}
.header-right .meta-label { color: var(--ink-faint); }
.header-right .meta-value { color: var(--ink-dim); }
/* ---------- main grid ---------- */
.main {
display: grid;
grid-template-columns: 1fr 320px;
gap: 28px;
align-items: start;
}
/* ---------- viewer panel ---------- */
.viewer {
background: linear-gradient(180deg, rgba(15, 29, 58, 0.6) 0%, rgba(10, 20, 40, 0.4) 100%);
border: 1px solid var(--hairline);
border-radius: 4px;
overflow: hidden;
position: relative;
backdrop-filter: blur(2px);
}
.viewer::before {
/* corner ticks */
content: '';
position: absolute;
inset: 12px;
pointer-events: none;
background-image:
linear-gradient(to right, var(--teal) 0, var(--teal) 12px, transparent 12px, transparent calc(100% - 12px), var(--teal) calc(100% - 12px)),
linear-gradient(to bottom, var(--teal) 0, var(--teal) 12px, transparent 12px, transparent calc(100% - 12px), var(--teal) calc(100% - 12px));
background-size: 100% 1px, 1px 100%;
background-position: 0 0, 0 0;
background-repeat: no-repeat;
opacity: 0.5;
}
.viewer-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 20px;
border-bottom: 1px solid var(--hairline);
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--ink-faint);
}
.viewer-header .live-dot {
display: inline-block;
width: 8px; height: 8px;
background: var(--teal-glow);
border-radius: 50%;
box-shadow: 0 0 8px var(--teal-glow);
animation: pulse 1.6s ease-in-out infinite;
margin-right: 8px;
vertical-align: middle;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.viewer-header .specimen-id {
color: var(--teal-glow);
}
.viewer-body {
height: 480px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.cell-svg {
width: 100%; height: 100%;
}
/* annotations overlay */
.annotation {
position: absolute;
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--ink-dim);
text-transform: uppercase;
letter-spacing: 0.15em;
pointer-events: none;
opacity: 0.7;
}
.annotation.tl { top: 24px; left: 24px; }
.annotation.tr { top: 24px; right: 24px; text-align: right; }
.annotation.bl { bottom: 24px; left: 24px; }
.annotation.br { bottom: 24px; right: 24px; text-align: right; }
/* ---------- side readout panel ---------- */
.readout {
display: flex;
flex-direction: column;
gap: 16px;
}
.readout-card {
background: linear-gradient(180deg, rgba(15, 29, 58, 0.6) 0%, rgba(10, 20, 40, 0.4) 100%);
border: 1px solid var(--hairline);
border-radius: 4px;
padding: 18px 20px;
position: relative;
}
.readout-card .label {
font-family: 'JetBrains Mono', monospace;
font-size: 9px;
color: var(--ink-faint);
text-transform: uppercase;
letter-spacing: 0.2em;
margin-bottom: 10px;
}
.readout-card .value {
font-family: 'Fraunces', serif;
font-size: 32px;
font-weight: 600;
line-height: 1;
margin-bottom: 6px;
transition: color 0.4s;
}
.readout-card .value .unit {
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
color: var(--ink-dim);
font-weight: 400;
margin-left: 4px;
}
.readout-card .desc {
font-size: 11.5px;
color: var(--ink-dim);
line-height: 1.5;
}
/* gauge bar */
.gauge-track {
position: relative;
height: 4px;
background: rgba(148, 163, 184, 0.15);
border-radius: 2px;
margin: 12px 0 8px;
overflow: hidden;
}
.gauge-fill {
position: absolute;
inset: 0;
background: linear-gradient(90deg, var(--teal) 0%, var(--teal-glow) 100%);
border-radius: 2px;
transform-origin: left;
transition: transform 0.4s ease, background 0.4s ease;
}
.danger .value { color: var(--red); }
.danger .gauge-fill { background: linear-gradient(90deg, var(--red-deep) 0%, var(--red) 100%); }
.warn .value { color: var(--amber); }
.warn .gauge-fill { background: linear-gradient(90deg, var(--amber) 0%, var(--amber-glow) 100%); }
.ok .value { color: var(--teal-glow); }
/* ---------- slider ---------- */
.slider-section {
margin-top: 8px;
background: linear-gradient(180deg, rgba(15, 29, 58, 0.6) 0%, rgba(10, 20, 40, 0.4) 100%);
border: 1px solid var(--hairline);
border-radius: 4px;
padding: 24px 28px 28px;
position: relative;
}
.slider-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 20px;
}
.slider-title {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--ink-faint);
text-transform: uppercase;
letter-spacing: 0.2em;
}
.slider-percent {
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
color: var(--teal-glow);
letter-spacing: 0.05em;
}
/* zone bar */
.zone-bar {
position: relative;
height: 14px;
margin-bottom: 6px;
border-radius: 7px;
overflow: hidden;
background: linear-gradient(
90deg,
var(--red-deep) 0%,
var(--red) 18%,
#7a3a18 30%,
var(--teal-deep) 38%,
var(--teal) 52%,
var(--teal-deep) 65%,
#7a4a18 73%,
var(--amber) 85%,
var(--red) 100%
);
box-shadow: inset 0 1px 2px rgba(0,0,0,0.5);
}
.zone-bar::before {
/* sweet-spot highlight band */
content: '';
position: absolute;
top: 0; bottom: 0;
left: 38%; width: 27%;
border-left: 1px dashed rgba(255,255,255,0.3);
border-right: 1px dashed rgba(255,255,255,0.3);
pointer-events: none;
}
/* zone labels */
.zone-labels {
position: relative;
height: 32px;
margin-bottom: 18px;
}
.zone-label {
position: absolute;
transform: translateX(-50%);
text-align: center;
font-family: 'JetBrains Mono', monospace;
font-size: 9.5px;
text-transform: uppercase;
letter-spacing: 0.15em;
line-height: 1.4;
}
.zone-label .name { color: var(--ink-dim); }
.zone-label .pct { color: var(--ink-faint); font-size: 8.5px; margin-top: 2px; }
.zone-label.aged .name { color: var(--red); }
.zone-label.sweet .name { color: var(--teal-glow); }
.zone-label.stem .name { color: var(--amber); }
.zone-label.cancer .name { color: var(--red); }
/* slider input */
.slider-input {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 38px;
background: transparent;
cursor: ew-resize;
margin: 0;
padding: 0;
position: relative;
z-index: 2;
}
.slider-input:focus { outline: none; }
.slider-input::-webkit-slider-runnable-track {
height: 38px;
background: transparent;
}
.slider-input::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
height: 38px;
width: 14px;
background: var(--ink);
border: 2px solid var(--bg-0);
border-radius: 2px;
box-shadow:
0 0 0 1px var(--teal-glow),
0 0 18px var(--teal-glow),
0 4px 12px rgba(0,0,0,0.6);
cursor: ew-resize;
transition: box-shadow 0.2s;
}
.slider-input::-moz-range-track {
height: 38px;
background: transparent;
}
.slider-input::-moz-range-thumb {
height: 38px;
width: 14px;
background: var(--ink);
border: 2px solid var(--bg-0);
border-radius: 2px;
box-shadow:
0 0 0 1px var(--teal-glow),
0 0 18px var(--teal-glow),
0 4px 12px rgba(0,0,0,0.6);
cursor: ew-resize;
}
/* tick marks under slider */
.tick-row {
margin-top: 8px;
height: 8px;
position: relative;
border-top: 1px solid var(--hairline);
}
.tick {
position: absolute;
width: 1px;
height: 6px;
background: var(--ink-faint);
transform: translateX(-50%);
top: 0;
}
.tick.major { height: 8px; background: var(--ink-dim); }
/* ---------- cell-type selector ---------- */
.cell-type-section {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.cell-type-section .label {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--ink-faint);
text-transform: uppercase;
letter-spacing: 0.2em;
}
.cell-type-buttons {
display: flex;
gap: 0;
border: 1px solid var(--hairline);
border-radius: 2px;
overflow: hidden;
}
.cell-type-btn {
background: transparent;
border: none;
padding: 9px 18px;
color: var(--ink-dim);
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.12em;
cursor: pointer;
border-right: 1px solid var(--hairline);
transition: all 0.2s;
}
.cell-type-btn:last-child { border-right: none; }
.cell-type-btn:hover { background: rgba(14, 165, 164, 0.08); color: var(--ink); }
.cell-type-btn.active {
background: var(--teal);
color: var(--bg-0);
font-weight: 600;
}
/* ---------- status banner ---------- */
.status-banner {
margin-top: 16px;
padding: 14px 18px;
border-radius: 4px;
border: 1px solid;
font-size: 13px;
line-height: 1.5;
display: flex;
align-items: center;
gap: 12px;
transition: all 0.4s;
}
.status-banner .status-icon {
flex-shrink: 0;
width: 22px; height: 22px;
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
font-weight: 700;
}
.status-banner.aged {
background: rgba(239, 68, 68, 0.08);
border-color: rgba(239, 68, 68, 0.3);
color: var(--ink);
}
.status-banner.aged .status-icon { background: var(--red); color: var(--ink); }
.status-banner.sweet {
background: rgba(14, 165, 164, 0.10);
border-color: rgba(20, 214, 211, 0.4);
color: var(--ink);
animation: sweet-pulse 2.4s ease-in-out infinite;
}
@keyframes sweet-pulse {
0%, 100% { box-shadow: 0 0 0 rgba(20, 214, 211, 0); }
50% { box-shadow: 0 0 24px rgba(20, 214, 211, 0.25); }
}
.status-banner.sweet .status-icon { background: var(--teal-glow); color: var(--bg-0); }
.status-banner.warn {
background: rgba(245, 158, 11, 0.08);
border-color: rgba(245, 158, 11, 0.3);
color: var(--ink);
}
.status-banner.warn .status-icon { background: var(--amber); color: var(--bg-0); }
.status-banner.danger {
background: rgba(185, 28, 28, 0.12);
border-color: rgba(239, 68, 68, 0.5);
color: var(--ink);
}
.status-banner.danger .status-icon { background: var(--red); color: var(--ink); }
.status-banner .title {
font-weight: 600;
margin-right: 6px;
}
/* ---------- footer ---------- */
.footer {
margin-top: 8px;
text-align: center;
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--ink-faint);
letter-spacing: 0.15em;
text-transform: uppercase;
}
.footer .em { color: var(--teal-glow); }
/* responsive */
@media (max-width: 980px) {
.stage { padding: 24px 20px 40px; }
.header { flex-direction: column; align-items: flex-start; }
.header-right { text-align: left; }
.main { grid-template-columns: 1fr; }
.header-left h1 { font-size: 28px; }
.viewer-body { height: 380px; }
.zone-label { font-size: 8px; }
.cell-type-section { flex-direction: column; align-items: flex-start; }
}
</style>
</head>
<body>
<div class="stage">
<!-- ===== HEADER ===== -->
<header class="header">
<div class="header-left">
<div class="eyebrow">Live Cellular Reprogramming Simulator</div>
<h1>Finding the <em>sweet spot</em></h1>
<p class="sub">
Drag the reprogramming slider. Watch the cell move from aged through partial rejuvenation toward pluripotency. The challenge isn't going further — it's stopping at the right place.
</p>
</div>
<div class="header-right">
<div class="meta-row"><span class="meta-label">Mode</span><span class="meta-value" id="modeLabel">OSK · Partial Reprogramming</span></div>
<div class="meta-row"><span class="meta-label">Reference</span><span class="meta-value">Lu et al. 2020</span></div>
<div class="meta-row"><span class="meta-label">Spec ID</span><span class="meta-value" id="specId">RPG-0001</span></div>
</div>
</header>
<!-- ===== MAIN GRID ===== -->
<div class="main">
<!-- VIEWER -->
<div class="viewer">
<div class="viewer-header">
<div><span class="live-dot"></span>SPECIMEN VIEWER</div>
<div>Cell type: <span class="specimen-id" id="specimenType">RETINAL GANGLION</span></div>
</div>
<div class="viewer-body">
<span class="annotation tl" id="annoTL">EPI-DRIFT · HIGH</span>
<span class="annotation tr" id="annoTR">METHYLATION · AGED</span>
<span class="annotation bl" id="annoBL">IDENTITY · INTACT</span>
<span class="annotation br" id="annoBR">RISK · LOW</span>
<svg class="cell-svg" id="cellSvg" viewBox="0 0 800 480" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- Gradients -->
<radialGradient id="membraneGrad" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#0ea5a4" stop-opacity="0.15"/>
<stop offset="80%" stop-color="#0f766e" stop-opacity="0.4"/>
<stop offset="100%" stop-color="#0a1428" stop-opacity="0"/>
</radialGradient>
<radialGradient id="nucleusGrad" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#14d6d3" stop-opacity="0.9"/>
<stop offset="60%" stop-color="#0ea5a4" stop-opacity="0.7"/>
<stop offset="100%" stop-color="#0f766e" stop-opacity="0.4"/>
</radialGradient>
<radialGradient id="stemGrad" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fcd34d" stop-opacity="1"/>
<stop offset="50%" stop-color="#f59e0b" stop-opacity="0.85"/>
<stop offset="100%" stop-color="#b45309" stop-opacity="0.4"/>
</radialGradient>
<radialGradient id="dangerGrad" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fca5a5" stop-opacity="1"/>
<stop offset="50%" stop-color="#ef4444" stop-opacity="0.85"/>
<stop offset="100%" stop-color="#7f1d1d" stop-opacity="0.4"/>
</radialGradient>
<!-- soft glow filter -->
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="6" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<!-- Tissue-specific context behind the cell -->
<g id="tissueContext" opacity="1"></g>
<!-- Outer glow halo (changes color with state) -->
<circle id="halo" cx="400" cy="240" r="190"
fill="url(#membraneGrad)" opacity="0.6"/>
<!-- Cell membrane -->
<path id="membrane" d=""
fill="rgba(14, 165, 164, 0.055)"
stroke="#0ea5a4"
stroke-width="2"
stroke-linejoin="round"
opacity="0.78"/>
<!-- ============================================
Cell-shape extensions (specific to cell type)
============================================ -->
<g id="cellShape" opacity="1">
<!-- Filled in by JS based on cell type -->
</g>
<!-- Mitochondria (organelles) - get healthier as we rejuvenate -->
<g id="organelles">
<!-- Filled in by JS -->
</g>
<!-- Epigenetic damage / "scratches" - fade as we rejuvenate -->
<g id="damage">
<!-- Filled in by JS -->
</g>
<!-- Nucleus -->
<g id="nucleusGroup">
<circle id="nucleus" cx="400" cy="240" r="50"
fill="url(#nucleusGrad)"
filter="url(#glow)"
opacity="0.9"/>
</g>
<!-- DNA / chromatin inside nucleus -->
<g id="chromatin"></g>
<!-- Yamanaka factor sparkles -->
<g id="factors"></g>
<!-- Warning corona (only shown near pluripotency) -->
<circle id="warningCorona" cx="400" cy="240" r="200"
fill="none"
stroke="#ef4444"
stroke-width="1"
stroke-dasharray="4 6"
opacity="0"/>
</svg>
</div>
</div>
<!-- READOUT PANEL -->
<aside class="readout">
<div class="readout-card" id="cardAge">
<div class="label">Biological age</div>
<div class="value"><span id="ageValue">72</span><span class="unit">years</span></div>
<div class="gauge-track"><div class="gauge-fill" id="ageGauge"></div></div>
<div class="desc">Estimated from epigenetic methylation patterns (Horvath-style clock).</div>
</div>
<div class="readout-card" id="cardIdentity">
<div class="label">Cell identity preserved</div>
<div class="value"><span id="identityValue">100</span><span class="unit">%</span></div>
<div class="gauge-track"><div class="gauge-fill" id="identityGauge"></div></div>
<div class="desc">How much the cell still functions as itself — retinal ganglion, cortical neuron, dermal fibroblast.</div>
</div>
<div class="readout-card" id="cardRisk">
<div class="label">Tumorigenic risk</div>
<div class="value"><span id="riskValue">Low</span></div>
<div class="gauge-track"><div class="gauge-fill" id="riskGauge"></div></div>
<div class="desc">Probability of uncontrolled proliferation. Climbs steeply as cells approach pluripotency.</div>
</div>
</aside>
</div>
<!-- ===== SLIDER SECTION ===== -->
<div class="slider-section">
<div class="cell-type-section">
<div class="label">Cell type</div>
<div class="cell-type-buttons" role="tablist">
<button class="cell-type-btn active" data-type="retinal">Retinal Ganglion</button>
<button class="cell-type-btn" data-type="neuron">Neuron</button>
<button class="cell-type-btn" data-type="skin">Skin Fibroblast</button>
</div>
</div>
<div class="slider-header">
<div class="slider-title">Reprogramming Intensity → OSK Exposure Duration</div>
<div class="slider-percent"><span id="sliderPct">0</span>%</div>
</div>
<div class="zone-bar"></div>
<input type="range" class="slider-input" id="slider" min="0" max="100" value="0" step="0.5"/>
<div class="tick-row">
<span class="tick major" style="left: 0%"></span>
<span class="tick" style="left: 10%"></span>
<span class="tick" style="left: 20%"></span>
<span class="tick major" style="left: 38%"></span>
<span class="tick" style="left: 50%"></span>
<span class="tick major" style="left: 65%"></span>
<span class="tick" style="left: 75%"></span>
<span class="tick" style="left: 85%"></span>
<span class="tick major" style="left: 100%"></span>
</div>
<div class="zone-labels">
<div class="zone-label aged" style="left: 8%"><div class="name">Aged Cell</div><div class="pct">0–18%</div></div>
<div class="zone-label sweet" style="left: 51.5%"><div class="name">Sweet Spot</div><div class="pct">38–65%</div></div>
<div class="zone-label stem" style="left: 80%"><div class="name">Pluripotent</div><div class="pct">75–90%</div></div>
<div class="zone-label cancer" style="left: 96%"><div class="name">Tumor Risk</div><div class="pct">90–100%</div></div>
</div>
<div class="status-banner aged" id="statusBanner">
<div class="status-icon" id="statusIcon">!</div>
<div><span class="title" id="statusTitle">AGED CELL</span><span id="statusDesc">Drifted methylation, reduced function. The starting point for therapy.</span></div>
</div>
</div>
<div class="footer">
<span class="em">PARTIAL REPROGRAMMING</span> · OSK CASCADES EPIGENETIC RESET WITHOUT ERASING CELL IDENTITY · BASED ON LU ET AL., NATURE 2020
</div>
</div>
<script>
// ============================================================
// STATE
// ============================================================
const state = {
intensity: 0, // 0 - 100
cellType: 'retinal', // retinal | neuron | skin
};
// ============================================================
// LERP & UTILITIES
// ============================================================
const lerp = (a, b, t) => a + (b - a) * t;
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
const smoothstep = (e0, e1, x) => {
const t = clamp((x - e0) / (e1 - e0), 0, 1);
return t * t * (3 - 2 * t);
};
// ============================================================
// DERIVED METRICS — what the audience sees
// ============================================================
function computeMetrics(intensity) {
const t = intensity / 100;
// BIOLOGICAL AGE: starts at 72 years (aged). Drops sharply through sweet spot,
// bottoms out near pluripotency at "0 years" (embryonic-equivalent).
// Mathematically: age decreases monotonically.
const startAge = 72;
const minAge = 0;
const ageRaw = startAge * Math.pow(1 - t, 1.6);
const age = Math.round(ageRaw);
// CELL IDENTITY PRESERVED: 100% at 0, stays high through sweet spot,
// collapses sharply as we approach full reprogramming.
// Use a smoothstep that holds high then drops between 65% and 90%.
const identityHold = 1 - smoothstep(0.55, 0.92, t);
const identity = Math.round(identityHold * 100);
// TUMORIGENIC RISK: stays low through aged & sweet spot, ramps up sharply
// as we cross into pluripotency, peaks at 100.
// Use smoothstep starting at 0.6, fully at 1.0.
const riskRaw = smoothstep(0.55, 1.0, t);
const risk = Math.round(riskRaw * 100);
let riskLabel;
if (risk < 15) riskLabel = "Low";
else if (risk < 40) riskLabel = "Moderate";
else if (risk < 70) riskLabel = "Elevated";
else if (risk < 90) riskLabel = "High";
else riskLabel = "Critical";
// ZONE classification - drives banner + colors
let zone;
if (t < 0.18) zone = 'aged';
else if (t < 0.38) zone = 'transitioning';
else if (t <= 0.65) zone = 'sweet';
else if (t <= 0.78) zone = 'overshoot';
else if (t <= 0.92) zone = 'pluripotent';
else zone = 'cancer';
return { age, identity, risk, riskLabel, zone, t };
}
// ============================================================
// STATUS BANNER CONTENT
// ============================================================
const ZONE_BANNERS = {
aged: {
cls: 'aged', icon: '!', title: 'AGED CELL',
desc: 'Drifted methylation, dysfunctional organelles. Functional but declining.'
},
transitioning: {
cls: 'aged', icon: '~', title: 'PARTIAL RESET BEGINNING',
desc: 'OSK is acting on the epigenome. Methylation is being rewritten. Hold steady — you\'re approaching the sweet spot.'
},
sweet: {
cls: 'sweet', icon: '✓', title: 'SWEET SPOT',
desc: 'Younger epigenetic state achieved while cell identity preserved. This is the therapeutic target. ER-100 lives here.'
},
overshoot: {
cls: 'warn', icon: '!', title: 'IDENTITY ERODING',
desc: 'Cell is starting to forget what it is. Function is degrading. You\'ve gone too far.'
},
pluripotent: {
cls: 'warn', icon: '!', title: 'APPROACHING PLURIPOTENCY',
desc: 'Cell is becoming stem-cell-like. In a body, this would mean uncontrolled growth.'
},
cancer: {
cls: 'danger', icon: '✕', title: 'TUMORIGENIC',
desc: 'Full reprogramming achieved. In a living organism, this cell would form a teratoma.'
},
};
// ============================================================
// CELL-TYPE SHAPES — drawn into <g id="cellShape">
// These are appendages that distinguish cell types.
// They fade out as the cell approaches pluripotency.
// ============================================================
const CELL_SHAPES = {
retinal: {
label: 'RETINAL GANGLION',
specId: 'RGC-0412',
body: 'M 286 236 C 286 168 339 123 405 126 C 476 129 528 180 525 244 C 522 311 468 354 398 350 C 329 346 286 303 286 236 Z',
context: `
<g opacity="0.38">
<path d="M 70 86 C 190 48 315 48 444 84 C 558 115 668 109 760 76" fill="none" stroke="#14d6d3" stroke-width="1" opacity="0.35"/>
<path d="M 64 402 C 185 356 310 358 433 393 C 560 430 660 422 758 388" fill="none" stroke="#14d6d3" stroke-width="1" opacity="0.22"/>
<path d="M 70 128 C 205 97 320 104 438 132 C 555 160 655 150 746 122" fill="none" stroke="#64748b" stroke-width="0.8" opacity="0.22"/>
<path d="M 62 352 C 178 326 303 322 432 350 C 555 376 657 372 748 345" fill="none" stroke="#64748b" stroke-width="0.8" opacity="0.18"/>
</g>
`,
svg: `
<path d="M 522 236 C 594 226 650 218 717 194" stroke="#0ea5a4" stroke-width="5.5" fill="none" stroke-linecap="round"/>
<path d="M 522 246 C 604 250 658 265 727 292" stroke="#0f766e" stroke-width="3.4" fill="none" stroke-linecap="round" opacity="0.85"/>
<path d="M 535 238 C 620 240 676 240 752 232" stroke="#14d6d3" stroke-width="2" fill="none" stroke-linecap="round" opacity="0.7"/>
<circle cx="717" cy="194" r="6" fill="#14d6d3" filter="url(#softGlow)"/>
<circle cx="727" cy="292" r="4.5" fill="#14d6d3" opacity="0.85"/>
<circle cx="752" cy="232" r="3.5" fill="#fcd34d" opacity="0.8"/>
<path d="M 292 220 C 245 192 207 164 151 134" stroke="#0ea5a4" stroke-width="3" fill="none" stroke-linecap="round"/>
<path d="M 291 235 C 235 230 188 228 126 238" stroke="#0ea5a4" stroke-width="2.7" fill="none" stroke-linecap="round"/>
<path d="M 299 258 C 251 291 213 316 162 351" stroke="#0ea5a4" stroke-width="2.5" fill="none" stroke-linecap="round"/>
<path d="M 314 196 C 282 159 267 132 245 91" stroke="#14d6d3" stroke-width="1.8" fill="none" stroke-linecap="round" opacity="0.8"/>
<path d="M 318 286 C 290 331 276 358 252 398" stroke="#14d6d3" stroke-width="1.8" fill="none" stroke-linecap="round" opacity="0.75"/>
<circle cx="151" cy="134" r="4" fill="#14d6d3"/>
<circle cx="126" cy="238" r="3.5" fill="#14d6d3"/>
<circle cx="162" cy="351" r="3.5" fill="#14d6d3"/>
`
},
neuron: {
label: 'CORTICAL NEURON',
specId: 'CTX-2209',
body: 'M 285 246 C 275 179 326 124 397 125 C 471 126 530 176 529 241 C 528 309 468 359 394 354 C 326 349 294 307 285 246 Z',
context: `
<g opacity="0.3">
<path d="M 72 92 C 176 124 252 122 350 95 C 466 62 572 69 734 120" fill="none" stroke="#64748b" stroke-width="1" opacity="0.28"/>
<path d="M 90 385 C 170 332 262 316 365 342 C 486 372 588 348 724 299" fill="none" stroke="#64748b" stroke-width="1" opacity="0.24"/>
<circle cx="144" cy="112" r="2.2" fill="#14d6d3" opacity="0.45"/>
<circle cx="650" cy="102" r="2.2" fill="#14d6d3" opacity="0.35"/>
<circle cx="684" cy="330" r="2.2" fill="#14d6d3" opacity="0.4"/>
<circle cx="196" cy="344" r="2.2" fill="#14d6d3" opacity="0.32"/>
</g>
`,
svg: `
<path d="M 524 242 C 596 243 660 245 739 242" stroke="#0ea5a4" stroke-width="4.6" fill="none" stroke-linecap="round"/>
<path d="M 739 242 L 764 226 M 739 242 L 766 242 M 739 242 L 764 259" stroke="#14d6d3" stroke-width="2.2" fill="none" stroke-linecap="round"/>
<path d="M 291 218 C 245 177 203 139 133 105 C 109 93 88 94 73 106" stroke="#0ea5a4" stroke-width="3" fill="none" stroke-linecap="round"/>
<path d="M 286 236 C 225 218 167 202 96 217" stroke="#0ea5a4" stroke-width="2.8" fill="none" stroke-linecap="round"/>
<path d="M 297 261 C 242 303 194 337 119 366" stroke="#0ea5a4" stroke-width="2.7" fill="none" stroke-linecap="round"/>
<path d="M 317 190 C 288 145 276 111 263 64" stroke="#14d6d3" stroke-width="1.8" fill="none" stroke-linecap="round" opacity="0.8"/>
<path d="M 326 288 C 286 337 267 373 247 424" stroke="#14d6d3" stroke-width="1.8" fill="none" stroke-linecap="round" opacity="0.8"/>
<path d="M 215 159 C 181 151 154 153 122 166 M 203 308 C 163 312 135 325 105 350 M 198 217 C 170 195 146 187 113 186" stroke="#0ea5a4" stroke-width="1.5" fill="none" stroke-linecap="round" opacity="0.72"/>
<circle cx="73" cy="106" r="3.5" fill="#14d6d3"/>
<circle cx="96" cy="217" r="3.5" fill="#14d6d3"/>
<circle cx="119" cy="366" r="3.5" fill="#14d6d3"/>
<circle cx="764" cy="226" r="2.8" fill="#fcd34d"/>
<circle cx="766" cy="242" r="2.8" fill="#fcd34d"/>
<circle cx="764" cy="259" r="2.8" fill="#fcd34d"/>
`
},
skin: {
label: 'DERMAL FIBROBLAST',
specId: 'FB-7781',
body: 'M 124 238 C 200 197 294 164 396 166 C 504 168 604 196 678 238 C 606 282 505 313 395 313 C 291 313 194 282 124 238 Z',
context: `
<g opacity="0.36">
<path d="M 42 118 C 151 82 258 92 357 126 C 455 160 561 165 758 112" fill="none" stroke="#fcd34d" stroke-width="1.4" opacity="0.24"/>
<path d="M 50 165 C 164 136 248 145 354 180 C 456 215 566 214 748 168" fill="none" stroke="#14d6d3" stroke-width="1" opacity="0.18"/>
<path d="M 42 334 C 155 300 258 309 360 342 C 466 376 576 377 758 323" fill="none" stroke="#fcd34d" stroke-width="1.4" opacity="0.25"/>
<path d="M 66 382 C 176 355 278 360 377 390 C 486 423 598 420 736 382" fill="none" stroke="#14d6d3" stroke-width="1" opacity="0.18"/>
<path d="M 82 88 L 738 397 M 70 401 L 735 83" stroke="#64748b" stroke-width="0.65" opacity="0.16"/>
</g>
`,
svg: `
<path d="M 125 238 C 86 223 58 205 36 183" stroke="#0ea5a4" stroke-width="2.6" fill="none" stroke-linecap="round"/>
<path d="M 125 238 C 82 253 55 272 31 298" stroke="#0ea5a4" stroke-width="2.6" fill="none" stroke-linecap="round"/>
<path d="M 675 238 C 716 220 743 202 771 176" stroke="#0ea5a4" stroke-width="2.6" fill="none" stroke-linecap="round"/>
<path d="M 675 238 C 716 255 744 276 770 303" stroke="#0ea5a4" stroke-width="2.6" fill="none" stroke-linecap="round"/>
<path d="M 205 222 C 276 197 331 190 405 191 C 489 192 546 207 606 230" stroke="#14d6d3" stroke-width="1.3" fill="none" opacity="0.42"/>
<path d="M 205 257 C 280 283 341 290 419 286 C 499 282 555 266 606 245" stroke="#14d6d3" stroke-width="1.3" fill="none" opacity="0.38"/>
<ellipse cx="400" cy="240" rx="118" ry="42" fill="none" stroke="#0f766e" stroke-width="1.2" opacity="0.35"/>
`
}
};
function stemLikeBodyPath(t) {
const r = lerp(158, 122, smoothstep(0.6, 0.95, t));
return `
M ${400 - r} 240
C ${400 - r} ${240 - r * 0.58}, ${400 - r * 0.58} ${240 - r}, 400 ${240 - r}
C ${400 + r * 0.58} ${240 - r}, ${400 + r} ${240 - r * 0.58}, ${400 + r} 240
C ${400 + r} ${240 + r * 0.58}, ${400 + r * 0.58} ${240 + r}, 400 ${240 + r}
C ${400 - r * 0.58} ${240 + r}, ${400 - r} ${240 + r * 0.58}, ${400 - r} 240 Z
`;
}
function membranePathFor(type, t) {
return t < 0.72 ? CELL_SHAPES[type].body : stemLikeBodyPath(t);
}
function renderTissueContext(type, intensity) {
const t = intensity / 100;
const identityOpacity = 1 - smoothstep(0.55, 0.92, t);
const stemFogOpacity = smoothstep(0.68, 0.95, t) * 0.55;
return `
<g opacity="${identityOpacity.toFixed(3)}">${CELL_SHAPES[type].context}</g>
<g opacity="${stemFogOpacity.toFixed(3)}">
<circle cx="358" cy="197" r="18" fill="#f59e0b" opacity="0.22" filter="url(#softGlow)"/>
<circle cx="448" cy="194" r="22" fill="#fcd34d" opacity="0.18" filter="url(#softGlow)"/>
<circle cx="487" cy="281" r="16" fill="#f59e0b" opacity="0.16" filter="url(#softGlow)"/>
<circle cx="314" cy="282" r="16" fill="#fcd34d" opacity="0.14" filter="url(#softGlow)"/>
</g>
`;
}
// ============================================================
// ORGANELLES — fade in / out and "heal" with rejuvenation
// ============================================================
function renderOrganelles(intensity) {
const t = intensity / 100;
// Fade out as cell becomes stem-cell-like (homogenous interior)
const orgOpacity = 1 - smoothstep(0.55, 0.85, t);
// Health: aged cells have shriveled, dim mitochondria; healthy cells have plump, bright ones
const health = clamp(t * 1.8, 0, 1) * (1 - smoothstep(0.6, 0.85, t));
const mitoSize = lerp(4, 8, health);
const mitoBright = lerp(0.3, 0.95, health);
// Mitochondria scattered in cytoplasm
const mitos = [
{ x: 320, y: 200 }, { x: 470, y: 195 }, { x: 340, y: 290 },
{ x: 460, y: 295 }, { x: 305, y: 245 }, { x: 495, y: 245 },
{ x: 380, y: 180 }, { x: 420, y: 305 },
];
return mitos.map((m, i) => {
const angle = (i % 2 ? -18 : 22);
return `
<g transform="rotate(${angle} ${m.x} ${m.y})" opacity="${orgOpacity * mitoBright}">
<ellipse cx="${m.x}" cy="${m.y}" rx="${mitoSize * 1.35}" ry="${mitoSize * 0.72}"
fill="#fcd34d" filter="url(#softGlow)"/>
<path d="M ${m.x - mitoSize * 0.72} ${m.y} C ${m.x - mitoSize * 0.28} ${m.y - mitoSize * 0.38}, ${m.x + mitoSize * 0.22} ${m.y + mitoSize * 0.38}, ${m.x + mitoSize * 0.72} ${m.y}"
stroke="#7c2d12" stroke-width="0.8" fill="none" opacity="${health * 0.6}"/>
</g>`;
}).join('');
}
// ============================================================
// EPIGENETIC DAMAGE - "scratches"
// Heavy at low intensity (aged), fades through sweet spot
// ============================================================
function renderDamage(intensity) {
const t = intensity / 100;
// Damage starts high, fades as we rejuvenate
const damageOpacity = (1 - smoothstep(0.0, 0.5, t)) * 0.6;
if (damageOpacity < 0.02) return '';
// Use deterministic "noise" — small lines & dots scattered throughout cytoplasm
const seeds = [
{ x: 295, y: 175, r: 18, a: 35 }, { x: 478, y: 178, r: 14, a: -28 },
{ x: 320, y: 320, r: 16, a: 50 }, { x: 470, y: 325, r: 12, a: -45 },
{ x: 270, y: 250, r: 20, a: 90 }, { x: 528, y: 255, r: 18, a: -90 },
{ x: 350, y: 175, r: 10, a: 15 }, { x: 440, y: 320, r: 14, a: -20 },
{ x: 285, y: 215, r: 9, a: 60 }, { x: 510, y: 285, r: 11, a: -65 },
];
const dots = [
{ x: 305, y: 200 }, { x: 488, y: 215 }, { x: 348, y: 305 },
{ x: 440, y: 310 }, { x: 285, y: 280 }, { x: 510, y: 215 },
{ x: 370, y: 165 }, { x: 425, y: 320 },
];
let svg = '';
seeds.forEach(s => {
const x2 = s.x + s.r * Math.cos(s.a * Math.PI / 180);
const y2 = s.y + s.r * Math.sin(s.a * Math.PI / 180);
svg += `<line x1="${s.x}" y1="${s.y}" x2="${x2}" y2="${y2}"
stroke="#ef4444" stroke-width="1.5" opacity="${damageOpacity}"/>`;
});
dots.forEach(d => {
svg += `<circle cx="${d.x}" cy="${d.y}" r="1.5" fill="#fca5a5" opacity="${damageOpacity * 0.8}"/>`;
});
return svg;
}
// ============================================================
// CHROMATIN inside nucleus
// ============================================================
function renderChromatin(intensity) {
const t = intensity / 100;
// At low t: messy, scattered (drift). At high t (stem): smooth, glowing
const messiness = (1 - smoothstep(0.3, 0.9, t));
const opacity = lerp(0.4, 0.7, messiness);
const cx = 400, cy = 240;
let svg = '';
// Concentric arcs representing chromatin loops
for (let i = 0; i < 5; i++) {
const r = 18 + i * 6;
const offsetA = messiness * (Math.random() - 0.5) * 0; // deterministic for now, no jitter
svg += `<circle cx="${cx}" cy="${cy}" r="${r}"
fill="none" stroke="#14d6d3"
stroke-width="0.7" opacity="${opacity * (1 - i * 0.15)}"/>`;
}
return svg;
}
// ============================================================
// YAMANAKA FACTOR SPARKLES — visible during reprogramming
// ============================================================
function renderFactors(intensity) {
const t = intensity / 100;
// Peaks during active reprogramming (sweet spot region)
const factorVisibility = Math.sin(t * Math.PI) * 0.9;
if (factorVisibility < 0.05) return '';
const positions = [
{ x: 260, y: 130 }, { x: 540, y: 130 }, { x: 260, y: 350 }, { x: 540, y: 350 },
{ x: 200, y: 240 }, { x: 600, y: 240 }, { x: 400, y: 100 }, { x: 400, y: 380 },
{ x: 320, y: 145 }, { x: 480, y: 335 },
];
return positions.map((p, i) => {
const seed = i * 0.35;
const phase = (t + seed) % 1;
const localOpacity = factorVisibility * (0.5 + 0.5 * Math.sin(phase * Math.PI * 2));
return `<g opacity="${localOpacity}">
<circle cx="${p.x}" cy="${p.y}" r="2" fill="#fcd34d" filter="url(#softGlow)"/>
<circle cx="${p.x}" cy="${p.y}" r="5" fill="none" stroke="#fcd34d" stroke-width="0.5" opacity="0.6"/>
</g>`;
}).join('');
}
// ============================================================
// RENDER LOOP
// ============================================================
function render() {
const m = computeMetrics(state.intensity);
const t = m.t;
// ---- cell shape (cell type) — fades out as we approach pluripotency
const cellShape = document.getElementById('cellShape');
const shapeOpacity = 1 - smoothstep(0.55, 0.92, t);
cellShape.innerHTML = CELL_SHAPES[state.cellType].svg;
cellShape.setAttribute('opacity', shapeOpacity.toFixed(3));
document.getElementById('specimenType').textContent = CELL_SHAPES[state.cellType].label;
document.getElementById('specId').textContent = CELL_SHAPES[state.cellType].specId;
document.getElementById('tissueContext').innerHTML = renderTissueContext(state.cellType, state.intensity);
// ---- membrane: specimen-specific morphology collapses into a round stem-like body
const membrane = document.getElementById('membrane');
membrane.setAttribute('d', membranePathFor(state.cellType, t));
membrane.setAttribute('stroke', t > 0.92 ? '#ef4444' : (t > 0.7 ? '#f59e0b' : '#0ea5a4'));
membrane.setAttribute('fill', t > 0.92 ? 'rgba(239, 68, 68, 0.09)' : (t > 0.7 ? 'rgba(245, 158, 11, 0.10)' : 'rgba(14, 165, 164, 0.055)'));
// ---- nucleus: grows as cell approaches pluripotency, color shifts
const nucleusR = lerp(50, 95, smoothstep(0.6, 0.95, t));
const nucleus = document.getElementById('nucleus');
nucleus.setAttribute('r', nucleusR);
// Gradient transition: teal nucleus at low/mid intensity, amber stem at high, red at extreme
let nucleusFill = 'url(#nucleusGrad)';
if (t > 0.92) nucleusFill = 'url(#dangerGrad)';
else if (t > 0.7) nucleusFill = 'url(#stemGrad)';
nucleus.setAttribute('fill', nucleusFill);
// ---- halo: glows brighter through sweet spot, turns amber/red toward end
const halo = document.getElementById('halo');
let haloOpacity = 0.4 + 0.5 * Math.sin(t * Math.PI);
let haloFill = 'url(#membraneGrad)';
if (t > 0.92) haloFill = 'url(#dangerGrad)';
else if (t > 0.7) haloFill = 'url(#stemGrad)';
halo.setAttribute('opacity', haloOpacity.toFixed(2));
halo.setAttribute('fill', haloFill);
// ---- organelles
document.getElementById('organelles').innerHTML = renderOrganelles(state.intensity);
// ---- damage
document.getElementById('damage').innerHTML = renderDamage(state.intensity);
// ---- chromatin
document.getElementById('chromatin').innerHTML = renderChromatin(state.intensity);
// ---- yamanaka factors
document.getElementById('factors').innerHTML = renderFactors(state.intensity);
// ---- warning corona
const wc = document.getElementById('warningCorona');
const warnOpacity = smoothstep(0.78, 0.95, t);
wc.setAttribute('opacity', warnOpacity.toFixed(2));
wc.setAttribute('r', 175 + warnOpacity * 25);
// ---- side readouts
document.getElementById('ageValue').textContent = m.age;
document.getElementById('identityValue').textContent = m.identity;
document.getElementById('riskValue').textContent = m.riskLabel;
// gauges
const ageFraction = (72 - m.age) / 72; // how much progress in age reduction
setGauge('ageGauge', ageFraction, m.age < 5 ? 'danger' : (ageFraction > 0.4 && ageFraction < 0.85 ? 'ok' : 'warn'), 'cardAge');
setGauge('identityGauge', m.identity / 100, m.identity > 70 ? 'ok' : (m.identity > 30 ? 'warn' : 'danger'), 'cardIdentity');
setGauge('riskGauge', m.risk / 100, m.risk < 15 ? 'ok' : (m.risk < 60 ? 'warn' : 'danger'), 'cardRisk');
// ---- slider %
document.getElementById('sliderPct').textContent = state.intensity.toFixed(0);
// ---- status banner
const banner = document.getElementById('statusBanner');
const cfg = ZONE_BANNERS[m.zone];
banner.className = 'status-banner ' + cfg.cls;
document.getElementById('statusIcon').textContent = cfg.icon;
document.getElementById('statusTitle').textContent = cfg.title;
document.getElementById('statusDesc').textContent = cfg.desc;
// ---- annotations on viewer
const annoTL = document.getElementById('annoTL');
const annoTR = document.getElementById('annoTR');
const annoBL = document.getElementById('annoBL');
const annoBR = document.getElementById('annoBR');
annoTL.textContent = `EPI-DRIFT · ${t < 0.2 ? 'HIGH' : t < 0.5 ? 'RESETTING' : t < 0.7 ? 'YOUTHFUL' : 'ERASED'}`;
annoTR.textContent = `METHYLATION · ${t < 0.2 ? 'AGED' : t < 0.65 ? 'YOUTHFUL' : t < 0.9 ? 'EMBRYONIC' : 'BLANK'}`;
annoBL.textContent = `IDENTITY · ${m.identity > 80 ? 'INTACT' : m.identity > 40 ? 'WAVERING' : 'LOST'}`;
annoBR.textContent = `RISK · ${m.riskLabel.toUpperCase()}`;
}
function setGauge(gaugeId, fraction, klass, cardId) {
const el = document.getElementById(gaugeId);
el.style.transform = `scaleX(${clamp(fraction, 0, 1)})`;
const card = document.getElementById(cardId);
card.classList.remove('ok', 'warn', 'danger');
card.classList.add(klass);
}
// ============================================================
// EVENT WIRING
// ============================================================
const slider = document.getElementById('slider');
slider.addEventListener('input', (e) => {
state.intensity = parseFloat(e.target.value);
render();
});
document.querySelectorAll('.cell-type-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.cell-type-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
state.cellType = btn.dataset.type;
render();
});
});
function applyHashPreset() {
const raw = window.location.hash.replace('#', '').trim();
if (!raw) return;
const [type, value] = raw.split(':');
if (CELL_SHAPES[type]) {
state.cellType = type;
document.querySelectorAll('.cell-type-btn').forEach(b => {
b.classList.toggle('active', b.dataset.type === type);
});
}
const parsed = Number(value);
if (Number.isFinite(parsed)) {
state.intensity = clamp(parsed, 0, 100);
slider.value = state.intensity;
}
}
// Initial render
applyHashPreset();
render();
</script>
</body>
</html>

Xet Storage Details

Size:
47.8 kB
·
Xet hash:
e6c6fd623b0742a3d60dbe4a2b7caab280d11cc2075a76789e3ee1c65881c9ea

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.