{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ds-empty-state-illustration",
  "description": "Empty state with 8 animated SVG illustrations for common UI contexts.",
  "dependencies": [
    "class-variance-authority"
  ],
  "registryDependencies": [
    "@lema-ds/skeleton",
    "@lema-ds/button",
    "@lema-ds/tokens"
  ],
  "files": [
    {
      "path": "components/ds/ds-empty-state-illustration.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { cn } from \"@/lib/utils\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport type EmptyStateVariant =\n  | \"default\"\n  | \"search\"\n  | \"error\"\n  | \"success\"\n  | \"inbox\"\n  | \"filter\"\n  | \"upload\"\n  | \"locked\"\n\nexport type EmptyStateSize = \"sm\" | \"md\" | \"lg\"\n\nexport interface EmptyStateIllustrationProps extends React.HTMLAttributes<HTMLDivElement> {\n  variant?: EmptyStateVariant\n  title?: string\n  description?: string\n  action?: React.ReactNode\n  size?: EmptyStateSize\n  loading?: boolean\n  /** Override the SVG illustration with a custom node */\n  illustration?: React.ReactNode\n}\n\n// ── Variants ───────────────────────────────────────────────────────────────\n\nexport const emptyStateContainerVariants = cva(\n  \"flex w-full flex-col items-center justify-center gap-4 text-center\",\n  {\n    variants: {\n      size: {\n        sm: \"py-8\",\n        md: \"py-12\",\n        lg: \"py-20\",\n      },\n    },\n    defaultVariants: { size: \"md\" },\n  }\n)\n\nexport const emptyStateTitleVariants = cva(\"font-semibold text-foreground\", {\n  variants: {\n    size: {\n      sm: \"text-sm\",\n      md: \"text-base\",\n      lg: \"text-lg\",\n    },\n  },\n  defaultVariants: { size: \"md\" },\n})\n\nexport const emptyStateDescriptionVariants = cva(\n  \"max-w-sm text-muted-foreground\",\n  {\n    variants: {\n      size: {\n        sm: \"text-xs\",\n        md: \"text-sm\",\n        lg: \"text-base\",\n      },\n    },\n    defaultVariants: { size: \"md\" },\n  }\n)\n\n// ── Skeleton dims ─────────────────────────────────────────────────────────\n\nconst skeletonDims: Record<\n  EmptyStateSize,\n  { illus: string; title: string; desc: string }\n> = {\n  sm: { illus: \"size-16\", title: \"h-4 w-32\", desc: \"h-3 w-40\" },\n  md: { illus: \"size-24\", title: \"h-5 w-40\", desc: \"h-4 w-52\" },\n  lg: { illus: \"size-32\", title: \"h-6 w-48\", desc: \"h-5 w-64\" },\n}\n\n// ── SVG Sizes ────────────────────────────────────────────────────────────\n\nconst svgSize: Record<EmptyStateSize, number> = { sm: 64, md: 96, lg: 128 }\n\n// ── Illustrations ─────────────────────────────────────────────────────────\n\nfunction DefaultIllustration({ size }: { size: number }) {\n  return (\n    <svg\n      width={size}\n      height={size}\n      viewBox=\"0 0 96 96\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <style>\n        {\n          \"@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}\"\n        }\n      </style>\n      <g style={{ animation: \"float 3s ease-in-out infinite\" }}>\n        <rect x=\"16\" y=\"28\" width=\"64\" height=\"48\" rx=\"8\" fill=\"var(--muted)\" />\n        <rect\n          x=\"24\"\n          y=\"40\"\n          width=\"48\"\n          height=\"4\"\n          rx=\"2\"\n          fill=\"var(--muted-foreground)\"\n          fillOpacity=\".25\"\n        />\n        <rect\n          x=\"24\"\n          y=\"50\"\n          width=\"32\"\n          height=\"4\"\n          rx=\"2\"\n          fill=\"var(--muted-foreground)\"\n          fillOpacity=\".15\"\n        />\n        <rect\n          x=\"24\"\n          y=\"60\"\n          width=\"40\"\n          height=\"4\"\n          rx=\"2\"\n          fill=\"var(--muted-foreground)\"\n          fillOpacity=\".15\"\n        />\n        <circle\n          cx=\"48\"\n          cy=\"20\"\n          r=\"8\"\n          fill=\"var(--muted)\"\n          stroke=\"var(--border)\"\n          strokeWidth=\"2\"\n        />\n        <circle\n          cx=\"48\"\n          cy=\"20\"\n          r=\"3\"\n          fill=\"var(--muted-foreground)\"\n          fillOpacity=\".3\"\n        />\n      </g>\n    </svg>\n  )\n}\n\nfunction SearchIllustration({ size }: { size: number }) {\n  return (\n    <svg\n      width={size}\n      height={size}\n      viewBox=\"0 0 96 96\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <style>{\"@keyframes pulse2{0%,100%{opacity:1}50%{opacity:.5}}\"}</style>\n      <circle\n        cx=\"40\"\n        cy=\"40\"\n        r=\"22\"\n        stroke=\"var(--muted-foreground)\"\n        strokeWidth=\"4\"\n        strokeOpacity=\".3\"\n      />\n      <line\n        x1=\"56\"\n        y1=\"56\"\n        x2=\"76\"\n        y2=\"76\"\n        stroke=\"var(--muted-foreground)\"\n        strokeWidth=\"4\"\n        strokeLinecap=\"round\"\n        strokeOpacity=\".3\"\n      />\n      <circle\n        cx=\"40\"\n        cy=\"40\"\n        r=\"12\"\n        fill=\"var(--muted)\"\n        style={{ animation: \"pulse2 2s ease-in-out infinite\" }}\n      />\n      <path\n        d=\"M36 40 Q40 34 44 40\"\n        stroke=\"var(--muted-foreground)\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeOpacity=\".4\"\n        fill=\"none\"\n      />\n    </svg>\n  )\n}\n\nfunction ErrorIllustration({ size }: { size: number }) {\n  return (\n    <svg\n      width={size}\n      height={size}\n      viewBox=\"0 0 96 96\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <style>\n        {\n          \"@keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-4px)}40%,80%{transform:translateX(4px)}}\"\n        }\n      </style>\n      <g style={{ animation: \"shake 2.5s ease-in-out infinite\" }}>\n        <circle\n          cx=\"48\"\n          cy=\"48\"\n          r=\"30\"\n          fill=\"var(--destructive)\"\n          fillOpacity=\".1\"\n        />\n        <circle\n          cx=\"48\"\n          cy=\"48\"\n          r=\"22\"\n          stroke=\"var(--destructive)\"\n          strokeWidth=\"3\"\n          strokeOpacity=\".4\"\n        />\n        <line\n          x1=\"38\"\n          y1=\"38\"\n          x2=\"58\"\n          y2=\"58\"\n          stroke=\"var(--destructive)\"\n          strokeWidth=\"4\"\n          strokeLinecap=\"round\"\n          strokeOpacity=\".6\"\n        />\n        <line\n          x1=\"58\"\n          y1=\"38\"\n          x2=\"38\"\n          y2=\"58\"\n          stroke=\"var(--destructive)\"\n          strokeWidth=\"4\"\n          strokeLinecap=\"round\"\n          strokeOpacity=\".6\"\n        />\n      </g>\n    </svg>\n  )\n}\n\nfunction SuccessIllustration({ size }: { size: number }) {\n  return (\n    <svg\n      width={size}\n      height={size}\n      viewBox=\"0 0 96 96\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <style>\n        {\n          \"@keyframes pop{0%{transform:scale(0.8)}50%{transform:scale(1.05)}100%{transform:scale(1)}}\"\n        }\n      </style>\n      <circle\n        cx=\"48\"\n        cy=\"48\"\n        r=\"30\"\n        fill=\"var(--success)\"\n        fillOpacity=\".1\"\n        style={{ animation: \"pop 0.6s ease-out forwards\" }}\n      />\n      <circle\n        cx=\"48\"\n        cy=\"48\"\n        r=\"22\"\n        stroke=\"var(--success)\"\n        strokeWidth=\"3\"\n        strokeOpacity=\".4\"\n      />\n      <polyline\n        points=\"34,48 44,58 62,38\"\n        stroke=\"var(--success)\"\n        strokeWidth=\"4\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeOpacity=\".7\"\n        fill=\"none\"\n      />\n    </svg>\n  )\n}\n\nfunction InboxIllustration({ size }: { size: number }) {\n  return (\n    <svg\n      width={size}\n      height={size}\n      viewBox=\"0 0 96 96\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <style>\n        {\n          \"@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-5px)}}\"\n        }\n      </style>\n      <g style={{ animation: \"float 3s ease-in-out infinite\" }}>\n        <rect x=\"14\" y=\"36\" width=\"68\" height=\"44\" rx=\"8\" fill=\"var(--muted)\" />\n        <rect\n          x=\"14\"\n          y=\"36\"\n          width=\"68\"\n          height=\"22\"\n          rx=\"8\"\n          fill=\"var(--muted-foreground)\"\n          fillOpacity=\".08\"\n        />\n        <path\n          d=\"M14 50 L48 64 L82 50\"\n          stroke=\"var(--muted-foreground)\"\n          strokeWidth=\"1.5\"\n          strokeOpacity=\".2\"\n        />\n        {/* envelope flap */}\n        <path\n          d=\"M14 36 L48 58 L82 36\"\n          fill=\"none\"\n          stroke=\"var(--muted-foreground)\"\n          strokeWidth=\"2\"\n          strokeOpacity=\".2\"\n        />\n        {/* star badge */}\n        <circle cx=\"72\" cy=\"28\" r=\"10\" fill=\"var(--chart-2)\" fillOpacity=\".8\" />\n        <text x=\"72\" y=\"32\" textAnchor=\"middle\" fontSize=\"12\" fill=\"white\">\n          ★\n        </text>\n      </g>\n    </svg>\n  )\n}\n\nfunction FilterIllustration({ size }: { size: number }) {\n  return (\n    <svg\n      width={size}\n      height={size}\n      viewBox=\"0 0 96 96\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <style>{\"@keyframes pulse2{0%,100%{opacity:1}50%{opacity:.4}}\"}</style>\n      <path\n        d=\"M20 28 H76 L56 52 V72 L40 80 V52 L20 28Z\"\n        fill=\"var(--muted)\"\n        stroke=\"var(--border)\"\n        strokeWidth=\"2\"\n      />\n      <line\n        x1=\"30\"\n        y1=\"38\"\n        x2=\"66\"\n        y2=\"38\"\n        stroke=\"var(--muted-foreground)\"\n        strokeWidth=\"2\"\n        strokeOpacity=\".3\"\n        strokeDasharray=\"4 3\"\n        style={{ animation: \"pulse2 2s ease-in-out infinite\" }}\n      />\n      <circle\n        cx=\"48\"\n        cy=\"62\"\n        r=\"4\"\n        fill=\"var(--muted-foreground)\"\n        fillOpacity=\".2\"\n      />\n    </svg>\n  )\n}\n\nfunction UploadIllustration({ size }: { size: number }) {\n  return (\n    <svg\n      width={size}\n      height={size}\n      viewBox=\"0 0 96 96\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <style>\n        {\n          \"/* intentional delight: bounce for empty-state */ @keyframes bounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}}\"\n        }\n      </style>\n      <rect\n        x=\"20\"\n        y=\"52\"\n        width=\"56\"\n        height=\"26\"\n        rx=\"6\"\n        fill=\"var(--muted)\"\n        stroke=\"var(--border)\"\n        strokeWidth=\"2\"\n      />\n      <rect\n        x=\"28\"\n        y=\"60\"\n        width=\"40\"\n        height=\"3\"\n        rx=\"1.5\"\n        fill=\"var(--muted-foreground)\"\n        fillOpacity=\".2\"\n      />\n      <rect\n        x=\"28\"\n        y=\"67\"\n        width=\"28\"\n        height=\"3\"\n        rx=\"1.5\"\n        fill=\"var(--muted-foreground)\"\n        fillOpacity=\".1\"\n      />\n      <g style={{ animation: \"bounce 1.5s ease-in-out infinite\" }}>\n        <line\n          x1=\"48\"\n          y1=\"46\"\n          x2=\"48\"\n          y2=\"20\"\n          stroke=\"var(--primary)\"\n          strokeWidth=\"3\"\n          strokeLinecap=\"round\"\n          strokeOpacity=\".6\"\n        />\n        <polyline\n          points=\"36,32 48,20 60,32\"\n          fill=\"none\"\n          stroke=\"var(--primary)\"\n          strokeWidth=\"3\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          strokeOpacity=\".6\"\n        />\n      </g>\n    </svg>\n  )\n}\n\nfunction LockedIllustration({ size }: { size: number }) {\n  return (\n    <svg\n      width={size}\n      height={size}\n      viewBox=\"0 0 96 96\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <style>\n        {\n          \"@keyframes sway{0%,100%{transform:rotate(0deg)}30%{transform:rotate(-5deg)}60%{transform:rotate(5deg)}}\"\n        }\n      </style>\n      <g style={{ animation: \"sway 3s ease-in-out infinite\" }}>\n        <rect\n          x=\"24\"\n          y=\"48\"\n          width=\"48\"\n          height=\"34\"\n          rx=\"8\"\n          fill=\"var(--muted)\"\n          stroke=\"var(--border)\"\n          strokeWidth=\"2\"\n        />\n        <path\n          d=\"M34 48 V36 Q34 18 48 18 Q62 18 62 36 V48\"\n          stroke=\"var(--muted-foreground)\"\n          strokeWidth=\"4\"\n          strokeOpacity=\".3\"\n          fill=\"none\"\n          strokeLinecap=\"round\"\n        />\n        <circle\n          cx=\"48\"\n          cy=\"65\"\n          r=\"6\"\n          fill=\"var(--muted-foreground)\"\n          fillOpacity=\".25\"\n        />\n        <rect\n          x=\"46\"\n          y=\"65\"\n          width=\"4\"\n          height=\"6\"\n          rx=\"1\"\n          fill=\"var(--muted-foreground)\"\n          fillOpacity=\".25\"\n        />\n      </g>\n    </svg>\n  )\n}\n\nconst ILLUSTRATIONS: Record<EmptyStateVariant, React.FC<{ size: number }>> = {\n  default: DefaultIllustration,\n  search: SearchIllustration,\n  error: ErrorIllustration,\n  success: SuccessIllustration,\n  inbox: InboxIllustration,\n  filter: FilterIllustration,\n  upload: UploadIllustration,\n  locked: LockedIllustration,\n}\n\n// ── Default titles ─────────────────────────────────────────────────────────\n\nconst DEFAULT_TITLE: Record<EmptyStateVariant, string> = {\n  default: \"Nothing here yet\",\n  search: \"No results found\",\n  error: \"Something went wrong\",\n  success: \"All done!\",\n  inbox: \"Your inbox is empty\",\n  filter: \"No items match your filters\",\n  upload: \"Drop files to upload\",\n  locked: \"Access restricted\",\n}\n\nconst DEFAULT_DESCRIPTION: Record<EmptyStateVariant, string> = {\n  default: \"This section is empty. Create or add content to get started.\",\n  search:\n    \"We couldn't find anything matching your search. Try different keywords.\",\n  error: \"An unexpected error occurred. Please try again or contact support.\",\n  success: \"Everything is complete. There's nothing left to do here.\",\n  inbox: \"No new messages. You're all caught up.\",\n  filter: \"Try removing some filters to see more results.\",\n  upload: \"Drag and drop your files here, or click to browse.\",\n  locked: \"You don't have permission to view this content.\",\n}\n\n// ── EmptyStateIllustration ─────────────────────────────────────────────────\n\nexport function EmptyStateIllustration({\n  variant = \"default\",\n  title,\n  description,\n  action,\n  size = \"md\",\n  loading = false,\n  illustration,\n  className,\n  ...props\n}: EmptyStateIllustrationProps) {\n  // ── Loading ─────────────────────────────────────────────────────────────\n\n  if (loading) {\n    const d = skeletonDims[size]\n    return (\n      <div\n        className={cn(emptyStateContainerVariants({ size }), className)}\n        aria-hidden=\"true\"\n      >\n        <Skeleton className={cn(\"rounded-full\", d.illus)} />\n        <div className=\"flex flex-col items-center gap-2\">\n          <Skeleton className={d.title} />\n          <Skeleton className={d.desc} />\n        </div>\n      </div>\n    )\n  }\n\n  const Illustration = ILLUSTRATIONS[variant]\n  const displayTitle = title ?? DEFAULT_TITLE[variant]\n  const displayDescription = description ?? DEFAULT_DESCRIPTION[variant]\n\n  return (\n    <div\n      className={cn(emptyStateContainerVariants({ size }), className)}\n      role=\"status\"\n      aria-label={displayTitle}\n      {...props}\n    >\n      {/* Illustration */}\n      <div\n        className={cn(\n          \"flex items-center justify-center\",\n          size === \"sm\" && \"size-16\",\n          size === \"md\" && \"size-24\",\n          size === \"lg\" && \"size-32\"\n        )}\n        aria-hidden=\"true\"\n      >\n        {illustration ?? <Illustration size={svgSize[size]} />}\n      </div>\n\n      {/* Text */}\n      <div className=\"flex flex-col items-center gap-1.5\">\n        <h3 className={emptyStateTitleVariants({ size })}>{displayTitle}</h3>\n        <p className={emptyStateDescriptionVariants({ size })}>\n          {displayDescription}\n        </p>\n      </div>\n\n      {/* Action slot */}\n      {action && <div className=\"mt-1\">{action}</div>}\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}
