Backgrounds
Meteors
A decorative diagonal shooting-star field for hero sections and card backdrops — pure CSS, deterministically scattered.
Preview in your theme
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/meteors.jsonPrompt
The prompt behind this component — paste it into your AI assistant to recreate or adapt it.
Build a React + TypeScript + Tailwind "Meteors" component — a decorative
diagonal shooting-star field for hero sections and card backdrops. Its only
dependency is a cn() class merger (clsx + tailwind-merge). No hooks, no
events, no browser APIs, so it must stay a server component — do NOT add
"use client".
Contract
- export function Meteors(props): props extend React.ComponentProps<"div">
(rest props spread onto the root div) plus:
- count?: number (default 12) — how many meteor streaks to render.
- children render above the effect layer; className merges onto the root via
cn(), so sizing, borders, rounding and the background color come from the
call site.
Behavior
- Root div: relative isolate overflow-hidden. An absolutely-positioned effect
layer (aria-hidden, pointer-events-none, inset-0) holds `count` meteor
spans; children sit in a separate relative z-10 wrapper above it.
- Each meteor is a thin bar (h-px w-12) animated via one shared @keyframes
loop: the transform stays rotate(135deg) for the whole cycle (a 45deg
diagonal line) while translateX walks it along that same rotated axis, so
it travels top-right -> bottom-left; opacity fades in a few percent into
the cycle and fades out before the end, so it never pops in or vanishes
abruptly at the seams.
- Per-meteor start position (top/left %), phase delay and duration are NOT
Math.random() — render must stay a pure function of props (React purity
rule + stable SSR output). Instead a small integer hash, e.g.
Math.imul(i * 374761393 + salt * 668265263 + 0x9e3779b9, 2654435761) >>> 0
divided by 2**32, maps (index, salt) to a deterministic [0, 1) value. Four
salted calls per index derive top%, left%, a duration mapped into a
1.6s-3.2s band, and a NEGATIVE delay (-hash * duration), so the field looks
fully scattered and mid-flight on first paint instead of every meteor
launching from the same point in sync.
- The @keyframes ship inside the component via a React 19 hoisted
<style href="zyeon-meteors" precedence="medium"> tag — no Tailwind config
edits, and multiple instances dedupe to one style tag by href.
- prefers-reduced-motion: every meteor span gets motion-reduce:hidden — this
is pure decoration, so under reduced motion it disappears entirely rather
than freezing mid-animation; children are unaffected either way.
Rendering & styling
- Semantic tokens only. Each meteor is
bg-gradient-to-r from-transparent via-foreground/40 to-foreground/70 — the
bright tip (to-foreground/70) leads the direction of travel, the tail fades
to transparent behind it. No hex / rgb() / oklch() anywhere, so the streaks
re-skin themselves with the host theme and dark mode automatically.
- Merge consumer className via cn() on the root div; the component sets no
background of its own, so a light card, an inverted bg-foreground hero, or
any other surface all work as the backdrop.
Customization levers
- Count: the count prop is the density knob — a handful (4-8) reads as a
restrained accent on a card, 20+ reads as a dense shower for a hero or
launch screen.
- Speed range: the 1.6s-3.2s duration band lives in one expression in the
hash-to-duration mapping; widen or shift it to speed up or slow down the
whole field uniformly.
- Angle: the shared rotate(135deg) is the only place the flight angle is set;
swap it (and the translateX sign if needed) to fly meteors at a different
diagonal.
- Color: swap via-foreground/to-foreground for via-primary/to-primary (or
any other token) for tinted, on-brand meteors instead of neutral ones.
- Region clipping: cap the hash-derived top% range (e.g. 0-40 instead of
0-100) to keep meteors confined to the upper portion of a tall container
instead of the full height.Concepts
- Deterministic pseudo-randomness — a seeded integer hash replaces
Math.random(), so every meteor's position, delay and duration are a pure function of its index: identical output on the server and the client, no hydration mismatch, no reshuffling on re-render. - Phase-staggered delay — each meteor's
animation-delayis a negative multiple of its own duration, so the field starts mid-cycle and scattered rather than every streak launching from the same point in lockstep. - Single rotated axis — the flight keyframes hold
rotate(135deg)fixed and only animatetranslateX, so the bar always travels along its own long axis — a 45° diagonal — with the gradient's bright end naturally leading. - Token-derived streak — the bar's gradient runs over
--foreground(via-foreground/40,to-foreground/70), so it reads correctly against any card, hero, or theme without a single hardcoded color. - Decorative reduced-motion — meteors are pure ornament: under
prefers-reduced-motionthey vanish completely (motion-reduce:hidden) instead of freezing, and children are never affected either way. - Layer separation — the streak field lives in a single
aria-hidden,pointer-events-nonelayer; content sits in az-10layer above it, so decoration never captures clicks or enters the accessibility tree.