/* Self-hosted, dependency-free — no CDN. Colors come from CSS custom properties set inline per
   request in lib/View.php, from the client's own branding, so this one stylesheet works for every
   storefront without ever being edited per client. */
:root{--sf-brand:#a855f7;--sf-brand-dark:#7c3aed;--sf-button:#a855f7;--sf-font:#1a1a1a;--sf-footer:#111827;--sf-footer-text:#fff;--sf-bg:#f7f7f8;--sf-card:#ffffff;--sf-border:#e6e6ea;--sf-muted:#6b7280;--sf-danger:#e5484d;--sf-radius:10px;--sf-btn-radius:8px}
*{box-sizing:border-box}
html{height:100%;scroll-behavior:smooth}
body{margin:0;min-height:100%;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;color:var(--sf-font);background:#fff;line-height:1.5}
/* Sticky footer: .sf-main grows to fill any leftover space so the footer always sits at the
   bottom of the viewport on short-content pages instead of floating right under the content. */
.sf-main{flex:1 0 auto}
.sf-footer{flex-shrink:0}
a{color:var(--sf-brand);text-decoration:none}
img{max-width:100%;display:block}
.sf-container{max-width:1500px;margin:0 auto;padding:0 1.25rem}

.sf-header{background:var(--sf-card);border-bottom:1px solid var(--sf-border);position:sticky;top:0;z-index:10}
.sf-header-static{position:static}
/* No .sf-container here on purpose — the logo/nav are meant to sit close to the true edges of the
   bar at any screen size (like a normal site nav), not inside .sf-container's 1500px cap, which on
   a wide monitor left huge symmetric gutters on both sides that a laptop viewport never showed. */
.sf-header-inner{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;gap:1rem;position:relative}
.sf-brand{font-weight:700;font-size:1.15rem;color:var(--sf-font)}
.sf-nav{display:flex;gap:1.25rem;align-items:center}
.sf-nav a{color:var(--sf-font);font-weight:500;font-size:.95rem}
.sf-nav a:hover{color:var(--sf-accent,var(--sf-brand))}
.sf-nav-toggle{display:none;background:none;border:none;padding:.35rem;margin:-.35rem;color:var(--sf-font);cursor:pointer;border-radius:6px}
.sf-nav-toggle:hover,.sf-nav-toggle-open{background:var(--sf-bg)}

/* Collapses the inline nav into a hamburger-triggered dropdown panel below the header on narrow
   viewports — .sf-header-inner needs position:relative (set above) so .sf-nav can anchor to it
   with position:absolute instead of a hardcoded top offset that would break if the header's own
   height ever changes (e.g. a taller logo). */
@media (max-width:760px){
.sf-nav-toggle{display:flex;align-items:center;justify-content:center}
.sf-nav{position:absolute;top:100%;left:0;right:0;flex-direction:column;align-items:stretch;gap:0;background:var(--sf-card);border-bottom:1px solid var(--sf-border);box-shadow:0 10px 20px rgba(0,0,0,.08);padding:.25rem 1.25rem;opacity:0;visibility:hidden;transform:translateY(-6px);transition:opacity .15s ease,transform .15s ease}
.sf-nav.sf-nav-open{opacity:1;visibility:visible;transform:translateY(0)}
.sf-nav a{padding:.85rem 0;border-bottom:1px solid var(--sf-border)}
.sf-nav a:last-child{border-bottom:none}
/* Get Started sits inline with the hamburger toggle at this width now (see View.php — it's a
   .sf-header-actions sibling, not nested in the collapsible .sf-nav dropdown), so it needs to be
   compact rather than the full-size desktop button to leave the toggle room. */
.sf-header-cta{padding:.5rem .8rem;font-size:.88rem}
}

/* Hero — same 120deg dark→light brand gradient, copy and CTA as the central landing page. A client
   can swap the gradient for their own image (.sf-hero-has-image, set only when one's uploaded) —
   background-size:cover means any aspect ratio they upload renders acceptably without needing to be
   pixel-perfect; the crop tool in the branding form is just a framing convenience on top of that. */
/* display:flex + align-items:center is harmless when no inline height is set (a client who's never
   touched the height slider) — with no fixed height to center within, this just behaves like the
   old block layout, sized purely by .sf-hero-inner's own padding. Once index.php sets an explicit
   height (0-100 mapped to 0-60vh), this is what actually centers the content within that taller box
   instead of it sitting pinned to the top. */
.sf-hero{position:relative;background:linear-gradient(120deg,var(--sf-brand-dark),var(--sf-brand));color:#fff;display:flex;align-items:center}
/* The gradient is kept as a second background-image layer, not dropped — if --sf-hero-img ever
   points at a URL that fails to load (a transient network hiccup, a host blocking outbound
   requests, anything), CSS just treats that one layer as empty and the gradient underneath still
   paints, instead of the whole hero going blank with white text stranded on a white page. */
.sf-hero-has-image{background-image:var(--sf-hero-img),linear-gradient(120deg,var(--sf-brand-dark),var(--sf-brand));background-size:cover;background-position:center}
.sf-hero-overlay{position:absolute;inset:0}
/* flex:0 0 auto + an explicit width (not just max-width) gives this a definite size flexbox can rely
   on, so the two .sf-hero-spacer siblings (see index.php) only ever divide up whatever's left over —
   margin used to be auto/auto for centering, but flexbox resolves auto-margins on an item BEFORE
   handing any remaining space to growable siblings, which would swallow all of it and leave both
   spacers stuck at their 10px floor regardless of the text-position slider. At the default 50/50
   spacer split this still renders exactly centered, so nothing changes for a client who's never
   touched that slider. */
.sf-hero-inner{position:relative;z-index:1;flex:0 0 auto;width:900px;max-width:calc(100% - 20px);padding-top:5rem;padding-bottom:5rem;margin-left:0;margin-right:0}
/* Divides whatever horizontal space .sf-hero-inner doesn't itself claim, in the ratio index.php sets
   via inline flex-grow (0-100 / 100-0) — min-width is the 10px floor that keeps the text block at
   least that far from either edge no matter how far the slider's pushed. */
.sf-hero-spacer{flex-shrink:0;min-width:10px}
.sf-hero h1{margin:0 0 .75rem;font-size:2.6rem;font-weight:800;line-height:1.15;letter-spacing:-.02em}
.sf-hero p{margin:0 0 1.5rem;opacity:.92;font-size:1.15rem;max-width:640px}
/* This is the hero's own CTA specifically (its only use in this package) — sitting over a colored
   gradient or, now, a client's own photo, so its text needs to stay reliably white rather than
   var(--sf-font) (a client's general body-text color, often dark — easy to lose against a dark
   image/overlay). The .sf-btn base rule further down happens to already set color:#fff for the same
   element, but that's incidental to base .sf-btn styling, not a guarantee for this specific button —
   pin it here explicitly instead of relying on cascade order between the two. */
.sf-btn-light{background:#fff;color:#fff}
.sf-btn-light:hover{color:#fff}
/* .sf-hero-spacer hidden — the text-position slider is desktop-only by design (explicitly requested
   not to affect mobile); with both spacers gone .sf-hero-inner is this row's only flex item again and
   just takes the full width, same as it always has on mobile. */
@media (max-width:760px){.sf-hero h1{font-size:1.9rem}.sf-hero-inner{padding-top:3rem;padding-bottom:3rem;width:auto}.sf-hero-spacer{display:none}}
/* Mobile hero image only takes over once the nav's own breakpoint kicks in — matches the header's
   existing 760px collapse point, not a separate one, so there's no in-between width using neither. */
@media (max-width:760px){.sf-hero-has-image{background-image:var(--sf-hero-img-mobile),linear-gradient(120deg,var(--sf-brand-dark),var(--sf-brand))}}

/* Feature trio — mirrors the landing page's "Clinician guided / Convenient care / Clear pricing" band. */
.sf-promo-banner{text-align:center;padding:.7rem 1rem;position:sticky;top:var(--sf-header-height,64px);z-index:9}
/* A builder-placed Promo Banner isn't necessarily right under the header (unlike the storefront's
   own hardcoded one, always the first thing on the homepage) — sticking it to --sf-header-height
   from wherever it happens to sit further down a page would look wrong, so this instance is static. */
.sf-b-promo-banner{position:static}
/* Marquee builder block — a repeated item list slides from 0 to one-set-width so the loop point is
   invisible (every repeated set is identical); overflow:hidden on the outer element (from its own
   Border/Size fields via styleFromGroups, or the browser default of visible clipped by
   width+no-wrap here) keeps only one row visible at a time. The baked HTML always ships exactly 2
   copies of the item set (see components.jsx's Marquee.render) — app.js clones extra copies at
   runtime and sets --sf-marquee-dist whenever 2 copies aren't wide enough to fill the container
   (a short item list on a wide section), which is also why the fallback below assumes 2 sets.
   Disabled entirely under prefers-reduced-motion. */
.sf-b-marquee{overflow:hidden;width:100%}
.sf-b-marquee-track{display:inline-flex;align-items:center;gap:2.5rem;white-space:nowrap;animation:sf-marquee-scroll linear infinite;padding-right:2.5rem}
.sf-b-marquee-item{display:inline-flex;align-items:center;gap:.5rem;white-space:nowrap}
.sf-b-marquee-icon{font-size:1.15em;line-height:1}
@keyframes sf-marquee-scroll{from{transform:translateX(0)}to{transform:translateX(calc(-1 * var(--sf-marquee-dist, 50%)))}}
@media (prefers-reduced-motion:reduce){.sf-b-marquee-track{animation:none}}
.sf-features{display:grid;grid-template-columns:repeat(3,1fr);gap:1.5rem;text-align:center;padding-top:1rem;padding-bottom:1rem}
.sf-features h2{margin:0 0 .35rem;font-size:1.1rem;font-weight:700}
.sf-features p{margin:0;color:var(--sf-muted)}
@media (max-width:760px){.sf-features{grid-template-columns:1fr}}

.sf-products{background:var(--sf-bg);padding:3rem 0}
/* Sticks the same way the category sidebar below already does (same --sf-sticky-offset, right under
   the header/promo banner) — the 20px top margin is desktop-only per spec, so mobile keeps its
   original flush spacing instead of extra gap above an already-cramped small screen. */
.sf-product-search{position:sticky;top:var(--sf-sticky-offset,84px);z-index:5;background:var(--sf-bg);padding:.75rem 0}
@media (min-width:761px){.sf-product-search{margin-top:20px}}
.sf-product-search-inner{position:relative;max-width:420px;margin:0 auto}
/* Shifts the search box right by half the category sidebar's own width (130px = 260px / 2) so it
   stays centered over the product grid instead of the full page — without this, centering against
   the whole .sf-container (which the sidebar eats into) looks visibly off relative to the grid
   below it. Only applies once the sidebar is actually showing beside the grid (desktop widths;
   below 761px it stacks above instead, where full-page centering is correct again — see the
   max-width:760px override further down). */
@media (min-width:761px){.sf-product-search--with-sidebar .sf-product-search-inner{margin-left:calc((100% - 420px) / 2 + 130px);margin-right:auto}}
.sf-product-search-inner svg{position:absolute;left:.9rem;top:50%;transform:translateY(-50%);color:var(--sf-muted);pointer-events:none}
.sf-product-search-inner input{width:100%;padding:.65rem 1rem .65rem 2.6rem;border:1px solid var(--sf-border);border-radius:999px;background:var(--sf-card);color:var(--sf-font);font:inherit;font-size:.95rem}
.sf-product-search-inner input:focus{outline:none;border-color:var(--sf-brand)}
.sf-products-intro{text-align:center;margin-bottom:2rem;scroll-margin-top:var(--sf-sticky-offset,84px)}
.sf-products-intro:not(:first-child){margin-top:3rem}
.sf-products-intro h2{margin:0 0 .35rem;font-size:1.9rem;font-weight:700}
.sf-products-intro p{margin:0;color:var(--sf-muted)}

/* Category sidebar — sits directly beside the product grid (deliberately outside .sf-container,
   which would otherwise leave empty space between the sidebar and the grid on wide screens),
   sticking just below the storefront's own sticky header (and promo banner, when one's enabled —
   see --sf-sticky-offset in View.php) once scrolled past its normal position, so it stays reachable
   no matter how far down a long product list the customer has scrolled. max-width+margin:auto here
   caps and centers the whole sidebar+grid row itself (260px sidebar + .sf-shop-categories' own
   1300px cap below) — without it, on a monitor much wider than that the row still anchored to the
   true left edge, dumping all the leftover space as dead air on the right instead of splitting it
   evenly like every other section of the page. */
.sf-shop-layout{display:flex;align-items:flex-start;max-width:1560px;margin:0 auto}
/* --sf-cat-sticky-offset (set in View.php, alongside --sf-sticky-offset) is --sf-sticky-offset PLUS
   the search bar's own rendered height — the search bar above is sticky at the plain offset, so the
   sidebar needs the extra room or the two would both land on the same fixed position once scrolled
   and the sidebar (later in the DOM, so painted on top) would cover the search bar entirely. */
.sf-cat-sidebar{flex:0 0 260px;position:sticky;top:var(--sf-cat-sticky-offset,var(--sf-sticky-offset,84px));max-height:calc(100vh - var(--sf-cat-sticky-offset,var(--sf-sticky-offset,84px)) - 20px);overflow-y:auto;padding:0 1rem 2rem 1.5rem}
.sf-cat-sidebar-heading{margin:0 0 .75rem;padding:0 .75rem;font-size:.78rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--sf-muted)}
.sf-cat-sidebar nav{display:flex;flex-direction:column;gap:.15rem}
.sf-cat-sidebar a{display:flex;align-items:center;justify-content:space-between;gap:.5rem;padding:.55rem .75rem;border-radius:8px;color:var(--sf-font);font-weight:600;font-size:.92rem}
.sf-cat-sidebar a:hover{background:var(--sf-card);color:var(--sf-brand)}
.sf-cat-count{color:var(--sf-muted);font-weight:600;font-size:.8rem;background:var(--sf-card);border-radius:999px;padding:.1rem .55rem}
.sf-shop-categories{flex:1;min-width:0;max-width:1300px;padding:0 1.25rem}
@media (min-width:900px){.sf-shop-categories .sf-grid{grid-template-columns:repeat(3,1fr)}}
/* A dropdown reads far better than a wrapped row of pill buttons once the sidebar drops below the
   product grid on narrow screens — a custom button+list (not a native <select>) so it stays fully
   styleable; an open native picker is positioned entirely by the OS/browser with no CSS control at
   all over where it lands. */
.sf-cat-toggle{display:none}
/* display:block (not flex-direction:column) deliberately — .sf-shop-layout's own align-items:
   flex-start above governs the vertical axis in the default row layout, but the same property
   would instead govern the HORIZONTAL axis once switched to a column flex direction, shrinking
   each child to its own content width instead of stretching full-width — which is exactly what
   was leaving the product grid narrower than the viewport with dead space beside it. Falling back
   to plain block stacking sidesteps that flex cross-axis behavior altogether. */
@media (max-width:760px){
.sf-product-search{position:static;margin-top:0}
.sf-product-search-inner{max-width:none}
.sf-shop-layout{display:block}
.sf-cat-sidebar{position:static;max-height:none;width:100%;padding:0 1.25rem;margin-bottom:1rem}
.sf-cat-toggle{display:flex;align-items:center;justify-content:space-between;gap:.5rem;width:100%;padding:.65rem .9rem;border:1px solid var(--sf-border);border-radius:8px;background:var(--sf-card);color:var(--sf-font);font:inherit;font-size:.95rem;cursor:pointer}
.sf-cat-chev{flex-shrink:0;transition:transform .15s ease}
.sf-cat-sidebar.sf-cat-open .sf-cat-chev{transform:rotate(180deg)}
.sf-cat-sidebar nav{display:none;flex-direction:column;gap:0;margin-top:.4rem;background:var(--sf-card);border:1px solid var(--sf-border);border-radius:8px;padding:.3rem}
.sf-cat-sidebar.sf-cat-open nav{display:flex}
.sf-shop-categories{padding:0 1.25rem}
}

/* auto-fit (not auto-fill) is what matters here — auto-fill reserves a track for every column that
   could theoretically fit the container width, even an empty one with nothing in it, which left a
   lone product card stranded on the left with dead space beside it instead of stretching to fill
   the row. auto-fit collapses those empty tracks so real items expand into the space instead. */
.sf-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1.5rem;margin-bottom:1rem}
/* Website builder: same .sf-card markup as .sf-grid above, just a horizontally-scrolling row
   instead of a wrapping grid — each card gets a fixed width so it doesn't stretch full-width. */
.sf-products-carousel{display:flex;gap:1.5rem;overflow-x:auto;margin-bottom:1rem;scroll-snap-type:x mandatory;justify-content:center;scroll-behavior:smooth;scrollbar-width:none;-ms-overflow-style:none}
.sf-products-carousel::-webkit-scrollbar{display:none}
.sf-products-carousel .sf-card{flex:0 0 300px;scroll-snap-align:start}
/* Prev/next arrows (see assets/app.js) — only shown once JS confirms the row actually overflows
   its container; a short row stays a plain block, centered by .sf-products-carousel's own
   justify-content:center, with no arrows at all. Once overflowing, the wrap becomes a real flex
   row (arrow | viewport | arrow) — flexbox centers the group itself, which is what guarantees the
   two arrows sit at equal distance from each edge; absolute-positioned arrows over hand-measured
   padding previously got this slightly wrong on some widths. */
.sf-carousel-wrap.sf-carousel-overflow{display:flex;align-items:center;justify-content:center;gap:.6rem}
.sf-carousel-wrap.sf-carousel-overflow .sf-products-carousel{justify-content:flex-start}
/* Clipped to an exact whole-card width by JS (see app.js) so the row always cuts off cleanly on a
   card boundary — never a sliver of the next card peeking in at the edge. flex:0 0 auto so flexbox
   never stretches/shrinks it off that exact width once JS has set it. */
.sf-carousel-viewport{overflow:hidden}
.sf-carousel-wrap.sf-carousel-overflow .sf-carousel-viewport{flex:0 0 auto}
.sf-carousel-arrow{flex:0 0 auto;width:40px;height:40px;border-radius:50%;border:1px solid var(--sf-border);background:var(--sf-card);color:var(--sf-font);font-size:1.4rem;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 10px rgba(0,0,0,.12)}
.sf-carousel-arrow:hover{background:var(--sf-bg)}
@media (max-width:760px){.sf-carousel-arrow{width:34px;height:34px;font-size:1.2rem}}
.sf-category-grid{display:grid;grid-template-columns:var(--sf-grid-columns,repeat(auto-fit,minmax(200px,1fr)));gap:1rem;margin-bottom:1rem}
.sf-b-search{margin-bottom:1.5rem}
.sf-b-search .sf-product-search-inner{margin-bottom:1.5rem}
/* All Products builder block — .sf-product-search's own background:var(--sf-bg) (a light grey,
   #f7f7f8) is right for the classic storefront homepage it was originally built for (whose page
   background IS that color), but this block can land inside any section — including one with its
   own dark/colored background (Section's own Background field) — where that hardcoded grey shows
   up as an obvious mismatched box instead of blending in. Also drops the sticky positioning: it's
   fine anchored to the classic homepage's own top offset, but has no correct offset to anchor to
   inside an arbitrary builder page. */
.sf-b-all-products .sf-product-search{background:transparent;position:static;margin-top:0;padding:0 0 1.25rem}
/* Website builder Animations group (Entrance/Exit/Duration/Delay/Hover — see builder-src/src/
   components.jsx's animationFields()) — see assets/app.js's own IntersectionObserver, which adds/
   removes .sf-anim-in/.sf-anim-out and sets each element's own transition-duration/-delay from its
   data-sf-duration/-delay attributes. transition-property is fixed here (opacity+transform); only
   the per-element TIMING is configurable, not which properties animate. */
[data-sf-enter],[data-sf-exit]{opacity:0;transition-property:opacity,transform;transition-timing-function:ease}
.sf-enter-fade-up{transform:translateY(30px)}
.sf-enter-fade-down{transform:translateY(-30px)}
.sf-enter-slide-left{transform:translateX(-50px)}
.sf-enter-slide-right{transform:translateX(50px)}
.sf-enter-zoom-in{transform:scale(.85)}
.sf-enter-bounce-in{transform:scale(.3)}
/* transform:none!important resets an entrance's own starting transform (scale/translate) once it's
   done — but !important always wins over a running CSS animation for that same property, no matter
   how specific the animation's own selector is, so this was silently freezing a loop animation
   (also driven by transform) solid on any element that had BOTH an entrance and a loop set. The
   opacity reset still applies to every element either way; the transform reset is skipped for one
   that's also looping, since its own keyframes need to keep driving transform instead. */
.sf-anim-in{opacity:1!important}
.sf-anim-in:not(.sf-loop-float):not(.sf-loop-grow):not(.sf-loop-pulse){transform:none!important}
.sf-anim-out{opacity:0!important}
.sf-exit-zoom-out.sf-anim-out{transform:scale(.85)!important}
.sf-exit-slide-out-left.sf-anim-out{transform:translateX(-50px)!important}
.sf-exit-slide-out-right.sf-anim-out{transform:translateX(50px)!important}
/* Hover effects — a plain CSS class, not tied to entrance/exit at all, so they work even on an
   element with no scroll animation set. */
.sf-hover-grow:hover{transform:scale(1.05);transition:transform .2s ease}
.sf-hover-shrink:hover{transform:scale(.95);transition:transform .2s ease}
.sf-hover-float:hover{transform:translateY(-6px);transition:transform .2s ease}
.sf-hover-pulse:hover{animation:sfHoverPulse .6s ease infinite}
@keyframes sfHoverPulse{0%,100%{transform:scale(1)}50%{transform:scale(1.05)}}
/* Loop animations — unlike entrance/exit (scroll-triggered, needs the IntersectionObserver in
   app.js) and hover (only while the pointer is over it), these run continuously from the moment the
   element is on the page, so they're plain always-on CSS classes with no JS involved at all. */
.sf-loop-float{animation:sfLoopFloat 3s ease-in-out infinite}
.sf-loop-grow{animation:sfLoopGrow 2s ease-in-out infinite}
.sf-loop-pulse{animation:sfLoopPulse 1.4s ease-in-out infinite}
@keyframes sfLoopFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-12px)}}
@keyframes sfLoopGrow{0%,100%{transform:scale(1)}50%{transform:scale(1.08)}}
@keyframes sfLoopPulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.04);opacity:.85}}
@media (prefers-reduced-motion:reduce){[data-sf-enter],[data-sf-exit]{opacity:1;transform:none;transition:none}.sf-hover-grow:hover,.sf-hover-shrink:hover,.sf-hover-float:hover{transform:none}.sf-hover-pulse:hover{animation:none}.sf-loop-float,.sf-loop-grow,.sf-loop-pulse{animation:none}}
.sf-category-tile{display:block;padding:1.25rem;background:var(--sf-card);border-radius:var(--sf-radius);box-shadow:0 2px 12px rgba(0,0,0,.06);text-decoration:none;color:var(--sf-font);transition:box-shadow .15s,transform .15s}
.sf-category-tile:hover{box-shadow:0 8px 24px rgba(0,0,0,.1);transform:translateY(-2px)}
.sf-category-tile-name{font-weight:700}
.sf-category-tile-count{color:var(--sf-muted);font-size:.85rem;margin-top:.25rem}
.sf-card{background:var(--sf-card);color:var(--sf-font);border:none;border-radius:var(--sf-radius);overflow:hidden;position:relative;box-shadow:0 2px 12px rgba(0,0,0,.06);transition:box-shadow .15s,transform .15s;display:flex;flex-direction:column}
.sf-card:hover{box-shadow:0 8px 24px rgba(0,0,0,.1);transform:translateY(-2px)}
/* Deliberately NOT .sf-card's own background/color (var(--sf-card) is a fixed #ffffff with no
   dark-theme variant anywhere in this stylesheet, and var(--sf-font) a fixed dark grey) — a category
   card sits directly on whatever background/text-color the page around it already has (a plain
   Section/Container with its own background, light or dark), the same way Heading/Text/every other
   element here does by default. Forcing a hardcoded white box behind it is exactly the "why does it
   have a white background I never added" bug this card style went through several rounds chasing
   symptoms of (letterboxed images, an empty text block still taking up space, a radius fight with
   .sf-card's own) before landing on the actual root cause: the background itself was never wanted at
   all, only the hover-lift motion was. transparent/inherit here means a client who DOES want a
   visible card background can still get one from the Category Grid's own Background field group
   (styleFromGroups(props), applied to the outer grid — or, once cards can be styled individually,
   from there) without this element forcing one whether they asked for it or not. Also drops the base
   (non-hover) box-shadow, which read as its own faint "boxed" edge around an otherwise-transparent
   card — the hover-only shadow is kept, since that's the actual "lift" effect being reused here. */
.sf-b-catgrid-card{background:transparent;color:inherit;border:none;transition:box-shadow .15s,transform .15s;display:flex;flex-direction:column;text-decoration:none}
.sf-b-catgrid-card:hover{box-shadow:0 8px 24px rgba(0,0,0,.1);transform:translateY(-2px)}
/* Bottle shots are contained (not cropped) on white, exactly like the landing page's cards. */
.sf-card img,.sf-card-noimg{width:100%;height:220px;object-fit:contain;padding:1rem;background:#fff;display:block;box-sizing:border-box}
.sf-card-img-btn{display:block;width:100%;padding:0;margin:0;border:none;background:none;cursor:pointer;text-align:inherit;font:inherit;color:inherit}
.sf-card-body{padding:0 1.25rem 1.25rem;display:flex;flex-direction:column;flex:1}
.sf-card-body h3{margin:0 0 .35rem;font-size:1.25rem;font-weight:700;text-align:center}
.sf-card-desc{color:var(--sf-muted);font-size:.92rem;margin:0 0 .5rem}
.sf-price-block{margin:auto 0 1rem;text-align:center}
.sf-price-block small{display:block;text-transform:uppercase;color:var(--sf-muted);font-size:.72rem;letter-spacing:.04em}
.sf-price-block strong{font-size:1.65rem;font-weight:700}
.sf-price-suffix{color:var(--sf-muted)}
.sf-price-original{display:inline-block;color:var(--sf-muted);text-decoration:line-through;font-size:.85em;font-weight:500;margin-right:.4em}
.sf-card .sf-btn,.sf-card form{margin-top:0}
.sf-price-label{display:block;text-transform:uppercase;font-size:.7rem;letter-spacing:.03em;font-weight:600;color:var(--sf-muted);margin-bottom:.15rem}
.sf-badge{position:absolute;top:.65rem;left:.65rem;background:var(--sf-danger);color:#fff;font-weight:700;font-size:.7rem;padding:.28rem .6rem;border-radius:999px;z-index:2;letter-spacing:.02em}
.sf-empty{color:var(--sf-muted);padding:3rem 0;text-align:center}
.w-100{width:100%}

.sf-back{display:block;max-width:1500px;margin:1.25rem auto 0;padding:0 1.25rem;color:var(--sf-muted);font-size:.9rem}
.sf-back:hover{color:var(--sf-brand)}

/* align-items:start — grid columns default to stretch, which grew the image box to match the
   right column's height once the dose grid rendered (more variations = a taller, mostly-empty
   white box on the left). The image now just sizes to its own content, same as the text column. */
.sf-product{display:grid;grid-template-columns:1fr 1fr;gap:2.5rem;padding:1.5rem 1.25rem 2.5rem;align-items:start}
.sf-product-media{position:relative;background:var(--sf-card);border:1px solid var(--sf-border);border-radius:var(--sf-radius);overflow:hidden;display:flex;align-items:center;justify-content:center}
.sf-product-media img,.sf-product-noimg{width:100%;border-radius:0;height:360px;object-fit:contain;background:var(--sf-card)}
.sf-product-info h1{margin-top:0;font-size:1.6rem}
.sf-product-info p{color:var(--sf-muted)}
.sf-product-desc{color:var(--sf-font)}
.sf-price{font-size:1.3rem;font-weight:700;margin:1rem 0}
/* The dose-picker section (badges/purchase-option/state field/results) used to sit inside the old
   rxModal()'s own .sf-modal-footer (margin-top:1.25rem) and have its "Change state" link styled like
   #sf-payment-step's own .sf-btn-link — same two rules, just reused here now that it lives directly
   on the product page instead of inside that modal. The margin-top moved from #sf-pkg-continue-btn
   itself onto .sf-pkg-actions (see that rule, further down) once "Add to cart" started sharing the
   same flex row: a margin on only one of the two buttons pushed "Buy now" down inside that row
   without moving "Add to cart", misaligning the pair even after both buttons' own padding/border
   were made identical — same visible "still looks off" symptom, different (and more visible) cause.
   Putting the spacing on the row's own container keeps the gap above the actions as a whole instead
   of inside it. */
/* :not([hidden]) — without it this always-display:block rule outranks the [hidden] attribute
   product-page.js sets for MyOrbit (backBtn.hidden = true, since MyOrbit has no state step to go
   back to), so "← Change state" kept showing on a MyOrbit product no matter what JS did. */
#sf-page-pkg #sf-pkg-back-btn:not([hidden]){display:block;margin-top:.85rem}

.sf-modal-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:100;padding:1rem}
.sf-modal-backdrop[hidden]{display:none}
/* Header/footer stay put, only .sf-modal-body scrolls — the dose-picker step (many option tiles)
   used to grow the whole modal, pushing "Continue to checkout" out of view until you scrolled all
   the way down. display:flex + overflow:hidden here, flex:1/overflow-y:auto on the body below, is
   what keeps the CTA reachable without scrolling regardless of how many options there are. */
.sf-modal{background:#fff;color:var(--sf-font);border-radius:var(--sf-radius);max-width:500px;width:100%;max-height:85vh;padding:1.5rem;display:flex;flex-direction:column;overflow:hidden}
@media (min-width:992px){.sf-modal{max-width:800px}}
.sf-modal-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.1rem;flex-shrink:0}
.sf-modal-header h2{margin:0;font-size:1.15rem}
.sf-modal-close{background:none;border:none;font-size:1.6rem;line-height:1;cursor:pointer;color:var(--sf-muted);padding:0}
.sf-modal-close:hover{color:var(--sf-font)}
.sf-modal-body{overflow-y:auto;min-height:0;flex:1 1 auto}
.sf-modal-footer{margin-top:1.25rem;flex-shrink:0}

/* .sf-form label{display:block} (below) would otherwise outrank the [hidden] attribute's own default
   display:none — needed since this label is still toggled by id, not removed from the DOM. */
#sf-pkg-mode-row[hidden]{display:none}
/* Same gotcha — stays hidden whenever the cart already has a sync consult locked in (see
   product-page.js's cartHasSync), in favor of #sf-pkg-consult-locked's plain note instead. */
#sf-pkg-consult-row[hidden]{display:none}
/* Same gotcha, same fix — the MyOrbit dose-picker hides this label (see assets/product-page.js's
   isMyOrbit branch) instead of the whole state-picking step, so it needs its own targeted override too. */
#sf-pkg-state-label[hidden]{display:none}

.pkg-badges{display:flex;gap:.5rem;margin-bottom:1.25rem;padding-bottom:1.25rem;border-bottom:1px solid var(--sf-border)}
.pkg-badge{flex:1;min-width:0;display:flex;align-items:center;gap:.6rem;padding:0 .6rem}
.pkg-badge:not(:last-child){border-right:1px solid var(--sf-border)}
.pkg-badge-icon{flex:none;width:44px;height:44px;border-radius:50%;background:color-mix(in srgb, var(--sf-brand) 15%, #ffffff);color:var(--sf-brand);display:flex;align-items:center;justify-content:center;font-size:1.1rem}
.pkg-badge-icon svg,.pkg-badge-icon i{color:var(--sf-brand)}
.pkg-badge-text{font-weight:700;color:#000;font-size:.82rem;line-height:1.25}
@media (max-width:600px){.pkg-badges{flex-direction:column;gap:.75rem}.pkg-badge{padding:0}.pkg-badge:not(:last-child){border-right:none;border-bottom:1px solid var(--sf-border);padding-bottom:.75rem}}

/* Landing page "What's Included" section — a pale tint of the client's own primary colour (not
   the hero's solid gradient), holding a white card of icon/title/subtext items divided by thin
   vertical rules, circular colored icon badges rather than the popup's bordered squares. */
.sf-included{background:color-mix(in srgb, var(--sf-brand) 8%, #ffffff);border-radius:20px;padding:2.5rem 2rem;margin:1rem 0 3rem}
.sf-included-title{display:flex;align-items:center;justify-content:center;gap:1.25rem;margin:0 0 1.75rem;font-size:1.4rem;font-weight:800;color:var(--sf-font)}
.sf-included-title::before,.sf-included-title::after{content:'';flex:1;max-width:110px;height:1px;background:color-mix(in srgb, var(--sf-brand) 35%, transparent)}
.sf-included-items{background:#fff;border-radius:16px;display:flex;padding:1.75rem 1.25rem;box-shadow:0 2px 16px rgba(0,0,0,.05)}
.sf-included-item{flex:1;display:flex;align-items:center;gap:1rem;padding:0 1.25rem}
.sf-included-item:not(:last-child){border-right:1px solid var(--sf-border)}
.sf-included-icon{flex:none;width:56px;height:56px;border-radius:50%;background:var(--sf-brand);color:#fff;display:flex;align-items:center;justify-content:center;font-size:1.4rem}
.sf-included-item-title{font-weight:800;color:var(--sf-font);font-size:1rem;margin-bottom:.15rem}
.sf-included-item-sub{margin:0;color:var(--sf-muted);font-size:.85rem;line-height:1.4}
@media (max-width:760px){.sf-included{padding:1.75rem 1rem}.sf-included-items{flex-direction:column;gap:1.25rem;padding:1.5rem 1.25rem}.sf-included-item{padding:0 0 1.25rem;border-right:none!important;border-bottom:1px solid var(--sf-border)}.sf-included-item:last-child{padding-bottom:0;border-bottom:none}.sf-included-title{gap:.65rem}.sf-included-title::before,.sf-included-title::after{max-width:40px}}

.sf-pkg-error{color:var(--sf-danger);font-size:.85rem;margin:.4rem 0}

.dose-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.6rem}
@media (max-width:576px){.dose-grid{grid-template-columns:repeat(2,1fr)}}
.dose-tile{border:2px solid var(--sf-border);border-radius:14px;padding:.9rem .5rem;text-align:center;cursor:pointer;background:color-mix(in srgb,var(--sf-brand) 9%,#fff);transition:border-color .15s ease,background .15s ease,color .15s ease}
.dose-tile:hover{border-color:var(--sf-brand)}
.dose-tile.selected{background:var(--sf-brand);border-color:var(--sf-brand);color:#fff}
.dose-tile.selected .dose-tile-sub{color:rgba(255,255,255,.85)}
.dose-tile.selected .dose-tile-price-original{color:rgba(255,255,255,.75)}
.dose-tile-icon-wrap{width:46px;height:46px;border-radius:12px;display:flex;align-items:center;justify-content:center;margin:0 auto .4rem;color:var(--sf-brand)}
.dose-tile.selected .dose-tile-icon-wrap{color:#fff}
.dose-tile-decrease{background:color-mix(in srgb,#e8930f 14%,#fff);border-color:color-mix(in srgb,#e8930f 30%,var(--sf-border))}
.dose-tile-decrease .dose-tile-icon-wrap{color:#b4660a}
.dose-tile-increase{background:color-mix(in srgb,#2f9e52 14%,#fff);border-color:color-mix(in srgb,#2f9e52 30%,var(--sf-border))}
.dose-tile-increase .dose-tile-icon-wrap{color:#237a3f}
.dose-tile-icon{margin-bottom:0}
.dose-tile-title{font-weight:700;font-size:.92rem;line-height:1.25}
.dose-tile-sub{font-size:.76rem;color:var(--sf-muted);margin-top:.1rem}
.dose-tile-price{font-weight:700;margin-top:.35rem;font-size:.88rem}
.dose-tile-price-original{display:inline-block;color:var(--sf-muted);text-decoration:line-through;font-weight:500;font-size:.85em;margin-right:.35em}

.sf-quiz-heading{margin:0 0 .9rem;font-size:1.05rem}
.sf-quiz-choices{display:flex;flex-wrap:wrap;gap:.6rem}
.sf-quiz-choices .sf-btn{flex:1 1 120px}
.sf-quiz-notice{display:flex;gap:.5rem;background:#fff8e6;border:1px solid #f3dca1;color:#7a5b06;border-radius:10px;padding:.75rem .9rem;font-size:.85rem;line-height:1.4;margin-top:.75rem}
#sf-quiz-q2,#sf-quiz-q3{margin-top:0}
#sf-quiz-q2 .sf-btn-link,#sf-quiz-q3 .sf-btn-link{display:inline-block;margin-top:.75rem}
.sf-quiz-loading-card{text-align:center;max-width:320px;padding:2.25rem 1.75rem}
.sf-quiz-loading-text{margin:0;font-weight:600;color:var(--sf-font)}

/* border:1.5px solid transparent, not none — .sf-btn-outline adds a real 1.5px border, and with no
   explicit height set anywhere, box-sizing:border-box only redistributes an element's EXPLICITLY set
   width/height between content/padding/border; on an auto-sized button a real border still adds its
   own width on top of padding regardless. A solid .sf-btn-lg button (no border) and an outline one
   sitting right next to it (1.5px border) would render ~3px apart in height even with identical
   padding/font-size unless every .sf-btn reserves the same border-box space up front, transparent or
   not — this is what still left "Add to cart" a few px shorter than "Buy now" after the .sf-btn-outline
   dedup above already fixed the much larger padding/font-size gap. */
.sf-btn{display:inline-block;background:var(--sf-button);color:#fff;border:1.5px solid transparent;border-radius:var(--sf-btn-radius);padding:.6rem 1.1rem;font-weight:600;cursor:pointer;font-size:.95rem;white-space:nowrap;text-align:center}
.sf-btn[hidden]{display:none}
.sf-btn:hover{filter:brightness(.92)}
.sf-btn-lg{padding:.85rem 1.4rem;font-size:1rem}
.sf-btn-link{background:none;border:none;color:var(--sf-brand);font-size:.85rem;cursor:pointer;padding:0}

.sf-cookie-banner{position:fixed;left:0;right:0;bottom:0;z-index:200;background:var(--sf-footer);color:#fff;padding:1rem;display:flex;gap:1rem;flex-wrap:wrap;align-items:center;justify-content:space-between;box-shadow:0 -4px 16px rgba(0,0,0,.15)}
.sf-cookie-banner[hidden]{display:none}
.sf-cookie-banner p{margin:0;font-size:.9rem;color:#fff}
.sf-cookie-banner-actions{display:flex;gap:.6rem;flex-shrink:0}
.sf-cookie-banner .sf-btn-outline{border-color:rgba(255,255,255,.4);color:#fff}

/* z-index above every other overlay (.sf-select-panel's 9999 included) — this one must sit on top
   of literally everything else since it's meant to block the page, not share it. */
.sf-age-gate{position:fixed;inset:0;z-index:10000;background:rgba(0,0,0,.8);display:flex;align-items:center;justify-content:center;padding:1rem}
.sf-age-gate[hidden]{display:none}
.sf-age-gate-panel{border-radius:var(--sf-radius);max-width:420px;width:100%;padding:2rem;text-align:center;box-shadow:0 1.5rem 3rem rgba(0,0,0,.3)}
.sf-age-gate-panel h2{margin:0 0 .6rem;font-size:1.25rem}
.sf-age-gate-panel p{margin:.6rem 0 0;font-size:.95rem;line-height:1.5}
.sf-age-gate-panel p:first-of-type{margin-top:0}
.sf-age-gate-logo{display:block;max-height:80px;max-width:70%;margin:0 auto 1.25rem}
.sf-age-gate-actions{display:flex;gap:.75rem;justify-content:center;margin-top:1.5rem;flex-wrap:wrap}

.sf-muted{color:var(--sf-muted)}

.sf-form label{display:block;font-weight:600;font-size:.88rem;margin-bottom:1rem}
.sf-required{color:var(--sf-danger);font-weight:700}
.sf-form input,.sf-form select,.sf-form textarea{display:block;width:100%;margin-top:.35rem;padding:.6rem;border:1px solid var(--sf-border);border-radius:8px;font-size:.95rem;font-family:inherit}
.sf-form textarea{resize:vertical}
/* A plain <select> never had any author styling for its own dropdown arrow — no appearance:none, no
   background-image chevron, nothing — so every browser drew its own native control icon flush to the
   control's own edge instead of inset to match this rule's border-radius/border, visibly sitting
   outside the rounded border on some browsers (Safari/WebKit especially). appearance:none hands
   drawing the icon entirely to this one SVG chevron, with padding-right reserving room so it's
   clearly inside the padding-box instead of wherever the native control decided to put it. */
.sf-form select{appearance:none;-webkit-appearance:none;-moz-appearance:none;padding-right:2.25rem;background-repeat:no-repeat;background-position:right .75rem center;background-size:14px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E")}
/* ---- searchable state dropdown — a custom-styled, scrollable panel instead of a native <datalist>
   (whose suggestion UI is unstyled and wildly inconsistent between browsers). Wraps the same
   input[name=state]/[name=billing_state] fields already in use, so nothing else has to change. ---- */
.sf-select{margin-top:.35rem}
.sf-select input{margin-top:0}
/* position:fixed, not absolute — top/left/width are set inline by app.js from the input's own
   getBoundingClientRect() each time it opens. A merely-absolute panel still counts toward any
   scrolling ancestor's overflow (the "Choose your option" modal's own overflow-y:auto in
   particular), so it could force the modal to grow/scroll just to fit the list even while visually
   floating on top of everything. Fixed positioning removes it from that calculation — it's anchored
   to the viewport, never to a container's scrollable content area. */
.sf-select-panel{position:fixed;z-index:9999;background:var(--sf-card);border:1px solid var(--sf-border);border-radius:8px;box-shadow:0 10px 28px rgba(0,0,0,.14);max-height:230px;overflow-y:auto;padding:.3rem}
.sf-select-option{padding:.5rem .65rem;border-radius:6px;cursor:pointer;font-size:.9rem;font-weight:500}
.sf-select-option:hover,.sf-select-option.is-active{background:var(--sf-bg);color:var(--sf-brand-dark)}
.sf-select-option[hidden]{display:none}
.sf-select-empty{padding:.5rem .65rem;font-size:.88rem;color:var(--sf-muted)}
.sf-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:0 1.25rem}
.sf-form-row{display:grid;grid-template-columns:1fr 1fr;gap:0 1.25rem}
.sf-form-row-3{grid-template-columns:1fr 1fr 1fr}
.sf-form label.sf-check{display:flex;align-items:center;gap:.5rem;font-weight:500}
.sf-check input{width:auto;margin:0}
.sf-terms-link,.sf-privacy-link{text-decoration:underline}
.sf-inline{display:flex;gap:.75rem;align-items:flex-end;flex-wrap:wrap}
.sf-inline label{margin-bottom:0}

.sf-error{background:#fdecea;color:#b3261e;padding:.85rem 1rem;border-radius:8px;margin-bottom:1.25rem}
.sf-order-summary{display:flex;align-items:center;background:#e7f6ec;color:#1e7e34;border-radius:var(--sf-radius);padding:1rem;margin-bottom:1.25rem;gap:1rem}
.sf-order-summary img{width:56px;height:56px;object-fit:contain;background:#fff;border-radius:8px;padding:.35rem;flex:none}
.sf-order-summary-info{display:flex;flex-direction:column;gap:.15rem;flex:1;min-width:0}
/* Truncates with an ellipsis instead of wrapping — the row's own image + "Change" button already
   leave this column narrow (worse on mobile), and a long product name or dose+price pairing was
   wrapping onto a second line each, instead of staying on their own single line. */
.sf-order-summary-info strong{font-size:.95rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sf-order-summary-info span{font-size:.88rem;opacity:.9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sf-order-summary .sf-btn-outline{color:#1e7e34;border-color:#1e7e34;flex:none}
.sf-order-total{border:1px solid var(--sf-border);border-radius:var(--sf-radius);padding:.9rem 1rem;margin-bottom:1.25rem}
.sf-order-total-row{display:flex;justify-content:space-between;padding:.3rem 0;font-size:.92rem;color:var(--sf-muted)}
.sf-order-total-row.sf-order-total-final{border-top:1px solid var(--sf-border);margin-top:.25rem;padding-top:.65rem;font-weight:700;font-size:1.05rem;color:var(--sf-font)}
.sf-order-total-row.sf-order-total-recurring{margin-top:.25rem;font-size:.85rem}
.sf-order-total-note{font-size:.8rem;color:var(--sf-muted);text-align:right;margin-top:-.1rem}
.sf-success{background:#e7f6ec;color:#1e7e34;padding:.85rem 1rem;border-radius:8px}
.sf-coupon-result{margin:-.5rem 0 1rem;font-size:.85rem}
.sf-coupon-ok{color:#1e7e34}
.sf-coupon-bad{color:var(--sf-danger)}
.sf-checkout-note{color:var(--sf-muted);font-size:.82rem;text-align:center;margin:1rem 0 0}
.sf-payment-element{margin:0 0 1.25rem;min-height:55px}
#sf-payment-step .sf-btn-link{display:block;text-align:center;margin-top:.85rem}
/* PayPal's inline Card Fields — each #sf-card-* div is handed to CardFields.NumberField()/etc.'s
   own render(), which fills it with a borderless iframe; the border/padding/focus treatment all
   comes from this wrapper instead, to match every other .sf-form input on the page. */
.sf-payment-divider{display:flex;align-items:center;gap:.75rem;margin:1.1rem 0;color:var(--sf-muted);font-size:.82rem}
.sf-payment-divider[hidden]{display:none}
.sf-payment-divider::before,.sf-payment-divider::after{content:"";flex:1;height:1px;background:var(--sf-border)}
/* box-sizing:border-box so the padding doesn't push the box past its own set height, and
   overflow:hidden as a safety net; the actual fix is the iframe rule below — PayPal drops a raw
   <iframe> into this container with no sizing of its own, so without it the iframe renders at its
   browser-default intrinsic size (commonly ~300x150) instead of filling the wrapper, visually
   overlapping every field/button below it rather than sitting inline like a normal input. */
/* No padding here — the iframe fills the box edge to edge below, and PayPal's own internal CSS
   (configured via the inputStyle passed to paypal.CardFields()) handles the input text's padding
   from the inside; padding on this wrapper just shrank the iframe's available space instead,
   clipping its contents oddly rather than inset-ing them. */
.sf-card-field{margin-top:.35rem;border:1px solid var(--sf-border);border-radius:8px;height:2.75rem;overflow:hidden;transition:border-color .15s}
.sf-card-field iframe{width:100%;height:100%;border:0;display:block}
.sf-card-field.is-focused{border-color:var(--sf-brand)}
.sf-card-field.is-invalid{border-color:var(--sf-danger)}

.sf-checkout{padding:2.5rem 1.25rem;max-width:760px}
.sf-checkout-card{background:#fff;border:1px solid var(--sf-border);border-radius:var(--sf-radius);box-shadow:0 2px 12px rgba(0,0,0,.05);padding:2rem;margin-top:1rem}
.sf-checkout-card h1{margin-top:0;font-size:1.5rem}
/* .sf-checkout's own side padding plus .sf-checkout-card's padding stacked on top of it (20px + 32px
   per side) left very little room for the actual input fields on a phone-width screen. Cut both back
   on mobile instead of just one, so the card still has *some* breathing room from the screen edge
   without doubling up. */
@media (max-width:760px){.sf-checkout{padding:1.5rem .5rem}.sf-checkout-card{padding:1.25rem}}
.sf-cart,.sf-confirm,.sf-account{padding:2.5rem 1.25rem;max-width:720px}

/* ---- Post-checkout consultation wait/reveal (success.php) ---- */
.sf-consult{text-align:center;max-width:480px;margin:0 auto}
/* Widened by JS (success.php's showReady()) once the MyOrbit intake iframe is actually populated —
   480px was sized for the old short spinner/message content, too narrow for an embedded form. Capped
   at 720px to match .sf-confirm's own max-width just above — this is already the true ceiling since
   .sf-consult sits inside it, so this fills that available width rather than exceeding it. */
.sf-consult.sf-consult-wide{max-width:720px}
.sf-consult-stage{opacity:0;transform:translateY(6px)}
.sf-consult-stage:not([hidden]){animation:sfConsultIn .45s ease forwards}
.sf-consult-fade-out{animation:sfConsultOut .2s ease forwards!important}
@keyframes sfConsultIn{to{opacity:1;transform:translateY(0)}}
@keyframes sfConsultOut{to{opacity:0;transform:translateY(-6px)}}
.sf-consult h1{font-size:1.4rem;margin:0 0 .6rem}
.sf-consult-lead{color:var(--sf-font);font-size:1rem;margin:0 0 .5rem;opacity:.85}
.sf-consult-note{font-size:.85rem;color:var(--sf-muted);margin-top:1rem}
.sf-consult .sf-muted{color:var(--sf-muted);font-size:.88rem}
.sf-consult-spinner{width:56px;height:56px;margin:0 auto 1.5rem;position:relative}
.sf-consult-spinner span{position:absolute;inset:0;border-radius:50%;border:3px solid transparent;border-top-color:var(--sf-brand);animation:sfSpin 1.1s cubic-bezier(.5,0,.5,1) infinite}
.sf-consult-spinner span:nth-child(2){border-top-color:transparent;border-right-color:var(--sf-brand-dark);opacity:.6;animation-duration:1.6s;animation-direction:reverse}
.sf-consult-spinner span:nth-child(3){inset:12px;border-top-color:var(--sf-brand);opacity:.35;animation-duration:.85s}
@keyframes sfSpin{to{transform:rotate(360deg)}}
.sf-consult-icon{width:64px;height:64px;border-radius:50%;margin:0 auto 1.25rem;display:flex;align-items:center;justify-content:center;animation:sfPopIn .45s cubic-bezier(.34,1.56,.64,1)}
.sf-consult-icon-ready{background:linear-gradient(135deg,var(--sf-brand-dark),var(--sf-brand))}
.sf-consult-icon-later{background:var(--sf-bg);border:1px solid var(--sf-border)}
@keyframes sfPopIn{from{transform:scale(0)}to{transform:scale(1)}}
.sf-consult #sf-consult-btn{margin-top:.5rem}
/* Bundle-order step indicator ("Form 1 of 2") + its "continue"/"I'm done" button — both only ever
   shown when a MyOrbit order resolved to more than one intake link (see success.php's own
   renderCurrentLink()); a single-item order never renders either, so this adds nothing visible to
   the far more common case. */
.sf-consult-step{font-size:.8rem;font-weight:700;letter-spacing:.02em;text-transform:uppercase;color:var(--sf-muted);margin:0 0 .75rem}
#sf-consult-next-btn{margin-top:.85rem;width:100%}
.sf-cart-row{display:flex;gap:1.25rem;align-items:center;background:var(--sf-card);border:1px solid var(--sf-border);border-radius:var(--sf-radius);padding:1rem}
.sf-cart-row img{width:100px;height:100px;object-fit:cover;border-radius:8px}
.sf-cart-actions{display:flex;gap:.75rem;margin-top:1.25rem}

.sf-table{width:100%;border-collapse:collapse;margin-top:1.5rem}
.sf-table th,.sf-table td{text-align:left;padding:.65rem .5rem;border-bottom:1px solid var(--sf-border);font-size:.92rem}

/* ---- My Account: passwordless dashboard ---- */
.small{font-size:.85rem}
.mt-2{margin-top:.5rem}
.mb-0{margin-bottom:0}
.sf-account-narrow{max-width:400px;margin:2rem auto}
.sf-btn-sm{padding:.4rem .8rem;font-size:.85rem}
.sf-btn-link-danger{background:none;border:none;color:var(--sf-danger);font-size:.85rem;cursor:pointer;padding:0;text-decoration:underline}

/* Sidebar shell — sits inside .sf-container (already capped at 1500px), so only vertical padding
   is needed here; the sidebar is sticky so it stays reachable next to a long Orders/Payments list. */
.sf-account-shell{display:flex;align-items:flex-start;gap:2rem;padding:2.5rem 0 4rem}
.sf-account-sidebar{flex:0 0 250px;background:var(--sf-card);border:1px solid var(--sf-border);border-radius:var(--sf-radius);padding:1.5rem 1.15rem;position:sticky;top:100px}
.sf-account-user{display:flex;align-items:center;gap:.75rem;padding-bottom:1.15rem;margin-bottom:1rem;border-bottom:1px solid var(--sf-border)}
.sf-account-avatar{flex:none;width:42px;height:42px;border-radius:50%;background:linear-gradient(135deg,var(--sf-brand),var(--sf-brand-dark));color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:.95rem}
.sf-account-user-info{min-width:0;display:flex;flex-direction:column;gap:.05rem}
.sf-account-user-info strong{font-size:.92rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}
.sf-account-user-info span{font-size:.78rem;color:var(--sf-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}
.sf-account-nav{display:flex;flex-direction:column;gap:.15rem}
.sf-account-nav a{display:flex;align-items:center;gap:.7rem;padding:.6rem .7rem;border-radius:8px;color:var(--sf-font);font-weight:600;font-size:.88rem;transition:background .12s,color .12s}
.sf-account-nav a svg{flex:none;opacity:.6}
.sf-account-nav a:hover{background:var(--sf-bg)}
.sf-account-nav a.active{background:var(--sf-brand);color:#fff}
.sf-account-nav a.active svg{opacity:1}
.sf-account-nav-count{margin-left:auto;background:var(--sf-bg);color:var(--sf-muted);font-size:.72rem;font-weight:700;padding:.1rem .5rem;border-radius:999px}
.sf-account-nav a.active .sf-account-nav-count{background:rgba(255,255,255,.25);color:#fff}
.sf-account-signout{margin-top:1rem;padding-top:1rem;border-top:1px solid var(--sf-border)}
.sf-account-signout button{display:flex;align-items:center;gap:.7rem;width:100%;background:none;border:none;padding:.6rem .7rem;border-radius:8px;color:var(--sf-danger);font-weight:600;font-size:.88rem;cursor:pointer}
.sf-account-signout button:hover{background:#fdecea}

.sf-account-main{flex:1;min-width:0;padding:.25rem 0}
.sf-account-main h1{margin:0 0 .3rem;font-size:1.5rem}
.sf-account-sub{color:var(--sf-muted);margin:0 0 1.75rem}
.sf-account-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:1rem;margin-bottom:2rem}
.sf-stat-card{background:var(--sf-card);border:1px solid var(--sf-border);border-radius:var(--sf-radius);padding:1.1rem 1.25rem}
.sf-stat-card span{display:block;color:var(--sf-muted);font-size:.76rem;text-transform:uppercase;letter-spacing:.03em;margin-bottom:.35rem}
.sf-stat-card strong{font-size:1.55rem;font-weight:700}
.sf-account-section-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem}
.sf-account-section-head h2{margin:0;font-size:1.05rem}
.sf-address-card{background:var(--sf-card);border:1px solid var(--sf-border);border-radius:var(--sf-radius);padding:1.5rem;max-width:560px}
@media (max-width:860px){.sf-account-shell{flex-direction:column;padding-top:1.5rem}.sf-account-sidebar{position:static;width:100%;flex:none}.sf-account-nav{flex-direction:row;flex-wrap:wrap}.sf-account-nav a{flex:1 1 auto;justify-content:center}.sf-account-user{justify-content:center}}

.sf-orders{display:flex;flex-direction:column;gap:1rem;margin-top:1rem}
.sf-order-card{background:var(--sf-card);border:1px solid var(--sf-border);border-radius:var(--sf-radius);padding:1.25rem}
.sf-order-card-head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;padding-bottom:.85rem;margin-bottom:.85rem;border-bottom:1px solid var(--sf-border)}
.sf-order-badge{flex:none;background:var(--sf-bg);color:var(--sf-font);font-weight:600;font-size:.78rem;padding:.3rem .7rem;border-radius:999px;white-space:nowrap}
.sf-order-badge-info{background:#e8f1ff;color:#0d47a1}
.sf-order-card-body{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:.75rem 1.25rem}
.sf-order-fact{display:flex;flex-direction:column;gap:.15rem}
.sf-order-fact span{color:var(--sf-muted);font-size:.76rem;text-transform:uppercase;letter-spacing:.03em}
.sf-order-consult{display:flex;align-items:center;gap:.85rem;flex-wrap:wrap;margin-top:1rem;padding-top:1rem;border-top:1px solid var(--sf-border)}
.sf-order-cancel{margin-top:.85rem}
@media (max-width:600px){.sf-order-card-head{flex-direction:column}}

.sf-activate{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:1.5rem;background:var(--sf-bg)}
.sf-activate-box{max-width:420px;width:100%;background:#fff;border:1px solid var(--sf-border);border-radius:var(--sf-radius);padding:2rem;text-align:center}
.sf-activate-box h1{margin-top:0;font-size:1.4rem}
.sf-activate-box .sf-form{text-align:left}
.sf-activate-logo{display:block;margin:0 auto 1.25rem;max-height:40px}
.sf-hint{color:var(--sf-muted);font-size:.85rem;margin-top:1rem}

.sf-footer{background:var(--sf-footer);color:var(--sf-footer-text,#fff);margin-top:3rem}
/* A builder page provides its own bottom spacing via each section's own Spacing fields — the fixed
   3rem gap above was designed for the non-builder pages' own content, and on a builder page whose
   last section has a full-bleed dark background, it shows as a visible stray white strip (body's
   own background) between that section and the footer instead of just breathing room. */
body:has(.sf-builder-page) .sf-footer{margin-top:0}
/* .sf-main's own sticky-footer flex-grow (below) has the same effect on a SHORT builder page: it
   stretches taller than the page's actual content to push the footer to the bottom of the viewport,
   and that extra stretched space sits below the last Section, inside .sf-main's own (unstyled, so
   body's white) box — a second, independent source of the exact same stray white strip the margin
   fix above addresses, this time between a full-bleed colored last section and the footer even with
   zero margin between them. A builder page is expected to size itself via its own content, not the
   sticky-footer mechanic, so opt it out entirely rather than trying to color space with no reliable
   single color to give it (the last section's own background is arbitrary/dynamic per client). */
body:has(.sf-builder-page) .sf-main{flex:0 0 auto}
.sf-footer-inner{display:flex;justify-content:space-between;flex-wrap:wrap;gap:.5rem;padding:1.5rem 1.25rem;font-size:.9rem}
.sf-footer a{color:var(--sf-footer-text,#fff);opacity:.85}
.sf-footer-blurb{width:100%;opacity:.85}
/* Icons only ever appear for whichever social links a client actually filled in (see
   storefront/lib/View.php's footerBar()) — outline, not each platform's own brand color, to stay
   neutral against any theme color a client has picked. Glyph color comes from the icon font's own
   inherited `color` (set on .sf-footer above), not repeated here — border/hover just follow the same
   --sf-footer-text variable so the ring and the glyph it surrounds are always the same color. */
.sf-footer-social{display:flex;justify-content:center;flex-wrap:wrap;gap:.75rem;padding:0 1.25rem 1.25rem;border-top:1px solid rgba(255,255,255,.12);margin-top:.25rem;padding-top:1.25rem}
.sf-footer-social a{width:36px;height:36px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--sf-footer-text,#fff);border-radius:50%;font-size:1rem;opacity:.85;transition:opacity .15s,border-color .15s}
.sf-footer-social a:hover{opacity:1;border-color:var(--sf-footer-text,#fff)}
/* Stacked, centered, and reordered (email, then affiliate links, then copyright last) — the
   order property only reflows the visual/reading order, the underlying markup order (copyright
   first) is unchanged so nothing else needs to know about this. */
@media (max-width:760px){.sf-footer-inner{flex-direction:column;align-items:center;text-align:center}.sf-footer-email{order:1}.sf-footer-affiliate{order:2}.sf-footer-copy{order:3}}

@media (max-width:760px){.sf-product{grid-template-columns:1fr}.sf-grid-2{grid-template-columns:1fr}.sf-form-row,.sf-form-row-3{grid-template-columns:1fr}
/* Product names carry the full ingredient list (e.g. "Female Hair Loss Treatment (Biotin,
   Finasteride, GHK-Cu, ...)"), same long strings BottleGeneratorService has to fit on a vial label —
   at the desktop 1.6rem size that wraps across most of a phone screen before any actual product
   info is visible. Same downsizing .sf-hero h1 already gets at this breakpoint. */
.sf-product-info h1{font-size:1.25rem}}
/* Website builder Product Grid / Category Grid blocks (sf_render_product_grid()/sf_render_category_grid()
   in WebsiteRenderer.php) set their column count as an inline style/custom property per the admin's
   own "Columns" field, with no responsive fallback of their own — unlike every other multi-column
   block on this site, this one is server-rendered fresh on every request rather than baked from
   builder JS, so it has no per-instance responsive <style> tag the way the builder's own Columns
   component does. Only a stylesheet rule marked !important can override an inline style, which is
   why this needs it and the plain class-only rules above don't. */
@media (max-width:760px){.sf-grid{grid-template-columns:repeat(1,1fr) !important}.sf-category-grid{grid-template-columns:repeat(1,1fr) !important}}

/* Secondary-action counterpart to .sf-btn (Remove/Change/Apply/Add to cart/etc.) — only one
   definition now; this used to be declared twice (once here, once up near .sf-btn-lg), the second
   copy re-declaring padding/font-size/display that .sf-btn and .sf-btn-lg already set. Both
   declarations had equal specificity, so whichever came later in the file silently won — "Add to
   cart" (.sf-btn .sf-btn-outline .sf-btn-lg) ended up with THIS rule's smaller padding/font-size
   instead of .sf-btn-lg's, while "Buy now" (.sf-btn .sf-btn-lg, no .sf-btn-outline) correctly kept
   .sf-btn-lg's sizing — same two buttons, visibly different heights in the same row. Only
   color/border/background are genuinely specific to "outline"; everything else now comes from
   .sf-btn/.sf-btn-lg like every other button. */
.sf-btn-outline{background:transparent;color:var(--sf-button);border:1.5px solid var(--sf-button)}
.sf-btn-outline:hover{background:color-mix(in srgb, var(--sf-button) 10%, transparent)}
.sf-pkg-actions{display:flex;gap:.6rem;flex-wrap:wrap;margin-top:1.25rem}
.sf-pkg-actions>*{flex:1 1 140px}
/* Groups the hamburger toggle + nav as ONE flex item on the right of .sf-header-inner (which is
   justify-content:space-between) — without this wrapper, each was its own top-level flex child and
   space-between spread them out instead of keeping them together on the right. The cart icon itself
   lives inside <nav> (last, right before Get Started — see View::headerBar()), after every other
   menu item including My Orders. */
.sf-header-actions{display:flex;align-items:center;gap:.25rem}
.sf-cart-icon-btn{position:relative;background:none;border:none;cursor:pointer;color:var(--sf-font);padding:.4rem;display:inline-flex;align-items:center;vertical-align:middle}
.sf-cart-badge{position:absolute;top:-2px;right:-2px;background:var(--sf-danger);color:#fff;font-size:.65rem;font-weight:700;line-height:1;padding:.2rem .4rem;border-radius:999px;min-width:1.2em;text-align:center}
/* Narrow viewport: the cart now sits outside .sf-nav (see View.php) specifically so it can stay
   reachable here at all times instead of only showing once the hamburger menu is opened — a fixed,
   circular floating button in the bottom corner, same pattern as the chat-widget bubble most clients
   already have layered on their site, parked a little higher (bottom:5.5rem) so the two never
   overlap. Reuses the theme's own footer color/text tokens rather than a hardcoded black, so it
   still matches each client's branding like every other themed element. */
@media (max-width:760px){
.sf-cart-icon-btn{position:fixed;right:1rem;bottom:5.5rem;z-index:150;width:52px;height:52px;padding:0;justify-content:center;background:var(--sf-footer);color:var(--sf-footer-text);border-radius:50%;box-shadow:0 4px 14px rgba(0,0,0,.3)}
.sf-cart-icon-btn svg{width:24px;height:24px}
.sf-cart-badge{top:-4px;right:-4px}
}
.sf-cart-drawer-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:98}
.sf-cart-drawer-backdrop[hidden]{display:none}
.sf-cart-drawer{position:fixed;top:0;right:0;bottom:0;width:380px;max-width:92vw;background:var(--sf-card);z-index:99;display:flex;flex-direction:column;box-shadow:-8px 0 24px rgba(0,0,0,.15)}
.sf-cart-drawer[hidden]{display:none}
.sf-cart-drawer-head{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid var(--sf-border)}
.sf-cart-drawer-head h2{margin:0;font-size:1.1rem}
.sf-cart-close{background:none;border:none;font-size:1.6rem;line-height:1;cursor:pointer;color:var(--sf-muted)}
.sf-cart-drawer-body{flex:1;overflow-y:auto;padding:0 1.25rem}
.sf-cart-item{display:flex;align-items:flex-start;gap:.6rem;padding:.9rem 0;border-bottom:1px solid var(--sf-border)}
.sf-cart-item-link{display:flex;align-items:flex-start;gap:.6rem;flex:1;min-width:0;color:inherit;text-decoration:none}
.sf-cart-item-link:hover .sf-cart-item-name{text-decoration:underline}
.sf-cart-item img{width:48px;height:48px;object-fit:contain;background:#fff;border:1px solid var(--sf-border);border-radius:8px;padding:.3rem;flex:none}
.sf-cart-item-info{flex:1;min-width:0}
.sf-cart-item-name{font-weight:600;font-size:.92rem}
.sf-cart-item-sub{font-size:.8rem;color:var(--sf-muted)}
.sf-cart-item-type{display:inline-block;font-size:.7rem;font-weight:600;color:var(--sf-muted);background:var(--sf-bg);border:1px solid var(--sf-border);border-radius:999px;padding:.1rem .5rem;margin-top:.3rem}
.sf-cart-item-price{font-weight:600;font-size:.9rem;white-space:nowrap}
.sf-cart-item-remove{background:none;border:none;font-size:1.3rem;line-height:1;color:var(--sf-muted);cursor:pointer;padding:0 0 0 .3rem}
.sf-cart-item-consult .sf-cart-item-name{font-weight:500;color:var(--sf-muted)}
.sf-cart-drawer-foot{padding:1rem 1.25rem;border-top:1px solid var(--sf-border)}
.sf-cart-subtotal{display:flex;justify-content:space-between;font-weight:700;margin-bottom:.75rem}
.sf-order-summary-bundle{display:flex;flex-direction:column;gap:.6rem;margin-bottom:1.25rem}
.sf-order-summary-row{display:flex;align-items:center;background:#e7f6ec;color:#1e7e34;border-radius:var(--sf-radius);padding:.85rem 1rem;gap:1rem}
.sf-order-summary-row img{width:48px;height:48px;object-fit:contain;background:#fff;border-radius:8px;padding:.3rem;flex:none}
.sf-order-summary-row .sf-order-summary-info{display:flex;flex-direction:column;gap:.1rem;flex:1;min-width:0}
.sf-order-summary-row .sf-order-summary-info strong{font-size:.92rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sf-order-summary-row .sf-order-summary-info span{font-size:.85rem;opacity:.9}
.sf-order-summary-row .sf-btn-link{color:#1e7e34;text-decoration:underline;flex:none}
