{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "empty-glass",
  "title": "Glass empty",
  "description": "Styled empty: the Glass set. 5 decorative empty variations (FrostEmpty, TintEmpty, SmokeEmpty, CrystalEmpty, DepthEmpty) on the ai2 token system, driven by CSS token transitions and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens",
    "@ai2/glass"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/empty-glass.tsx",
      "content": "\"use client\"\n\nimport type * as React from \"react\"\nimport { Box, Gem, Inbox, Layers, Sparkles } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { glassDepth } from \"@/registry/ai2/ui/glass\"\n\n/* Glass empty family: 5 frosted / translucent empty-state panels. The glass\n   surface derives from the glassDepth scale inside @ai2/glass (no hand-written\n   backdrop-blur), so they all carry the ai2 inset highlight signature; the\n   difference is the depth step, the tint and a light detail specific to the\n   variant. Color comes ONLY from semantic tokens, with transparency via\n   color-mix. size = padding + icon/text scale. Renders with no props too. Static -\n   no motion. Note: the panel only makes sense sitting on top of a background.\n\n   Depth map (a large panel sitting in the page flow):\n     Crystal -> sm  (4px)  clear glass + a thin light line on top\n     Frost   -> md  (8px)  the canonical default\n     Tint    -> md  (8px)  the same glass as Frost, differing in its info hue\n     Smoke   -> lg  (16px) dense smoke\n     Depth   -> xl  (24px) a floating layered panel\n\n   The tint surfaces are written as bg-[color-mix(...)], NOT [background:...]:\n   only bg-* is in the same tailwind-merge group as the scale's bg-background/N\n   and replaces it cleanly; since [background:...] is a separate group both would\n   survive and the winner would be decided by stylesheet order rather than class\n   order. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\ntype Scale = {\n  root: string\n  icon: string\n  badge: string\n  title: string\n  description: string\n}\n\nconst scales: Record<StyledSize, Scale> = {\n  sm: {\n    root: \"gap-2 p-6\",\n    icon: \"size-6\",\n    badge: \"size-12\",\n    title: \"text-sm\",\n    description: \"text-xs\",\n  },\n  md: {\n    root: \"gap-3 p-8\",\n    icon: \"size-8\",\n    badge: \"size-16\",\n    title: \"text-base\",\n    description: \"text-sm\",\n  },\n  lg: {\n    root: \"gap-3.5 p-10\",\n    icon: \"size-10\",\n    badge: \"size-20\",\n    title: \"text-lg\",\n    description: \"text-sm\",\n  },\n  xl: {\n    root: \"gap-4 p-12\",\n    icon: \"size-12\",\n    badge: \"size-24\",\n    title: \"text-xl\",\n    description: \"text-base\",\n  },\n}\n\ninterface EmptyProps {\n  className?: string\n  size?: StyledSize\n  icon?: React.ReactNode\n  title?: React.ReactNode\n  description?: React.ReactNode\n  action?: React.ReactNode\n}\n\nconst rootBase =\n  \"relative flex flex-col items-center justify-center overflow-hidden rounded-2xl border text-center\"\n\nconst iconBase = \"[&_i]:leading-none [&_svg]:size-full\"\n\n/* Shared glass shell: the panel class carries the variant's technique. */\nfunction GlassShell({\n  panelClassName,\n  badgeClassName,\n  fallbackIcon,\n  className,\n  size = \"md\",\n  icon,\n  title,\n  description,\n  action,\n  children,\n}: EmptyProps & {\n  panelClassName: string\n  badgeClassName: string\n  fallbackIcon: React.ReactNode\n  children?: React.ReactNode\n}) {\n  const s = scales[size]\n  return (\n    <div\n      data-slot=\"styled-empty\"\n      className={cn(rootBase, panelClassName, s.root, className)}\n    >\n      {children}\n      <span\n        aria-hidden=\"true\"\n        className={cn(\n          \"relative flex items-center justify-center rounded-full border\",\n          badgeClassName,\n          s.badge\n        )}\n      >\n        <span className={cn(iconBase, s.icon)}>{icon ?? fallbackIcon}</span>\n      </span>\n      <p className={cn(\"relative font-semibold text-foreground\", s.title)}>\n        {title}\n      </p>\n      {description ? (\n        <p\n          className={cn(\n            \"relative max-w-prose text-muted-foreground\",\n            s.description\n          )}\n        >\n          {description}\n        </p>\n      ) : null}\n      {action ? <div className=\"relative mt-2\">{action}</div> : null}\n    </div>\n  )\n}\n\nconst neutralBadge =\n  \"text-muted-foreground [background:color-mix(in_oklab,var(--color-foreground)_8%,transparent)] [border-color:color-mix(in_oklab,var(--color-border)_70%,transparent)]\"\n\n/* FrostEmpty: the canonical default glass (md/8px). The ground is now the scale's own bg-background/60 - the variant only softens the border. */\nexport function FrostEmpty({\n  title = \"Nothing here yet\",\n  description = \"This frosted panel is waiting for its first item.\",\n  ...props\n}: EmptyProps) {\n  return (\n    <GlassShell\n      panelClassName={cn(\n        glassDepth.md,\n        \"[border-color:color-mix(in_oklab,var(--color-border)_60%,transparent)]\"\n      )}\n      badgeClassName={neutralBadge}\n      fallbackIcon={<Inbox />}\n      title={title}\n      description={description}\n      {...props}\n    />\n  )\n}\n\n/* TintEmpty: the SAME md step as Frost; what distinguishes it is the info hue, not the blur. (Because primary/brand carries ~0.005 chroma, a \"primary tint\" would be invisible.) The icon colour was also moved from text-primary to text-info: inside an info-toned badge, primary read as neutral, so the tint was inconsistent. */\nexport function TintEmpty({\n  title = \"Your collection is empty\",\n  description = \"Add something and it will appear on this tinted panel.\",\n  ...props\n}: EmptyProps) {\n  return (\n    <GlassShell\n      panelClassName={cn(\n        glassDepth.md,\n        \"bg-[color-mix(in_oklab,var(--color-info)_10%,transparent)]\",\n        \"supports-[backdrop-filter]:bg-[color-mix(in_oklab,var(--color-info)_10%,transparent)]\",\n        \"[border-color:color-mix(in_oklab,var(--color-info)_25%,transparent)]\"\n      )}\n      badgeClassName=\"text-info [background:color-mix(in_oklab,var(--color-info)_16%,transparent)] [border-color:color-mix(in_oklab,var(--color-info)_30%,transparent)]\"\n      fallbackIcon={<Sparkles />}\n      title={title}\n      description={description}\n      {...props}\n    />\n  )\n}\n\n/* SmokeEmpty: lg (16px). It used to be xl; a large panel sits on a flat page ground, and with nothing to diffuse the 24px cost a compositor layer without adding anything visually. */\nexport function SmokeEmpty({\n  title = \"No activity to show\",\n  description = \"Events will surface through the smoke as they happen.\",\n  ...props\n}: EmptyProps) {\n  return (\n    <GlassShell\n      panelClassName={cn(\n        glassDepth.lg,\n        \"bg-[color-mix(in_oklab,var(--color-foreground)_12%,transparent)]\",\n        \"supports-[backdrop-filter]:bg-[color-mix(in_oklab,var(--color-foreground)_12%,transparent)]\",\n        \"[border-color:color-mix(in_oklab,var(--color-foreground)_18%,transparent)]\"\n      )}\n      badgeClassName={neutralBadge}\n      fallbackIcon={<Layers />}\n      title={title}\n      description={description}\n      {...props}\n    />\n  )\n}\n\n/* CrystalEmpty: sm (4px) - this file was already on the right step with blur-sm, and now the same value comes from the scale together with the signature. Its character is the thin light line on top. */\nexport function CrystalEmpty({\n  title = \"A clean slate\",\n  description = \"Nothing is stored here yet. Whatever you add shows up first.\",\n  ...props\n}: EmptyProps) {\n  return (\n    <GlassShell\n      panelClassName={cn(\n        glassDepth.sm,\n        \"[border-color:color-mix(in_oklab,var(--color-border)_50%,transparent)]\"\n      )}\n      badgeClassName={neutralBadge}\n      fallbackIcon={<Gem />}\n      title={title}\n      description={description}\n      {...props}\n    >\n      <span\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute inset-x-6 top-0 h-px [background:linear-gradient(to_right,transparent,color-mix(in_oklab,var(--color-foreground)_30%,transparent),transparent)]\"\n      />\n    </GlassShell>\n  )\n}\n\n/* DepthEmpty: xl (24px) - the only variant in the family meant to read as a\n   genuinely floating panel; this is the one place that earns the depth.\n   The old shadow-lg + [box-shadow:inset...] pair was REMOVED. They were in the\n   same tailwind-merge group, so shadow-lg was already dropping silently; worse,\n   the remaining [box-shadow:...] would now take the place of the glassDepth\n   signature. The depth is now told through the scale's ambient drop plus a radial\n   light coming from below. */\nexport function DepthEmpty({\n  title = \"No items in this space\",\n  description = \"This layered panel fills up as soon as you create something.\",\n  ...props\n}: EmptyProps) {\n  return (\n    <GlassShell\n      panelClassName={cn(\n        glassDepth.xl,\n        \"[border-color:color-mix(in_oklab,var(--color-border)_60%,transparent)]\"\n      )}\n      badgeClassName={neutralBadge}\n      fallbackIcon={<Box />}\n      title={title}\n      description={description}\n      {...props}\n    >\n      <span\n        aria-hidden=\"true\"\n        /* The light derives from foreground: in this theme --color-primary resolves to BYTE-IDENTICAL the same value as --color-foreground, so a \"primary glow\" was both an invisible claim and a wrong signal. Primary is a role token (active/selected/focus), not a decorative colour. */\n        className=\"pointer-events-none absolute inset-0 [background:radial-gradient(120%_80%_at_50%_120%,color-mix(in_oklab,var(--color-foreground)_14%,transparent),transparent_70%)]\"\n      />\n    </GlassShell>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/empty-glass.tsx"
    }
  ],
  "categories": [
    "styled",
    "empty"
  ],
  "type": "registry:component"
}