{
  "id": "product-launch",
  "name": "SaaS product launch page",
  "description": "A complete product launch page adapted from Leopoldo Miranda’s MIT template: layered hero cards, product capabilities, services, workflow, customer perspectives, exact-price billing choices, host-connected updates and native FAQ disclosures.",
  "category": "Marketing",
  "blockIds": [],
  "tags": [
    "product launch",
    "saas landing",
    "startup",
    "complete page",
    "pricing",
    "newsletter",
    "产品发布",
    "SaaS官网",
    "定价",
    "完整页面"
  ],
  "searchCategories": [
    "marketing"
  ],
  "featured": true,
  "style": "clean-saas",
  "registryId": "recipe-product-launch",
  "componentName": "ProductLaunchRecipe",
  "componentSource": "content/recipe-components/product-launch.tsx",
  "source": {
    "name": "Shadcn/UI Landing Page Template",
    "url": "https://github.com/leoMirandaa/shadcn-landing-page",
    "license": "MIT",
    "author": "Leopoldo Miranda",
    "revision": "ae3716dbce5c8a26815222bb743994b5717938f1",
    "licenseFile": "licenses/third-party/shadcn-landing-MIT.txt",
    "files": [
      "src/App.tsx",
      "src/components/Navbar.tsx",
      "src/components/Hero.tsx",
      "src/components/HeroCards.tsx",
      "src/components/Sponsors.tsx",
      "src/components/About.tsx",
      "src/components/Statistics.tsx",
      "src/components/HowItWorks.tsx",
      "src/components/Features.tsx",
      "src/components/Services.tsx",
      "src/components/Cta.tsx",
      "src/components/Testimonials.tsx",
      "src/components/Pricing.tsx",
      "src/components/Newsletter.tsx",
      "src/components/FAQ.tsx",
      "src/components/Footer.tsx",
      "src/App.css",
      "src/index.css"
    ],
    "modifications": [
      "Adapted the original complete Vite/React page composition and layered hero-card visual structure into one self-contained installable React/Tailwind 4 page.",
      "Replaced Radix primitives, global CSS and theme provider with native disclosures, scoped Tailwind utilities and a host-inheriting theme; no Next.js imports, fonts or new dependencies.",
      "Replaced upstream PNG illustrations and hand-drawn Icons.tsx paths because README and assets supplied no separate asset provenance or license; no upstream PNG or SVG paths are redistributed. The new product-launch-art.tsx SVG primitives are original code authored for this adaptation. The hero uses the existing lucide Lightbulb.",
      "Removed external avatars and customer brands; initials and actual host-supplied testimonials replace them. Fictional business records appear only with explicit mode=\"demo\".",
      "Added typed live host content, exact monthly/annual display prices, controlled billing period, awaited subscription and plan callbacks, pending/error/retry states and safe disconnected defaults. Demo callbacks never contact host services.",
      "Changed fixed/absolute hero-card layout to an intrinsically sized staggered grid and added mobile/long-content/reduced-motion handling."
    ]
  },
  "installUsage": "import ProductLaunchRecipe from \"@/components/uiblocks/recipe-product-launch/recipe-product-launch\";\n\nexport default function ProductPage() {\n  return (\n    <ProductLaunchRecipe\n      brand=\"Your product\"\n      primaryHref=\"/app\"\n      copy={{ title: \"Your actual product promise.\", accent: \"A clear next step.\", description: \"Describe the product you really provide.\" }}\n      features={[{ id: \"actual-feature\", title: \"Your capability\", description: \"Explain the actual workflow.\", illustration: \"growth\" }]}\n      plans={[{ id: \"team\", name: \"Your team plan\", description: \"Your verified plan description\", prices: { monthly: { amount: \"Your actual price\", period: \"/month\" } }, benefits: [\"Your actual included feature\"] }]}\n    />\n  );\n}",
  "demoUsage": "import ProductLaunchRecipe from \"@/components/uiblocks/recipe-product-launch/recipe-product-launch\";\n\nexport default function DemoPage() {\n  return <ProductLaunchRecipe mode=\"demo\" theme=\"dark\" />;\n}",
  "propsContract": [
    {
      "name": "mode, theme, className",
      "description": "Controlled by default. Explicit mode=\"demo\" enables fictional content and local interactions; demo service callbacks are never called. Theme inherits the host or can be scoped to dark."
    },
    {
      "name": "brand, copy, primaryHref, secondaryHref, homeHref, navigation, footerLinks, copyright",
      "description": "Provide actual product copy and ordinary host-owned URLs. Copy is a typed partial object covering every section. Missing primary URLs disable the CTA; executable URL schemes are rejected."
    },
    {
      "name": "features, services, steps, highlights, metrics, testimonials, faq",
      "description": "Readonly host-owned collections update directly from props; explicit [] remains empty even in demo mode. Features can choose growth/reflecting/lookingAhead original inline code illustrations; testimonials use initials and contain no inferred endorsements."
    },
    {
      "name": "plans, billingPeriod, onBillingPeriodChange",
      "description": "Plans contain id/name/description/benefits and prices.monthly/annual objects with exact amount/period/note strings. Never calculate discounts. A missing period shows Not offered. A single-period offering selects that available period unless the host explicitly controls billingPeriod."
    },
    {
      "name": "onSelectPlan(planId, period)",
      "description": "Optional awaited host callback. Missing callback disables selection; rejection keeps an error and retry action. It performs no payment. Demo chooses locally and never calls the callback."
    },
    {
      "name": "onSubscribe(email), subscriptionSuccessMessage",
      "description": "Optional awaited real host subscription callback. Missing callback disables the email field and submit. Pending guards duplicate submits, rejection preserves the address and retry, and success appears only after resolution. Demo never sends the address or invokes this callback."
    }
  ],
  "contentGuidelines": [
    "Supply verified product capabilities, prices, customer permissions and metrics in controlled mode; empty defaults contain no sample business records.",
    "Keep the original layered-card hero and complete section progression; adapt section copy through typed props instead of replacing the page with unrelated blocks.",
    "Wire onSubscribe and onSelectPlan to actual host services before enabling those actions. A resolved callback means the host accepted the request; this page does not provide checkout, billing or subscription infrastructure.",
    "For each billing period supply the exact display amount and period. Annual values are never calculated from monthly values and no savings badge is invented.",
    "Original upstream PNGs and hand-drawn SVG paths have unconfirmed asset provenance and are not included. The replacement SVG illustrations are self-contained code; there are no external image/font URLs.",
    "Verify real long Chinese copy at mobile/desktop sizes and use mode=\"demo\" only for explicitly labelled illustrations."
  ],
  "prompt": "Install the complete UIBlocks recipe-product-launch page and use its ProductLaunchRecipe implementation. Preserve the MIT-author-attributed layered hero, emerald/neutral visual system, actual nine-section page and native FAQ/mobile navigation. Supply my real product copy, features, services, workflow, exact billing prices, authorized testimonials, FAQs and routes. Connect subscription and plan callbacks to my real host services with pending/error/retry behavior; leave actions disabled until connected. Keep mode controlled for production and use explicit demo only for labelled fictional examples. Do not add Next.js dependencies, external fonts, unverified customer logos or unsupported original illustrations.",
  "origin": "external",
  "preview": "/preview/recipes/product-launch",
  "screenshot": "/previews/recipe-product-launch.png",
  "screenshotMetadata": {
    "renderer": "Chromium",
    "id": "recipe-product-launch",
    "image": "recipe-product-launch.png",
    "sourceFiles": [
      "app/globals.css",
      "components/brand.tsx",
      "components/copy-button.tsx",
      "components/footer.tsx",
      "components/header.tsx",
      "components/recipes/recipe-preview.tsx",
      "components/registry-setup.tsx",
      "components/seo-jsonld.tsx",
      "components/theme-toggle.tsx",
      "components/ui/button.tsx",
      "components/ui/chart.tsx",
      "components/ui/dialog.tsx",
      "components/ui/dropdown-menu.tsx",
      "components/ui/input-group.tsx",
      "components/ui/input.tsx",
      "components/ui/separator.tsx",
      "components/ui/sheet.tsx",
      "components/ui/sidebar.tsx",
      "components/ui/skeleton.tsx",
      "components/ui/sonner.tsx",
      "components/ui/table.tsx",
      "components/ui/textarea.tsx",
      "components/ui/tooltip.tsx",
      "config.ts",
      "content/blocks-metadata.ts",
      "content/catalog.ts",
      "content/community-blocks.ts",
      "content/components/dashboard/dashboard-01/customers-table.tsx",
      "content/components/dashboard/dashboard-01/dashboard-charts.tsx",
      "content/components/marketing/faq-01.tsx",
      "content/components/marketing/features-01.tsx",
      "content/components/marketing/footer-01.tsx",
      "content/components/marketing/hero-01.tsx",
      "content/components/marketing/logo-cloud-01.tsx",
      "content/components/marketing/navigation-01.tsx",
      "content/components/marketing/pricing-01.tsx",
      "content/components/workspace/results-gallery-01.tsx",
      "content/components/workspace/task-queue-01.tsx",
      "content/components/workspace/upload-workflow-01.tsx",
      "content/components/workspace/usage-panel-01.tsx",
      "content/components/workspace/workspace-header-01.tsx",
      "content/declarations.ts",
      "content/product-launch-recipe.ts",
      "content/recipe-components/product-launch-art.tsx",
      "content/recipe-components/product-launch.tsx",
      "content/recipe-components/saas-dashboard.tsx",
      "content/recipe-components/studio-agency.tsx",
      "content/recipe-components/studio-agency/demo-assets.ts",
      "content/recipe-components/studio-agency/demo.ts",
      "content/recipe-components/studio-agency/styles.ts",
      "content/recipe-components/studio-agency/types.ts",
      "content/recipe-components/team-inbox.tsx",
      "content/recipe-components/tool-landing.tsx",
      "content/recipe-components/tool-workspace.tsx",
      "content/recipes.ts",
      "content/studio-agency-recipe.ts",
      "content/template-recipes.ts",
      "hooks/use-mobile.ts",
      "hooks/use-registry-origin.ts",
      "lib/google-analytics.ts",
      "lib/utils.ts",
      "package-lock.json",
      "package.json",
      "postcss.config.mjs",
      "public/icon.svg",
      "public/opengraph-image.png",
      "scripts/capture-previews.mjs",
      "scripts/preview-provenance.mjs",
      "src/lib/head.ts",
      "src/routes/__root.tsx",
      "src/routes/preview.recipes.$recipeId.tsx",
      "vite.config.ts"
    ],
    "sourceHash": "3ad7fecc2d3e9ea08d3b8655c7cabbc1d38f91b9f71a192a08752f93dfb7a102",
    "imageHash": "a2f1487a511fa735560caafdc223e93a8941cb873f9941ec273507b64720d797",
    "capturedAt": "2026-10-30T09:46:27.327Z",
    "viewport": {
      "width": 1200,
      "height": 800
    },
    "dimensions": {
      "width": 1200,
      "height": 5303
    },
    "state": "demo"
  },
  "url": "/recipes/product-launch",
  "registry": "/r/recipe-product-launch.json",
  "install": {
    "direct": "npx shadcn@latest add https://uiblocks.org/r/recipe-product-launch.json",
    "namespaced": "npx shadcn@latest add @uiblocks/recipe-product-launch",
    "registryConfig": {
      "registries": {
        "@uiblocks": "https://uiblocks.org/r/{name}.json"
      }
    },
    "entry": "@components/uiblocks/recipe-product-launch/recipe-product-launch.tsx",
    "exportName": "default",
    "dependencies": [
      "cn@0.2.6",
      "lucide-react@0.577.0"
    ],
    "requirements": {
      "react": ">=19",
      "tailwind": ">=4"
    },
    "setup": [
      {
        "id": "project",
        "title": "Open your existing React project",
        "description": "Use React 19+, TypeScript/TSX, and a working Tailwind CSS 4 build. Keep the project's existing router, dependencies, and global stylesheet."
      },
      {
        "id": "shadcn",
        "title": "Check components.json before installing",
        "description": "Reuse an existing components.json. Its aliases.components must resolve in TypeScript and the bundler, and tailwind.css must point to the stylesheet your app loads. Do not reinitialize an already configured project.",
        "command": "npx shadcn@latest init",
        "when": "Only if components.json is missing; run inside the existing app and follow its framework setup."
      },
      {
        "id": "styles",
        "title": "Include the installed sources in Tailwind",
        "description": "Keep the host's standard shadcn theme variables, Tailwind @theme mappings, and base border styles loaded (background, foreground, border, sidebar, and chart tokens). Ensure Tailwind scans the actual installed components directory. Restricted source(none), ignored paths, and monorepos may need an @source path relative to the stylesheet. For theme='dark', use a class-based dark variant in the host CSS."
      },
      {
        "id": "import",
        "title": "Import through your configured components alias",
        "description": "The @components/ entry is a registry target placeholder, not a JavaScript import. Replace the example's @/components prefix with components.json aliases.components, verify the file printed by the CLI, and mount it in an existing host route."
      }
    ],
    "usage": "import ProductLaunchRecipe from \"@/components/uiblocks/recipe-product-launch/recipe-product-launch\";\n\nexport default function ProductPage() {\n  return (\n    <ProductLaunchRecipe\n      brand=\"Your product\"\n      primaryHref=\"/app\"\n      copy={{ title: \"Your actual product promise.\", accent: \"A clear next step.\", description: \"Describe the product you really provide.\" }}\n      features={[{ id: \"actual-feature\", title: \"Your capability\", description: \"Explain the actual workflow.\", illustration: \"growth\" }]}\n      plans={[{ id: \"team\", name: \"Your team plan\", description: \"Your verified plan description\", prices: { monthly: { amount: \"Your actual price\", period: \"/month\" } }, benefits: [\"Your actual included feature\"] }]}\n    />\n  );\n}",
    "demoUsage": "import ProductLaunchRecipe from \"@/components/uiblocks/recipe-product-launch/recipe-product-launch\";\n\nexport default function DemoPage() {\n  return <ProductLaunchRecipe mode=\"demo\" theme=\"dark\" />;\n}"
  },
  "compositionSource": {
    "language": "tsx",
    "componentName": "ProductLaunchRecipe",
    "referenceOnly": false,
    "installedEntry": "@components/uiblocks/recipe-product-launch/recipe-product-launch.tsx",
    "content": "\"use client\";\n\n/**\n * Product launch page adapted from leoMirandaa/shadcn-landing-page.\n * MIT License · Copyright (c) 2024 Leopoldo Miranda.\n * Upstream revision: ae3716dbce5c8a26815222bb743994b5717938f1\n * Original composition: src/App.tsx; Navbar, Hero, HeroCards, Sponsors,\n * About, Statistics, HowItWorks, Features, Services, Cta, Testimonials,\n * Pricing, Newsletter, FAQ and Footer under src/components/.\n * Adaptations: self-contained React/Tailwind 4 page, native disclosure controls,\n * host-owned content/actions, explicit demo mode, scoped theme and responsive cards.\n * Complete notice: licenses/third-party/shadcn-landing-MIT.txt\n */\n\nimport {\n  ArrowRight,\n  Check,\n  ChevronDown,\n  Layers3,\n  Lightbulb,\n  Menu,\n  MessageSquare,\n  MoveUpRight,\n  PanelsTopLeft,\n  Sparkles,\n  Users,\n  Workflow,\n} from \"lucide-react\";\nimport {\n  useEffect,\n  useId,\n  useRef,\n  useState,\n  type FormEvent,\n  type ReactNode,\n} from \"react\";\nimport { cn } from \"./shared/lib/utils\";\nimport { ProductLaunchIllustration } from \"./shared/content/recipe-components/product-launch-art\";\n\nexport type ProductLaunchBillingPeriod = \"monthly\" | \"annual\";\nexport type ProductLaunchPrice = {\n  /** Exact host-supplied display value; this page never calculates a discount. */\n  amount: string;\n  period?: string;\n  note?: string;\n};\nexport type ProductLaunchPlan = {\n  id: string;\n  name: string;\n  description: string;\n  prices: Partial<Record<ProductLaunchBillingPeriod, ProductLaunchPrice>>;\n  benefits: readonly string[];\n  highlighted?: boolean;\n  badge?: string;\n  actionLabel?: string;\n};\nexport type ProductLaunchFeature = {\n  id: string;\n  title: string;\n  description: string;\n  illustration?: \"growth\" | \"reflecting\" | \"lookingAhead\";\n};\nexport type ProductLaunchTestimonial = {\n  id: string;\n  name: string;\n  role?: string;\n  quote: string;\n};\nexport type ProductLaunchLink = { label: string; href: string };\nexport type ProductLaunchCopy = {\n  eyebrow: string;\n  title: string;\n  accent: string;\n  description: string;\n  primaryLabel: string;\n  secondaryLabel: string;\n  featuresTitle: string;\n  featuresDescription: string;\n  servicesTitle: string;\n  servicesDescription: string;\n  workflowTitle: string;\n  workflowDescription: string;\n  ctaTitle: string;\n  ctaDescription: string;\n  testimonialsTitle: string;\n  pricingTitle: string;\n  pricingDescription: string;\n  newsletterTitle: string;\n  newsletterDescription: string;\n  newsletterLabel: string;\n  faqTitle: string;\n  footerDescription: string;\n};\nexport type ProductLaunchRecipeProps = {\n  /** Controlled is a safe empty starting point. Demo content requires this opt-in. */\n  mode?: \"controlled\" | \"demo\";\n  theme?: \"inherit\" | \"dark\";\n  className?: string;\n  brand?: string;\n  homeHref?: string;\n  copy?: Partial<ProductLaunchCopy>;\n  primaryHref?: string;\n  secondaryHref?: string;\n  navigation?: readonly ProductLaunchLink[];\n  highlights?: readonly string[];\n  metrics?: readonly { label: string; value: string }[];\n  features?: readonly ProductLaunchFeature[];\n  services?: readonly ProductLaunchFeature[];\n  steps?: readonly ProductLaunchFeature[];\n  testimonials?: readonly ProductLaunchTestimonial[];\n  plans?: readonly ProductLaunchPlan[];\n  faq?: readonly { id: string; question: string; answer: string }[];\n  footerLinks?: readonly ProductLaunchLink[];\n  copyright?: string;\n  /** When supplied, the host owns the selected display period. */\n  billingPeriod?: ProductLaunchBillingPeriod;\n  onBillingPeriodChange?: (period: ProductLaunchBillingPeriod) => void;\n  /** Resolves only after the host accepts the request; no payment is performed here. */\n  onSelectPlan?: (\n    planId: string,\n    period: ProductLaunchBillingPeriod\n  ) => void | Promise<void>;\n  /** Connect this to the consuming project's actual subscription service. */\n  onSubscribe?: (email: string) => void | Promise<void>;\n  subscriptionSuccessMessage?: string;\n};\n\nconst emptyItems: readonly never[] = [];\nconst emailPattern = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst safeCopy: ProductLaunchCopy = {\n  eyebrow: \"Product launch\",\n  title: \"Introduce your product.\",\n  accent: \"Make the next step clear.\",\n  description:\n    \"Supply your product story, verified capabilities and actual plans through this page's props.\",\n  primaryLabel: \"Get started\",\n  secondaryLabel: \"Learn more\",\n  featuresTitle: \"Product capabilities\",\n  featuresDescription: \"Show the capabilities your product actually provides.\",\n  servicesTitle: \"A workflow built around your needs\",\n  servicesDescription:\n    \"Supply your actual services and the problems they address.\",\n  workflowTitle: \"How it works\",\n  workflowDescription: \"Explain the steps your customers will really take.\",\n  ctaTitle: \"A clear next step for your product\",\n  ctaDescription:\n    \"Connect the primary action to your own onboarding or product page.\",\n  testimonialsTitle: \"Customer perspectives\",\n  pricingTitle: \"Choose a plan\",\n  pricingDescription: \"Prices and billing periods are supplied by the host.\",\n  newsletterTitle: \"Stay in the loop\",\n  newsletterDescription: \"Subscribe through the product's connected service.\",\n  newsletterLabel: \"Subscribe\",\n  faqTitle: \"Frequently asked questions\",\n  footerDescription: \"Your product story and next steps, in one place.\",\n};\nconst demoCopy: Partial<ProductLaunchCopy> = {\n  eyebrow: \"A little more clarity. A lot more possibility.\",\n  title: \"Great products start\",\n  accent: \"with a clear plan.\",\n  description:\n    \"Sprout brings your ideas, decisions and next steps together, so your team can focus on the work that matters.\",\n  primaryLabel: \"Explore the plans\",\n  secondaryLabel: \"See how it works\",\n  featuresTitle: \"Small details. Great possibilities.\",\n  featuresDescription:\n    \"Give every idea the context, care and space it needs to become something useful.\",\n  servicesTitle: \"Your ideas, connected.\",\n  servicesDescription:\n    \"A calm place for the conversations, decisions and handoffs behind every good product.\",\n  workflowTitle: \"From a spark to your next chapter\",\n  workflowDescription:\n    \"A simple rhythm for moving good ideas forward together.\",\n  ctaTitle: \"All your ideas & concepts in one place.\",\n  ctaDescription:\n    \"Make room for the work you care about. Start with a shared plan and build from there.\",\n  testimonialsTitle: \"A little perspective from the team\",\n  pricingTitle: \"A little room to start. Space to grow.\",\n  pricingDescription: \"Illustrative plans for this local page demonstration.\",\n  newsletterTitle: \"A fresh perspective, now and then.\",\n  newsletterDescription:\n    \"Try the form locally. Demo addresses are never sent to a service.\",\n  footerDescription: \"A clearer starting point for your next great idea.\",\n};\nconst demoFeatures: readonly ProductLaunchFeature[] = [\n  {\n    id: \"context\",\n    title: \"Keep the whole story\",\n    description:\n      \"Give every project a home for the context, decisions and details that make it yours.\",\n    illustration: \"growth\",\n  },\n  {\n    id: \"collaborate\",\n    title: \"Think better, together\",\n    description:\n      \"Bring different perspectives into the same conversation, with clear ownership along the way.\",\n    illustration: \"reflecting\",\n  },\n  {\n    id: \"focus\",\n    title: \"Find your next move\",\n    description:\n      \"Turn the big picture into smaller, thoughtful steps your team can take with confidence.\",\n    illustration: \"lookingAhead\",\n  },\n];\nconst demoServices: readonly ProductLaunchFeature[] = [\n  {\n    id: \"planning\",\n    title: \"Thoughtful planning\",\n    description:\n      \"Connect your goals with the decisions and milestones that bring them closer.\",\n  },\n  {\n    id: \"handoffs\",\n    title: \"Clearer handoffs\",\n    description:\n      \"Keep the why alongside the what, so the next person can pick up the thread.\",\n  },\n  {\n    id: \"reflection\",\n    title: \"Room to reflect\",\n    description:\n      \"Capture what you learned and bring a little more clarity to your next chapter.\",\n  },\n];\nconst demoSteps: readonly ProductLaunchFeature[] = [\n  {\n    id: \"capture\",\n    title: \"Start with a spark\",\n    description: \"Capture your idea and the problem you want to solve.\",\n  },\n  {\n    id: \"shape\",\n    title: \"Shape it together\",\n    description: \"Invite a new perspective and agree on the next step.\",\n  },\n  {\n    id: \"move\",\n    title: \"Keep moving\",\n    description: \"Make progress visible and keep the context close.\",\n  },\n];\nconst demoPlans: readonly ProductLaunchPlan[] = [\n  {\n    id: \"starter\",\n    name: \"Starter\",\n    description: \"A small space for a good idea.\",\n    prices: {\n      monthly: { amount: \"$0\", period: \"/month\" },\n      annual: { amount: \"$0\", period: \"/year\" },\n    },\n    benefits: [\n      \"One shared project\",\n      \"Notes and next steps\",\n      \"Local demo experience\",\n    ],\n    actionLabel: \"Explore Starter\",\n  },\n  {\n    id: \"team\",\n    name: \"Team\",\n    description: \"A little more room to work together.\",\n    highlighted: true,\n    badge: \"Team pick\",\n    prices: {\n      monthly: { amount: \"$12\", period: \"/month\" },\n      annual: {\n        amount: \"$120\",\n        period: \"/year\",\n        note: \"Illustrative annual total\",\n      },\n    },\n    benefits: [\n      \"Shared project spaces\",\n      \"Team decisions and handoffs\",\n      \"Reusable planning notes\",\n    ],\n    actionLabel: \"Explore Team\",\n  },\n  {\n    id: \"studio\",\n    name: \"Studio\",\n    description: \"Space for your next chapter.\",\n    prices: {\n      monthly: { amount: \"$29\", period: \"/month\" },\n      annual: {\n        amount: \"$290\",\n        period: \"/year\",\n        note: \"Illustrative annual total\",\n      },\n    },\n    benefits: [\n      \"Multiple team spaces\",\n      \"Cross-project planning\",\n      \"Workspace review tools\",\n    ],\n    actionLabel: \"Explore Studio\",\n  },\n];\nconst demoTestimonials: readonly ProductLaunchTestimonial[] = [\n  {\n    id: \"alex\",\n    name: \"Alex Morgan\",\n    role: \"Fictional product designer\",\n    quote:\n      \"The most useful thing is having the context next to the decision. Our next step feels a lot clearer.\",\n  },\n  {\n    id: \"sam\",\n    name: \"Sam Lee\",\n    role: \"Fictional project lead\",\n    quote:\n      \"A little structure gives our ideas room to breathe. It is easier to pick up where a conversation left off.\",\n  },\n  {\n    id: \"robin\",\n    name: \"Robin Chen\",\n    role: \"Fictional developer\",\n    quote:\n      \"I can see the why behind a handoff, then get back to the work in front of me.\",\n  },\n];\nconst demoFaq = [\n  {\n    id: \"demo\",\n    question: \"Is this a live product?\",\n    answer:\n      \"No. Sprout is a fictional product used to demonstrate this complete page. Prices, people and product capabilities are illustrative.\",\n  },\n  {\n    id: \"actions\",\n    question: \"Do the demo actions contact a service?\",\n    answer:\n      \"No. Plan choices and newsletter submissions stay in this local demo. Consuming projects connect their own callbacks in controlled mode.\",\n  },\n  {\n    id: \"content\",\n    question: \"Can I use my own product content?\",\n    answer:\n      \"Yes. Supply your actual capabilities, services, plans, FAQs and links through the page props.\",\n  },\n  {\n    id: \"billing\",\n    question: \"How are annual prices displayed?\",\n    answer:\n      \"Each billing period displays the exact price supplied for that period. The page does not calculate discounts or invent an annual offer.\",\n  },\n];\n\n/** Reject executable schemes while retaining ordinary host routes and links. */\nexport function productLaunchHref(\n  value: string | undefined\n): string | undefined {\n  if (!value || /[\\u0000-\\u0020\\u007f\\\\]/.test(value) || value.startsWith(\"//\"))\n    return;\n  if (\n    /^[a-z][a-z\\d+.-]*:/i.test(value) &&\n    !/^(https?:|mailto:|tel:)/i.test(value)\n  )\n    return;\n  return value;\n}\n\nexport function productLaunchPrice(\n  plan: ProductLaunchPlan,\n  period: ProductLaunchBillingPeriod\n): ProductLaunchPrice | undefined {\n  const price = plan.prices[period];\n  return price?.amount.trim() ? price : undefined;\n}\n\nexport async function submitProductLaunchSubscription(\n  email: string,\n  mode: \"controlled\" | \"demo\",\n  onSubscribe?: ProductLaunchRecipeProps[\"onSubscribe\"]\n): Promise<\"demo\" | \"accepted\"> {\n  const address = email.trim();\n  if (address.length > 320 || !emailPattern.test(address))\n    throw new Error(\"Enter a valid email address.\");\n  if (mode === \"demo\") return \"demo\";\n  if (!onSubscribe) throw new Error(\"No subscription service is connected.\");\n  await onSubscribe(address);\n  return \"accepted\";\n}\n\nexport async function selectProductLaunchPlan(\n  plan: ProductLaunchPlan,\n  period: ProductLaunchBillingPeriod,\n  mode: \"controlled\" | \"demo\",\n  onSelectPlan?: ProductLaunchRecipeProps[\"onSelectPlan\"]\n): Promise<\"demo\" | \"accepted\"> {\n  if (!productLaunchPrice(plan, period))\n    throw new Error(\"This billing period is not offered.\");\n  if (mode === \"demo\") return \"demo\";\n  if (!onSelectPlan) throw new Error(\"No plan selection service is connected.\");\n  await onSelectPlan(plan.id, period);\n  return \"accepted\";\n}\n\nconst container = \"mx-auto w-full max-w-6xl px-5 sm:px-8\";\nconst card =\n  \"rounded-2xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-800 dark:bg-[#1c1917]\";\nconst muted = \"text-zinc-600 dark:text-zinc-400\";\nconst actionClass =\n  \"inline-flex min-h-11 max-w-full items-center justify-center gap-2 rounded-lg px-5 py-3 text-center text-sm font-semibold whitespace-normal [overflow-wrap:anywhere] outline-none transition-colors focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-45 motion-reduce:transition-none\";\nconst greenAction =\n  \"bg-emerald-600 text-white hover:bg-emerald-700 dark:bg-emerald-400 dark:text-emerald-950 dark:hover:bg-emerald-300\";\nconst outlineAction =\n  \"border border-zinc-300 bg-transparent hover:bg-zinc-100 dark:border-zinc-700 dark:hover:bg-zinc-800\";\n\nfunction PageAction({\n  href,\n  children,\n  secondary = false,\n}: {\n  href?: string;\n  children: ReactNode;\n  secondary?: boolean;\n}) {\n  const safeHref = productLaunchHref(href);\n  const className = cn(actionClass, secondary ? outlineAction : greenAction);\n  return safeHref ? (\n    <a className={className} href={safeHref}>\n      {children}\n    </a>\n  ) : (\n    <button className={className} disabled type=\"button\">\n      {children}\n    </button>\n  );\n}\n\nfunction SectionHeading({\n  title,\n  description,\n  centered = false,\n}: {\n  title: string;\n  description?: string;\n  centered?: boolean;\n}) {\n  return (\n    <div className={cn(\"mb-9 max-w-2xl\", centered && \"mx-auto text-center\")}>\n      <h2 className=\"text-3xl font-semibold tracking-tight text-balance sm:text-4xl [overflow-wrap:anywhere]\">\n        {title}\n      </h2>\n      {description && (\n        <p\n          className={cn(\n            \"mt-4 text-base leading-7 [overflow-wrap:anywhere]\",\n            muted\n          )}\n        >\n          {description}\n        </p>\n      )}\n    </div>\n  );\n}\n\nfunction Initials({ name, large = false }: { name: string; large?: boolean }) {\n  const initials =\n    name\n      .trim()\n      .split(/\\s+/)\n      .map((part) => Array.from(part)[0])\n      .slice(0, 2)\n      .join(\"\")\n      .toUpperCase() || \"P\";\n  return (\n    <span\n      aria-hidden=\"true\"\n      className={cn(\n        \"inline-flex shrink-0 items-center justify-center rounded-full bg-emerald-100 font-semibold text-emerald-800 dark:bg-emerald-900/60 dark:text-emerald-300\",\n        large\n          ? \"size-16 text-xl ring-8 ring-white dark:ring-[#1c1917]\"\n          : \"size-10 text-xs\"\n      )}\n    >\n      {initials}\n    </span>\n  );\n}\n\nfunction EmptySection({ children }: { children: ReactNode }) {\n  return (\n    <p\n      className={cn(\n        \"rounded-xl border border-dashed border-zinc-300 px-5 py-8 text-sm dark:border-zinc-700\",\n        muted\n      )}\n    >\n      {children}\n    </p>\n  );\n}\n\nexport default function ProductLaunchRecipe(props: ProductLaunchRecipeProps) {\n  const {\n    mode = \"controlled\",\n    theme = \"inherit\",\n    className,\n    onSubscribe,\n    onSelectPlan,\n  } = props;\n  const demo = mode === \"demo\";\n  const brand = props.brand ?? (demo ? \"Sprout\" : \"Your product\");\n  const copy = { ...safeCopy, ...(demo ? demoCopy : {}), ...props.copy };\n  const features = props.features ?? (demo ? demoFeatures : emptyItems);\n  const services = props.services ?? (demo ? demoServices : emptyItems);\n  const steps = props.steps ?? (demo ? demoSteps : emptyItems);\n  const testimonials =\n    props.testimonials ?? (demo ? demoTestimonials : emptyItems);\n  const plans = props.plans ?? (demo ? demoPlans : emptyItems);\n  const faq = props.faq ?? (demo ? demoFaq : emptyItems);\n  const metrics =\n    props.metrics ??\n    (demo\n      ? [\n          { value: \"3\", label: \"Illustrative plans\" },\n          { value: \"4\", label: \"Demo questions\" },\n        ]\n      : emptyItems);\n  const highlights =\n    props.highlights ??\n    (demo\n      ? [\n          \"Shared context\",\n          \"Clear handoffs\",\n          \"Thoughtful planning\",\n          \"Room to grow\",\n        ]\n      : emptyItems);\n  const uniqueId = useId().replace(/:/g, \"\");\n  const sectionId = (name: string) => `product-launch-${uniqueId}-${name}`;\n  const ids = {\n    top: sectionId(\"top\"),\n    features: sectionId(\"features\"),\n    workflow: sectionId(\"workflow\"),\n    pricing: sectionId(\"pricing\"),\n    faq: sectionId(\"faq\"),\n    newsletter: sectionId(\"newsletter\"),\n  };\n  const primaryHref =\n    props.primaryHref ?? (demo ? `#${ids.pricing}` : undefined);\n  const secondaryHref = props.secondaryHref ?? `#${ids.features}`;\n  const navigation = props.navigation ?? [\n    { label: \"Features\", href: `#${ids.features}` },\n    { label: \"How it works\", href: `#${ids.workflow}` },\n    { label: \"Pricing\", href: `#${ids.pricing}` },\n    { label: \"FAQ\", href: `#${ids.faq}` },\n  ];\n  const footerLinks = props.footerLinks ?? navigation;\n  const [localPeriod, setLocalPeriod] =\n    useState<ProductLaunchBillingPeriod>(\"monthly\");\n  const hasMonthly = plans.some((plan) => productLaunchPrice(plan, \"monthly\"));\n  const hasAnnual = plans.some((plan) => productLaunchPrice(plan, \"annual\"));\n  // A product offering only one period remains usable without inventing the other.\n  const localPeriodOffered = localPeriod === \"monthly\" ? hasMonthly : hasAnnual;\n  const period =\n    props.billingPeriod ??\n    (localPeriodOffered\n      ? localPeriod\n      : hasMonthly\n        ? \"monthly\"\n        : hasAnnual\n          ? \"annual\"\n          : localPeriod);\n  const [subscription, setSubscription] = useState<{\n    status: \"idle\" | \"pending\" | \"error\" | \"accepted\" | \"demo\";\n    message: string;\n  }>({ status: \"idle\", message: \"\" });\n  const [planAction, setPlanAction] = useState<{\n    id?: string;\n    status: \"idle\" | \"pending\" | \"error\" | \"accepted\" | \"demo\";\n    message: string;\n  }>({ status: \"idle\", message: \"\" });\n  const subscriptionLock = useRef(false);\n  const planLock = useRef(false);\n  const mounted = useRef(true);\n  const menuRef = useRef<HTMLDetailsElement>(null);\n  useEffect(() => {\n    mounted.current = true;\n    return () => {\n      mounted.current = false;\n    };\n  }, []);\n  const changePeriod = (next: ProductLaunchBillingPeriod) => {\n    if (props.billingPeriod === undefined) setLocalPeriod(next);\n    if (!demo) props.onBillingPeriodChange?.(next);\n  };\n  const subscribe = async (event: FormEvent<HTMLFormElement>) => {\n    event.preventDefault();\n    if (subscriptionLock.current || (!demo && !onSubscribe)) return;\n    const email = String(new FormData(event.currentTarget).get(\"email\") ?? \"\");\n    subscriptionLock.current = true;\n    setSubscription({ status: \"pending\", message: \"\" });\n    try {\n      const result = await submitProductLaunchSubscription(\n        email,\n        mode,\n        onSubscribe\n      );\n      if (mounted.current)\n        setSubscription({\n          status: result,\n          message:\n            result === \"demo\"\n              ? \"Demo only · This address was not sent anywhere.\"\n              : (props.subscriptionSuccessMessage ??\n                \"Your request was accepted by the connected service.\"),\n        });\n    } catch (error) {\n      if (mounted.current)\n        setSubscription({\n          status: \"error\",\n          message:\n            error instanceof Error\n              ? error.message\n              : \"The subscription request failed. Try again.\",\n        });\n    } finally {\n      subscriptionLock.current = false;\n    }\n  };\n  const choosePlan = async (plan: ProductLaunchPlan) => {\n    if (planLock.current || (!demo && !onSelectPlan)) return;\n    planLock.current = true;\n    setPlanAction({ id: plan.id, status: \"pending\", message: \"\" });\n    try {\n      const result = await selectProductLaunchPlan(\n        plan,\n        period,\n        mode,\n        onSelectPlan\n      );\n      if (mounted.current)\n        setPlanAction({\n          id: plan.id,\n          status: result,\n          message:\n            result === \"demo\"\n              ? `Demo only · ${plan.name} was selected locally. No service was contacted.`\n              : \"Your plan request was accepted by the host. No payment is performed by this page.\",\n        });\n    } catch (error) {\n      if (mounted.current)\n        setPlanAction({\n          id: plan.id,\n          status: \"error\",\n          message:\n            error instanceof Error\n              ? error.message\n              : \"The plan request failed. Try again.\",\n        });\n    } finally {\n      planLock.current = false;\n    }\n  };\n  const firstFeature = features[0];\n  const firstService = services[0];\n  const firstPlan = plans.find((plan) => plan.highlighted) ?? plans[0];\n  const heroPrice = firstPlan\n    ? productLaunchPrice(firstPlan, period)\n    : undefined;\n  const heroQuote = testimonials[0];\n\n  return (\n    <div\n      className={cn(theme === \"dark\" && \"dark\", className)}\n      data-product-launch-mode={mode}\n    >\n      <div\n        className=\"isolate overflow-x-clip bg-white text-zinc-950 dark:bg-[#100f0e] dark:text-zinc-100 [overflow-wrap:anywhere]\"\n        id={ids.top}\n      >\n        <a\n          className=\"sr-only z-50 rounded-md bg-emerald-600 p-3 text-white focus:not-sr-only focus:fixed focus:left-4 focus:top-4\"\n          href={`#${ids.features}`}\n        >\n          Skip to product content\n        </a>\n        <header className=\"border-b border-zinc-200 dark:border-zinc-800\">\n          <div\n            className={cn(\n              container,\n              \"flex min-h-20 flex-wrap items-center justify-between gap-4 py-4\"\n            )}\n          >\n            <a\n              className=\"flex min-w-0 max-w-full items-center gap-2 font-semibold tracking-tight outline-none focus-visible:ring-2 focus-visible:ring-emerald-500\"\n              href={productLaunchHref(props.homeHref) ?? `#${ids.top}`}\n            >\n              <PanelsTopLeft\n                aria-hidden\n                className=\"size-6 shrink-0 text-emerald-600 dark:text-emerald-400\"\n              />\n              <span className=\"break-words\">{brand}</span>\n            </a>\n            <nav\n              aria-label=\"Product navigation\"\n              className=\"hidden flex-wrap items-center gap-x-6 gap-y-3 text-sm lg:flex\"\n            >\n              {navigation.map(\n                (link, index) =>\n                  productLaunchHref(link.href) && (\n                    <a\n                      className={cn(\n                        \"rounded-sm hover:text-emerald-600 focus-visible:outline-2 focus-visible:outline-emerald-500\",\n                        muted\n                      )}\n                      href={productLaunchHref(link.href)}\n                      key={`${link.label}-${index}`}\n                    >\n                      {link.label}\n                    </a>\n                  )\n              )}\n            </nav>\n            <details className=\"relative lg:hidden\" ref={menuRef}>\n              <summary\n                aria-label=\"Open product navigation\"\n                className=\"flex min-h-11 min-w-11 cursor-pointer list-none items-center justify-center rounded-lg border border-zinc-300 focus-visible:outline-2 focus-visible:outline-emerald-500 dark:border-zinc-700 [&::-webkit-details-marker]:hidden\"\n              >\n                <Menu aria-hidden className=\"size-5\" />\n              </summary>\n              <nav\n                aria-label=\"Mobile product navigation\"\n                className={cn(\n                  card,\n                  \"absolute right-0 top-full z-50 mt-2 grid w-64 max-w-[80vw] gap-1 p-3 text-sm\"\n                )}\n              >\n                {navigation.map(\n                  (link, index) =>\n                    productLaunchHref(link.href) && (\n                      <a\n                        className=\"rounded-md px-3 py-3 hover:bg-emerald-50 focus-visible:outline-2 focus-visible:outline-emerald-500 dark:hover:bg-emerald-950\"\n                        href={productLaunchHref(link.href)}\n                        key={`${link.label}-${index}`}\n                        onClick={() => {\n                          if (menuRef.current) menuRef.current.open = false;\n                        }}\n                      >\n                        {link.label}\n                      </a>\n                    )\n                )}\n              </nav>\n            </details>\n          </div>\n        </header>\n\n        <main>\n          {demo && (\n            <p className=\"border-b border-emerald-200 bg-emerald-50 px-5 py-3 text-center text-xs leading-5 text-emerald-900 dark:border-emerald-900 dark:bg-emerald-950/50 dark:text-emerald-200\">\n              Demo data · Fictional product, people, capabilities and prices.\n              Actions stay local; no service or payment connection.\n            </p>\n          )}\n          <section\n            aria-labelledby={`${ids.top}-title`}\n            className={cn(\n              container,\n              \"relative grid gap-14 py-16 sm:py-24 lg:grid-cols-[1fr_1.1fr] lg:items-center lg:gap-12\"\n            )}\n          >\n            <div className=\"relative z-10 max-w-xl\">\n              <p className=\"mb-5 text-xs font-semibold tracking-[0.16em] text-emerald-700 uppercase dark:text-emerald-400\">\n                {copy.eyebrow}\n              </p>\n              <h1\n                className=\"text-4xl leading-[1.12] font-semibold tracking-tight text-balance sm:text-6xl\"\n                id={`${ids.top}-title`}\n              >\n                {copy.title}{\" \"}\n                <span className=\"bg-linear-to-r from-emerald-600 to-teal-500 bg-clip-text text-transparent dark:from-emerald-300 dark:to-teal-400\">\n                  {copy.accent}\n                </span>\n              </h1>\n              <p className={cn(\"mt-6 max-w-lg text-lg leading-8\", muted)}>\n                {copy.description}\n              </p>\n              <div className=\"mt-8 flex flex-wrap gap-3\">\n                <PageAction href={primaryHref}>\n                  {copy.primaryLabel}\n                  <ArrowRight aria-hidden className=\"size-4 shrink-0\" />\n                </PageAction>\n                <PageAction href={secondaryHref} secondary>\n                  {copy.secondaryLabel}\n                </PageAction>\n              </div>\n            </div>\n            <div\n              className=\"relative px-3 py-5 sm:px-5\"\n              aria-label=\"Product preview cards\"\n            >\n              <div\n                aria-hidden\n                className=\"pointer-events-none absolute inset-5 -z-10 rounded-full bg-emerald-500/10 blur-3xl\"\n              />\n              <div className=\"grid gap-4 sm:grid-cols-2 sm:items-start\">\n                <article\n                  className={cn(\n                    card,\n                    \"relative z-20 p-5 sm:translate-y-5 sm:-rotate-3 motion-reduce:transform-none\"\n                  )}\n                >\n                  <div className=\"mb-4 flex items-center gap-3\">\n                    <Initials name={heroQuote?.name ?? brand} />\n                    <div className=\"min-w-0\">\n                      <p className=\"text-sm font-semibold\">\n                        {heroQuote?.name ?? \"Product overview\"}\n                      </p>\n                      <p className={cn(\"text-xs\", muted)}>\n                        {heroQuote?.role ?? \"Host-supplied content\"}\n                      </p>\n                    </div>\n                  </div>\n                  <p className=\"text-sm leading-6\">\n                    {heroQuote?.quote ??\n                      firstFeature?.description ??\n                      \"Supply your product content to bring this preview to life.\"}\n                  </p>\n                </article>\n                <article\n                  className={cn(\n                    card,\n                    \"relative z-10 p-5 text-center sm:rotate-3 motion-reduce:transform-none\"\n                  )}\n                >\n                  <Initials name={brand} large />\n                  <h2 className=\"mt-4 text-lg font-semibold\">{brand}</h2>\n                  <p className=\"mt-1 text-xs font-medium text-emerald-700 dark:text-emerald-400\">\n                    {firstFeature?.title ?? \"Your product story\"}\n                  </p>\n                  <p className={cn(\"mt-3 text-sm leading-6\", muted)}>\n                    {firstFeature?.description ??\n                      \"No feature details supplied.\"}\n                  </p>\n                  <div\n                    aria-hidden\n                    className=\"mt-4 flex justify-center gap-3 text-emerald-600 dark:text-emerald-400\"\n                  >\n                    <Layers3 className=\"size-4\" />\n                    <Users className=\"size-4\" />\n                    <Workflow className=\"size-4\" />\n                  </div>\n                </article>\n                <article\n                  className={cn(\n                    card,\n                    \"relative z-30 p-5 sm:translate-x-2 sm:rotate-1 motion-reduce:transform-none\"\n                  )}\n                >\n                  <div className=\"flex flex-wrap items-start justify-between gap-2\">\n                    <h2 className=\"text-lg font-semibold\">\n                      {firstPlan?.name ?? \"Your plans\"}\n                    </h2>\n                    {firstPlan?.badge && (\n                      <span className=\"rounded-full bg-emerald-100 px-2 py-1 text-[10px] font-medium text-emerald-800 dark:bg-emerald-900 dark:text-emerald-200\">\n                        {firstPlan.badge}\n                      </span>\n                    )}\n                  </div>\n                  <p className=\"mt-4 text-3xl font-semibold tracking-tight\">\n                    {heroPrice?.amount ?? \"Not supplied\"}\n                    <span className={cn(\"ml-1 text-xs font-normal\", muted)}>\n                      {heroPrice?.period}\n                    </span>\n                  </p>\n                  <p className={cn(\"mt-3 text-xs leading-5\", muted)}>\n                    {firstPlan?.description ??\n                      \"Connect your actual plan prices.\"}\n                  </p>\n                  <ul className=\"mt-4 space-y-2 border-t border-zinc-200 pt-4 text-xs dark:border-zinc-800\">\n                    {firstPlan?.benefits.slice(0, 3).map((benefit, index) => (\n                      <li\n                        className=\"flex items-start gap-2\"\n                        key={`${benefit}-${index}`}\n                      >\n                        <Check\n                          aria-hidden\n                          className=\"size-4 shrink-0 text-emerald-600 dark:text-emerald-400\"\n                        />\n                        <span>{benefit}</span>\n                      </li>\n                    ))}\n                  </ul>\n                </article>\n                <article\n                  className={cn(\n                    card,\n                    \"relative z-40 flex items-start gap-3 p-5 sm:translate-y-8 sm:-rotate-2 motion-reduce:transform-none\"\n                  )}\n                >\n                  <span className=\"shrink-0 rounded-xl bg-emerald-500/10 p-1\">\n                    <Lightbulb\n                      aria-hidden\n                      className=\"size-12 text-emerald-500\"\n                    />\n                  </span>\n                  <div className=\"min-w-0\">\n                    <h2 className=\"text-sm font-semibold\">\n                      {firstService?.title ?? \"A clearer next step\"}\n                    </h2>\n                    <p className={cn(\"mt-2 text-xs leading-5\", muted)}>\n                      {firstService?.description ??\n                        \"Supply your actual services and product workflow.\"}\n                    </p>\n                  </div>\n                </article>\n              </div>\n            </div>\n          </section>\n\n          {highlights.length > 0 && (\n            <div\n              className={cn(\n                container,\n                \"flex flex-wrap justify-center gap-x-8 gap-y-4 border-b border-zinc-200 pb-12 text-sm font-medium text-zinc-500 dark:border-zinc-800 dark:text-zinc-400\"\n              )}\n            >\n              {highlights.map((highlight, index) => (\n                <span\n                  className=\"flex items-start gap-2\"\n                  key={`${highlight}-${index}`}\n                >\n                  <Sparkles\n                    aria-hidden\n                    className=\"mt-0.5 size-4 shrink-0 text-emerald-600 dark:text-emerald-400\"\n                  />\n                  {highlight}\n                </span>\n              ))}\n            </div>\n          )}\n\n          <section\n            className={cn(container, \"scroll-mt-8 py-16 sm:py-24\")}\n            id={ids.features}\n          >\n            <SectionHeading\n              title={copy.featuresTitle}\n              description={copy.featuresDescription}\n              centered\n            />\n            {metrics.length > 0 && (\n              <dl className=\"mb-12 grid gap-6 rounded-2xl bg-zinc-100 p-6 text-center sm:grid-cols-2 lg:grid-cols-4 dark:bg-zinc-900\">\n                {metrics.map((metric, index) => (\n                  <div key={`${metric.label}-${index}`}>\n                    <dd className=\"text-3xl font-semibold tracking-tight text-emerald-700 dark:text-emerald-400\">\n                      {metric.value}\n                    </dd>\n                    <dt className={cn(\"mt-2 text-xs\", muted)}>\n                      {metric.label}\n                    </dt>\n                  </div>\n                ))}\n              </dl>\n            )}\n            {features.length ? (\n              <div className=\"grid gap-6 md:grid-cols-3\">\n                {features.map((feature) => (\n                  <article\n                    className={cn(\n                      card,\n                      \"flex min-w-0 flex-col overflow-hidden p-6\"\n                    )}\n                    key={feature.id}\n                  >\n                    <h3 className=\"text-lg font-semibold\">{feature.title}</h3>\n                    <p className={cn(\"mt-3 text-sm leading-6\", muted)}>\n                      {feature.description}\n                    </p>\n                    {feature.illustration && (\n                      <ProductLaunchIllustration\n                        className=\"mx-auto mt-8 max-w-64\"\n                        variant={feature.illustration}\n                      />\n                    )}\n                  </article>\n                ))}\n              </div>\n            ) : (\n              <EmptySection>No product capabilities supplied.</EmptySection>\n            )}\n          </section>\n\n          <section\n            className={cn(\n              container,\n              \"grid gap-10 pb-16 sm:pb-24 lg:grid-cols-[1fr_.7fr] lg:items-center\"\n            )}\n          >\n            <div>\n              <SectionHeading\n                title={copy.servicesTitle}\n                description={copy.servicesDescription}\n              />\n              {services.length ? (\n                <div className=\"space-y-4\">\n                  {services.map((service, index) => {\n                    const Icon = [MessageSquare, Workflow, Layers3][index % 3];\n                    return (\n                      <article\n                        className={cn(card, \"flex items-start gap-4 p-5\")}\n                        key={service.id}\n                      >\n                        <span className=\"rounded-xl bg-emerald-500/10 p-3 text-emerald-700 dark:text-emerald-400\">\n                          <Icon aria-hidden className=\"size-5\" />\n                        </span>\n                        <div className=\"min-w-0\">\n                          <h3 className=\"font-semibold\">{service.title}</h3>\n                          <p className={cn(\"mt-2 text-sm leading-6\", muted)}>\n                            {service.description}\n                          </p>\n                        </div>\n                      </article>\n                    );\n                  })}\n                </div>\n              ) : (\n                <EmptySection>No services supplied.</EmptySection>\n              )}\n            </div>\n            <ProductLaunchIllustration\n              className=\"mx-auto max-w-sm\"\n              variant=\"services\"\n            />\n          </section>\n\n          <section\n            className={cn(container, \"scroll-mt-8 pb-16 sm:pb-24\")}\n            id={ids.workflow}\n          >\n            <SectionHeading\n              title={copy.workflowTitle}\n              description={copy.workflowDescription}\n              centered\n            />\n            {steps.length ? (\n              <ol className=\"grid gap-6 md:grid-cols-3\">\n                {steps.map((step, index) => (\n                  <li className={cn(card, \"p-6\")} key={step.id}>\n                    <span className=\"mb-5 inline-flex size-10 items-center justify-center rounded-full border border-emerald-200 bg-emerald-50 text-sm font-semibold text-emerald-700 dark:border-emerald-900 dark:bg-emerald-950 dark:text-emerald-300\">\n                      {String(index + 1).padStart(2, \"0\")}\n                    </span>\n                    <h3 className=\"text-lg font-semibold\">{step.title}</h3>\n                    <p className={cn(\"mt-3 text-sm leading-6\", muted)}>\n                      {step.description}\n                    </p>\n                  </li>\n                ))}\n              </ol>\n            ) : (\n              <EmptySection>No workflow steps supplied.</EmptySection>\n            )}\n          </section>\n\n          <section className=\"border-y border-zinc-200 bg-zinc-100 py-12 dark:border-zinc-800 dark:bg-[#1c1917]\">\n            <div\n              className={cn(\n                container,\n                \"flex flex-col items-start justify-between gap-8 lg:flex-row lg:items-center\"\n              )}\n            >\n              <div className=\"max-w-2xl\">\n                <h2 className=\"text-2xl font-semibold tracking-tight sm:text-3xl\">\n                  {copy.ctaTitle}\n                </h2>\n                <p className={cn(\"mt-4 text-sm leading-6\", muted)}>\n                  {copy.ctaDescription}\n                </p>\n              </div>\n              <div className=\"flex max-w-full flex-wrap gap-3 lg:max-w-sm\">\n                <PageAction href={primaryHref}>\n                  {copy.primaryLabel}\n                  <MoveUpRight aria-hidden className=\"size-4 shrink-0\" />\n                </PageAction>\n              </div>\n            </div>\n          </section>\n\n          <section className={cn(container, \"py-16 sm:py-24\")}>\n            <SectionHeading title={copy.testimonialsTitle} />\n            {testimonials.length ? (\n              <div className=\"grid items-start gap-6 md:grid-cols-3\">\n                {testimonials.map((testimonial) => (\n                  <figure className={cn(card, \"p-6\")} key={testimonial.id}>\n                    <figcaption className=\"mb-5 flex items-center gap-3\">\n                      <Initials name={testimonial.name} />\n                      <span className=\"min-w-0\">\n                        <span className=\"block text-sm font-semibold\">\n                          {testimonial.name}\n                        </span>\n                        {testimonial.role && (\n                          <span\n                            className={cn(\n                              \"mt-1 block text-xs leading-5\",\n                              muted\n                            )}\n                          >\n                            {testimonial.role}\n                          </span>\n                        )}\n                      </span>\n                    </figcaption>\n                    <blockquote className=\"text-sm leading-7\">\n                      “{testimonial.quote}”\n                    </blockquote>\n                  </figure>\n                ))}\n              </div>\n            ) : (\n              <EmptySection>No customer testimonials supplied.</EmptySection>\n            )}\n          </section>\n\n          <section\n            className={cn(container, \"scroll-mt-8 pb-16 sm:pb-24\")}\n            id={ids.pricing}\n          >\n            <SectionHeading\n              title={copy.pricingTitle}\n              description={copy.pricingDescription}\n              centered\n            />\n            {hasMonthly && hasAnnual && (\n              <div\n                aria-label=\"Billing period\"\n                className=\"mx-auto mb-10 flex w-fit max-w-full gap-1 rounded-xl border border-zinc-200 bg-zinc-100 p-1 dark:border-zinc-800 dark:bg-zinc-900\"\n                role=\"group\"\n              >\n                {([\"monthly\", \"annual\"] as const).map((value) => (\n                  <button\n                    aria-pressed={period === value}\n                    className={cn(\n                      \"min-h-11 rounded-lg px-5 py-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 disabled:cursor-not-allowed disabled:opacity-50\",\n                      period === value &&\n                        \"bg-white font-semibold shadow-sm dark:bg-zinc-800\"\n                    )}\n                    disabled={\n                      props.billingPeriod !== undefined &&\n                      (demo || !props.onBillingPeriodChange)\n                    }\n                    key={value}\n                    onClick={() => changePeriod(value)}\n                    type=\"button\"\n                  >\n                    {value === \"monthly\" ? \"Monthly\" : \"Annual\"}\n                  </button>\n                ))}\n              </div>\n            )}\n            {plans.length ? (\n              <div className=\"grid items-stretch gap-6 md:grid-cols-3\">\n                {plans.map((plan) => {\n                  const price = productLaunchPrice(plan, period);\n                  const busy = planAction.status === \"pending\";\n                  return (\n                    <article\n                      className={cn(\n                        card,\n                        \"flex min-w-0 flex-col p-6\",\n                        plan.highlighted &&\n                          \"border-emerald-500 ring-1 ring-emerald-500 dark:border-emerald-400 dark:ring-emerald-400\"\n                      )}\n                      key={plan.id}\n                    >\n                      <div className=\"flex flex-wrap items-start justify-between gap-3\">\n                        <h3 className=\"text-xl font-semibold\">{plan.name}</h3>\n                        {plan.badge && (\n                          <span className=\"rounded-full bg-emerald-100 px-3 py-1 text-xs font-medium text-emerald-800 dark:bg-emerald-900 dark:text-emerald-200\">\n                            {plan.badge}\n                          </span>\n                        )}\n                      </div>\n                      <p className={cn(\"mt-3 text-sm leading-6\", muted)}>\n                        {plan.description}\n                      </p>\n                      <p className=\"mt-6 text-4xl font-semibold tracking-tight\">\n                        {price?.amount ?? \"Not offered\"}\n                        {price?.period && (\n                          <span\n                            className={cn(\"ml-1 text-sm font-normal\", muted)}\n                          >\n                            {price.period}\n                          </span>\n                        )}\n                      </p>\n                      {price?.note && (\n                        <p className={cn(\"mt-2 text-xs leading-5\", muted)}>\n                          {price.note}\n                        </p>\n                      )}\n                      <ul className=\"my-7 flex-1 space-y-4 border-t border-zinc-200 pt-6 dark:border-zinc-800\">\n                        {plan.benefits.map((benefit, index) => (\n                          <li\n                            className=\"flex items-start gap-3 text-sm leading-6\"\n                            key={`${benefit}-${index}`}\n                          >\n                            <Check\n                              aria-hidden\n                              className=\"mt-0.5 size-5 shrink-0 text-emerald-600 dark:text-emerald-400\"\n                            />\n                            <span>{benefit}</span>\n                          </li>\n                        ))}\n                      </ul>\n                      <button\n                        aria-busy={\n                          (busy && planAction.id === plan.id) || undefined\n                        }\n                        aria-disabled={busy || undefined}\n                        className={cn(\n                          actionClass,\n                          plan.highlighted ? greenAction : outlineAction\n                        )}\n                        disabled={!price || (!demo && !onSelectPlan)}\n                        onClick={() => {\n                          if (!busy) void choosePlan(plan);\n                        }}\n                        type=\"button\"\n                      >\n                        {busy && planAction.id === plan.id\n                          ? \"Waiting for host…\"\n                          : planAction.status === \"error\" &&\n                              planAction.id === plan.id\n                            ? \"Try again\"\n                            : (plan.actionLabel ?? \"Choose plan\")}\n                      </button>\n                    </article>\n                  );\n                })}\n              </div>\n            ) : (\n              <EmptySection>\n                No plans supplied. Add your actual prices and billing periods.\n              </EmptySection>\n            )}\n            {planAction.message && (\n              <p\n                className={cn(\n                  \"mt-5 rounded-xl border p-4 text-sm leading-6\",\n                  planAction.status === \"error\"\n                    ? \"border-red-200 text-red-700 dark:border-red-900 dark:text-red-300\"\n                    : \"border-emerald-200 text-emerald-800 dark:border-emerald-900 dark:text-emerald-200\"\n                )}\n                role={planAction.status === \"error\" ? \"alert\" : \"status\"}\n              >\n                {planAction.message}\n              </p>\n            )}\n          </section>\n\n          <section\n            className=\"border-y border-zinc-200 py-16 sm:py-20 dark:border-zinc-800\"\n            id={ids.newsletter}\n          >\n            <div className={cn(container, \"max-w-2xl\")}>\n              <SectionHeading\n                title={copy.newsletterTitle}\n                description={copy.newsletterDescription}\n                centered\n              />\n              <form\n                aria-label=\"Product updates subscription\"\n                className=\"mx-auto max-w-lg\"\n                onSubmit={(event) => {\n                  void subscribe(event);\n                }}\n              >\n                <label\n                  className=\"mb-2 block text-sm font-medium\"\n                  htmlFor={`${ids.newsletter}-email`}\n                >\n                  Email address\n                </label>\n                <div className=\"flex flex-col gap-3 sm:flex-row\">\n                  <input\n                    aria-describedby={`${ids.newsletter}-status`}\n                    autoComplete=\"email\"\n                    className=\"min-h-12 min-w-0 flex-1 rounded-lg border border-zinc-300 bg-zinc-50 px-4 py-3 text-base outline-none focus-visible:border-emerald-500 focus-visible:ring-2 focus-visible:ring-emerald-500/30 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-700 dark:bg-zinc-900\"\n                    disabled={!demo && !onSubscribe}\n                    id={`${ids.newsletter}-email`}\n                    maxLength={320}\n                    name=\"email\"\n                    placeholder=\"you@example.com\"\n                    readOnly={subscription.status === \"pending\"}\n                    required\n                    type=\"email\"\n                  />\n                  <button\n                    aria-busy={subscription.status === \"pending\" || undefined}\n                    aria-disabled={\n                      subscription.status === \"pending\" || undefined\n                    }\n                    className={cn(actionClass, greenAction)}\n                    disabled={!demo && !onSubscribe}\n                    type=\"submit\"\n                  >\n                    {subscription.status === \"pending\"\n                      ? \"Waiting for host…\"\n                      : subscription.status === \"error\"\n                        ? \"Try again\"\n                        : copy.newsletterLabel}\n                  </button>\n                </div>\n                <p\n                  className={cn(\n                    \"mt-3 text-xs leading-5\",\n                    subscription.status === \"error\"\n                      ? \"text-red-700 dark:text-red-300\"\n                      : muted\n                  )}\n                  id={`${ids.newsletter}-status`}\n                  role={subscription.status === \"error\" ? \"alert\" : \"status\"}\n                >\n                  {subscription.message ||\n                    (demo\n                      ? \"Demo only · Email addresses stay in this browser interaction.\"\n                      : !onSubscribe\n                        ? \"No subscription service connected.\"\n                        : \"Your address is submitted only to the connected host service.\")}\n                </p>\n              </form>\n            </div>\n          </section>\n\n          <section\n            className={cn(container, \"scroll-mt-8 py-16 sm:py-24\")}\n            id={ids.faq}\n          >\n            <SectionHeading title={copy.faqTitle} />\n            {faq.length ? (\n              <div className=\"divide-y divide-zinc-200 border-y border-zinc-200 dark:divide-zinc-800 dark:border-zinc-800\">\n                {faq.map((item) => (\n                  <details className=\"group py-5\" key={item.id}>\n                    <summary className=\"flex cursor-pointer list-none items-start justify-between gap-4 rounded-sm py-1 text-base font-medium outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 [&::-webkit-details-marker]:hidden\">\n                      <span>{item.question}</span>\n                      <ChevronDown\n                        aria-hidden\n                        className=\"mt-1 size-4 shrink-0 transition-transform group-open:rotate-180 motion-reduce:transition-none\"\n                      />\n                    </summary>\n                    <p\n                      className={cn(\"max-w-3xl pt-4 text-sm leading-7\", muted)}\n                    >\n                      {item.answer}\n                    </p>\n                  </details>\n                ))}\n              </div>\n            ) : (\n              <EmptySection>No questions and answers supplied.</EmptySection>\n            )}\n          </section>\n        </main>\n\n        <footer className=\"border-t border-zinc-200 py-10 dark:border-zinc-800\">\n          <div className={cn(container, \"grid gap-8 sm:grid-cols-[1fr_auto]\")}>\n            <div>\n              <a\n                className=\"inline-flex items-start gap-2 font-semibold focus-visible:outline-2 focus-visible:outline-emerald-500\"\n                href={`#${ids.top}`}\n              >\n                <PanelsTopLeft\n                  aria-hidden\n                  className=\"mt-0.5 size-5 shrink-0 text-emerald-600 dark:text-emerald-400\"\n                />\n                {brand}\n              </a>\n              <p className={cn(\"mt-3 max-w-md text-sm leading-6\", muted)}>\n                {copy.footerDescription}\n              </p>\n              {props.copyright && (\n                <p className={cn(\"mt-5 text-xs\", muted)}>{props.copyright}</p>\n              )}\n            </div>\n            <nav\n              aria-label=\"Product footer\"\n              className=\"flex max-w-sm flex-wrap items-start gap-x-6 gap-y-4 text-sm\"\n            >\n              {footerLinks.map(\n                (link, index) =>\n                  productLaunchHref(link.href) && (\n                    <a\n                      className=\"rounded-sm hover:text-emerald-600 focus-visible:outline-2 focus-visible:outline-emerald-500\"\n                      href={productLaunchHref(link.href)}\n                      key={`${link.label}-${index}`}\n                    >\n                      {link.label}\n                    </a>\n                  )\n              )}\n            </nav>\n          </div>\n        </footer>\n      </div>\n    </div>\n  );\n}\n",
    "guidance": "Install this complete recipe with its single shadcn command, then import the component using your configured components alias and meta.entry. Matching blocks, helpers, and MIT notices are bundled. Pass your own copy, navigation, data, and callbacks through the published props. Recipes with meta.demoUsage require explicit demo mode for sample data or simulation; controlled mode delegates service work to your application."
  }
}
