← Blurr Motion content-houdini-paint-quiet
Categorie content Tier 3 Techniek #42 Deps gsap

Notes / Quiet

Restraint is a kind of clarity, expressed through what is left out.

A surface that whispers rather than shouts. The grain is barely there — under one percent ink — present only when the eye searches for it. Otherwise it sits behind the words like a held breath.

Houdini paint worklets let the browser draw this texture procedurally, without an image asset, without a network request. The fallback is an SVG data-uri, also weightless, also quiet.

Negative space does the rest of the work.

1. Mechanisme — kopieer 1-op-1, geen styling-keuzes
// Mechanisme: content-houdini-paint-quiet
// Houdini paint worklet (technique #42) — subtle dot/cross hatch, sub-1% opacity ink.
// Loaded via Blob URL to avoid extra HTTP. SVG data-uri fallback when Houdini unsupported.
import gsap from 'https://esm.sh/gsap@3.12.5';
import{ScrollTrigger}from 'https://esm.sh/gsap@3.12.5/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);

const workletCode = `registerPaint('quiet-grain',class{static get inputProperties(){return['--grain-ink','--grain-step']}paint(ctx,geom,props){const ink=(props.get('--grain-ink').toString()||'rgba(10,10,10,.06)').trim();const step=parseFloat(props.get('--grain-step').toString())||14;ctx.fillStyle=ink;for(let y=0;y<geom.height;y+=step){for(let x=0;x<geom.width;x+=step){ctx.fillRect(x,y,1,1);ctx.fillRect(x+step/2,y+step/2,1,1)}}}});`;

if(window.CSS&&CSS.paintWorklet){
  const url=URL.createObjectURL(new Blob([workletCode],{type:'text/javascript'}));
  CSS.paintWorklet.addModule(url).catch(()=>{});
}else{
  // SVG dot-pattern data-uri fallback
  const svg='data:image/svg+xml;utf8,'+encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14"><circle cx="1" cy="1" r="0.6" fill="rgba(10,10,10,0.06)"/><circle cx="8" cy="8" r="0.6" fill="rgba(10,10,10,0.06)"/></svg>');
  document.querySelectorAll('.quiet-paint').forEach(el=>{el.style.backgroundImage='url("'+svg+'")';el.style.backgroundRepeat='repeat';});
}

if(!window.matchMedia('(prefers-reduced-motion: reduce)').matches){
  ScrollTrigger.batch('.quiet-reveal',{
    onEnter:b=>gsap.fromTo(b,{autoAlpha:0,y:18},{autoAlpha:1,y:0,duration:1.2,ease:'power2.out',stagger:0.07}),
    start:'top 88%',once:true
  });
}
2. Skeleton — DOM + class-namen, mag herschikken
<!-- Skeleton: content-houdini-paint-quiet -->
<section class="quiet-paint">
  <div class="col">
    <p class="eyebrow">Eyebrow</p>
    <h2 class="quiet-reveal">Headline in serif</h2>
    <p class="quiet-reveal">Body paragraph...</p>
    <p class="quiet-reveal">Body paragraph...</p>
  </div>
</section>
3. Styling-template — verplicht eigen invulling per merk
/* Styling: content-houdini-paint-quiet */
:root {
  --block-bg: #F4F1EB;
  --block-fg: #0A0A0A;
  --block-accent: rgba(10,10,10,.06);
  --grain-ink: rgba(10,10,10,.06);
  --grain-step: 14;
}
.quiet-paint {
  background: paint(quiet-grain), #F4F1EB;
  background-color: #F4F1EB;
}