// Mechanisme: content-houdini-paint-kinetic
// Houdini Paint Worklet — registreer via blob-URL (geen extern bestand nodig).
const workletCode = `
registerPaint('kinetic-stripes', class {
static get inputProperties() { return ['--kin-bg','--kin-fg','--kin-accent','--kin-phase','--kin-stripe-count']; }
paint(ctx, size, props) {
const bg = (props.get('--kin-bg').toString() || '#0A0A0A').trim();
const fg = (props.get('--kin-fg').toString() || '#F4F1EB').trim();
const accent = (props.get('--kin-accent').toString() || '#FF4A1C').trim();
const phase = parseFloat(props.get('--kin-phase').toString()) || 0;
const count = parseInt(props.get('--kin-stripe-count').toString()) || 14;
ctx.fillStyle = bg; ctx.fillRect(0, 0, size.width, size.height);
const stripeH = size.height / count;
const accentIndex = Math.floor(phase * count) % count;
for (let i = 0; i < count; i++) {
const wave = Math.sin((i * 0.6) + phase * Math.PI * 2) * size.width * 0.18;
const x = -size.width * 0.25 + wave + (phase * size.width * 0.5);
const w = size.width * 0.65;
const y = i * stripeH;
ctx.fillStyle = (i === accentIndex) ? accent : fg;
ctx.fillRect(x, y, w, stripeH * 0.62);
}
}
});`;
if ('paintWorklet' in CSS) {
const blob = new Blob([workletCode], { type: 'text/javascript' });
CSS.paintWorklet.addModule(URL.createObjectURL(blob));
}
// rAF-driven --kin-phase animation (continuous kinetic motion)
import gsap from 'https://esm.sh/gsap@3.12.5';
gsap.registerPlugin();
const stage = document.querySelector('.kin-stage');
if (stage && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
let phase = 0; let raf;
const tick = () => { phase = (phase + 0.0035) % 1; stage.style.setProperty('--kin-phase', phase.toFixed(4)); raf = requestAnimationFrame(tick); };
raf = requestAnimationFrame(tick);
// Snappy intro reveal: stagger 0.04, duration 0.7, back.out(1.4)
gsap.fromTo('.kin-line > span', { yPercent: 115, autoAlpha: 0 },
{ yPercent: 0, autoAlpha: 1, duration: 0.75, stagger: 0.04, ease: 'back.out(1.4)' });
} <!-- Skeleton: content-houdini-paint-kinetic -->
<section class="kin-stage">
<div class="kin-paint"></div>
<div class="kin-content">
<p class="kin-meta">// content / houdini / kinetic / #42</p>
<h2>
<span class="kin-line"><span>FAST</span></span>
<span class="kin-line"><span>LOUD</span></span>
<span class="kin-line"><span>UNMISSABLE.</span></span>
</h2>
<p class="kin-body">Stripes painted live by a Houdini worklet, driven by rAF.</p>
</div>
</section> /* Styling: content-houdini-paint-kinetic */
:root {
--block-bg: #0A0A0A;
--block-fg: #F4F1EB;
--block-accent: #FF4A1C;
}
.kin-stage {
position: relative; min-height: 80vh; isolation: isolate; overflow: hidden;
--kin-bg: #0A0A0A; --kin-fg: #F4F1EB; --kin-accent: #FF4A1C;
--kin-phase: 0; --kin-stripe-count: 14;
}
.kin-paint {
position: absolute; inset: 0; z-index: 0;
/* Fallback: animated linear-gradient stripes */
background: repeating-linear-gradient(180deg, #F4F1EB 0 18px, transparent 18px 60px), #0A0A0A;
animation: kin-slide 4s linear infinite;
}
@supports (background: paint(kinetic-stripes)) {
.kin-paint { background: paint(kinetic-stripes); animation: none; }
}
@keyframes kin-slide { from { background-position: 0 0; } to { background-position: 0 60px; } }
@property --kin-phase { syntax: '<number>'; inherits: true; initial-value: 0; }
@property --kin-stripe-count { syntax: '<integer>'; inherits: true; initial-value: 14; }
@media (prefers-reduced-motion: reduce) {
.kin-paint { animation: none !important; }
.kin-line > span { transform: none !important; opacity: 1 !important; }
}