Gradient Sweep

Kinetic display type: an iridescent gradient sweeps through the letters while a shimmer band passes over them and the variable-font weight gently breathes. A second line tints each word a staggered hue.

  • background-clip: text
  • animated background-position
  • font-variation-settings
  • staggered delays

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.

text/gradient-sweep.astro
---
export const meta = {
  title: "Gradient Sweep",
  tags: ["text", "kinetic", "gradient", "css-only"],
  description:
    "Kinetic display type: an iridescent gradient sweeps through the letters while a shimmer band passes over them and the variable-font weight gently breathes. A second line tints each word a staggered hue.",
  tech: ["background-clip: text", "animated background-position", "font-variation-settings", "staggered delays"],
  height: 380,
};

const words = ["colour", "motion", "type", "light"];
const headline = "Designed in motion";
---

<section class="gsweep">
  <div class="gsweep__stack">
    <p class="gsweep__kicker">Kinetic typography</p>
    <h1 class="gsweep__title" data-text={headline}>{headline}</h1>
    <p class="gsweep__words" aria-label="colour, motion, type, light">
      {words.map((w, i) => (
        <span class="gsweep__word" style={`--i:${i}`} aria-hidden="true">{w}</span>
      ))}
    </p>
  </div>
</section>

<style>
  .gsweep {
    position: relative; min-height: 380px; display: grid; place-items: center;
    padding: 3rem 1.5rem; overflow: hidden; isolation: isolate; text-align: center;
    background:
      radial-gradient(120% 80% at 50% 120%, color-mix(in oklab, var(--spectrum-1, #8b5cf6) 16%, transparent), transparent 70%),
      var(--bg, #0e0f1a);
    color: var(--ink, #f2f2f7);
  }
  .gsweep__stack { display: grid; gap: 0.9rem; justify-items: center; max-width: 46rem; }

  .gsweep__kicker {
    margin: 0; font-size: var(--step--1, 0.85rem); font-weight: 600;
    letter-spacing: 0.24em; text-transform: uppercase; color: var(--ink-muted, #b7b9c9);
  }

  /* --- The sweeping iridescent headline ----------------------------------
     Base element carries the iridescent ramp (animates background-position)
     and the weight breathe. A ::after clone carries the shimmer band on its
     OWN background-position, so the two never fight over one property. */
  .gsweep__title {
    position: relative; margin: 0; font-family: var(--font-display, system-ui, sans-serif);
    font-size: clamp(2.4rem, 1.4rem + 5vw, 5.4rem); line-height: 1.04;
    letter-spacing: -0.03em; font-weight: 800; text-wrap: balance;
    background-image: linear-gradient(100deg,
      var(--spectrum-1, #8b5cf6), var(--spectrum-2, #5b8def),
      var(--spectrum-3, #45d3e8), var(--spectrum-4, #4ade80),
      var(--spectrum-5, #fbbf24), var(--spectrum-6, #f472b6),
      var(--spectrum-1, #8b5cf6));
    background-size: 300% 100%; background-position: 0% 0; background-repeat: no-repeat;
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
    font-variation-settings: "wght" 640;
    animation: gsweep-ramp 9s linear infinite, gsweep-breathe 6s ease-in-out infinite;
  }
  .gsweep__title::after {
    content: attr(data-text); position: absolute; inset: 0; pointer-events: none;
    background-image: linear-gradient(115deg,
      transparent 40%, color-mix(in oklab, #fff 94%, transparent) 50%, transparent 60%);
    background-size: 220% 100%; background-position: 130% 0; background-repeat: no-repeat;
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
    animation: gsweep-shine 4.8s var(--ease-in-out, cubic-bezier(0.65,0,0.35,1)) 1.2s infinite;
  }
  @keyframes gsweep-ramp    { to { background-position: -300% 0; } }
  @keyframes gsweep-shine   { 0% { background-position: 130% 0; } 55%, 100% { background-position: -130% 0; } }
  @keyframes gsweep-breathe { 50% { font-variation-settings: "wght" 880; } }

  /* --- The staggered-hue word line -------------------------------------- */
  .gsweep__words {
    margin: 0; display: flex; flex-wrap: wrap; gap: 0.15rem 0.9rem; justify-content: center;
    font-weight: 700; font-size: clamp(1.1rem, 0.9rem + 1vw, 1.7rem); letter-spacing: -0.01em;
  }
  .gsweep__word {
    background-image: linear-gradient(100deg,
      oklch(78% 0.17 calc(200 + var(--i) * 48)),
      oklch(80% 0.17 calc(280 + var(--i) * 48)));
    background-size: 200% 100%;
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
    animation: gsweep-wordshift 5s ease-in-out infinite;
    animation-delay: calc(var(--i) * 0.35s);
  }
  @keyframes gsweep-wordshift { 50% { background-position: 100% 0; } }

  @media (prefers-reduced-motion: reduce) {
    .gsweep__title { animation: none; background-position: 40% 0; font-variation-settings: "wght" 760; }
    .gsweep__title::after { animation: none; opacity: 0; }
    .gsweep__word { animation: none; }
  }
</style>