{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "empty",
  "title": "Empty",
  "author": "AniBT <https://anibt.net>",
  "description": "AniBT empty state: a framed well, or A's tilted paper note with your picture peeking over its edge between twinkling sparkles; icon, image and peek media slots.",
  "dependencies": [
    "class-variance-authority",
    "lucide-react"
  ],
  "registryDependencies": [
    "@anibt/theme"
  ],
  "files": [
    {
      "path": "registry/anibt/ui/empty.tsx",
      "content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { HeartIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/**\n * Empty — the AniBT 「贴纸手账」 empty state.\n *\n * Three frames:\n * - `well` (default): a group inside a panel — the 1px edge, radius 10,\n *   centred copy. The everyday \"nothing here yet\".\n * - `note`: A's empty state — a paper note tilted −1°, on the pop shadow, with\n *   a picture peeking over its top edge (`EmptyMedia variant=\"peek\"`) between\n *   two twinkling ✦. For the one big empty on a page.\n * - `plain`: no frame, for a place that already has one.\n *\n * The media slot takes an icon (`variant=\"icon\"`: a small white sticker,\n * tilted, the glyph in pink), a picture (`variant=\"image\"`), or the peeking\n * picture of a note (`variant=\"peek\"`). Bring your own picture — a mascot, an\n * illustration; the component draws the frame, never the art.\n *\n * The parts are shadcn's (`Empty`, `EmptyHeader`, `EmptyMedia`, `EmptyTitle`,\n * `EmptyDescription`, `EmptyContent`).\n */\nconst emptyVariants = cva(\"flex min-w-0 flex-col items-center text-center text-balance\", {\n  variants: {\n    variant: {\n      well: \"anibt-well w-full flex-1 justify-center gap-4 px-6\",\n      note: \"w-full flex-1 justify-center px-4\",\n      plain: \"w-full flex-1 justify-center gap-4 px-6\",\n    },\n    size: {\n      default: \"\",\n      sm: \"\",\n    },\n  },\n  compoundVariants: [\n    { variant: \"well\", size: \"default\", class: \"py-10\" },\n    { variant: \"well\", size: \"sm\", class: \"py-6\" },\n    { variant: \"plain\", size: \"default\", class: \"py-10\" },\n    { variant: \"plain\", size: \"sm\", class: \"py-6\" },\n    { variant: \"note\", size: \"default\", class: \"pt-28 pb-10\" },\n    { variant: \"note\", size: \"sm\", class: \"pt-24 pb-6\" },\n  ],\n  defaultVariants: {\n    variant: \"well\",\n    size: \"default\",\n  },\n})\n\nfunction Empty({\n  className,\n  variant,\n  size,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof emptyVariants>) {\n  const frame = variant ?? \"well\"\n  return (\n    <div\n      data-slot=\"empty\"\n      data-variant={frame}\n      data-size={size ?? \"default\"}\n      className={cn(emptyVariants({ variant, size }), className)}\n      {...props}\n    >\n      {frame === \"note\" ? (\n        <div\n          data-slot=\"empty-note\"\n          className=\"anibt-pop relative flex w-full max-w-[340px] -rotate-1 flex-col items-center gap-3 px-4 pt-5 pb-4 transition-transform duration-500 ease-anibt-spring motion-safe:hover:rotate-0\"\n        >\n          {children}\n        </div>\n      ) : (\n        children\n      )}\n    </div>\n  )\n}\n\nfunction EmptyHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"empty-header\"\n      className={cn(\"flex max-w-sm flex-col items-center gap-1.5 text-center\", className)}\n      {...props}\n    />\n  )\n}\n\nconst emptyMediaVariants = cva(\n  \"relative flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: \"mb-1 bg-transparent text-muted-foreground\",\n        // A's section mark: a small white sticker, tilted, the glyph in pink.\n        icon: \"anibt-rim mb-2 size-10 -rotate-3 rounded-[10px] bg-card text-anibt-mark [&_svg:not([class*='size-'])]:size-5\",\n        image: \"mb-2 [&_img]:h-auto [&_img]:max-w-full\",\n        // Peeks over the top edge of an `Empty variant=\"note\"`: the picture\n        // stands on the note's edge and its lower 28px are hidden behind it.\n        peek: \"pointer-events-none absolute bottom-full left-1/2 -translate-x-1/2 translate-y-7 [clip-path:inset(-64px_-64px_28px_-64px)] [&_img]:block [&_img]:max-w-none\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nfunction EmptyMedia({\n  className,\n  variant = \"default\",\n  sparkles,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> &\n  VariantProps<typeof emptyMediaVariants> & {\n    /** The ✦ ✦ ♥ around a peeking picture. Default on for `peek`, off otherwise. */\n    sparkles?: boolean\n  }) {\n  const decorate = sparkles ?? variant === \"peek\"\n  return (\n    <div\n      data-slot=\"empty-icon\"\n      data-variant={variant}\n      aria-hidden={variant === \"peek\" ? true : undefined}\n      className={cn(emptyMediaVariants({ variant }), className)}\n      {...props}\n    >\n      {variant === \"peek\" ? <div className=\"animate-anibt-peek\">{children}</div> : children}\n      {decorate ? <EmptySparkles /> : null}\n    </div>\n  )\n}\n\n/** Two ✦ and a small heart round the media. Decorative. */\nfunction EmptySparkles() {\n  const sparkle =\n    \"pointer-events-none absolute inline-block animate-anibt-twinkle [-webkit-mask:var(--anibt-sparkle)_center/contain_no-repeat] [mask:var(--anibt-sparkle)_center/contain_no-repeat]\"\n  return (\n    <>\n      <span aria-hidden=\"true\" className={cn(sparkle, \"top-5 -left-7 size-4 bg-anibt-mark\")} />\n      <span\n        aria-hidden=\"true\"\n        className={cn(sparkle, \"top-1 -right-6 size-3 bg-anibt-time [animation-delay:0.8s]\")}\n      />\n      <HeartIcon\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute top-12 -right-9 size-3 animate-anibt-bob fill-current text-anibt-mark\"\n      />\n    </>\n  )\n}\n\n/** 15/700 ink. */\nfunction EmptyTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"empty-title\"\n      className={cn(\"text-[15px] leading-[1.45] font-bold text-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\n/** 13, muted; links underline and turn pink. */\nfunction EmptyDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"empty-description\"\n      className={cn(\n        \"text-[13px] leading-relaxed text-muted-foreground [&>a]:text-accent-foreground [&>a]:underline [&>a]:underline-offset-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/** The actions: one candy button at most, a quiet one beside it. */\nfunction EmptyContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"empty-content\"\n      className={cn(\n        \"flex w-full max-w-sm min-w-0 flex-wrap items-center justify-center gap-2 text-[13px] text-balance\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia, emptyVariants }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "feedback"
  ],
  "type": "registry:ui"
}