← Blurr Motion content-svg-morph-minimal
Categorie content Tier 2 Techniek #33 Deps gsap

003 / Sequence

Een gebaar.
Drie betekenissen.

dot → arrow → check

1. Mechanisme — kopieer 1-op-1, geen styling-keuzes
// Mechanisme: content-svg-morph-minimal (techniek #33 — SVG morphing)
// Drie SVG-paden (dot -> arrow -> check) waarvan er één zichtbaar is.
// GSAP attr-tween op het 'd'-attribuut van het visible-path naar het volgende path.
// IntersectionObserver triggert auto-loop wanneer in viewport, pauzeert bij uitscroll.
// Subtiel: 1.5s morph duration, 2s gap tussen morphs, fade-cross tussen paths via opacity.
import gsap from 'https://esm.sh/[email protected]';
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const root = document.querySelector('.svg-morph-minimal');
if (root) {
  const paths = root.querySelectorAll('.morph-path');
  if (reduce) {
    paths.forEach((p, i) => p.setAttribute('opacity', i === paths.length - 1 ? '1' : '0'));
  } else if (paths.length >= 2) {
    let idx = 0;
    const active = paths[0];
    active.setAttribute('opacity', '1');
    paths.forEach((p, i) => { if (i !== 0) p.setAttribute('opacity', '0'); });
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting && !root.dataset.running) {
          root.dataset.running = '1';
          const tick = () => {
            if (!root.dataset.running) return;
            const next = (idx + 1) % paths.length;
            const target = paths[next].getAttribute('d');
            if (target) gsap.to(active, { attr: { d: target }, duration: 1.5, ease: 'power2.inOut' });
            idx = next;
            setTimeout(tick, 3500);
          };
          tick();
        } else if (!e.isIntersecting) {
          delete root.dataset.running;
        }
      });
    }, { threshold: 0.4 });
    io.observe(root);
  }
}
2. Skeleton — DOM + class-namen, mag herschikken
<!-- Skeleton: content-svg-morph-minimal -->
<section class="content-morph-min">
  <p class="eyebrow">003 / Sequence</p>
  <h2 class="title">Een gebaar.<br/>Drie betekenissen.</h2>
  <div class="svg-morph-minimal" aria-hidden="true">
    <svg viewBox="0 0 200 200" width="160" height="160">
      <path class="morph-path" d="M100,90 C106,90 110,94 110,100 C110,106 106,110 100,110 C94,110 90,106 90,100 C90,94 94,90 100,90Z"/>
      <path class="morph-path" d="M40,100 L160,100 L130,70 M160,100 L130,130"/>
      <path class="morph-path" d="M50,105 L85,140 L160,65"/>
    </svg>
  </div>
  <p class="caption">dot → arrow → check</p>
</section>
3. Styling-template — verplicht eigen invulling per merk
/* Styling: content-svg-morph-minimal — cream + ink, Inter, generous whitespace */
:root {
  --block-bg: #F4F1EB;
  --block-fg: #0A0A0A;
  --block-accent: rgba(10,10,10,0.4);
}
.content-morph-min {
  background: var(--block-bg);
  color: var(--block-fg);
  font-family: 'Inter', system-ui, sans-serif;
  padding: clamp(6rem, 14vw, 14rem) clamp(2rem, 8vw, 10rem);
  display: grid;
  gap: clamp(3rem, 6vw, 5rem);
  justify-items: start;
  min-height: 80vh;
}
.eyebrow {
  font-size: 0.75rem; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--block-accent); margin: 0;
}
.title {
  font-size: clamp(2rem, 4.5vw, 4rem); font-weight: 300; line-height: 1.05;
  letter-spacing: -0.02em; margin: 0; max-width: 18ch;
}
.svg-morph-minimal svg path {
  fill: none; stroke: var(--block-fg); stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
}
.caption {
  font-size: 0.8rem; letter-spacing: 0.04em;
  color: var(--block-accent); margin: 0; font-feature-settings: 'tnum';
}