{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "lens-clock",
  "type": "registry:ui",
  "title": "Lens Clock",
  "description": "Component for lens-clock",
  "dependencies": [
    "framer-motion"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/ui/lens-clock.tsx",
      "type": "registry:ui",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { useReducedMotion, useSpring } from \"framer-motion\"\nimport { cn } from \"@/lib/utils\"\n\nexport interface LensClockProps {\n  /** IANA time zone such as \"America/Los_Angeles\". Omit to use the viewer's own zone. */\n  timeZone?: string\n  /** Fixed moment to display. Omit to keep the clock live, read from the viewer's device clock. */\n  time?: Date\n  /** Force a palette. Omit to follow the surrounding `dark` class. */\n  theme?: \"light\" | \"dark\"\n  className?: string\n}\n\n// Geometry in card units (the card is a 100 x 100 viewBox).\nconst TICK_OUTER = 54\nconst MINOR_INNER = 50.5\nconst HOUR_INNER = 42\nconst LABEL_RADIUS = 61\nconst HAND_LENGTH = 33\nconst MINUTE_LENGTH = 48\nconst SECOND_LENGTH = 52\n// How far the dial centre sits behind the card centre, opposite the hand.\nconst PAN_DISTANCE = 24\n\nconst LIGHT_VARS =\n  \"[--lc-accent:#ff8a00] [--lc-lume:#62c42a] [--lc-card-from:#f6f6f8] [--lc-card-to:#e2e1e6] [--lc-edge:rgba(255,255,255,0.9)] [--lc-tick:#a3a2a8] [--lc-label:#8e8d93] [--lc-spoke:#6f6e76] [--lc-hand:#fbfbfc] [--lc-hand-edge:rgba(0,0,0,0.06)] [--lc-pivot-from:#ffffff] [--lc-pivot-to:#e6e5ea] [--lc-sleeve:#ffffff]\"\n\nconst DARK_VARS =\n  \"[--lc-accent:#ff9f0a] [--lc-lume:#6fd136] [--lc-card-from:#262628] [--lc-card-to:#141415] [--lc-edge:rgba(255,255,255,0.07)] [--lc-tick:#76767b] [--lc-label:#808086] [--lc-spoke:#a2a2a8] [--lc-hand:#46464a] [--lc-hand-edge:rgba(255,255,255,0.06)] [--lc-pivot-from:#4a4a4d] [--lc-pivot-to:#2a2a2c] [--lc-sleeve:#38383b]\"\n\n// Written out in full so Tailwind can detect every class.\nconst AUTO_DARK_VARS =\n  \"dark:[--lc-accent:#ff9f0a] dark:[--lc-lume:#6fd136] dark:[--lc-card-from:#262628] dark:[--lc-card-to:#141415] dark:[--lc-edge:rgba(255,255,255,0.07)] dark:[--lc-tick:#76767b] dark:[--lc-label:#808086] dark:[--lc-spoke:#a2a2a8] dark:[--lc-hand:#46464a] dark:[--lc-hand-edge:rgba(255,255,255,0.06)] dark:[--lc-pivot-from:#4a4a4d] dark:[--lc-pivot-to:#2a2a2c] dark:[--lc-sleeve:#38383b]\"\n\nconst TIME_FORMAT: Intl.DateTimeFormatOptions = {\n  hour: \"numeric\",\n  minute: \"numeric\",\n  second: \"numeric\",\n  hourCycle: \"h23\",\n}\n\nfunction createFormatter(timeZone?: string) {\n  try {\n    return new Intl.DateTimeFormat(\"en-US\", { ...TIME_FORMAT, timeZone })\n  } catch {\n    // Unknown zone: fall back to the viewer's own.\n    return new Intl.DateTimeFormat(\"en-US\", TIME_FORMAT)\n  }\n}\n\nfunction readTime(date: Date, formatter: Intl.DateTimeFormat) {\n  const parts = formatter.formatToParts(date)\n  const get = (type: Intl.DateTimeFormatPartTypes) =>\n    Number(parts.find((part) => part.type === type)?.value ?? 0)\n  return { hours: get(\"hour\") % 24, minutes: get(\"minute\"), seconds: get(\"second\") }\n}\n\ntype SpringOptions = Parameters<typeof useSpring>[1]\n\n// Springs toward an unwrapped angle so 59 -> 0 keeps moving forward.\nfunction useAngle(target: number | null, reduceMotion: boolean, options: SpringOptions) {\n  const angle = useSpring(0, options)\n  const unwrapped = React.useRef<number | null>(null)\n\n  React.useEffect(() => {\n    if (target === null) return\n    const previous = unwrapped.current\n    const next =\n      previous === null\n        ? target\n        : previous + ((((target - previous) % 360) + 540) % 360) - 180\n    unwrapped.current = next\n    if (previous === null || reduceMotion) angle.jump(next)\n    else angle.set(next)\n  }, [target, reduceMotion, angle])\n\n  return angle\n}\n\nfunction useNow(fixed?: Date) {\n  const [now, setNow] = React.useState<Date | null>(null)\n\n  React.useEffect(() => {\n    if (fixed) return\n    const tick = () => setNow(new Date())\n    tick()\n    let interval: ReturnType<typeof setInterval> | undefined\n    // Align updates with the wall-clock second.\n    const timeout = setTimeout(() => {\n      tick()\n      interval = setInterval(tick, 1000)\n    }, 1000 - (Date.now() % 1000))\n    return () => {\n      clearTimeout(timeout)\n      if (interval) clearInterval(interval)\n    }\n  }, [fixed])\n\n  return fixed ?? now\n}\n\nfunction pad(value: number) {\n  return value.toString().padStart(2, \"0\")\n}\n\nexport function LensClock({ timeZone, time, theme, className }: LensClockProps) {\n  const now = useNow(time)\n  const formatter = React.useMemo(() => createFormatter(timeZone), [timeZone])\n  const parts = now ? readTime(now, formatter) : null\n  const reduceMotion = useReducedMotion() ?? false\n  const id = React.useId().replace(/:/g, \"\")\n\n  const dialRef = React.useRef<SVGGElement>(null)\n  const hourRef = React.useRef<SVGGElement>(null)\n  const minuteRef = React.useRef<SVGGElement>(null)\n  const secondRef = React.useRef<SVGGElement>(null)\n\n  const ready = now !== null\n  const live = !time\n  const target = parts\n    ? ((parts.hours % 12) + parts.minutes / 60 + parts.seconds / 3600) * 30\n    : null\n\n  // A fixed `time` springs into place; the live clock is drawn per frame below.\n  const hourAngle = useAngle(live ? null : target, reduceMotion, { stiffness: 70, damping: 18 })\n  const minuteAngle = useAngle(\n    !live && parts ? (parts.minutes + parts.seconds / 60) * 6 : null,\n    reduceMotion,\n    { stiffness: 90, damping: 18 }\n  )\n  const secondAngle = useAngle(!live && parts ? parts.seconds * 6 : null, reduceMotion, {\n    stiffness: 90,\n    damping: 18,\n  })\n\n  const draw = React.useCallback((hour: number, minute: number, second: number) => {\n    const rad = (hour * Math.PI) / 180\n    const cx = 50 - PAN_DISTANCE * Math.sin(rad)\n    const cy = 50 + PAN_DISTANCE * Math.cos(rad)\n    dialRef.current?.setAttribute(\"transform\", `translate(${cx} ${cy})`)\n    hourRef.current?.setAttribute(\"transform\", `rotate(${hour})`)\n    minuteRef.current?.setAttribute(\"transform\", `rotate(${minute})`)\n    secondRef.current?.setAttribute(\"transform\", `rotate(${second})`)\n  }, [])\n\n  React.useEffect(() => {\n    if (live) return\n    const update = () => draw(hourAngle.get(), minuteAngle.get(), secondAngle.get())\n    update()\n    const unsubscribe = [\n      hourAngle.on(\"change\", update),\n      minuteAngle.on(\"change\", update),\n      secondAngle.on(\"change\", update),\n    ]\n    return () => unsubscribe.forEach((fn) => fn())\n  }, [live, ready, hourAngle, minuteAngle, secondAngle, draw])\n\n  // Live: the time-zone wall clock is read once a second, and every frame in\n  // between adds the device clock's elapsed milliseconds for a smooth sweep.\n  const baseSeconds = live && parts ? parts.hours * 3600 + parts.minutes * 60 + parts.seconds : null\n  const baseAt = live && now ? Math.floor(now.getTime() / 1000) * 1000 : null\n\n  React.useEffect(() => {\n    if (baseSeconds === null || baseAt === null) return\n    let frame = 0\n    const tick = () => {\n      const elapsed = (Date.now() - baseAt) / 1000\n      // Reduced motion: step once a second instead of sweeping.\n      const t = baseSeconds + (reduceMotion ? Math.floor(elapsed) : elapsed)\n      draw((t / 120) % 360, (t / 10) % 360, (t * 6) % 360)\n      frame = requestAnimationFrame(tick)\n    }\n    tick()\n    return () => cancelAnimationFrame(frame)\n  }, [baseSeconds, baseAt, reduceMotion, draw])\n\n  const hours = parts?.hours ?? 0\n  const minutes = parts?.minutes ?? 0\n\n  const ticks = React.useMemo(\n    () =>\n      Array.from({ length: 60 }, (_, i) => {\n        const deg = i * 6\n        const rad = (deg * Math.PI) / 180\n        const sin = Math.sin(rad)\n        const cos = -Math.cos(rad)\n        return { i, deg, sin, cos, isHour: i % 5 === 0 }\n      }),\n    []\n  )\n\n  const dial = (\n    <>\n      {/* Hairline spokes converging on the pivot */}\n      {Array.from({ length: 12 }, (_, i) => (\n        <line\n          key={i}\n          x1={0}\n          y1={0}\n          x2={0}\n          y2={-TICK_OUTER + 4}\n          transform={`rotate(${i * 30})`}\n          stroke={`url(#${id}-spoke)`}\n          strokeWidth={0.18}\n        />\n      ))}\n\n      {ticks.map(({ i, sin, cos, isHour }) => {\n        if (!isHour) {\n          return (\n            <line\n              key={i}\n              x1={sin * MINOR_INNER}\n              y1={cos * MINOR_INNER}\n              x2={sin * TICK_OUTER}\n              y2={cos * TICK_OUTER}\n              stroke=\"var(--lc-tick)\"\n              strokeWidth={0.85}\n              strokeLinecap=\"round\"\n            />\n          )\n        }\n        return (\n          <g key={i} filter={`url(#${id}-lift)`}>\n            <line\n              x1={sin * HOUR_INNER}\n              y1={cos * HOUR_INNER}\n              x2={sin * TICK_OUTER}\n              y2={cos * TICK_OUTER}\n              stroke=\"var(--lc-sleeve)\"\n              strokeWidth={2.5}\n              strokeLinecap=\"round\"\n            />\n            <line\n              x1={sin * (HOUR_INNER + 0.7)}\n              y1={cos * (HOUR_INNER + 0.7)}\n              x2={sin * (TICK_OUTER - 0.7)}\n              y2={cos * (TICK_OUTER - 0.7)}\n              stroke=\"var(--lc-lume)\"\n              strokeWidth={1.05}\n              strokeLinecap=\"round\"\n            />\n          </g>\n        )\n      })}\n\n      {Array.from({ length: 12 }, (_, i) => {\n        const hour = i === 0 ? 12 : i\n        const deg = i * 30\n        const rad = (deg * Math.PI) / 180\n        // Tangential labels that stay readable: tops point out on the upper\n        // half of the dial and in on the lower half.\n        const rotation = deg > 90 && deg < 270 ? deg - 180 : deg\n        return (\n          <text\n            key={hour}\n            x={Math.sin(rad) * LABEL_RADIUS}\n            y={-Math.cos(rad) * LABEL_RADIUS}\n            transform={`rotate(${rotation} ${Math.sin(rad) * LABEL_RADIUS} ${-Math.cos(rad) * LABEL_RADIUS})`}\n            textAnchor=\"middle\"\n            dominantBaseline=\"central\"\n            fontSize={5.4}\n            fill=\"var(--lc-label)\"\n            className=\"font-mono\"\n          >\n            {hour}\n          </text>\n        )\n      })}\n\n      {/* Hour hand */}\n      <g ref={hourRef}>\n        <g filter={`url(#${id}-hand-shadow)`}>\n          <path\n            d={`M -1.35 2 L -0.95 ${-HAND_LENGTH + 2.4} Q 0 ${-HAND_LENGTH - 0.6} 0.95 ${-HAND_LENGTH + 2.4} L 1.35 2 Z`}\n            fill=\"var(--lc-hand)\"\n            stroke=\"var(--lc-hand-edge)\"\n            strokeWidth={0.2}\n          />\n        </g>\n        <line\n          x1={0}\n          y1={-12}\n          x2={0}\n          y2={-HAND_LENGTH + 6}\n          stroke=\"var(--lc-lume)\"\n          strokeWidth={1}\n          strokeLinecap=\"round\"\n        />\n      </g>\n\n      {/* Minute hand */}\n      <g ref={minuteRef}>\n        <g filter={`url(#${id}-hand-shadow)`}>\n          <path\n            d={`M -0.95 2 L -0.6 ${-MINUTE_LENGTH + 1.6} Q 0 ${-MINUTE_LENGTH - 0.4} 0.6 ${-MINUTE_LENGTH + 1.6} L 0.95 2 Z`}\n            fill=\"var(--lc-hand)\"\n            stroke=\"var(--lc-hand-edge)\"\n            strokeWidth={0.2}\n          />\n        </g>\n        <line\n          x1={0}\n          y1={-16}\n          x2={0}\n          y2={-MINUTE_LENGTH + 4}\n          stroke=\"var(--lc-lume)\"\n          strokeWidth={0.6}\n          strokeLinecap=\"round\"\n        />\n      </g>\n\n      <circle\n        r={3.9}\n        fill={`url(#${id}-pivot)`}\n        filter={`url(#${id}-hand-shadow)`}\n      />\n\n      {/* Seconds hand, with a short counterweight tail */}\n      <g ref={secondRef}>\n        <line\n          x1={0}\n          y1={9}\n          x2={0}\n          y2={-SECOND_LENGTH}\n          stroke=\"var(--lc-accent)\"\n          strokeWidth={0.4}\n          strokeLinecap=\"round\"\n        />\n        <circle r={1.3} fill=\"var(--lc-accent)\" />\n      </g>\n    </>\n  )\n\n  const timeLabel = now\n    ? `${pad(hours)}:${pad(minutes)}`\n    : undefined\n\n  return (\n    <div\n      role=\"img\"\n      aria-label={timeLabel ? `Clock showing ${timeLabel}` : \"Clock\"}\n      className={cn(\n        LIGHT_VARS,\n        theme === \"dark\" ? DARK_VARS : theme === \"light\" ? \"\" : AUTO_DARK_VARS,\n        \"@container relative isolate aspect-square w-72 select-none overflow-hidden rounded-[22%]\",\n        \"bg-[linear-gradient(160deg,var(--lc-card-from),var(--lc-card-to))]\",\n        \"shadow-[inset_0_1px_0_var(--lc-edge),inset_0_0_0_1px_var(--lc-edge)]\",\n        className\n      )}\n    >\n      <svg\n        viewBox=\"0 0 100 100\"\n        className=\"absolute inset-0 size-full\"\n        aria-hidden=\"true\"\n      >\n        <defs>\n          <radialGradient id={`${id}-spoke`} gradientUnits=\"userSpaceOnUse\" cx={0} cy={0} r={TICK_OUTER}>\n            <stop offset=\"0\" stopColor=\"var(--lc-spoke)\" stopOpacity={0.16} />\n            <stop offset=\"0.75\" stopColor=\"var(--lc-spoke)\" stopOpacity={0.03} />\n            <stop offset=\"1\" stopColor=\"var(--lc-spoke)\" stopOpacity={0} />\n          </radialGradient>\n          <radialGradient id={`${id}-pivot`} cx=\"0.35\" cy=\"0.3\" r=\"0.8\">\n            <stop offset=\"0\" stopColor=\"var(--lc-pivot-from)\" />\n            <stop offset=\"1\" stopColor=\"var(--lc-pivot-to)\" />\n          </radialGradient>\n          <filter id={`${id}-hand-shadow`} x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\">\n            <feDropShadow dx={0.5} dy={1.1} stdDeviation={0.9} floodColor=\"#000\" floodOpacity={0.18} />\n          </filter>\n          {/* User-space region: straight 12/3/6/9 ticks have a zero-area bounding box */}\n          <filter id={`${id}-lift`} filterUnits=\"userSpaceOnUse\" x={-70} y={-70} width={140} height={140}>\n            <feDropShadow dx={0.2} dy={0.45} stdDeviation={0.35} floodColor=\"#000\" floodOpacity={0.14} />\n          </filter>\n          <filter id={`${id}-defocus`} x=\"-10%\" y=\"-10%\" width=\"120%\" height=\"120%\">\n            <feGaussianBlur stdDeviation={1.3} />\n          </filter>\n          {/* Shallow depth of field: sharp in the middle, soft toward the corners */}\n          <radialGradient id={`${id}-focus`} gradientUnits=\"userSpaceOnUse\" cx={50} cy={50} r={71}>\n            <stop offset=\"0.55\" stopColor=\"#fff\" />\n            <stop offset=\"0.82\" stopColor=\"#000\" />\n          </radialGradient>\n          <radialGradient id={`${id}-blur`} gradientUnits=\"userSpaceOnUse\" cx={50} cy={50} r={71}>\n            <stop offset=\"0.55\" stopColor=\"#000\" />\n            <stop offset=\"0.82\" stopColor=\"#fff\" stopOpacity={0.75} />\n          </radialGradient>\n          <mask id={`${id}-focus-mask`} maskUnits=\"userSpaceOnUse\" x={0} y={0} width={100} height={100}>\n            <rect width={100} height={100} fill={`url(#${id}-focus)`} />\n          </mask>\n          <mask id={`${id}-blur-mask`} maskUnits=\"userSpaceOnUse\" x={0} y={0} width={100} height={100}>\n            <rect width={100} height={100} fill={`url(#${id}-blur)`} />\n          </mask>\n        </defs>\n\n        {now && (\n          <>\n            <g mask={`url(#${id}-focus-mask)`}>\n              <g id={`${id}-dial`} ref={dialRef}>\n                {dial}\n              </g>\n            </g>\n            <g mask={`url(#${id}-blur-mask)`} filter={`url(#${id}-defocus)`}>\n              <use href={`#${id}-dial`} />\n            </g>\n          </>\n        )}\n      </svg>\n\n    </div>\n  )\n}\n"
    }
  ]
}
