Word-by-word reveal that lands like a punchline.
Split a headline into individual words, animate them up from below with staggered timing. The italic accent word gets a slightly longer delay so it lands last — the rhythm makes the headline feel earned.
kinetic-typography · intensity bold
Base-catalogue pattern — no generated axes.
Generation prompts are part of a CreativePower subscription, and are never public.
See plans.kt-headline {
font-family: "Geist", system-ui, sans-serif;
font-size: clamp(2.5rem, 8vw, 6rem);
font-weight: 700;
letter-spacing: -0.04em;
line-height: 0.98;
/* stageMode "adaptive": type only, so it takes the surface's own ink. */
--kt-ink: var(--color-on-surface, #fafafa);
/* The italic word is the headline's one saturated mark; it mixes toward the surface
first so a dark or cool primary still lands as emphasis rather than as a hole. */
--kt-accent: color-mix(in oklab, var(--color-primary, #ff6b1a) 80%, var(--color-surface, #ff6b1a));
color: var(--kt-ink);
overflow: hidden;
padding: 0.1em 0;
}
.kt-word {
display: inline-block;
opacity: 0;
transform: translateY(60%);
margin-right: 0.25em;
transition: opacity 700ms cubic-bezier(0.16,1,0.3,1), transform 700ms cubic-bezier(0.16,1,0.3,1);
}
.kt-word.kt-shown { opacity: 1; transform: none; }
.kt-word.it {
font-family: "Instrument Serif", serif;
font-style: italic;
font-weight: 400;
color: var(--kt-accent);
}(function() {
var words = Array.prototype.slice.call(document.querySelectorAll('.kt-word'));
if (!words.length) return;
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function hideAll() { words.forEach(function(w){ w.classList.remove('kt-shown'); w.style.opacity=''; w.style.transform=''; }); }
function showAllInstant() { words.forEach(function(w){ w.classList.add('kt-shown'); }); }
function revealCss() {
// Staggered CSS reveal (no GSAP needed).
words.forEach(function(w, i){ setTimeout(function(){ w.classList.add('kt-shown'); }, 200 + i * 120); });
}
function revealGsap() {
try {
window.gsap.set('.kt-word', { clearProps: 'opacity,transform' });
window.gsap.fromTo('.kt-word',
{ opacity: 0, y: '60%' },
{ opacity: 1, y: 0, duration: 1, ease: 'power3.out', stagger: 0.12, delay: 0.2 });
words.forEach(function(w){ w.classList.add('kt-shown'); });
} catch (e) { revealCss(); }
}
function reveal() {
if (reduce) { showAllInstant(); return; }
if (typeof window.gsap !== 'undefined' && window.gsap.fromTo) revealGsap();
else revealCss();
}
// Wait briefly for GSAP; if it never arrives, reveal via CSS so the headline is
// NEVER left invisible (production safety).
var gsapTries = 0;
function whenReady(cb) {
if (reduce) { cb(); return; }
if (typeof window.gsap !== 'undefined') { cb(); return; }
if (gsapTries++ > 25) { cb(); return; } // ~2.5s max, then proceed (CSS fallback)
setTimeout(function(){ whenReady(cb); }, 100);
}
var played = false;
function playOnce() {
if (played) return; played = true;
whenReady(reveal);
/* cp:demo-start */startDemoLoopIfNeeded();/* cp:demo-end */
}
/* cp:demo-start */
function startDemoLoopIfNeeded() {
if (!window.__CP_DEMO__ || reduce) return;
setInterval(function(){
if (window.__cpPaused) return;
hideAll();
setTimeout(reveal, 250);
}, 3800);
}
/* cp:demo-end */
// Trigger on visibility, with a load fallback.
var triggered = false;
function fire(){ if (triggered) return; triggered = true; playOnce(); }
try {
var io = new IntersectionObserver(function(entries){
entries.forEach(function(e){ if (e.isIntersecting) fire(); });
}, { threshold: 0.2 });
var h = document.querySelector('.kt-headline'); if (h) io.observe(h);
} catch (e) {}
setTimeout(fire, 500);
// Absolute safety net: no matter what, the headline is visible within 3s.
if (!reduce) setTimeout(function(){ if (!played) { showAllInstant(); } }, 3000);
})();<h1 class="kt-headline"> <span class="kt-word">The</span> <span class="kt-word">design</span> <span class="kt-word">system</span> <span class="kt-word it">behind</span> <span class="kt-word">every</span> <span class="kt-word">page.</span> </h1>