{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "feature9",
  "title": "Three-Step How-To",
  "description": "Three side-by-side step cards with a media surface anchored to the bottom of each card. Each slot accepts an image, video, or live registry-component asset.",
  "dependencies": [
    "clsx",
    "tailwind-merge",
    "lucide-react"
  ],
  "registryDependencies": [
    "badge"
  ],
  "files": [
    {
      "path": "registry/feature9/feature9.tsx",
      "content": "\"use client\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Calendar1 } from \"@/components/beste/piece/calendar1\";\nimport { Terminal2 } from \"@/components/beste/piece/terminal2\";\nimport { Upload1 } from \"@/components/beste/piece/upload1\";\nimport { cn } from \"@/lib/utils\";\n\ntype ComponentBackground =\n  | { type: \"dots\" }\n  | { type: \"none\" }\n  | { type: \"image\"; src: string; alt?: string }\n  | { type: \"video\"; src: string; alt?: string };\n\ntype StepMedia =\n  | { type: \"image\"; src: string; alt?: string }\n  | { type: \"video\"; src: string; alt?: string }\n  | {\n      type: \"component\";\n      component: React.ReactNode;\n      background?: ComponentBackground;\n    };\n\ninterface StepCard {\n  title: string;\n  description: string;\n  media: StepMedia;\n}\n\ninterface Feature9Props {\n  badge?: {\n    label: string;\n    variant?: \"default\" | \"secondary\" | \"outline\";\n  };\n  heading?: string;\n  description?: string;\n  cards?: StepCard[];\n  className?: string;\n}\n\nexport const feature9Demo: Feature9Props = {\n  badge: { label: \"How it works\", variant: \"outline\" },\n  heading: \"Ship your next release in three steps\",\n  description:\n    \"Each step shows the surface you'll touch — a real asset, not a mockup — so the workflow stays grounded in what your team actually sees.\",\n  cards: [\n    {\n      title: \"Drop in your assets\",\n      description:\n        \"Upload designs, brand guidelines, or reference images. The upload card tracks progress so the team knows when it's safe to start.\",\n      media: {\n        type: \"component\",\n        component: <Upload1 filename=\"brand-guidelines.pdf\" size=\"12.4 MB\" progress={68} />,\n      },\n    },\n    {\n      title: \"Plan the release window\",\n      description:\n        \"A single calendar tile keeps the launch date front and center. Tone reflects whether you're on track or running hot.\",\n      media: {\n        type: \"component\",\n        background: { type: \"none\" },\n        component: <Calendar1 month=\"APR\" day=\"21\" weekday=\"Tuesday\" tone=\"primary\" />,\n      },\n    },\n    {\n      title: \"Ship the change\",\n      description:\n        \"One copy-and-paste command pushes the release. The terminal pill drops into changelogs and onboarding docs without extra wiring.\",\n      media: {\n        type: \"component\",\n        component: <Terminal2 command=\"pnpm dlx shadcn@latest add feature9\" />,\n      },\n    },\n  ],\n};\n\nexport function Feature9({ badge, heading, description, cards = [], className }: Feature9Props) {\n  return (\n    <section className={cn(\"py-16 md:py-24 w-full\", className)}>\n      <div className=\"mx-auto max-w-6xl px-4 md:px-6\">\n        <div className=\"mx-auto mb-9 max-w-2xl text-center\">\n          {badge && (\n            <div className=\"mb-4 flex justify-center\">\n              <Badge variant={badge.variant ?? \"default\"}>{badge.label}</Badge>\n            </div>\n          )}\n          {heading && (\n            <h2 className=\"mb-4 text-2xl font-semibold tracking-tight md:text-4xl\">{heading}</h2>\n          )}\n          {description && (\n            <p className=\"text-base md:text-lg text-muted-foreground\">{description}</p>\n          )}\n        </div>\n\n        <div className=\"flex flex-col items-stretch justify-center gap-6 md:flex-row\">\n          {cards.map((card, index) => (\n            <article\n              key={index}\n              className=\"flex w-full flex-col gap-3 rounded-md border bg-muted/30 p-6 md:w-[350px]\"\n            >\n              <h3 className=\"text-base font-medium md:text-lg\">{card.title}</h3>\n              <p className=\"text-sm text-muted-foreground md:text-base\">{card.description}</p>\n              <div className=\"mt-auto pt-4\">\n                <StepMediaSurface media={card.media} />\n              </div>\n            </article>\n          ))}\n        </div>\n      </div>\n    </section>\n  );\n}\n\nfunction StepMediaSurface({ media }: { media: StepMedia }) {\n  const componentBackground =\n    media.type === \"component\" ? (media.background ?? { type: \"dots\" }) : null;\n  const showDots = componentBackground?.type === \"dots\";\n\n  return (\n    <div\n      className=\"relative flex aspect-[4/3] items-center justify-center overflow-hidden rounded-md border bg-card\"\n      style={\n        showDots\n          ? {\n              backgroundImage: \"radial-gradient(circle, var(--border) 1px, transparent 1px)\",\n              backgroundSize: \"16px 16px\",\n            }\n          : undefined\n      }\n    >\n      {componentBackground?.type === \"image\" && (\n        <img\n          className=\"absolute inset-0 size-full object-cover\"\n          src={componentBackground.src}\n          alt={componentBackground.alt ?? \"\"}\n          aria-hidden=\"true\"\n        />\n      )}\n      {componentBackground?.type === \"video\" && (\n        <video\n          src={componentBackground.src}\n          autoPlay\n          loop\n          muted\n          playsInline\n          className=\"absolute inset-0 size-full object-cover\"\n        />\n      )}\n\n      {media.type === \"video\" ? (\n        <video\n          src={media.src}\n          autoPlay\n          loop\n          muted\n          playsInline\n          aria-label={media.alt}\n          className=\"size-full object-cover\"\n        />\n      ) : media.type === \"image\" ? (\n        <img\n          className=\"absolute inset-0 size-full object-cover\"\n          src={media.src}\n          alt={media.alt ?? \"\"}\n        />\n      ) : (\n        <div className=\"relative flex size-full items-center justify-center\">{media.component}</div>\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/beste/block/feature9.tsx"
    },
    {
      "path": "registry-pieces/calendar1/calendar1.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype Surface = \"card\" | \"glass\";\n\ntype Tone = \"foreground\" | \"primary\" | \"destructive\";\n\ninterface Calendar1Props {\n  month?: string;\n  day?: string;\n  weekday?: string;\n  tone?: Tone;\n  surface?: Surface;\n  bordered?: boolean;\n  inverted?: boolean;\n  className?: string;\n}\n\nconst toneStripClasses: Record<Tone, string> = {\n  foreground: \"bg-foreground text-background\",\n  primary: \"bg-primary text-primary-foreground\",\n  destructive: \"bg-destructive text-white\",\n};\n\n\n/* The card sets the colour and everything inside it is drawn in `current`, so\n   inverting is two classes rather than a condition on every element.\n   `glass` is a deliberate exception to the solid-surface rule: these pieces sit\n   over section background images, and a frosted panel is the point of it. */\nconst surfaceClasses: Record<Surface, { plain: string; inverted: string }> = {\n  card: {\n    plain: \"bg-card text-card-foreground\",\n    inverted: \"bg-foreground text-background\",\n  },\n  glass: {\n    plain: \"bg-card/60 text-card-foreground backdrop-blur-md\",\n    inverted: \"bg-foreground/60 text-background backdrop-blur-md\",\n  },\n};\n\nexport const calendar1Demo: Calendar1Props = {\n  surface: \"card\",\n  bordered: true,\n  inverted: false,\n  month: \"APR\",\n  day: \"21\",\n  weekday: \"Tuesday\",\n  tone: \"destructive\",\n};\n\nexport function Calendar1({\n  month,\n  day,\n  weekday,\n  tone = \"destructive\",\n  surface = \"card\",\n  bordered = true,\n  inverted = false,\n  className,\n}: Calendar1Props) {\n  const surfaceTone = surfaceClasses[surface][inverted ? \"inverted\" : \"plain\"];\n\n  return (\n    <div\n      className={cn(\n        \"relative flex size-full items-center justify-center p-4\",\n        className\n      )}\n    >\n      <div className={cn(\"flex w-24 flex-col overflow-hidden rounded-lg shadow-sm\", surfaceTone, bordered && \"border border-current/15\")}>\n        <div\n          className={cn(\n            \"flex h-5 items-center justify-center text-xs font-bold uppercase tracking-wider\",\n            toneStripClasses[tone]\n          )}\n        >\n          {month}\n        </div>\n        <div className=\"flex flex-col items-center gap-0.5 px-2 py-2\">\n          <span className=\"text-3xl font-bold leading-none tabular-nums\">\n            {day}\n          </span>\n          <span className=\"text-xs font-medium uppercase tracking-wide text-current/60\">\n            {weekday}\n          </span>\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/beste/piece/calendar1.tsx"
    },
    {
      "path": "registry-pieces/terminal2/terminal2.tsx",
      "content": "\"use client\";\n\nimport { Check, Copy } from \"lucide-react\";\nimport { useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface Terminal2Props {\n  command?: string;\n  className?: string;\n}\n\nexport const terminal2Demo: Terminal2Props = {\n  command: \"pnpm dlx shadcn@latest add button\",\n};\n\nexport function Terminal2({ command = \"\", className }: Terminal2Props) {\n  const [copied, setCopied] = useState(false);\n\n  const handleCopy = async () => {\n    try {\n      await navigator.clipboard.writeText(command);\n      setCopied(true);\n      setTimeout(() => setCopied(false), 1400);\n    } catch {\n      /* noop */\n    }\n  };\n\n  return (\n    <div\n      className={cn(\n        \"relative flex size-full items-center justify-center p-4\",\n        className\n      )}\n    >\n      <div className=\"flex w-full max-w-72 items-center gap-2 rounded-lg border border-border bg-muted py-1.5 pl-3 pr-1.5 shadow-sm\">\n        <span\n          className=\"shrink-0 select-none text-xs font-medium text-muted-foreground\"\n          aria-hidden=\"true\"\n        >\n          ›\n        </span>\n        <code className=\"flex-1 truncate font-mono text-xs text-card-foreground\">\n          {command}\n        </code>\n        <button\n          type=\"button\"\n          onClick={handleCopy}\n          className=\"flex size-7 shrink-0 items-center justify-center rounded-md border border-transparent text-muted-foreground transition-colors hover:border-border hover:bg-card hover:text-card-foreground\"\n          aria-label={copied ? \"Copied\" : \"Copy command\"}\n        >\n          {copied ? (\n            <Check className=\"size-3.5 text-emerald-500\" />\n          ) : (\n            <Copy className=\"size-3.5\" />\n          )}\n        </button>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/beste/piece/terminal2.tsx"
    },
    {
      "path": "registry-pieces/upload1/upload1.tsx",
      "content": "\"use client\";\n\nimport { FileText } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\ntype Surface = \"card\" | \"glass\";\n\ninterface Upload1Props {\n  filename?: string;\n  size?: string;\n  progress?: number;\n  surface?: Surface;\n  bordered?: boolean;\n  inverted?: boolean;\n  className?: string;\n}\n\n\n/* The card sets the colour and everything inside it is drawn in `current`, so\n   inverting is two classes rather than a condition on every element.\n   `glass` is a deliberate exception to the solid-surface rule: these pieces sit\n   over section background images, and a frosted panel is the point of it. */\nconst surfaceClasses: Record<Surface, { plain: string; inverted: string }> = {\n  card: {\n    plain: \"bg-card text-card-foreground\",\n    inverted: \"bg-foreground text-background\",\n  },\n  glass: {\n    plain: \"bg-card/60 text-card-foreground backdrop-blur-md\",\n    inverted: \"bg-foreground/60 text-background backdrop-blur-md\",\n  },\n};\n\nexport const upload1Demo: Upload1Props = {\n  surface: \"card\",\n  bordered: true,\n  inverted: false,\n  filename: \"brand-guidelines.pdf\",\n  size: \"12.4 MB\",\n  progress: 68,\n};\n\nexport function Upload1({\n  filename,\n  size,\n  progress = 0,\n  surface = \"card\",\n  bordered = true,\n  inverted = false,\n  className,\n}: Upload1Props) {\n  const pct = Math.max(0, Math.min(100, progress));\n\n  const surfaceTone = surfaceClasses[surface][inverted ? \"inverted\" : \"plain\"];\n\n  return (\n    <div\n      className={cn(\n        \"relative flex size-full items-center justify-center p-4\",\n        className\n      )}\n    >\n      <div\n        className={cn(\n          \"flex w-full max-w-60 flex-col gap-2 rounded-lg p-3 shadow-sm\",\n          surfaceTone,\n          bordered && \"border border-current/15\"\n        )}\n      >\n        <div className=\"flex items-center gap-2.5\">\n          <div className=\"flex size-9 shrink-0 items-center justify-center rounded-md bg-sky-500/10 text-sky-600 dark:text-sky-400\">\n            <FileText className=\"size-4\" aria-hidden=\"true\" />\n          </div>\n          <div className=\"flex min-w-0 flex-1 flex-col\">\n            <span className=\"truncate text-xs font-medium\">\n              {filename}\n            </span>\n            <span className=\"text-xs text-current/60\">\n              {size} · {pct}%\n            </span>\n          </div>\n        </div>\n        <div className=\"h-1 w-full overflow-hidden rounded-full bg-current/10\">\n          <div\n            className=\"h-full rounded-full bg-sky-500 transition-all\"\n            style={{ width: `${pct}%` }}\n          />\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/beste/piece/upload1.tsx"
    }
  ],
  "type": "registry:block"
}