NO ACCIDENTAL CLICKS — PRESS AND KEEP PRESSING.
// READY // HOLDING… // CONFIRMED ✓
// Mechanisme: cta-hold-to-confirm-brutalist
// Mousedown/touchstart vult progress 0 -> 100% in 1.2s; volledig vol = success.
// Auto-demo simuleert om de 4s een hold om te tonen wat het block doet.
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); }, 800);
}, DURATION + 50);
};
setTimeout(autoLoop, 1500);
} <!-- Skeleton: cta-hold-to-confirm-brutalist -->
<section class="hold-block">
<div class="hold-grid">
<span class="hold-tag">[ 01 / CONFIRM ]</span>
<h2 class="hold-headline">HOLD<br/>TO<br/>CONFIRM</h2>
<p class="hold-sub">No accidental clicks. Press and keep pressing.</p>
</div>
<button class="hold-btn" type="button">
<span class="hold-label">HOLD ▸</span>
<span class="hold-track">
<span class="hold-fill"></span>
</span>
</button>
<p class="hold-status" data-state="idle">
<span class="s-idle">// READY</span>
<span class="s-hold">// HOLDING…</span>
<span class="s-ok">// CONFIRMED ✓</span>
</p>
</section> /* Styling: cta-hold-to-confirm-brutalist — 2px borders, JetBrains Mono UPPERCASE, hot-spot #FF4A1C */
.hold-block {
--paper: #FFFFFF;
--ink: #0A0A0A;
--hot: #FF4A1C;
background: var(--paper);
color: var(--ink);
font-family: 'JetBrains Mono', monospace;
text-transform: uppercase;
padding: clamp(2rem, 5vw, 4rem);
display: grid;
gap: 2rem;
border: 2px solid var(--ink);
box-shadow: 12px 12px 0 0 var(--ink);
}
.hold-headline {
font-size: clamp(3rem, 9vw, 6rem);
line-height: .88;
letter-spacing: -.02em;
font-weight: 700;
margin: 0;
}
.hold-btn {
display: grid;
border: 2px solid var(--ink);
background: var(--paper);
box-shadow: 8px 8px 0 0 var(--ink);
padding: 0;
font-family: inherit;
cursor: pointer;
}
.hold-btn:active { transform: translate(4px, 4px); box-shadow: 4px 4px 0 0 var(--ink); }
.hold-label { padding: 1.6rem 2rem; font-size: 1.6rem; font-weight: 700; text-align: left; }
.hold-track { height: 14px; border-top: 2px solid var(--ink); overflow: hidden; }
.hold-fill { display: block; height: 100%; background: var(--hot); transform: scaleX(0); transform-origin: left; }
[data-state="confirmed"] { color: var(--hot); }
@media (prefers-reduced-motion: reduce) {
.hold-fill { transform: scaleX(1); }
}