{"$schema":"https://ui.artbloom.tech/schema/registry-item.json","name":"sprig","type":"registry:page","title":"Sprig","description":"A DTC sparkling-botanical beverage storefront: a video hero, a rotating promo bar, flavour tabs that cross-fade the can and recolour the section accent, a bestseller row with a fly-to-cart animation and a slide-in cart with a free-shipping progress bar. Ships a looping hero video, seven product renders; reduced-motion respected.","author":"@artbloom","dependencies":[],"registryDependencies":[],"files":[{"path":"app/sprig/page.tsx","target":"app/sprig/page.tsx","content":"\"use client\"\n\nimport { useEffect, useRef, useState } from \"react\"\n\n/**\n * Sprig — a conversion storefront for a sparkling adaptogen + prebiotic soda.\n *\n * The archetype is the modular Shopify homepage: rotating promo bar, campaign\n * hero, flavour switcher, collection tiles, bestseller carousel, a slide-in cart\n * with a free-shipping meter, subscribe-and-save, social proof, and capture.\n *\n * Anti-slop notes: the base is a warm off-white with a COOL neutral ink (never a\n * brown-tinted black), and the \"one bold move\" is colour itself — six candy\n * accents keyed to flavours, with the flavour section recolouring live as you\n * switch tabs. No tracked-caps eyebrows, no middle-dot metas, no em-dash label\n * fragments, no arrow-suffixed links, no glassmorphism. Geist throughout, big and\n * rounded and friendly. Every transition and animation is gated globally on\n * prefers-reduced-motion via the data-reduced attribute on the root.\n */\n\ntype Flavor = {\n  id: string\n  name: string\n  taste: string\n  benefit: string\n  note: string\n  img: string\n  accent: string\n  accentInk: string\n  tint: string\n}\n\nconst FLAVORS: Flavor[] = [\n  {\n    id: \"lemon\",\n    name: \"Lemon Zip\",\n    taste: \"Meyer lemon with a whisper of yuzu.\",\n    benefit: \"Tart, bright, and wide awake.\",\n    note: \"with L-theanine\",\n    img: \"/sprig/can1.png\",\n    accent: \"#F2B705\",\n    accentInk: \"#3a2c00\",\n    tint: \"#FEF6D9\",\n  },\n  {\n    id: \"raspberry\",\n    name: \"Raspberry Rose\",\n    taste: \"Sun-warm raspberries, a soft floral finish.\",\n    benefit: \"The one people get shy about loving.\",\n    note: \"with ashwagandha\",\n    img: \"/sprig/can2.png\",\n    accent: \"#E5346D\",\n    accentInk: \"#ffffff\",\n    tint: \"#FCE1EC\",\n  },\n  {\n    id: \"cucumber\",\n    name: \"Cucumber Lime\",\n    taste: \"Cold-pressed cucumber, fresh-squeezed lime.\",\n    benefit: \"Tastes like the last day of a good holiday.\",\n    note: \"with electrolytes\",\n    img: \"/sprig/can3.png\",\n    accent: \"#3DA35D\",\n    accentInk: \"#ffffff\",\n    tint: \"#E4F3E7\",\n  },\n  {\n    id: \"cola\",\n    name: \"Vintage Cola\",\n    taste: \"The cola you grew up on, quietly reformulated.\",\n    benefit: \"For people who said they'd never switch.\",\n    note: \"with prebiotic fibre\",\n    img: \"/sprig/can4.png\",\n    accent: \"#B5651D\",\n    accentInk: \"#ffffff\",\n    tint: \"#F3E7D6\",\n  },\n  {\n    id: \"peach\",\n    name: \"Peach Ginger\",\n    taste: \"Orchard peach with a clean ginger kick.\",\n    benefit: \"Warm and cold at the same time, somehow.\",\n    note: \"with fresh ginger\",\n    img: \"/sprig/can5.png\",\n    accent: \"#FF7A4D\",\n    accentInk: \"#3a1600\",\n    tint: \"#FFE7DC\",\n  },\n  {\n    id: \"grape\",\n    name: \"Concord Grape\",\n    taste: \"Purple grape straight off the vine.\",\n    benefit: \"It tastes, unapologetically, like recess.\",\n    note: \"with magnesium\",\n    img: \"/sprig/can6.png\",\n    accent: \"#8E44AD\",\n    accentInk: \"#ffffff\",\n    tint: \"#F0E3F7\",\n  },\n]\n\nconst PROMOS = [\n  \"Free shipping once you pass $45. You're closer than you think.\",\n  \"New drop: Concord Grape is in the fridge and going fast.\",\n  \"Subscribe and take 20% off every order. Cancel whenever, no hard feelings.\",\n  \"Hate your first sip? We refund it and you keep the cans.\",\n]\n\ntype Product = {\n  id: string\n  name: string\n  taste: string\n  price: number\n  rating: number\n  reviews: number\n  img: string\n  accent: string\n}\n\nconst PRODUCTS: Product[] = FLAVORS.map((f, i) => ({\n  id: f.id,\n  name: `${f.name} 12-pack`,\n  taste: f.taste,\n  price: [28, 28, 26, 28, 28, 26][i],\n  rating: [4.9, 4.8, 4.7, 4.9, 4.8, 5.0][i],\n  reviews: [8421, 6690, 4123, 9002, 5510, 2740][i],\n  img: f.img,\n  accent: f.accent,\n}))\n\nconst COLLECTIONS = [\n  { title: \"Best sellers\", blurb: \"The four cans that keep selling out.\", img: \"/sprig/can2.png\", accent: \"#E5346D\" },\n  { title: \"Variety packs\", blurb: \"Can't choose? Good, don't.\", img: \"/sprig/hero.png\", accent: \"#F2B705\" },\n  { title: \"Caffeine-free\", blurb: \"All the ritual, none of the buzz.\", img: \"/sprig/can3.png\", accent: \"#3DA35D\" },\n  { title: \"Subscribe & save\", blurb: \"20% off, delivered on your schedule.\", img: \"/sprig/can6.png\", accent: \"#8E44AD\" },\n]\n\nconst BUNDLES = [\n  { size: 8, price: 22, label: \"Starter fridge\" },\n  { size: 16, price: 40, label: \"Household favourite\" },\n  { size: 24, price: 54, label: \"Full restock\" },\n]\n\nconst PRESS = [\"Bon Appétit\", \"The Strategist\", \"Well+Good\", \"Food & Wine\", \"Eater\", \"Cool Hunting\"]\n\nconst REVIEWS = [\n  { name: \"Dana R.\", flavor: \"Peach Ginger\", stars: 5, text: \"I bought one 12-pack to try it and immediately hid them from my roommates. Three orders deep now.\" },\n  { name: \"Marcus T.\", flavor: \"Vintage Cola\", stars: 5, text: \"I was the guy who said no soda would ever beat the real thing. I was wrong and I'm at peace with it.\" },\n  { name: \"Priya S.\", flavor: \"Cucumber Lime\", stars: 5, text: \"My 2pm slump used to run my whole afternoon. This quietly fixed it and I didn't even notice for a week.\" },\n  { name: \"Jules K.\", flavor: \"Raspberry Rose\", stars: 4, text: \"Genuinely delicious. Docking one star only because I now have a subscription I cannot cancel emotionally.\" },\n]\n\nconst FREE_SHIP = 45\n\nconst money = (n: number) => `$${n.toFixed(2)}`\n\n/** Reveal children once when they scroll into view. Reduced-motion shows instantly. */\nfunction useReveal<T extends HTMLElement>() {\n  const ref = useRef<T>(null)\n  const [shown, setShown] = useState(false)\n  useEffect(() => {\n    const node = ref.current\n    if (!node) return\n    if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) {\n      setShown(true)\n      return\n    }\n    const io = new IntersectionObserver(\n      ([e]) => {\n        if (e.isIntersecting) {\n          setShown(true)\n          io.disconnect()\n        }\n      },\n      { threshold: 0.18 },\n    )\n    io.observe(node)\n    return () => io.disconnect()\n  }, [])\n  return [ref, shown] as const\n}\n\ntype CartLine = { id: string; name: string; price: number; qty: number; accent: string }\n\nexport default function Sprig() {\n  const [reduced, setReduced] = useState(false)\n  const [promo, setPromo] = useState(0)\n  const [active, setActive] = useState(0)\n  const [cart, setCart] = useState<CartLine[]>([])\n  const [cartOpen, setCartOpen] = useState(false)\n  const [pulse, setPulse] = useState<string | null>(null)\n  const [scrollY, setScrollY] = useState(0)\n  const [bundle, setBundle] = useState(1)\n  const [weekly, setWeekly] = useState(false)\n  const [videoReady, setVideoReady] = useState(false)\n  const [bump, setBump] = useState(false)\n  const cartBtnRef = useRef<HTMLButtonElement>(null)\n  const videoRef = useRef<HTMLVideoElement>(null)\n\n  // Reduced-motion + hero parallax scroll tracking.\n  useEffect(() => {\n    const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\")\n    setReduced(mq.matches)\n    const onMq = () => setReduced(mq.matches)\n    mq.addEventListener(\"change\", onMq)\n\n    let raf = 0\n    const onScroll = () => {\n      if (raf) return\n      raf = requestAnimationFrame(() => {\n        setScrollY(window.scrollY)\n        raf = 0\n      })\n    }\n    window.addEventListener(\"scroll\", onScroll, { passive: true })\n    onScroll()\n    return () => {\n      mq.removeEventListener(\"change\", onMq)\n      window.removeEventListener(\"scroll\", onScroll)\n      if (raf) cancelAnimationFrame(raf)\n    }\n  }, [])\n\n  // Rotate the promo bar — never auto-advances under reduced motion.\n  useEffect(() => {\n    if (reduced) return\n    const t = setInterval(() => setPromo((p) => (p + 1) % PROMOS.length), 4200)\n    return () => clearInterval(t)\n  }, [reduced])\n\n  // The hero video often loads before React attaches onCanPlay (cache/fast\n  // net), so the event is missed and the layer stays at opacity 0. Check\n  // readyState on mount, and kick off muted playback imperatively — muted\n  // play() is always allowed by autoplay policy, so this covers browsers that\n  // don't honour the autoplay attribute here.\n  useEffect(() => {\n    const v = videoRef.current\n    if (!v || reduced) return\n    if (v.readyState >= 3) setVideoReady(true)\n    const p = v.play()\n    if (p && typeof p.catch === \"function\") p.catch(() => {})\n  }, [reduced])\n\n  const flavor = FLAVORS[active]\n  const count = cart.reduce((n, l) => n + l.qty, 0)\n  const total = cart.reduce((s, l) => s + l.price * l.qty, 0)\n  const remaining = Math.max(0, FREE_SHIP - total)\n  const shipPct = Math.min(100, (total / FREE_SHIP) * 100)\n\n  // Imperative fly-to-cart: a coloured disc arcs from the click point to the\n  // cart button, then the button bumps. WAAPI so it survives re-renders; the\n  // disc lives on document.body (outside the reduced-motion root) so we gate it\n  // here by hand rather than relying on the CSS kill-switch.\n  const flyToCart = (origin: { x: number; y: number }, accent: string) => {\n    const btn = cartBtnRef.current\n    if (!btn || typeof document === \"undefined\") return\n    if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return\n    const target = btn.getBoundingClientRect()\n    const tx = target.left + target.width / 2\n    const ty = target.top + target.height / 2\n    const disc = document.createElement(\"span\")\n    disc.className = \"sp-fly\"\n    disc.style.left = `${origin.x}px`\n    disc.style.top = `${origin.y}px`\n    disc.style.background = accent\n    document.body.appendChild(disc)\n    const dx = tx - origin.x\n    const dy = ty - origin.y\n    const anim = disc.animate(\n      [\n        { transform: \"translate(-50%, -50%) scale(1)\", opacity: 1, offset: 0 },\n        { transform: `translate(calc(-50% + ${dx * 0.5}px), calc(-50% + ${dy * 0.5 - 90}px)) scale(0.9)`, opacity: 1, offset: 0.55 },\n        { transform: `translate(calc(-50% + ${dx}px), calc(-50% + ${dy}px)) scale(0.28)`, opacity: 0.7, offset: 1 },\n      ],\n      { duration: 720, easing: \"cubic-bezier(0.5, 0, 0.5, 1)\", fill: \"forwards\" },\n    )\n    anim.onfinish = () => {\n      disc.remove()\n      setBump(true)\n      window.setTimeout(() => setBump(false), 420)\n    }\n  }\n\n  const addToCart = (\n    p: { id: string; name: string; price: number; accent: string },\n    e?: { clientX: number; clientY: number },\n    open = false,\n  ) => {\n    setCart((prev) => {\n      const found = prev.find((l) => l.id === p.id)\n      if (found) return prev.map((l) => (l.id === p.id ? { ...l, qty: l.qty + 1 } : l))\n      return [...prev, { id: p.id, name: p.name, price: p.price, qty: 1, accent: p.accent }]\n    })\n    setPulse(p.id)\n    window.setTimeout(() => setPulse((cur) => (cur === p.id ? null : cur)), 900)\n    if (e) flyToCart({ x: e.clientX, y: e.clientY }, p.accent)\n    if (open) setCartOpen(true)\n  }\n\n  const setQty = (id: string, delta: number) =>\n    setCart((prev) =>\n      prev\n        .map((l) => (l.id === id ? { ...l, qty: l.qty + delta } : l))\n        .filter((l) => l.qty > 0),\n    )\n\n  const par = (rate: number) => (reduced ? 0 : scrollY * rate)\n\n  const [flavorRef, flavorShown] = useReveal<HTMLDivElement>()\n  const [storyRef, storyShown] = useReveal<HTMLDivElement>()\n  const [reviewsRef, reviewsShown] = useReveal<HTMLDivElement>()\n\n  return (\n    <div className=\"sprig-root\" data-reduced={reduced}>\n      <style>{css}</style>\n\n      {/* PROMO BAR — rotating announcements */}\n      <div className=\"sp-promo\" role=\"region\" aria-label=\"Announcements\">\n        <button\n          className=\"sp-promo-arrow\"\n          aria-label=\"Previous announcement\"\n          onClick={() => setPromo((p) => (p - 1 + PROMOS.length) % PROMOS.length)}\n        >\n          ‹\n        </button>\n        <div className=\"sp-promo-viewport\">\n          {PROMOS.map((m, i) => (\n            <p key={i} className={`sp-promo-msg ${i === promo ? \"is-on\" : \"\"}`} aria-hidden={i !== promo}>\n              {m}\n            </p>\n          ))}\n        </div>\n        <button\n          className=\"sp-promo-arrow\"\n          aria-label=\"Next announcement\"\n          onClick={() => setPromo((p) => (p + 1) % PROMOS.length)}\n        >\n          ›\n        </button>\n      </div>\n\n      {/* NAV */}\n      <header className=\"sp-nav\">\n        <a className=\"sp-logo\" href=\"#top\" aria-label=\"Sprig home\">\n          <span className=\"sp-logo-dot\" aria-hidden=\"true\" />\n          Sprig\n        </a>\n        <nav className=\"sp-nav-links\">\n          <a href=\"#flavors\">Flavours</a>\n          <a href=\"#shop\">Shop</a>\n          <a href=\"#subscribe\">Subscribe</a>\n          <a href=\"#story\">Our story</a>\n        </nav>\n        <button\n          ref={cartBtnRef}\n          className={`sp-cart-btn ${bump ? \"is-bump\" : \"\"}`}\n          onClick={() => setCartOpen(true)}\n          aria-label={`Open cart, ${count} items`}\n        >\n          Cart\n          <span className={`sp-cart-count ${count ? \"is-on\" : \"\"}`}>{count}</span>\n        </button>\n      </header>\n\n      {/* HERO */}\n      <section className=\"sp-hero\" id=\"top\">\n        <div className=\"sp-hero-img\" style={{ transform: `translate3d(0, ${par(0.06)}px, 0)` }} />\n        {!reduced && (\n          <video\n            ref={videoRef}\n            className={`sp-hero-video ${videoReady ? \"is-ready\" : \"\"}`}\n            src=\"/sprig/hero.mp4\"\n            poster=\"/sprig/hero.png\"\n            autoPlay\n            muted\n            loop\n            playsInline\n            preload=\"auto\"\n            onCanPlay={() => setVideoReady(true)}\n            onError={() => setVideoReady(false)}\n          />\n        )}\n        <div className=\"sp-hero-veil\" />\n        <div className=\"sp-hero-inner\">\n          <p className=\"sp-hero-kicker\">Sparkling adaptogen soda</p>\n          <h1 className=\"sp-hero-title\">\n            Soda that\n            <br />\n            likes you back.\n          </h1>\n          <p className=\"sp-hero-lede\">\n            Prebiotics, adaptogens, real pressed fruit, and three grams of sugar. All the fizz you\n            grew up on, without the 2pm crash you also grew up on.\n          </p>\n          <div className=\"sp-hero-cta\">\n            <a className=\"sp-btn sp-btn-lg\" href=\"#shop\">\n              Shop the fridge\n            </a>\n            <a className=\"sp-btn sp-btn-ghost sp-btn-lg\" href=\"#subscribe\">\n              Build a variety pack\n            </a>\n          </div>\n          <div className=\"sp-hero-proof\">\n            <span className=\"sp-stars\" aria-hidden=\"true\">\n              {\"★★★★★\"}\n            </span>\n            <span>\n              <strong>50,000+</strong> five-star reviews and a 4.8 average\n            </span>\n          </div>\n        </div>\n      </section>\n\n      {/* FLAVOUR TABS — switching recolours the section + crossfades the can */}\n      <section\n        className={`sp-flavors ${flavorShown ? \"is-shown\" : \"\"}`}\n        id=\"flavors\"\n        ref={flavorRef}\n        style={\n          {\n            \"--flavor\": flavor.accent,\n            \"--flavor-ink\": flavor.accentInk,\n            \"--flavor-tint\": flavor.tint,\n          } as React.CSSProperties\n        }\n      >\n        <div className=\"sp-flavors-head\">\n          <h2 className=\"sp-h2\">Pick your fizz</h2>\n          <p className=\"sp-sub\">Six flavours, one very short attention span. Tap to meet each one.</p>\n        </div>\n\n        <div className=\"sp-tabs\" role=\"tablist\" aria-label=\"Flavours\">\n          {FLAVORS.map((f, i) => (\n            <button\n              key={f.id}\n              role=\"tab\"\n              aria-selected={i === active}\n              className={`sp-tab ${i === active ? \"is-active\" : \"\"}`}\n              style={{ \"--tab\": f.accent } as React.CSSProperties}\n              onClick={() => setActive(i)}\n            >\n              {f.name}\n            </button>\n          ))}\n        </div>\n\n        <div className=\"sp-flavor-stage\">\n          <div className=\"sp-flavor-cans\">\n            {FLAVORS.map((f, i) => (\n              <div\n                key={f.id}\n                className={`sp-flavor-can ${i === active ? \"is-on\" : \"\"}`}\n                style={{ backgroundImage: `url(${f.img})` }}\n                aria-hidden={i !== active}\n              />\n            ))}\n            <div className=\"sp-flavor-halo\" aria-hidden=\"true\" />\n          </div>\n\n          <div className=\"sp-flavor-detail\">\n            <span className=\"sp-flavor-note\">{flavor.note}</span>\n            <h3 className=\"sp-flavor-name\">{flavor.name}</h3>\n            <p className=\"sp-flavor-taste\">{flavor.taste}</p>\n            <p className=\"sp-flavor-benefit\">{flavor.benefit}</p>\n            <div className=\"sp-flavor-buy\">\n              <button\n                className={`sp-flavor-add ${pulse === flavor.id ? \"is-pulse\" : \"\"}`}\n                onClick={(e) => addToCart({ id: flavor.id, name: `${flavor.name} 12-pack`, price: 28, accent: flavor.accent }, e)}\n              >\n                <span className=\"sp-flavor-add-label\">Add a 12-pack</span>\n                <span className=\"sp-flavor-add-done\" aria-hidden=\"true\">Added</span>\n              </button>\n              <span className=\"sp-flavor-price\">{money(28)}</span>\n            </div>\n          </div>\n        </div>\n      </section>\n\n      {/* COLLECTION TILES */}\n      <section className=\"sp-tiles\">\n        <div className=\"sp-tiles-head\">\n          <h2 className=\"sp-h2\">Find your shelf</h2>\n          <p className=\"sp-sub\">However you shop, there's a door in for you.</p>\n        </div>\n        <div className=\"sp-tiles-grid\">\n          {COLLECTIONS.map((c) => (\n            <a key={c.title} className=\"sp-tile\" href=\"#shop\" style={{ \"--tile\": c.accent } as React.CSSProperties}>\n              <div className=\"sp-tile-img\" style={{ backgroundImage: `url(${c.img})` }} />\n              <div className=\"sp-tile-body\">\n                <h3 className=\"sp-tile-title\">{c.title}</h3>\n                <p className=\"sp-tile-blurb\">{c.blurb}</p>\n                <span className=\"sp-tile-link\">Shop now</span>\n              </div>\n            </a>\n          ))}\n        </div>\n      </section>\n\n      {/* BESTSELLERS CAROUSEL */}\n      <section className=\"sp-shop\" id=\"shop\">\n        <div className=\"sp-shop-head\">\n          <div>\n            <h2 className=\"sp-h2\">The fridge favourites</h2>\n            <p className=\"sp-sub\">Rated by the people who reorder without thinking about it.</p>\n          </div>\n          <span className=\"sp-shop-hint\" aria-hidden=\"true\">Scroll for more</span>\n        </div>\n        <div className=\"sp-carousel\">\n          {PRODUCTS.map((p) => (\n            <article className=\"sp-card\" key={p.id} style={{ \"--card\": p.accent } as React.CSSProperties}>\n              <div className=\"sp-card-media\">\n                <div className=\"sp-card-img\" style={{ backgroundImage: `url(${p.img})` }} />\n                <div className=\"sp-card-swatches\" aria-hidden=\"true\">\n                  {FLAVORS.slice(0, 4).map((f) => (\n                    <span key={f.id} style={{ background: f.accent }} />\n                  ))}\n                </div>\n              </div>\n              <div className=\"sp-card-body\">\n                <div className=\"sp-card-rating\">\n                  <span className=\"sp-stars\" aria-hidden=\"true\">{\"★★★★★\"}</span>\n                  <span className=\"sp-card-rating-num\">\n                    {p.rating.toFixed(1)} <span>({p.reviews.toLocaleString()})</span>\n                  </span>\n                </div>\n                <h3 className=\"sp-card-name\">{p.name}</h3>\n                <p className=\"sp-card-taste\">{p.taste}</p>\n                <div className=\"sp-card-buy\">\n                  <span className=\"sp-card-price\">{money(p.price)}</span>\n                  <button\n                    className={`sp-quick ${pulse === p.id ? \"is-pulse\" : \"\"}`}\n                    onClick={(e) => addToCart(p, e)}\n                    aria-label={`Quick add ${p.name}`}\n                  >\n                    <span className=\"sp-quick-plus\" aria-hidden=\"true\">+</span>\n                    <span className=\"sp-quick-check\" aria-hidden=\"true\">✓</span>\n                  </button>\n                </div>\n              </div>\n            </article>\n          ))}\n        </div>\n      </section>\n\n      {/* SUBSCRIBE & SAVE / BUILD YOUR BUNDLE */}\n      <section className=\"sp-sub-block\" id=\"subscribe\">\n        <div className=\"sp-sub-grid\">\n          <div className=\"sp-sub-copy\">\n            <h2 className=\"sp-h2\">Build a bundle, save 20%</h2>\n            <p className=\"sp-sub\">\n              Mix any flavours you like, pick how often, and let it show up on its own. Skip, swap,\n              or cancel any time — the whole thing lives in one tap.\n            </p>\n            <ul className=\"sp-sub-perks\">\n              <li>20% off every single order, for as long as you stay</li>\n              <li>Free shipping on subscriptions, no minimum</li>\n              <li>First dibs on new flavours before they hit the shop</li>\n            </ul>\n          </div>\n\n          <div className=\"sp-builder\">\n            <p className=\"sp-builder-step\">How many cans?</p>\n            <div className=\"sp-builder-sizes\">\n              {BUNDLES.map((b, i) => (\n                <button\n                  key={b.size}\n                  className={`sp-size ${i === bundle ? \"is-active\" : \"\"}`}\n                  onClick={() => setBundle(i)}\n                >\n                  <span className=\"sp-size-n\">{b.size}</span>\n                  <span className=\"sp-size-label\">{b.label}</span>\n                </button>\n              ))}\n            </div>\n\n            <p className=\"sp-builder-step\">How often?</p>\n            <div className=\"sp-freq\">\n              <button className={`sp-freq-opt ${!weekly ? \"is-active\" : \"\"}`} onClick={() => setWeekly(false)}>\n                Every month\n              </button>\n              <button className={`sp-freq-opt ${weekly ? \"is-active\" : \"\"}`} onClick={() => setWeekly(true)}>\n                Every 2 weeks\n              </button>\n            </div>\n\n            {(() => {\n              const b = BUNDLES[bundle]\n              const sub = b.price * 0.8\n              return (\n                <div className=\"sp-builder-total\">\n                  <div>\n                    <span className=\"sp-builder-was\">{money(b.price)}</span>\n                    <span className=\"sp-builder-now\">{money(sub)}</span>\n                    <span className=\"sp-builder-cadence\">{weekly ? \"every 2 weeks\" : \"per month\"}</span>\n                  </div>\n                  <button\n                    className=\"sp-btn sp-btn-lg\"\n                    onClick={(e) =>\n                      addToCart(\n                        { id: `sub-${b.size}`, name: `${b.size}-can subscription`, price: Math.round(sub * 100) / 100, accent: \"#3DA35D\" },\n                        e,\n                        true,\n                      )\n                    }\n                  >\n                    Start subscription\n                  </button>\n                </div>\n              )\n            })()}\n          </div>\n        </div>\n      </section>\n\n      {/* FOUNDER STORY + PRESS */}\n      <section className=\"sp-story\" id=\"story\" ref={storyRef}>\n        <div className={`sp-story-grid ${storyShown ? \"is-shown\" : \"\"}`}>\n          <div className=\"sp-story-media\">\n            <div className=\"sp-story-img\" style={{ backgroundImage: `url(/sprig/can5.png)` }} />\n          </div>\n          <div className=\"sp-story-copy\">\n            <h2 className=\"sp-h2\">We just wanted a soda that wasn't a compromise</h2>\n            <p>\n              Sprig started in a rented kitchen in 2021, after our founder quit a nine-can-a-day\n              habit and hated every \"healthy\" replacement she tried. They all tasted like an\n              apology.\n            </p>\n            <p>\n              So we spent two years getting the fizz, the sugar, and the fruit exactly right, then\n              added prebiotics and adaptogens because a drink you have every day may as well do\n              something. No apology. Just a really good soda.\n            </p>\n            <a className=\"sp-btn sp-btn-ghost\" href=\"#top\">Read the whole story</a>\n          </div>\n        </div>\n        <div className=\"sp-press\">\n          <span className=\"sp-press-label\">As seen in</span>\n          <div className=\"sp-press-marquee\">\n            <div className=\"sp-press-row\">\n              {[...PRESS, ...PRESS].map((p, i) => (\n                <span key={i} className=\"sp-press-name\">{p}</span>\n              ))}\n            </div>\n          </div>\n        </div>\n      </section>\n\n      {/* REVIEWS + UGC */}\n      <section className=\"sp-reviews\" ref={reviewsRef}>\n        <div className=\"sp-reviews-head\">\n          <h2 className=\"sp-h2\">50,000+ people, one very full fridge</h2>\n          <p className=\"sp-sub\">A 4.8 average across every flavour. Here's a few, unedited.</p>\n        </div>\n        <div className={`sp-reviews-grid ${reviewsShown ? \"is-shown\" : \"\"}`}>\n          {REVIEWS.map((r, i) => (\n            <figure className=\"sp-review\" key={r.name} style={{ transitionDelay: `${i * 90}ms` }}>\n              <span className=\"sp-stars\" aria-hidden=\"true\">{\"★★★★★\".slice(0, r.stars) + \"☆☆☆☆☆\".slice(0, 5 - r.stars)}</span>\n              <blockquote>{r.text}</blockquote>\n              <figcaption>\n                <strong>{r.name}</strong>\n                <span>on {r.flavor}</span>\n              </figcaption>\n            </figure>\n          ))}\n        </div>\n        <div className=\"sp-ugc\">\n          {[\"/sprig/can1.png\", \"/sprig/hero.png\", \"/sprig/can3.png\", \"/sprig/can6.png\", \"/sprig/can2.png\"].map((src, i) => (\n            <div key={i} className=\"sp-ugc-tile\" style={{ backgroundImage: `url(${src})` }} />\n          ))}\n        </div>\n      </section>\n\n      {/* NEWSLETTER CAPTURE */}\n      <section className=\"sp-capture\">\n        <div className=\"sp-capture-inner\">\n          <h2 className=\"sp-capture-title\">Take 20% off your first order</h2>\n          <p className=\"sp-capture-sub\">\n            Drop your email and we'll send a code, plus a heads-up whenever a new flavour lands.\n          </p>\n          <form className=\"sp-capture-form\" onSubmit={(e) => e.preventDefault()}>\n            <input\n              className=\"sp-capture-input\"\n              type=\"email\"\n              required\n              placeholder=\"you@email.com\"\n              aria-label=\"Email address\"\n            />\n            <button className=\"sp-btn sp-btn-lg\" type=\"submit\">Send my code</button>\n          </form>\n          <p className=\"sp-capture-fine\">No spam, no more than one email a week. Unsubscribe in a tap.</p>\n        </div>\n      </section>\n\n      {/* FOOTER */}\n      <footer className=\"sp-footer\">\n        <div className=\"sp-footer-grid\">\n          <div className=\"sp-footer-brand\">\n            <a className=\"sp-logo\" href=\"#top\">\n              <span className=\"sp-logo-dot\" aria-hidden=\"true\" />\n              Sprig\n            </a>\n            <p>Sparkling adaptogen soda, brewed in small batches and shipped cold to your door.</p>\n          </div>\n          <div className=\"sp-footer-col\">\n            <h3>Shop</h3>\n            <a href=\"#shop\">All flavours</a>\n            <a href=\"#subscribe\">Variety packs</a>\n            <a href=\"#subscribe\">Subscribe & save</a>\n            <a href=\"#shop\">Gift cards</a>\n          </div>\n          <div className=\"sp-footer-col\">\n            <h3>Company</h3>\n            <a href=\"#story\">Our story</a>\n            <a href=\"#story\">Ingredients</a>\n            <a href=\"#top\">Careers</a>\n            <a href=\"#top\">Wholesale</a>\n          </div>\n          <div className=\"sp-footer-col\">\n            <h3>Help</h3>\n            <a href=\"#top\">Track an order</a>\n            <a href=\"#subscribe\">Manage subscription</a>\n            <a href=\"#top\">Returns</a>\n            <a href=\"#top\">Contact us</a>\n          </div>\n        </div>\n        <div className=\"sp-footer-base\">\n          <span>© 2026 Sprig Beverages</span>\n          <div className=\"sp-footer-legal\">\n            <a href=\"#top\">Privacy</a>\n            <a href=\"#top\">Terms</a>\n            <a href=\"#top\">Accessibility</a>\n          </div>\n        </div>\n      </footer>\n\n      {/* SECTIONS-END */}\n\n      {/* CART DRAWER */}\n      <div className={`sp-cart-scrim ${cartOpen ? \"is-open\" : \"\"}`} onClick={() => setCartOpen(false)} aria-hidden={!cartOpen} />\n      <aside className={`sp-drawer ${cartOpen ? \"is-open\" : \"\"}`} aria-label=\"Your cart\" aria-hidden={!cartOpen}>\n        <div className=\"sp-drawer-top\">\n          <h2 className=\"sp-drawer-title\">Your cart{count ? ` (${count})` : \"\"}</h2>\n          <button className=\"sp-drawer-close\" onClick={() => setCartOpen(false)} aria-label=\"Close cart\">×</button>\n        </div>\n\n        <div className=\"sp-ship\">\n          <p className=\"sp-ship-msg\">\n            {remaining > 0 ? (\n              <>You're <strong>{money(remaining)}</strong> away from free shipping.</>\n            ) : (\n              <>Nice — you've unlocked <strong>free shipping</strong>.</>\n            )}\n          </p>\n          <div className=\"sp-ship-track\">\n            <div className={`sp-ship-fill ${remaining === 0 ? \"is-full\" : \"\"}`} style={{ width: `${shipPct}%` }} />\n          </div>\n        </div>\n\n        <div className=\"sp-drawer-lines\">\n          {cart.length === 0 ? (\n            <div className=\"sp-cart-empty\">\n              <p>Your cart's feeling a little flat.</p>\n              <button className=\"sp-btn\" onClick={() => setCartOpen(false)}>Start shopping</button>\n            </div>\n          ) : (\n            cart.map((l) => (\n              <div className=\"sp-line\" key={l.id}>\n                <span className=\"sp-line-swatch\" style={{ background: l.accent }} aria-hidden=\"true\" />\n                <div className=\"sp-line-info\">\n                  <p className=\"sp-line-name\">{l.name}</p>\n                  <p className=\"sp-line-price\">{money(l.price)}</p>\n                </div>\n                <div className=\"sp-stepper\">\n                  <button onClick={() => setQty(l.id, -1)} aria-label={`Decrease ${l.name}`}>−</button>\n                  <span>{l.qty}</span>\n                  <button onClick={() => setQty(l.id, 1)} aria-label={`Increase ${l.name}`}>+</button>\n                </div>\n              </div>\n            ))\n          )}\n        </div>\n\n        {cart.length > 0 && (\n          <div className=\"sp-drawer-foot\">\n            <div className=\"sp-drawer-subtotal\">\n              <span>Subtotal</span>\n              <strong>{money(total)}</strong>\n            </div>\n            <button className=\"sp-btn sp-btn-lg sp-drawer-checkout\">Checkout</button>\n            <p className=\"sp-drawer-fine\">Taxes calculated at checkout. 30-day taste guarantee.</p>\n          </div>\n        )}\n      </aside>\n    </div>\n  )\n}\n\nconst css = `\n.sprig-root {\n  --cream: #fffdf6;\n  --paper: #ffffff;\n  --ink: #191a1f;\n  --ink-soft: #5a5c66;\n  --line: rgba(20, 20, 30, 0.10);\n  --line-2: rgba(20, 20, 30, 0.16);\n  --brand: #E5346D;\n  --shadow: 0 24px 60px -30px rgba(20, 18, 30, 0.4);\n  background: var(--cream);\n  color: var(--ink);\n  font-family: var(--font-geist), system-ui, -apple-system, sans-serif;\n  -webkit-font-smoothing: antialiased;\n  line-height: 1.55;\n  overflow-x: hidden;\n}\n.sprig-root *,\n.sprig-root *::before,\n.sprig-root *::after { box-sizing: border-box; }\n\n/* Global reduced-motion gate: kill every transition + animation. */\n.sprig-root[data-reduced=\"true\"] * {\n  transition: none !important;\n  animation: none !important;\n  scroll-behavior: auto !important;\n}\n\n.sprig-root a { color: inherit; }\n\n/* Shared button */\n.sp-btn {\n  display: inline-flex; align-items: center; justify-content: center;\n  font-family: inherit; font-weight: 650; font-size: 1rem;\n  text-decoration: none; cursor: pointer; border: none;\n  background: var(--ink); color: var(--cream);\n  padding: 0.8rem 1.5rem; border-radius: 999px;\n  transition: transform 0.35s cubic-bezier(0.16,1,0.3,1), box-shadow 0.35s, background 0.3s, color 0.3s;\n}\n.sp-btn:hover { transform: translateY(-2px); box-shadow: 0 16px 30px -14px rgba(20,18,30,0.5); }\n.sp-btn-lg { padding: 1rem 1.9rem; font-size: 1.06rem; }\n.sp-btn-ghost { background: transparent; color: var(--ink); box-shadow: inset 0 0 0 1.5px var(--line-2); }\n.sp-btn-ghost:hover { box-shadow: inset 0 0 0 1.5px var(--ink); }\n\n/* PROMO BAR */\n.sp-promo {\n  position: relative; z-index: 60;\n  display: flex; align-items: center; gap: 0.5rem;\n  background: var(--ink); color: var(--cream);\n  padding: 0.55rem clamp(0.75rem, 3vw, 1.5rem);\n  min-height: 2.6rem;\n}\n.sp-promo-viewport { position: relative; flex: 1; height: 1.5rem; overflow: hidden; }\n.sp-promo-msg {\n  position: absolute; inset: 0; margin: 0; text-align: center;\n  font-size: 0.9rem; font-weight: 500; letter-spacing: -0.005em;\n  opacity: 0; transform: translateY(60%);\n  transition: opacity 0.5s ease, transform 0.5s cubic-bezier(0.16,1,0.3,1);\n  white-space: nowrap; text-overflow: ellipsis; overflow: hidden;\n}\n.sp-promo-msg.is-on { opacity: 1; transform: translateY(0); }\n.sp-promo-arrow {\n  flex: none; width: 1.7rem; height: 1.7rem; border-radius: 999px;\n  border: none; background: rgba(255,255,255,0.1); color: var(--cream);\n  font-size: 1.15rem; line-height: 1; cursor: pointer; transition: background 0.25s;\n}\n.sp-promo-arrow:hover { background: rgba(255,255,255,0.22); }\n\n/* NAV */\n.sp-nav {\n  position: sticky; top: 0; z-index: 55;\n  display: flex; align-items: center; justify-content: space-between;\n  gap: 1rem; padding: 0.9rem clamp(1rem, 4vw, 2.75rem);\n  background: rgba(255,253,246,0.92);\n  border-bottom: 1px solid var(--line);\n}\n.sp-logo {\n  display: inline-flex; align-items: center; gap: 0.5rem;\n  font-size: 1.45rem; font-weight: 750; letter-spacing: -0.03em;\n  text-decoration: none; color: var(--ink);\n}\n.sp-logo-dot {\n  width: 0.85rem; height: 0.85rem; border-radius: 999px;\n  background: var(--brand);\n  box-shadow: 0 0 0 4px rgba(229,52,109,0.16);\n}\n.sp-nav-links { display: flex; align-items: center; gap: clamp(1rem, 2.4vw, 2rem); }\n.sp-nav-links a { text-decoration: none; font-weight: 550; font-size: 0.98rem; color: var(--ink-soft); transition: color 0.25s; }\n.sp-nav-links a:hover { color: var(--ink); }\n.sp-cart-btn {\n  display: inline-flex; align-items: center; gap: 0.5rem;\n  font-family: inherit; font-weight: 650; font-size: 0.98rem; cursor: pointer;\n  background: var(--ink); color: var(--cream);\n  border: none; border-radius: 999px; padding: 0.6rem 1.15rem;\n  transition: transform 0.3s cubic-bezier(0.16,1,0.3,1);\n}\n.sp-cart-btn:hover { transform: translateY(-2px); }\n.sp-cart-count {\n  min-width: 1.4rem; height: 1.4rem; padding: 0 0.35rem; border-radius: 999px;\n  display: inline-grid; place-items: center; font-size: 0.8rem; font-weight: 700;\n  background: var(--brand); color: #fff;\n  transform: scale(0); transition: transform 0.35s cubic-bezier(0.34,1.56,0.64,1);\n}\n.sp-cart-count.is-on { transform: scale(1); }\n@media (max-width: 720px) { .sp-nav-links { display: none; } }\n\n/* HERO */\n.sp-hero {\n  position: relative; min-height: min(88svh, 760px);\n  display: grid; align-items: center; overflow: hidden;\n  padding: clamp(2.5rem, 7vw, 6rem) clamp(1.25rem, 6vw, 5rem);\n}\n.sp-hero-img {\n  position: absolute; inset: -8% 0;\n  background: url(/sprig/hero.png) center/cover no-repeat;\n  will-change: transform;\n}\n.sp-hero-veil {\n  position: absolute; inset: 0;\n  background: linear-gradient(100deg, rgba(255,253,246,0.94) 0%, rgba(255,253,246,0.78) 38%, rgba(255,253,246,0.12) 72%, rgba(255,253,246,0) 100%);\n}\n@media (max-width: 720px) {\n  .sp-hero-veil { background: linear-gradient(180deg, rgba(255,253,246,0.7) 0%, rgba(255,253,246,0.88) 55%, var(--cream) 100%); }\n}\n.sp-hero-inner { position: relative; max-width: 38rem; }\n.sp-hero-kicker {\n  display: inline-block; margin: 0 0 1.1rem;\n  font-size: 0.95rem; font-weight: 650; color: var(--brand);\n  background: rgba(229,52,109,0.1); padding: 0.35rem 0.85rem; border-radius: 999px;\n}\n.sp-hero-title {\n  margin: 0; font-weight: 780; letter-spacing: -0.04em; line-height: 0.98;\n  font-size: clamp(2.9rem, 8.5vw, 6rem);\n}\n.sp-hero-lede {\n  margin: 1.4rem 0 2rem; max-width: 32rem;\n  font-size: clamp(1.05rem, 1.7vw, 1.3rem); color: var(--ink-soft);\n}\n.sp-hero-cta { display: flex; flex-wrap: wrap; gap: 0.8rem; }\n.sp-hero-proof {\n  display: flex; align-items: center; gap: 0.6rem; margin-top: 1.8rem;\n  font-size: 0.98rem; color: var(--ink-soft);\n}\n.sp-hero-proof strong { color: var(--ink); }\n.sp-stars { color: #F2B705; letter-spacing: 0.06em; font-size: 1.05rem; }\n\n/* Shared section furniture */\n.sp-h2 {\n  margin: 0; font-weight: 760; letter-spacing: -0.035em; line-height: 1.02;\n  font-size: clamp(2rem, 5vw, 3.4rem);\n}\n.sp-sub { margin: 0.7rem 0 0; color: var(--ink-soft); font-size: clamp(1rem, 1.5vw, 1.2rem); max-width: 34rem; }\n\n/* FLAVOUR TABS */\n.sp-flavors {\n  padding: clamp(3.5rem, 8vw, 7rem) clamp(1.25rem, 6vw, 5rem);\n  background:\n    radial-gradient(120% 90% at 82% 8%, var(--flavor-tint) 0%, transparent 60%),\n    var(--cream);\n  transition: background 0.7s ease;\n}\n.sp-flavors-head { max-width: 40rem; margin-bottom: clamp(2rem, 4vw, 3rem); }\n.sp-tabs { display: flex; flex-wrap: wrap; gap: 0.55rem; margin-bottom: clamp(2rem, 4vw, 3rem); }\n.sp-tab {\n  font-family: inherit; font-weight: 600; font-size: 0.98rem; cursor: pointer;\n  padding: 0.6rem 1.15rem; border-radius: 999px;\n  background: var(--paper); color: var(--ink-soft);\n  border: 1.5px solid var(--line);\n  transition: color 0.3s, border-color 0.3s, background 0.3s, transform 0.3s cubic-bezier(0.16,1,0.3,1);\n}\n.sp-tab:hover { transform: translateY(-2px); color: var(--ink); }\n.sp-tab.is-active {\n  color: #fff; background: var(--tab); border-color: var(--tab);\n  box-shadow: 0 12px 26px -14px var(--tab);\n}\n.sp-flavor-stage {\n  display: grid; grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);\n  align-items: center; gap: clamp(1.5rem, 5vw, 4.5rem);\n}\n.sp-flavor-cans {\n  position: relative; aspect-ratio: 1 / 1; border-radius: 28px; overflow: hidden;\n  background: var(--flavor-tint); transition: background 0.7s ease;\n  box-shadow: var(--shadow);\n}\n.sp-flavor-halo {\n  position: absolute; inset: 0; pointer-events: none;\n  background: radial-gradient(60% 60% at 50% 42%, color-mix(in srgb, var(--flavor) 24%, transparent), transparent 70%);\n  transition: background 0.7s ease;\n}\n.sp-flavor-can {\n  position: absolute; inset: 0;\n  background-size: cover; background-position: center;\n  opacity: 0; transform: scale(1.06);\n  transition: opacity 0.7s ease, transform 0.9s cubic-bezier(0.16,1,0.3,1);\n}\n.sp-flavor-can.is-on { opacity: 1; transform: scale(1); }\n.sp-flavor-detail { min-width: 0; }\n.sp-flavor-note {\n  display: inline-block; margin-bottom: 1rem;\n  font-size: 0.9rem; font-weight: 650;\n  color: var(--flavor-ink); background: var(--flavor);\n  padding: 0.35rem 0.9rem; border-radius: 999px;\n  transition: background 0.6s ease, color 0.6s ease;\n}\n.sp-flavor-name {\n  margin: 0; font-weight: 760; letter-spacing: -0.035em; line-height: 1;\n  font-size: clamp(2.4rem, 6vw, 4rem);\n}\n.sp-flavor-taste { margin: 1rem 0 0.4rem; font-size: clamp(1.1rem, 2vw, 1.45rem); font-weight: 550; }\n.sp-flavor-benefit { margin: 0 0 2rem; color: var(--ink-soft); font-size: 1.05rem; max-width: 26rem; }\n.sp-flavor-buy { display: flex; align-items: center; gap: 1.1rem; }\n.sp-flavor-add {\n  position: relative; overflow: hidden;\n  font-family: inherit; font-weight: 680; font-size: 1.02rem; cursor: pointer;\n  color: var(--flavor-ink); background: var(--flavor); border: none;\n  padding: 0.9rem 1.7rem; border-radius: 999px;\n  transition: transform 0.35s cubic-bezier(0.16,1,0.3,1), background 0.6s ease, color 0.6s ease, box-shadow 0.35s;\n}\n.sp-flavor-add:hover { transform: translateY(-2px); box-shadow: 0 16px 30px -14px var(--flavor); }\n.sp-flavor-add-label, .sp-flavor-add-done {\n  display: block; transition: transform 0.4s cubic-bezier(0.16,1,0.3,1), opacity 0.4s;\n}\n.sp-flavor-add-done {\n  position: absolute; inset: 0; display: grid; place-items: center;\n  opacity: 0; transform: translateY(100%);\n}\n.sp-flavor-add.is-pulse .sp-flavor-add-label { opacity: 0; transform: translateY(-100%); }\n.sp-flavor-add.is-pulse .sp-flavor-add-done { opacity: 1; transform: translateY(0); }\n.sp-flavor-price { font-size: 1.2rem; font-weight: 700; font-variant-numeric: tabular-nums; }\n@media (max-width: 800px) {\n  .sp-flavor-stage { grid-template-columns: 1fr; }\n  .sp-flavor-cans { max-width: 24rem; margin: 0 auto; width: 100%; }\n}\n\n/* COLLECTION TILES */\n.sp-tiles { padding: clamp(3rem, 7vw, 6rem) clamp(1.25rem, 6vw, 5rem); }\n.sp-tiles-head { max-width: 40rem; margin-bottom: clamp(1.8rem, 3.5vw, 2.6rem); }\n.sp-tiles-grid {\n  display: grid; gap: 1.1rem;\n  grid-template-columns: repeat(4, 1fr);\n}\n.sp-tile {\n  position: relative; overflow: hidden; border-radius: 22px;\n  min-height: 20rem; display: flex; align-items: flex-end;\n  text-decoration: none; color: #fff;\n  border: 1px solid var(--line);\n  transition: transform 0.4s cubic-bezier(0.16,1,0.3,1), box-shadow 0.4s;\n}\n.sp-tile:hover { transform: translateY(-6px); box-shadow: var(--shadow); }\n.sp-tile-img {\n  position: absolute; inset: 0; background-size: cover; background-position: center;\n  transition: transform 0.6s cubic-bezier(0.16,1,0.3,1);\n}\n.sp-tile:hover .sp-tile-img { transform: scale(1.06); }\n.sp-tile::after {\n  content: \"\"; position: absolute; inset: 0;\n  background: linear-gradient(to top, color-mix(in srgb, var(--tile) 92%, black) 0%, color-mix(in srgb, var(--tile) 30%, transparent) 45%, transparent 78%);\n}\n.sp-tile-body { position: relative; padding: 1.4rem; }\n.sp-tile-title { margin: 0 0 0.3rem; font-size: 1.4rem; font-weight: 720; letter-spacing: -0.02em; }\n.sp-tile-blurb { margin: 0 0 0.7rem; font-size: 0.95rem; opacity: 0.92; }\n.sp-tile-link { font-weight: 650; font-size: 0.95rem; border-bottom: 2px solid rgba(255,255,255,0.6); padding-bottom: 1px; }\n@media (max-width: 900px) { .sp-tiles-grid { grid-template-columns: repeat(2, 1fr); } }\n@media (max-width: 480px) { .sp-tiles-grid { grid-template-columns: 1fr; } }\n\n/* BESTSELLERS CAROUSEL */\n.sp-shop { padding: clamp(3rem, 7vw, 6rem) 0; background: var(--paper); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }\n.sp-shop-head {\n  display: flex; align-items: flex-end; justify-content: space-between; gap: 1.5rem;\n  padding: 0 clamp(1.25rem, 6vw, 5rem); margin-bottom: clamp(1.6rem, 3vw, 2.4rem);\n}\n.sp-shop-hint { color: var(--ink-soft); font-size: 0.9rem; font-weight: 550; white-space: nowrap; }\n.sp-carousel {\n  display: flex; gap: 1.1rem; overflow-x: auto; scroll-snap-type: x mandatory;\n  padding: 0.4rem clamp(1.25rem, 6vw, 5rem) 1.4rem;\n  scrollbar-width: thin; scrollbar-color: var(--line-2) transparent;\n}\n.sp-carousel::-webkit-scrollbar { height: 7px; }\n.sp-carousel::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 999px; }\n.sp-card {\n  scroll-snap-align: start; flex: 0 0 clamp(15rem, 30vw, 18.5rem);\n  background: var(--cream); border: 1px solid var(--line); border-radius: 20px; overflow: hidden;\n  transition: border-color 0.35s, transform 0.4s cubic-bezier(0.16,1,0.3,1), box-shadow 0.4s;\n}\n.sp-card:hover { transform: translateY(-6px); border-color: var(--card); box-shadow: var(--shadow); }\n.sp-card-media { position: relative; }\n.sp-card-img { aspect-ratio: 1 / 1; background-size: cover; background-position: center; }\n.sp-card-swatches { position: absolute; left: 0.9rem; bottom: 0.9rem; display: flex; gap: 0.35rem; }\n.sp-card-swatches span { width: 0.85rem; height: 0.85rem; border-radius: 999px; box-shadow: 0 0 0 2px rgba(255,255,255,0.85); }\n.sp-card-body { padding: 1.15rem 1.25rem 1.4rem; }\n.sp-card-rating { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.55rem; }\n.sp-card-rating-num { font-size: 0.86rem; font-weight: 600; color: var(--ink); }\n.sp-card-rating-num span { color: var(--ink-soft); font-weight: 500; }\n.sp-card-name { margin: 0 0 0.25rem; font-size: 1.15rem; font-weight: 700; letter-spacing: -0.02em; }\n.sp-card-taste { margin: 0 0 1.1rem; font-size: 0.92rem; color: var(--ink-soft); min-height: 2.6em; }\n.sp-card-buy { display: flex; align-items: center; justify-content: space-between; }\n.sp-card-price { font-size: 1.2rem; font-weight: 700; font-variant-numeric: tabular-nums; }\n.sp-quick {\n  position: relative; width: 2.7rem; height: 2.7rem; border-radius: 999px; cursor: pointer;\n  border: none; background: var(--ink); color: var(--cream);\n  transition: transform 0.35s cubic-bezier(0.34,1.56,0.64,1), background 0.3s;\n}\n.sp-quick:hover { transform: scale(1.08); background: var(--card); }\n.sp-quick-plus, .sp-quick-check {\n  position: absolute; inset: 0; display: grid; place-items: center;\n  font-size: 1.4rem; line-height: 1; transition: opacity 0.3s, transform 0.4s cubic-bezier(0.16,1,0.3,1);\n}\n.sp-quick-check { opacity: 0; transform: scale(0.4); font-size: 1.15rem; }\n.sp-quick.is-pulse { background: var(--card); }\n.sp-quick.is-pulse .sp-quick-plus { opacity: 0; transform: scale(0.4); }\n.sp-quick.is-pulse .sp-quick-check { opacity: 1; transform: scale(1); }\n\n/* CART DRAWER */\n.sp-cart-scrim {\n  position: fixed; inset: 0; z-index: 90;\n  background: rgba(20, 18, 30, 0.4);\n  opacity: 0; visibility: hidden; transition: opacity 0.4s ease, visibility 0.4s;\n}\n.sp-cart-scrim.is-open { opacity: 1; visibility: visible; }\n.sp-drawer {\n  position: fixed; top: 0; right: 0; bottom: 0; z-index: 95;\n  width: min(29rem, 100vw); display: flex; flex-direction: column;\n  background: var(--cream); box-shadow: -30px 0 60px -30px rgba(20,18,30,0.45);\n  transform: translateX(100%); transition: transform 0.5s cubic-bezier(0.16,1,0.3,1);\n}\n.sp-drawer.is-open { transform: translateX(0); }\n.sp-drawer-top {\n  display: flex; align-items: center; justify-content: space-between;\n  padding: 1.4rem 1.5rem 1.1rem; border-bottom: 1px solid var(--line);\n}\n.sp-drawer-title { margin: 0; font-size: 1.3rem; font-weight: 720; letter-spacing: -0.02em; }\n.sp-drawer-close {\n  width: 2.2rem; height: 2.2rem; border-radius: 999px; cursor: pointer;\n  border: none; background: transparent; font-size: 1.6rem; line-height: 1; color: var(--ink-soft);\n  transition: background 0.25s, color 0.25s;\n}\n.sp-drawer-close:hover { background: rgba(20,18,30,0.06); color: var(--ink); }\n\n.sp-ship { padding: 1.1rem 1.5rem; background: var(--paper); border-bottom: 1px solid var(--line); }\n.sp-ship-msg { margin: 0 0 0.7rem; font-size: 0.95rem; color: var(--ink-soft); }\n.sp-ship-msg strong { color: var(--ink); }\n.sp-ship-track { height: 0.6rem; border-radius: 999px; background: rgba(20,18,30,0.08); overflow: hidden; }\n.sp-ship-fill {\n  height: 100%; border-radius: 999px; background: var(--brand);\n  transition: width 0.7s cubic-bezier(0.16,1,0.3,1), background 0.4s;\n}\n.sp-ship-fill.is-full { background: #3DA35D; }\n\n.sp-drawer-lines { flex: 1; overflow-y: auto; padding: 0.6rem 1.5rem; }\n.sp-cart-empty { display: grid; gap: 1rem; place-items: center; text-align: center; padding: 3.5rem 1rem; color: var(--ink-soft); }\n.sp-line { display: flex; align-items: center; gap: 0.9rem; padding: 1rem 0; border-bottom: 1px solid var(--line); }\n.sp-line-swatch { flex: none; width: 2.6rem; height: 2.6rem; border-radius: 12px; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08); }\n.sp-line-info { flex: 1; min-width: 0; }\n.sp-line-name { margin: 0; font-weight: 620; font-size: 0.98rem; }\n.sp-line-price { margin: 0.1rem 0 0; color: var(--ink-soft); font-size: 0.9rem; font-variant-numeric: tabular-nums; }\n.sp-stepper { display: flex; align-items: center; gap: 0.15rem; border: 1px solid var(--line-2); border-radius: 999px; padding: 0.15rem; }\n.sp-stepper button {\n  width: 1.7rem; height: 1.7rem; border-radius: 999px; cursor: pointer; border: none; background: transparent;\n  font-size: 1.05rem; line-height: 1; color: var(--ink); transition: background 0.2s;\n}\n.sp-stepper button:hover { background: rgba(20,18,30,0.08); }\n.sp-stepper span { min-width: 1.4rem; text-align: center; font-weight: 650; font-size: 0.95rem; font-variant-numeric: tabular-nums; }\n\n.sp-drawer-foot { padding: 1.2rem 1.5rem 1.5rem; border-top: 1px solid var(--line); background: var(--paper); }\n.sp-drawer-subtotal { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 1rem; }\n.sp-drawer-subtotal span { color: var(--ink-soft); font-size: 0.98rem; }\n.sp-drawer-subtotal strong { font-size: 1.5rem; font-weight: 750; font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }\n.sp-drawer-checkout { width: 100%; }\n.sp-drawer-fine { margin: 0.8rem 0 0; text-align: center; font-size: 0.82rem; color: var(--ink-soft); }\n\n/* SUBSCRIBE & SAVE */\n.sp-sub-block { padding: clamp(3.5rem, 8vw, 7rem) clamp(1.25rem, 6vw, 5rem); }\n.sp-sub-grid {\n  display: grid; grid-template-columns: 1fr 1fr; gap: clamp(1.75rem, 5vw, 4rem); align-items: center;\n  background: linear-gradient(135deg, #171820 0%, #24202e 100%); color: var(--cream);\n  border-radius: 30px; padding: clamp(2rem, 5vw, 4rem);\n}\n.sp-sub-copy .sp-h2 { color: var(--cream); }\n.sp-sub-copy .sp-sub { color: rgba(255,253,246,0.72); }\n.sp-sub-perks { margin: 1.6rem 0 0; padding: 0; list-style: none; display: grid; gap: 0.7rem; }\n.sp-sub-perks li { position: relative; padding-left: 1.7rem; font-size: 0.98rem; color: rgba(255,253,246,0.9); }\n.sp-sub-perks li::before {\n  content: \"✓\"; position: absolute; left: 0; top: 0;\n  width: 1.2rem; height: 1.2rem; border-radius: 999px; display: grid; place-items: center;\n  background: #3DA35D; color: #fff; font-size: 0.75rem; font-weight: 700;\n}\n.sp-builder { background: var(--cream); color: var(--ink); border-radius: 22px; padding: clamp(1.5rem, 3vw, 2rem); }\n.sp-builder-step { margin: 0 0 0.7rem; font-weight: 650; font-size: 0.95rem; }\n.sp-builder-step:not(:first-child) { margin-top: 1.4rem; }\n.sp-builder-sizes { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.6rem; }\n.sp-size {\n  display: grid; gap: 0.2rem; text-align: left; cursor: pointer;\n  padding: 0.9rem 0.9rem; border-radius: 14px; background: var(--paper);\n  border: 1.5px solid var(--line); font-family: inherit;\n  transition: border-color 0.3s, transform 0.3s cubic-bezier(0.16,1,0.3,1), background 0.3s;\n}\n.sp-size:hover { transform: translateY(-2px); }\n.sp-size.is-active { border-color: var(--ink); background: #fff; box-shadow: var(--shadow); }\n.sp-size-n { font-size: 1.5rem; font-weight: 760; letter-spacing: -0.03em; }\n.sp-size-label { font-size: 0.8rem; color: var(--ink-soft); }\n.sp-freq { display: flex; gap: 0.5rem; }\n.sp-freq-opt {\n  flex: 1; cursor: pointer; font-family: inherit; font-weight: 600; font-size: 0.95rem;\n  padding: 0.75rem; border-radius: 999px; background: var(--paper);\n  border: 1.5px solid var(--line); color: var(--ink-soft);\n  transition: color 0.3s, border-color 0.3s, background 0.3s;\n}\n.sp-freq-opt.is-active { color: var(--ink); border-color: var(--ink); background: #fff; }\n.sp-builder-total { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-top: 1.6rem; flex-wrap: wrap; }\n.sp-builder-was { text-decoration: line-through; color: var(--ink-soft); font-size: 1rem; margin-right: 0.5rem; font-variant-numeric: tabular-nums; }\n.sp-builder-now { font-size: 1.9rem; font-weight: 760; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; }\n.sp-builder-cadence { display: block; font-size: 0.85rem; color: var(--ink-soft); margin-top: 0.15rem; }\n@media (max-width: 820px) { .sp-sub-grid { grid-template-columns: 1fr; } }\n\n/* HERO VIDEO — fades in over the poster still once it can play */\n.sp-hero-video {\n  position: absolute; inset: -8% 0; width: 100%; height: 116%;\n  object-fit: cover; opacity: 0; z-index: 1;\n  transition: opacity 1.1s ease;\n}\n.sp-hero-video.is-ready { opacity: 1; }\n.sp-hero-veil { z-index: 2; }\n.sp-hero-inner { z-index: 3; }\n\n/* FLY-TO-CART DISC (appended to body, so styled globally not under root) */\n.sp-fly {\n  position: fixed; z-index: 200; width: 1.5rem; height: 1.5rem;\n  border-radius: 999px; pointer-events: none;\n  box-shadow: 0 6px 16px -4px rgba(20,18,30,0.5), inset 0 0 0 2px rgba(255,255,255,0.7);\n}\n.sp-cart-btn.is-bump { animation: sp-bump 0.42s cubic-bezier(0.34,1.56,0.64,1); }\n@keyframes sp-bump {\n  0% { transform: scale(1); }\n  40% { transform: scale(1.16); }\n  100% { transform: scale(1); }\n}\n\n/* FOUNDER STORY + PRESS */\n.sp-story { padding: clamp(3.5rem, 8vw, 7rem) clamp(1.25rem, 6vw, 5rem) clamp(2.5rem, 5vw, 4rem); }\n.sp-story-grid {\n  display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);\n  align-items: center; gap: clamp(1.75rem, 5vw, 4.5rem);\n}\n.sp-story-media {\n  opacity: 0; transform: translateX(-24px);\n  transition: opacity 0.8s ease, transform 0.9s cubic-bezier(0.16,1,0.3,1);\n}\n.sp-story-copy {\n  opacity: 0; transform: translateY(24px);\n  transition: opacity 0.8s ease 0.1s, transform 0.9s cubic-bezier(0.16,1,0.3,1) 0.1s;\n}\n.sp-story-grid.is-shown .sp-story-media,\n.sp-story-grid.is-shown .sp-story-copy { opacity: 1; transform: none; }\n.sp-story-img {\n  aspect-ratio: 4 / 5; border-radius: 26px; overflow: hidden;\n  background: var(--flavor-tint, #FFE7DC) center/cover no-repeat;\n  box-shadow: var(--shadow);\n}\n.sp-story-copy .sp-h2 { max-width: 20ch; }\n.sp-story-copy p { margin: 1.2rem 0 0; color: var(--ink-soft); font-size: 1.06rem; max-width: 46ch; }\n.sp-story-copy .sp-btn { margin-top: 1.8rem; }\n@media (max-width: 820px) {\n  .sp-story-grid { grid-template-columns: 1fr; }\n  .sp-story-img { max-width: 26rem; }\n}\n\n.sp-press { margin-top: clamp(3rem, 6vw, 5rem); padding-top: clamp(2rem, 4vw, 3rem); border-top: 1px solid var(--line); }\n.sp-press-label { display: block; text-align: center; margin-bottom: 1.4rem; font-size: 0.9rem; font-weight: 600; color: var(--ink-soft); }\n.sp-press-marquee { position: relative; overflow: hidden; mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent); }\n.sp-press-row { display: flex; gap: clamp(2rem, 5vw, 4rem); width: max-content; animation: sp-marquee 26s linear infinite; }\n.sp-press-name { font-size: clamp(1.15rem, 2.4vw, 1.7rem); font-weight: 620; letter-spacing: -0.02em; color: var(--ink); opacity: 0.55; white-space: nowrap; }\n@keyframes sp-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }\n\n/* REVIEWS + UGC */\n.sp-reviews { padding: clamp(3.5rem, 8vw, 7rem) clamp(1.25rem, 6vw, 5rem); background: var(--paper); border-top: 1px solid var(--line); }\n.sp-reviews-head { max-width: 42rem; margin-bottom: clamp(2rem, 4vw, 3rem); }\n.sp-reviews-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.1rem; }\n.sp-review {\n  margin: 0; padding: 1.5rem; border-radius: 20px;\n  background: var(--cream); border: 1px solid var(--line);\n  opacity: 0; transform: translateY(26px);\n  transition: opacity 0.7s ease, transform 0.8s cubic-bezier(0.16,1,0.3,1);\n}\n.sp-reviews-grid.is-shown .sp-review { opacity: 1; transform: none; }\n.sp-review .sp-stars { display: block; margin-bottom: 0.9rem; }\n.sp-review blockquote { margin: 0 0 1.2rem; font-size: 1rem; line-height: 1.55; letter-spacing: -0.01em; }\n.sp-review figcaption { display: flex; flex-direction: column; gap: 0.1rem; }\n.sp-review figcaption strong { font-weight: 680; font-size: 0.95rem; }\n.sp-review figcaption span { font-size: 0.85rem; color: var(--ink-soft); }\n@media (max-width: 980px) { .sp-reviews-grid { grid-template-columns: repeat(2, 1fr); } }\n@media (max-width: 520px) { .sp-reviews-grid { grid-template-columns: 1fr; } }\n\n.sp-ugc { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0.8rem; margin-top: clamp(2rem, 4vw, 3rem); }\n.sp-ugc-tile {\n  aspect-ratio: 1 / 1; border-radius: 16px; overflow: hidden;\n  background: var(--flavor-tint, #FCE1EC) center/cover no-repeat;\n  transition: transform 0.5s cubic-bezier(0.16,1,0.3,1);\n}\n.sp-ugc-tile:hover { transform: scale(1.04); }\n@media (max-width: 720px) { .sp-ugc { grid-template-columns: repeat(3, 1fr); } .sp-ugc-tile:nth-child(n+4) { display: none; } }\n\n/* NEWSLETTER CAPTURE */\n.sp-capture {\n  padding: clamp(3.5rem, 8vw, 7rem) clamp(1.25rem, 6vw, 5rem);\n  background:\n    radial-gradient(120% 120% at 18% 0%, rgba(229,52,109,0.14) 0%, transparent 55%),\n    radial-gradient(120% 120% at 90% 100%, rgba(242,183,5,0.16) 0%, transparent 55%),\n    var(--cream);\n}\n.sp-capture-inner { max-width: 40rem; margin: 0 auto; text-align: center; }\n.sp-capture-title { margin: 0; font-size: clamp(2.1rem, 5.5vw, 3.6rem); font-weight: 770; letter-spacing: -0.035em; line-height: 1.02; }\n.sp-capture-sub { margin: 1rem auto 2rem; max-width: 34rem; color: var(--ink-soft); font-size: clamp(1rem, 1.6vw, 1.2rem); }\n.sp-capture-form { display: flex; gap: 0.7rem; max-width: 30rem; margin: 0 auto; flex-wrap: wrap; }\n.sp-capture-input {\n  flex: 1; min-width: 12rem; font-family: inherit; font-size: 1rem;\n  padding: 1rem 1.3rem; border-radius: 999px; color: var(--ink);\n  background: var(--paper); border: 1.5px solid var(--line-2);\n  transition: border-color 0.3s, box-shadow 0.3s;\n}\n.sp-capture-input:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 4px rgba(229,52,109,0.14); }\n.sp-capture-fine { margin: 1.2rem 0 0; font-size: 0.85rem; color: var(--ink-soft); }\n\n/* FOOTER */\n.sp-footer { background: var(--ink); color: rgba(255,253,246,0.8); padding: clamp(3rem, 6vw, 5rem) clamp(1.25rem, 6vw, 5rem) 2rem; }\n.sp-footer-grid {\n  display: grid; grid-template-columns: 1.6fr 1fr 1fr 1fr; gap: clamp(1.5rem, 4vw, 3rem);\n  padding-bottom: clamp(2rem, 4vw, 3rem); border-bottom: 1px solid rgba(255,253,246,0.12);\n}\n.sp-footer-brand .sp-logo { color: var(--cream); margin-bottom: 1rem; }\n.sp-footer-brand p { margin: 0; max-width: 30ch; font-size: 0.95rem; line-height: 1.55; }\n.sp-footer-col h3 { margin: 0 0 1rem; font-size: 0.8rem; font-weight: 700; letter-spacing: 0.02em; color: var(--cream); }\n.sp-footer-col a { display: block; margin-bottom: 0.6rem; text-decoration: none; font-size: 0.95rem; color: rgba(255,253,246,0.7); transition: color 0.25s; }\n.sp-footer-col a:hover { color: var(--cream); }\n.sp-footer-base {\n  display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;\n  padding-top: 1.6rem; font-size: 0.88rem; color: rgba(255,253,246,0.6);\n}\n.sp-footer-legal { display: flex; gap: 1.4rem; }\n.sp-footer-legal a { text-decoration: none; color: rgba(255,253,246,0.6); transition: color 0.25s; }\n.sp-footer-legal a:hover { color: var(--cream); }\n@media (max-width: 820px) { .sp-footer-grid { grid-template-columns: 1fr 1fr; } }\n@media (max-width: 480px) { .sp-footer-grid { grid-template-columns: 1fr; } }\n\n/* CSS-END */\n`\n","type":"registry:page"}],"assets":[{"url":"https://ui.artbloom.tech/r/asset/templates/sprig/assets/hero.mp4","target":"public/sprig/hero.mp4","bytes":2864000},{"url":"https://ui.artbloom.tech/r/asset/templates/sprig/assets/hero.png","target":"public/sprig/hero.png","bytes":2823671},{"url":"https://ui.artbloom.tech/r/asset/templates/sprig/assets/can1.png","target":"public/sprig/can1.png","bytes":1938138},{"url":"https://ui.artbloom.tech/r/asset/templates/sprig/assets/can2.png","target":"public/sprig/can2.png","bytes":2065193},{"url":"https://ui.artbloom.tech/r/asset/templates/sprig/assets/can3.png","target":"public/sprig/can3.png","bytes":2116214},{"url":"https://ui.artbloom.tech/r/asset/templates/sprig/assets/can4.png","target":"public/sprig/can4.png","bytes":2181641},{"url":"https://ui.artbloom.tech/r/asset/templates/sprig/assets/can5.png","target":"public/sprig/can5.png","bytes":2100014},{"url":"https://ui.artbloom.tech/r/asset/templates/sprig/assets/can6.png","target":"public/sprig/can6.png","bytes":1933072}],"meta":{"kind":"templates","categories":["landing"],"docs":"https://ui.artbloom.tech/artbloom/templates/sprig"}}