← Blurr Motion cta-hold-to-confirm-minimal
Categorie cta Tier 1 Techniek #18 Deps

Bevestig met aandacht

Houd ingedrukt om
te bevestigen.

klaar bezig… bevestigd ✓

1. Mechanisme — kopieer 1-op-1, geen styling-keuzes
// Mechanisme: cta-hold-to-confirm-minimal
// Press-and-hold vult een 1px lijn van 0 -> 100% in 1.2s; auto-demo loop elke 4s.
const root = document.querySelector('.hold-block');
if (root && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
  const btn = root.querySelector('.hold-btn');
  const fill = root.querySelector('.hold-fill');
  const status = root.querySelector('.hold-status');
  const DURATION = 1200;
  let raf, t0, holding = false, autoTimer, autoDemo = true;

  const setProgress = (p) => {
    fill.style.transform = 'scaleX(' + p + ')';
    status.dataset.state = p >= 1 ? 'confirmed' : (p > 0 ? 'holding' : 'idle');
  };
  const tick = (ts) => {
    if (!holding) return;
    if (!t0) t0 = ts;
    const p = Math.min(1, (ts - t0) / DURATION);
    setProgress(p);
    if (p >= 1) { holding = false; return; }
    raf = requestAnimationFrame(tick);
  };
  const start = () => { holding = true; t0 = 0; cancelAnimationFrame(raf); raf = requestAnimationFrame(tick); };
  const cancel = () => { if (holding) { holding = false; setProgress(0); } };
  const stopAuto = () => { autoDemo = false; clearTimeout(autoTimer); };

  btn.addEventListener('mousedown', () => { stopAuto(); start(); });
  btn.addEventListener('touchstart', (e) => { e.preventDefault(); stopAuto(); start(); }, { passive: false });
  ['mouseup','mouseleave','touchend','touchcancel'].forEach(ev => btn.addEventListener(ev, cancel));

  const autoLoop = () => {
    if (!autoDemo) return;
    start();
    autoTimer = setTimeout(() => {
      if (!autoDemo) return;
      setTimeout(() => { setProgress(0); autoTimer = setTimeout(autoLoop, 2800); }, 900);
    }, DURATION + 50);
  };
  setTimeout(autoLoop, 1500);
}
2. Skeleton — DOM + class-namen, mag herschikken
<!-- Skeleton: cta-hold-to-confirm-minimal -->
<section class="hold-block">
  <p class="hold-eyebrow">Bevestig met aandacht</p>
  <h2 class="hold-headline">Houd ingedrukt om<br/>te bevestigen.</h2>
  <button class="hold-btn" type="button">
    <span class="hold-label">houd vast</span>
    <span class="hold-track"><span class="hold-fill"></span></span>
  </button>
  <p class="hold-status" data-state="idle">
    <span class="s-idle">klaar</span>
    <span class="s-hold">bezig…</span>
    <span class="s-ok">bevestigd</span>
  </p>
</section>
3. Styling-template — verplicht eigen invulling per merk
/* Styling: cta-hold-to-confirm-minimal — cream/ink, Inter 300, single 1px rule */
.hold-block {
  --paper: #F4F1EB;
  --ink: #0A0A0A;
  --rule: rgba(10,10,10,.18);
  background: var(--paper);
  color: var(--ink);
  font-family: 'Inter', system-ui, sans-serif;
  font-weight: 300;
  display: grid;
  gap: 2.25rem;
  padding: clamp(3rem, 8vw, 6rem) 0;
  max-width: 56ch;
}
.hold-headline { font-size: clamp(1.6rem, 3.2vw, 2.4rem); line-height: 1.2; margin: 0; font-weight: 300; letter-spacing: -.01em; }
.hold-btn {
  background: transparent;
  border: 0;
  border-top: 1px solid var(--ink);
  border-bottom: 1px solid var(--rule);
  padding: 0;
  cursor: pointer;
  font-family: inherit;
  font-weight: 300;
  display: grid;
}
.hold-label { padding: 1.4rem 0; text-align: left; font-size: 1rem; letter-spacing: .04em; }
.hold-track { height: 1px; background: transparent; position: relative; }
.hold-fill { position: absolute; inset: 0; background: var(--ink); transform: scaleX(0); transform-origin: left; }
@media (prefers-reduced-motion: reduce) { .hold-fill { transform: scaleX(1); } }