/* Tiki Shop — matches the tikimassage.ca refined site's palette, type and
   header-scroll behaviour. Same brand, same logo, different storefront. */

:root {
  --night: #042536;
  --deep: #0a3a56;
  --deep-2: #0e4a6b;
  --blue: #0b7f9e;
  --aqua: #79d3d8;
  --aqua-soft: #e2f4f4;
  --lime: #bfe57a;
  --coral: #ff7d64;
  --coral-ink: #7a2f22;
  --sand: #fdf5ea;
  --paper: #ffffff;
  --ink: #16303b;
  --muted: #5b7280;
  --line: rgba(10, 58, 86, .14);
  --shadow: 0 22px 55px rgba(6, 38, 58, .16);
  --shadow-sm: 0 10px 30px rgba(6, 38, 58, .1);
  --radius: 16px;
  --radius-lg: 24px;
  --radius-pill: 999px;
  --header-h: 72px;
  --shell: min(1180px, 100% - 40px);
  --serif: "Playfair Display", "Iowan Old Style", "Palatino Linotype", Georgia, serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0; background: var(--sand); color: var(--ink);
  font-family: var(--sans); font-size: 1rem; line-height: 1.6;
  -webkit-font-smoothing: antialiased; overflow-x: hidden;
}
img { display: block; max-width: 100%; height: auto; }
a { color: var(--blue); }
button { font: inherit; color: inherit; cursor: pointer; }

h1, h2, h3 {
  margin: 0 0 .4em; font-family: var(--serif); font-weight: 600;
  line-height: 1.15; letter-spacing: -.01em; color: var(--night);
}
h1 { font-size: clamp(1.9rem, 4vw, 2.6rem); }
h2 { font-size: clamp(1.3rem, 2.5vw, 1.6rem); }
p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

/* The browser's own rule for this is `[hidden] { display: none }`, which any
   rule with more weight quietly beats — and an id with a `display` on it has
   far more. That is not a theoretical risk here: the price gate is toggled
   entirely through the `hidden` property, and #cart-foot-out carries
   `display: grid`, so a signed-in client was still shown "Sign in to see what
   your basket costs" directly above their own subtotal. Nothing in the JS was
   wrong; the instruction was being ignored.

   !important is the right tool for exactly this: `hidden` is a statement that
   the element is not relevant, and no layout rule should be able to outvote
   it. Toggling `hidden` is the pattern used throughout this codebase, so the
   guard belongs here once rather than as a fix per element. */
[hidden] { display: none !important; }

.shell { width: var(--shell); margin-inline: auto; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* ---------- Header — transparent over the hero, solid once scrolled ---------- */
.site-header {
  position: sticky; top: 0; z-index: 100;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background .2s ease, border-color .2s ease, box-shadow .2s ease;
}
.site-header.scrolled {
  background: rgba(4, 37, 54, .96);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border-color: rgba(255,255,255,.08);
  box-shadow: 0 6px 24px rgba(6, 38, 58, .18);
}
.header-inner {
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px 0; gap: 16px; min-height: var(--header-h);
}
.brand { display: inline-flex; align-items: center; gap: 10px; color: #fff; text-decoration: none; }
.brand img { width: 36px; height: 36px; }
.brand-name {
  font-family: var(--serif); font-size: 1.25rem; font-weight: 700; letter-spacing: -.01em;
  color: #fff;
}
.cart-btn {
  position: relative; display: inline-flex; align-items: center; gap: 8px;
  background: var(--coral); color: #fff; border: none; border-radius: var(--radius-pill);
  padding: 10px 18px; font-weight: 700; font-size: .92rem;
  transition: transform .2s ease, box-shadow .2s ease;
}
.cart-btn:hover { transform: translateY(-1px); box-shadow: 0 10px 22px rgba(255,125,100,.35); }
.cart-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 5px; border-radius: 999px;
  background: #fff; color: var(--coral-ink); font-size: .74rem; font-weight: 800;
}

/* ---------- Page hero — sits under the transparent header ---------- */
/* The hero sits under the transparent sticky header, so its top padding has
   to clear the header's height before anything is visible. The extra 40px on
   top of that put a 59px hole between the logo and the page title and made
   the band 298px tall — on a page whose actual content is a single email
   field, that is a lot of screen spent on nothing. 8px of clearance is enough
   to keep the title off the logo. */
.page-hero {
  background: linear-gradient(100deg, var(--night) 0%, var(--deep) 100%);
  color: #fff; margin-top: calc(var(--header-h) * -1);
  padding: calc(var(--header-h) + 8px) 0 24px;
}
.page-hero h1 { color: #fff; }
.page-hero p { color: rgba(255,255,255,.82); max-width: 640px; font-size: 1.02rem; }
.page-hero .note { font-size: .84rem; color: rgba(255,255,255,.6); margin-top: 10px; }

/* ---------- Order summary on success.html — same card/typography
   treatment as the receipt email, so the page and the email that lands a
   moment later read as the same document. ---------- */
.os-card {
  background: #fff; border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
  padding: 22px 24px; margin: 22px 0 6px; max-width: 560px; color: var(--ink);
}
.os-head {
  display: flex; align-items: baseline; gap: 10px;
  padding-bottom: 12px; border-bottom: 1px solid var(--line);
}
.os-label {
  font-size: .72rem; letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
}
.os-no { font-family: var(--serif); font-size: 1.3rem; font-weight: 700; color: var(--night); }
.os-table { width: 100%; border-collapse: collapse; margin-top: 6px; }
.os-table td { padding: 10px 0; border-bottom: 1px solid #eef1f2; font-size: .92rem; }
.os-item { padding-right: 14px; }
.os-amt { text-align: right; white-space: nowrap; }
.os-totals { width: 100%; border-collapse: collapse; margin-top: 10px; }
.os-totals td { padding: 3px 0; font-size: .86rem; color: var(--muted); }
.os-totals .os-grand td {
  padding-top: 9px; font-size: 1.02rem; font-weight: 700; color: var(--night);
}
.os-ship {
  margin-top: 16px; padding: 14px 16px; background: var(--aqua-soft);
  border-radius: 12px; font-size: .86rem; color: var(--deep); line-height: 1.55;
}
/* .page-hero p sets white text for the hero's own copy and outranks a bare
   .os-note, which left this invisible on the card's white background. */
.page-hero .os-note, .os-note {
  margin: 14px 0 0; font-size: .82rem; color: var(--muted); max-width: none;
}

/* ---------- Category nav, merged into the header on the shop page: logo
   flush left, category chips + "All products" next, search pushed to the
   far right with margin-left:auto, cart button (mobile only) after it. ---------- */
.shop-header-inner {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 10px 20px; padding: 14px clamp(16px, 2vw, 28px); min-height: var(--header-h);
}
.cat-nav-inline { display: flex; align-items: center; flex-wrap: wrap; gap: 2px; }
.cat-nav-items { display: flex; flex-wrap: wrap; }
.cat-nav-top {
  background: none; border: none; padding: 10px 14px; display: inline-block;
  font-size: .82rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: rgba(255,255,255,.82); border-radius: var(--radius-pill); text-decoration: none;
}
.cat-nav-top:hover, .cat-nav-top.open { color: #fff; background: rgba(255,255,255,.1); }
.search-form { margin-left: auto; display: flex; }
.search-input {
  margin-left: auto; padding: 8px 14px; border: 1px solid var(--line); border-radius: var(--radius-pill);
  font-size: .88rem; min-width: 200px; background: #fff; color: var(--ink);
}

/* ---------- Chips (reused by brand filter + "All products") ---------- */
.chip {
  padding: 7px 15px; border-radius: var(--radius-pill); border: 1px solid var(--line);
  background: #fff; color: var(--muted); font-size: .86rem; font-weight: 600;
  transition: all .15s ease;
}
.chip:hover { border-color: var(--blue); color: var(--blue); }
.chip.active { background: var(--deep); border-color: var(--deep); color: #fff; }

/* ---------- Three-column shop layout: brand sidebar / grid / cart panel ---------- */
.shop-layout { padding: 28px 0 72px; }
.shop-sidebar h4 {
  margin: 0 0 12px; font-family: var(--sans); font-size: .78rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .06em; color: var(--muted);
}
.filter-group { display: flex; flex-wrap: wrap; gap: 8px; }
.filter-group--vertical { flex-direction: column; align-items: flex-start; }
.filter-group--vertical .chip { width: 100%; text-align: left; }
.shop-sidebar h4.sidebar-section { margin-top: 24px; }
.skin-filters { display: flex; flex-direction: column; gap: 4px; }
.skin-check {
  display: flex; align-items: center; gap: 8px; padding: 5px 2px;
  font-size: .86rem; color: var(--ink); cursor: pointer;
}
.skin-check input { width: 16px; height: 16px; accent-color: var(--deep); cursor: pointer; }

.result-count { font-size: .8rem; color: var(--muted); margin: 0 0 4px; }

.product-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 20px;
}
.product-card {
  background: #fff; border-radius: var(--radius); overflow: hidden;
  box-shadow: var(--shadow-sm); display: flex; flex-direction: column;
  transition: transform .2s ease, box-shadow .2s ease;
}
.product-card:hover { transform: translateY(-3px); box-shadow: var(--shadow); }
.product-photo {
  aspect-ratio: 4 / 5; background: #fff; display: flex; align-items: center;
  justify-content: center; padding: 14px; position: relative;
}
.product-photo-link { display: block; }
.product-name-link { color: inherit; text-decoration: none; }
.product-name-link:hover { color: var(--blue); }
/* width/height 100% + object-fit, not max-width/max-height: Safari doesn't
   reliably resolve percentage max-height against a flex parent sized via
   aspect-ratio, so tall product photos rendered at native size uncropped
   on iOS instead of being contained — this form works everywhere. */
.product-photo img { width: 100%; height: 100%; object-fit: contain; }
.product-photo--none { color: var(--blue); font-size: 1.6rem; }
.brand-tag {
  position: absolute; top: 10px; left: 10px; padding: 3px 10px; border-radius: var(--radius-pill);
  background: rgba(4,37,54,.82); color: #fff; font-size: .66rem; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase;
}
.product-body { padding: 14px 16px 16px; display: flex; flex-direction: column; gap: 4px; flex: 1; }
.product-body h3 { font-size: .98rem; margin: 0; line-height: 1.3; }
.product-meta { font-size: .78rem; color: var(--muted); }
.product-price {
  margin-top: auto; padding-top: 10px; font-family: var(--serif); font-weight: 600;
  font-size: 1.1rem; color: var(--night); display: flex; align-items: center;
  flex-wrap: wrap; row-gap: 8px; gap: 8px;
}
.product-price .add-btn { margin-left: auto; }
.product-price small { font-family: var(--sans); font-size: .66rem; font-weight: 700; color: var(--muted); text-transform: uppercase; }
.add-btn {
  border: 1px solid var(--deep); background: #fff; color: var(--deep); border-radius: var(--radius-pill);
  padding: 6px 14px; font-size: .8rem; font-weight: 700; white-space: nowrap;
  transition: all .15s ease;
}
.add-btn:hover { background: var(--deep); color: #fff; }
.add-btn.added { background: var(--lime); border-color: var(--lime); color: var(--night); }

.empty-state { text-align: center; padding: 60px 20px; color: var(--muted); }

/* ---------- Cart panel: a slide-in overlay drawer by default (mobile/tablet),
   promoted to a static third column at desktop widths (see min-width:1024px below) ---------- */
.cart-overlay {
  position: fixed; inset: 0; background: rgba(4,27,41,.5); z-index: 150;
  opacity: 0; pointer-events: none; transition: opacity .25s ease;
}
.cart-overlay.open { opacity: 1; pointer-events: auto; }
.shop-cart-panel {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(420px, 100%);
  background: #fff; z-index: 151; box-shadow: -20px 0 50px rgba(0,0,0,.2);
  display: flex; flex-direction: column; transform: translateX(100%);
  transition: transform .3s ease;
}
.shop-cart-panel.open { transform: translateX(0); }
.cart-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px 20px; border-bottom: 1px solid var(--line);
}
.cart-close { background: none; border: none; font-size: 1.4rem; line-height: 1; color: var(--muted); padding: 4px; }
.cart-items { flex: 1; overflow-y: auto; padding: 12px 20px; }
.cart-item { display: flex; gap: 12px; padding: 14px 0; border-bottom: 1px solid var(--line); }
.cart-item img { width: 56px; height: 56px; object-fit: contain; background: var(--aqua-soft); border-radius: 8px; padding: 4px; }
.cart-item-body { flex: 1; min-width: 0; }
.cart-item-body h4 { margin: 0 0 2px; font-size: .88rem; font-family: var(--sans); font-weight: 700; }
.cart-item-meta { font-size: .76rem; color: var(--muted); }
.cart-item-row { display: flex; align-items: center; justify-content: space-between; margin-top: 6px; }
.qty-controls { display: flex; align-items: center; gap: 8px; }
.qty-controls button {
  width: 24px; height: 24px; border-radius: 50%; border: 1px solid var(--line); background: #fff;
  font-size: .9rem; line-height: 1;
}
.remove-link { font-size: .74rem; color: var(--coral-ink); background: none; border: none; text-decoration: underline; }
.cart-empty { text-align: center; color: var(--muted); padding: 40px 10px; }
.cart-foot { padding: 16px 20px 22px; border-top: 1px solid var(--line); }
.cart-summary-row { display: flex; justify-content: space-between; font-size: .88rem; color: var(--muted); margin-bottom: 6px; }
.cart-summary-row.total { font-size: 1.05rem; color: var(--night); font-weight: 700; font-family: var(--serif); margin: 10px 0; }
.shipping-note { font-size: .76rem; color: var(--muted); margin-bottom: 12px; }
.view-cart-btn {
  display: block; width: 100%; padding: 12px; margin-bottom: 10px; box-sizing: border-box;
  border: 1px solid var(--deep); border-radius: var(--radius-pill);
  background: #fff; color: var(--deep); font-weight: 700; font-size: .92rem;
  text-align: center; text-decoration: none;
}
.view-cart-btn:hover { background: var(--aqua-soft); }
/* Used on both a <button> and an <a>. A button centres its text and has no
   underline for free; an anchor does neither, so the same class produced a
   left-aligned, underlined "See prices" in the cart. Set both explicitly
   rather than relying on whichever element happens to be used. */
.checkout-btn {
  display: block; width: 100%; padding: 14px; border: none;
  border-radius: var(--radius-pill); background: var(--deep); color: #fff;
  font-weight: 700; font-size: .96rem; text-align: center; text-decoration: none;
}
a.checkout-btn:hover { color: #fff; text-decoration: none; }
.checkout-btn:hover { background: var(--deep-2); }
.checkout-btn:disabled { opacity: .6; cursor: default; }
.checkout-error { color: var(--coral-ink); font-size: .82rem; margin-top: 10px; text-align: center; }

/* "Pay in clinic" sits above "Pay with Square" and is outlined rather than
   filled. Two identical buttons stacked would make the client stop and work
   out which one they are, which is exactly the hesitation that loses an
   order — the filled one is the ordinary path, this is the deliberate
   alternative. Same size and shape, so it reads as an equal choice and not as
   a lesser one. */
.clinic-btn {
  display: block; width: 100%; padding: 13px; box-sizing: border-box;
  border: 1px solid var(--deep); border-radius: var(--radius-pill);
  background: #fff; color: var(--deep); font-weight: 700; font-size: .96rem;
  text-align: center; text-decoration: none; cursor: pointer;
}
.clinic-btn:hover { background: var(--aqua-soft); }
.clinic-btn:disabled { opacity: .6; cursor: default; }
.clinic-pay-note {
  font-size: .78rem; color: var(--muted); text-align: center;
  margin: 8px 0 14px; line-height: 1.45;
}

/* ---------- Floating buttons: "back to shop" on every page except the
   shop itself (same pattern as tikimassage.ca's "back-home"), "back to
   top" on the shop page instead, shown once you've scrolled down. ---------- */
.back-home, .back-top {
  position: fixed; z-index: 80;
  left: clamp(16px, 4vw, 32px); bottom: clamp(16px, 4vw, 32px);
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px; padding: 10px 22px;
  border: 1px solid rgba(255,255,255,.16); border-radius: var(--radius-pill);
  background: var(--deep); color: #fff;
  font-size: .875rem; font-weight: 700; text-decoration: none; cursor: pointer;
  box-shadow: 0 12px 30px rgba(4,24,36,.38);
  transition: transform .25s ease, box-shadow .25s ease, background .25s ease, opacity .25s ease;
}
.back-home span, .back-top span { transition: transform .25s ease; }
.back-home:hover, .back-top:hover {
  background: var(--deep-2); transform: translateY(-2px);
  box-shadow: 0 16px 36px rgba(4,24,36,.46);
}
.back-home:hover span { transform: translateX(-3px); }
.back-top:hover span { transform: translateY(-2px); }
.back-home:active, .back-top:active { transform: scale(.97); transition-duration: .12s; }
.back-top { opacity: 0; pointer-events: none; }
.back-top.visible { opacity: 1; pointer-events: auto; }

/* ---------- Footer — matches tikimassage.ca's richer footer (brand blurb +
   link columns + a bottom bar), not just a one-line contact strip. ---------- */
.site-footer {
  background: var(--night); color: rgba(255,255,255,.8); padding: 32px 0 22px; font-size: .88rem;
}
/* Content-hugging columns (not 1.4fr/1fr/1fr proportional ones) at desktop
   widths — fractional grid columns stretch to fill the whole row even
   though the actual link lists don't need nearly that width, which left
   large, awkward gaps *inside* each column instead of the columns just
   sitting at a sensible width next to each other. `auto` columns size to
   their own content and the row just ends where the content does,
   leaving any extra space as a single trailing gap instead of three
   stretched-out ones. Narrower breakpoints below switch back to
   proportional columns, where full-width columns are what let each one's
   own link list wrap onto multiple lines instead of overflowing. */
.footer-grid { display: grid; grid-template-columns: repeat(3, auto); gap: 24px 56px; }
.footer-grid > div:first-child p { white-space: nowrap; }
/* On pages with the merged header (.shop-header — logo flush left, no
   .shell centering, own clamp() side padding), the footer used to sit
   inside a .shell like the rest of the page, capping it at 1180px and
   centering it — which left big, increasingly obvious blank margins on
   wide screens and put the footer logo out of horizontal alignment with
   the header logo above it (different left insets). Match the footer's
   inner wrapper to the header's own full-width + side-padding approach
   instead, on those pages specifically — success.html/cancel.html still
   use the older shell-centered header/hero, so their footer stays as-is
   to keep aligned with *their* header. */
body:has(.shop-header) .site-footer .shell {
  width: 100%; max-width: none; margin-inline: 0;
  padding-inline: clamp(16px, 2vw, 28px);
}
.site-footer .brand { color: #fff; margin-bottom: 8px; }
.site-footer .brand-name { color: #fff; }
.site-footer h4 {
  color: #fff; font-family: var(--sans); font-size: .74rem;
  letter-spacing: .12em; text-transform: uppercase; margin-bottom: 8px;
}
.site-footer a { color: rgba(255,255,255,.8); text-decoration: none; }
.site-footer a:hover { color: #fff; }
.footnote {
  cursor: help; font-weight: 700; color: rgba(255,255,255,.9);
}
/* The tooltip anchors to the paragraph, not to the asterisk itself. Anchored
   to the asterisk it was a 220px box hanging off a character near the end of
   a line, which pushed the document ~190px wider than the viewport on a
   phone — every page on the site could be swiped sideways into blank space.
   Anchoring to the paragraph keeps it on screen at every width. */
.footer-grid > div:first-child p { position: relative; }
.footnote-tip {
  position: absolute; left: 0; bottom: calc(100% + 10px); z-index: 10;
  width: min(220px, 100%); padding: 10px 12px; border-radius: var(--radius);
  background: #fff; color: var(--ink); font-size: .78rem; font-weight: 400; line-height: 1.4;
  box-shadow: var(--shadow); opacity: 0; transform: translateY(4px); pointer-events: none;
  transition: opacity .15s ease, transform .15s ease;
}
.footnote:hover .footnote-tip, .footnote:focus .footnote-tip, .footnote:focus-visible .footnote-tip {
  opacity: 1; transform: translateY(0);
}
.footer-links { display: grid; gap: 5px; }
.footer-links--inline { display: flex; flex-wrap: wrap; gap: 6px 16px; }
.footer-links span { color: rgba(255,255,255,.55); font-size: .82rem; }
.footer-bottom {
  margin-top: 20px; padding-top: 14px; border-top: 1px solid rgba(255,255,255,.12);
  display: flex; flex-wrap: wrap; gap: 6px 20px; justify-content: space-between;
  font-size: .78rem; color: rgba(255,255,255,.6);
}
@media (max-width: 760px) {
  .footer-grid { grid-template-columns: 1fr 1fr; }
  .footer-grid > div:first-child p { white-space: normal; }
}
@media (max-width: 520px) {
  .footer-grid { grid-template-columns: 1fr; }
}

/* ---------- Desktop: real 3-column layout, cart docked as a static panel ---------- */
/* Desktop: sidebar and cart panel pinned to the true left/right edges of
   the browser window (not just the edges of the centered .shell), so they
   read as fixed rails either side of the page rather than a boxed-in
   3-column block. The product grid keeps its own centered .shell width and
   just gets side margins to clear the two fixed rails. */
@media (min-width: 1024px) {
  :root { --rail-w: 230px; }

  /* shop-layout still carries the shared .shell class (for the mobile/
     tablet stacked layout), which caps its width at 1180px — override
     that here, since at this breakpoint the sidebar/cart are fixed rails
     and shop-main needs to use the real remaining viewport width, not a
     width capped independently of how wide the window actually is. */
  .shop-layout { display: block; width: 100%; max-width: none; margin-inline: 0; }

  .shop-sidebar {
    position: fixed; left: 0; top: var(--header-h); bottom: 0; width: var(--rail-w);
    padding: 24px clamp(16px, 2vw, 28px); overflow-y: auto; z-index: 40;
  }
  .shop-main { margin: 0 var(--rail-w); padding: 0 clamp(16px, 2vw, 28px); min-width: 0; }
  /* The fixed sidebar/cart rails span the full page height, so anything
     below the product grid (the footer) needs the same side margin or the
     rails cover its edges too. product.html only has the cart rail (no
     brand sidebar), so the two sides are conditioned separately. */
  body:has(.shop-cart-panel) .site-footer { margin-right: var(--rail-w); }
  body:has(.shop-sidebar) .site-footer { margin-left: var(--rail-w); }
  .cart-btn { display: none; } /* redundant once the cart panel is always visible */
  .cart-overlay { display: none; }
  .shop-cart-panel {
    position: fixed; top: var(--header-h); right: 0; bottom: 0; left: auto;
    width: var(--rail-w); max-width: var(--rail-w);
    transform: none; box-shadow: none; border-radius: 0;
    border-left: 1px solid var(--line); z-index: 40;
  }
  .cart-close { display: none; } /* nothing to close — it's a fixed panel, not a drawer */
}

@media (min-width: 1280px) {
  :root { --rail-w: 280px; }
}

/* Product grid: explicit column counts at desktop widths so it never
   collapses to just 1-2 columns in the space between the fixed rails —
   auto-fill alone was doing that around 1024-1279px. */
@media (min-width: 1024px) {
  .product-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 1280px) {
  .product-grid { grid-template-columns: repeat(4, 1fr); }
}
@media (min-width: 1600px) {
  .product-grid { grid-template-columns: repeat(5, 1fr); }
}
@media (min-width: 1920px) {
  .product-grid { grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
}

@media (max-width: 640px) {
  .cat-nav-panel { left: 0; right: 0; min-width: 0; width: calc(100vw - 32px); flex-wrap: wrap; }
  .cat-nav-top { padding: 12px 10px; font-size: .76rem; }

  /* Two rows instead of four (logo / nav / search / cart each on their
     own line, which was pushing the header well down the screen): logo
     + search share the top row, category chips + cart pill share the
     second. The ::before is a flex "line break" — an empty, zero-height
     item forced to flex-basis:100% between the two row groups — rather
     than a fixed height on the container, so it still reflows correctly
     if the nav chips wrap to a 3rd line on very narrow phones. */
  .shop-header-inner { row-gap: 8px; }
  .shop-header .brand { order: 1; flex: 0 0 auto; }
  .shop-header .search-input,
  .shop-header .search-form {
    /* flex-basis:0, not auto — flex-wrap decides whether an item fits a
       line using its *unshrunk* preferred width, before flex-shrink is
       ever applied. auto's preferred width (a text input's UA-default
       ~170px+) was wider than the space left after the logo, so it wrapped
       to its own line regardless of flex-shrink:1. basis:0 makes it
       start from nothing and grow to fill the line instead. */
    order: 2; margin-left: 12px; width: auto; flex: 1 1 0; min-width: 0;
  }
  .shop-header .shop-header-inner::before {
    content: ""; order: 3; flex-basis: 100%; height: 0;
  }
  /* The 5 chips already fill the row's full width on their own, so
     making them wrap (or shrinking them further) still leaves no room
     for the cart pill beside them — scroll the chip row horizontally
     instead and pin the cart pill fixed at its end. */
  .shop-header .cat-nav-inline {
    /* Same flex-basis:0 fix as the search input above — with basis:auto
       its nowrap chip content's full unshrunk width was evaluated against
       the cart pill for line-fitting, didn't fit, and wrapped away from
       it entirely rather than sharing the line and scrolling. */
    order: 4; flex: 1 1 0; min-width: 0; overflow-x: auto;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
  }
  .shop-header .cat-nav-inline::-webkit-scrollbar { display: none; }
  .shop-header .cat-nav-items { flex-wrap: nowrap; }
  .shop-header .cat-nav-top { white-space: nowrap; }
  .shop-header .cart-btn { order: 5; margin-left: 10px; flex: 0 0 auto; }
}


/* ---------- Product detail page ----------
   Lived inline in product.html, which meant the static pages under
   /products/ — generated by tools/build-seo.js and linking only this
   file — rendered with no product styling at all, so photos came out
   at their natural size. Shared from here instead. ---------- */
.pdp { padding: 28px 0 72px; max-width: 1400px; }
.pdp-crumbs { font-size: .82rem; color: var(--muted); margin-bottom: 18px; }
.pdp-crumbs a { color: var(--blue); }
.pdp-grid { display: grid; grid-template-columns: 1fr; gap: 32px; }
.pdp-photo-col { display: flex; flex-direction: column; gap: 14px; position: relative; }
.pdp-photo {
  background: #fff; border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
  display: flex; align-items: center; justify-content: center; padding: 24px;
  aspect-ratio: 4 / 5; width: 100%; overflow: hidden; position: relative;
}
/* Hover-zoom: in-place, over the same spot the photo already sits in —
   not a separate side panel. Simpler and matches the reference the
   owner pointed at (dr-spillercanada.com's own product zoom): the
   zoomed image replaces the plain one within the exact same box, panned
   to keep whatever's under the cursor centred. No side panel means no
   overlap-with-the-cart-rail or off-screen-at-narrow-widths issues. */
.pdp-zoom-overlay {
  position: absolute; inset: 0; background-repeat: no-repeat;
  opacity: 0; pointer-events: none; transition: opacity .12s ease;
}
.pdp-photo.zoomable { cursor: zoom-in; }
.pdp-photo.zoomable.zooming img { opacity: 0; }
.pdp-photo.zoomable.zooming .pdp-zoom-overlay { opacity: 1; }
/* Fixed aspect-ratio box, not a min/max-height range, so the frame is
   the exact same size for every photo in a product's gallery — some
   manufacturer galleries mix product shots with very differently-
   shaped images (tall ingredient graphics, wide banners), and without
   a fixed frame the box itself used to resize per image, which read as
   "different sized" photos when clicking through thumbnails. */
.pdp-photo img { width: 100%; height: 100%; object-fit: contain; }
.pdp-photo--none { color: var(--blue); font-size: 3rem; }
.pdp-thumbs { display: flex; flex-wrap: wrap; gap: 10px; }
.pdp-thumb {
  width: 72px; height: 72px; padding: 6px; border-radius: 10px; border: 1px solid var(--line);
  background: #fff; display: flex; align-items: center; justify-content: center; overflow: hidden;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.pdp-thumb img { max-width: 100%; max-height: 100%; object-fit: contain; }
.pdp-thumb.active { border-color: var(--blue); box-shadow: 0 0 0 2px var(--aqua-soft); }
.pdp-brand { font-size: .8rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--blue); margin-bottom: 6px; }
.pdp-meta { color: var(--muted); font-size: .92rem; margin-bottom: 14px; }
.pdp-price { font-family: var(--serif); font-size: 1.8rem; font-weight: 700; color: var(--night); margin-bottom: 18px; }
.pdp-price small { font-family: var(--sans); font-size: .6rem; font-weight: 700; color: var(--muted); text-transform: uppercase; vertical-align: middle; margin-left: 6px; }
.pdp-qty { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
.pdp-qty button { width: 32px; height: 32px; border-radius: 50%; border: 1px solid var(--line); background: #fff; font-size: 1rem; }
.pdp-add {
  display: inline-flex; align-items: center; gap: 8px; border: none; border-radius: var(--radius-pill);
  background: var(--deep); color: #fff; padding: 14px 26px; font-weight: 700; font-size: .96rem;
}
.pdp-add:hover { background: var(--deep-2); }
.pdp-add.added { background: var(--lime); color: var(--night); }
.pdp-desc { margin-top: 28px; padding-top: 24px; border-top: 1px solid var(--line); }
.pdp-desc h2 { font-size: 1.1rem; margin-top: 20px; }
.pdp-desc h2:first-child { margin-top: 0; }
.pdp-list { margin: 0; padding-left: 20px; color: var(--ink); }
.pdp-list li { margin-bottom: 4px; }
.pdp-caution {
  margin: 14px 0; padding: 10px 14px; border-radius: var(--radius);
  background: var(--coral-soft, #ffe9e3); color: var(--coral-ink); font-size: .88rem;
}
.pdp-note { font-size: .86rem; color: var(--muted); margin-top: 14px; }
.pdp-source-note {
  margin-top: 22px; padding: 14px 16px; border-radius: var(--radius); background: var(--aqua-soft);
  font-size: .82rem; color: #234b57;
}
.pdp-not-found { text-align: center; padding: 80px 20px; color: var(--muted); }
@media (min-width: 800px) {
  .pdp-grid { grid-template-columns: 1.15fr 1fr; align-items: start; gap: 40px; }
}
@media (min-width: 1024px) {
  /* .pdp has no shop-sidebar, only the fixed cart rail — needs its own
     right margin so content doesn't run under it (shop-main handles
     this on index.html, but .pdp isn't that element). */
  .pdp { margin-right: var(--rail-w); padding: 28px clamp(16px, 2vw, 28px) 72px; }
}

/* ---------- Discount code entry on checkout ---------- */
.discount-box { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); }
.discount-box label {
  display: block; font-size: .74rem; font-weight: 800; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 6px;
}
.discount-entry { display: flex; gap: 8px; }
.discount-entry input {
  flex: 1 1 0; min-width: 0; padding: 9px 12px; font-size: .9rem;
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: #fff; color: var(--ink); text-transform: uppercase;
}
.discount-entry button {
  flex: 0 0 auto; padding: 9px 18px; border: none; border-radius: var(--radius-pill);
  background: var(--deep); color: #fff; font-weight: 700; font-size: .86rem;
}
.discount-entry button:disabled { opacity: .55; }
.discount-msg { margin: 7px 0 0; font-size: .82rem; color: var(--muted); min-height: 1.1em; }
.discount-msg.ok { color: #2f7d32; }
.discount-msg.bad { color: var(--coral-ink); }
.cart-summary-row.discount-row span { color: var(--blue); }
.os-totals .os-disc td { color: var(--blue); }

/* ---------- Policy pages (returns, terms, privacy) ----------
   Long-form text needs a light background and a readable measure.
   .page-hero is dark and caps paragraphs at 640px, which suits a lead
   sentence but not pages a customer has to read through — and these are
   pages they may be reading while annoyed, so legibility matters. */
/* Generous bottom padding: the floating "Shop" pill is fixed at bottom-left
   and would otherwise sit on top of the closing paragraph. */
.policy { background: var(--sand); padding: 34px 0 96px; }
.policy-card {
  background: var(--paper); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
  padding: 30px clamp(20px, 4vw, 44px); max-width: 780px; color: var(--ink);
}
.policy-card h2 {
  font-family: var(--serif); font-size: 1.3rem; color: var(--night); margin: 32px 0 10px;
}
.policy-card > h2:first-child { margin-top: 0; }
.policy-card h3 { font-size: .97rem; color: var(--deep); margin: 20px 0 6px; }
.policy-card p, .policy-card li { line-height: 1.65; font-size: .95rem; }
.policy-card p { margin: 0 0 12px; }
.policy-card ul { margin: 0 0 14px; padding-left: 20px; }
.policy-card li { margin-bottom: 6px; }
.policy-card a { color: var(--blue); }
.policy-card strong { color: var(--night); }

/* The rule customers most often miss, so it is stated once in a box they
   cannot skim past rather than buried in a list. */
.policy-note {
  background: var(--aqua-soft); border-radius: var(--radius);
  padding: 15px 17px; margin: 0 0 20px; font-size: .92rem; color: var(--deep);
}
.policy-note p:last-child { margin-bottom: 0; }

.policy-table { width: 100%; border-collapse: collapse; margin: 0 0 16px; font-size: .92rem; }
.policy-table th, .policy-table td {
  text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--line);
}
.policy-table th { color: var(--deep); font-weight: 600; }
.policy-updated { font-size: .8rem; color: var(--muted); margin: 26px 0 0; }
.footer-legal { display: flex; flex-wrap: wrap; gap: 4px 14px; }

/* ---------- Forms (account request; reusable for checkout) ----------
   The checkout form has been relying on browser defaults, which is why it
   looks unlike the rest of the site. These are written generically so that
   form can adopt them later without a rewrite. */
.form-grid { display: grid; gap: 14px; margin: 0 0 18px; }
.form-row { display: grid; gap: 14px; grid-template-columns: 1fr 1fr; }
@media (max-width: 560px) { .form-row { grid-template-columns: 1fr; } }
.field { display: grid; gap: 5px; }
.field label { font-size: .82rem; font-weight: 600; color: var(--deep); }
.field .hint { font-size: .78rem; color: var(--muted); font-weight: 400; }
.field input, .field textarea {
  font: inherit; font-size: .95rem; color: var(--ink); background: #fff;
  border: 1px solid var(--line); border-radius: 10px; padding: 11px 13px;
  width: 100%; transition: border-color .15s ease, box-shadow .15s ease;
}
.field textarea { min-height: 84px; resize: vertical; }
.field input:focus, .field textarea:focus {
  outline: none; border-color: var(--blue); box-shadow: 0 0 0 3px var(--aqua-soft);
}
.field input[aria-invalid="true"] { border-color: var(--coral); }
.field .err { font-size: .78rem; color: var(--coral-ink); min-height: 1em; }

.form-submit {
  font: inherit; font-weight: 700; font-size: .95rem; color: #fff;
  background: var(--night); border: 0; border-radius: var(--radius-pill);
  padding: 13px 30px; cursor: pointer; min-height: 46px;
  transition: background .2s ease, transform .15s ease;
}
.form-submit:hover:not(:disabled) { background: var(--deep-2); transform: translateY(-1px); }
.form-submit:disabled { opacity: .6; cursor: default; }
.form-note { font-size: .84rem; color: var(--muted); margin: 12px 0 0; }
.form-done {
  background: var(--aqua-soft); border-radius: var(--radius);
  padding: 18px 20px; color: var(--deep); font-size: .95rem; line-height: 1.6;
}
.form-error {
  background: #fbe9e7; border-radius: var(--radius); padding: 13px 16px;
  color: var(--coral-ink); font-size: .9rem; margin: 0 0 16px;
}

/* ---------- The clinic notice on the shop page ----------
   Has to be read, not skimmed past, because it is the site's answer to both
   "why can't I see a price?" and "is this brand being resold openly online?".
   Sized to sit above the grid without pushing the products off a phone
   screen — the shop is still a catalogue first. */
.clinic-note {
  background: var(--paper); border: 1px solid var(--line); border-left: 4px solid var(--deep);
  border-radius: var(--radius); padding: 16px 20px; margin: 0 0 18px;
}
.clinic-note h2 {
  font-size: 1.05rem; margin: 0 0 6px; color: var(--night);
}
.clinic-note p { margin: 0 0 8px; font-size: .88rem; line-height: 1.55; color: var(--ink); }
.clinic-note .clinic-note-foot {
  margin: 10px 0 0; padding-top: 9px; border-top: 1px solid var(--line);
  font-size: .83rem; color: var(--muted);
}
@media (max-width: 620px) {
  .clinic-note { padding: 15px 16px; margin-bottom: 18px; }
  .clinic-note h2 { font-size: 1rem; }
  .clinic-note p { font-size: .86rem; }
}

/* Desktop inset for the clinic notice. This lives at the end of the file on
   purpose: the base .clinic-note rule is appended after the main desktop
   media query, so an override placed up there loses on source order and the
   notice slid under the fixed sidebar. */
@media (min-width: 1024px) {
  .clinic-note {
    margin-inline-start: calc(var(--rail-w) + clamp(16px, 2vw, 28px));
    margin-inline-end: calc(var(--rail-w) + clamp(16px, 2vw, 28px));
  }
}

/* ---------- Signed-out cart footer ----------
   The basket is real and full; it just has no prices in it yet. This has to
   explain that without reading as an error. */
.cart-gate {
  margin: 0 0 12px; font-size: .84rem; line-height: 1.5; color: var(--muted);
}
.cart-foot .view-cart-btn, #cart-page-out .view-cart-btn { margin-top: 8px; }
/* An empty price slot should not collapse the row it sits in. */
.price-slot:empty { display: inline-block; min-height: 1em; }

/* The signed-out price slot on a product page. Deliberately quiet — it sits
   where a large price used to, and shouting about not showing one would be
   worse than the absence. */
.pdp-price-gate {
  display: block; font-family: var(--sans); font-size: .88rem; font-weight: 400;
  line-height: 1.5; color: var(--muted); max-width: 34ch;
}
.pdp-price-gate a { font-weight: 600; white-space: nowrap; }

/* The signed-out cart footer puts its two actions side by side. Stacked full
   width they read as a sequence of steps, which they are not — they are two
   doors to the same place, and "Log in" is short enough to sit beside
   "See prices" instead of below it. */
#cart-foot-out, #cart-page-out { display: grid; gap: 8px; }
#cart-foot-out .gate-actions, #cart-page-out .gate-actions {
  display: flex; gap: 8px; align-items: stretch;
}
#cart-foot-out .gate-actions > *, #cart-page-out .gate-actions > * {
  flex: 1 1 0; width: auto; margin: 0; min-width: 0;
  display: inline-flex; align-items: center; justify-content: center;
  padding-inline: 10px; white-space: nowrap;
}

/* ---------- Form pages ----------
   The 780px measure on .policy-card exists so long prose stays readable. A
   form is not prose: capping it there left the fields squeezed into the left
   half of the page with a wide empty gutter beside them. These pages use the
   full shell instead. */
.policy-card--form { max-width: none; }
@media (min-width: 900px) {
  .policy-card--form .form-grid { max-width: 860px; }
}
/* Button and its small print on one line, rather than the note dangling
   underneath looking like an error message. */
.submit-row {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin-top: 4px;
}
.submit-row .form-submit { flex: 0 0 auto; }
.submit-row .form-note { margin: 0; flex: 1 1 260px; min-width: 0; }
/* The 640px cap on a hero paragraph suits a long lead sentence. On the two
   form pages it just left a hole in the top right, so they get a wider
   measure — still short of full width, which would be unreadable. */
.page-hero--wide p { max-width: 880px; }

/* ---------- Collection vs delivery ---------- */
.fulfil-choice { display: grid; gap: 10px; }
.fulfil-opt {
  display: flex; gap: 12px; align-items: flex-start; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius); padding: 13px 15px;
  background: #fff; transition: border-color .15s ease, background .15s ease;
}
.fulfil-opt:hover { border-color: var(--blue); }
.fulfil-opt:has(input:checked) { border-color: var(--deep); background: var(--aqua-soft); }
.fulfil-opt input { margin-top: 3px; width: 17px; height: 17px; accent-color: var(--deep); flex: 0 0 auto; }
.fulfil-opt strong { display: block; font-size: .95rem; color: var(--night); }
.fulfil-opt small { display: block; margin-top: 2px; font-size: .82rem; color: var(--muted); line-height: 1.45; }
.fulfil-opt.disabled { opacity: .55; cursor: not-allowed; }
.fulfil-opt.disabled:hover { border-color: var(--line); }
.addr-note { font-size: .8rem; color: var(--muted); margin: 6px 0 0; }
/* A secondary action that still looks like a button. Used for "Request an
   account" on the sign-in page, where the primary action is signing in. */
.form-submit--outline {
  background: #fff; color: var(--deep); border: 1px solid var(--deep);
  text-decoration: none; display: inline-flex; align-items: center;
}
.form-submit--outline:hover:not(:disabled) { background: var(--aqua-soft); color: var(--deep); }

/* The second route on the sign-in page. Set apart so it reads as "or, if
   you're new, start here" rather than as more of the sign-in instructions. */
.alt-path {
  margin-top: 30px; padding-top: 24px; border-top: 1px solid var(--line);
}
.alt-path h2 { margin-top: 0; }
/* A single email field does not need the full width of a form card. Scoped
   to .policy-card--form so it outranks that rule's 860px cap, which is set
   with the same two-class specificity. */
.policy-card--form .form-grid--narrow { max-width: 420px; }
/* Shown in place of everything else when ordering is paused. */
.cart-shut {
  background: #fdf1de; border-radius: var(--radius); padding: 12px 14px;
  color: var(--coral-ink); margin: 0;
}
/* The About link sits inside a paragraph in the footer's brand column, where
   the surrounding text is the same colour — without this it reads as ordinary
   prose and nobody clicks it. */
.footer-grid > div:first-child a:not(.brand) {
  color: #fff; text-decoration: underline; text-underline-offset: 2px;
}
.footer-grid > div:first-child a:not(.brand):hover { color: var(--aqua); }

/* ---------- Collapsible filters and notice (phones) ----------
   Both use <details> so they work with no JavaScript and keep native
   keyboard and screen-reader behaviour. The summary is hidden entirely on
   desktop, where there is room for everything and the sidebar is a fixed
   rail rather than something to fold away. */
.filter-panel > summary,
.clinic-more > summary {
  cursor: pointer; list-style: none; display: flex; align-items: center; gap: 8px;
}
.filter-panel > summary::-webkit-details-marker,
.clinic-more > summary::-webkit-details-marker { display: none; }
.filter-panel > summary::after {
  content: "▾"; margin-left: auto; font-size: .8em; transition: transform .15s ease;
}
/* Sits immediately after the words, not pushed to the far edge — pinned right
   it read as a rule across the card rather than as part of the control. */
.clinic-more > summary::after {
  content: "▾"; font-size: .75em; transition: transform .15s ease;
}
.filter-panel:not([open]) > summary::after,
.clinic-more:not([open]) > summary::after { transform: rotate(-90deg); }

.filter-summary {
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: #fff; padding: 11px 18px; font-size: .88rem; font-weight: 700;
  color: var(--deep); min-height: 44px;
}
.filter-panel.has-filters > .filter-summary { border-color: var(--deep); background: var(--aqua-soft); }

/* Has to look like something you press. It is the only route to the
   explanation on a phone, and a line of quiet blue text with a chevron at the
   far side was being read as a heading. */
/* Scoped past `.clinic-more > summary { display: flex }` above, which is the
   same two-class specificity and would otherwise stretch this to full width —
   making it look like a second copy of the filter pill directly beneath it.
   It is a help chip, not a primary control, and should size to its words. */
.clinic-more > summary.clinic-more-summary {
  display: inline-flex; align-items: center; gap: 6px; width: auto;
  font-size: .88rem; font-weight: 700; color: var(--blue);
  padding: 7px 14px; margin: 2px 0 0;
  border: 1px solid rgba(11,127,158,.35); border-radius: var(--radius-pill);
  background: rgba(11,127,158,.06); min-height: 38px;
}
.clinic-more-summary:hover { background: var(--aqua-soft); border-color: var(--blue); }
.clinic-more[open] > .clinic-more-summary { margin-bottom: 4px; }
.clinic-more > p:first-of-type { margin-top: 10px; }

@media (min-width: 901px) {
  /* Matches the chip's own selector specificity, or the shrink-wrap rule
     above wins and the control stays visible on desktop — where there is
     nothing to expand, because everything is already open. */
  .filter-panel > summary,
  .clinic-more > summary,
  .clinic-more > summary.clinic-more-summary { display: none; }
}

/* ---------- Category nav on phones ----------
   The five categories and the Cart button share a row, and the row does not
   fit: at 428px "All" sat underneath Cart, at 390px "Sets" went too. Both
   were unreachable — and "All" is the one that matters most on a catalogue
   of 190 products.

   Scrolling the nav rather than wrapping it keeps the header two rows tall,
   which is the height we just spent effort reclaiming. The Cart sits outside
   the scrolling area so it can never cover a category again, and the last
   chip keeps a little padding so it is obvious there is more to swipe to. */
@media (max-width: 900px) {
  .shop-header-inner > .cat-nav-inline {
    flex: 1 1 0; min-width: 0;
    overflow-x: auto; overflow-y: hidden;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
    scroll-snap-type: x proximity;
    /* Fades the right edge so a cut-off chip reads as "more this way"
       rather than as a rendering fault. */
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 22px), transparent);
            mask-image: linear-gradient(90deg, #000 calc(100% - 22px), transparent);
  }
  .shop-header-inner > .cat-nav-inline::-webkit-scrollbar { display: none; }
  .cat-nav-items { flex-wrap: nowrap; padding-right: 22px; }
  .cat-nav-top { white-space: nowrap; scroll-snap-align: start; }
  /* Out of the scroller and pinned, so it stops sitting on top of the nav. */
  .shop-header-inner > .cart-btn { flex: 0 0 auto; margin-left: 8px; }
}
/* Categories on one row, brands on the next. Left to wrap on its own the list
   ran "…Sets All products Dr. Spiller…", which reads as though "All products"
   and "Dr. Spiller" were one link — or worse, that All products meant all of
   Dr. Spiller's. */
.footer-brands { margin-top: 6px; }
