{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "spotlight-bento",
  "type": "registry:ui",
  "title": "Spotlight bento",
  "description": "Carries one continuous spotlight across multiple tiles to unify the bento surface.",
  "dependencies": [
    "motion"
  ],
  "categories": [
    "visual-ambient"
  ],
  "docs": "https://motion-lexicon.pages.dev/en/components/spotlight-bento/",
  "meta": {
    "engines": [
      "motion"
    ],
    "runtimeCost": "medium",
    "signature": "Continuous spotlight sharing coordinates across tiles",
    "sceneFamily": "spatial-dark",
    "motionRole": "ambient",
    "primaryState": "Connected bento with the spotlight on its active tile",
    "assetProvenance": "self-contained"
  },
  "files": [
    {
      "path": "src/registry/components/spotlight-bento.tsx",
      "type": "registry:ui",
      "target": "components/motion-lexicon/spotlight-bento.tsx",
      "content": "\"use client\";\n\nimport {\n  useEffect,\n  useRef,\n  type PointerEvent as ReactPointerEvent,\n  type ReactNode,\n} from \"react\";\nimport { useReducedMotion } from \"motion/react\";\n\nexport type SpotlightBentoItem = {\n  id: string;\n  label: string;\n  value: string;\n  meta?: string;\n  icon?: ReactNode;\n  tone?: \"blue\" | \"clay\" | \"moss\" | \"ink\";\n  wide?: boolean;\n};\n\nexport type SpotlightBentoProps = {\n  items: readonly SpotlightBentoItem[];\n  label?: string;\n  className?: string;\n  selectedId?: string;\n  onSelect?: (item: SpotlightBentoItem) => void;\n};\n\nconst toneClass = {\n  blue: \"bg-[#315f8c] text-[#eff9ff]\",\n  clay: \"bg-[#d36a42] text-[#fff3df]\",\n  moss: \"bg-[#6b8760] text-[#f5ffe8]\",\n  ink: \"bg-[#2a2520] text-[#fff0dc]\",\n} as const;\n\nexport function SpotlightBento({\n  items,\n  label = \"Product signals\",\n  className = \"\",\n  selectedId,\n  onSelect,\n}: SpotlightBentoProps) {\n  const rootRef = useRef<HTMLDivElement>(null);\n  const lightRef = useRef<HTMLDivElement>(null);\n  const frameRef = useRef<number | null>(null);\n  const scheduleRef = useRef<() => void>(() => undefined);\n  const pointRef = useRef({ x: 220, y: 125 });\n  const reduced = useReducedMotion() === true;\n\n  const paint = () => {\n    frameRef.current = null;\n    const light = lightRef.current;\n    if (!light) return;\n    light.style.transform = `translate3d(${pointRef.current.x - 150}px, ${pointRef.current.y - 150}px, 0)`;\n  };\n\n  const schedule = () => {\n    if (frameRef.current === null) frameRef.current = requestAnimationFrame(paint);\n  };\n  scheduleRef.current = schedule;\n\n  useEffect(() => {\n    const root = rootRef.current;\n    if (!root) return;\n    const resize = new ResizeObserver(([entry]) => {\n      pointRef.current = {\n        x: entry.contentRect.width * 0.72,\n        y: entry.contentRect.height * 0.28,\n      };\n      scheduleRef.current();\n    });\n    resize.observe(root);\n    return () => {\n      resize.disconnect();\n      if (frameRef.current !== null) cancelAnimationFrame(frameRef.current);\n    };\n  }, []);\n\n  const moveLight = (event: ReactPointerEvent<HTMLDivElement>) => {\n    if (\n      reduced ||\n      event.pointerType !== \"mouse\" ||\n      !window.matchMedia(\"(hover: hover) and (pointer: fine)\").matches\n    ) {\n      return;\n    }\n    const rect = event.currentTarget.getBoundingClientRect();\n    pointRef.current = { x: event.clientX - rect.left, y: event.clientY - rect.top };\n    schedule();\n  };\n\n  return (\n    <section\n      aria-label={label}\n      className={`w-full rounded-[18px] border border-[#f3dab2]/20 bg-[#15130f] p-2 shadow-[0_24px_48px_-34px_rgba(0,0,0,.9)] ${className}`}\n    >\n      <div\n        ref={rootRef}\n        onPointerMove={moveLight}\n        onPointerLeave={() => {\n          const root = rootRef.current;\n          if (!root) return;\n          pointRef.current = { x: root.clientWidth * 0.72, y: root.clientHeight * 0.28 };\n          schedule();\n        }}\n        className=\"relative isolate grid grid-cols-2 gap-1.5 overflow-hidden rounded-[13px] bg-[radial-gradient(circle_at_76%_16%,rgba(229,154,89,.22),transparent_34%),radial-gradient(circle_at_12%_90%,rgba(80,130,116,.22),transparent_40%)]\"\n      >\n        <div\n          ref={lightRef}\n          aria-hidden\n          className={`pointer-events-none absolute left-0 top-0 z-0 size-[300px] rounded-full bg-[radial-gradient(circle,rgba(255,225,179,.44)_0%,rgba(255,225,179,0)_72%)] ${\n            reduced ? \"\" : \"will-change-transform\"\n          }`}\n        />\n\n        {items.map((item) => {\n          const interactive = typeof onSelect === \"function\";\n          const selected = interactive && item.id === selectedId;\n          const cardClassName = `group relative z-10 min-h-[106px] min-w-0 overflow-hidden rounded-xl border bg-[#211d17]/94 p-3 text-left shadow-[inset_0_1px_0_rgba(255,255,255,.03)] ${\n            selected\n              ? \"border-[#f1c98d]/75\"\n              : \"border-white/[0.1]\"\n          } ${\n            item.wide ? \"col-span-2\" : \"\"\n          } ${\n            interactive\n              ? \"outline-none focus-visible:ring-2 focus-visible:ring-[#f1c98d] focus-visible:ring-offset-1 focus-visible:ring-offset-[#15130f]\"\n              : \"\"\n          } ${\n            interactive && !reduced\n              ? \"transition-[transform,border-color,background-color] duration-200 [transition-timing-function:cubic-bezier(.2,.8,.2,1)] [@media(hover:hover)_and_(pointer:fine)]:hover:-translate-y-0.5 [@media(hover:hover)_and_(pointer:fine)]:hover:border-[#f1c98d]/45\"\n              : \"\"\n          }`;\n          const content = (\n            <>\n              <span aria-hidden className=\"absolute -right-7 -top-8 size-24 rounded-full border border-white/[0.08]\" />\n              <span className=\"flex items-start justify-between gap-2\">\n                <span className={`grid size-9 shrink-0 place-items-center rounded-lg ${toneClass[item.tone ?? \"ink\"]}`}>\n                  {item.icon ?? <span className=\"size-1.5 rounded-full bg-current\" />}\n                </span>\n                {item.meta ? (\n                  <span className=\"font-mono text-[10.5px] uppercase tracking-[0.1em] text-[#f6dfbb]/52\">{item.meta}</span>\n                ) : null}\n              </span>\n              <span className=\"mt-3 block min-w-0\">\n                <strong className=\"block truncate text-[14px] font-medium tracking-[-0.025em] text-[#fff0d8]\">{item.value}</strong>\n                <span className=\"mt-0.5 block truncate text-[10.5px] text-[#f6dfbb]/60\">{item.label}</span>\n              </span>\n              {interactive ? (\n                <span\n                  aria-hidden\n                  data-spotlight-indicator\n                  data-motion-mode={reduced ? \"instant\" : \"standard\"}\n                  className={`absolute inset-x-3 bottom-0 h-px bg-[#f2c98c] ${selected ? \"opacity-100\" : \"opacity-0 group-focus-visible:opacity-100 [@media(hover:hover)_and_(pointer:fine)]:group-hover:opacity-100\"} ${\n                    reduced\n                      ? \"\"\n                      : `origin-left transition-[opacity,transform] duration-200 [transition-timing-function:cubic-bezier(.2,.8,.2,1)] ${selected ? \"scale-x-100\" : \"scale-x-0 group-focus-visible:scale-x-100 [@media(hover:hover)_and_(pointer:fine)]:group-hover:scale-x-100\"}`\n                  }`}\n                />\n              ) : null}\n            </>\n          );\n\n          return interactive ? (\n            <button\n              key={item.id}\n              type=\"button\"\n              aria-pressed={selected}\n              onClick={() => onSelect(item)}\n              className={cardClassName}\n            >\n              {content}\n            </button>\n          ) : (\n            <article key={item.id} className={cardClassName}>\n              {content}\n            </article>\n          );\n        })}\n      </div>\n    </section>\n  );\n}\n"
    }
  ]
}
