← Blurr Motion footer-noise-bg-quiet
Categorie footers Tier 2 Techniek #35 Deps simplex-noise@4.0.3, gsap@3.12.5
1. Mechanisme — kopieer 1-op-1, geen styling-keuzes
// Mechanisme: footer-noise-bg-quiet (canvas2D simplex noise, sub-2% ink)
import { createNoise2D } from 'https://esm.sh/simplex-noise@4.0.3';
import gsap from 'https://esm.sh/gsap@3.12.5';

const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const cv = document.querySelector('.noise-canvas-quiet');
if (cv) {
  const ctx = cv.getContext('2d');
  const dpr = Math.min(window.devicePixelRatio || 1, 2);
  const resize = () => {
    cv.width = Math.floor(cv.offsetWidth * dpr);
    cv.height = Math.floor(cv.offsetHeight * dpr);
  };
  resize();
  window.addEventListener('resize', resize);

  const noise2D = createNoise2D();
  const draw = (t) => {
    const w = cv.width, h = cv.height;
    const img = ctx.createImageData(w, h);
    const data = img.data;
    const scale = 0.012;
    for (let y = 0; y < h; y += 2) {
      for (let x = 0; x < w; x += 2) {
        const n = (noise2D(x * scale, y * scale + t) + 1) * 0.5;
        const v = (n * 255) | 0;
        const a = 5; // sub-2% ink
        for (let oy = 0; oy < 2; oy++) {
          for (let ox = 0; ox < 2; ox++) {
            const i = ((y + oy) * w + (x + ox)) * 4;
            data[i] = data[i+1] = data[i+2] = v;
            data[i+3] = a;
          }
        }
      }
    }
    ctx.putImageData(img, 0, 0);
  };

  if (reduce) {
    draw(0);
  } else {
    let t = 0;
    (function tick(){
      t += 0.0015;
      draw(t);
      requestAnimationFrame(tick);
    })();
  }
}

if (!reduce) {
  gsap.from('.fnq-reveal', {
    autoAlpha: 0,
    y: 12,
    duration: 1.2,
    ease: 'power2.out',
    stagger: 0.07
  });
}
2. Skeleton — DOM + class-namen, mag herschikken
<!-- Skeleton: footer-noise-bg-quiet -->
<footer class="fnq">
  <canvas class="noise-canvas-quiet" aria-hidden="true"></canvas>
  <div class="fnq-inner">
    <p class="fnq-eyebrow fnq-reveal">Studio note</p>
    <p class="fnq-manifest fnq-reveal">A quiet practice. Made with care, slowly, for work that lasts.</p>
    <ul class="fnq-links fnq-reveal">
      <li><a href="#">Work</a></li>
      <li><a href="#">Studio</a></li>
      <li><a href="#">Contact</a></li>
      <li><a href="#">Index</a></li>
    </ul>
    <p class="fnq-meta fnq-reveal">© 2026</p>
  </div>
</footer>
3. Styling-template — verplicht eigen invulling per merk
/* Styling: footer-noise-bg-quiet */
.fnq {
  --bg: #F6F4EF;
  --fg: #161513;
  --muted: #6B6862;
  position: relative;
  background: var(--bg);
  color: var(--fg);
  padding: clamp(5rem, 12vw, 10rem) clamp(1.5rem, 4vw, 4rem);
  min-height: 70vh;
  overflow: hidden;
}
.noise-canvas-quiet {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
}
.fnq-inner { position: relative; max-width: 540px; }
.fnq-eyebrow {
  font-family: 'Archivo', sans-serif;
  font-size: 0.75rem; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--muted);
  margin: 0 0 2rem;
}
.fnq-manifest {
  font-family: 'Fraunces', Georgia, serif;
  font-weight: 300; font-size: clamp(1.5rem, 2.4vw, 2rem);
  line-height: 1.35; letter-spacing: -0.01em;
  margin: 0 0 3.5rem;
}
.fnq-links {
  list-style: none; padding: 0; margin: 0 0 2rem;
  display: flex; flex-wrap: wrap; gap: 1.5rem;
}
.fnq-links a {
  font-family: 'Archivo', sans-serif;
  font-size: 0.875rem; color: var(--fg);
  text-decoration: none; border-bottom: 1px solid transparent;
  padding-bottom: 2px; transition: border-color .3s ease;
}
.fnq-links a:hover { border-color: var(--fg); }
.fnq-meta {
  font-family: 'Archivo', sans-serif;
  font-size: 0.75rem; color: var(--muted);
  margin: 0;
}