40×20 wave-reveal grid with HSL rotation and terminal type-in.
<!doctype html>
<!-- Example: end-card-install — 40×20 generative grid wave · HSL rotate · terminal type-in -->
<html data-duration="7" 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: var(--cream); color: var(--ink); height: 100vh; overflow: hidden;
display: grid; place-items: center; font-family: ui-sans-serif, system-ui;
position: relative; }
.wave { position: absolute; inset: 0; display: grid;
grid-template-columns: repeat(40, 1fr); grid-template-rows: repeat(20, 1fr);
gap: 1px; padding: 24px; pointer-events: none; }
.wave i { background: var(--signal); border-radius: 2px; opacity: 0;
will-change: transform, opacity, background; }
.card { position: relative; width: 820px; padding: 64px; background: white; border-radius: 28px;
border: 1px solid var(--line); box-shadow: 0 40px 100px -50px rgba(0,0,0,.3);
text-align: center; opacity: 0; transform: translateY(28px) scale(.94); z-index: 2;
will-change: transform, opacity; }
.logo { width: 64px; height: 64px; border-radius: 16px; background: var(--ink);
color: var(--cream); display: inline-grid; place-items: center; font-weight: 800;
font-family: ui-monospace, monospace; font-size: 28px; margin-bottom: 22px; }
h1 { font-size: 56px; font-weight: 600; letter-spacing: -0.02em; margin: 0 0 12px; }
p { color: var(--mute); margin: 0 0 28px; font-size: 18px; }
.cmd { display: inline-flex; align-items: center; gap: 8px; background: var(--ink); color: var(--cream);
padding: 16px 24px; font-family: ui-monospace, monospace; font-size: 15px;
border-radius: 12px; letter-spacing: .04em; }
.cmd em { color: var(--signal); font-style: normal; }
.caret { display: inline-block; width: 9px; height: 1em; background: var(--cream);
vertical-align: -0.16em; margin-left: 2px; }
.foot { position: absolute; left: 0; right: 0; bottom: 24px; text-align: center;
font-family: ui-monospace, monospace; font-size: 11px; letter-spacing: .25em;
color: var(--mute); z-index: 3; }
</style></head><body>
<div class="wave" id="w"></div>
<div class="card" id="c">
<div class="logo">HF</div>
<h1>Ship video like code.</h1>
<p>Composable HTML compositions, rendered in your browser or via CLI.</p>
<div class="cmd"><span>$</span><span id="tx"></span><span class="caret"></span></div>
</div>
<div class="foot">hyperframes.dev · MIT · v1.0</div>
<script>
var w = document.getElementById('w');
var c = document.getElementById('c');
var tx = document.getElementById('tx');
var COLS = 40, ROWS = 20;
var cells = [];
for (var r = 0; r < ROWS; r++) {
for (var col = 0; col < COLS; col++) {
var i = document.createElement('i');
w.appendChild(i);
cells.push({ el: i, c: col, r: r });
}
}
var FULL = 'npx hyperframes init';
var t = 0;
var reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
function back(x){ var c1=1.70158, c3=c1+1; return 1 + c3*Math.pow(x-1,3) + c1*Math.pow(x-1,2); }
function render() {
var tt = reduced ? 3 : t;
// Radial wave reveals the grid: cells light up as a ring expands from center.
var cx = COLS / 2, cy = ROWS / 2;
var waveR = tt * 12;
cells.forEach(function(o) {
var d = Math.hypot(o.c - cx, o.r - cy);
var u = Math.max(0, Math.min(1, (waveR - d) / 4));
var fade = Math.max(0, Math.min(1, (waveR - d - 4) / 12));
// HSL hue rotates around brand color as the wave passes.
var hue = 8 + (d / 14) * 36 + tt * 12;
o.el.style.background = 'hsl(' + hue.toFixed(0) + ', 90%, 55%)';
o.el.style.opacity = String(u * (0.35 + 0.65 * (1 - fade * 0.6)));
o.el.style.transform = 'scale(' + (0.5 + u * 0.8).toFixed(2) + ')';
});
// Card pops in after the wave starts.
var uc = Math.max(0, Math.min(1, (tt - 1.2) / 1.0));
var ec = back(uc);
c.style.opacity = uc;
c.style.transform = 'translateY(' + ((1 - ec) * 28).toFixed(1) + 'px) scale(' + (0.94 + 0.06 * ec).toFixed(3) + ')';
// Terminal command types in.
var ut = Math.max(0, Math.min(1, (tt - 2.6) / 1.6));
var n = Math.floor(ut * FULL.length);
tx.innerHTML = '<em>' + FULL.slice(0, n) + '</em>';
}
addEventListener('hf-seek', function(e) { t = e.detail.time; render(); });
render();
</script>
</body></html>