{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-tempo",
  "title": "useTempo & Motion Resolver",
  "description": "Tempo context and the pure motion resolver (resolveMotion, useTempo, useResolvedMotion, useReducedMotion): one viewer preference (auto / 12 on twos / 60 smooth / 0 still) for every FRAME/12 animation, with OS reduced motion always winning.",
  "files": [
    {
      "path": "hooks/use-tempo.ts",
      "content": "'use client';\n\nimport * as React from 'react';\n\n// Same union as `MotionMode` in use-on-twos.ts. Declared locally (not imported) so this module has\n// no dependencies: use-on-twos.ts imports from here, never the other way round.\ntype MotionMode = 'stepped' | 'smooth' | 'cut';\n\n/**\n * The viewer's animation tempo.\n *\n * | Tempo   | Label | Meaning                                                              |\n * |---------|-------|----------------------------------------------------------------------|\n * | `auto`  | AUTO  | each component's own intended motion (default)                        |\n * | `twos`  | 12    | on twos: held poses, 12 per second                                    |\n * | `ones`  | 60    | smooth, continuous motion (follows the display's refresh rate)        |\n * | `still` | 0     | no non-essential motion                                               |\n */\nexport type Tempo = 'auto' | 'twos' | 'ones' | 'still';\n\nexport const TEMPOS: readonly Tempo[] = ['auto', 'twos', 'ones', 'still'];\n\nexport function isTempo(value: unknown): value is Tempo {\n  return value === 'auto' || value === 'twos' || value === 'ones' || value === 'still';\n}\n\n/** Any invalid runtime value (a stored `\"fast\"`, `null`, a number) is treated as `auto`. */\nexport function normalizeTempo(value: unknown): Tempo {\n  return isTempo(value) ? value : 'auto';\n}\n\nexport interface ResolveMotionInput {\n  /** The component's `motion` prop, otherwise its own default (`Metric` quiet → `'cut'`). Undefined means `'stepped'`. */\n  base?: MotionMode;\n  /** The nearest provider's tempo. Invalid values are treated as `auto`. */\n  tempo?: Tempo;\n  /** True when any provider above the nearest one is `still`. */\n  ancestorStill?: boolean;\n  /** The OS `prefers-reduced-motion: reduce` preference. */\n  reduced?: boolean;\n}\n\n/**\n * Resolves the motion a component actually plays. Rules, in order:\n *   1. OS reduced motion → `'cut'`.\n *   2. `still` here or in any ancestor → `'cut'`.\n *   3. `base === 'cut'` → `'cut'`: tempo never turns motion on where a component, a variant or an\n *      explicit prop says none.\n *   4. `twos` → `'stepped'`; `ones` → `'smooth'`.\n *   5. `auto` → `base`.\n * Direct manipulation (pointer tracking, drag, scroll, typing, focus movement) never goes through\n * this: it is never stepped or delayed, at any tempo.\n */\nexport function resolveMotion({\n  base,\n  tempo,\n  ancestorStill = false,\n  reduced = false,\n}: ResolveMotionInput): MotionMode {\n  if (reduced) return 'cut';\n  const t = normalizeTempo(tempo);\n  if (t === 'still' || ancestorStill) return 'cut';\n  const b: MotionMode = base === undefined ? 'stepped' : base;\n  if (b === 'cut') return 'cut';\n  if (t === 'twos') return 'stepped';\n  if (t === 'ones') return 'smooth';\n  return b;\n}\n\nexport interface TempoContextValue {\n  /** The nearest provider's tempo (`auto` without a provider). */\n  tempo: Tempo;\n  /** Sets the nearest provider's tempo. A no-op without a provider. */\n  setTempo: (tempo: Tempo) => void;\n  /** True when a provider above the nearest one is `still` (it cannot be undone below). */\n  ancestorStill: boolean;\n  /** False when no TempoProvider is above this point. */\n  inProvider: boolean;\n}\n\nconst NO_PROVIDER: TempoContextValue = {\n  tempo: 'auto',\n  setTempo: () => {},\n  ancestorStill: false,\n  inProvider: false,\n};\n\nexport const TempoContext = React.createContext<TempoContextValue>(NO_PROVIDER);\nTempoContext.displayName = 'TempoContext';\n\nconst REDUCED_MOTION_QUERY = '(prefers-reduced-motion: reduce)';\n\nfunction subscribeReducedMotion(onStoreChange: () => void): () => void {\n  if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n    return () => {};\n  }\n  const q = window.matchMedia(REDUCED_MOTION_QUERY);\n  if (q.addEventListener) {\n    q.addEventListener('change', onStoreChange);\n    return () => q.removeEventListener('change', onStoreChange);\n  }\n  q.addListener(onStoreChange);\n  return () => q.removeListener(onStoreChange);\n}\n\nfunction getReducedMotionSnapshot(): boolean {\n  if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n    return false;\n  }\n  return window.matchMedia(REDUCED_MOTION_QUERY).matches;\n}\n\nfunction getReducedMotionServerSnapshot(): boolean {\n  return false;\n}\n\n/** The OS `prefers-reduced-motion: reduce` preference. Without `matchMedia`, false. */\nexport function useReducedMotion(): boolean {\n  return React.useSyncExternalStore(\n    subscribeReducedMotion,\n    getReducedMotionSnapshot,\n    getReducedMotionServerSnapshot,\n  );\n}\n\nexport interface TempoState extends TempoContextValue {\n  /** The OS reduced-motion preference. */\n  reduced: boolean;\n  /** What actually applies here: `still` when reduced motion or an ancestor forces it. */\n  effectiveTempo: Tempo;\n}\n\n/** The tempo at this point in the tree, plus what actually applies after the OS and ancestors. */\nexport function useTempo(): TempoState {\n  const ctx = React.useContext(TempoContext);\n  const reduced = useReducedMotion();\n  return React.useMemo(\n    () => ({ ...ctx, reduced, effectiveTempo: reduced || ctx.ancestorStill ? 'still' : ctx.tempo }),\n    [ctx, reduced],\n  );\n}\n\n/** The motion a component with this `base` should play here (see `resolveMotion`). */\nexport function useResolvedMotion(base?: MotionMode): MotionMode {\n  const { tempo, ancestorStill } = React.useContext(TempoContext);\n  const reduced = useReducedMotion();\n  return resolveMotion({ base, tempo, ancestorStill, reduced });\n}\n",
      "type": "registry:hook",
      "target": "hooks/use-tempo.ts"
    }
  ],
  "type": "registry:hook"
}