Magnetisch
Beweging volgt aandacht. Niets meer.
// Mechanisme: cta-magnetic-button-minimal
// Subtle magnetic-tilt op cursor (0.3 * delta, capped). Auto-demo: lemniscaat
// (∞-curve) bij viewport-enter zodat de knop automatisch zwiept zonder hover.
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 = 28;
const STRENGTH = 0.3;
let 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();
apply(e.clientX - (r.left + r.width / 2), e.clientY - (r.top + r.height / 2));
});
stage.addEventListener('mouseleave', () => { btn.style.transform = ''; });
const io = new IntersectionObserver((entries) => {
entries.forEach(en => {
if (en.isIntersecting && !btn._auto) {
btn._auto = true;
const t0 = performance.now();
const loop = (now) => {
if (!userActive) {
const t = (now - t0) / 1000;
const a = 110;
const s = Math.sin(t * 0.7);
const c = Math.cos(t * 0.7);
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-minimal -->
<section class="mag-block">
<p class="mag-eyebrow">Magnetisch</p>
<h2 class="mag-headline">Een knop die<br/>weet dat je kijkt.</h2>
<hr class="mag-rule" />
<div class="mag-stage">
<button class="mag-btn" type="button">
<span class="mag-label">Start een project</span>
<span class="mag-arrow">→</span>
</button>
</div>
<p class="mag-foot">Beweging volgt aandacht. Niets meer.</p>
</section> /* Styling: cta-magnetic-button-minimal — cream/ink, Inter 300, single 1px rule */
.mag-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: 2rem;
padding: clamp(2rem, 6vw, 4rem) 0;
max-width: 64ch;
}
.mag-headline { font-size: clamp(1.8rem, 3.6vw, 2.6rem); line-height: 1.18; margin: 0; font-weight: 300; }
.mag-rule { border: 0; border-top: 1px solid var(--rule); margin: 0; }
.mag-stage { min-height: 220px; display: grid; place-items: center; }
.mag-btn {
background: transparent;
border: 1px solid var(--ink);
color: var(--ink);
padding: 1.1rem 2rem;
font-family: inherit;
font-weight: 300;
font-size: 1rem;
border-radius: 999px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: .7rem;
will-change: transform;
}