Motion Lab / FAQ / popover anchor / minimal
// Mechanisme: faq-popover-anchor-minimal (techniek #41 — Anchor positioning + Popover API) // Native [popover] + CSS anchor-name / position-anchor. Auto-loop opent elke 3s een volgende anchor. import gsap from 'https://esm.sh/[email protected]'; const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches; const root = document.querySelector('.faq-pa-min'); if (root) { const triggers = Array.from(root.querySelectorAll('[data-pop-trigger]')); const popoverSupport = 'popover' in HTMLElement.prototype; const closeAll = () => { triggers.forEach((b) => b.classList.remove('is-active')); root.querySelectorAll('.faq-pop').forEach((p) => { try { if (popoverSupport && p.matches(':popover-open')) p.hidePopover(); } catch (_) {} p.classList.remove('is-open'); }); }; const open = (btn) => { if (!btn) return; const id = btn.getAttribute('data-pop-target'); const target = document.getElementById(id); if (!target) return; closeAll(); btn.classList.add('is-active'); try { if (popoverSupport && target.showPopover) target.showPopover(); else target.classList.add('is-open'); } catch (_) { target.classList.add('is-open'); } if (!reduce) gsap.fromTo(target, { y: -4, autoAlpha: 0 }, { y: 0, autoAlpha: 1, duration: 0.3, ease: 'power2.out' }); }; if (!reduce) { let i = 0; const tick = () => { open(triggers[i]); setTimeout(closeAll, 2200); i = (i + 1) % triggers.length; }; setTimeout(tick, 500); setInterval(tick, 3000); } }
<!-- Skeleton: faq-popover-anchor-minimal -->
<section class="faq-pa-min">
<h2>Vragen, kort beantwoord.</h2>
<div class="anchor-row">
<button data-pop-trigger data-pop-target="popm-0" popovertarget="popm-0" class="pop-anchor-0">01 Snelheid</button>
<button data-pop-trigger data-pop-target="popm-1" popovertarget="popm-1" class="pop-anchor-1">02 Prijs</button>
<!-- 4-6 anchor-knoppen -->
</div>
<div id="popm-0" popover class="faq-pop">Antwoord-tekst.</div>
<div id="popm-1" popover class="faq-pop">Antwoord-tekst.</div>
</section> /* Styling: faq-popover-anchor-minimal */
:root {
--block-bg: #F4F1EB;
--block-fg: #0A0A0A;
--block-rule: rgba(10,10,10,.14);
}
.faq-pa-min { background: var(--block-bg); color: var(--block-fg); font-family: 'Inter', system-ui, sans-serif; padding: clamp(4rem,10vw,9rem) clamp(2rem,6vw,5rem); }
.faq-pa-min h2 { font-weight: 300; font-size: clamp(2rem,4vw,3rem); letter-spacing: -0.02em; margin: 0 0 4rem 0; }
.faq-pa-min .anchor-row { display: flex; flex-wrap: wrap; gap: 0; border-top: 1px solid var(--block-rule); }
.faq-pa-min [data-pop-trigger] {
appearance: none; background: transparent; border: 0; border-bottom: 1px solid var(--block-rule);
padding: 1.25rem 1.5rem 1.25rem 0; font: 400 0.95rem/1 'Inter', sans-serif;
color: var(--block-fg); cursor: pointer; flex: 1 1 auto; text-align: left;
transition: color .2s ease;
}
.faq-pa-min [data-pop-trigger]:hover, .faq-pa-min [data-pop-trigger].is-active { color: rgba(10,10,10,.55); }
.faq-pa-min .pop-anchor-0 { anchor-name: --pm0; }
.faq-pa-min .pop-anchor-1 { anchor-name: --pm1; }
.faq-pa-min .pop-anchor-2 { anchor-name: --pm2; }
.faq-pa-min .pop-anchor-3 { anchor-name: --pm3; }
.faq-pa-min .pop-anchor-4 { anchor-name: --pm4; }
.faq-pa-min .pop-anchor-5 { anchor-name: --pm5; }
.faq-pa-min .faq-pop {
margin: 0; padding: 1.25rem 1.5rem; max-width: 320px; border: 1px solid var(--block-fg);
background: var(--block-bg); color: var(--block-fg); border-radius: 0;
font: 400 0.9rem/1.55 'Inter', sans-serif;
}
.faq-pa-min #popm-0 { position-anchor: --pm0; }
.faq-pa-min #popm-1 { position-anchor: --pm1; }
.faq-pa-min #popm-2 { position-anchor: --pm2; }
.faq-pa-min #popm-3 { position-anchor: --pm3; }
.faq-pa-min #popm-4 { position-anchor: --pm4; }
.faq-pa-min #popm-5 { position-anchor: --pm5; }
.faq-pa-min .faq-pop { top: anchor(bottom); left: anchor(left); margin-top: 0.5rem; }
@media (prefers-reduced-motion: reduce) { .faq-pa-min .faq-pop { transition: none; } }