← Blurr Motion content-houdini-paint-brutalist
Categorie content Tier 3 Techniek #42 Deps gsap, css-houdini-paint-api

// content / houdini paint / brutalist / #42

RAW SYSTEM NO APOLOGIES.

Hard edges. No gradients. No softness. The grid is the message — painted pixel-by-pixel by a Houdini worklet at runtime.

1. Mechanisme — kopieer 1-op-1, geen styling-keuzes
// Mechanisme: content-houdini-paint-brutalist
// Houdini Paint Worklet — registreer via blob-URL (geen extern bestand nodig).
const workletCode = `
registerPaint('brutal-grid', class {
  static get inputProperties() { return ['--grid-bg','--grid-fg','--grid-density','--grid-seed']; }
  paint(ctx, size, props) {
    const bg = (props.get('--grid-bg').toString() || '#F4F1EB').trim();
    const fg = (props.get('--grid-fg').toString() || '#0A0A0A').trim();
    const density = parseInt(props.get('--grid-density').toString()) || 14;
    let seed = parseInt(props.get('--grid-seed').toString()) || 7;
    const rand = () => { seed = (seed * 9301 + 49297) % 233280; return seed / 233280; };
    ctx.fillStyle = bg; ctx.fillRect(0, 0, size.width, size.height);
    ctx.fillStyle = fg;
    for (let i = 0; i < density; i++) {
      const w = Math.floor(rand() * size.width * 0.5) + 40;
      const h = Math.floor(rand() * size.height * 0.4) + 30;
      const x = Math.floor(rand() * size.width) - w/2;
      const y = Math.floor(rand() * size.height) - h/2;
      ctx.fillRect(x, y, w, h);
    }
  }
});`;
if ('paintWorklet' in CSS) {
  const blob = new Blob([workletCode], { type: 'text/javascript' });
  CSS.paintWorklet.addModule(URL.createObjectURL(blob));
}
// Tekst-reveal (brutal & instant): stagger 0.03, duration 0.5, power4.out.
import gsap from 'https://esm.sh/gsap@3.12.5';
import { ScrollTrigger } from 'https://esm.sh/gsap@3.12.5/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
  gsap.fromTo('.brutal-line', { yPercent: 110, autoAlpha: 0 },
    { yPercent: 0, autoAlpha: 1, duration: 0.55, stagger: 0.03, ease: 'power4.out',
      scrollTrigger: { trigger: '.brutal-stage', start: 'top 80%', once: true } });
}
2. Skeleton — DOM + class-namen, mag herschikken
<!-- Skeleton: content-houdini-paint-brutalist -->
<section class="brutal-stage">
  <div class="brutal-paint"></div>
  <div class="brutal-content">
    <p class="brutal-meta">// content / houdini paint / brutalist</p>
    <h2><span class="brutal-line">RAW</span><span class="brutal-line">SYSTEM</span><span class="brutal-line">NO APOLOGIES.</span></h2>
    <p class="brutal-body">Hard edges. No gradients. No softness. The grid is the message.</p>
  </div>
</section>
3. Styling-template — verplicht eigen invulling per merk
/* Styling: content-houdini-paint-brutalist */
:root {
  --block-bg: #F4F1EB;
  --block-fg: #0A0A0A;
  --block-accent: #0A0A0A;
}
.brutal-stage { position: relative; min-height: 80vh; isolation: isolate; }
.brutal-paint {
  position: absolute; inset: 0; z-index: 0;
  --grid-bg: #F4F1EB; --grid-fg: #0A0A0A;
  --grid-density: 16; --grid-seed: 7;
  background: paint(brutal-grid);
  /* Fallback: brutal repeating bands wanneer Houdini ontbreekt */
  background:
    repeating-linear-gradient(90deg, #0A0A0A 0 14vw, transparent 14vw 22vw),
    repeating-linear-gradient(0deg,  #0A0A0A 0 9vw,  transparent 9vw 18vw),
    #F4F1EB;
}
@supports (background: paint(brutal-grid)) {
  .brutal-paint { background: paint(brutal-grid); }
}
@media (prefers-reduced-motion: reduce) {
  .brutal-line { transform: none !important; opacity: 1 !important; }
}