{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"meteors","type":"registry:ui","title":"Meteors","description":"Meteor shower in depth: seeded meteors with glowing heads and tapering tails fall diagonally in parallax (near ones larger, brighter and faster), fading in and burning out, a few flaring before they go","author":"fancy-ui <https://fancy-ui.rama.app>","categories":["effects"],"registryDependencies":["https://fancy-ui.rama.app/r/utils.json"],"files":[{"path":"react/src/components/meteors/Meteors.tsx","content":"\"use client\";\n\nimport { useMemo, type CSSProperties } from \"react\";\nimport { cn } from \"../../utils\";\nimport \"./meteors.css\";\n\n/**\n * Meteors - Animated meteor shower effect\n *\n * A seeded field of meteors, each at its own depth: near ones are larger,\n * brighter, longer-tailed and faster, far ones faint and slow, so the\n * shower reads in parallax. Each meteor fades in, streaks across on a\n * diagonal and burns out; a few flare before they go.\n */\nexport interface MeteorsProps {\n\t/** Number of meteors to render */\n\tcount?: number;\n\t/** Additional CSS classes applied to each meteor */\n\tclassName?: string;\n\t/** Direction of travel, in degrees (215 = down and to the right) */\n\tangle?: number;\n\t/** Speed multiplier: 2 is twice as fast */\n\tspeed?: number;\n\t/** Head and tail colour. Defaults to a pale blue-white on dark pages, slate on light ones */\n\tcolor?: string;\n\t/** Seed for the field — same seed, same shower (and the same markup on server and client) */\n\tseed?: number;\n}\n\nexport interface Meteor {\n\t/** Start position, % of the container */\n\tleft: number;\n\ttop: number;\n\t/** 0 (far) to 1 (near) */\n\tdepth: number;\n\t/** Seconds for one pass */\n\tduration: number;\n\t/** Negative start offset, so the shower is already under way */\n\tdelay: number;\n\t/** Whether this one flares before it burns out */\n\tflare: boolean;\n}\n\n/** Small seedable PRNG. Same seed → same sequence, on the server and in the browser. */\nfunction mulberry32(seed: number): () => number {\n\tlet a = seed >>> 0;\n\treturn () => {\n\t\ta = (a + 0x6d2b79f5) >>> 0;\n\t\tlet t = a;\n\t\tt = Math.imul(t ^ (t >>> 15), t | 1);\n\t\tt ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n\t\treturn ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n\t};\n}\n\n/**\n * The shower. Starts reach well past the left edge (meteors drift right\n * as they fall, so a start on the left is what fills the left half) and\n * over the upper part of the height; depth skewed toward far, the way a\n * real sky has many faint meteors and few bright ones.\n */\nexport function meteorField(count: number, seed = 1): Meteor[] {\n\tconst n = Math.max(0, Math.floor(Number.isFinite(count) ? count : 0));\n\tconst rand = mulberry32(seed);\n\treturn Array.from({ length: n }, () => {\n\t\tconst depth = Math.pow(rand(), 1.6);\n\t\tconst duration = 7 - depth * 4.5 + rand() * 1.5; // near: ~2.5–4s, far: ~7–8.5s\n\t\treturn {\n\t\t\tleft: -50 + rand() * 150,\n\t\t\ttop: -30 + rand() * 60,\n\t\t\tdepth,\n\t\t\tduration,\n\t\t\tdelay: -rand() * duration * 1.6,\n\t\t\tflare: rand() < 0.2,\n\t\t};\n\t});\n}\n\nfunction styleFor(m: Meteor, speed: number, angle: number, color?: string): CSSProperties {\n\tconst scale = 0.55 + m.depth * 0.9; // size of the head\n\tconst tail = Math.round(40 + m.depth * 110); // px\n\tconst brightness = (0.35 + m.depth * 0.65).toFixed(2);\n\tconst style: Record<string, string> = {\n\t\tleft: `${m.left.toFixed(2)}%`,\n\t\ttop: `${m.top.toFixed(2)}%`,\n\t\tanimationDelay: `${(m.delay / speed).toFixed(2)}s`,\n\t\tanimationDuration: `${(m.duration / speed).toFixed(2)}s`,\n\t\t\"--meteor-angle\": `${angle}deg`,\n\t\t\"--meteor-scale\": scale.toFixed(2),\n\t\t\"--meteor-tail\": `${tail}px`,\n\t\t\"--meteor-brightness\": brightness,\n\t\t\"--meteor-travel\": `${Math.round(700 + m.depth * 500)}px`,\n\t};\n\tif (color) style[\"--meteor-color\"] = color;\n\treturn style as CSSProperties;\n}\n\nexport function Meteors({\n\tcount = 20,\n\tclassName,\n\tangle = 215,\n\tspeed = 1,\n\tcolor,\n\tseed = 1,\n}: MeteorsProps) {\n\t// Recomputed only when `count` or `seed` changes — the same dependencies\n\t// the Svelte source's `$derived` tracks.\n\tconst meteors = useMemo(() => meteorField(count, seed), [count, seed]);\n\tconst speedC = Number.isFinite(speed) && speed > 0 ? speed : 1;\n\tconst angleC = Number.isFinite(angle) ? angle : 215;\n\n\treturn (\n\t\t<>\n\t\t\t{meteors.map((meteor, i) => (\n\t\t\t\t<span\n\t\t\t\t\tkey={i}\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"meteor pointer-events-none absolute top-0 h-0.5 w-0.5 rounded-full opacity-0\",\n\t\t\t\t\t\tmeteor.flare && \"meteor--flare\",\n\t\t\t\t\t\tclassName\n\t\t\t\t\t)}\n\t\t\t\t\tstyle={styleFor(meteor, speedC, angleC, color)}\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t/>\n\t\t\t))}\n\t\t</>\n\t);\n}\n","type":"registry:ui","target":"@ui/fancy/components/meteors/Meteors.tsx"},{"path":"react/src/components/meteors/index.ts","content":"\"use client\";\n\nexport { Meteors } from \"./Meteors\";\nexport type { MeteorsProps } from \"./Meteors\";\n","type":"registry:ui","target":"@ui/fancy/components/meteors/index.ts"},{"path":"react/src/components/meteors/meteors.css","content":"/* Light pages get a slate meteor; dark ones a pale blue-white. A `color`\n   prop (inline) overrides both. */\n.meteor {\n\t--_meteor-color: var(--meteor-color, #475569);\n\tbackground: var(--_meteor-color);\n\ttransform: rotate(var(--meteor-angle, 215deg)) scale(var(--meteor-scale, 1));\n\t/* the head's glow */\n\tbox-shadow:\n\t\t0 0 2px 1px color-mix(in srgb, var(--_meteor-color) 70%, transparent),\n\t\t0 0 8px 2px color-mix(in srgb, var(--_meteor-color) 35%, transparent);\n\tanimation: meteor-fall 5s cubic-bezier(0.3, 0, 0.9, 1) infinite;\n}\n\n.dark .meteor {\n\t--_meteor-color: var(--meteor-color, #dbeafe);\n}\n\n/* The tail: thickest at the head, fading out behind it. */\n.meteor::before {\n\tcontent: \"\";\n\tposition: absolute;\n\ttop: 50%;\n\tleft: 1px;\n\twidth: var(--meteor-tail, 60px);\n\theight: 1px;\n\ttransform: translateY(-50%);\n\tborder-radius: 999px;\n\tbackground: linear-gradient(\n\t\tto right,\n\t\tvar(--_meteor-color),\n\t\tcolor-mix(in srgb, var(--_meteor-color) 35%, transparent) 30%,\n\t\ttransparent\n\t);\n}\n\n@keyframes meteor-fall {\n\t0% {\n\t\ttransform: rotate(var(--meteor-angle, 215deg)) scale(var(--meteor-scale, 1)) translateX(0);\n\t\topacity: 0;\n\t}\n\t8% {\n\t\topacity: var(--meteor-brightness, 1);\n\t}\n\t70% {\n\t\topacity: var(--meteor-brightness, 1);\n\t}\n\t100% {\n\t\ttransform: rotate(var(--meteor-angle, 215deg)) scale(var(--meteor-scale, 1))\n\t\t\ttranslateX(calc(-1 * var(--meteor-travel, 800px)));\n\t\topacity: 0;\n\t}\n}\n\n/* A flare: the meteor swells and brightens just before it burns out. */\n.meteor.meteor--flare {\n\tanimation-name: meteor-fall-flare;\n}\n\n@keyframes meteor-fall-flare {\n\t0% {\n\t\ttransform: rotate(var(--meteor-angle, 215deg)) scale(var(--meteor-scale, 1)) translateX(0);\n\t\topacity: 0;\n\t}\n\t8% {\n\t\topacity: var(--meteor-brightness, 1);\n\t}\n\t62% {\n\t\topacity: var(--meteor-brightness, 1);\n\t\tbox-shadow:\n\t\t\t0 0 2px 1px color-mix(in srgb, var(--_meteor-color) 70%, transparent),\n\t\t\t0 0 8px 2px color-mix(in srgb, var(--_meteor-color) 35%, transparent);\n\t}\n\t74% {\n\t\topacity: 1;\n\t\tbox-shadow:\n\t\t\t0 0 4px 2px var(--_meteor-color),\n\t\t\t0 0 18px 6px color-mix(in srgb, var(--_meteor-color) 55%, transparent);\n\t}\n\t100% {\n\t\ttransform: rotate(var(--meteor-angle, 215deg)) scale(var(--meteor-scale, 1))\n\t\t\ttranslateX(calc(-1 * var(--meteor-travel, 800px)));\n\t\topacity: 0;\n\t}\n}\n\n/* Reduced motion: a still sky — each meteor frozen part-way along its path. */\n@media (prefers-reduced-motion: reduce) {\n\t.meteor,\n\t.meteor.meteor--flare {\n\t\tanimation: none;\n\t\topacity: calc(var(--meteor-brightness, 1) * 0.7);\n\t\ttransform: rotate(var(--meteor-angle, 215deg)) scale(var(--meteor-scale, 1))\n\t\t\ttranslateX(calc(-0.35 * var(--meteor-travel, 800px)));\n\t}\n}\n","type":"registry:ui","target":"@ui/fancy/components/meteors/meteors.css"}],"meta":{"license":"MIT","package":"fancy-ui-react","version":"0.7.0","source":"https://fancy-ui.rama.app/docs/components/meteors?framework=react"}}