# Meteors

> A deterministic diagonal meteor shower background.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/components/meteors
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/meteors.json
```

Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Dependencies

No extra dependencies.

## Usage

```tsx title="components/meteors-demo.tsx"
import { Meteors } from "@/components/meteors"

export default function MeteorsDemo() {
  return (
    <div className="relative flex h-80 w-full max-w-2xl items-center justify-center overflow-hidden rounded-3xl border border-white/10 bg-zinc-950 text-sky-200">
      <Meteors count={22} />
      <div className="relative z-10 px-6 text-center">
        <p className="text-xs font-medium tracking-[0.25em] text-sky-300/70 uppercase">Perseids</p>
        <h3 className="mt-2 text-3xl font-semibold tracking-tight text-white">Look up tonight</h3>
        <p className="mt-2 text-sm text-zinc-400">Up to 100 meteors an hour at the peak.</p>
      </div>
    </div>
  )
}
```

## Source

```tsx title="components/meteors.tsx"
import * as React from "react"

import { cn } from "@/lib/utils"

const keyframes = `@keyframes atelier-meteor{0%{transform:rotate(215deg) translateX(0);opacity:1}70%{opacity:1}100%{transform:rotate(215deg) translateX(-700px);opacity:0}}`

// Integer hash -> [0, 1). Pure and identical on server and client, so the
// meteor field never causes a hydration mismatch.
function hash(n: number, salt: number) {
  let x = Math.imul(n * 374761393 + salt * 668265263, 0x85ebca6b)
  x ^= x >>> 13
  x = Math.imul(x, 0xc2b2ae35)
  x ^= x >>> 16
  return (x >>> 0) / 4294967296
}

export interface MeteorsProps extends React.ComponentProps<"div"> {
  /** Number of streaks */
  count?: number
  /** Streak color, any CSS color. Defaults to the inherited text color */
  color?: string
  /** Shortest fall time in seconds */
  minDuration?: number
  /** Longest fall time in seconds */
  maxDuration?: number
  /** Seed to get a different but still deterministic arrangement */
  seed?: number
}

// Fills its (relative, overflow-hidden) parent. Negative delays mean the sky is
// already populated on first paint.
export function Meteors({
  count = 20,
  color = "currentColor",
  minDuration = 3,
  maxDuration = 8,
  seed = 0,
  className,
  style,
  ...props
}: MeteorsProps) {
  return (
    <div
      aria-hidden
      className={cn("pointer-events-none absolute inset-0 overflow-hidden motion-reduce:hidden", className)}
      style={{ "--meteor-color": color, ...style } as React.CSSProperties}
      {...props}
    >
      <style href="atelier-meteors" precedence="default">
        {keyframes}
      </style>
      {Array.from({ length: count }, (_, i) => {
        const left = -20 + hash(i, seed + 1) * 120
        const top = -10 + hash(i, seed + 2) * 60
        const duration = minDuration + hash(i, seed + 3) * (maxDuration - minDuration)
        const delay = -hash(i, seed + 4) * duration * 2
        const tail = 40 + Math.round(hash(i, seed + 5) * 60)
        return (
          <span
            key={i}
            className="absolute size-0.5 rounded-full bg-(--meteor-color) shadow-[0_0_0_1px_rgb(255_255_255/0.08)]"
            style={{
              left: `${left.toFixed(2)}%`,
              top: `${top.toFixed(2)}%`,
              animationName: "atelier-meteor",
              animationDuration: `${duration.toFixed(2)}s`,
              animationDelay: `${delay.toFixed(2)}s`,
              animationTimingFunction: "linear",
              animationIterationCount: "infinite",
              opacity: 0,
            }}
          >
            <span
              className="absolute top-1/2 left-full h-px -translate-y-1/2"
              style={{
                width: tail,
                background: "linear-gradient(to right, color-mix(in oklab, var(--meteor-color) 80%, transparent), transparent)",
              }}
            />
          </span>
        )
      })}
    </div>
  )
}
```
