← Blurr Motion testimonial-drag-cards-minimal
Categorie testimonials Tier 1 Techniek #17 Deps gsap, Draggable

"Ze leverden precies wat ze beloofden — geen ruis, alleen werk dat staat."

Mara Verhoeven · Founder, Studio Lente

"Het tempo en de zorgvuldigheid bleven hetzelfde, van eerste call tot oplevering."

Jelle Bos · CMO, Klinkhart

"De motion-detail tilt de hele site een klasse hoger. Klanten merken het."

Sanne de Vries · Brand Director

"Restraint waar het kon, kracht waar het moest. Precies de balans."

Tom Hendrikse · CEO, Maison Atelier

Drag of wacht — de stapel beweegt zelf.

1. Mechanisme — kopieer 1-op-1, geen styling-keuzes
// Mechanisme: testimonial-drag-cards-minimal
// Stack van 4 cards. Top-card is Draggable. Drag-end (>100px) of auto-demo:
// card vliegt rechts uit (x:360, rotation:12, autoAlpha:0). Onderliggende cards
// schuiven omhoog, nieuwe top wordt draggable. Loop oneindig.
import gsap from 'https://esm.sh/[email protected]';
import { Draggable } from 'https://esm.sh/[email protected]/Draggable';
gsap.registerPlugin(Draggable);
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;

const cards = Array.from(document.querySelectorAll('.tdc-card'));
let order = cards.slice();
let dragInst, autoTimer;

function layout() {
  order.forEach((c, i) => gsap.to(c, {
    x: 0, y: i * 6, scale: 1 - i * 0.04, rotation: 0,
    zIndex: order.length - i, autoAlpha: i < 4 ? 1 : 0,
    duration: 0.5, ease: 'power2.out'
  }));
}
function flyOut(card, dir) {
  return gsap.to(card, {
    x: 360 * dir, rotation: 12 * dir, autoAlpha: 0,
    duration: 0.8, ease: 'power2.out',
    onComplete: () => {
      order.push(order.shift());
      gsap.set(card, { x: 0, rotation: 0 });
      layout();
      bindTop();
    }
  });
}
function bindTop() {
  if (dragInst) dragInst[0].kill();
  dragInst = Draggable.create(order[0], {
    type: 'x',
    onDragStart() { clearInterval(autoTimer); },
    onDragEnd() {
      if (Math.abs(this.x) > 100) flyOut(order[0], this.x > 0 ? 1 : -1);
      else { gsap.to(order[0], { x: 0, duration: 0.4 }); startAuto(); }
    }
  });
}
function startAuto() {
  autoTimer = setInterval(() => flyOut(order[0], 1), 3500);
}
layout(); bindTop(); startAuto();
2. Skeleton — DOM + class-namen, mag herschikken
<!-- Skeleton: testimonial-drag-cards-minimal -->
<div class="tdc-stack">
  <article class="tdc-card">
    <p class="tdc-quote">"..."</p>
    <footer><b>Naam</b> · Rol</footer>
  </article>
  <!-- x 4 -->
</div>
3. Styling-template — verplicht eigen invulling per merk
/* Styling-template: testimonial-drag-cards-minimal */
:root {
  --tdc-bg: #F4F1EB;
  --tdc-card-bg: #FFFFFF;
  --tdc-ink: #0A0A0A;
  --tdc-rule: rgba(10,10,10,.18);
  --tdc-radius: 0;
  --tdc-font: 'Inter', system-ui, sans-serif;
}