Split Reveal Hero
An editorial hero whose display headline wipes in line-by-line: each line rides inside an overflow-clipped wrapper while its inner span slides up from 100%, staggered by animation-delay. The type also breathes through a slow variable-font-weight shift. Pure CSS.
- overflow clip mask
- staggered animation-delay
- variable font-weight
- text-wrap: balance
Source
A single self-contained .astro component. It uses Prism's design tokens (--spectrum-*, --ink…) with sensible fallbacks, so it renders even outside this site.
---
export const meta = {
title: "Split Reveal Hero",
tags: ["hero", "reveal", "typography", "css-only"],
description:
"An editorial hero whose display headline wipes in line-by-line: each line rides inside an overflow-clipped wrapper while its inner span slides up from 100%, staggered by animation-delay. The type also breathes through a slow variable-font-weight shift. Pure CSS.",
tech: ["overflow clip mask", "staggered animation-delay", "variable font-weight", "text-wrap: balance"],
height: 560,
};
const lines = ["Design is the", "art of making", "the invisible", "obvious."];
---
<section class="split-reveal">
<div class="split-reveal__inner">
<p class="split-reveal__eyebrow"><span aria-hidden="true">✳</span> Studio manifesto — Vol. 04</p>
<h1 class="split-reveal__title">
{lines.map((line, i) => (
<span class="line" style={`--i:${i}`}>
<span class="line__inner">{line}</span>
</span>
))}
</h1>
<p class="split-reveal__sub">
We build interfaces at the seam of restraint and spectacle — where every
pixel earns its place and nothing shouts louder than the idea.
</p>
<div class="split-reveal__actions">
<a href="#" class="sr-btn sr-btn--solid">Start a project</a>
<a href="#" class="sr-btn sr-btn--ghost">See the work <span aria-hidden="true">→</span></a>
</div>
</div>
<div class="split-reveal__rule" aria-hidden="true"></div>
</section>
<style>
.split-reveal {
position: relative;
min-height: 560px;
display: grid;
place-items: center;
padding: clamp(2.5rem, 1.5rem + 4vw, 5rem) var(--gutter, 1.5rem);
overflow: hidden;
isolation: isolate;
background:
radial-gradient(120% 90% at 100% 0%, color-mix(in oklab, var(--spectrum-1, #8b5cf6) 14%, transparent), transparent 55%),
radial-gradient(90% 80% at 0% 100%, color-mix(in oklab, var(--spectrum-3, #45d3e8) 12%, transparent), transparent 55%),
var(--bg, #0e0f1a);
color: var(--ink, #f2f2f7);
}
.split-reveal__inner {
width: 100%;
max-width: var(--container, 64rem);
display: grid;
gap: clamp(1rem, 0.7rem + 1.2vw, 1.6rem);
justify-items: start;
text-align: left;
}
.split-reveal__eyebrow {
margin: 0;
display: inline-flex;
align-items: center;
gap: 0.55rem;
font-family: var(--font-mono, ui-monospace, monospace);
font-size: var(--step--1, 0.85rem);
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--ink-muted, #b7b9c9);
opacity: 0;
animation: sr-fade 0.8s var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1)) 0.1s both;
}
.split-reveal__eyebrow span {
color: var(--spectrum-5, #fbbf24);
animation: sr-spin 14s linear infinite;
}
.split-reveal__title {
margin: 0;
font-family: var(--font-display, system-ui, sans-serif);
font-size: clamp(2.6rem, 1.4rem + 6vw, 6rem);
line-height: 0.98;
letter-spacing: -0.035em;
font-weight: 640;
text-wrap: balance;
/* animate variable weight for fonts that support it */
animation: sr-weight 9s var(--ease-in-out, cubic-bezier(0.65, 0, 0.35, 1)) infinite;
}
/* Each line: a clip window; the inner span slides up from below it. */
.split-reveal__title .line {
display: block;
overflow: hidden;
padding-block: 0.04em; /* room for descenders under the clip */
}
.split-reveal__title .line__inner {
display: block;
transform: translateY(105%);
animation: sr-rise var(--dur-slow, 0.7s) var(--ease-emph, cubic-bezier(0.2, 0, 0, 1)) forwards;
animation-delay: calc(0.18s + var(--i) * 0.11s);
}
/* accent the final line */
.split-reveal__title .line:last-child .line__inner {
background: linear-gradient(100deg, var(--spectrum-1, #8b5cf6), var(--spectrum-3, #45d3e8), var(--spectrum-5, #fbbf24));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.split-reveal__sub {
margin: 0;
max-width: 44ch;
font-size: var(--step-1, 1.2rem);
line-height: 1.55;
color: var(--ink-muted, #b7b9c9);
opacity: 0;
animation: sr-fade 0.9s var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1)) 0.75s both;
}
.split-reveal__actions {
display: flex;
flex-wrap: wrap;
gap: 0.8rem;
margin-top: 0.4rem;
opacity: 0;
animation: sr-fade 0.9s var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1)) 0.9s both;
}
.sr-btn {
padding: 0.85rem 1.5rem;
border-radius: var(--radius-round, 999px);
font-weight: 600;
font-size: var(--step-0, 1rem);
text-decoration: none;
transition: transform var(--dur-fast, 0.16s) var(--ease-spring, cubic-bezier(0.34, 1.56, 0.64, 1)),
box-shadow var(--dur, 0.3s) var(--ease-out, ease);
}
.sr-btn:hover { transform: translateY(-2px); }
.sr-btn--solid {
background: var(--ink, #f2f2f7);
color: var(--bg, #0e0f1a);
box-shadow: var(--shadow-m, 0 8px 24px rgba(0, 0, 0, 0.25));
}
.sr-btn--solid:hover { box-shadow: var(--glow, 0 0 40px rgba(139, 92, 246, 0.35)); }
.sr-btn--ghost {
color: var(--ink, #f2f2f7);
border: 1px solid color-mix(in oklab, currentColor 30%, transparent);
}
.sr-btn--ghost:hover { border-color: color-mix(in oklab, currentColor 60%, transparent); }
/* thin vertical accent rule bottom-right, for editorial framing */
.split-reveal__rule {
position: absolute;
right: clamp(1rem, 4vw, 4rem);
bottom: 0;
width: 1px;
height: clamp(3rem, 12vh, 7rem);
background: linear-gradient(to bottom, transparent, var(--spectrum-3, #45d3e8));
z-index: -1;
}
@keyframes sr-rise { to { transform: translateY(0); } }
@keyframes sr-fade { to { opacity: 1; } }
@keyframes sr-spin { to { transform: rotate(360deg); } }
@keyframes sr-weight {
0%, 100% { font-weight: 600; letter-spacing: -0.035em; }
50% { font-weight: 720; letter-spacing: -0.045em; }
}
@media (prefers-reduced-motion: reduce) {
.split-reveal__title .line__inner {
transform: none;
animation: none;
}
.split-reveal__eyebrow,
.split-reveal__sub,
.split-reveal__actions { opacity: 1; animation: none; }
.split-reveal__eyebrow span,
.split-reveal__title { animation: none; }
}
</style>