{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "elements-surfaces",
  "type": "registry:component",
  "title": "Elements Surfaces",
  "description": "Shared design language for the elements family: surface, ink, and motion class recipes plus the shimmer utility.",
  "dependencies": [
    "tw-shimmer"
  ],
  "css": {
    "@import \"tw-shimmer\"": {}
  },
  "files": [
    {
      "type": "registry:lib",
      "path": "components/elements/surfaces.tsx",
      "content": "\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport { useLayoutEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport const paper =\n  \"bg-background shadow-[0_1px_2px_rgba(0,0,0,0.04),0_12px_32px_-16px_rgba(0,0,0,0.12)] dark:bg-popover dark:shadow-none\";\n\nexport const floating =\n  \"bg-background shadow-[0_2px_8px_rgba(0,0,0,0.05),0_20px_48px_-16px_rgba(0,0,0,0.18)] dark:bg-popover dark:shadow-[0_20px_48px_-16px_rgba(0,0,0,0.55)]\";\n\nexport const field = \"bg-foreground/[0.04] dark:bg-foreground/[0.06]\";\n\nexport const fieldInteractive =\n  \"bg-foreground/[0.04] transition-colors hover:bg-foreground/[0.07] dark:bg-foreground/[0.06] dark:hover:bg-foreground/[0.09]\";\n\nexport const pressable =\n  \"transition-transform duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] active:scale-[0.96] motion-reduce:transition-none\";\n\nexport const ghostButton =\n  \"flex items-center justify-center rounded-full text-foreground/45 outline-none transition-[background-color,color,scale] duration-150 hover:bg-foreground/[0.06] hover:text-foreground/90 active:scale-[0.96] focus-visible:ring-2 focus-visible:ring-foreground/20 motion-reduce:transition-none dark:hover:bg-foreground/[0.09]\";\n\nexport const inkButton =\n  \"bg-foreground text-background transition-[opacity,scale] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] hover:opacity-90 active:scale-[0.96] motion-reduce:transition-none\";\n\nexport const iconSwap =\n  \"[grid-area:1/1] transition-[opacity,scale,filter] duration-200 ease-[cubic-bezier(0.2,0,0,1)] motion-reduce:transition-none\";\n\nexport const iconSwapIn = \"scale-100 opacity-100 blur-none\";\n\nexport const iconSwapOut = \"scale-[0.25] opacity-0 blur-[4px]\";\n\nexport const labelSwap =\n  \"col-start-1 row-start-1 flex w-max items-center gap-1.5 leading-none transition-[opacity,filter] duration-300 ease-[cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none\";\n\nexport const labelSwapIn = \"opacity-100 blur-none\";\n\nexport const labelSwapOut =\n  \"pointer-events-none select-none opacity-0 blur-[2px]\";\n\nexport const collapsePanel =\n  \"h-(--collapsible-panel-height) overflow-hidden transition-[height] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] data-[ending-style]:h-0 data-[starting-style]:h-0 motion-reduce:transition-none\";\n\nexport const live = \"text-blue-500 dark:text-blue-400\";\n\nexport const mono = \"font-mono text-[11px] tracking-tight\";\n\nexport function ShimmerLabel({\n  active = true,\n  className,\n  ...props\n}: ComponentProps<\"span\"> & { active?: boolean }) {\n  return (\n    <span\n      className={cn(active && \"shimmer motion-reduce:animate-none\", className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * Scroll region for content that keeps its own whitespace. `whitespace-pre` in\n * a bounded box clips a long line with no way to reach it, so the rows scroll\n * instead.\n *\n * `codeSurface` wraps all the rows as one block, and the rows are its children.\n * It cannot go on each row: `min-width: 100%` resolves against the scroll\n * container's visible width rather than its scroll width, so a per-row width\n * leaves every row except the longest ending its background at the fold.\n */\nexport const codeScroll = \"overflow-x-auto\";\n\nexport const codeSurface = \"w-max min-w-full\";\n\nexport function SwapLabel({\n  active,\n  children,\n  className,\n}: {\n  active: 0 | 1;\n  children: [React.ReactNode, React.ReactNode];\n  className?: string;\n}) {\n  const layers = [useRef<HTMLSpanElement>(null), useRef<HTMLSpanElement>(null)];\n  const [width, setWidth] = useState<number | null>(null);\n\n  useLayoutEffect(() => {\n    const target = layers[active]?.current;\n    if (!target) return undefined;\n    const measure = () =>\n      setWidth(Math.ceil(target.getBoundingClientRect().width));\n    measure();\n    const observer = new ResizeObserver(measure);\n    observer.observe(target);\n    return () => observer.disconnect();\n  }, [active]);\n\n  return (\n    <span\n      style={width === null ? undefined : { width }}\n      className={cn(\n        \"grid overflow-x-clip transition-[width] duration-300 ease-[cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none\",\n        className,\n      )}\n    >\n      {children.map((layer, index) => (\n        <span\n          key={index}\n          ref={layers[index]}\n          aria-hidden={active !== index}\n          className={cn(\n            labelSwap,\n            active === index ? labelSwapIn : labelSwapOut,\n          )}\n        >\n          {layer}\n        </span>\n      ))}\n    </span>\n  );\n}\n"
    }
  ]
}