Shadow rise with spring overshoot and flipping percentage chips.
<!doctype html>
<!-- Example: kpi-card-pop — focal hero metric · stroke-dashoffset ring · supporting sparklines -->
<html data-duration="5" data-aspect="16:9"><head><style>
:root {
--cream:#f6f5f1; --cream-2:#efece4; --ink:#0a0a0a; --mute:#6b6862;
--line:#e3dfd3; --signal:#ff3b1f; --signal-2:#ff6a4a; --frame:#ffb800;
--green:#1f8a5b; --blue:#2b66ff;
}
* { box-sizing: border-box; }
body { margin:0; }
body { background: #f4f1ea; color: var(--ink); height: 100vh; overflow: hidden;
font-family: ui-sans-serif, system-ui; position: relative;
display: grid; grid-template-columns: 1.55fr 1fr; gap: 48px; padding: 64px 80px; align-items: center; }
.rule { position: absolute; left: 80px; right: 80px; top: 56px; height: 1px;
background: rgba(10,10,10,.16); transform-origin: left; will-change: transform; }
.hd { position: absolute; left: 80px; top: 28px; right: 80px;
display: flex; justify-content: space-between; align-items: baseline;
font-family: ui-monospace, monospace; font-size: 11px; letter-spacing: .26em;
text-transform: uppercase; color: var(--mute); }
.hd .live { color: var(--signal); display: inline-flex; align-items: center; gap: 7px; }
.hd .live::before { content:""; width: 7px; height: 7px; border-radius:50%; background: var(--signal); }
.grain { position: absolute; inset: 0; pointer-events: none; opacity: .07; mix-blend-mode: multiply;
background-image: radial-gradient(rgba(0,0,0,.6) 1px, transparent 1px);
background-size: 3px 3px; }
.hero { position: relative; display: grid; grid-template-columns: auto 1fr; gap: 36px; align-items: center; }
.hero .ringwrap { position: relative; width: 280px; height: 280px; }
.hero svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.hero .track { fill: none; stroke: rgba(10,10,10,.08); stroke-width: 14; }
.hero .ring { fill: none; stroke: var(--signal); stroke-width: 14; stroke-linecap: round; }
.hero .ringlbl { position: absolute; inset: 0; display: grid; place-items: center; text-align: center; }
.hero .ringlbl b { font-family: ui-monospace, monospace; font-size: 14px; letter-spacing: .2em;
text-transform: uppercase; color: var(--mute); font-weight: 500; display: block; }
.hero .ringlbl em { font-style: normal; font-size: 60px; font-weight: 700; letter-spacing: -0.03em;
color: var(--ink); display: block; margin-top: 2px; font-variant-numeric: tabular-nums; }
.hero .meta { display: grid; gap: 14px; }
.hero .meta .lbl { font-family: ui-monospace, monospace; font-size: 12px; letter-spacing: .22em;
text-transform: uppercase; color: var(--mute); }
.hero .meta .v { font-size: 96px; font-weight: 700; letter-spacing: -0.04em; line-height: .9;
font-variant-numeric: tabular-nums; }
.hero .meta .v small { font-size: 36px; color: var(--mute); margin-left: 6px; font-weight: 500; letter-spacing: -0.02em; }
.hero .meta .copy { font-family: ui-serif, Georgia, serif; font-size: 19px; color: rgba(10,10,10,.7);
line-height: 1.4; max-width: 30ch; font-style: italic; }
.side { display: grid; gap: 28px; opacity: 0; transform: translateY(18px); }
.tile { display: grid; gap: 8px; padding-bottom: 22px; border-bottom: 1px solid rgba(10,10,10,.12); }
.tile:last-child { border-bottom: 0; }
.tile .lbl { font-family: ui-monospace, monospace; font-size: 11px; letter-spacing: .22em;
text-transform: uppercase; color: var(--mute); }
.tile .row { display: flex; justify-content: space-between; align-items: end; gap: 16px; }
.tile .v { font-size: 44px; font-weight: 600; letter-spacing: -0.02em;
font-variant-numeric: tabular-nums; line-height: 1; }
.tile .delta { font-family: ui-monospace, monospace; font-size: 12px; letter-spacing: .08em;
color: rgba(255,59,31,.85); margin-top: 4px; }
.tile .delta.dn { color: rgba(31,138,91,.85); }
.tile svg.spark { width: 100%; height: 38px; }
.tile .spark path { fill: none; stroke: var(--signal); stroke-width: 1.6; }
.tile .spark .area { fill: rgba(255,59,31,.12); stroke: none; }
.foot { position: absolute; left: 80px; right: 80px; bottom: 28px;
display: flex; justify-content: space-between;
font-family: ui-monospace, monospace; font-size: 11px; letter-spacing: .26em;
text-transform: uppercase; color: var(--mute); }
</style></head><body>
<div class="rule" id="rl"></div>
<div class="hd">
<span>Q3 · render telemetry · clip-id 0182</span>
<span class="live">live · t<span id="tc">0.00</span>s</span>
</div>
<div class="hero" id="hero">
<div class="ringwrap">
<svg viewBox="0 0 200 200">
<circle class="track" cx="100" cy="100" r="86"/>
<circle class="ring" id="ring" cx="100" cy="100" r="86"
stroke-dasharray="540.35" stroke-dashoffset="540.35"/>
</svg>
<div class="ringlbl"><b>Throughput</b><em id="pct">0%</em></div>
</div>
<div class="meta">
<span class="lbl">Render time · p50</span>
<div class="v"><span id="hv">0.0</span><small>s / clip</small></div>
<div class="copy">38% faster than last quarter — and the long tail is shorter, too.</div>
</div>
</div>
<div class="side" id="side">
<div class="tile">
<span class="lbl">Frames / sec</span>
<div class="row">
<span class="v" id="sv1">0</span>
<svg class="spark" viewBox="0 0 120 38" preserveAspectRatio="none">
<path class="area" id="sp1a" d=""/>
<path id="sp1" d=""/>
</svg>
</div>
<span class="delta dn">▲ 12 fps · 7-day</span>
</div>
<div class="tile">
<span class="lbl">Bundle · gzipped</span>
<div class="row">
<span class="v" id="sv2">0<small style="font-size:18px;color:var(--mute);margin-left:4px;letter-spacing:-0.01em;"> kb</small></span>
<svg class="spark" viewBox="0 0 120 38" preserveAspectRatio="none">
<path class="area" id="sp2a" d=""/>
<path id="sp2" d=""/>
</svg>
</div>
<span class="delta">▲ 2 kb · within budget</span>
</div>
</div>
<div class="grain"></div>
<div class="foot">
<span>// rev. <span id="rv">000</span></span>
<span>1920 × 1080 · 60 fps</span>
</div>
<script>
var ring = document.getElementById('ring');
var pct = document.getElementById('pct');
var hv = document.getElementById('hv');
var sv1 = document.getElementById('sv1');
var sv2 = document.getElementById('sv2');
var sp1 = document.getElementById('sp1');
var sp1a = document.getElementById('sp1a');
var sp2 = document.getElementById('sp2');
var sp2a = document.getElementById('sp2a');
var side = document.getElementById('side');
var hero = document.getElementById('hero');
var rl = document.getElementById('rl');
var tc = document.getElementById('tc');
var rv = document.getElementById('rv');
var C = 2 * Math.PI * 86; // ring circumference (~540.35)
var TARGET_PCT = 0.74;
var TARGET_HV = 4.2;
var TARGET_S1 = 60;
var TARGET_S2 = 184;
// Deterministic sparkline data — seeded sine + index-driven jitter, no random.
function spark(seed, n, baseline) {
var pts = [];
for (var i = 0; i < n; i++) {
var u = i / (n - 1);
var y = baseline + Math.sin(u * 6.2 + seed) * 7 + Math.sin(u * 13.1 + seed * 0.7) * 3 + ((i * 7) % 5) - 2;
pts.push([u * 120, 19 + y]);
}
return pts;
}
function path(pts, k) {
// Reveal-from-left: clip to fractional length k of polyline.
var d = '';
var lastX = pts[0][0], lastY = pts[0][1];
var totalLen = pts.length - 1;
var cut = k * totalLen;
for (var i = 0; i < pts.length; i++) {
if (i === 0) { d = 'M ' + pts[0][0].toFixed(1) + ' ' + pts[0][1].toFixed(1); continue; }
if (i <= Math.floor(cut)) {
d += ' L ' + pts[i][0].toFixed(1) + ' ' + pts[i][1].toFixed(1);
lastX = pts[i][0]; lastY = pts[i][1];
} else if (i === Math.floor(cut) + 1) {
var frac = cut - Math.floor(cut);
var px = pts[i-1][0] + (pts[i][0] - pts[i-1][0]) * frac;
var py = pts[i-1][1] + (pts[i][1] - pts[i-1][1]) * frac;
d += ' L ' + px.toFixed(1) + ' ' + py.toFixed(1);
lastX = px; lastY = py;
break;
}
}
return { line: d, area: d + ' L ' + lastX.toFixed(1) + ' 38 L ' + pts[0][0].toFixed(1) + ' 38 Z' };
}
var SP1 = spark(1.7, 24, -2);
var SP2 = spark(4.2, 24, 2);
var t = 0;
var reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
function easeOut(x){ return 1 - Math.pow(1 - x, 3); }
function damped(x){ return 1 - Math.cos(x * Math.PI * 1.5) * Math.exp(-3 * x); }
function render() {
var tt = reduced ? 3.0 : t;
// Hairline rule slides in 0.0–0.7s.
var ur = easeOut(Math.min(1, tt / 0.7));
rl.style.transform = 'scaleX(' + ur.toFixed(3) + ')';
// Hero ring + value: 0.4–2.8s entrance.
var uh = Math.max(0, Math.min(1, (tt - 0.4) / 2.4));
var eh = easeOut(uh);
var pctVal = TARGET_PCT * eh;
ring.setAttribute('stroke-dashoffset', String(C * (1 - pctVal)));
pct.textContent = Math.round(pctVal * 100) + '%';
hv.textContent = (TARGET_HV * eh).toFixed(1);
// Hero value scale-in via damped cosine.
var uhs = Math.max(0, Math.min(1, (tt - 0.3) / 1.1));
hero.style.transform = 'translateY(' + ((1 - easeOut(uhs)) * 12).toFixed(1) + 'px)';
// Side metrics fade up 1.4–2.6s.
var us = Math.max(0, Math.min(1, (tt - 1.4) / 1.2));
var es = easeOut(us);
side.style.opacity = es;
side.style.transform = 'translateY(' + ((1 - es) * 18).toFixed(1) + 'px)';
sv1.textContent = Math.round(TARGET_S1 * es);
sv2.firstChild.textContent = Math.round(TARGET_S2 * es);
// Sparklines draw left-to-right 1.7–3.4s.
var usp = Math.max(0, Math.min(1, (tt - 1.7) / 1.7));
var p1 = path(SP1, easeOut(usp));
var p2 = path(SP2, easeOut(usp));
sp1.setAttribute('d', p1.line);
sp1a.setAttribute('d', p1.area);
sp2.setAttribute('d', p2.line);
sp2a.setAttribute('d', p2.area);
// Post-settle: hero ring breathes (0.5% range), starts after entrance settles.
var post = Math.max(0, tt - 3.0);
var breathe = 1 + 0.005 * Math.sin(post * 1.4);
hero.style.transformOrigin = 'left center';
hero.style.transform = 'translateY(' + ((1 - easeOut(uhs)) * 12).toFixed(1) + 'px) scale(' + breathe.toFixed(4) + ')';
tc.textContent = tt.toFixed(2);
rv.textContent = String(Math.floor(tt * 60)).padStart(3, '0');
}
addEventListener('hf-seek', function(e) { t = e.detail.time; render(); });
render();
</script>
</body></html>