{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hand-toggle",
  "title": "Theme Toggle",
  "description": "A dithered 'hand of god' dark-mode toggle with a circular view-transition reveal.",
  "dependencies": [
    "next-themes",
    "lucide-react"
  ],
  "files": [
    {
      "path": "components/library/hand-toggle/index.ts",
      "content": "export { HandThemeToggle, type HandThemeToggleProps } from \"./hand-toggle\";\nexport type { ThemeController } from \"./theme\";\n",
      "type": "registry:component"
    },
    {
      "path": "components/library/hand-toggle/hand-toggle.tsx",
      "content": "\"use client\";\n\nimport type * as React from \"react\";\nimport {\n  useCallback,\n  useEffect,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n  useSyncExternalStore,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { renderHandBitmap, type BitmapSource } from \"./dither\";\nimport { ensureHandStyles } from \"./styles\";\nimport { useTheme } from \"next-themes\";\nimport { Moon, Sun } from \"lucide-react\";\nimport type { ThemeController } from \"./theme\";\n\nexport interface HandThemeToggleProps {\n  leftImage?: string;\n  rightImage?: string;\n  columns?: number;\n  cellSize?: number;\n  lightColor?: string;\n  darkColor?: string;\n  travelDistance?: number;\n  gap?: number;\n  className?: string;\n  triggerClassName?: string;\n  children?: React.ReactNode;\n  onToggle?: (isDark: boolean) => void;\n  isDark?: boolean;\n}\n\nconst SLIDE_IN_SECONDS = 0.35;\nconst HOLD_SECONDS = 1.25;\nconst SLIDE_OUT_SECONDS = 0.4;\nconst TOTAL_SECONDS = SLIDE_IN_SECONDS + HOLD_SECONDS + SLIDE_OUT_SECONDS;\nconst KEYFRAME_TIMES = [\n  0,\n  SLIDE_IN_SECONDS / TOTAL_SECONDS,\n  (SLIDE_IN_SECONDS + HOLD_SECONDS) / TOTAL_SECONDS,\n  1,\n] as const;\nconst EASE_ENTRY = \"cubic-bezier(0.65, 0, 0.35, 1)\";\nconst EASE_EXIT = \"cubic-bezier(0.55, 0, 0.85, 0.35)\";\nconst MIN_SCALE = 0.16;\n\nfunction runHandTimeline(el: HTMLCanvasElement, fromX: number): Animation {\n  return el.animate(\n    [\n      { transform: `translateX(${fromX}px)`, easing: EASE_ENTRY },\n      { transform: \"translateX(0px)\", offset: KEYFRAME_TIMES[1], easing: \"linear\" },\n      { transform: \"translateX(0px)\", offset: KEYFRAME_TIMES[2], easing: EASE_EXIT },\n      { transform: `translateX(${fromX}px)` },\n    ],\n    { duration: TOTAL_SECONDS * 1000, fill: \"both\" },\n  );\n}\n\nfunction usePrefersReducedMotion(): boolean {\n  const [reduced, setReduced] = useState(\n    () =>\n      typeof window !== \"undefined\" &&\n      window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches,\n  );\n  useEffect(() => {\n    const query = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    const onChange = () => setReduced(query.matches);\n    query.addEventListener(\"change\", onChange);\n    return () => query.removeEventListener(\"change\", onChange);\n  }, []);\n  return reduced;\n}\n\nfunction computeScale(columns = 100, cellSize = 10, gap = 10, innerWidth?: number): number {\n  if (typeof window === \"undefined\" && innerWidth == null) return 1;\n  const w = innerWidth ?? window.innerWidth;\n  const unscaledPairWidth = columns * cellSize * 2 + gap;\n  return Math.min(1, Math.max(MIN_SCALE, w / unscaledPairWidth));\n}\n\nfunction computePairWidth(columns: number, cellSize: number, gap: number, scale: number): number {\n  return (columns * cellSize * 2 + gap) * scale;\n}\n\nfunction computeTravel(\n  travelDistance: number | undefined,\n  pairWidth: number,\n  innerWidth?: number,\n): number {\n  if (travelDistance != null) return travelDistance;\n  if (typeof window === \"undefined\" && innerWidth == null) return 300;\n  const w = innerWidth ?? window.innerWidth;\n  return w / 2 + pairWidth;\n}\n\nfunction createFallbackHand(mirror: boolean): BitmapSource {\n  const width = 200;\n  const height = 280;\n  const canvas = document.createElement(\"canvas\");\n  canvas.width = width;\n  canvas.height = height;\n  const ctx = canvas.getContext(\"2d\")!;\n  if (mirror) {\n    ctx.translate(width, 0);\n    ctx.scale(-1, 1);\n  }\n\n  ctx.fillStyle = \"#000000\";\n  ctx.strokeStyle = \"#000000\";\n\n  ctx.beginPath();\n  ctx.roundRect(58, 150, 88, 112, 32);\n  ctx.fill();\n\n  ctx.lineCap = \"round\";\n  const fingers = [\n    { x: 74, tip: 88 },\n    { x: 97, tip: 66 },\n    { x: 120, tip: 76 },\n    { x: 141, tip: 100 },\n  ];\n  for (const finger of fingers) {\n    ctx.lineWidth = 21;\n    ctx.beginPath();\n    ctx.moveTo(finger.x, 168);\n    ctx.lineTo(finger.x, finger.tip);\n    ctx.stroke();\n  }\n\n  ctx.lineWidth = 24;\n  ctx.beginPath();\n  ctx.moveTo(72, 196);\n  ctx.lineTo(34, 164);\n  ctx.stroke();\n\n  return { source: canvas, width, height };\n}\n\ninterface HandAsset {\n  variants: { dark: HTMLCanvasElement; light: HTMLCanvasElement };\n}\n\nfunction blitHand(\n  canvas: HTMLCanvasElement,\n  ctx: CanvasRenderingContext2D,\n  asset: HandAsset,\n  isDark: boolean,\n) {\n  const variant = isDark ? asset.variants.dark : asset.variants.light;\n  ctx.save();\n  ctx.setTransform(1, 0, 0, 1, 0, 0);\n  ctx.clearRect(0, 0, canvas.width, canvas.height);\n  ctx.drawImage(variant, 0, 0);\n  ctx.restore();\n}\n\nfunction parkHand(left: HTMLElement, right: HTMLElement, travel: number) {\n  left.style.transform = `translateX(${-travel}px)`;\n  right.style.transform = `translateX(${travel}px)`;\n}\n\nlet clientMounted = false;\nconst mountListeners = new Set<() => void>();\n\nfunction subscribeToMount(callback: () => void) {\n  mountListeners.add(callback);\n  if (!clientMounted) {\n    clientMounted = true;\n    queueMicrotask(() => {\n      for (const listener of mountListeners) listener();\n    });\n  }\n  return () => {\n    mountListeners.delete(callback);\n  };\n}\n\nfunction getClientMountSnapshot() {\n  return clientMounted;\n}\nfunction getServerMountSnapshot() {\n  return false;\n}\nfunction useHasMounted() {\n  return useSyncExternalStore(subscribeToMount, getClientMountSnapshot, getServerMountSnapshot);\n}\n\nexport function HandThemeToggle({\n  leftImage = \"/left.png\",\n  rightImage = \"/right.png\",\n  columns = 100,\n  cellSize = 10,\n  lightColor = \"#111111\",\n  darkColor = \"#eaeaea\",\n  travelDistance,\n  gap = 10,\n  className,\n  triggerClassName,\n  children,\n  onToggle,\n  isDark: isDarkProp,\n}: HandThemeToggleProps) {\n  const hasMounted = useHasMounted();\n  const { resolvedTheme, setTheme } = useTheme();\n  const internalController = useMemo<ThemeController>(\n    () => ({\n      isDark: resolvedTheme === \"dark\",\n      setDark: (next: boolean) => setTheme(next ? \"dark\" : \"light\"),\n    }),\n    [resolvedTheme, setTheme],\n  );\n  const prefersReducedMotion = usePrefersReducedMotion();\n\n  const isDark = isDarkProp ?? internalController.isDark;\n\n  const setNextTheme = useCallback(\n    (nextIsDark: boolean) => {\n      if (isDarkProp == null) internalController.setDark(nextIsDark);\n      onToggle?.(nextIsDark);\n    },\n    [isDarkProp, internalController, onToggle],\n  );\n\n  const leftRef = useRef<HTMLCanvasElement>(null);\n  const rightRef = useRef<HTMLCanvasElement>(null);\n  const leftCtxRef = useRef<CanvasRenderingContext2D | null>(null);\n  const rightCtxRef = useRef<CanvasRenderingContext2D | null>(null);\n  const leftSrcRef = useRef<BitmapSource | null>(null);\n  const rightSrcRef = useRef<BitmapSource | null>(null);\n  const leftAssetRef = useRef<HandAsset | null>(null);\n  const rightAssetRef = useRef<HandAsset | null>(null);\n\n  const [phase, setPhase] = useState<\"idle\" | \"running\">(\"idle\");\n  const phaseRef = useRef<\"idle\" | \"running\">(\"idle\");\n  const needsSyncRef = useRef(false);\n  const activeAnimationsRef = useRef<Animation[]>([]);\n  const rebuildGenerationRef = useRef(0);\n  const builtSignatureRef = useRef<{ left: string | null; right: string | null }>({\n    left: null,\n    right: null,\n  });\n  const swapTimerRef = useRef<number | null>(null);\n  const travelRef = useRef(300);\n\n  const buttonIsDark = hasMounted && isDark;\n  const isDarkRef = useRef(isDark);\n\n  useEffect(() => {\n    isDarkRef.current = isDark;\n  }, [isDark]);\n\n  const [innerWidth, setInnerWidth] = useState(() =>\n    typeof window !== \"undefined\" ? window.innerWidth : 0,\n  );\n  const [scale, setScale] = useState(() =>\n    computeScale(columns, cellSize, gap, typeof window !== \"undefined\" ? window.innerWidth : undefined),\n  );\n\n  const scaleRef = useRef(scale);\n  const innerWidthRef = useRef(innerWidth);\n\n  useEffect(() => {\n    scaleRef.current = scale;\n  }, [scale]);\n  useEffect(() => {\n    innerWidthRef.current = innerWidth;\n  }, [innerWidth]);\n\n  const stopActiveAnimations = useCallback(() => {\n    for (const a of activeAnimationsRef.current) a.cancel();\n    activeAnimationsRef.current = [];\n  }, []);\n\n  const invalidateRebuilds = useCallback(() => {\n    rebuildGenerationRef.current++;\n    builtSignatureRef.current = { left: null, right: null };\n  }, []);\n\n  const rebuild = useCallback(async () => {\n    const generation = ++rebuildGenerationRef.current;\n    const s = scaleRef.current;\n    const sides = [\n      { key: \"left\" as const, src: leftSrcRef.current, canvas: leftRef.current, ctxOut: leftCtxRef, assetOut: leftAssetRef },\n      { key: \"right\" as const, src: rightSrcRef.current, canvas: rightRef.current, ctxOut: rightCtxRef, assetOut: rightAssetRef },\n    ];\n    for (const side of sides) {\n      if (!side.src || !side.canvas) continue;\n      const dpr = Math.min(window.devicePixelRatio || 1, 2);\n      const w = Math.round(columns * cellSize * s);\n      const h = Math.round((side.src.height / side.src.width) * w);\n      const dw = Math.round(w * dpr);\n      const dh = Math.round(h * dpr);\n      const signature = `${dw}x${dh}|${darkColor}|${lightColor}`;\n      if (builtSignatureRef.current[side.key] === signature) continue;\n      if (side.canvas.width !== dw) side.canvas.width = dw;\n      if (side.canvas.height !== dh) side.canvas.height = dh;\n      side.canvas.style.width = `${w}px`;\n      side.canvas.style.height = `${h}px`;\n      const ctx = side.canvas.getContext(\"2d\")!;\n      side.ctxOut.current = ctx;\n      const dark = renderHandBitmap(side.src, darkColor, true, dw, dh);\n      if (generation !== rebuildGenerationRef.current) return;\n      const light = renderHandBitmap(side.src, lightColor, false, dw, dh);\n      if (generation !== rebuildGenerationRef.current) return;\n      side.assetOut.current = { variants: { dark, light } };\n      blitHand(side.canvas, ctx, side.assetOut.current, isDarkRef.current);\n      builtSignatureRef.current[side.key] = signature;\n      await new Promise<void>((resolve) => {\n        if (\"requestIdleCallback\" in window) {\n          requestIdleCallback(() => resolve(), { timeout: 200 });\n        } else {\n          requestAnimationFrame(() => resolve());\n        }\n      });\n      if (generation !== rebuildGenerationRef.current) return;\n    }\n  }, [columns, cellSize, darkColor, lightColor]);\n\n  const repark = useCallback(() => {\n    const left = leftRef.current;\n    const right = rightRef.current;\n    if (!left || !right) return;\n    const pw = computePairWidth(columns, cellSize, gap, scaleRef.current);\n    const travel = computeTravel(travelDistance, pw, innerWidthRef.current);\n    travelRef.current = travel;\n    parkHand(left, right, travel);\n  }, [columns, cellSize, gap, travelDistance]);\n\n  useEffect(() => {\n    if (!hasMounted) return;\n    let cancelled = false;\n\n    const loadImage = (src: string, mirror: boolean, onReady: (src: BitmapSource) => void) => {\n      if (!src) {\n        onReady(createFallbackHand(mirror));\n        return;\n      }\n      const img = new Image();\n      img.crossOrigin = \"anonymous\";\n      const handleLoad = () => {\n        if (cancelled) return;\n        onReady({ source: img, width: img.naturalWidth, height: img.naturalHeight });\n      };\n      const handleError = () => {\n        if (cancelled) return;\n        onReady(createFallbackHand(mirror));\n      };\n      img.onload = handleLoad;\n      img.onerror = handleError;\n      img.src = src;\n      if (img.complete && img.naturalWidth) handleLoad();\n      if (img.complete && !img.naturalWidth) handleError();\n    };\n\n    loadImage(leftImage, false, (src) => {\n      leftSrcRef.current = src;\n      void rebuild();\n    });\n    loadImage(rightImage, true, (src) => {\n      rightSrcRef.current = src;\n      void rebuild();\n    });\n\n    return () => {\n      cancelled = true;\n    };\n  }, [hasMounted, leftImage, rightImage, rebuild]);\n\n  useEffect(() => {\n    if (!hasMounted) return;\n    ensureHandStyles();\n    document.documentElement.style.setProperty(\"--hvt-duration\", `${HOLD_SECONDS}s`);\n    document.documentElement.style.setProperty(\"--hvt-easing\", \"var(--hvt-ease-expo-out)\");\n    return () => {\n      invalidateRebuilds();\n      stopActiveAnimations();\n      if (swapTimerRef.current != null) {\n        window.clearTimeout(swapTimerRef.current);\n        swapTimerRef.current = null;\n      }\n    };\n  }, [hasMounted, stopActiveAnimations, invalidateRebuilds]);\n\n  useLayoutEffect(() => {\n    if (!hasMounted) return;\n    repark();\n  }, [hasMounted, repark]);\n\n  useEffect(() => {\n    if (!hasMounted) return;\n    let frame: number | null = null;\n    const onResize = () => {\n      if (frame != null) return;\n      frame = requestAnimationFrame(() => {\n        setInnerWidth(window.innerWidth);\n        setScale(computeScale(columns, cellSize, gap, window.innerWidth));\n        frame = null;\n      });\n    };\n    onResize();\n    window.addEventListener(\"resize\", onResize);\n    window.visualViewport?.addEventListener(\"resize\", onResize);\n    return () => {\n      if (frame != null) cancelAnimationFrame(frame);\n      window.removeEventListener(\"resize\", onResize);\n      window.visualViewport?.removeEventListener(\"resize\", onResize);\n    };\n  }, [hasMounted, columns, cellSize, gap]);\n\n  useEffect(() => {\n    if (!hasMounted) return;\n    if (phaseRef.current === \"running\") {\n      needsSyncRef.current = true;\n      return;\n    }\n    void rebuild();\n    repark();\n  }, [hasMounted, scale, innerWidth, rebuild, repark]);\n\n  useEffect(() => {\n    if (!hasMounted) return;\n    if (phaseRef.current === \"running\") {\n      needsSyncRef.current = true;\n      return;\n    }\n    const left = leftRef.current;\n    const right = rightRef.current;\n    if (left && leftCtxRef.current && leftAssetRef.current) {\n      blitHand(left, leftCtxRef.current, leftAssetRef.current, isDark);\n    }\n    if (right && rightCtxRef.current && rightAssetRef.current) {\n      blitHand(right, rightCtxRef.current, rightAssetRef.current, isDark);\n    }\n  }, [hasMounted, isDark]);\n\n  const handleToggle = useCallback(async () => {\n    if (phaseRef.current !== \"idle\") return;\n    const left = leftRef.current;\n    const right = rightRef.current;\n    const leftAsset = leftAssetRef.current;\n    const rightAsset = rightAssetRef.current;\n    const leftCtx = leftCtxRef.current;\n    const rightCtx = rightCtxRef.current;\n    if (!left || !right || !leftAsset || !rightAsset || !leftCtx || !rightCtx) return;\n\n    const nextIsDark = !isDark;\n\n    const applySwap = () => {\n      setNextTheme(nextIsDark);\n      blitHand(left, leftCtx, leftAsset, nextIsDark);\n      blitHand(right, rightCtx, rightAsset, nextIsDark);\n    };\n\n    if (prefersReducedMotion) {\n      if (document.startViewTransition) {\n        document.startViewTransition(applySwap);\n      } else {\n        applySwap();\n      }\n      return;\n    }\n\n    phaseRef.current = \"running\";\n    setPhase(\"running\");\n\n    for (const a of activeAnimationsRef.current) a.cancel();\n    activeAnimationsRef.current = [];\n\n    const travel = travelRef.current;\n\n    swapTimerRef.current = window.setTimeout(() => {\n      swapTimerRef.current = null;\n      if (document.startViewTransition) {\n        const transition = document.startViewTransition(applySwap);\n        transition.finished.catch(() => {});\n      } else {\n        applySwap();\n      }\n    }, SLIDE_IN_SECONDS * 1000);\n\n    try {\n      const animations = [\n        runHandTimeline(left, -travel),\n        runHandTimeline(right, travel),\n      ];\n      activeAnimationsRef.current = animations;\n      await Promise.all(animations.map((a) => a.finished.catch(() => {})));\n    } finally {\n      for (const a of activeAnimationsRef.current) a.cancel();\n      activeAnimationsRef.current = [];\n      if (swapTimerRef.current != null) {\n        window.clearTimeout(swapTimerRef.current);\n        swapTimerRef.current = null;\n      }\n      if (needsSyncRef.current) {\n        needsSyncRef.current = false;\n        void rebuild();\n      }\n      repark();\n      phaseRef.current = \"idle\";\n      setPhase(\"idle\");\n    }\n  }, [isDark, prefersReducedMotion, setNextTheme, rebuild, repark]);\n\n  const overlay = hasMounted\n    ? createPortal(\n        <div\n          aria-hidden\n          data-running={phase === \"running\" ? \"true\" : undefined}\n          className={\"hvt-overlay\" + (className ? ` ${className}` : \"\")}\n          style={{ gap: gap * scale }}\n        >\n          <canvas ref={leftRef} className=\"hvt-canvas\" />\n          <canvas ref={rightRef} className=\"hvt-canvas\" />\n        </div>,\n        document.body,\n      )\n    : null;\n\n  return (\n    <>\n      <button\n        type=\"button\"\n        role=\"switch\"\n        aria-checked={buttonIsDark}\n        aria-busy={phase === \"running\"}\n        aria-label=\"Toggle dark mode\"\n        disabled={!hasMounted}\n        onPointerDown={(e) => {\n          if (e.button !== 0) return;\n          e.preventDefault();\n          void handleToggle();\n        }}\n        onKeyDown={(e) => {\n          if (e.key !== \"Enter\" && e.key !== \" \") return;\n          e.preventDefault();\n          void handleToggle();\n        }}\n        className={\"hvt-trigger\" + (triggerClassName ? ` ${triggerClassName}` : \"\")}\n      >\n        {children ?? (buttonIsDark ? <Sun className=\"h-5 w-5\" /> : <Moon className=\"h-5 w-5\" />)}\n      </button>\n      {overlay}\n    </>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "components/library/hand-toggle/dither.ts",
      "content": "export interface BitmapSource {\n  source: CanvasImageSource;\n  width: number;\n  height: number;\n}\n\nconst BAYER_8X8 = [\n  0, 32, 8, 40, 2, 34, 10, 42,\n  48, 16, 56, 24, 50, 18, 58, 26,\n  12, 44, 4, 36, 14, 46, 6, 38,\n  60, 28, 52, 20, 62, 30, 54, 22,\n  3, 35, 11, 43, 1, 33, 9, 41,\n  51, 19, 59, 27, 49, 17, 57, 25,\n  15, 47, 7, 39, 13, 45, 5, 37,\n  63, 31, 55, 23, 61, 29, 53, 21,\n];\n\nexport function renderHandBitmap(\n  src: BitmapSource,\n  colorHex: string,\n  isDark: boolean,\n  outWidth: number,\n  outHeight: number,\n): HTMLCanvasElement {\n  const canvas = document.createElement(\"canvas\");\n  canvas.width = outWidth;\n  canvas.height = outHeight;\n  const ctx = canvas.getContext(\"2d\", { willReadFrequently: true })!;\n  ctx.drawImage(src.source, 0, 0, outWidth, outHeight);\n  const imageData = ctx.getImageData(0, 0, outWidth, outHeight);\n  const pixels = imageData.data;\n  const count = outWidth * outHeight;\n  const luminance = new Float32Array(count);\n  const opaque = new Uint8Array(count);\n\n  for (let p = 0, i = 0; p < count; p++, i += 4) {\n    if (pixels[i + 3] < 128) continue;\n    opaque[p] = 1;\n    luminance[p] = 0.299 * pixels[i] + 0.587 * pixels[i + 1] + 0.114 * pixels[i + 2];\n  }\n\n  if (isDark) {\n    ditherOrdered(luminance, opaque, outWidth, outHeight);\n  } else {\n    ditherAtkinson(luminance, opaque, outWidth, outHeight);\n  }\n\n  const r = parseInt(colorHex.slice(1, 3), 16);\n  const g = parseInt(colorHex.slice(3, 5), 16);\n  const b = parseInt(colorHex.slice(5, 7), 16);\n\n  for (let p = 0, i = 0; p < count; p++, i += 4) {\n    if (!opaque[p]) {\n      pixels[i + 3] = 0;\n      continue;\n    }\n    if (luminance[p] > 127) {\n      pixels[i + 3] = 0;\n    } else {\n      pixels[i] = r;\n      pixels[i + 1] = g;\n      pixels[i + 2] = b;\n      pixels[i + 3] = 255;\n    }\n  }\n\n  ctx.putImageData(imageData, 0, 0);\n  return canvas;\n}\n\nfunction ditherOrdered(\n  lum: Float32Array,\n  opaque: Uint8Array,\n  w: number,\n  h: number,\n): void {\n  for (let y = 0; y < h; y++) {\n    const matrixRow = (y & 7) * 8;\n    for (let x = 0; x < w; x++) {\n      const p = y * w + x;\n      if (!opaque[p]) continue;\n      const threshold = ((BAYER_8X8[matrixRow + (x & 7)] + 0.5) / 64) * 255;\n      lum[p] = lum[p] > threshold ? 255 : 0;\n    }\n  }\n}\n\nfunction ditherAtkinson(\n  lum: Float32Array,\n  opaque: Uint8Array,\n  w: number,\n  h: number,\n): void {\n  for (let y = 0; y < h; y++) {\n    for (let x = 0; x < w; x++) {\n      const p = y * w + x;\n      if (!opaque[p]) continue;\n      const old = lum[p];\n      const next = old > 127 ? 255 : 0;\n      lum[p] = next;\n      const error = (old - next) / 8;\n      spread(lum, opaque, x + 1, y, w, h, error);\n      spread(lum, opaque, x + 2, y, w, h, error);\n      spread(lum, opaque, x - 1, y + 1, w, h, error);\n      spread(lum, opaque, x, y + 1, w, h, error);\n      spread(lum, opaque, x + 1, y + 1, w, h, error);\n      spread(lum, opaque, x, y + 2, w, h, error);\n    }\n  }\n}\n\nfunction spread(\n  lum: Float32Array,\n  opaque: Uint8Array,\n  x: number,\n  y: number,\n  w: number,\n  h: number,\n  error: number,\n): void {\n  if (x < 0 || x >= w || y >= h) return;\n  const p = y * w + x;\n  if (!opaque[p]) return;\n  lum[p] += error;\n}\n",
      "type": "registry:component"
    },
    {
      "path": "components/library/hand-toggle/styles.ts",
      "content": "const STYLE_ID = \"hand-toggle-styles\";\n\nconst CSS = `\n:root {\n  --hvt-ease-expo-out: linear(\n    0 0%, 0.1684 2.66%, 0.3165 5.49%,\n    0.446 8.52%, 0.5581 11.78%,\n    0.6535 15.29%, 0.7341 19.11%,\n    0.8011 23.3%, 0.8557 27.93%,\n    0.8962 32.68%, 0.9283 38.01%,\n    0.9529 44.08%, 0.9711 51.14%,\n    0.9833 59.06%, 0.9915 68.74%, 1 100%\n  );\n}\n\n@keyframes hvt-circle-reveal {\n  from { clip-path: circle(0% at 50% 50%); }\n  to { clip-path: circle(100% at 50% 50%); }\n}\n\n::view-transition-old(root),\n::view-transition-new(root) {\n  animation: none;\n  mix-blend-mode: normal;\n}\n\n::view-transition-new(root) {\n  animation: hvt-circle-reveal var(--hvt-duration, 1.25s) var(--hvt-easing, var(--hvt-ease-expo-out));\n  animation-fill-mode: both;\n}\n\n::view-transition-old(root),\n.dark::view-transition-old(root) {\n  z-index: -1;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  ::view-transition-group(root),\n  ::view-transition-old(root),\n  ::view-transition-new(root) {\n    animation: none !important;\n    transition: none !important;\n  }\n  .hvt-trigger {\n    transition: none !important;\n  }\n}\n\n.hvt-trigger {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem;\n  height: 2.5rem;\n  border-radius: 9999px;\n  border: none;\n  background: transparent;\n  color: inherit;\n  font-size: 1rem;\n  line-height: 1;\n  cursor: pointer;\n  outline: none;\n  transition: background-color 0.15s ease;\n}\n\n.hvt-trigger:hover {\n  background-color: color-mix(in srgb, currentColor 8%, transparent);\n}\n\n.hvt-trigger:focus-visible {\n  outline: 2px solid currentColor;\n  outline-offset: 2px;\n}\n\n.hvt-trigger[disabled] {\n  opacity: 0.6;\n  cursor: not-allowed;\n}\n\n.hvt-trigger[aria-busy=\"true\"] {\n  pointer-events: none;\n}\n\n.hvt-overlay {\n  position: fixed;\n  inset: 0;\n  z-index: 9999;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  overflow: hidden;\n  pointer-events: none;\n  visibility: hidden;\n}\n\n.hvt-overlay[data-running=\"true\"] {\n  visibility: visible;\n}\n\n.hvt-canvas {\n  will-change: transform;\n  image-rendering: pixelated;\n}\n`;\n\nexport function ensureHandStyles(): void {\n  if (typeof document === \"undefined\") return;\n  if (document.getElementById(STYLE_ID)) return;\n  const style = document.createElement(\"style\");\n  style.id = STYLE_ID;\n  style.textContent = CSS;\n  document.head.appendChild(style);\n}\n",
      "type": "registry:component"
    },
    {
      "path": "components/library/hand-toggle/theme.ts",
      "content": "export interface ThemeController {\n  isDark: boolean;\n  setDark(next: boolean): void;\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}