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.

heroes/split-reveal.astro
---
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>