{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "motion-primitives",
  "type": "registry:component",
  "title": "Motion primitives",
  "description": "Animation building blocks: FacadeMotionProvider, FadeIn, Reveal, Stagger and Collapse. They animate only opacity and transform, and follow the reduced-motion setting.",
  "categories": [
    "motion"
  ],
  "dependencies": [
    "motion@^13.4.0"
  ],
  "registryDependencies": [
    "https://facadeui.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "src/lib/motion.ts",
      "type": "registry:lib",
      "target": "lib/motion.ts",
      "content": "/**\n * Motion constants and variants shared by the `motion/` primitives.\n *\n * These numbers mirror the `--facade-duration-*` / `--facade-ease-*` CSS tokens.\n * `motion` needs plain numbers and cubic-bezier arrays, which CSS custom\n * properties cannot provide at render time, so the values are duplicated here —\n * and `motion.test.ts` parses `tokens/globals.css` to prove they stay in sync.\n *\n * a11y: nothing here opts out of reduced motion. `FacadeMotionProvider` sets\n * `reducedMotion=\"user\"` once, which neutralises every variant below.\n *\n * Dependencies: motion (types only).\n */\n\nimport type { Transition, Variants } from \"motion/react\"\n\n/** Milliseconds, matching `--facade-duration-*`. */\nexport const FACADE_DURATION_MS = {\n  fast: 150,\n  base: 320,\n  slow: 620,\n} as const\n\n/** Seconds — the unit `motion` expects. */\nexport const facadeDuration = {\n  fast: FACADE_DURATION_MS.fast / 1000,\n  base: FACADE_DURATION_MS.base / 1000,\n  slow: FACADE_DURATION_MS.slow / 1000,\n} as const\n\n/**\n * Cubic-bezier control points, matching `--facade-ease-*`.\n *\n * Typed as mutable 4-tuples rather than `as const`: motion's `Easing[]` will not\n * accept a readonly tuple, and `as const` widens under spread to a union array.\n */\nexport type CubicBezier = [number, number, number, number]\n\nexport const facadeEase: Record<\"out\" | \"inOut\" | \"spring\", CubicBezier> = {\n  out: [0.16, 1, 0.3, 1],\n  inOut: [0.65, 0, 0.35, 1],\n  spring: [0.34, 1.4, 0.64, 1],\n}\n\n/** rem, matching `--facade-motion-distance`. Converted to px for transforms. */\nexport const FACADE_MOTION_DISTANCE_REM = 1\nexport const FACADE_MOTION_DISTANCE_PX = FACADE_MOTION_DISTANCE_REM * 16\n\nexport type FadeDirection = \"up\" | \"down\" | \"left\" | \"right\" | \"none\"\n\n/** Only `opacity` and `transform` are animated — never anything that reflows. */\nexport function offsetFor(\n  direction: FadeDirection,\n  distance = FACADE_MOTION_DISTANCE_PX,\n) {\n  switch (direction) {\n    case \"up\":\n      return { y: distance, x: 0 }\n    case \"down\":\n      return { y: -distance, x: 0 }\n    case \"left\":\n      return { x: distance, y: 0 }\n    case \"right\":\n      return { x: -distance, y: 0 }\n    case \"none\":\n      return { x: 0, y: 0 }\n  }\n}\n\nexport const facadeTransition = (\n  duration: number = facadeDuration.base,\n  delay = 0,\n): Transition => ({\n  duration,\n  delay,\n  ease: facadeEase.out,\n})\n\nexport function fadeVariants(\n  direction: FadeDirection = \"up\",\n  distance?: number,\n  duration?: number,\n): Variants {\n  const offset = offsetFor(direction, distance)\n  return {\n    hidden: { opacity: 0, ...offset },\n    visible: { opacity: 1, x: 0, y: 0, transition: facadeTransition(duration) },\n  }\n}\n\n/** Container variants for `Stagger`. Children inherit `hidden`/`visible`. */\nexport function staggerVariants(stagger = 0.08, delayChildren = 0): Variants {\n  return {\n    hidden: {},\n    visible: {\n      transition: { staggerChildren: stagger, delayChildren },\n    },\n  }\n}\n\n/** Default viewport config for `Reveal`: fire once, slightly before fully in view. */\nexport const FACADE_VIEWPORT = {\n  once: true,\n  amount: 0.25,\n  margin: \"0px 0px -10% 0px\",\n} as const\n"
    },
    {
      "path": "src/motion/facade-motion-provider.tsx",
      "type": "registry:component",
      "target": "components/motion/facade-motion-provider.tsx",
      "content": "\"use client\"\n\n/**\n * FacadeMotionProvider — one `MotionConfig` for the whole app.\n *\n * Mount it once, near the root. Everything in `motion/` assumes it is present\n * but degrades to motion's own defaults if it is not.\n *\n * a11y: `reducedMotion=\"user\"` makes every transform/opacity animation below a\n * no-op for visitors whose OS asks for reduced motion. Combined with the\n * `prefers-reduced-motion` block in `tokens/globals.css`, that covers both the\n * JS and the CSS side.\n *\n * Dependencies: motion, react.\n */\n\nimport { MotionConfig } from \"motion/react\"\nimport type { ReactNode } from \"react\"\n\nimport { facadeDuration, facadeEase } from \"@/lib/motion\"\n\nexport interface FacadeMotionProviderProps {\n  children: ReactNode\n  /**\n   * `\"user\"` (default) follows the OS setting. Use `\"always\"` for reduced-motion\n   * screenshots, and `\"never\"` only in tests.\n   */\n  reducedMotion?: \"user\" | \"always\" | \"never\"\n  /** Disables `nonce`-less inline style injection in strict CSP setups. */\n  nonce?: string\n}\n\nexport function FacadeMotionProvider({\n  children,\n  reducedMotion = \"user\",\n  nonce,\n}: FacadeMotionProviderProps) {\n  return (\n    <MotionConfig\n      reducedMotion={reducedMotion}\n      nonce={nonce}\n      transition={{ duration: facadeDuration.base, ease: facadeEase.out }}\n    >\n      {children}\n    </MotionConfig>\n  )\n}\n"
    },
    {
      "path": "src/motion/fade-in.tsx",
      "type": "registry:component",
      "target": "components/motion/fade-in.tsx",
      "content": "\"use client\"\n\n/**\n * FadeIn — the base entrance primitive: opacity plus a short translate.\n *\n * Plays on mount. For \"play when scrolled into view\" use `Reveal`; for lists use\n * `Stagger` + `StaggerItem`, which drive their children through variants instead.\n *\n * a11y: animates only `opacity` and `transform`, so it can never shift layout or\n * trigger CLS. Neutralised entirely under `FacadeMotionProvider`'s\n * `reducedMotion=\"user\"`.\n *\n * Dependencies: motion, react, @/lib/motion, @/lib/utils.\n */\n\nimport { motion } from \"motion/react\"\nimport type { ReactNode } from \"react\"\n\nimport { facadeDuration, fadeVariants, type FadeDirection } from \"@/lib/motion\"\nimport { cn } from \"@/lib/utils\"\n\n/** Tags `FadeIn`, `Reveal` and `Stagger` can render as. */\nexport type MotionTag =\n  | \"div\"\n  | \"span\"\n  | \"p\"\n  | \"section\"\n  | \"article\"\n  | \"header\"\n  | \"footer\"\n  | \"ul\"\n  | \"ol\"\n  | \"li\"\n  | \"dl\"\n  | \"figure\"\n\nexport interface FadeInProps {\n  children: ReactNode\n  /** The direction the element moves in *from*. `\"none\"` fades without moving. */\n  direction?: FadeDirection\n  /** Seconds. Defaults to `--facade-duration-base`. */\n  duration?: number\n  /** Seconds. */\n  delay?: number\n  /** Distance moved, in px. Defaults to `--facade-motion-distance` (16px). */\n  distance?: number\n  as?: MotionTag\n  className?: string\n}\n\nexport function FadeIn({\n  children,\n  direction = \"up\",\n  duration = facadeDuration.base,\n  delay = 0,\n  distance,\n  as = \"div\",\n  className,\n}: FadeInProps) {\n  const Component = motion[as]\n  const variants = fadeVariants(direction, distance, duration)\n\n  return (\n    <Component\n      className={cn(className)}\n      initial=\"hidden\"\n      animate=\"visible\"\n      variants={variants}\n      transition={{ delay }}\n    >\n      {children}\n    </Component>\n  )\n}\n"
    },
    {
      "path": "src/motion/reveal.tsx",
      "type": "registry:component",
      "target": "components/motion/reveal.tsx",
      "content": "\"use client\"\n\n/**\n * Reveal — entrance animation triggered when the element scrolls into view.\n *\n * Fires once and stays visible. The `once` default matters: re-animating on every\n * scroll pass is the single most common way marketing motion becomes annoying,\n * and it also defeats browser find-in-page.\n *\n * a11y: opacity/transform only. The element is in the DOM and fully readable by\n * assistive tech before the animation runs — nothing is gated behind the\n * intersection observer.\n *\n * Dependencies: motion, react, @/lib/motion, @/lib/utils.\n */\n\nimport { motion } from \"motion/react\"\nimport type { ReactNode } from \"react\"\n\nimport {\n  FACADE_VIEWPORT,\n  facadeDuration,\n  fadeVariants,\n  type FadeDirection,\n} from \"@/lib/motion\"\nimport { cn } from \"@/lib/utils\"\nimport type { MotionTag } from \"@/components/motion/fade-in\"\n\nexport interface RevealProps {\n  children: ReactNode\n  direction?: FadeDirection\n  duration?: number\n  delay?: number\n  distance?: number\n  as?: MotionTag\n  className?: string\n  /** Replays each time the element comes back into view. Default `false`. */\n  repeat?: boolean\n  /** How much of the element must be visible to start, from 0 to 1. Default `0.25`. */\n  amount?: number\n}\n\nexport function Reveal({\n  children,\n  direction = \"up\",\n  duration = facadeDuration.base,\n  delay = 0,\n  distance,\n  as = \"div\",\n  className,\n  repeat = false,\n  amount = FACADE_VIEWPORT.amount,\n}: RevealProps) {\n  const Component = motion[as]\n\n  return (\n    <Component\n      className={cn(className)}\n      initial=\"hidden\"\n      whileInView=\"visible\"\n      viewport={{ once: !repeat, amount, margin: FACADE_VIEWPORT.margin }}\n      variants={fadeVariants(direction, distance, duration)}\n      transition={{ delay }}\n    >\n      {children}\n    </Component>\n  )\n}\n"
    },
    {
      "path": "src/motion/stagger.tsx",
      "type": "registry:component",
      "target": "components/motion/stagger.tsx",
      "content": "\"use client\"\n\n/**\n * Stagger — a container that reveals its `StaggerItem` children in sequence.\n *\n * Split into two components on purpose: the container owns the timing, each item\n * owns its own offset, and neither needs to know how many siblings exist. Use\n * `StaggerItem` for every direct child you want animated; unwrapped children\n * simply render immediately.\n *\n *   <Stagger as=\"ul\">\n *     {items.map((item) => <StaggerItem key={item.id} as=\"li\">…</StaggerItem>)}\n *   </Stagger>\n *\n * a11y: the container renders whatever tag you pass, so `ul`/`li` semantics\n * survive the wrapper. Opacity/transform only.\n *\n * Dependencies: motion, react, @/lib/motion, @/lib/utils.\n */\n\nimport { motion } from \"motion/react\"\nimport type { ReactNode } from \"react\"\n\nimport {\n  FACADE_VIEWPORT,\n  facadeDuration,\n  fadeVariants,\n  staggerVariants,\n  type FadeDirection,\n} from \"@/lib/motion\"\nimport { cn } from \"@/lib/utils\"\nimport type { MotionTag } from \"@/components/motion/fade-in\"\n\nexport interface StaggerProps {\n  children: ReactNode\n  /** Seconds between each child. Default `0.08`. */\n  stagger?: number\n  /** Seconds before the first child starts. Default `0`. */\n  delayChildren?: number\n  as?: MotionTag\n  className?: string\n  /** `\"view\"` (default) waits until it is scrolled into view. `\"mount\"` plays at once. */\n  trigger?: \"view\" | \"mount\"\n  repeat?: boolean\n  amount?: number\n}\n\nexport function Stagger({\n  children,\n  stagger = 0.08,\n  delayChildren = 0,\n  as = \"div\",\n  className,\n  trigger = \"view\",\n  repeat = false,\n  amount = FACADE_VIEWPORT.amount,\n}: StaggerProps) {\n  const Component = motion[as]\n  const activation =\n    trigger === \"mount\"\n      ? ({ animate: \"visible\" } as const)\n      : ({\n          whileInView: \"visible\",\n          viewport: { once: !repeat, amount, margin: FACADE_VIEWPORT.margin },\n        } as const)\n\n  return (\n    <Component\n      className={cn(className)}\n      initial=\"hidden\"\n      variants={staggerVariants(stagger, delayChildren)}\n      {...activation}\n    >\n      {children}\n    </Component>\n  )\n}\n\nexport interface StaggerItemProps {\n  children: ReactNode\n  direction?: FadeDirection\n  duration?: number\n  distance?: number\n  as?: MotionTag\n  className?: string\n}\n\nexport function StaggerItem({\n  children,\n  direction = \"up\",\n  duration = facadeDuration.base,\n  distance,\n  as = \"div\",\n  className,\n}: StaggerItemProps) {\n  const Component = motion[as]\n\n  return (\n    <Component\n      className={cn(className)}\n      variants={fadeVariants(direction, distance, duration)}\n    >\n      {children}\n    </Component>\n  )\n}\n"
    },
    {
      "path": "src/motion/collapse.tsx",
      "type": "registry:component",
      "target": "components/motion/collapse.tsx",
      "content": "\"use client\"\n\n/**\n * Collapse — animated disclosure for FAQ panels and the mobile nav drawer.\n *\n * The one deliberate exception to the \"transform and opacity only\" rule: a\n * disclosure has to animate `height`, because the size change *is* the content of\n * the interaction. It is user-initiated and never runs on load, so it cannot\n * contribute to CLS. Everything else in `motion/` stays on the compositor.\n *\n * `Collapse` is presentation only — it renders no button and manages no state.\n * Pair it with Base UI's Accordion or Collapsible, which own the ARIA wiring.\n *\n * a11y: when closed the panel is unmounted (or `hidden`, with `keepMounted`), so\n * its contents stay out of the tab order and out of the accessibility tree.\n *\n * Dependencies: motion, react, @/lib/motion, @/lib/utils.\n */\n\nimport { AnimatePresence, motion } from \"motion/react\"\nimport type { ReactNode } from \"react\"\n\nimport { facadeDuration, facadeEase } from \"@/lib/motion\"\nimport { cn } from \"@/lib/utils\"\n\nexport interface CollapseProps {\n  open: boolean\n  children: ReactNode\n  /** Seconds. Defaults to `--facade-duration-fast`, which suits short panels. */\n  duration?: number\n  className?: string\n  /**\n   * Keeps the panel in the DOM and hides it with `hidden`. This lets browser find-in-page\n   * reach the content.\n   */\n  keepMounted?: boolean\n  /** Set on the panel element. Use the same value as the trigger's `aria-controls`. */\n  id?: string\n}\n\nconst transition = (duration: number) => ({ duration, ease: facadeEase.inOut })\n\nexport function Collapse({\n  open,\n  children,\n  duration = facadeDuration.fast,\n  className,\n  keepMounted = false,\n  id,\n}: CollapseProps) {\n  if (keepMounted) {\n    return (\n      <motion.div\n        id={id}\n        hidden={!open}\n        className={cn(\"overflow-hidden\", className)}\n        initial={false}\n        animate={{ height: open ? \"auto\" : 0, opacity: open ? 1 : 0 }}\n        transition={transition(duration)}\n      >\n        {children}\n      </motion.div>\n    )\n  }\n\n  return (\n    <AnimatePresence initial={false}>\n      {open ? (\n        <motion.div\n          id={id}\n          className={cn(\"overflow-hidden\", className)}\n          initial={{ height: 0, opacity: 0 }}\n          animate={{ height: \"auto\", opacity: 1 }}\n          exit={{ height: 0, opacity: 0 }}\n          transition={transition(duration)}\n        >\n          {children}\n        </motion.div>\n      ) : null}\n    </AnimatePresence>\n  )\n}\n"
    },
    {
      "path": "src/motion/slots.tsx",
      "type": "registry:component",
      "target": "components/motion/slots.tsx",
      "content": "\"use client\"\n\n/**\n * Motion adapters for the section slot props.\n *\n * Every `-motion` section variant needs the same four wrappers, differing only\n * in the tag they render. Keeping them here means a motion variant is genuinely\n * three lines of composition, and that a change to the choreography — timing,\n * direction, travel — happens once rather than in a dozen near-identical files.\n *\n * The tag matters: `StaggerList` renders a `ul` and `StaggerListItem` an `li`,\n * so wrapping a list in motion never costs it its list semantics.\n *\n * Dependencies: react, @/components/motion/stagger.\n */\n\nimport type { ReactNode } from \"react\"\n\nimport { Stagger, StaggerItem } from \"@/components/motion/stagger\"\n\nexport interface MotionSlotProps {\n  className?: string\n  children?: ReactNode\n}\n\n/** Drop-in for a section's `listAs`. Renders a `<ul>`. */\nexport function StaggerList({ className, children }: MotionSlotProps) {\n  return (\n    <Stagger as=\"ul\" className={className}>\n      {children}\n    </Stagger>\n  )\n}\n\n/** Drop-in for a section's `itemAs`. Renders an `<li>`. */\nexport function StaggerListItem({ className, children }: MotionSlotProps) {\n  return (\n    <StaggerItem as=\"li\" className={className}>\n      {children}\n    </StaggerItem>\n  )\n}\n\n/**\n * Drop-in for a section's `stackAs`. Renders a `<div>` and plays on mount\n * rather than on scroll, because a hero is above the fold by definition.\n */\nexport function StaggerStack({ className, children }: MotionSlotProps) {\n  return (\n    <Stagger trigger=\"mount\" stagger={0.07} className={className}>\n      {children}\n    </Stagger>\n  )\n}\n\n/** Drop-in for a section's `blockAs`. Renders a `<div>`. */\nexport function StaggerBlock({ className, children }: MotionSlotProps) {\n  return <StaggerItem className={className}>{children}</StaggerItem>\n}\n"
    }
  ],
  "docs": "Render FacadeMotionProvider once near the root of your app. Icons passed to a -motion section need a client boundary. Docs: https://facadeui.dev/components/motion-primitives"
}
