Warp Tunnel Background with Rising Colour Beams

A white section that looks down a square tunnel. The floor, ceiling and both walls are grids drawn in perspective, and blocks of colour shoot along them from the far end toward the viewer, like light trails at warp speed. A waitlist card with an email field sits in the centre. The tunnel is built from four tilted boxes with CSS only. On phones the card fills the width and the tunnel keeps its depth.

Code

// Adapted from Magic UI (https://magicui.design/docs/components/warp-background)
// Copyright (c) Magic UI. MIT License: https://we.inc/components/licenses
import { useState } from "react";
import type { CSSProperties, FormEvent } from "react";
import { motion, useReducedMotion } from "framer-motion";

/** Tunnel settings. */
const PERSPECTIVE = 120; // pixels: smaller is a deeper, faster-looking tunnel
const CELL = 5; // grid cell and beam width, as a percent of each wall
const BEAMS_PER_SIDE = 6;
const BEAM_SECONDS = 3;
const GRID_COLOR = "rgba(15, 23, 42, 0.16)";

/** Small repeatable random, so the server and the browser place the same beams. */
function seeded(seed: number) {
  let s = seed;
  return () => {
    s = (s * 16807) % 2147483647;
    return (s - 1) / 2147483646;
  };
}

function makeBeams(seed: number) {
  const rand = seeded(seed);
  const cells = Math.floor(100 / CELL);
  const step = cells / BEAMS_PER_SIDE;
  return Array.from({ length: BEAMS_PER_SIDE }, (_, i) => ({
    x: Math.floor(i * step + rand() * (step - 1)) * CELL,
    hue: Math.floor(rand() * 360),
    ratio: 3 + Math.floor(rand() * 8),
    delay: -rand() * BEAM_SECONDS,
  }));
}

const walls = [
  { name: "top", beams: makeBeams(11) },
  { name: "bottom", beams: makeBeams(23) },
  { name: "left", beams: makeBeams(37) },
  { name: "right", beams: makeBeams(51) },
];

function WarpBackground() {
  return (
    <div
      aria-hidden="true"
      className="warp-tunnel-background-stage"
      style={{ "--warp-perspective": `${PERSPECTIVE}px`, "--warp-cell": `${CELL}%`, "--warp-grid": GRID_COLOR, "--warp-seconds": `${BEAM_SECONDS}s` } as CSSProperties}
    >
      {walls.map((wall) => (
        <div key={wall.name} className={`warp-tunnel-background-wall warp-tunnel-background-${wall.name}`}>
          {wall.beams.map((b, i) => (
            <span
              key={i}
              className="warp-tunnel-background-beam"
              style={{ left: `${b.x}%`, aspectRatio: `1 / ${b.ratio}`, background: `linear-gradient(hsl(${b.hue} 90% 58%), transparent)`, animationDelay: `${b.delay}s` }}
            />
          ))}
        </div>
      ))}
    </div>
  );
}

export default function WarpTunnelBackgroundWithRisingColourBeams() {
  const reduce = useReducedMotion();
  const [email, setEmail] = useState("");
  const [joined, setJoined] = useState(false);

  const submit = (e: FormEvent) => {
    e.preventDefault();
    if (email.includes("@")) setJoined(true);
  };

  return (
    <section className="relative flex min-h-[640px] items-center justify-center overflow-hidden bg-white px-4 py-20 sm:px-6">
      <style>{`
        @keyframes warp-tunnel-background-rise {
          from { transform: translate(-50%, 100cqmax); }
          to { transform: translate(-50%, -100%); }
        }
        .warp-tunnel-background-stage {
          position: absolute;
          inset: 0;
          overflow: hidden;
          pointer-events: none;
          container-type: size;
          clip-path: inset(0);
          perspective: var(--warp-perspective);
          transform-style: preserve-3d;
        }
        .warp-tunnel-background-wall {
          position: absolute;
          height: 100cqmax;
          container-type: inline-size;
          transform-style: preserve-3d;
          background:
            linear-gradient(var(--warp-grid) 0 1px, transparent 1px var(--warp-cell)) 50% -0.5px / var(--warp-cell) var(--warp-cell),
            linear-gradient(90deg, var(--warp-grid) 0 1px, transparent 1px var(--warp-cell)) 50% 50% / var(--warp-cell) var(--warp-cell);
        }
        .warp-tunnel-background-top { width: 100cqi; transform-origin: 50% 0%; transform: rotateX(-90deg); z-index: 2; }
        .warp-tunnel-background-bottom { top: 100%; width: 100cqi; transform-origin: 50% 0%; transform: rotateX(-90deg); }
        .warp-tunnel-background-left { top: 0; left: 0; width: 100cqh; transform-origin: 0% 0%; transform: rotate(90deg) rotateX(-90deg); }
        .warp-tunnel-background-right { top: 0; right: 0; width: 100cqh; transform-origin: 100% 0%; transform: rotate(-90deg) rotateX(-90deg); }
        .warp-tunnel-background-beam {
          position: absolute;
          top: 0;
          width: var(--warp-cell);
          transform: translate(-50%, 100cqmax);
          animation: warp-tunnel-background-rise var(--warp-seconds) linear infinite;
        }
        @media (prefers-reduced-motion: reduce) {
          .warp-tunnel-background-beam { animation: none; transform: translate(-50%, 30cqmax); }
        }
      `}</style>

      <WarpBackground />
      {/* Soft white pool so the far end of the tunnel does not show through the card edges. */}
      <div aria-hidden="true" className="pointer-events-none absolute inset-0" style={{ background: "radial-gradient(ellipse 40% 48% at 50% 50%, #fff 45%, rgba(255,255,255,0) 100%)" }} />

      <motion.div
        initial={reduce ? false : { opacity: 0, scale: 0.94, y: 16 }}
        animate={{ opacity: 1, scale: 1, y: 0 }}
        transition={{ type: "spring", stiffness: 150, damping: 18 }}
        className="relative w-full max-w-md rounded-3xl bg-white p-7 text-center shadow-2xl shadow-slate-900/15 ring-1 ring-slate-900/10 sm:p-9"
      >
        <p className="inline-flex items-center gap-2 rounded-full bg-slate-900 px-3 py-1 text-xs font-semibold uppercase tracking-widest text-white">
          <span className="h-1.5 w-1.5 rounded-full bg-emerald-400" aria-hidden="true" />
          Private beta
        </p>
        <h1 className="mt-5 text-3xl font-semibold tracking-tight text-slate-900 sm:text-4xl">Deploys that land before you look up</h1>
        <p className="mt-3 text-slate-600">Slipstream builds only what changed and ships it to 34 regions in under nine seconds. Invites go out every Thursday.</p>

        {joined ? (
          <p role="status" className="mt-7 rounded-2xl bg-emerald-50 px-4 py-4 text-sm font-medium text-emerald-800">
            You are on the list. We will write to {email} when your invite is ready.
          </p>
        ) : (
          <form onSubmit={submit} className="mt-7 flex flex-col gap-2.5 sm:flex-row">
            <label htmlFor="warp-tunnel-background-email" className="sr-only">Work email</label>
            <input
              id="warp-tunnel-background-email"
              type="email"
              required
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="[email protected]"
              className="h-12 w-full min-w-0 flex-none rounded-xl sm:w-auto sm:flex-1 border border-slate-300 bg-white px-4 text-sm text-slate-900 placeholder:text-slate-500 focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-900/20"
            />
            <button type="submit" className="h-12 rounded-xl bg-slate-900 px-5 text-sm font-semibold text-white transition-colors hover:bg-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-slate-900 focus-visible:ring-offset-2">
              Request invite
            </button>
          </form>
        )}
        <p className="mt-4 text-xs text-slate-600">2,180 teams are ahead of you. No card needed.</p>
      </motion.div>
    </section>
  );
}

Install with one command: npx shadcn@latest add https://we.inc/r/warp-tunnel-background-with-rising-colour-beams.json

When to use it

How to customize it

Accessibility

Questions

How is the tunnel built?

The stage has a CSS `perspective`. Inside it are four long boxes, each hinged on one edge of the stage and rotated ninety degrees away from the viewer, so they become the ceiling, floor and two walls. Each box carries a grid drawn with two repeating gradients.

What are the `cqmax`, `cqi` and `cqh` units?

They are container query length units. The stage is declared as a size container, so `100cqi` is its width and `100cqh` its height. This lets the walls take their size from the section, whatever its shape, without JavaScript.

How do the beams move?

Each beam is a gradient block on a wall. A keyframe animation slides it from the far end of the wall to past the near end. Because the wall is tilted in 3D, the browser draws that straight slide as a rush toward the viewer.

Which browsers support it?

It needs container query units and 3D transforms, which current versions of Chrome, Edge, Safari and Firefox all have. In an old browser the section shows a plain background with the card.

Can I wrap other content instead of the card?

Yes. `WarpBackground` fills any `relative` parent with `overflow-hidden`. Place it first and put your content after it with `relative` so it sits on top.

Adapted from Warp Background in Magic UI, used under its open-source licence. Licences and credits

Related: Animated Grid Background with Travelling Light Beams, Starfield Background with Depth and Shooting Stars, Newsletter Signup with Benefit List.

More animated backgrounds or all free UI components. Start for free to have a full site built around it.

More in Backgrounds

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap