Skip to content
All Taste components

Animated heroes

Hero with spotlight grid

A centred opening section over a dot grid that a slow spotlight sweeps across, lighting the dots in brand colour.

dot matrix · sweeping spotlight · lit dots · live status chips

Uptime monitoring

Know it’s down before your customers do.

Halcyon checks your endpoints from 18 regions every 30 seconds and pages the right person with the logs already attached.

Mumbai42 msFrankfurt88 msVirginia131 ms
"use client";
import { ArrowRight } from "lucide-react";

/* Add once to globals.css. The lit dots move against the beam, so they stay
   in place while the light passes over them:
@keyframes beam { from { transform: translateX(-30%); } to { transform: translateX(130%); } }
@keyframes beam-in { from { transform: translateX(15%); } to { transform: translateX(-65%); } }
*/

const regions = [
  { city: "Mumbai", ms: "42 ms" },
  { city: "Frankfurt", ms: "88 ms" },
  { city: "Virginia", ms: "131 ms" },
];
const dots = "radial-gradient(circle at 1px 1px, var(--tst-border-strong) 1px, transparent 1.6px)";
const litDots = "radial-gradient(circle at 1px 1px, var(--tst-primary) 1.3px, transparent 1.9px)";
const spot = "radial-gradient(closest-side, var(--tst-fg), transparent)";

export function HeroSpotlightGrid() {
  return (
    <section aria-labelledby="spot-title" className="relative isolate overflow-hidden bg-bg font-sans">
      <div
        aria-hidden
        className="absolute inset-0 -z-20 [mask-image:radial-gradient(ellipse_85%_75%_at_50%_35%,var(--tst-fg)_30%,transparent_80%)]"
        style={{ backgroundImage: dots, backgroundSize: "18px 18px" }}
      />
      <div
        aria-hidden
        className="absolute inset-y-0 left-0 -z-10 w-1/2 overflow-hidden bg-[radial-gradient(closest-side,color-mix(in_srgb,var(--tst-primary)_18%,transparent),transparent)] animate-[beam_10s_ease-in-out_infinite_alternate] motion-reduce:animate-none"
        style={{ maskImage: spot, WebkitMaskImage: spot }}
      >
        <div
          className="absolute inset-y-0 left-0 w-[200%] animate-[beam-in_10s_ease-in-out_infinite_alternate] motion-reduce:animate-none"
          style={{ backgroundImage: litDots, backgroundSize: "18px 18px" }}
        />
      </div>

      <div className="mx-auto flex max-w-3xl flex-col items-center gap-5 px-4 py-24 text-center sm:px-6">
        <span className="rounded-full border border-border-strong px-2.5 py-0.5 text-xs font-medium text-fg-muted animate-rise motion-reduce:animate-none">Uptime monitoring</span>
        <h1
          id="spot-title"
          className="text-balance bg-[linear-gradient(180deg,var(--tst-fg)_35%,color-mix(in_srgb,var(--tst-fg)_55%,var(--tst-bg)))] bg-clip-text text-4xl font-medium leading-[1.05] tracking-tight text-transparent sm:text-5xl lg:text-6xl animate-rise [animation-delay:70ms] motion-reduce:animate-none"
        >
          Know it’s down before your customers do.
        </h1>
        <p className="max-w-xl text-pretty text-base leading-relaxed text-fg-muted sm:text-lg animate-rise [animation-delay:140ms] motion-reduce:animate-none">
          Halcyon checks your endpoints from 18 regions every 30 seconds and pages the right person with the logs already attached.
        </p>
        <div className="flex flex-wrap justify-center gap-3 animate-rise [animation-delay:210ms] motion-reduce:animate-none">
          <a href="/signup" className="inline-flex h-11 items-center gap-1.5 rounded bg-primary px-5 font-medium text-primary-fg transition hover:bg-primary-hover active:scale-[.97]">
            Start monitoring <ArrowRight className="size-4" aria-hidden />
          </a>
          <a href="/status" className="inline-flex h-11 items-center rounded px-5 font-medium text-fg transition hover:bg-surface-2">View live status</a>
        </div>
        <div className="mt-2 flex flex-wrap justify-center gap-2">
          {regions.map((r, i) => (
            <span key={r.city} className="inline-flex h-7 items-center gap-2 rounded-full border border-border bg-bg px-3 text-xs text-fg-muted animate-pop motion-reduce:animate-none" style={{ animationDelay: 500 + i * 70 + "ms" }}>
              <span className="size-1.5 animate-pulse rounded-full bg-success motion-reduce:animate-none" />
              {r.city} <b className="font-medium tabular-nums text-fg">{r.ms}</b>
            </span>
          ))}
        </div>
      </div>
    </section>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.