{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "day-night-toggle",
  "title": "Day / Night Toggle",
  "description": "Theme switch where the sun rolls into a moon, clouds fall away and stars drop in. Free, MIT, by qodesign.dev.",
  "dependencies": [
    "framer-motion"
  ],
  "files": [
    {
      "path": "components/day-night-toggle/react/src/components/DayNightToggle.tsx",
      "content": "'use client';\nimport { motion, useReducedMotion } from 'framer-motion';\nimport { useMemo, useState } from 'react';\n\nexport type DayNightToggleProps = {\n  /** start in night mode (uncontrolled) */\n  defaultNight?: boolean;\n  /** control the state yourself; pair it with onChange */\n  night?: boolean;\n  /** called with the new state after every toggle */\n  onChange?: (night: boolean) => void;\n  /** width in px; everything scales from the 640 x 256 design */\n  width?: number;\n  /** accessible label */\n  label?: string;\n};\n\n// same curves as the CSS version\nconst KNOB_EASE = [0.7, -0.3, 0.3, 1.3] as const;\nconst CSS_EASE = [0.25, 0.1, 0.25, 1] as const;\nconst STAR_EASE = [0.2, 0.8, 0.2, 1.2] as const;\nconst EASE_IN = [0.42, 0, 1, 1] as const;\n\nconst CLOUDS = [\n  { w: 190, h: 70, right: 50, top: 150, delay: 0, opacity: 1 },\n  { w: 138, h: 55, right: 188, top: 188, delay: 0.06, opacity: 1 },\n  { w: 112, h: 48, right: 88, top: 88, delay: 0.12, opacity: 0.85 },\n];\nconst CRATERS = [\n  { s: 48, left: 48, top: 52 },\n  { s: 30, left: 122, top: 120 },\n  { s: 22, left: 72, top: 148 },\n];\n\nexport default function DayNightToggle({\n  defaultNight = false,\n  night: controlled,\n  onChange,\n  width = 640,\n  label = 'Dark mode',\n}: DayNightToggleProps) {\n  const [inner, setInner] = useState(defaultNight);\n  const night = controlled ?? inner;\n  const reduce = useReducedMotion();\n  // under Reduce Motion everything cross-fades in 0.2 s instead of rolling and falling\n  const T = (t: object) => (reduce ? { duration: 0.2 } : t);\n  const scale = width / 640;\n\n  // 22 stars, same pseudo-random layout as the HTML version\n  const stars = useMemo(\n    () =>\n      Array.from({ length: 22 }, (_, i) => {\n        const r = (n: number) => Math.abs((Math.sin(i * 97.13 + n) * 43758.5453) % 1);\n        return { left: 30 + r(1) * 340, top: 22 + r(2) * 212, delay: +(r(3) * 0.35).toFixed(2), size: 4 + r(4) * 6 };\n      }),\n    [],\n  );\n\n  const toggle = () => {\n    const next = !night;\n    setInner(next);\n    onChange?.(next);\n  };\n\n  return (\n    <div className=\"qd-dnt\" style={{ width, height: 256 * scale }}>\n      <button\n        type=\"button\"\n        role=\"switch\"\n        aria-checked={night}\n        aria-label={label}\n        onClick={toggle}\n        className=\"relative block origin-top-left cursor-pointer overflow-hidden rounded-full border-0 p-0 outline-none focus-visible:[outline:10px_solid_#8B6CFF] focus-visible:[outline-offset:8px]\"\n        style={{\n          width: 640,\n          height: 256,\n          transform: `scale(${scale})`,\n          // inline, so the keyboard focus ring above is an outline: a Tailwind ring is a box-shadow and would lose to this\n          boxShadow: 'inset 0 10px 22px rgba(0,0,0,.25)',\n          background: night ? 'linear-gradient(#0B1033, #27306B)' : 'linear-gradient(#5DB8FF, #A8DBFF)',\n        }}\n      >\n        {stars.map((s, i) => (\n          <motion.i\n            key={i}\n            className=\"absolute block rounded-full bg-white\"\n            style={{ left: s.left, top: s.top, width: s.size, height: s.size }}\n            initial={false}\n            animate={reduce ? { y: 0, opacity: night ? 1 : 0 } : { y: night ? 0 : -300, opacity: 1 }}\n            transition={T({ duration: 0.7, ease: STAR_EASE, delay: s.delay })}\n          />\n        ))}\n        {CLOUDS.map((c, i) => (\n          <motion.div\n            key={i}\n            className=\"absolute rounded-full bg-white\"\n            style={{ width: c.w, height: c.h, right: c.right, top: c.top, opacity: c.opacity }}\n            initial={false}\n            animate={reduce ? { y: 0, opacity: night ? 0 : c.opacity } : { y: night ? 240 : 0, opacity: c.opacity }}\n            transition={T({ duration: 0.8, ease: EASE_IN, delay: c.delay })}\n          />\n        ))}\n        <motion.div\n          className=\"absolute rounded-full\"\n          style={{ top: 22, left: 22, width: 212, height: 212 }}\n          initial={false}\n          animate={{\n            x: night ? 384 : 0,\n            rotate: night && !reduce ? 360 : 0,\n            backgroundColor: night ? '#E9ECF5' : '#FFD23F',\n            boxShadow: night ? '0 0 50px #9DB2FF' : '0 0 70px #FFD23F',\n          }}\n          transition={reduce ? { duration: 0.2 } : {\n            x: { duration: 0.8, ease: KNOB_EASE },\n            rotate: { duration: 0.8, ease: KNOB_EASE },\n            backgroundColor: { duration: 0.8, ease: CSS_EASE },\n            boxShadow: { duration: 0.8, ease: CSS_EASE },\n          }}\n        >\n          {CRATERS.map((c, i) => (\n            <motion.div\n              key={i}\n              className=\"absolute rounded-full bg-[#C3C9DB]\"\n              style={{ width: c.s, height: c.s, left: c.left, top: c.top }}\n              initial={false}\n              animate={{ opacity: night ? 1 : 0 }}\n              transition={T({ duration: 0.5, delay: 0.3 })}\n            />\n          ))}\n        </motion.div>\n      </button>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/day-night-toggle/DayNightToggle.tsx"
    }
  ],
  "type": "registry:component"
}