Four-stage clip wipe with chromatic edge and SVG film grain.
<!doctype html>
<!-- Example: wipe-bright — 4-stage clip wipe + chromatic edge + SVG film grain -->
<html data-duration="3" 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(--ink); height: 100vh; overflow: hidden; position: relative;
font-family: ui-sans-serif, system-ui; color: white; }
.scene { position: absolute; inset: 0; display: grid; place-items: center; }
#a { background: linear-gradient(135deg, #1a1a1a, #0a0a0a); color: white; }
#b { background: linear-gradient(135deg, var(--signal), var(--frame)); color: var(--ink);
clip-path: polygon(-10% 100%, -10% 100%, 110% 100%, 110% 100%); }
.scene .stack { text-align: center; }
.scene .big { font-size: 132px; font-weight: 800; letter-spacing: -0.04em; line-height: 1; }
.scene .lbl { font-family: ui-monospace, monospace; font-size: 12px; letter-spacing: .25em;
text-transform: uppercase; margin-top: 14px; opacity: .7; }
.gh { position: absolute; pointer-events: none; }
.grain { position: absolute; inset: 0; pointer-events: none; opacity: 0;
mix-blend-mode: overlay; }
.edge { position: absolute; inset: 0; pointer-events: none; opacity: 0; }
.edge::before, .edge::after { content: ''; position: absolute; left: 0; right: 0;
height: 2px; }
</style></head><body>
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
<defs>
<filter id="film">
<feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" seed="6"/>
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .35 0"/>
</filter>
</defs>
</svg>
<div class="scene" id="a">
<div class="stack">
<div class="big">BEFORE</div>
<div class="lbl">scene 01 · 16:9 · matte</div>
</div>
</div>
<div class="scene" id="b">
<div class="stack">
<div class="big">AFTER</div>
<div class="lbl">scene 02 · brand layer</div>
</div>
</div>
<svg class="grain" id="gr" preserveAspectRatio="none">
<rect width="100%" height="100%" filter="url(#film)"/>
</svg>
<script>
var b = document.getElementById('b');
var gr = document.getElementById('gr');
var t = 0;
var reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
function ease(x){ return x < 0.5 ? 2*x*x : 1 - Math.pow(-2*x+2, 2)/2; }
function render() {
var tt = reduced ? 4 : t;
// 4 keyframes: hold, accel, decel, settle.
// The wipe runs from t=1.0 → t=3.4 with a brief pause mid-stroke.
var u = 0;
if (tt >= 1.0 && tt < 2.0) u = ease((tt - 1.0) / 1.0) * 0.5;
else if (tt >= 2.0 && tt < 2.3) u = 0.5; // brief pause
else if (tt >= 2.3 && tt < 3.4) u = 0.5 + ease((tt - 2.3) / 1.1) * 0.5;
else if (tt >= 3.4) u = 1;
var top = 100 - u * 120;
var skew = 10;
b.style.clipPath =
'polygon(-10% 100%, -10% ' + (top + skew) + '%, 110% ' + (top - skew) + '%, 110% 100%)';
// Film grain pulses across the wipe duration.
var grain = u > 0 && u < 1 ? Math.sin(u * Math.PI) * 0.6 : 0;
gr.style.opacity = grain;
// Chromatic edge offset peaks at the wipe's mid-stroke.
var split = Math.max(0, 1 - Math.abs(u - 0.5) * 2) * 6;
b.style.filter = 'drop-shadow(' + (-split).toFixed(1) + 'px 0 0 rgba(43,102,255,.5)) drop-shadow(' + split.toFixed(1) + 'px 0 0 rgba(255,59,31,.5))';
}
addEventListener('hf-seek', function(e) { t = e.detail.time; render(); });
render();
</script>
</body></html>