← Blurr Motion form-success-burst-minimal
Categorie forms Tier 1 Techniek #19 Deps gsap
— contact

Stuur een bericht.

1. Mechanisme — kopieer 1-op-1, geen styling-keuzes
// Mechanisme: form-success-burst-minimal
// Submit -> form fadet uit (autoAlpha 0, scale .95) -> success-state in (check + tekst).
// Single-pulse op check-icon. 6-dot radial burst, fade out 0.6s.
// AUTO-DEMO: na 2s init, daarna elke 5s een nieuwe submit-cycle.
import gsap from 'https://esm.sh/[email protected]';

(() => {
  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const root = document.querySelector('.fsb-mn');
  if (!root) return;
  const form = root.querySelector('.fsb-mn-form');
  const success = root.querySelector('.fsb-mn-success');
  const check = root.querySelector('.fsb-mn-check');
  const dots = root.querySelectorAll('.fsb-mn-dot');

  const trigger = () => {
    if (reduce) {
      gsap.set(form, { autoAlpha: 0 });
      gsap.set(success, { autoAlpha: 1 });
      gsap.set(check, { scale: 1, opacity: 1 });
      return;
    }
    const tl = gsap.timeline();
    tl.to(form, { autoAlpha: 0, scale: 0.95, duration: 0.4, ease: 'power2.out' })
      .set(success, { autoAlpha: 1 }, '-=0.05')
      .fromTo(check, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.5, ease: 'back.out(2)' })
      .to(check, { scale: 1.08, duration: 0.18, yoyo: true, repeat: 1, ease: 'power2.inOut' });
    dots.forEach((d, i) => {
      const angle = (i / dots.length) * Math.PI * 2;
      const dist = 56;
      tl.fromTo(d,
        { x: 0, y: 0, opacity: 1, scale: 1 },
        { x: Math.cos(angle) * dist, y: Math.sin(angle) * dist, opacity: 0, scale: 0.4, duration: 0.6, ease: 'power2.out' },
        '-=0.55'
      );
    });
  };

  const reset = () => {
    gsap.set(form, { autoAlpha: 1, scale: 1 });
    gsap.set(success, { autoAlpha: 0 });
    gsap.set(check, { scale: 0, opacity: 0 });
    gsap.set(dots, { x: 0, y: 0, opacity: 0, scale: 1 });
  };

  reset();
  if (form) form.addEventListener('submit', e => { e.preventDefault(); trigger(); });

  if (!reduce) {
    setTimeout(() => {
      trigger();
      setInterval(() => { reset(); setTimeout(trigger, 250); }, 5000);
    }, 2000);
  }
})();
2. Skeleton — DOM + class-namen, mag herschikken
<!-- Skeleton: form-success-burst-minimal -->
<div class="fsb-mn">
  <form class="fsb-mn-form">
    <label><span>Naam</span><input type="text"/></label>
    <label><span>Email</span><input type="email"/></label>
    <button type="submit">Verstuur</button>
  </form>
  <div class="fsb-mn-success" aria-live="polite">
    <div class="fsb-mn-burst">
      <span class="fsb-mn-dot"></span><span class="fsb-mn-dot"></span><span class="fsb-mn-dot"></span>
      <span class="fsb-mn-dot"></span><span class="fsb-mn-dot"></span><span class="fsb-mn-dot"></span>
      <svg class="fsb-mn-check" viewBox="0 0 24 24"><polyline points="5 12 10 17 19 8"/></svg>
    </div>
    <p>Verzonden</p>
  </div>
</div>
3. Styling-template — verplicht eigen invulling per merk
/* Styling: form-success-burst-minimal */
:root {
  --mn-paper: #F4F1EB;
  --mn-ink: #0A0A0A;
  --mn-line: rgba(10,10,10,.18);
}
.fsb-mn { background: var(--mn-paper); color: var(--mn-ink); font-family: 'Inter', system-ui, sans-serif; }
.fsb-mn-form button { background: var(--mn-ink); color: var(--mn-paper); border: 0; padding: .9rem 2rem; cursor: pointer; }
.fsb-mn-success { position: absolute; inset: 0; display: grid; place-items: center; opacity: 0; visibility: hidden; }
.fsb-mn-check { width: 48px; height: 48px; stroke: var(--mn-ink); stroke-width: 1.5; fill: none; opacity: 0; transform: scale(0); }
.fsb-mn-dot { position: absolute; left: 50%; top: 50%; width: 5px; height: 5px; border-radius: 50%; background: var(--mn-ink); opacity: 0; }