// CURSOR ATTRACTS — AUTO-DEMO LOOPT IN ∞
// Mechanisme: cta-magnetic-button-brutalist
// Magnetic-tilt naar cursor (0.3 * delta, capped). Auto-demo: programmatic
// lemniscaat-pad (∞-curve) zodat de knop bij viewport-enter automatisch zwiept.
const root = document.querySelector('.mag-block');
if (root && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
const btn = root.querySelector('.mag-btn');
const stage = root.querySelector('.mag-stage');
const CAP = 36;
const STRENGTH = 0.3;
let cx = 0, cy = 0, userActive = false, userTimer;
const apply = (dx, dy) => {
const mag = Math.hypot(dx, dy) || 1;
const k = Math.min(1, CAP / (mag * STRENGTH));
btn.style.transform = 'translate(' + (dx * STRENGTH * k) + 'px,' + (dy * STRENGTH * k) + 'px)';
};
stage.addEventListener('mousemove', (e) => {
userActive = true;
clearTimeout(userTimer);
userTimer = setTimeout(() => userActive = false, 1500);
const r = btn.getBoundingClientRect();
cx = r.left + r.width / 2;
cy = r.top + r.height / 2;
apply(e.clientX - cx, e.clientY - cy);
});
stage.addEventListener('mouseleave', () => { btn.style.transform = ''; });
// Auto-demo: lemniscaat van Bernoulli
const io = new IntersectionObserver((entries) => {
entries.forEach(en => {
if (en.isIntersecting && !btn._auto) {
btn._auto = true;
let t0 = performance.now();
const loop = (now) => {
if (userActive) { btn._auto && requestAnimationFrame(loop); return; }
const t = (now - t0) / 1000;
const a = 140;
const s = Math.sin(t * 0.9);
const c = Math.cos(t * 0.9);
const dx = (a * c) / (1 + s * s);
const dy = (a * s * c) / (1 + s * s);
apply(dx, dy);
if (btn._auto) requestAnimationFrame(loop);
};
requestAnimationFrame(loop);
}
});
}, { threshold: 0.2 });
io.observe(stage);
} <!-- Skeleton: cta-magnetic-button-brutalist -->
<section class="mag-block">
<span class="mag-tag">[ MAGNETIC / 002 ]</span>
<h2 class="mag-headline">PULL<br/>ME<br/>IN.</h2>
<div class="mag-stage">
<button class="mag-btn" type="button">
<span class="mag-arrow">→</span>
<span class="mag-label">START PROJECT</span>
</button>
</div>
<p class="mag-foot">// CURSOR ATTRACTS — RELEASE TO RESET</p>
</section> /* Styling: cta-magnetic-button-brutalist — 2px borders, no-radius, JetBrains Mono UPPERCASE, hot-spot */
.mag-block {
--paper: #FFFFFF;
--ink: #0A0A0A;
--hot: #FF4A1C;
background: var(--paper);
color: var(--ink);
font-family: 'JetBrains Mono', monospace;
text-transform: uppercase;
display: grid;
gap: 2rem;
padding: clamp(2rem, 5vw, 4rem);
border: 2px solid var(--ink);
box-shadow: 12px 12px 0 0 var(--ink);
}
.mag-headline { font-size: clamp(3rem, 9vw, 6rem); line-height: .88; font-weight: 700; margin: 0; }
.mag-stage { min-height: 240px; display: grid; place-items: center; border: 2px dashed var(--ink); }
.mag-btn {
background: var(--hot);
color: var(--paper);
border: 2px solid var(--ink);
box-shadow: 8px 8px 0 0 var(--ink);
padding: 1.6rem 2.4rem;
font-family: inherit;
font-weight: 700;
cursor: pointer;
will-change: transform;
}