A study in restrained typographic motion — Volume I.
// Mechanisme: hero-char-reveal-editorial import gsap from 'https://esm.sh/[email protected]'; const el = document.querySelector('.char-reveal-target'); if (el) { const wrap = (txt) => [...txt].map(c => c === ' ' ? '<span style="display:inline-block;width:.28em"> </span>' : '<span class="cw" style="display:inline-block;overflow:hidden;vertical-align:bottom"><span class="ci" style="display:inline-block;will-change:transform">' + c + '</span></span>' ).join(''); const walk = (node) => { [...node.childNodes].forEach(n => { if (n.nodeType === 3) { const span = document.createElement('span'); span.innerHTML = wrap(n.textContent); node.replaceChild(span, n); } else if (n.nodeType === 1) walk(n); }); }; walk(el); const chars = el.querySelectorAll('.ci'); if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { gsap.set(chars, { yPercent: 0, autoAlpha: 1 }); } else { gsap.fromTo(chars, { yPercent: 110, autoAlpha: 0 }, { yPercent: 0, autoAlpha: 1, duration: 1.4, stagger: 0.018, ease: 'expo.out' } ); } }
<!-- Skeleton: hero-char-reveal-editorial -->
<section class="he-stage">
<aside class="he-eyebrow">
<span class="he-num">N° 003</span>
<span class="he-rule"></span>
<span class="he-cat">Heroes — Char Reveal — Editorial</span>
</aside>
<h1 class="char-reveal-target">
Movement begins with <em>intention</em>
</h1>
<p class="he-credit">A study in restrained typographic motion.</p>
</section> /* Styling: hero-char-reveal-editorial */
.he-stage{background:#F4F1EB;color:#0A0A0A;font-family:'Fraunces',Georgia,serif}
.he-eyebrow{display:flex;flex-direction:column;gap:.6rem;font-size:.7rem;letter-spacing:.18em;text-transform:uppercase}
.he-rule{display:block;width:48px;height:1px;background:#0A0A0A}
.char-reveal-target{font-family:'Fraunces',Georgia,serif;font-weight:300;font-size:clamp(3rem,9vw,8.5rem);line-height:.95;letter-spacing:-.02em}
.char-reveal-target em{font-style:italic;font-weight:400}
.he-credit{font-style:italic;color:rgba(10,10,10,.55)}