> ## Documentation Index
> Fetch the complete documentation index at: https://docs.actionx.top/llms.txt
> Use this file to discover all available pages before exploring further.

# Soft mention

> Blend one subtle product suggestion into a longer answer without breaking the conversational tone.

export const ExampleShowcase = ({defaultExampleId = "text-links", showCatalog = true}) => {
  const products = [{
    id: "lv-pochette",
    name: "Louis Vuitton Pochette Sling PM",
    brand: "Louis Vuitton",
    adCopy: "Perfect anniversary gift. Modern elegance meets timeless luxury.",
    softPlacementCopy: "This compact black bag feels polished and giftable, with a classic chain detail that works for both daily use and dinner plans.",
    displayPrice: "$2,030.00",
    url: "https://us.louisvuitton.com/eng-us/products/pochette-sling-pm-h27-nvprod7150038v/M27822",
    image: "https://images.unsplash.com/photo-1584917865442-de89df76afd3?w=400&h=400&fit=crop",
    imagePosition: "object-center",
    badge: "High intent",
    priceBand: "Ultra luxury",
    matchReason: "A strong fit for someone who likes sleek silhouettes and a polished day-to-night feel.",
    styleTags: ["structured", "sleek", "day-to-night"],
    occasionTags: ["anniversary", "birthday", "holiday"],
    supportingPoints: ["Modern profile with strong visual impact", "Feels premium without looking overly formal", "Easy to style across daytime and evening outfits"],
    scores: {
      versatility: 92,
      statement: 88,
      giftAppeal: 95
    },
    personaFit: {
      minimalist: 96,
      office: 80,
      everyday: 82
    },
    occasionMessages: {
      birthday: "Feels celebratory and visually striking for a birthday gift.",
      anniversary: "The strongest milestone-gift option in this shortlist.",
      holiday: "A festive pick when you want high recognition and polish."
    }
  }, {
    id: "loewe-amazona",
    name: "Large Amazona 180",
    brand: "Loewe",
    adCopy: "Effortlessly sophisticated. A refined gift for someone special.",
    softPlacementCopy: "A softer and more design-led direction if you want the gift to feel tasteful rather than obviously brand-forward.",
    displayPrice: "$3,500.00",
    url: "https://www.loewe.com/int/en/women/bags/amazona/large-amazona-180-bag-in-suede/A039ASRX03-0018.html",
    image: "https://www.loewe.com/dw/image/v2/BBPC_PRD/on/demandware.static/-/Sites-Loewe_master/default/dwbef4ac14/images_rd/A039ASRX03/A039ASRX03-0018/A039ASRX03_0018_3O.jpg?sw=850&q=100",
    imagePosition: "object-center",
    badge: "Editorial",
    priceBand: "High luxury",
    matchReason: "Best for a softer, quieter luxury look with a more relaxed and artistic feel.",
    styleTags: ["soft luxury", "single-handle", "editorial"],
    occasionTags: ["anniversary", "holiday", "everyday-upgrade"],
    supportingPoints: ["Distinct silhouette without looking loud", "Carries a crafted, design-forward identity", "Balances elegance with an effortless attitude"],
    scores: {
      versatility: 84,
      statement: 82,
      giftAppeal: 88
    },
    personaFit: {
      minimalist: 78,
      office: 84,
      everyday: 90
    },
    occasionMessages: {
      birthday: "A thoughtful birthday gift when you want taste over flash.",
      anniversary: "Works well for an anniversary with a more refined tone.",
      holiday: "A sophisticated holiday option with a strong editorial mood."
    }
  }, {
    id: "celine-luggage",
    name: 'Celine "New Luggage"',
    brand: "Celine",
    adCopy: "Iconic and versatile. A gift that works across occasions.",
    softPlacementCopy: "A recognizable icon if you want the recommendation to land with stronger brand recognition while staying clean and wearable.",
    displayPrice: "$3,900.00",
    url: "https://www.celine.com/en-us/celine-shop-women/handbags/new-luggage/",
    image: "https://www.celine.com/on/demandware.static/-/Library-Sites-Celine-SharedLibrary/default/dw68d58acb/content_assets/block%20editor/CELINE_SUMMER_26_4X5_2400PX_02.jpg?im=Resize=(800)",
    imagePosition: "object-top",
    badge: "Brand-led",
    priceBand: "High luxury",
    matchReason: "A reliable recommendation when you want a recognizable icon with a cleaner 2026 update.",
    styleTags: ["iconic", "structured", "professional"],
    occasionTags: ["birthday", "anniversary", "office-style"],
    supportingPoints: ["Strong brand recognition and proven silhouette", "Feels versatile across professional and social settings", "Updated shape keeps it current without losing familiarity"],
    scores: {
      versatility: 90,
      statement: 85,
      giftAppeal: 89
    },
    personaFit: {
      minimalist: 86,
      office: 94,
      everyday: 78
    },
    occasionMessages: {
      birthday: "A dependable birthday gift when you want iconic recognition.",
      anniversary: "A polished anniversary option with broad appeal.",
      holiday: "A structured holiday pick that still feels practical."
    }
  }];
  const categories = [{
    id: "soft-placement",
    label: "Soft placement",
    description: "Keep the recommendation subtle by blending products into a longer, more natural answer."
  }, {
    id: "answer-native",
    label: "Answer-native",
    description: "Keep the recommendation inside the answer flow and progressively enrich it."
  }, {
    id: "browsing-native",
    label: "Browsing-native",
    description: "Turn the same response into layouts built for exploration and discovery."
  }, {
    id: "decision-support",
    label: "Decision-support",
    description: "Help users compare, filter, and shortlist before they take the next step."
  }, {
    id: "action-extensions",
    label: "Action-extensions",
    description: "Extend the recommendation into service, wishlist, and follow-up actions."
  }];
  const accentByCategory = {
    "soft-placement": "border-rose-700 bg-rose-700 text-white",
    "answer-native": "border-slate-900 bg-slate-900 text-white",
    "browsing-native": "border-amber-700 bg-amber-700 text-white",
    "decision-support": "border-emerald-700 bg-emerald-700 text-white",
    "action-extensions": "border-indigo-700 bg-indigo-700 text-white"
  };
  const demos = [{
    id: "soft-mention",
    categoryId: "soft-placement",
    label: "Soft mention",
    eyebrow: "Subtle handoff",
    title: "Blend one product suggestion into the answer without breaking the tone",
    description: "Keep the response centered on advice, then softly introduce one relevant item as a natural follow-up instead of switching immediately into a shopping layout.",
    notes: ["Best when you want the recommendation to feel like part of the assistant response, not a separate ad module.", "A good bridge pattern before stronger commerce treatments such as links, cards, or shelves."],
    code: `return (
  <>
    <AdviceAnswer />
    <SoftMentionCard product={products[0]} />
  </>
);`
  }, {
    id: "contextual-entity-links",
    categoryId: "soft-placement",
    label: "Contextual entity links",
    eyebrow: "Inline entities",
    title: "Embed linked brands and products directly inside the answer context",
    description: "Keep the recommendation paragraph readable, but turn the relevant brands and product entities into links so the commercial surface stays soft and contextual.",
    notes: ["Useful when the answer should still read like advice, with commerce appearing only on the important entities.", "A good pattern for editorial assistants or brand-aware recommendation flows."],
    code: `return (
  <AnswerParagraph>
    <ContextEntityLinks products={products} />
  </AnswerParagraph>
);`
  }, {
    id: "contextual-links-with-ad-hover",
    categoryId: "soft-placement",
    label: "Contextual links with ad hover",
    eyebrow: "Hover disclosure",
    title: "Keep inline entity links subtle, then reveal the product card from a tiny AD marker",
    description: "Let the answer stay paragraph-first, attach a small AD marker to the linked entity, and show the richer product card only when the user intentionally hovers or focuses that marker.",
    notes: ["Useful when you want the commercial cue to be visible but still much softer than an always-on card.", "A good pattern for conversational surfaces where disclosure matters but UI noise should stay low."],
    code: `return (
  <AnswerParagraph>
    <LinkedEntityWithAdHint product={products[0]} />
  </AnswerParagraph>
);`
  }, {
    id: "clickable-recommendation-block",
    categoryId: "soft-placement",
    label: "Clickable recommendation block",
    eyebrow: "Larger hit area",
    title: "Turn one recommendation paragraph into a softer click target",
    description: "Instead of linking only a few words, make the recommendation paragraph itself clickable so the commercial action is easier without feeling like a separate card.",
    notes: ["Useful when one paragraph is clearly the recommendation section and deserves a larger tap target.", "Good for mobile surfaces where larger hit areas improve usability without adding hard merchandising UI."],
    code: `return (
  <RecommendationBlockLink product={products[0]}>
    {products[0].matchReason}
  </RecommendationBlockLink>
);`
  }, {
    id: "inline-recommendation-insert",
    categoryId: "soft-placement",
    label: "Inline recommendation insert",
    eyebrow: "Embedded card",
    title: "Insert one compact recommendation card into a longer answer",
    description: "Keep the answer content-rich, then place one lightweight product card inside the response exactly where the recommendation becomes relevant.",
    notes: ["Useful when the answer has several advice points and only one section naturally transitions into a product suggestion.", "Very close to assistant-style soft placement because the product sits inside the flow rather than below it."],
    code: `return (
  <AdviceList>
    <InlineRecommendationInsert product={products[0]} />
  </AdviceList>
);`
  }, {
    id: "text-links",
    categoryId: "answer-native",
    label: "Text links",
    eyebrow: "Lightweight rendering",
    title: "Turn product mentions into clickable commerce links",
    description: "Keep the answer mostly text-first and only upgrade product names into links for the lightest possible enrichment layer.",
    notes: ["Best when you want almost no visual interruption inside a chat interface.", "Useful as the lowest-effort integration before experimenting with richer cards."],
    code: `products.map((product) => (
  <LinkedProductName key={product.id} product={product} />
));`
  }, {
    id: "inline-cards",
    categoryId: "answer-native",
    label: "Inline product cards",
    eyebrow: "Contextual enrichment",
    title: "Insert richer product blocks directly after the answer",
    description: "Preserve the conversational answer, then attach visual cards that make the next click much easier.",
    notes: ["Good when the answer still carries most of the meaning but visuals improve engagement.", "A common step up from plain text links in assistant-style products."],
    code: `return (
  <>
    <AnswerBody />
    <InlineCards products={products} />
  </>
);`
  }, {
    id: "reason-chips",
    categoryId: "answer-native",
    label: "Reason chips",
    eyebrow: "Explainability",
    title: "Add compact reason chips after each recommendation",
    description: "Use short tags to explain why an item matches the query without forcing users to read longer paragraphs.",
    notes: ["Helpful when recommendation quality needs a visible explanation layer.", "Chips are especially effective on mobile because they scan quickly."],
    code: `return (
  <>
    <p>{product.matchReason}</p>
    <ReasonChips product={product} />
  </>
);`
  }, {
    id: "expandable-picks",
    categoryId: "answer-native",
    label: "Expandable picks",
    eyebrow: "Progressive disclosure",
    title: "Collapse the shortlist until the user asks for more detail",
    description: "Show only the product names first, then let users expand each recommendation to inspect the supporting reasons.",
    notes: ["Useful when answer density matters and the first screen should stay compact.", "Works well in docs because the interaction cost is low and easy to explain."],
    code: `products.map((product) => (
  <ExpandablePick key={product.id} product={product} />
));`
  }, {
    id: "hover-sync-answer",
    categoryId: "answer-native",
    label: "Hover sync answer",
    eyebrow: "Linked interaction",
    title: "Sync answer reasoning with a richer product panel",
    description: "Link a compact answer summary on the left to a richer visual card on the right using hover or focus.",
    notes: ["Good for desktop-first docs that want to show coordinated UI states.", "Makes the relation between reasoning and product presentation more explicit."],
    code: `const [activeId, setActiveId] = useState(products[0].id);
<AnswerList onHover={setActiveId} />
<ProductPanel product={activeProduct} />`
  }, {
    id: "chat-carousel",
    categoryId: "answer-native",
    label: "Chat carousel",
    eyebrow: "Touch-friendly browsing",
    title: "Keep the chat context and move the products into a scrollable shelf",
    description: "This keeps the answer bubble intact but turns the product layer into a horizontally scrollable sequence of cards.",
    notes: ["Good for mobile experiences and chat interfaces with constrained vertical space.", "Feels native to touch interactions without abandoning the chat framing."],
    code: `return (
  <ProductCarousel products={products} />
);`
  }, {
    id: "product-gallery",
    categoryId: "browsing-native",
    label: "Product gallery",
    eyebrow: "Direct merchandising",
    title: "Render the same items as a standalone recommendation grid",
    description: "Move from answer-first to browsing-first by presenting the same product payload as a clear grid.",
    notes: ["A simple and reliable option for landing pages or post-answer recommendation zones.", "Good baseline for comparing richer browsing layouts against a straightforward grid."],
    code: `return <ProductGallery products={products} />;`
  }, {
    id: "editorial-spotlight",
    categoryId: "browsing-native",
    label: "Editorial spotlight",
    eyebrow: "Hero-led browsing",
    title: "Promote one hero item while keeping supporting picks nearby",
    description: "Use a large feature card for the strongest recommendation and smaller slots for alternatives.",
    notes: ["Works well for content-driven surfaces or brand-heavy presentation.", "Useful when one product clearly deserves more attention than the rest."],
    code: `return (
  <Spotlight hero={products[0]} supporting={products.slice(1)} />
);`
  }, {
    id: "bento-discovery-grid",
    categoryId: "browsing-native",
    label: "Bento discovery grid",
    eyebrow: "Exploration-first",
    title: "Use an editorial bento layout to make a small catalog feel richer",
    description: "A bento grid gives the content more shape and makes the recommendation feel like a discovery surface instead of a utility list.",
    notes: ["Useful for demoing how layout alone changes the perceived product experience.", "Can make even three products feel more dynamic and premium."],
    code: `return (
  <BentoGrid featured={products[0]} sideItems={products.slice(1)} />
);`
  }, {
    id: "lookbook-strip",
    categoryId: "browsing-native",
    label: "Lookbook strip",
    eyebrow: "Image-first storytelling",
    title: "Push imagery forward with a lookbook-style presentation",
    description: "Emphasize photography and mood so the same product data reads more like an editorial feature.",
    notes: ["Best for lifestyle-led merchandising or inspirational landing pages.", "Useful for showing that docs can host presentation-heavy UI, not just reference layouts."],
    code: `products.map((product) => (
  <LookbookCard key={product.id} product={product} />
));`
  }, {
    id: "compact-result-shelf",
    categoryId: "browsing-native",
    label: "Compact result shelf",
    eyebrow: "Dense placement",
    title: "Shrink the same recommendations into a tight recommendation row",
    description: "Render a denser shelf that could live inside a results page, side rail, or lower-priority placement.",
    notes: ["Good for embedding product recommendations in tighter layout constraints.", "Helps show how one payload can scale down without needing a different backend response."],
    code: `products.map((product) => (
  <ShelfCard key={product.id} product={product} />
));`
  }, {
    id: "comparison-table",
    categoryId: "decision-support",
    label: "Comparison table",
    eyebrow: "Structured trade-offs",
    title: "Compare style, occasion, and gifting logic in one table",
    description: "Turn descriptive product data into a comparison matrix for users who are already evaluating trade-offs.",
    notes: ["Tables are often easier to scan than cards once the user is in decision mode.", "This is especially useful for partner docs explaining recommendation rationale."],
    code: `return (
  <ComparisonTable rows={attributes} products={products} />
);`
  }, {
    id: "attribute-scorecards",
    categoryId: "decision-support",
    label: "Attribute scorecards",
    eyebrow: "Visible ranking logic",
    title: "Expose recommendation criteria through compact scorecards",
    description: "Make hidden ranking dimensions more legible by visualizing scores such as versatility, statement value, and gift appeal.",
    notes: ["Helpful when the user wants to understand why one product ranks higher than another.", "A good way to turn abstract recommendation logic into something concrete and visual."],
    code: `return (
  <Scorecard product={product} scores={product.scores} />
);`
  }, {
    id: "persona-matcher",
    categoryId: "decision-support",
    label: "Persona matcher",
    eyebrow: "Intent adaptation",
    title: "Re-rank the shortlist around different user personas",
    description: "Let readers switch between personas such as minimalist, office style, or daily use and see the same data reprioritized.",
    notes: ["Useful for showing how one product set can serve multiple audience lenses.", "Demonstrates lightweight interactivity without involving external data."],
    code: `const ranked = sortBy(products, product => product.personaFit[persona]);
return <PersonaRankedList products={ranked} />;`
  }, {
    id: "occasion-switcher",
    categoryId: "decision-support",
    label: "Occasion switcher",
    eyebrow: "Context switching",
    title: "Change the framing for birthday, anniversary, or holiday gifting",
    description: "Keep the same products but change the message and ordering depending on the occasion context.",
    notes: ["Good for recommendation systems that adapt copy rather than inventory.", "Shows how local UI logic can personalize the same backend result set."],
    code: `const ordered = rankByOccasion(products, activeOccasion);
return <OccasionView products={ordered} />;`
  }, {
    id: "shortlist-compare-tray",
    categoryId: "decision-support",
    label: "Shortlist compare tray",
    eyebrow: "Mid-funnel state",
    title: "Let users save two or three picks before comparing them",
    description: "Simulate the common mid-funnel moment where users gather likely candidates before making a decision.",
    notes: ["Good for illustrating shortlist behavior inside docs without building a full compare page.", "Adds just enough interactivity to make the experience feel product-like."],
    code: `const selectedProducts = products.filter((product) =>
  selectedIds.includes(product.id)
);

return <ShortlistTray selectedProducts={selectedProducts} />;`
  }, {
    id: "follow-up-service-card",
    categoryId: "action-extensions",
    label: "Follow-up service card",
    eyebrow: "Extended action",
    title: "Attach a post-recommendation service module",
    description: "Add a second action layer after the recommendation, such as premium gift wrapping or another add-on flow.",
    notes: ["Good for showing how a recommendation can hand off into a monetizable secondary action.", "Keeps the interaction simple while proving the docs page can host a coded module."],
    code: `return (
  <ServiceCard title="Premium gift wrapping" />
);`
  }, {
    id: "gift-bundle-builder",
    categoryId: "action-extensions",
    label: "Gift bundle builder",
    eyebrow: "Configurable add-ons",
    title: "Turn a recommendation into a light bundle-building flow",
    description: "Keep the product recommendation on screen while letting the user configure simple gift options.",
    notes: ["Useful for service add-ons that naturally follow a product recommendation.", "Helps teams imagine how the recommendation area can branch into configuration UI."],
    code: `const [selectedOptions, setSelectedOptions] = useState(["wrap"]);
return <BundleBuilder options={selectedOptions} />;`
  }, {
    id: "save-or-share-wishlist",
    categoryId: "action-extensions",
    label: "Save or share wishlist",
    eyebrow: "Intent capture",
    title: "Convert recommendations into a lightweight wishlist surface",
    description: "Give the user a softer action than purchase by offering save, share, or copy-style follow-ups.",
    notes: ["Best when the recommendation is valuable but the user is not ready to act immediately.", "Makes the recommendation module useful even before a final decision happens."],
    code: `return (
  <WishlistActions actions={["save", "share", "copy"]} />
);`
  }, {
    id: "next-step-action-sheet",
    categoryId: "action-extensions",
    label: "Next-step action sheet",
    eyebrow: "Clear handoff",
    title: "Surface the likely next actions in a compact action sheet",
    description: "Add a bottom action layer that makes the handoff from recommendation to user action explicit.",
    notes: ["Useful when you want to keep the answer area focused but still show the main downstream actions.", "A clean pattern for docs that want to simulate a fuller product journey."],
    code: `return (
  <ActionSheet actions={["view", "compare", "service"]} />
);`
  }];
  const supportingCodeByDemoId = {
    "soft-mention": {
      title: "SoftMentionCard",
      code: `function SoftMentionCard({ product }) {
  return (
    <a className="border-none" href={product.url} rel="noreferrer" target="_blank">
      <div className="flex items-center gap-3 rounded-2xl border border-slate-200 bg-white p-3">
        <ProductImage product={product} size="thumb" />
        <div className="min-w-0 flex-1">
          <p className="text-sm font-semibold">{product.name}</p>
          <p className="mt-1 text-xs text-slate-500">{product.adCopy}</p>
        </div>
        <span className="text-xs text-slate-400">Open</span>
      </div>
    </a>
  );
}`
    },
    "contextual-entity-links": {
      title: "ContextEntityLinks",
      code: `function ContextEntityLinks({ products }) {
  return (
    <p>
      If you want one polished option,{" "}
      <a className="border-none text-primary no-underline hover:underline" href={products[0].url}>
        {products[0].brand}
      </a>{" "}
      and{" "}
      <a className="border-none text-primary no-underline hover:underline" href={products[1].url}>
        {products[1].name}
      </a>{" "}
      are easy starting points because they feel giftable without overwhelming the answer.
    </p>
  );
}`
    },
    "contextual-links-with-ad-hover": {
      title: "LinkedEntityWithAdHint",
      code: `function LinkedEntityWithAdHint({ product, children }) {
  return (
    <span className="inline-flex items-center gap-1">
      <a className="border-none text-primary no-underline hover:underline" href={product.url}>
        {children}
      </a>
      <span className="group relative inline-flex">
        <span className="inline-flex cursor-default rounded-full bg-amber-500 px-1 py-[1px] text-[9px] font-semibold uppercase leading-none text-white">
          AD
        </span>
        <span className="absolute left-0 top-full z-10 mt-2 hidden w-[32rem] max-w-[calc(100vw-4rem)] group-hover:block group-focus-within:block">
          <SoftPlacementCard product={product} />
        </span>
      </span>
    </span>
  );
}`
    },
    "clickable-recommendation-block": {
      title: "RecommendationBlockLink",
      code: `function RecommendationBlockLink({ product, children }) {
  return (
    <a className="border-none block" href={product.url} rel="noreferrer" target="_blank">
      <p className="rounded-xl px-1 py-1 text-sm leading-7 text-slate-700 transition hover:bg-amber-50/60">
        <span className="font-medium text-slate-900">
          {children}
        </span>
      </p>
    </a>
  );
}`
    },
    "inline-recommendation-insert": {
      title: "InlineRecommendationInsert",
      code: `function InlineRecommendationInsert({ product }) {
  return (
    <div className="my-4">
      <SoftPlacementCard
        description={product.softPlacementCopy}
        product={product}
      />
    </div>
  );
}`
    },
    "text-links": {
      title: "LinkedProductName",
      code: `function LinkedProductName({ product }) {
  return (
    <a className="border-none" href={product.url} rel="noreferrer" target="_blank">
      {product.name}
    </a>
  );
}`
    },
    "inline-cards": {
      title: "InlineCards",
      code: `function InlineCards({ products }) {
  return (
    <div className="grid gap-4">
      {products.map((product) => (
        <a className="border-none" href={product.url} key={product.id} rel="noreferrer" target="_blank">
          <ProductCard compact product={product} />
        </a>
      ))}
    </div>
  );
}`
    },
    "reason-chips": {
      title: "ReasonChips",
      code: `function ReasonChips({ product }) {
  return (
    <div className="flex flex-wrap gap-2">
      {product.styleTags.map((tag) => (
        <span className="rounded-full border px-2.5 py-1 text-xs" key={tag}>
          {tag}
        </span>
      ))}
    </div>
  );
}`
    },
    "expandable-picks": {
      title: "ExpandablePick",
      code: `function ExpandablePick({ product }) {
  return (
    <details className="rounded-2xl border p-4">
      <summary className="cursor-pointer font-semibold">{product.name}</summary>
      <p className="mt-3 text-sm">{product.matchReason}</p>
      <ul className="mt-3 space-y-2">
        {product.supportingPoints.map((point) => (
          <li className="rounded-xl bg-slate-50 px-3 py-2" key={point}>
            {point}
          </li>
        ))}
      </ul>
    </details>
  );
}`
    },
    "hover-sync-answer": {
      title: "ProductPanel",
      code: `function ProductPanel({ product }) {
  return (
    <div className="rounded-2xl border bg-slate-900 p-4 text-white">
      <ProductCard compact emphasis product={product} />
      <div className="mt-4 space-y-2">
        {product.supportingPoints.map((point) => (
          <div className="rounded-xl bg-white/10 px-3 py-2 text-sm" key={point}>
            {point}
          </div>
        ))}
      </div>
    </div>
  );
}`
    },
    "chat-carousel": {
      title: "ProductCarousel",
      code: `function ProductCarousel({ products }) {
  return (
    <div className="overflow-x-auto pb-2">
      <div className="flex gap-4">
        {products.map((product) => (
          <a className="w-72 shrink-0 border-none" href={product.url} key={product.id}>
            <ProductCard product={product} />
          </a>
        ))}
      </div>
    </div>
  );
}`
    },
    "product-gallery": {
      title: "ProductGallery",
      code: `function ProductGallery({ products }) {
  return (
    <div className="grid gap-4 lg:grid-cols-3">
      {products.map((product) => (
        <a className="border-none" href={product.url} key={product.id} rel="noreferrer" target="_blank">
          <ProductCard product={product} />
        </a>
      ))}
    </div>
  );
}`
    },
    "editorial-spotlight": {
      title: "Spotlight",
      code: `function Spotlight({ hero, supporting }) {
  return (
    <div className="grid gap-4 lg:grid-cols-3">
      <div className="lg:col-span-2">
        <ProductCard emphasis product={hero} />
      </div>
      <div className="grid gap-4">
        {supporting.map((product) => (
          <ProductCard compact key={product.id} product={product} />
        ))}
      </div>
    </div>
  );
}`
    },
    "bento-discovery-grid": {
      title: "BentoGrid",
      code: `function BentoGrid({ featured, sideItems }) {
  return (
    <div className="grid gap-4 lg:grid-cols-3">
      <div className="rounded-3xl bg-slate-900 p-5 text-white lg:col-span-2">
        <ProductImage product={featured} size="hero" />
        <h3 className="mt-4 text-2xl font-semibold">{featured.name}</h3>
      </div>
      <div className="grid gap-4">
        {sideItems.map((product) => (
          <CompactDiscoveryCard key={product.id} product={product} />
        ))}
      </div>
    </div>
  );
}`
    },
    "lookbook-strip": {
      title: "LookbookCard",
      code: `function LookbookCard({ product }) {
  return (
    <a className="group relative overflow-hidden rounded-3xl border border-none" href={product.url}>
      <ProductImage imageClassName="transition group-hover:scale-105" product={product} size="tall" />
      <div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/80 to-transparent p-5 text-white">
        <p className="text-lg font-semibold">{product.name}</p>
      </div>
    </a>
  );
}`
    },
    "compact-result-shelf": {
      title: "ShelfCard",
      code: `function ShelfCard({ product }) {
  return (
    <a className="w-64 shrink-0 rounded-2xl border bg-white p-3 border-none" href={product.url}>
      <ProductImage product={product} size="shelf" />
      <p className="mt-3 text-sm font-semibold">{product.name}</p>
      <p className="mt-1 text-xs text-slate-500">{product.brand}</p>
    </a>
  );
}`
    },
    "comparison-table": {
      title: "ComparisonTable",
      code: `function ComparisonTable({ products }) {
  return (
    <table className="min-w-full text-left text-sm">
      <thead>
        <tr>
          <th>Attribute</th>
          {products.map((product) => (
            <th key={product.id}>{product.brand}</th>
          ))}
        </tr>
      </thead>
      <tbody>{/* render rows from product fields */}</tbody>
    </table>
  );
}`
    },
    "attribute-scorecards": {
      title: "Scorecard",
      code: `function Scorecard({ product }) {
  return (
    <div className="rounded-3xl border bg-white p-5 shadow-sm">
      <p className="text-base font-semibold">{product.name}</p>
      <ScoreBar label="Versatility" value={product.scores.versatility} />
      <ScoreBar label="Statement" value={product.scores.statement} />
      <ScoreBar label="Gift appeal" value={product.scores.giftAppeal} />
    </div>
  );
}`
    },
    "persona-matcher": {
      title: "PersonaRankedList",
      code: `function PersonaRankedList({ persona, products }) {
  const ranked = [...products].sort(
    (left, right) => right.personaFit[persona] - left.personaFit[persona]
  );

  return ranked.map((product) => (
    <ProductCard compact key={product.id} product={product} />
  ));
}`
    },
    "occasion-switcher": {
      title: "OccasionView",
      code: `function OccasionView({ activeOccasion, products }) {
  return (
    <div className="grid gap-4 lg:grid-cols-3">
      {products.map((product) => (
        <div className="rounded-3xl border bg-white p-4" key={product.id}>
          <ProductImage product={product} size="card" />
          <p className="mt-4 text-base font-semibold">{product.name}</p>
          <p className="mt-2 text-sm">{product.occasionMessages[activeOccasion]}</p>
        </div>
      ))}
    </div>
  );
}`
    },
    "shortlist-compare-tray": {
      title: "ShortlistTray",
      code: `function ShortlistTray({ selectedProducts }) {
  return (
    <div className="rounded-3xl border bg-slate-50 p-4">
      <div className="flex flex-wrap gap-2">
        {selectedProducts.map((product) => (
          <span className="rounded-full bg-slate-900 px-3 py-1 text-xs text-white" key={product.id}>
            {product.name}
          </span>
        ))}
      </div>
      <button className="mt-4 rounded-full bg-slate-900 px-4 py-2 text-sm text-white" type="button">
        Compare shortlist
      </button>
    </div>
  );
}`
    },
    "follow-up-service-card": {
      title: "ServiceCard",
      code: `function ServiceCard() {
  return (
    <div className="rounded-3xl bg-gradient-to-r from-slate-900 to-slate-700 p-5 text-white">
      <p className="text-xs uppercase tracking-widest text-slate-300">Follow-up service</p>
      <h3 className="mt-2 text-xl font-semibold">Premium gift wrapping</h3>
      <p className="mt-3 text-sm text-slate-200">
        Add a handwritten card, premium wrapping, and delivery timing preferences.
      </p>
    </div>
  );
}`
    },
    "gift-bundle-builder": {
      title: "BundleBuilder",
      code: `function BundleBuilder({ options, selectedOptions, onToggle }) {
  return (
    <div className="grid gap-3">
      {options.map((option) => (
        <button
          className="rounded-2xl border px-4 py-3 text-left text-sm"
          key={option.id}
          onClick={() => onToggle(option.id)}
          type="button"
        >
          {option.label}
        </button>
      ))}
    </div>
  );
}`
    },
    "save-or-share-wishlist": {
      title: "WishlistActions",
      code: `function WishlistActions() {
  return (
    <div className="grid gap-3">
      <button className="rounded-full bg-white px-4 py-2 text-sm font-semibold text-slate-900" type="button">
        Save list
      </button>
      <button className="rounded-full border border-white/20 px-4 py-2 text-sm font-semibold text-white" type="button">
        Share with a friend
      </button>
      <button className="rounded-full border border-white/20 px-4 py-2 text-sm font-semibold text-white" type="button">
        Copy summary
      </button>
    </div>
  );
}`
    },
    "next-step-action-sheet": {
      title: "ActionSheet",
      code: `function ActionSheet() {
  return (
    <div className="rounded-3xl border border-slate-200 bg-slate-900 p-4 text-white">
      <div className="flex flex-wrap gap-2">
        <button className="rounded-full bg-white px-4 py-2 text-sm font-semibold text-slate-900" type="button">
          View details
        </button>
        <button className="rounded-full border border-white/20 px-4 py-2 text-sm font-semibold text-white" type="button">
          Compare now
        </button>
        <button className="rounded-full border border-white/20 px-4 py-2 text-sm font-semibold text-white" type="button">
          Add service
        </button>
      </div>
    </div>
  );
}`
    }
  };
  const getDemosByCategory = categoryId => demos.filter(demo => demo.categoryId === categoryId);
  const getProductById = productId => products.find(product => product.id === productId) || products[0];
  const getSortedProductsByPersona = persona => [...products].sort((left, right) => right.personaFit[persona] - left.personaFit[persona]);
  const getSortedProductsByOccasion = occasion => [...products].sort((left, right) => {
    const leftScore = left.occasionTags.includes(occasion) ? 1 : 0;
    const rightScore = right.occasionTags.includes(occasion) ? 1 : 0;
    return rightScore - leftScore;
  });
  const renderBoldSegments = text => {
    const segments = text.split(/(\*\*.*?\*\*)/g).filter(Boolean);
    return segments.map((segment, index) => {
      if (segment.startsWith("**") && segment.endsWith("**")) {
        return <strong key={`${segment}-${index}`}>{segment.slice(2, -2)}</strong>;
      }
      return <span key={`${segment}-${index}`}>{segment}</span>;
    });
  };
  const tag = (content, tone = "default", key) => {
    const toneClass = ({
      default: "border-slate-200 bg-white text-slate-600",
      warm: "border-amber-200 bg-amber-50 text-amber-800",
      dark: "border-slate-800 bg-slate-900 text-white",
      subtle: "border-slate-200 bg-slate-50 text-slate-600",
      success: "border-emerald-200 bg-emerald-50 text-emerald-800",
      indigo: "border-indigo-200 bg-indigo-50 text-indigo-700"
    })[tone] || "border-slate-200 bg-white text-slate-600";
    return <span className={`inline-flex rounded-full border px-2.5 py-1 text-xs font-medium ${toneClass}`} key={key}>
        {content}
      </span>;
  };
  const productImage = ({product, alt = product.name, frameClassName = "h-56 w-full bg-white", frameStyle, imageClassName = "", eager = false}) => <div className={`relative max-w-[full] max-h-[400px] overflow-hidden rounded-2xl bg-slate-100 ${frameClassName}`} style={frameStyle}>
      <img alt={alt} className={`m-0 block h-full w-full ${product.imagePosition || "object-center"} object-cover ${imageClassName}`} decoding="async" loading={eager ? "eager" : "lazy"} src={product.image} />
    </div>;
  const softPlacementCard = ({product, description = product.softPlacementCopy || product.adCopy, badge = "AD"}) => <a className="border-none block" href={product.url} rel="noreferrer" target="_blank">
      <div className="rounded-2xl border border-amber-200 bg-gradient-to-br from-amber-50 to-white p-3 shadow-[0_10px_30px_rgba(217,119,6,0.12)] transition hover:border-amber-300">
        <div className="flex gap-3">
          {productImage({
    frameClassName: "shrink-0 bg-white",
    frameStyle: {
      height: "5.5rem",
      minWidth: "5.5rem",
      width: "5.5rem"
    },
    product
  })}
          <div className="min-w-0 flex-1">
            <div className="flex flex-wrap items-center justify-between gap-2">
              <div className="flex items-center gap-2">
                <span className="inline-flex rounded-full bg-amber-600 px-1.5 py-[1px] text-[9px] font-semibold uppercase leading-none text-white">
                  {badge}
                </span>
                <span className="truncate text-sm font-semibold text-slate-900">{product.name}</span>
              </div>
              <span className="shrink-0 rounded-full bg-amber-100 px-2.5 py-1 text-xs font-semibold text-amber-800">
                {product.displayPrice}
              </span>
            </div>
            <p className="mt-2 text-xs leading-5 text-slate-600">{description}</p>
          </div>
        </div>
      </div>
    </a>;
  const scoreBar = ({label, value}) => <div className="space-y-1.5">
      <div className="flex items-center justify-between text-xs font-medium text-slate-600">
        <span>{label}</span>
        <span>{value}</span>
      </div>
      <div className="h-2 rounded-full bg-slate-200">
        <div className="h-2 rounded-full bg-slate-900" style={{
    width: `${value}%`
  }} />
      </div>
    </div>;
  const previewFrame = ({children, footer}) => <div className="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm">
      <div className="border-b border-slate-200 bg-slate-50 px-5 py-4">
        <div className="flex items-start gap-3">
          <span className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-slate-900 text-sm font-semibold text-white">
            U
          </span>
          <p className="rounded-2xl bg-slate-900 px-4 py-3 text-sm leading-6 text-white">
            Please help me find some handbags. I am planning to buy them as gifts for my girlfriend.
          </p>
        </div>
      </div>
      <div className="space-y-5 px-5 py-5">{children}</div>
      {footer ? <div className="border-t border-slate-200 bg-slate-50 px-5 py-4">{footer}</div> : null}
    </div>;
  const productCard = ({product, compact = false, emphasis = false, frameStyle = null, footer}) => <div className={`overflow-hidden rounded-2xl border ${emphasis ? "border-slate-900 bg-slate-900 text-white shadow-md" : "border-amber-200 bg-amber-50 text-slate-900 shadow-sm"}`}>
      <div className={compact ? "flex gap-4 p-4" : "p-4"}>
        <div className={compact ? "shrink-0" : "mb-4"}>
          {productImage({
    eager: !compact,
    frameClassName: compact ? "shrink-0 bg-white" : "max-h-[200px] max-w-[200px] w-full bg-white",
    frameStyle: frameStyle || (compact ? {
      height: "6rem",
      minWidth: "6rem",
      width: "6rem"
    } : undefined),
    product
  })}
        </div>
        <div className="min-w-0 flex-1">
          <div className="mb-2 flex flex-wrap items-center gap-2">
            {tag(product.badge, emphasis ? "dark" : "warm")}
            {tag(product.priceBand, emphasis ? "subtle" : "default")}
          </div>
          <p className={`mb-1 text-base w-full block font-semibold ${emphasis ? "text-white" : "text-slate-900"}`}>
            {product.name}
          </p>
          <p className={`mb-2 text-sm ${emphasis ? "text-slate-200" : "text-slate-600"}`}>
            {product.brand}
          </p>
          <p className={`text-sm leading-6 ${emphasis ? "text-slate-200" : "text-slate-600"}`}>
            {product.adCopy}
          </p>
          {footer ? <div className="mt-4">{footer}</div> : null}
        </div>
      </div>
    </div>;
  const answerList = ({linkNames = false, showReasonChips = false, highlightProductId, itemAction}) => <div className="flex gap-3">
      <span className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-sm font-semibold text-slate-700">
        AX
      </span>
      <div className="min-w-0 flex-1 rounded-2xl bg-slate-50 p-4 text-sm leading-6 text-slate-700">
        <p className="mb-2 text-base font-semibold text-slate-900">2026 handbag gifting guide</p>
        <p className="mb-4">
          If you are looking for a milestone gift, these three handbag directions cover the strongest combinations of style, recognition, and gifting potential.
        </p>
        <ul className="space-y-3">
          {products.map(product => {
    const isActive = highlightProductId === product.id;
    return <li className={`rounded-2xl border px-4 py-3 transition ${isActive ? "border-slate-900 bg-white shadow-sm" : "border-slate-200 bg-white"}`} key={product.id} onMouseEnter={itemAction ? () => itemAction(product.id) : undefined}>
                <p className="text-sm leading-6 text-slate-700">
                  <strong>
                    {linkNames ? <a className="border-none text-primary no-underline hover:underline" href={product.url} rel="noreferrer" target="_blank">
                        {product.name}
                      </a> : product.name}
                    :
                  </strong>{" "}
                  {renderBoldSegments(product.matchReason)}
                </p>
                {showReasonChips ? <div className="mt-3 flex flex-wrap gap-2">
                    {product.styleTags.map(item => tag(item, "subtle", `${product.id}-${item}`))}
                  </div> : null}
              </li>;
  })}
        </ul>
      </div>
    </div>;
  const TextLinksPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          Lowest-friction option. It keeps the assistant answer intact and only upgrades the product names.
        </p>,
    children: answerList({
      linkNames: true
    })
  });
  const SoftMentionPreview = () => {
    const featuredProduct = products[0];
    return previewFrame({
      footer: <p className="text-xs leading-5 text-slate-500">
          This pattern keeps the answer in advice mode, then introduces one product as a light suggestion instead of a hard merchandising switch.
        </p>,
      children: <div className="space-y-4">
          <div className="flex gap-3">
            <span className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-sm font-semibold text-slate-700">
              AX
            </span>
            <div className="min-w-0 max-w-3xl rounded-2xl bg-slate-50 p-4 text-sm leading-6 text-slate-700">
              <p className="mb-3">
                If you are choosing a gift for your girlfriend, I would usually start from her daily style instead of the product category itself. That makes it easier to avoid recommendations that feel technically correct but emotionally flat.
              </p>
              <p className="mb-3">
                For example, if she usually likes polished pieces that still feel easy to wear, then a refined bag can be a very safe direction. If she is more design-led, then I would lean toward something quieter and more distinctive instead of something obviously logo-driven.
              </p>
              <p>
                To keep it simple, I can start with one softer recommendation first and only expand into a fuller shortlist if you want more options after that.
              </p>
            </div>
          </div>

          <div className="flex justify-end">
            <div className="max-w-md rounded-2xl bg-slate-900 px-4 py-3 text-sm leading-6 text-white">
              Yes, show me one subtle option first.
            </div>
          </div>

          <div className="flex gap-3">
            <span className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-sm font-semibold text-slate-700">
              AX
            </span>
            <div className="min-w-0 max-w-3xl rounded-2xl bg-slate-50 p-4 text-sm leading-6 text-slate-700">
              <p className="mb-3">
                A gentle starting point would be this one. It feels polished and gift-ready, but still understated enough that the recommendation does not overpower the conversation.
              </p>
              <p className="mt-3 text-sm leading-6 text-slate-600">
                If this direction feels right, I can then give you two stronger alternatives: one more iconic and one more design-led.
              </p>
               {softPlacementCard({
        badge: "AD",
        description: featuredProduct.softPlacementCopy,
        product: featuredProduct
      })}
            </div>
          </div>
        </div>
    });
  };
  const ContextualEntityLinksPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          This works well when the answer should remain paragraph-first and only the important entities need commercial affordance.
        </p>,
    children: <div className="space-y-4">
          <div className="flex gap-3">
            <span className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-sm font-semibold text-slate-700">
              AX
            </span>
            <div className="min-w-0 max-w-3xl rounded-2xl bg-slate-50 p-4 text-sm leading-6 text-slate-700">
              <p className="mb-3">
                If you want the recommendation to stay elegant, I would keep the shortlist very tight and only mention the strongest directions directly inside the answer instead of switching into a visible product module too early.
              </p>
              <p className="mb-3">
                For a more polished and gift-ready option,{" "}
                <a className="border-none font-medium text-primary no-underline hover:underline" href={products[0].url} rel="noreferrer" target="_blank">
                  Louis Vuitton
                </a>{" "}
                is the safest place to start. If you want something quieter and more design-led,{" "}
                <a className="border-none font-medium text-primary no-underline hover:underline" href={products[1].url} rel="noreferrer" target="_blank">
                  Large Amazona 180
                </a>{" "}
                feels more understated and design-forward.
              </p>
              <p>
                That way, the answer still reads like advice first, while the most important brand and product entities quietly remain clickable.
              </p>
            </div>
          </div>
        </div>
  });
  const ContextualLinksWithAdHoverPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          This keeps the paragraph visually quiet, while the richer product card only appears when the user inspects the AD marker.
        </p>,
    children: <div className="space-y-4">
          <div className="flex gap-3">
            <span className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-sm font-semibold text-slate-700">
              AX
            </span>
            <div className="min-w-0 max-w-3xl rounded-2xl bg-slate-50 p-4 text-sm leading-7 text-slate-700">
              <p className="mb-3">
                If you want to keep the recommendation elegant, one good approach is to mention the strongest option directly in the paragraph and only expose the richer product card when the user shows intent.
              </p>
              <p className="mb-3">
                For a polished and gift-ready direction,{" "}
                <a className="border-none font-medium text-primary no-underline hover:underline" href={products[0].url} rel="noreferrer" target="_blank">
                  Louis Vuitton
                </a>
                <span className="relative ml-1 inline-flex align-middle">
                  <span className="group inline-flex">
                    <span className="inline-flex cursor-default rounded-full bg-amber-500 px-1 py-[1px] text-[9px] font-semibold uppercase leading-none text-white" tabIndex={0}>
                      AD
                    </span>
                    <span className="absolute left-0 top-full z-10 mt-2 hidden w-[32rem] max-w-[calc(100vw-4rem)] group-hover:block group-focus-within:block">
                      {softPlacementCard({
      badge: "AD",
      description: products[0].softPlacementCopy,
      product: products[0]
    })}
                    </span>
                  </span>
                </span>{" "}
                is an easy place to start because it feels refined without making the answer suddenly look like a product grid.
              </p>
              <p>
                This way, the main answer remains readable, but users who notice the disclosure marker can still inspect the product with a much richer preview.
              </p>
            </div>
          </div>
        </div>
  });
  const ClickableRecommendationBlockPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          This pattern enlarges the click target only where the answer clearly moves into recommendation mode.
        </p>,
    children: <div className="space-y-4">
          <div className="flex gap-3">
            <span className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-sm font-semibold text-slate-700">
              AX
            </span>
            <div className="min-w-0 max-w-3xl rounded-2xl bg-slate-50 p-4 text-sm leading-6 text-slate-700">
              <p className="mb-3">
                If you are buying for your girlfriend, I would first narrow the answer to one recommendation paragraph instead of showing a full shopping block right away. That keeps the tone helpful and personal, especially if the user is still thinking through taste, budget, and occasion.
              </p>
              <a className="border-none block" href={products[0].url} rel="noreferrer" target="_blank">
                <p className="rounded-xl px-1 py-1 text-sm leading-7 text-slate-700 transition hover:bg-amber-50/60">
                  <span className="font-medium text-slate-900">
                    My soft recommendation would be {products[0].name}
                  </span>{" "}
                  because it feels polished, giftable, and easy to imagine across both daytime and dinner settings, so the paragraph itself can stay close to normal body copy while still acting as the click target.
                </p>
              </a>
              <p className="mt-3">
                If you prefer, I can then expand into a fuller shortlist with one more iconic option and one quieter alternative.
              </p>
            </div>
          </div>
        </div>
  });
  const InlineRecommendationInsertPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          This mirrors a long-form answer where one compact recommendation card appears exactly at the point where a product becomes relevant.
        </p>,
    children: <div className="space-y-4">
          <div className="flex gap-3">
            <span className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-sm font-semibold text-slate-700">
              AX
            </span>
            <div className="min-w-0 max-w-3xl rounded-2xl bg-slate-50 p-4 text-sm leading-7 text-slate-700">
              <p className="mb-4">
                If you are choosing a gift for your girlfriend and are not fully sure what direction to take yet, I would usually think through a few categories first and only surface one product when it naturally matches the answer.
              </p>
              <ul className="space-y-4">
                <li>
                  <strong>Jewelry:</strong> A necklace, bracelet, or earrings can work very well if she already likes delicate accessories and tends to wear them often.
                </li>
                <li>
                  <strong>Beauty:</strong> If she has a favorite brand, a premium beauty or skincare item can feel thoughtful without being too difficult to choose.
                </li>
                <li>
                  <strong>Bag direction:</strong> If you want something that feels more complete as a milestone gift, a refined handbag is often the safest upgrade because it feels both practical and special.
                </li>
              </ul>
              <div className="my-4">
                {softPlacementCard({
      badge: "AD",
      description: products[0].softPlacementCopy,
      product: products[0]
    })}
              </div>
              <ul className="space-y-4">
                <li>
                  <strong>Experience gifts:</strong> A dinner, short trip, or a memorable shared activity can also feel more personal than a physical item.
                </li>
                <li>
                  <strong>Final filter:</strong> The best choice usually comes down to whether she would enjoy using it in daily life instead of only appreciating it as a pretty gift.
                </li>
              </ul>
            </div>
          </div>
        </div>
  });
  const InlineCardsPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          Same answer payload, then a richer visual layer for faster comparison and higher engagement.
        </p>,
    children: <>
          {answerList({})}
          <div className="grid gap-4">
            {products.map(product => <a className="border-none" href={product.url} key={product.id} rel="noreferrer" target="_blank">
                {productCard({
      compact: true,
      product
    })}
              </a>)}
          </div>
        </>
  });
  const ReasonChipsPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          Chips help users understand why each item was selected without leaving the answer context.
        </p>,
    children: answerList({
      showReasonChips: true
    })
  });
  const ExpandablePicksPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          Good when you want to keep the first screen compact but still let users inspect the reasoning.
        </p>,
    children: <>
          <div className="rounded-2xl border border-slate-200 bg-slate-50 p-4">
            <p className="text-base font-semibold text-slate-900">2026 handbag gifting guide</p>
            <p className="mt-2 text-sm leading-6 text-slate-600">
              If you are looking for a milestone gift, these three handbag directions cover the strongest combinations of style, recognition, and gifting potential.
            </p>
          </div>
          <div className="grid gap-3">
            {products.map(product => <details className="rounded-2xl border border-slate-200 bg-white p-4 shadow-sm" key={product.id}>
                <summary className="cursor-pointer list-none text-sm font-semibold text-slate-900">
                  {product.name}
                </summary>
                <p className="mt-3 text-sm leading-6 text-slate-600">{product.matchReason}</p>
                <ul className="mt-3 space-y-2 text-sm leading-6 text-slate-600">
                  {product.supportingPoints.map(point => <li className="rounded-xl bg-slate-50 px-3 py-2" key={point}>
                      {point}
                    </li>)}
                </ul>
              </details>)}
          </div>
        </>
  });
  const HoverSyncPreview = () => {
    const [hoveredProductId, setHoveredProductId] = useState(products[0].id);
    const activeProduct = getProductById(hoveredProductId);
    return previewFrame({
      footer: <p className="text-xs leading-5 text-slate-500">
          Hover or focus can connect reasoning on the left to a richer product treatment on the right.
        </p>,
      children: <div className="grid gap-4 lg:grid-cols-2">
          {answerList({
        highlightProductId: hoveredProductId,
        itemAction: setHoveredProductId
      })}
          <div className="rounded-2xl border border-slate-200 bg-white p-4 shadow-sm">
            {productCard({
        frameStyle: {
          width: '100%',
          height: '400px'
        },
        compact: false,
        emphasis: true,
        footer: <div className="space-y-2">
                  {activeProduct.supportingPoints.map(point => <div className="rounded-xl border border-white/10 bg-white/10 px-3 py-2 text-sm text-slate-100" key={point}>
                      {point}
                    </div>)}
                </div>,
        product: activeProduct
      })}
          </div>
        </div>
    });
  };
  const ChatCarouselPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          A carousel keeps the chat framing but makes product browsing feel more native to touch interfaces.
        </p>,
    children: <>
          {answerList({})}
          <div className="overflow-x-auto pb-2">
            <div className="flex gap-4">
              {products.map(product => <a className="w-80 shrink-0 border-none" href={product.url} key={product.id} rel="noreferrer" target="_blank">
                  {productCard({
      frameStyle: {
        width: '100%',
        height: '260px'
      },
      product
    })}
                </a>)}
            </div>
          </div>
        </>
  });
  const GalleryPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          The same dataset can move from chat-first into a straightforward recommendation grid.
        </p>,
    children: <>
          <div className="rounded-2xl border border-slate-200 bg-slate-50 p-4">
            <p className="text-sm font-semibold uppercase tracking-widest text-slate-500">
              Recommendation gallery
            </p>
            <h3 className="mt-2 text-xl font-semibold text-slate-900">Gift-ready luxury handbags</h3>
            <p className="mt-2 text-sm leading-6 text-slate-600">
              A gallery-first view works well when the user wants to compare several options visually.
            </p>
          </div>
          <div className="grid gap-1 lg:grid-cols-3">
            {products.map(product => <a className="border-none" href={product.url} key={product.id} rel="noreferrer" target="_blank">
                {productCard({
      frameStyle: {
        width: '100%',
        height: '240px'
      },
      product
    })}
              </a>)}
          </div>
        </>
  });
  const EditorialSpotlightPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          Spotlight layouts are useful when you want one hero recommendation and two supporting options.
        </p>,
    children: <div className="grid gap-4 lg:grid-cols-3">
          <div className="lg:col-span-2">
            {productCard({
      emphasis: true,
      footer: <div className="flex flex-wrap gap-2">
                  {products[0].styleTags.map(item => tag(item, "subtle", item))}
                </div>,
      product: products[0]
    })}
          </div>
          <div className="grid gap-4">
            {products.slice(1).map(product => <a className="border-none" href={product.url} key={product.id} rel="noreferrer" target="_blank">
                {productCard({
      compact: true,
      product
    })}
              </a>)}
          </div>
        </div>
  });
  const BentoDiscoveryPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          A bento layout can make even a small dataset feel more editorial and discovery-oriented.
        </p>,
    children: <div className="grid gap-4 lg:grid-cols-3">
          <div className="rounded-3xl border border-slate-200 bg-slate-900 p-5 text-white shadow-sm lg:col-span-2">
            <p className="text-xs font-semibold uppercase tracking-widest text-slate-300">
              Discovery hero
            </p>
            <div className="mt-4 grid gap-4 lg:grid-cols-2">
              {productImage({
      eager: true,
      frameClassName: "h-72 w-full rounded-2xl bg-slate-100",
      product: products[0]
    })}
              <div className="space-y-4">
                <h3 className="text-2xl font-semibold">{products[0].name}</h3>
                <p className="text-sm leading-6 text-slate-200">{products[0].matchReason}</p>
                <div className="flex flex-wrap gap-2">
                  {products[0].supportingPoints.map(point => tag(point, "subtle", point))}
                </div>
              </div>
            </div>
          </div>
          <div className="grid gap-4">
            {products.slice(1).map(product => <div className="rounded-3xl border border-slate-200 bg-slate-50 p-4 shadow-sm" key={product.id}>
                {productImage({
      frameClassName: "h-44 w-full bg-white",
      frameStyle: {
        width: '100%',
        height: '200px'
      },
      product
    })}
                <p className="mt-4 text-base font-semibold text-slate-900">{product.name}</p>
                <p className="mt-2 text-sm leading-6 text-slate-600">{product.adCopy}</p>
              </div>)}
          </div>
        </div>
  });
  const LookbookStripPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          Lookbook strips push the imagery forward and make the merchandising feel more lifestyle-led.
        </p>,
    children: <div className="grid gap-4 md:grid-cols-3">
          {products.map(product => <a className="group relative overflow-hidden rounded-3xl border border-slate-200 border-none" href={product.url} key={product.id} rel="noreferrer" target="_blank">
              {productImage({
      frameClassName: "h-80 w-full rounded-none bg-slate-100",
      imageClassName: "transition duration-300 group-hover:scale-105",
      product
    })}
              <div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/80 to-transparent p-5 text-white">
                <p className="mt-2 text-base font-semibold">{product.name}</p>
                <p className="mt-2 text-sm leading-6 text-slate-200">{product.adCopy}</p>
              </div>
            </a>)}
        </div>
  });
  const CompactShelfPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          Compact shelves are good for side rails, search results, and recommendation rows inside denser pages.
        </p>,
    children: <>
          <div className="rounded-2xl border border-slate-200 bg-slate-50 p-4">
            <p className="text-sm font-semibold text-slate-900">Related gift-ready picks</p>
          </div>
          <div className="overflow-x-auto pb-2">
            <div className="flex gap-3">
              {products.map(product => <a className="w-64 shrink-0 rounded-2xl border border-slate-200 bg-white p-3 shadow-sm border-none" href={product.url} key={product.id} rel="noreferrer" target="_blank">
                  {productImage({
      frameClassName: "h-36 w-full bg-white",
      frameStyle: {
        width: '100%',
        height: '300px'
      },
      product
    })}
                  <p className="mt-3 text-sm font-semibold text-slate-900">{product.name}</p>
                </a>)}
            </div>
          </div>
        </>
  });
  const ComparisonTablePreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          Tables are useful when the user is already comparing and wants structured trade-offs.
        </p>,
    children: <div className="overflow-x-auto rounded-2xl border border-slate-200 bg-white shadow-sm">
          <table className="min-w-full text-left text-sm text-slate-600">
            <thead className="bg-slate-50 text-slate-900">
              <tr>
                <th className="px-4 py-3 font-semibold">Attribute</th>
                {products.map(product => <th className="px-4 py-3 font-semibold" key={product.id}>
                    {product.brand}
                  </th>)}
              </tr>
            </thead>
            <tbody>
              <tr className="border-t border-slate-200">
                <td className="px-4 py-3 font-medium text-slate-900">Style</td>
                {products.map(product => <td className="px-4 py-3" key={product.id}>
                    {product.styleTags.slice(0, 2).join(", ")}
                  </td>)}
              </tr>
              <tr className="border-t border-slate-200">
                <td className="px-4 py-3 font-medium text-slate-900">Best occasion</td>
                {products.map(product => <td className="px-4 py-3" key={product.id}>
                    {product.occasionTags[0]}
                  </td>)}
              </tr>
              <tr className="border-t border-slate-200">
                <td className="px-4 py-3 font-medium text-slate-900">Price band</td>
                {products.map(product => <td className="px-4 py-3" key={product.id}>
                    {product.priceBand}
                  </td>)}
              </tr>
              <tr className="border-t border-slate-200">
                <td className="px-4 py-3 font-medium text-slate-900">Gift reason</td>
                {products.map(product => <td className="px-4 py-3" key={product.id}>
                    {product.matchReason}
                  </td>)}
              </tr>
            </tbody>
          </table>
        </div>
  });
  const AttributeScorecardsPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          Scorecards make implicit ranking criteria visible without overloading the page with prose.
        </p>,
    children: <div className="grid gap-4 lg:grid-cols-3">
          {products.map(product => <div className="rounded-3xl border border-slate-200 bg-white p-5 shadow-sm" key={product.id}>
              <p className="text-base font-semibold text-slate-900">{product.name}</p>
              <p className="mt-1 text-sm text-slate-500">{product.brand}</p>
              <div className="mt-5 space-y-4">
                {scoreBar({
      label: "Versatility",
      value: product.scores.versatility
    })}
                {scoreBar({
      label: "Statement",
      value: product.scores.statement
    })}
                {scoreBar({
      label: "Gift appeal",
      value: product.scores.giftAppeal
    })}
              </div>
            </div>)}
        </div>
  });
  const PersonaMatcherPreview = () => {
    const personas = [{
      id: "minimalist",
      label: "Minimalist"
    }, {
      id: "office",
      label: "Office style"
    }, {
      id: "everyday",
      label: "Daily use"
    }];
    const [activePersona, setActivePersona] = useState(personas[0].id);
    const rankedProducts = getSortedProductsByPersona(activePersona);
    return previewFrame({
      footer: <p className="text-xs leading-5 text-slate-500">
          Persona switches help the same catalog adapt to different buyer intents without changing the data source.
        </p>,
      children: <>
          <div className="flex flex-wrap gap-2">
            {personas.map(persona => <button className={`rounded-full border px-4 py-2 text-sm font-semibold ${activePersona === persona.id ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 bg-white text-slate-700"}`} key={persona.id} onClick={() => setActivePersona(persona.id)} type="button">
                {persona.label}
              </button>)}
          </div>
          <div className="grid gap-4">
            {rankedProducts.map((product, index) => <div className="rounded-2xl border border-slate-200 bg-white p-4 shadow-sm" key={product.id}>
                <div className="flex items-start justify-between gap-4">
                  <div>
                    <p className="text-base font-semibold text-slate-900">
                      {index + 1}. {product.name}
                    </p>
                    <p className="mt-1 text-sm text-slate-500">{product.brand}</p>
                  </div>
                  {tag(`${product.personaFit[activePersona]} match`, "success")}
                </div>
                <p className="mt-3 text-sm leading-6 text-slate-600">{product.matchReason}</p>
              </div>)}
          </div>
        </>
    });
  };
  const OccasionSwitcherPreview = () => {
    const occasions = [{
      id: "birthday",
      label: "Birthday"
    }, {
      id: "anniversary",
      label: "Anniversary"
    }, {
      id: "holiday",
      label: "Holiday"
    }];
    const [activeOccasion, setActiveOccasion] = useState(occasions[0].id);
    const orderedProducts = getSortedProductsByOccasion(activeOccasion);
    return previewFrame({
      footer: <p className="text-xs leading-5 text-slate-500">
          Occasion switching is useful when the same response needs a different tone or ranking depending on context.
        </p>,
      children: <>
          <div className="flex flex-wrap gap-2">
            {occasions.map(occasion => <button className={`rounded-full border px-4 py-2 text-sm font-semibold ${activeOccasion === occasion.id ? "border-amber-700 bg-amber-700 text-white" : "border-slate-200 bg-white text-slate-700"}`} key={occasion.id} onClick={() => setActiveOccasion(occasion.id)} type="button">
                {occasion.label}
              </button>)}
          </div>
          <div className="grid gap-4 lg:grid-cols-3">
            {orderedProducts.map(product => <div className="rounded-3xl border border-slate-200 bg-white p-4 shadow-sm" key={product.id}>
                {productImage({
        frameClassName: "h-48 w-full bg-slate-50",
        frameStyle: {
          width: '100%',
          height: '300px'
        },
        product
      })}
                <p className="mt-4 text-base font-semibold text-slate-900">{product.name}</p>
                <p className="mt-2 text-sm leading-6 text-slate-600">
                  {product.occasionMessages[activeOccasion] || product.matchReason}
                </p>
              </div>)}
          </div>
        </>
    });
  };
  const ShortlistCompareTrayPreview = () => {
    const [selectedProductIds, setSelectedProductIds] = useState([products[0].id]);
    const toggleProduct = productId => {
      setSelectedProductIds(current => {
        if (current.includes(productId)) {
          return current.filter(id => id !== productId);
        }
        if (current.length >= 3) {
          return [...current.slice(1), productId];
        }
        return [...current, productId];
      });
    };
    const selectedProducts = products.filter(product => selectedProductIds.includes(product.id));
    return previewFrame({
      footer: <p className="text-xs leading-5 text-slate-500">
          Shortlisting is a useful middle state between passive browsing and final action.
        </p>,
      children: <>
          <div className="grid gap-4 lg:grid-cols-3">
            {products.map(product => {
        const isSelected = selectedProductIds.includes(product.id);
        return <div className={`rounded-3xl border p-4 shadow-sm ${isSelected ? "border-slate-900 bg-slate-900 text-white" : "border-slate-200 bg-white text-slate-900"}`} key={product.id}>
                  {productImage({
          frameClassName: "h-44 w-full bg-slate-50",
          frameStyle: {
            width: '100%',
            height: '300px'
          },
          product
        })}
                  <p className="mt-4 text-base font-semibold">{product.name}</p>
                  <button className={`mt-4 rounded-full px-4 py-2 text-sm font-semibold block ${isSelected ? "bg-white text-slate-900" : "bg-slate-900 text-white"}`} onClick={() => toggleProduct(product.id)} type="button">
                    {isSelected ? "Remove" : "Add to shortlist"}
                  </button>
                </div>;
      })}
          </div>
          <div className="rounded-3xl border border-slate-200 bg-slate-50 p-4">
            <div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
              <div className="flex flex-wrap gap-2">
                {selectedProducts.map(product => tag(product.name, "dark", product.id))}
              </div>
              <button className="inline-flex rounded-full bg-slate-900 px-4 py-2 text-sm font-semibold text-white" type="button">
                Compare shortlist
              </button>
            </div>
          </div>
        </>
    });
  };
  const FollowUpServiceCardPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          A docs page can host the first step of a richer service flow without embedding a full application.
        </p>,
    children: <>
          {answerList({})}
          <div className="rounded-3xl border border-slate-200 bg-gradient-to-r from-slate-900 via-slate-800 to-slate-700 p-5 text-white shadow-sm">
            <div className="flex flex-col gap-5 lg:items-center lg:justify-between">
              <div className="w-full">
                <p className="text-xs font-semibold uppercase tracking-widest text-slate-300">
                  Follow-up service
                </p>
                <h3 className="mt-2 text-xl font-semibold">Premium gift wrapping</h3>
                <p className="mt-3 text-sm leading-6 text-slate-200">
                  Add a handwritten card, premium wrapping, and delivery timing preferences without leaving the answer flow.
                </p>
                <div className="mt-4 flex gap-2">
                  {tag("Gift note", "subtle")}
                  {tag("Premium packaging", "subtle")}
                  {tag("Delivery timing", "subtle")}
                </div>
              </div>
              <div className="rounded-2xl bg-white/10 p-4">
                <p className="text-sm font-semibold w-full block">Best for </p>
                <p className="mt-2 text-sm leading-6 text-slate-200">
                  Upsell modules, add-on services, and cross-functional flows that start from the same recommendation context.
                </p>
                <button className="mt-4 inline-flex items-center justify-center rounded-full bg-white px-4 py-2 text-sm font-semibold text-slate-900" type="button">
                  Add service
                </button>
              </div>
            </div>
          </div>
        </>
  });
  const GiftBundleBuilderPreview = () => {
    const bundleOptions = [{
      id: "wrap",
      label: "Premium wrap"
    }, {
      id: "card",
      label: "Handwritten card"
    }, {
      id: "delivery",
      label: "Timed delivery"
    }];
    const [selectedOptions, setSelectedOptions] = useState(["wrap"]);
    const toggleOption = optionId => {
      setSelectedOptions(current => current.includes(optionId) ? current.filter(item => item !== optionId) : [...current, optionId]);
    };
    return previewFrame({
      footer: <p className="text-xs leading-5 text-slate-500">
          Bundle builders let the recommendation branch into a simple service configuration flow.
        </p>,
      children: <div className="grid gap-4 lg:grid-cols-3">
          <div className="lg:col-span-2">{productCard({
        compact: true,
        product: products[0]
      })}</div>
          <div className="rounded-3xl border border-slate-200 bg-white p-5 shadow-sm lg:col-span-1">
            <p className="text-sm font-semibold text-slate-900">Build the gift bundle</p>
            <div className="mt-4 grid gap-3">
              {bundleOptions.map(option => {
        const isSelected = selectedOptions.includes(option.id);
        return <button className={`rounded-2xl border px-4 py-3 text-left text-sm font-medium ${isSelected ? "border-indigo-700 bg-indigo-50 text-indigo-700" : "border-slate-200 bg-slate-50 text-slate-700"}`} key={option.id} onClick={() => toggleOption(option.id)} type="button">
                    {option.label}
                  </button>;
      })}
            </div>
            <div className="mt-5 rounded-2xl bg-slate-50 p-4">
              <p className="text-xs uppercase tracking-widest text-slate-500">Selected</p>
              <div className="mt-3 flex flex-wrap gap-2">
                {selectedOptions.map(optionId => {
        const option = bundleOptions.find(item => item.id === optionId);
        return tag(option ? option.label : optionId, "indigo", optionId);
      })}
              </div>
            </div>
          </div>
        </div>
    });
  };
  const SaveOrShareWishlistPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          Wishlist actions are a simple way to capture intent when the user is not ready to decide yet.
        </p>,
    children: <div className="grid gap-4 lg:grid-cols-3">
          <div className="grid gap-4 lg:col-span-2">
            {products.slice(0, 2).map(product => <div className="rounded-3xl border border-slate-200 bg-white p-4 shadow-sm" key={product.id}>
                <div className="flex gap-4">
                  {productImage({
      frameClassName: "shrink-0 bg-slate-50",
      frameStyle: {
        height: "6rem",
        minWidth: "6rem",
        width: "6rem"
      },
      product
    })}
                  <div className="min-w-0 flex-1">
                    <p className="text-base font-semibold text-slate-900">{product.name}</p>
                    <p className="mt-1 text-sm text-slate-500">{product.brand}</p>
                    <p className="mt-2 text-sm leading-6 text-slate-600">{product.adCopy}</p>
                  </div>
                </div>
              </div>)}
          </div>
          <div className="rounded-3xl border border-slate-200 bg-slate-900 p-5 text-white shadow-sm lg:col-span-1">
            <p className="text-xs uppercase tracking-widest text-slate-300">Wishlist actions</p>
            <p className="mt-3 text-lg font-semibold">Save the shortlist for later</p>
            <div className="mt-5 grid gap-3">
              <button className="rounded-full bg-white px-4 py-2 text-sm font-semibold text-slate-900" type="button">
                Save list
              </button>
              <button className="rounded-full border border-white/20 px-4 py-2 text-sm font-semibold text-white" type="button">
                Share with a friend
              </button>
              <button className="rounded-full border border-white/20 px-4 py-2 text-sm font-semibold text-white" type="button">
                Copy summary
              </button>
            </div>
          </div>
        </div>
  });
  const NextStepActionSheetPreview = () => previewFrame({
    footer: <p className="text-xs leading-5 text-slate-500">
          Action sheets make the transition from recommendation to next step explicit and compact.
        </p>,
    children: <>
          {answerList({})}
          <div className="grid gap-4 lg:grid-cols-3">
            {products.map(product => <div className="rounded-3xl border border-slate-200 bg-white p-4 shadow-sm" key={product.id}>
                <p className="text-base font-semibold text-slate-900">{product.name}</p>
                <p className="mt-2 text-sm leading-6 text-slate-600">{product.matchReason}</p>
              </div>)}
          </div>
          <div className="rounded-3xl border border-slate-200 bg-slate-900 p-4 text-white shadow-sm">
            <div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
              <p className="text-sm text-slate-200">What do you want to do next with these recommendations?</p>
              <div className="flex flex-wrap gap-2">
                <button className="rounded-full bg-white px-4 py-2 text-sm font-semibold text-slate-900" type="button">
                  View details
                </button>
                <button className="rounded-full border border-white/20 px-4 py-2 text-sm font-semibold text-white" type="button">
                  Compare now
                </button>
                <button className="rounded-full border border-white/20 px-4 py-2 text-sm font-semibold text-white" type="button">
                  Add service
                </button>
              </div>
            </div>
          </div>
        </>
  });
  const renderPreview = demoId => {
    switch (demoId) {
      case "soft-mention":
        return <SoftMentionPreview />;
      case "contextual-entity-links":
        return <ContextualEntityLinksPreview />;
      case "contextual-links-with-ad-hover":
        return <ContextualLinksWithAdHoverPreview />;
      case "clickable-recommendation-block":
        return <ClickableRecommendationBlockPreview />;
      case "inline-recommendation-insert":
        return <InlineRecommendationInsertPreview />;
      case "text-links":
        return <TextLinksPreview />;
      case "inline-cards":
        return <InlineCardsPreview />;
      case "reason-chips":
        return <ReasonChipsPreview />;
      case "expandable-picks":
        return <ExpandablePicksPreview />;
      case "hover-sync-answer":
        return <HoverSyncPreview />;
      case "chat-carousel":
        return <ChatCarouselPreview />;
      case "product-gallery":
        return <GalleryPreview />;
      case "editorial-spotlight":
        return <EditorialSpotlightPreview />;
      case "bento-discovery-grid":
        return <BentoDiscoveryPreview />;
      case "lookbook-strip":
        return <LookbookStripPreview />;
      case "compact-result-shelf":
        return <CompactShelfPreview />;
      case "comparison-table":
        return <ComparisonTablePreview />;
      case "attribute-scorecards":
        return <AttributeScorecardsPreview />;
      case "persona-matcher":
        return <PersonaMatcherPreview />;
      case "occasion-switcher":
        return <OccasionSwitcherPreview />;
      case "shortlist-compare-tray":
        return <ShortlistCompareTrayPreview />;
      case "follow-up-service-card":
        return <FollowUpServiceCardPreview />;
      case "gift-bundle-builder":
        return <GiftBundleBuilderPreview />;
      case "save-or-share-wishlist":
        return <SaveOrShareWishlistPreview />;
      case "next-step-action-sheet":
        return <NextStepActionSheetPreview />;
      default:
        return <TextLinksPreview />;
    }
  };
  const initialDemo = demos.find(demo => demo.id === defaultExampleId) || demos[0];
  const [activeCategoryId, setActiveCategoryId] = useState(initialDemo.categoryId);
  const [activeDemoId, setActiveDemoId] = useState(initialDemo.id);
  const activeDemo = demos.find(demo => demo.id === activeDemoId) || demos[0];
  const activeCategory = categories.find(category => category.id === activeDemo.categoryId) || categories[0];
  const supportingCode = supportingCodeByDemoId[activeDemo.id];
  const demosInActiveCategory = getDemosByCategory(activeCategoryId);
  const activeDemoIndex = demos.findIndex(demo => demo.id === activeDemo.id) + 1;
  const handleCategoryChange = categoryId => {
    setActiveCategoryId(categoryId);
    const nextDemo = getDemosByCategory(categoryId)[0];
    if (nextDemo) {
      setActiveDemoId(nextDemo.id);
    }
  };
  const handleDemoChange = demo => {
    setActiveCategoryId(demo.categoryId);
    setActiveDemoId(demo.id);
  };
  return <div className={`my-8 grid gap-6 ${showCatalog ? "lg:grid-cols-4" : ""}`}>
      {showCatalog ? <aside className="space-y-4 lg:sticky lg:top-24 lg:col-span-1 lg:self-start">
        <div className="rounded-3xl border border-slate-200 bg-white p-4 shadow-sm">
          <p className="text-xs font-semibold uppercase tracking-widest text-slate-500">
            Demo catalog
          </p>
          <p className="mt-3 text-sm leading-6 text-slate-600">
            {demos.length} demos across {categories.length} categories. Select a category first, then switch among the specific layouts.
          </p>
        </div>

        <div className="rounded-3xl border border-slate-200 bg-white p-4 shadow-sm">
          <p className="text-xs font-semibold uppercase tracking-widest text-slate-500">
            Categories
          </p>
          <div className="mt-4 grid gap-3">
            {categories.map(category => {
    const isActive = category.id === activeCategoryId;
    const count = getDemosByCategory(category.id).length;
    return <button className={`rounded-2xl border px-4 py-3 text-left transition ${isActive ? accentByCategory[category.id] : "border-slate-200 bg-slate-50 text-slate-700 hover:border-slate-300 hover:bg-white"}`} key={category.id} onClick={() => handleCategoryChange(category.id)} type="button">
                  <div className="flex items-center justify-between gap-3">
                    <p className="text-sm font-semibold">{category.label}</p>
                    <span className={`rounded-full px-2.5 py-1 text-xs font-semibold ${isActive ? "bg-white/15 text-white" : "bg-white text-slate-500"}`}>
                      {count}
                    </span>
                  </div>
                  <p className={`mt-2 text-xs leading-5 ${isActive ? "text-slate-100" : "text-slate-500"}`}>
                    {category.description}
                  </p>
                </button>;
  })}
          </div>
        </div>

        <div className="rounded-3xl border border-slate-200 bg-white p-4 shadow-sm">
          <p className="text-xs font-semibold uppercase tracking-widest text-slate-500">
            Demos in this category
          </p>
          <div className="mt-4 grid gap-3">
            {demosInActiveCategory.map(demo => {
    const isActive = demo.id === activeDemoId;
    return <button className={`rounded-2xl border px-4 py-3 text-left transition ${isActive ? "border-slate-900 bg-slate-900 text-white shadow-sm" : "border-slate-200 bg-slate-50 text-slate-700 hover:border-slate-300 hover:bg-white"}`} key={demo.id} onClick={() => handleDemoChange(demo)} type="button">
                  <p className="text-sm font-semibold">{demo.label}</p>
                  <p className={`mt-1 text-xs leading-5 ${isActive ? "text-slate-300" : "text-slate-500"}`}>
                    {demo.eyebrow}
                  </p>
                </button>;
  })}
          </div>
        </div>
      </aside> : null}

      <section className={`space-y-5 ${showCatalog ? "lg:col-span-3" : ""}`}>
        <div className="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm">
          <div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
            <div className="max-w-3xl">
              <div className="flex flex-wrap items-center gap-2">
                {tag(activeCategory.label, "subtle")}
                {tag(`Demo ${activeDemoIndex} of ${demos.length}`, "warm")}
              </div>
              <h2 className="mt-3 text-2xl font-semibold text-slate-900">{activeDemo.title}</h2>
              <p className="mt-3 text-sm leading-7 text-slate-600">{activeDemo.description}</p>
            </div>
            <div className="rounded-2xl bg-slate-50 px-4 py-3 text-sm text-slate-600">
              {activeDemo.label}
            </div>
          </div>
        </div>

        {renderPreview(activeDemo.id)}

        <div className="space-y-5">
          <div className="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm">
            <p className="text-xs font-semibold uppercase tracking-widest text-slate-500">
              Implementation sketch
            </p>
            <pre className="mt-4 overflow-x-auto rounded-2xl bg-slate-950 p-4 text-sm leading-6 text-slate-100">
              <code>{activeDemo.code}</code>
            </pre>
          </div>

          {supportingCode ? <div className="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm">
              <p className="text-xs font-semibold uppercase tracking-widest text-slate-500">
                Supporting component
              </p>
              <p className="mt-3 text-sm font-medium text-slate-700">{supportingCode.title}</p>
              <pre className="mt-4 overflow-x-auto rounded-2xl bg-slate-950 p-4 text-sm leading-6 text-slate-100">
                <code>{supportingCode.code}</code>
              </pre>
            </div> : null}

          <div className="rounded-3xl border border-slate-200 bg-slate-50 p-6 shadow-sm">
            <p className="text-xs font-semibold uppercase tracking-widest text-slate-500">
              Why use it
            </p>
            <div className="mt-4 space-y-3">
              {activeDemo.notes.map(note => <div className="rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm leading-6 text-slate-600" key={note}>
                  {note}
                </div>)}
            </div>
          </div>
        </div>
      </section>
    </div>;
};

This example keeps the answer advice-first, then introduces one product as a soft follow-up suggestion inside the conversation.

<ExampleShowcase defaultExampleId="soft-mention" showCatalog={false} />
