{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pricing-landing-hero",
  "type": "registry:ui",
  "title": "Pricing Landing Hero",
  "description": "A service-style landing hero with scattered preview cards, price block with strike-through, scarcity line, dual CTAs, and trusted-by strip.",
  "files": [
    {
      "path": "registry/ruixenui/pricing-landing-hero.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport Link from \"next/link\";\nimport { Battery, Signal, Wifi } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\n/* ── types ───────────────────────────────────────────────────── */\n\ntype Action = { href: string; label: string };\n\ninterface PriceBlock {\n  /** Current price, e.g. \"$850\" */\n  current: string;\n  /** Original price shown with strike-through, e.g. \"$2,000\" */\n  original?: string;\n}\n\ninterface TrustedBySection {\n  heading?: string;\n  logos: React.ReactNode[];\n}\n\ninterface PhoneSheetItem {\n  title: string;\n  description: string;\n}\n\ninterface PhoneMockupProps {\n  /** Time shown in the status bar. */\n  time?: string;\n  /**\n   * Items to cycle through inside the sheet. When more than one is provided\n   * they crossfade automatically every `cycleMs` milliseconds.\n   */\n  items?: PhoneSheetItem[];\n  /** Interval between item transitions (ms). */\n  cycleMs?: number;\n}\n\nexport interface PricingLandingHeroProps {\n  /** Main headline */\n  title: React.ReactNode;\n  /** Subtitle / description text */\n  description?: string;\n  /** Phone mockup shown above the headline */\n  phone?: PhoneMockupProps;\n  /** Price block shown between description and CTAs */\n  price?: PriceBlock;\n  /** Small availability / scarcity line above the CTAs */\n  availability?: string;\n  /** Primary CTA button (filled) */\n  primaryAction?: Action;\n  /** Secondary CTA button (outlined) */\n  secondaryAction?: Action;\n  /** Trusted-by logo strip */\n  trustedBy?: TrustedBySection;\n  className?: string;\n}\n\n/* ── phone mockup ────────────────────────────────────────────── */\n\nconst DEFAULT_ITEMS: PhoneSheetItem[] = [\n  { title: \"Add a Title...\", description: \"Add a description...\" },\n];\n\nconst SPRING = \"cubic-bezier(0.22, 1, 0.36, 1)\";\n\nfunction SheetCardContent({ item }: { item: PhoneSheetItem }) {\n  return (\n    <>\n      {/* Drag handle bar — part of each card, slides with it */}\n      <div className=\"mx-auto h-1 w-12 rounded-full bg-foreground/15\" />\n      <div className=\"mt-6 text-center text-2xl font-black text-foreground/50\">\n        {item.title}\n      </div>\n      <div className=\"mt-3 text-center text-sm text-foreground/50\">\n        {item.description}\n      </div>\n    </>\n  );\n}\n\n/**\n * A sheet card that mounts at translateY(100%) and springs to 0 — used as\n * the \"top\" layer that slides over the previous card. Remounted via `key`\n * each tick so the animation replays.\n */\nfunction IncomingSheetCard({ item }: { item: PhoneSheetItem }) {\n  const [raised, setRaised] = React.useState(false);\n\n  React.useEffect(() => {\n    // Double rAF ensures the initial translateY(100%) paint lands before\n    // we flip to translateY(0) — otherwise the browser skips the transition.\n    let cancelled = false;\n    let raf2 = 0;\n    const raf1 = requestAnimationFrame(() => {\n      if (cancelled) return;\n      raf2 = requestAnimationFrame(() => {\n        if (!cancelled) setRaised(true);\n      });\n    });\n    return () => {\n      cancelled = true;\n      cancelAnimationFrame(raf1);\n      cancelAnimationFrame(raf2);\n    };\n  }, []);\n\n  return (\n    <div\n      className=\"absolute inset-0 bg-background px-6 pt-3\"\n      style={{\n        zIndex: 20,\n        transform: raised ? \"translateY(0)\" : \"translateY(100%)\",\n        transition: `transform 0.95s ${SPRING}`,\n        willChange: \"transform\",\n      }}\n    >\n      <SheetCardContent item={item} />\n    </div>\n  );\n}\n\nfunction AnimatedPhoneMockup({\n  time = \"9:41\",\n  items = DEFAULT_ITEMS,\n  cycleMs = 2800,\n}: PhoneMockupProps) {\n  const [idx, setIdx] = React.useState(0);\n  const [mounted, setMounted] = React.useState(false);\n\n  // Sheet rise on mount\n  React.useEffect(() => {\n    const t = setTimeout(() => setMounted(true), 120);\n    return () => clearTimeout(t);\n  }, []);\n\n  // Cycle items\n  React.useEffect(() => {\n    if (items.length <= 1) return;\n    const id = setInterval(\n      () => setIdx((i) => (i + 1) % items.length),\n      cycleMs,\n    );\n    return () => clearInterval(id);\n  }, [items.length, cycleMs]);\n\n  const prevIdx = idx === 0 ? items.length - 1 : idx - 1;\n\n  return (\n    <div\n      aria-hidden=\"true\"\n      className=\"relative mx-auto w-[23rem] max-w-full px-4 pt-2 [mask-image:linear-gradient(to_bottom,black_75%,transparent)]\"\n    >\n      {/* Outer bezel */}\n      <div className=\"mx-auto overflow-hidden rounded-t-[2.5rem] border border-transparent bg-background/75 px-2 pt-2 shadow-md shadow-black/[0.06] ring-1 ring-foreground/10\">\n        {/* Screen */}\n        <div className=\"overflow-hidden rounded-t-[2rem] bg-foreground/[0.04] px-6 shadow shadow-black/[0.06] ring-1 ring-foreground/10 dark:bg-black\">\n          {/* Status bar */}\n          <div className=\"flex items-center justify-between py-2 pl-4 text-xs\">\n            <span className=\"font-semibold\">{time}</span>\n            <div className=\"flex items-end gap-1\">\n              <Signal aria-hidden className=\"size-4\" />\n              <Wifi aria-hidden className=\"size-[18px]\" />\n              <Battery aria-hidden className=\"-mb-px size-5\" />\n            </div>\n          </div>\n\n          {/* Sheet backdrop */}\n          <div className=\"relative mt-2 before:absolute before:-inset-x-3 before:-top-2 before:bottom-0 before:rounded-t-[1.5rem] before:bg-background/60 dark:before:bg-background/30\">\n            {/* Rising sheet */}\n            <div\n              className=\"relative -mx-6 overflow-hidden rounded-t-[2rem] bg-background pb-32 shadow-[0_-12px_40px_-16px_rgba(0,0,0,0.08)]\"\n              style={{\n                transform: mounted ? \"translateY(0)\" : \"translateY(38%)\",\n                opacity: mounted ? 1 : 0,\n                transition: `transform 1.1s ${SPRING}, opacity 0.7s ease-out`,\n              }}\n            >\n              {/* Stacked sheet cards — new card slides up over previous,\n                  drag-handle bar slides as part of each card */}\n              <div className=\"relative h-[160px] overflow-hidden\">\n                {/* Backdrop: the previous card, static at rest */}\n                <div\n                  className=\"absolute inset-0 bg-background px-6 pt-3\"\n                  style={{ zIndex: 10 }}\n                >\n                  <SheetCardContent item={items[prevIdx]} />\n                </div>\n                {/* Incoming: current card, slides up from below on each tick */}\n                <IncomingSheetCard key={idx} item={items[idx]} />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\n/* ── component ───────────────────────────────────────────────── */\n\nexport function PricingLandingHero({\n  title,\n  description,\n  phone,\n  price,\n  availability,\n  primaryAction,\n  secondaryAction,\n  trustedBy,\n  className,\n}: PricingLandingHeroProps) {\n  return (\n    <section\n      className={cn(\"relative w-full bg-background\", className)}\n      aria-label=\"Pricing hero\"\n    >\n      <div className=\"mx-auto max-w-5xl px-6 pb-16 pt-20 text-center md:pt-28 lg:px-8\">\n        {/* ── phone mockup ─────────────────────────────────── */}\n        <div className=\"mb-10 md:mb-14\">\n          <AnimatedPhoneMockup\n            time={phone?.time}\n            items={phone?.items}\n            cycleMs={phone?.cycleMs}\n          />\n        </div>\n\n        {/* ── heading + description ────────────────────────── */}\n        <h1 className=\"mx-auto max-w-3xl text-balance text-5xl font-semibold tracking-tight text-foreground md:text-6xl lg:text-7xl lg:leading-[1.05]\">\n          {title}\n        </h1>\n\n        {description && (\n          <p className=\"mx-auto mt-6 max-w-xl text-balance text-base text-muted-foreground md:text-lg\">\n            {description}\n          </p>\n        )}\n\n        {/* ── price block ──────────────────────────────────── */}\n        {price && (\n          <div className=\"mt-12 flex items-baseline justify-center gap-3 md:mt-16\">\n            <span className=\"text-5xl font-semibold tracking-tight text-foreground md:text-6xl\">\n              {price.current}\n            </span>\n            {price.original && (\n              <span className=\"text-3xl font-medium text-muted-foreground/70 line-through md:text-4xl\">\n                {price.original}\n              </span>\n            )}\n          </div>\n        )}\n\n        {/* ── availability ─────────────────────────────────── */}\n        {availability && (\n          <p className=\"mt-6 text-sm text-muted-foreground\">{availability}</p>\n        )}\n\n        {/* ── CTAs ─────────────────────────────────────────── */}\n        {(primaryAction || secondaryAction) && (\n          <div className=\"mt-4 flex flex-wrap items-center justify-center gap-3\">\n            {primaryAction && (\n              <Link\n                href={primaryAction.href}\n                className=\"inline-flex h-11 items-center justify-center rounded-full bg-foreground px-6 text-sm font-medium text-background shadow-md shadow-black/10 transition-colors hover:bg-foreground/90\"\n              >\n                {primaryAction.label}\n              </Link>\n            )}\n            {secondaryAction && (\n              <Link\n                href={secondaryAction.href}\n                className=\"inline-flex h-11 items-center justify-center rounded-full border border-foreground/15 bg-background px-6 text-sm font-medium text-foreground transition-colors hover:bg-muted/60\"\n              >\n                {secondaryAction.label}\n              </Link>\n            )}\n          </div>\n        )}\n\n        {/* ── trusted-by strip ─────────────────────────────── */}\n        {trustedBy && trustedBy.logos.length > 0 && (\n          <div className=\"mt-20 md:mt-24\">\n            <p className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground\">\n              {trustedBy.heading || \"Trusted by\"}\n            </p>\n            <div className=\"mt-6 flex flex-wrap items-center justify-center gap-10 text-foreground/40 md:gap-16 [&_svg]:fill-foreground/40\">\n              {trustedBy.logos.map((logo, idx) => (\n                <div key={idx} className=\"flex items-center justify-center\">\n                  {logo}\n                </div>\n              ))}\n            </div>\n          </div>\n        )}\n      </div>\n    </section>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ruixen/pricing-landing-hero.tsx"
    }
  ]
}