{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bento-grid",
  "type": "registry:ui",
  "title": "Bento Grid",
  "description": "An animated bento feature grid with flowing files, drifting categories, a playable install and a copyable command.",
  "files": [
    {
      "path": "src/registry/ui/bento-grid.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n  Blocks,\n  Check,\n  Download,\n  FileCode2,\n  Folder,\n  Layers,\n  MousePointerClick,\n  Palette,\n  Sparkles,\n  SquareStack,\n  TextCursorInput,\n  Wand2,\n} from \"lucide-react\";\nimport Link from \"next/link\";\nimport { cn } from \"@/lib/utils\";\n\nconst ACCENT = \"#0A6EFF\";\nconst INSTALL_COMMAND = \"npx shadcn@latest add https://ui.devsloka.in/r/profile-card.json\";\n\nconst FILES = [\n  \"text-effect.tsx\",\n  \"profile-card.tsx\",\n  \"receipt-printer.tsx\",\n  \"tabs-switcher.tsx\",\n  \"now-playing-card.tsx\",\n  \"dock-gallery.tsx\",\n  \"team-carousel.tsx\",\n];\n\nconst CATEGORIES = [\n  { label: \"Interaction Patterns\", icon: MousePointerClick },\n  { label: \"Animated Inputs\", icon: TextCursorInput },\n  { label: \"Cards & Widgets\", icon: SquareStack },\n  { label: \"Motion Effects\", icon: Wand2 },\n  { label: \"Backgrounds\", icon: Palette },\n  { label: \"Page Blocks\", icon: Blocks },\n  { label: \"React Hooks\", icon: Layers },\n  { label: \"Templates\", icon: Sparkles },\n];\n\nconst REGISTRY_FILES = [\n  \"profile-card.json\",\n  \"receipt-printer.json\",\n  \"arc-scroll-steps.json\",\n  \"availability-card.json\",\n  \"reminders-card.json\",\n  \"morphing-nav.json\",\n];\n\nconst CODE_LINES = [\n  'import { ProfileCard } from \"@/components/ui/profile-card\";',\n  \"\",\n  \"export function Team() {\",\n  \"  return (\",\n  \"    <ProfileCard\",\n  '      name=\"Devsloka\"',\n  '      handle=\"@devsloka\"',\n  \"      verified\",\n  '      accent=\"#0A6EFF\"',\n  \"    />\",\n  \"  );\",\n  \"}\",\n];\n\n// Shared keyframes for the looping tracks; React hoists and dedupes the <style>.\nconst KEYFRAMES = `\n@keyframes bento-scroll-x { to { transform: translateX(-50%); } }\n@keyframes bento-scroll-y { to { transform: translateY(-50%); } }\n@keyframes bento-float { from { transform: translateY(-2px); } to { transform: translateY(4px); } }\n`;\n\n// Constant-speed loops: linear, paused while hovered, still under reduced motion.\nconst loopClass =\n  \"group-hover/loop:[animation-play-state:paused] motion-reduce:animate-none\";\nconst pressClass =\n  \"transition-[transform,background-color,opacity] duration-[160ms] ease-[cubic-bezier(0.23,1,0.32,1)] active:scale-[0.97] motion-reduce:active:scale-100\";\n\nexport function BentoGrid({ className, ...props }: React.ComponentProps<\"section\">) {\n  return (\n    <section\n      data-slot=\"bento-grid\"\n      style={{ \"--bento-accent\": ACCENT } as React.CSSProperties}\n      className={cn(\"@container w-full bg-[#EEEAE6] px-4 py-12 text-neutral-900 sm:px-8 sm:py-16\", className)}\n      {...props}\n    >\n      <style href=\"bento-grid-keyframes\" precedence=\"default\">\n        {KEYFRAMES}\n      </style>\n      {/* Container queries: the layout follows the component's own width, not the window's.\n          Bento from 56rem, two columns from 42rem (dense flow fills beside the tall tile), else stacked. */}\n      <div className=\"mx-auto grid max-w-6xl grid-cols-1 gap-3 @2xl:grid-flow-dense @2xl:grid-cols-2 @4xl:grid-flow-row @4xl:grid-cols-10 @4xl:grid-rows-[17rem_17rem]\">\n        <ResearchTile />\n        <CategoriesTile />\n        <InstallTile />\n        <WorkflowTile />\n        <FilesTile />\n      </div>\n    </section>\n  );\n}\n\nfunction Heading({ children, className }: { children: React.ReactNode; className?: string }) {\n  return (\n    <h3 className={cn(\"font-serif text-[1.75rem] leading-[1.05] tracking-tight @4xl:text-3xl\", className)}>\n      {children}\n    </h3>\n  );\n}\n\n// Files flow up past a fixed action chip, as if each one is being added in turn.\nfunction ResearchTile() {\n  return (\n    <article className=\"flex flex-col rounded-3xl bg-[#1F1D1B] p-2 text-white @2xl:row-span-2 @4xl:col-span-3\">\n      <div className=\"group/loop relative h-64 overflow-hidden rounded-2xl bg-[#EEEAE6] @4xl:h-[52%]\">\n        {/* The fade sits on the track, so chips melt into the light panel, not the dark tile. */}\n        <div className=\"absolute inset-0 [mask-image:linear-gradient(transparent,black_22%,black_78%,transparent)]\">\n          {/* Bottom padding equals the gap, so the -50% loop lands exactly on the next copy. */}\n          <div className={cn(\"flex flex-col items-center gap-2.5 pb-2.5 animate-[bento-scroll-y_14s_linear_infinite]\", loopClass)}>\n            {[...FILES, ...FILES].map((file, i) => (\n              <span\n                key={i}\n                aria-hidden={i >= FILES.length}\n                className=\"w-44 rounded-md border border-black/5 bg-white px-3 py-2 text-center text-xs text-neutral-700 shadow-sm\"\n              >\n                {file}\n              </span>\n            ))}\n          </div>\n        </div>\n        <span className=\"absolute top-1/2 left-1/2 w-44 -translate-x-1/2 -translate-y-1/2 rounded-md bg-[var(--bento-accent)] px-3 py-2.5 text-center text-xs font-medium text-white shadow-lg shadow-[color-mix(in_oklab,var(--bento-accent)_40%,transparent)]\">\n          Add to project\n        </span>\n      </div>\n      <div className=\"flex flex-1 flex-col px-5 pt-7 pb-5\">\n        <Heading>\n          Proven Components,\n          <br />\n          Rapid Results\n        </Heading>\n        <p className=\"mt-5 mb-6 max-w-xs text-sm leading-relaxed text-white/65\">\n          Interfaces that move? Solved. Copy production-ready React components, typed, accessible\n          and themable, straight into your codebase.\n        </p>\n        <Link\n          href=\"/components\"\n          className={cn(\n            \"mt-auto inline-flex h-9 w-fit items-center rounded-full bg-white px-4 text-sm font-medium text-neutral-900 outline-none hover:bg-white/90 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-[#1F1D1B]\",\n            pressClass\n          )}\n        >\n          Learn more\n        </Link>\n      </div>\n    </article>\n  );\n}\n\nfunction CategoryCard({ label, icon: Icon }: (typeof CATEGORIES)[number]) {\n  return (\n    <div className=\"flex h-24 w-32 shrink-0 flex-col justify-between rounded-xl bg-linear-to-b from-[#F5F2EF] to-white p-3\">\n      <span className=\"flex size-7 items-center justify-center rounded-md bg-white shadow-sm\">\n        <Icon aria-hidden className=\"size-4\" />\n      </span>\n      <span className=\"text-xs leading-tight\">{label}</span>\n    </div>\n  );\n}\n\n// Two rows of categories drifting in opposite directions.\nfunction CategoriesTile() {\n  const rows = [CATEGORIES.slice(0, 4), CATEGORIES.slice(4)];\n  return (\n    <article\n      aria-label=\"Component categories\"\n      className=\"group/loop flex flex-col justify-center gap-3 overflow-hidden rounded-3xl bg-white py-5 @4xl:col-span-3 [mask-image:linear-gradient(to_right,transparent,black_15%,black_85%,transparent)]\"\n    >\n      {rows.map((row, r) => (\n        <div\n          key={r}\n          className={cn(\n            \"flex w-max gap-3 pr-3 animate-[bento-scroll-x_24s_linear_infinite]\",\n            r === 1 && \"[animation-direction:reverse]\",\n            loopClass\n          )}\n        >\n          {[...row, ...row, ...row, ...row].map((category, i) => (\n            <div key={i} aria-hidden={i >= row.length}>\n              <CategoryCard {...category} />\n            </div>\n          ))}\n        </div>\n      ))}\n    </article>\n  );\n}\n\ntype InstallState = \"idle\" | \"installing\" | \"done\";\nconst INSTALL_MS = 1600;\nconst RESET_MS = 2400;\n\nfunction InstallTile() {\n  const [state, setState] = useState<InstallState>(\"idle\");\n\n  // Fill, confirm, then reset so the tile can be replayed.\n  useEffect(() => {\n    if (state === \"idle\") return;\n    const timer = setTimeout(\n      () => setState(state === \"installing\" ? \"done\" : \"idle\"),\n      state === \"installing\" ? INSTALL_MS : RESET_MS\n    );\n    return () => clearTimeout(timer);\n  }, [state]);\n\n  return (\n    // minmax(0, 1fr) lets the text column shrink, so the panel never overflows into the frame's padding.\n    <article className=\"grid grid-cols-1 gap-3 rounded-3xl bg-[#1F1D1B] p-2 text-white @lg:grid-cols-[minmax(0,1fr)_14rem] @4xl:grid-cols-[minmax(0,1fr)_10.5rem] @6xl:grid-cols-[minmax(0,1fr)_12rem] @2xl:col-span-2 @4xl:col-span-4\">\n      <div className=\"flex min-w-0 flex-col justify-center px-4 pt-5 @lg:pt-0 @6xl:px-5\">\n        <Heading className=\"@4xl:text-xl @6xl:text-[1.75rem]\">\n          Copy-Paste &amp;\n          <br />\n          Project-Ready\n        </Heading>\n        <p className=\"mt-4 max-w-[16rem] text-sm leading-relaxed text-white/65\">\n          Turn component chaos into a design system you own.\n        </p>\n      </div>\n      <div className=\"flex min-w-0 flex-col justify-between gap-4 rounded-2xl bg-[#EEEAE6] p-4 text-neutral-900 @6xl:p-5\">\n        <p className=\"text-sm leading-snug\">\n          Every file lands in <span className=\"font-mono text-xs\">components/ui</span>, typed,\n          themable and yours to change.\n        </p>\n        <div className=\"flex flex-col gap-2\">\n          <div className=\"flex h-10 items-center justify-between gap-2 rounded-md border border-black/10 bg-white px-3 text-xs\">\n            <span className=\"truncate\">96 components</span>\n            <Download aria-hidden className=\"size-4 shrink-0\" />\n          </div>\n          <button\n            type=\"button\"\n            disabled={state !== \"idle\"}\n            onClick={() => setState(\"installing\")}\n            className={cn(\n              \"relative flex h-10 cursor-pointer items-center justify-center overflow-hidden rounded-md bg-[#1F1D1B] text-sm font-medium text-white outline-none hover:bg-black focus-visible:ring-2 focus-visible:ring-[var(--bento-accent)] disabled:cursor-default\",\n              pressClass\n            )}\n          >\n            {/* Progress fill: a linear scale, since it tracks elapsed time. */}\n            <span\n              aria-hidden\n              className={cn(\n                \"absolute inset-0 origin-left bg-[var(--bento-accent)]\",\n                state === \"idle\" ? \"scale-x-0 transition-none\" : \"scale-x-100 transition-transform ease-linear motion-reduce:transition-none\"\n              )}\n              style={{ transitionDuration: state === \"idle\" ? \"0ms\" : `${INSTALL_MS}ms` }}\n            />\n            <span className=\"relative inline-flex items-center gap-1.5\">\n              {state === \"done\" && <Check aria-hidden className=\"size-4\" />}\n              {state === \"idle\" ? \"Install\" : state === \"installing\" ? \"Installing…\" : \"Installed\"}\n            </span>\n          </button>\n          <span role=\"status\" className=\"sr-only\">\n            {state === \"installing\" ? \"Installing components\" : state === \"done\" ? \"Components installed\" : \"\"}\n          </span>\n        </div>\n      </div>\n    </article>\n  );\n}\n\nfunction WorkflowTile() {\n  const [copied, setCopied] = useState(false);\n  const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n  useEffect(() => () => clearTimeout(timer.current), []);\n\n  const copy = async () => {\n    try {\n      await navigator.clipboard.writeText(INSTALL_COMMAND);\n    } catch {\n      return;\n    }\n    setCopied(true);\n    clearTimeout(timer.current);\n    timer.current = setTimeout(() => setCopied(false), 1800);\n  };\n\n  const code = (\n    <pre className=\"font-mono text-[0.6875rem] leading-relaxed whitespace-pre\">\n      {CODE_LINES.join(\"\\n\")}\n    </pre>\n  );\n\n  return (\n    <article className=\"grid grid-cols-1 items-center gap-6 overflow-hidden rounded-3xl bg-white p-6 @lg:grid-cols-[minmax(0,1.15fr)_minmax(0,1fr)] @2xl:col-span-2 @4xl:col-span-4\">\n      <div className=\"min-w-0\">\n        <Heading>\n          Aligned With\n          <br />\n          Your Workflow\n        </Heading>\n        <p className=\"mt-4 max-w-xs text-sm leading-relaxed text-neutral-600\">\n          Hand off a whole component with one command. It arrives as plain source you can read,\n          review and refactor.\n        </p>\n      </div>\n\n      <div className=\"relative h-48 select-none\">\n        {/* Soft, out-of-focus code around a crisp centre, where the action lives. */}\n        <div aria-hidden className=\"absolute inset-0 text-neutral-400 blur-[1.5px]\">\n          {code}\n        </div>\n        <div\n          aria-hidden\n          className=\"absolute inset-0 text-neutral-800 [mask-image:radial-gradient(ellipse_45%_35%_at_50%_55%,black,transparent)]\"\n        >\n          {code}\n        </div>\n\n        <div className=\"absolute top-[38%] left-1/2 flex -translate-x-1/2 flex-col items-center gap-1.5 animate-[bento-float_2.6s_ease-in-out_infinite_alternate] motion-reduce:animate-none\">\n          <span className=\"rounded-md bg-[var(--bento-accent)] px-2.5 py-1.5 text-xs font-medium whitespace-nowrap text-white shadow-lg\">\n            {copied ? \"Copied to clipboard\" : \"Copy the install command\"}\n          </span>\n          <button\n            type=\"button\"\n            onClick={copy}\n            aria-label=\"Copy install command\"\n            className={cn(\n              \"flex cursor-pointer items-center gap-1 rounded-full border border-black/10 bg-white px-3 py-1 font-mono text-[0.625rem] shadow-sm outline-none hover:bg-neutral-50 focus-visible:ring-2 focus-visible:ring-[var(--bento-accent)]\",\n              pressClass\n            )}\n          >\n            {copied ? <Check aria-hidden className=\"size-3\" /> : <FileCode2 aria-hidden className=\"size-3\" />}\n            npx shadcn add\n          </button>\n          <span role=\"status\" className=\"sr-only\">\n            {copied ? \"Install command copied\" : \"\"}\n          </span>\n        </div>\n      </div>\n    </article>\n  );\n}\n\nfunction FilesTile() {\n  return (\n    <article className=\"flex flex-col rounded-3xl bg-white p-6 @4xl:col-span-3\">\n      <p className=\"text-sm leading-snug\">\n        Every component ships with keyboard, screen reader and reduced-motion support.\n      </p>\n      <a\n        href=\"https://www.devsloka.in/contact\"\n        className={cn(\n          \"mt-4 flex h-10 items-center justify-center rounded-md bg-[#1F1D1B] text-sm font-medium text-white outline-none hover:bg-black focus-visible:ring-2 focus-visible:ring-[var(--bento-accent)] focus-visible:ring-offset-2\",\n          pressClass\n        )}\n      >\n        Ask Devsloka\n      </a>\n      <p className=\"mt-4 text-xs text-neutral-500\">96 Registry Files</p>\n      <div className=\"group/loop relative mt-2 h-32 overflow-hidden @4xl:h-auto @4xl:min-h-0 @4xl:flex-1 [mask-image:linear-gradient(transparent,black_14%,black_55%,transparent)]\">\n        <ul className={cn(\"flex flex-col gap-2 pb-2 animate-[bento-scroll-y_16s_linear_infinite]\", loopClass)}>\n          {[...REGISTRY_FILES, ...REGISTRY_FILES].map((file, i) => (\n            <li\n              key={i}\n              aria-hidden={i >= REGISTRY_FILES.length}\n              className=\"flex h-9 shrink-0 items-center gap-2 rounded-md border border-black/10 px-2.5 text-xs\"\n            >\n              <Folder aria-hidden className=\"size-4 text-neutral-500\" />\n              <span className=\"flex-1 truncate\">{file}</span>\n              <Download aria-hidden className=\"size-4 text-neutral-500\" />\n            </li>\n          ))}\n        </ul>\n      </div>\n    </article>\n  );\n}\n\nexport default BentoGrid;\n"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://ui.devsloka.in/r/profile-card.json"
  ]
}
