{
  "name": "clock",
  "title": "Clock",
  "type": "registry:component",
  "description": "Procedural analog wall clock; hands track the system clock or a given IANA timezone.",
  "dependencies": [
    "@react-three/fiber@^9.6.1",
    "@runek/core@^0.12.0",
    "three@^0.184.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "Clock.tsx",
      "content": "import { useFrame } from '@react-three/fiber'\nimport { useWorld, type Vec3 } from '@runek/core'\nimport { useMemo, useRef } from 'react'\nimport type { Group } from 'three'\n\nexport interface ClockProps {\n  position?: Vec3\n  rotation?: Vec3\n  /** Face radius in units. */\n  radius?: number\n  /**\n   * IANA timezone, e.g. \"Asia/Kolkata\". Omit to inherit the world's `timezone`\n   * (`<World timezone>`), then the local system time; if neither resolves, the\n   * clock falls back to UTC.\n   */\n  timezone?: string\n  /** Rim/frame color. Defaults to the world palette's `metal` slot. */\n  frameColor?: string\n  /** Dial color. */\n  faceColor?: string\n  /** Hour/minute hand and tick color. */\n  handColor?: string\n  /** Second hand + hub accent. Defaults to the world palette's `accent` slot. */\n  accentColor?: string\n}\n\n/**\n * Read hours/minutes/seconds for a timezone (or the local clock), with a UTC\n * fallback. The sub-second fraction is timezone-independent, so we add it back\n * for a smooth second-hand sweep.\n */\nfunction readTime(fmt: Intl.DateTimeFormat | null): { h: number; m: number; s: number } {\n  const now = new Date()\n  const frac = (now.getTime() % 1000) / 1000\n  try {\n    if (fmt) {\n      const part: Record<string, number> = {}\n      for (const p of fmt.formatToParts(now)) part[p.type] = Number(p.value)\n      const h = part.hour % 24\n      if (![h, part.minute, part.second].every(Number.isFinite)) throw new Error('bad zone')\n      return { h, m: part.minute, s: part.second + frac }\n    }\n    const h = now.getHours()\n    const m = now.getMinutes()\n    const s = now.getSeconds()\n    if (![h, m, s].every(Number.isFinite)) throw new Error('no local time')\n    return { h, m, s: s + frac }\n  } catch {\n    return { h: now.getUTCHours(), m: now.getUTCMinutes(), s: now.getUTCSeconds() + frac }\n  }\n}\n\n/**\n * A procedural analog clock — face, rim, ticks, and three hands, all from code\n * (no fonts or textures). The hands track the system clock, or a given `timezone`.\n * Decorative: it registers no colliders (mount it against a wall that has one).\n */\nexport function Clock({\n  position = [0, 0, 0],\n  rotation = [0, 0, 0],\n  radius = 0.55,\n  timezone,\n  frameColor,\n  faceColor = '#0d1117',\n  handColor = '#e8eef5',\n  accentColor,\n}: ClockProps) {\n  const { unit, palette, time } = useWorld()\n  const rim = frameColor ?? palette.metal\n  const accent = accentColor ?? palette.accent\n  const R = radius * unit\n  const zone = timezone ?? time.timezone\n\n  const fmt = useMemo(\n    () =>\n      zone\n        ? new Intl.DateTimeFormat('en-GB', {\n            timeZone: zone,\n            hour: '2-digit',\n            minute: '2-digit',\n            second: '2-digit',\n            hour12: false,\n          })\n        : null,\n    [zone],\n  )\n\n  const ticks = useMemo(() => {\n    const inset = R - 0.06 * unit\n    return Array.from({ length: 12 }, (_, i) => {\n      const a = (i / 12) * Math.PI * 2\n      return { a, big: i % 3 === 0, x: Math.sin(a) * inset, y: Math.cos(a) * inset }\n    })\n  }, [R, unit])\n\n  const hour = useRef<Group>(null)\n  const minute = useRef<Group>(null)\n  const second = useRef<Group>(null)\n\n  useFrame(() => {\n    const { h, m, s } = readTime(fmt)\n    const TAU = Math.PI * 2\n    // Clockwise, so negate; 12 o'clock is up (rotation 0).\n    if (second.current) second.current.rotation.z = -(s / 60) * TAU\n    if (minute.current) minute.current.rotation.z = -((m + s / 60) / 60) * TAU\n    if (hour.current) hour.current.rotation.z = -(((h % 12) + m / 60) / 12) * TAU\n  })\n\n  return (\n    <group position={position} rotation={rotation}>\n      {/* rim + dial, circular faces turned to point +z */}\n      <mesh rotation={[Math.PI / 2, 0, 0]} castShadow receiveShadow>\n        <cylinderGeometry args={[R + 0.05 * unit, R + 0.05 * unit, 0.06 * unit, 48]} />\n        <meshStandardMaterial color={rim} metalness={0.4} roughness={0.6} />\n      </mesh>\n      <mesh position={[0, 0, 0.031 * unit]} rotation={[Math.PI / 2, 0, 0]}>\n        <cylinderGeometry args={[R, R, 0.02 * unit, 48]} />\n        <meshStandardMaterial color={faceColor} roughness={0.5} />\n      </mesh>\n\n      {ticks.map(({ a, big, x, y }) => (\n        <mesh key={a} position={[x, y, 0.045 * unit]} rotation={[0, 0, -a]}>\n          <boxGeometry\n            args={[(big ? 0.03 : 0.015) * unit, (big ? 0.1 : 0.06) * unit, 0.01 * unit]}\n          />\n          <meshStandardMaterial color={handColor} emissive={handColor} emissiveIntensity={0.2} />\n        </mesh>\n      ))}\n\n      {/* hands: anchored at center, extending up; group rotation sweeps them */}\n      <group ref={hour}>\n        <mesh position={[0, R * 0.28, 0.05 * unit]}>\n          <boxGeometry args={[0.035 * unit, R * 0.55, 0.012 * unit]} />\n          <meshStandardMaterial color={handColor} emissive={handColor} emissiveIntensity={0.12} />\n        </mesh>\n      </group>\n      <group ref={minute}>\n        <mesh position={[0, R * 0.38, 0.055 * unit]}>\n          <boxGeometry args={[0.022 * unit, R * 0.78, 0.012 * unit]} />\n          <meshStandardMaterial color={handColor} emissive={handColor} emissiveIntensity={0.12} />\n        </mesh>\n      </group>\n      <group ref={second}>\n        <mesh position={[0, R * 0.42, 0.06 * unit]}>\n          <boxGeometry args={[0.008 * unit, R * 0.86, 0.01 * unit]} />\n          <meshStandardMaterial\n            color={accent}\n            emissive={accent}\n            emissiveIntensity={0.6}\n            toneMapped={false}\n          />\n        </mesh>\n      </group>\n\n      {/* center hub */}\n      <mesh position={[0, 0, 0.066 * unit]} rotation={[Math.PI / 2, 0, 0]}>\n        <cylinderGeometry args={[0.03 * unit, 0.03 * unit, 0.03 * unit, 16]} />\n        <meshStandardMaterial\n          color={accent}\n          emissive={accent}\n          emissiveIntensity={0.5}\n          toneMapped={false}\n        />\n      </mesh>\n    </group>\n  )\n}\n",
      "type": "registry:component"
    }
  ]
}
