Infinite Perspective Grid

A synthwave-tinted floor of grid lines receding to a glowing horizon, scrolling toward the viewer forever. Pure CSS 3D transforms and gradients — GPU-friendly, with a masked horizon fade and a centred caption.

  • perspective + rotateX
  • repeating-linear-gradient
  • mask fade
  • transform animation

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.

backgrounds/animated-grid.astro
---
export const meta = {
  title: "Infinite Perspective Grid",
  tags: ["background", "grid", "3d", "css-only"],
  description:
    "A synthwave-tinted floor of grid lines receding to a glowing horizon, scrolling toward the viewer forever. Pure CSS 3D transforms and gradients — GPU-friendly, with a masked horizon fade and a centred caption.",
  tech: ["perspective + rotateX", "repeating-linear-gradient", "mask fade", "transform animation"],
  height: 460,
};
---

<section class="pgrid">
  <div class="pgrid__scene" aria-hidden="true">
    <div class="pgrid__sun"></div>
    <div class="pgrid__floor"><div class="pgrid__lines"></div></div>
    <div class="pgrid__haze"></div>
  </div>

  <div class="pgrid__caption">
    <p class="pgrid__kicker">Endless runway</p>
    <h2 class="pgrid__title">Toward the horizon</h2>
    <p class="pgrid__sub">A grid that never stops arriving.</p>
  </div>
</section>

<style>
  .pgrid {
    position: relative; min-height: 460px; display: grid; place-items: center;
    overflow: hidden; isolation: isolate; padding: 2.5rem 1.5rem;
    color: var(--ink, #f4f2ff);
    background:
      linear-gradient(180deg,
        oklch(18% 0.06 285) 0%,
        oklch(14% 0.05 290) 46%,
        oklch(10% 0.04 300) 100%);
  }

  .pgrid__scene { position: absolute; inset: 0; z-index: -1; overflow: hidden; }

  /* glowing sun on the horizon line (~52% down) */
  .pgrid__sun {
    position: absolute; left: 50%; top: 52%; width: 26rem; height: 26rem;
    transform: translate(-50%, -60%);
    background:
      radial-gradient(circle at 50% 50%,
        color-mix(in oklab, var(--spectrum-6, #f472b6) 65%, #fff 0%) 0%,
        color-mix(in oklab, var(--spectrum-1, #8b5cf6) 45%, transparent) 42%,
        transparent 66%);
    filter: blur(4px); opacity: 0.85;
  }

  /* the receding floor plane */
  .pgrid__floor {
    position: absolute; left: 50%; top: 52%; width: 240%; height: 150%;
    transform: translateX(-50%) perspective(340px) rotateX(74deg);
    transform-origin: 50% 0;
    -webkit-mask-image: linear-gradient(to top, #000 8%, color-mix(in oklab, #000 55%, transparent) 40%, transparent 82%);
    mask-image: linear-gradient(to top, #000 8%, color-mix(in oklab, #000 55%, transparent) 40%, transparent 82%);
  }
  .pgrid__lines {
    position: absolute; inset: -50% 0 0 0; height: 200%;
    background-image:
      repeating-linear-gradient(90deg,
        color-mix(in oklab, var(--spectrum-3, #45d3e8) 62%, transparent) 0 2px,
        transparent 2px 88px),
      repeating-linear-gradient(0deg,
        color-mix(in oklab, var(--spectrum-1, #8b5cf6) 68%, transparent) 0 2px,
        transparent 2px 88px);
    background-position: 0 0;
    animation: pgrid-scroll 1.6s linear infinite;
    will-change: background-position;
  }
  /* moving the horizontal ruling downward reads as motion toward the viewer */
  @keyframes pgrid-scroll { to { background-position: 0 88px; } }

  /* soft atmospheric haze pooling at the horizon */
  .pgrid__haze {
    position: absolute; left: 0; right: 0; top: 40%; height: 26%;
    background: linear-gradient(180deg, transparent, color-mix(in oklab, var(--spectrum-1, #8b5cf6) 22%, transparent) 60%, transparent);
    filter: blur(10px); pointer-events: none;
  }

  /* --- caption ----------------------------------------------------------- */
  .pgrid__caption {
    position: relative; text-align: center; display: grid; gap: 0.5rem; justify-items: center;
    max-width: 32rem; margin-top: -3rem;
    text-shadow: 0 2px 18px oklch(10% 0.04 300 / 0.7);
  }
  .pgrid__kicker {
    margin: 0; font-size: var(--step--1, 0.85rem); font-weight: 700;
    letter-spacing: 0.28em; text-transform: uppercase;
    color: color-mix(in oklab, var(--spectrum-3, #45d3e8) 80%, #fff);
  }
  .pgrid__title {
    margin: 0; font-family: var(--font-display, system-ui, sans-serif);
    font-size: clamp(2rem, 1.3rem + 3.4vw, 3.4rem); font-weight: 800;
    letter-spacing: -0.02em; line-height: 1.05; color: var(--ink, #fff);
  }
  .pgrid__sub { margin: 0; color: color-mix(in oklab, var(--ink, #fff) 78%, transparent); font-size: var(--step-0, 1rem); }

  @media (prefers-reduced-motion: reduce) {
    .pgrid__lines { animation: none; }
  }
</style>