Motion Lab / Heroes / Word Reveal / Specimen
Gedachten ontvouwen zich woord voor woord—elk syllabe een kleine aankomst.
Copy-snippets — drie lagen
// Mechanisme: hero-word-reveal
// Kopieer 1-op-1. Geen stijlkeuzes.
import gsap from 'https://esm.sh/gsap@3.12.5';
import { ScrollTrigger } from 'https://esm.sh/gsap@3.12.5/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
const HEADLINE_TEXT = 'Jouw headline hier';
const SELECTOR = '.hero-headline';
function splitWords(el, text) {
el.innerHTML = text.split(" ")
.map(function(w) { return '<span class="word-wrap"><span class="word">' + w + '</span></span>'; })
.join(" ");
}
function initReveal() {
const headline = document.querySelector(SELECTOR);
if (!headline) return;
splitWords(headline, HEADLINE_TEXT);
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduced) {
headline.querySelectorAll('.word').forEach(w => {
w.style.opacity = '1';
w.style.transform = 'none';
});
return;
}
gsap.fromTo(
headline.querySelectorAll('.word'),
{ yPercent: 110, autoAlpha: 0 },
{ yPercent: 0, autoAlpha: 1, duration: 1.4, stagger: 0.09, ease: 'expo.out',
scrollTrigger: { trigger: headline, start: 'top 85%', once: true } }
);
}
document.readyState === 'loading'
? document.addEventListener('DOMContentLoaded', initReveal)
: initReveal(); <!-- Skeleton: hero-word-reveal
DOM + structurele CSS. Geen stijlkeuzes. -->
<section class="hero" aria-labelledby="hero-headline">
<div class="hero__inner">
<h1 id="hero-headline" class="hero-headline" tabindex="0">
<!-- JS vult .word-wrap / .word spans in -->
</h1>
<p class="hero__subtitle"></p>
</div>
</section>
/* Minimal structural CSS */
.hero-headline .word-wrap { display: inline-block; overflow: hidden; vertical-align: bottom; }
.hero-headline .word { display: inline-block; will-change: transform, opacity; }
.hero-headline:focus { outline: 2px solid currentColor; outline-offset: 4px; } /* Lege styling-template: hero-word-reveal
Vul alle var(--...) in per merk. Verander geen class-namen. */
:root {
--hero-bg: /* achtergrondkleur */;
--hero-color: /* tekstkleur */;
--hero-font-heading: /* serif of display font */;
--hero-font-body: /* body / subtitle font */;
--hero-heading-size: /* clamp(..., ...vw, ...) */;
--hero-heading-weight: /* 300 / 400 / 700 */;
--hero-heading-ls: /* letter-spacing, bijv. -0.02em */;
--hero-heading-lh: /* line-height */;
--hero-subtitle-size: /* clamp(...) */;
--hero-padding-x: /* clamp(...) */;
--hero-padding-y: /* clamp(...) */;
}
.hero { background: var(--hero-bg); color: var(--hero-color); padding: var(--hero-padding-y) var(--hero-padding-x); }
.hero__inner { max-width: /* bijv. 1100px */; margin: 0 auto; }
.hero-headline { font-family: var(--hero-font-heading); font-size: var(--hero-heading-size); font-weight: var(--hero-heading-weight); letter-spacing: var(--hero-heading-ls); line-height: var(--hero-heading-lh); }
.hero__subtitle { font-family: var(--hero-font-body); font-size: var(--hero-subtitle-size); }
.hero__eyebrow { font-variant: small-caps; letter-spacing: /* ... */; text-transform: uppercase; }
.hero__rule { height: 1px; background: /* rgba(...) */; }