/*
Theme Name:   Burly Labs
Theme URI:    https://burlylabs.is/
Description:  Child theme of Astra carrying the Burly Labs design. Astra keeps
              serving WooCommerce, the age gate, AffiliateWP, Complianz and
              WPForms; this theme supplies the brand chrome and the page styles.
Author:       Burly Labs
Template:     astra
Version:      0.6.1
*/

/* ==========================================================================
   THIS FILE REPLACES A DUPLICATION BUG.

   In the HTML drafts this block is copied into all eight pages, each marked
   "SHARED with index.html - keep in sync", and nothing warns you when a copy
   drifts. One already had: coa.html had moved `.visually-hidden` out of its
   shared block into its page styles. Harmless, identical rule, but it is
   exactly how a real divergence starts.

   Here it exists ONCE. A token change is one edit.

   Extracted 2026-10-06 from about.html, which matched four other pages byte for
   byte and is therefore the canonical copy.
   ========================================================================== */

:root{
  --blue:        #00132e;   /* brand hex — rgb(0,19,46) */
  --blue-rgb:    0,19,46;
  --slate:       #35485c;
  --slate-rgb:   53,72,92;
  --blue-edge:   #1b3350;   /* hairline seams where blue meets black */
  --black:       #000000;
  --white:       #ffffff;
  --ink:         #00132e;
  --muted:       rgba(255,255,255,.66);
  --muted-ink:   rgba(0,19,46,.60);

  --font-display: 'Archivo', 'Helvetica Neue', Arial, sans-serif;
  --font-body:    'Inter', 'Helvetica Neue', Arial, sans-serif;

  --pad-section:  clamp(80px, 10vw, 140px);
  --pad-gutter:   clamp(20px, 5vw, 72px);
  --maxw:         1360px;
  --gap:          clamp(16px, 2vw, 28px);
  --radius:       2px;

  --ease: cubic-bezier(.22,.61,.36,1);

  /* about-only: the header is fixed and this page's hero is NOT vertically
     centred like the homepage's, so the first band has to clear it by hand.
     18px padding × 2 + 38px logo + 1px border = 75px. */
  --header-h: 75px;
  --ticker-h: 42px;   /* promo bar above the header; see .ticker */
  --rule: rgba(0,19,46,.16);

  /* the hero's left-to-right dissolve, and the merch card width.
     Imported with the homepage 2026-10-07; nothing else uses them. */
  --tee-card:clamp(240px,38vw,440px);
  --fade:
    rgba(0,19,46,1) 0%,
    rgba(0,19,46,1) 40%,
    rgba(6,25,51,.985) 45.14%,
    rgba(13,32,57,.95) 51.02%,
    rgba(22,41,66,.87) 56.89%,
    rgba(31,50,74,.74) 64.24%,
    rgba(38,57,80,.60) 68.65%,
    rgba(45,64,85,.38) 74.52%,
    rgba(50,69,89,.16) 79.67%,
    rgba(52,71,91,.05) 86.28%,
    rgba(53,72,92,0) 98.03%,
    rgba(53,72,92,0) 100%;
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}

/* ==========================================================================
   WOOCOMMERCE SAFETY SCOPE

   These element-level rules were written for pages that own the whole canvas.
   Applied site-wide in WordPress they reach cart, checkout and account, and
   three of them do real damage there:

     body{background:navy;color:white}      checkout becomes white-on-navy, and
                                            WooCommerce's tables and form fields
                                            assume a light ground
     button{background:none;border:0}       strips "Proceed to checkout" and
                                            "Place order" back to bare text
     a{color:inherit;text-decoration:none}  "Return to cart" and "Lost your
                                            password?" stop looking like links

   So they are scoped to `.burly-page`, a body class functions.php adds to every
   page EXCEPT WooCommerce's. Cart, checkout, account and the shop keep Astra's
   defaults until they are designed properly.

   The header, ticker and footer style their own links and buttons by class
   (.nav a, .footer-col a, .nav-toggle), so the chrome is unaffected and still
   renders correctly on WooCommerce pages.

   When the commerce pages ARE designed, do not simply delete this scope. Widen
   it deliberately, one rule at a time, testing checkout after each.
   ========================================================================== */

body.burly-page{
  margin:0;
  background:var(--blue);
  color:var(--white);
  font-family:var(--font-body);
  font-size:16px;
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img{max-width:100%}
:where(.burly-page,.site-header,.site-footer) img{display:block}
/* author-origin img{display:block} outranks the UA's [hidden]{display:none}
   (author beats user-agent whatever the specificity), so the onerror image
   fallbacks silently do nothing without this line */
[hidden]{display:none !important}
/* ⚠ :where() IS LOAD BEARING ON EVERY RULE IN THIS BLOCK, added 2026-10-06.

   These are the drafts' bare element resets (a, button, img, headings). They
   have to be scoped so they never reach a WooCommerce page, but scoping them
   the obvious way (`.burly-page a`) RAISES their specificity from 0,0,1 to
   0,2,0, which makes them beat every single-class component rule in the page
   CSS. That is not theoretical: it rendered the About page's "View Catalog"
   button navy-on-navy, 1:1 contrast, because `.burly-page a{color:inherit}`
   outranked `.btn--ink{color:var(--white)}`.

   `:where()` contributes ZERO specificity, so these land at 0,0,1, exactly as
   the bare selectors did in the drafts, while still being scoped.
   Do not "simplify" them back to plain descendant selectors. */
:where(.burly-page,.site-header,.site-footer) a{color:inherit;text-decoration:none}
:where(.burly-page,.site-header) button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
:where(.burly-page) :where(h1,h2,h3,h4,h5,h6){
  font-family:var(--font-display);font-weight:700;margin:0;
  line-height:1.04;letter-spacing:-.02em;
  /* ⚠ color:inherit is LOAD BEARING. In the HTML drafts headings inherit
     white from body. Astra sets heading colours explicitly from the
     Customizer, so without this the About h1 renders BLACK ON NAVY and is
     all but invisible. Inherit puts the section back in charge. */
  /* typography stays at 0,0,1 so page rules win, see the :where note above */
}

/* ⚠ HEADING COLOUR NEEDS ITS OWN RULE AT 0,1,0. Measured 2026-10-06: Astra's
   inline Customizer CSS carries
     h1,h2,...,.entry-content :where(h1){color:var(--ast-global-color-2)}
   at specificity 0,1,0, which beats a 0,0,1 reset. With only the :where()
   version the About h1 rendered BLACK ON NAVY. This matches Astra's 0,1,0 and
   wins on document order, because this stylesheet loads last. It still loses to
   any page rule like `.about-body h2` (0,1,1), which is what we want. */
/* ⚠ EXTENDED 2026-10-07, and the reasoning above applies to every property in
   here, not just colour. Measured against the draft at 1440: Astra was ALSO
   winning font-family (Lexend, not Archivo), font-weight (500, not 700),
   line-height (1.4 on h1 and 1.25 on h2, not 1.04) and margin-bottom (35px,
   not 0). That last pair is what the owner saw as "everything is spaced out":
   the h1 alone was 290px tall against the draft's 216px, and every h2 carried
   35px of margin the design does not have.

   The 0,0,1 :where() block above sets all of this already. It simply loses to
   Astra's 0,1,0, exactly as the colour did. Anything listed here must also stay
   listed there, so a page rule at 0,1,1 can still override it: `.research h2`
   and `.contact h2` both set their own line-height and must keep winning. */
/* ⚠ `body.` IS LOAD BEARING, and 2026-10-07 is the second time specificity has
   bitten this block. WooCommerce's own stylesheet carries
     .woocommerce-js h2{margin-bottom:0.7em}
   at 0,1,1. WooCommerce puts `woocommerce-js` on <body> SITE WIDE, not just on
   shop pages, so that rule reaches every heading on every page we style, and at
   a 50px h2 it resolved to exactly the 35px of unwanted margin. A 0,1,0 rule
   cannot beat it. `body.burly-page` matches it at 0,1,1 and wins on load order,
   because this stylesheet is enqueued after WooCommerce's.

   Page rules stay in charge: `.research h2`, `.contact h2` and `.legal h2` are
   also 0,1,1 and they live LATER in this file, so they still override. Do not
   raise this rule any further or they stop working. */
body.burly-page :where(h1,h2,h3,h4,h5,h6){
  color:inherit;
  font-family:var(--font-display);font-weight:700;
  line-height:1.04;margin:0;
}

/* ⚠ Astra styles `button` with letter-spacing:1px and text-transform:capitalize,
   and NEITHER is part of the `font` shorthand, so `font:inherit` in the 0,0,1
   reset above does not undo them. `.trust-item` is a <button> wrapping a <p>,
   and both properties inherit, so the trust copy rendered Capitalised And
   Letterspaced. Added 2026-10-07. */
body.burly-page :where(button){letter-spacing:inherit;text-transform:inherit}

/* ⚠ THE BUTTON RESET HAS TO COVER :hover AND :focus TOO. Reported 2026-10-07:
   a black box appeared behind each item in the trust section on hover. Astra's
   Customizer CSS ships
     button:focus, button:hover, .ast-button:hover, ... {
       color:var(--ast-global-color-4); background-color:var(--ast-global-color-0);
       border-color:var(--ast-global-color-0) }
   and `.trust-item` is a full width <button>, so hovering anywhere on a row
   painted the whole row dark. The 0,0,1 `background:none` reset above cannot
   reach it, because a :hover selector is 0,1,1.

   ⚠ `:where(.burly-page)` NOT `.burly-page`, and the difference is the whole
   point. `:where()` contributes ZERO, so this lands at exactly 0,1,1: it ties
   Astra and wins on load order, and it ties our own button hovers and LOSES to
   them, because they sit later in this file. `.tee-arrow:hover`,
   `.legal .wpforms-submit:hover` and `.contact .wpforms-submit:hover` all still
   work. Writing `.burly-page button:hover` would be 0,1,2 and would silently
   kill every one of them. */
:where(.burly-page) button:hover,
:where(.burly-page) button:focus{
  background:none;color:inherit;border-color:transparent;
}

:focus-visible{outline:2px solid #6FA8FF;outline-offset:3px;border-radius:1px}

.wrap{max-width:var(--maxw);margin-inline:auto;padding-inline:var(--pad-gutter)}

/* ⚠ THE `p` IN `p.eyebrow` IS LOAD BEARING, and the obvious fix was wrong.
   Astra ships `.entry-content p{margin-bottom:1.6em}` at 0,1,1, which outranks
   a bare 0,1,0 class rule on a <p>: .eyebrow lost its 20px to 17.6px and
   .papers-note gained 21.6px.

   The first attempt scoped these to `.burly-page .eyebrow`, which is 0,2,0 and
   DID beat Astra, but it also beat the page rules that are supposed to win.
   `.hero p` and `.contact p` are 0,1,1, so the hero eyebrow collapsed from
   16.56px/40px to the generic 11px/20px and the hero lost 28px of height. The
   fix tested fine against Astra and broke the design.

   `p.eyebrow` is exactly 0,1,1: it ties Astra and wins on load order, because
   this sheet is enqueued later, and it ties the page rules and LOSES to them,
   because they sit later in this same file. That is the behaviour the drafts
   have. Verified: all five eyebrows match the draft to the pixel.

   Every `.eyebrow` and `.papers-note` in all seven drafts is a <p>, checked
   2026-10-07, so the element selector costs nothing. */
p.eyebrow{
  font-family:var(--font-display);
  font-size:11px;font-weight:600;letter-spacing:.22em;text-transform:uppercase;
  color:var(--muted);margin:0 0 20px;
}
.eyebrow--ink{color:var(--muted-ink)}

.ph{
  position:relative;width:100%;height:100%;
  background:
    repeating-linear-gradient(45deg,#dfe3e8 0 12px,#d6dae0 12px 24px);
  display:flex;align-items:center;justify-content:center;
  overflow:hidden;
}
.ph::after{
  content:attr(data-label);
  font-family:var(--font-display);
  font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  color:#5a636e;text-align:center;line-height:1.7;padding:12px;
  white-space:pre-line;
}
.ph--dark{background:repeating-linear-gradient(45deg,#12263d 0 12px,#0d1f34 12px 24px)}
.ph--dark::after{color:#6e7f96}

/* ---------- header ---------- */
.visually-hidden{
  position:absolute !important;width:1px;height:1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;
}

/* ⚠ WORD WRAPPING MUST MATCH THE DRAFTS. Added 2026-10-08.
   WordPress sets `.entry-content{overflow-wrap:break-word}` as a guard against
   long URLs, and it INHERITS into every section we render. The drafts use
   `normal`, so the two disagree on how a word too wide for its box behaves:
   break-word splits it mid-word with no hyphen.

   Caught at 390 on the homepage: "Bacteriostatic Water 10ml" in a 109px
   product card rendered as "Bacteriosta / tic Water / 10ml", three lines where
   the draft gives two, which also pushed that one card's price out of line with
   the other two. Measured: 45px tall against the draft's 30px.

   Restored to `normal` at 0,2,0. Re-verified for overflow afterwards across 14
   widths from 360 to 1920 on every page, because removing a break guard is
   exactly the kind of fix that trades a bad wrap for a sideways scrollbar. */
.burly-page .entry-content{overflow-wrap:normal}

/* ---------- the OTHER promo bar ----------
   wpfront-notification-bar carries the same two messages the ticker does
   ("free shipping over $250", "same-day shipping"), so only one of them ships.

   ⚠ ADDED 2026-10-07, and it is a FIX, not a preference. functions.php used to
   suppress this bar by dequeuing its CSS and JS. That did not suppress it: the
   markup still rendered, and stripping the stylesheet is what removed its own
   position:fixed, so it dropped into normal flow and pushed the whole document
   down 50px. Hidden behind the fixed ticker, it was invisible while doing it.
   That 50px, plus Astra's 64px #primary margin, was the gap under the header
   the owner reported twice.

   display:none on the spacer removes the bar AND its reserved space. The
   plugin keeps its settings, so turning this back on is deleting one line.

   ⚠ !important IS LOAD BEARING, and is not laziness. The plugin prints
   `#wpfront-notification-bar-spacer{display:block}` in an inline <style> in the
   BODY, after this stylesheet. Same specificity (1,0,0), later in the document,
   so it wins the cascade on source order. An id selector cannot outrank it and
   there is nothing further to scope. Measured in the browser 2026-10-07. */
#wpfront-notification-bar-spacer{display:none !important}

/* ---------- promo ticker ----------
   Fixed above the fixed header, so --ticker-h has to be added to every top
   offset on the page: .site-header's own top, the page's first-section
   padding, and the mobile nav's inset. Miss one and content slides under it.
   overflow:hidden is load-bearing: the track is wider than the viewport by
   design, and without it the whole page scrolls sideways. */
.ticker{
  position:fixed;inset:0 0 auto 0;z-index:101;
  height:var(--ticker-h);
  background:var(--white);              /* WHITE since 2026-09-29, on the user's
                                           request. Was --blue-edge #1b3350, picked
                                           to sit lighter than the black header
                                           below so the two chrome bands separated.
                                           White against black does that on its own,
                                           so the hairline below is now decorative. */
  border-bottom:1px solid rgba(0,19,46,.14);
  overflow:hidden;
  display:flex;align-items:center;
}
.ticker-track{
  display:flex;flex:none;min-width:max-content;
  animation:ticker-scroll 42s linear infinite;
  will-change:transform;
}
/* readable on demand: stop it to read it */
.ticker:hover .ticker-track,
.ticker:focus-within .ticker-track{animation-play-state:paused}
.ticker-run{display:flex;align-items:center;flex:none}
.ticker-item{
  font-family:var(--font-display);
  font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink);white-space:nowrap;      /* navy on white since 2026-09-29, 18.54:1 */
  padding:0 clamp(14px,1.8vw,26px);
}
.ticker-sep{font-size:6px;color:rgba(0,19,46,.60);line-height:1}  /* 4.92:1 on white.
   .46 was 3.13:1 and the design hook flagged it 2026-09-30. It is aria-hidden and
   purely decorative, so 3:1 would arguably do, but it is a visible glyph and
   darkening it costs nothing. Don't lighten it back below .58 (4.60:1). */
@keyframes ticker-scroll{
  from{transform:translateX(0)}
  to{transform:translateX(-50%)}   /* exactly one .ticker-run */
}

/* ==========================================================================
   THE CHROME MUST STAND ON ITS OWN
   ⚠ ADDED 2026-10-07 after auditing all ten pages. The header and footer
   render on EVERY page, including shop, cart and my-account, but the brand
   typography and colour were reaching them only by inheritance from
   `.burly-page` on <body> - and `body_class` deliberately withholds that class
   from WooCommerce pages.

   So on shop, cart and account the chrome inherited Astra's defaults instead:
   the footer rendered in Hind rather than Inter, and `.footer-base` inherited
   Astra's dark body colour, which put **black text on the black footer**. The
   research-use line and the copyright were invisible there. The ticker and the
   nav also sat 4-6px taller from Astra's larger line-height.

   The chrome now sets its own font, size, line-height and colour, so it no
   longer depends on a class it does not always get. These mirror the drafts'
   `body` rule. Children that set their own values still win: everything here
   is 0,1,0 and every footer component rule is 0,1,1 or higher.

   ⚠ `.ticker` is listed separately because it is NOT inside .site-header:
   it is its own fixed element above it, so it does not inherit from the
   header and was still 6px taller on commerce pages until it was added. */
.ticker,.site-header,.site-footer{
  font-family:var(--font-body);font-size:16px;line-height:1.6;color:var(--white);
}
.site-header{
  position:fixed;inset:var(--ticker-h) 0 auto 0;z-index:100;
  background:var(--black);
  border-bottom:1px solid transparent;
  transition:border-color .3s var(--ease);
}
.site-header.is-stuck{border-bottom-color:var(--blue-edge)}
.header-inner{
  max-width:var(--maxw);margin-inline:auto;padding:18px var(--pad-gutter);
  display:flex;align-items:center;justify-content:space-between;gap:24px;
}
.logo{display:flex;align-items:center;gap:12px;flex:0 0 auto}
.logo img{height:38px;width:auto;display:block}

.nav{display:flex;align-items:center;gap:clamp(18px,2.4vw,34px)}
.nav a{
  font-family:var(--font-display);
  font-size:12px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);transition:color .2s var(--ease);
}
.nav a:hover{color:var(--white)}
.nav a[aria-current="page"]{color:var(--white)}
.nav .cart{
  display:inline-flex;align-items:center;gap:7px;color:var(--white);
  border:1px solid rgba(255,255,255,.24);padding:8px 14px;border-radius:var(--radius);
  transition:border-color .2s var(--ease), background .2s var(--ease);
}
.nav .cart:hover{border-color:rgba(255,255,255,.6);background:rgba(255,255,255,.06)}

.nav-toggle{display:none;width:40px;height:40px;align-items:center;justify-content:center}
.nav-toggle span{display:block;width:20px;height:1.5px;background:#fff;position:relative;transition:background .2s}
.nav-toggle span::before,.nav-toggle span::after{
  content:'';position:absolute;left:0;width:20px;height:1.5px;background:#fff;
  transition:transform .25s var(--ease), top .25s var(--ease);
}
.nav-toggle span::before{top:-6px}
.nav-toggle span::after{top:6px}
.nav-toggle[aria-expanded="true"] span{background:transparent}
.nav-toggle[aria-expanded="true"] span::before{top:0;transform:rotate(45deg)}
.nav-toggle[aria-expanded="true"] span::after{top:0;transform:rotate(-45deg)}

/* ---------- buttons ---------- */
.btn{
  display:inline-flex;align-items:center;gap:12px;
  font-family:var(--font-display);
  font-size:13px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  padding:17px 32px;border-radius:var(--radius);
  background:var(--white);color:var(--blue);
  transition:transform .22s var(--ease), box-shadow .22s var(--ease);
}
.btn:hover{transform:translateY(-2px);box-shadow:0 10px 30px rgba(0,0,0,.4)}
.btn .arw{transition:transform .22s var(--ease)}
.btn:hover .arw{transform:translateX(4px)}
.btn--ghost{background:transparent;color:var(--white);border:1px solid rgba(255,255,255,.3)}
.btn--ghost:hover{border-color:var(--white);box-shadow:none}

/* ---------- footer ---------- */
/* ==========================================================================
   FOOTER, rebuilt 2026-10-06 (option A, grouped columns)

   Replaces a single ~130px bar that carried all eight links in one row. The
   owner's note was "too thin and cramped"; the row of eight was the cause, so
   the fix is grouping, not just height. Shop / Company / Legal, a base bar with
   the research-use statement, and a larger logo at the owner's request
   (32px -> 52px).
   ========================================================================== */
.site-footer{background:var(--black);border-top:1px solid var(--blue-edge)}
.footer-top{
  display:grid;grid-template-columns:1.3fr 1fr 1fr 1fr;
  gap:clamp(28px,3vw,44px);padding:clamp(48px,5vw,68px) 0 0;
}
.footer-brand .logo img{height:52px;width:auto}
.footer-blurb{
  font-size:14px;color:var(--muted);line-height:1.7;
  margin:20px 0 0;max-width:38ch;
}
.footer-col h2{
  font-family:var(--font-display);font-size:11px;font-weight:700;
  letter-spacing:.18em;text-transform:uppercase;color:var(--white);
  margin:0 0 16px;line-height:1.2;
}
.footer-col a{
  display:block;font-size:14px;color:var(--muted);padding:5px 0;
  transition:color .2s var(--ease);
}
.footer-col a:hover,.footer-col a:focus-visible{color:#fff}
.footer-base{
  border-top:1px solid var(--blue-edge);margin-top:clamp(34px,3.6vw,48px);
  padding:22px 0 26px;display:flex;justify-content:space-between;
  gap:16px;flex-wrap:wrap;align-items:center;
}
/* ⚠ ALPHA .48, NOT .4, AND IT IS A CONTRAST FLOOR NOT A TASTE CALL. Measured
   2026-10-08: white at .4 over the black footer is **3.66:1** at 12px, and the
   bar for 12px body text is 4.5:1. .48 measures 4.92:1.

   This is the trap recorded at the top of this file in another form: a faint
   value is not decorative if it carries real copy, and these two lines are the
   research-use-only statement and the copyright. The research-use line is a
   compliance statement; it has to be readable. Do not dim these back down. */
.footer-ruo{font-size:12px;color:rgba(255,255,255,.48);margin:0;max-width:62ch;line-height:1.6}
.footer-legal{font-size:12px;color:rgba(255,255,255,.48);margin:0}

/* ⚠⚠ THREE COLUMNS IN ONE ROW ON PHONES, 2026-10-09, AND THIS IS SITE-WIDE.
   Owner: *"the footer — that needs to list the 'shop', 'company', 'Legal' all in
   one row with the sub words under their respective category, if they dont fit
   in one row then fit two in a row and singular one is second row"*.

   Was `1fr 1fr` at 860 and `1fr` at 460, so a 390px phone stacked all three
   categories and the footer ran to 818px. Now 538px — 280px shorter.

   MEASURED, because "if they dont fit" needed a number rather than a guess.
   The longest labels are "FDA Disclaimer" (~98px at 14px Inter) and
   "COA register". Column width is (content - 2 x 14px gap) / 3:

     390 -> 107px a column, ZERO links wrap        <- the common phone width
     360 ->  97px, only "FDA Disclaimer" wraps
     320 ->  84px, "COA register" wraps too        <- too tight

   So three-up holds to 360 and breaks by 320. The owner's own fallback takes
   over at 340: two columns, which puts SHOP and COMPANY on the first row and
   LEGAL alone on the second, exactly as described. Verified 143px columns at
   340 and 133px at 320 with nothing wrapping at either.

   The brand block keeps `grid-column:1 / -1` and stays full width above them;
   only the three categories were in scope. */
@media (max-width:860px){
  .footer-top{grid-template-columns:repeat(3,minmax(0,1fr));gap:30px 14px}
  .footer-brand{grid-column:1 / -1}
  .footer-base{justify-content:flex-start}
}
@media (max-width:460px){
  /* ⚠ this used to be the `1fr` full stack. It now RE-ASSERTS three-up, because
     it is later in the file than the 860 block and would otherwise undo it. */
  .footer-top{grid-template-columns:repeat(3,minmax(0,1fr))}
}
@media (max-width:340px){
  .footer-top{grid-template-columns:repeat(2,minmax(0,1fr))}
}

/* ==========================================================================
   MOBILE CHROME, added 2026-10-06

   ⚠ THIS BLOCK WAS MISSING FROM THE FIRST EXTRACTION AND THE HEADER COULD NOT
   COLLAPSE. The desktop chrome rules were carried over from index.html but the
   @media block was not, so `.nav-toggle{display:none}` was never overridden:
   at 390px the nav ran inline and the cart button was clipped off the right
   edge. Caught by screenshotting staging at 390, not by any markup check.

   Only the SIX chrome rules from index.html's 860px block belong here. The
   rest of that block is hero, best-sellers, merch, trust, papers and contact,
   which are page sections and belong to page templates.
   ========================================================================== */
@media (max-width:860px){
  :root{--ticker-h:38px}
  .nav{
    position:fixed;inset:calc(var(--ticker-h) + 64px) 0 auto 0;
    flex-direction:column;align-items:stretch;gap:0;
    background:#000;padding:8px var(--pad-gutter) 24px;
    transform:translateY(-8px);opacity:0;pointer-events:none;
    transition:transform .22s ease,opacity .22s ease
  }
  .nav.is-open{transform:none;opacity:1;pointer-events:auto}
  .nav a{padding:15px 0;border-bottom:1px solid rgba(255,255,255,.08);font-size:14px}
  .nav .cart{margin-top:18px;justify-content:center;border-color:rgba(255,255,255,.3)}
  .nav-toggle{display:flex}
}

/* The global reduced-motion rule, so the ticker stops for anyone who asks it
   to and stays readable by swiping. */
@media (prefers-reduced-motion:reduce){
  .ticker-track{animation:none;transform:none}
  .ticker{overflow-x:auto}
  *,*::before,*::after{animation-duration:.001ms !important;transition-duration:.001ms !important}
}

/* ==========================================================================
   WORDPRESS MENU MARKUP, added 2026-10-06

   wp_nav_menu() renders <ul><li><a>, but the HTML drafts used bare <a> inside
   .nav, so the flex row applied directly to the links. In WordPress the single
   <ul> becomes the only flex item and the links stack as list items, complete
   with bullet markers. These two rules carry the row down to the list.

   ⚠ Keep .nav .cart working: it is a SIBLING of the <ul>, rendered by
   header.php outside the menu, so it stays a flex item of .nav itself.
   ========================================================================== */
.nav ul{list-style:none;margin:0;padding:0;display:flex;align-items:center;gap:inherit}
.nav li{list-style:none;margin:0;padding:0}

@media (max-width:860px){
  .nav ul{flex-direction:column;align-items:stretch;gap:0;width:100%}
  .nav li a{display:block}
}

/* ==========================================================================
   PAGE STYLES: ABOUT, added 2026-10-06

   Lifted verbatim from about.html's post-SHARED block. Every selector is
   class based, checked before it went in, so none of it reaches a
   WooCommerce page even though this stylesheet loads everywhere.
   ========================================================================== */
/* ==========================================================================
   1 · PAGE HERO — blue, type only
   The homepage hero earns a photograph and a diagonal; this one deliberately
   doesn't compete with it.
   ========================================================================== */
.page-hero{
  position:relative;background:var(--blue);overflow:hidden;
  /* reduced 2026-10-06 on the owner's note that this band was too big:
     clamp max 108 -> 68, measured 718px tall against a 900px viewport. */
  padding-top:calc(var(--ticker-h) + var(--header-h) + clamp(44px,4.8vw,68px));
  padding-bottom:clamp(44px,4.8vw,68px);
}

/* Short-page hero. MUST stay after .page-hero: same 0,1,0 specificity, so this
   only wins by sitting later. Keeps the ticker + header reservation, which is
   not optional, and trims only the breathing room below it. */
.page-hero--compact{
  padding-top:calc(var(--ticker-h) + var(--header-h) + clamp(32px,3.4vw,48px));
  padding-bottom:clamp(32px,3.4vw,48px);
}
/* soft slate bloom from the top-right — the same slate as the homepage fade,
   at low alpha, so the two pages read as one site without repeating the
   diagonal (which is the homepage's signature, not a house pattern) */
.page-hero::before{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(120% 100% at 100% 0%,
    rgba(var(--slate-rgb),.55) 0%,
    rgba(var(--slate-rgb),.22) 42%,
    rgba(var(--slate-rgb),0)   72%);
}
.page-hero .wrap{position:relative;z-index:1}
.page-hero h1{
  font-size:clamp(34px,5.2vw,74px); /* was 38/6.4vw/92, trimmed with the padding */
  font-weight:800;letter-spacing:-.035em;text-transform:uppercase;
  max-width:22ch;
}
.page-hero .lede{
  color:var(--muted);
  font-size:clamp(16px,1.35vw,20px);
  margin:30px 0 0;
}

/* ==========================================================================
   2 · BODY — white ground, plain professional prose
   Text runs the full page width (the site's --maxw container, same grid as
   the header and footer), which is what the user asked for. See CLAUDE.md:
   that is a long measure and the trade-off is deliberate, not an oversight.
   ========================================================================== */
/* ⚠ padding-block is `--pad-section` WITH A LOWER FLOOR, 2026-10-09, on the
   owner's note that the white section's top and bottom buffers were too big on
   a phone. The token is `clamp(80px,10vw,140px)`, and 10vw only passes 80px
   above an 800px viewport — so on every phone it was a flat 80px, against 44px
   on the navy strip above it (.page-hero's bottom) and 44px on the one below
   (.compliance). This is the SAME formula with the floor dropped to 48px.

   ⚠ IDENTICAL TO THE TOKEN AT 800px AND WIDER, which is why it is not in a
   media query: above 800 both resolve to 10vw, capped at 140. Between 480 and
   800 this ramps 48 -> 80 where the token sat flat at 80, which is what keeps
   the phone value from stepping. Desktop re-measured unchanged.
   Don't swap it back to the bare token — that is the thing that was too big. */
.about-body{background:var(--white);color:var(--ink);padding-block:clamp(48px,10vw,140px)}

.about-body h2{
  clear:both;                 /* a section never starts beside the photograph */
  font-size:clamp(20px,2vw,30px);
  font-weight:700;letter-spacing:-.025em;text-transform:uppercase;
  margin:0 0 20px;
}
.about-body section + section{margin-top:clamp(48px,6vw,86px)}

/* The three core values, three-up on desktop. At the full container width a
   single column of these would run ~140 characters; three columns bring each
   to a comfortable ~45. */
.values{
  display:grid;grid-template-columns:repeat(3,1fr);
  gap:clamp(24px,3vw,52px);
  margin-top:clamp(32px,3.6vw,52px);
}
.value{border-top:2px solid var(--ink);padding-top:clamp(16px,1.8vw,24px)}
.value h3{
  font-family:var(--font-display);
  font-size:clamp(14px,1.05vw,16px);font-weight:700;
  letter-spacing:.1em;text-transform:uppercase;line-height:1.3;
  margin:0 0 12px;color:var(--ink);
}
.value p{font-size:clamp(15px,1vw,16px);line-height:1.7;margin:0}

.about-body p{
  font-size:clamp(16px,1.15vw,17.5px);
  line-height:1.75;
  color:var(--muted-ink);
  margin:0 0 1.05em;
}
.about-body p:last-child{margin-bottom:0}
.about-body strong{color:var(--ink);font-weight:600}

/* ---------- the photograph ----------
   THE REQUESTED BEHAVIOUR: text runs above the picture, wraps down its left
   side, and continues below it. That is a float, not a grid cell — and the
   <figure> must stay in the source BEFORE the paragraphs meant to wrap beside
   it. Move it after them and the text stops wrapping. */
.portrait{
  float:right;
  /* Sized against the text that wraps beside it. The wrapping run has to reach
     past the photo bottom, or the next h2 clears and leaves a dead rectangle of
     white to its left. At 34%/420px it left a ~430px void. This is now tuned to
     a shortened "What we do": closing the gap completely at 1440 would need a
     ~220px photo, which is too small to be worth it, so a band of up to ~86px
     (measured at 1280; 73px at 1440, zero at 1100 and below) is accepted. Widen this, or cut that section further, and it grows again.

     UPDATE 2026-09-30: THE BAND IS NOW CLOSED AT EVERY WIDTH, and it was the
     caption removal that did it, not a change here. Losing the <figcaption>
     took ~69px off the figure, so the wrapping run reaches past the photo
     bottom on its own. Re-measured (wrapping text bottom minus photo bottom,
     so negative means closed): 1440 -19, 1280 -5, 1100 -93, 1024 -119. The
     1280 case was the documented worst one at +86 and is now -5, i.e. it only
     just clears, so this is NOT slack to spend. Put a caption back, or widen
     this, and the band returns. */
  width:min(30%, 300px);
  margin:6px 0 clamp(20px,2.4vw,32px) clamp(26px,3.2vw,52px);
}
.portrait-media{position:relative;aspect-ratio:4/5;overflow:hidden;background:#e9ecef}
.portrait-media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.portrait figcaption{
  margin-top:14px;padding-top:12px;border-top:1px solid var(--rule);
  font-size:13.5px;line-height:1.55;color:var(--muted-ink);
}
/* UNUSED since 2026-09-30 — the <figcaption> was removed from the markup (see
   the comment in the .portrait figure for why it must not come back). Kept, not
   deleted, so a real captioned photograph is a markup-only change. */
.portrait figcaption b{
  display:block;
  font-family:var(--font-display);font-size:12px;font-weight:700;
  letter-spacing:.1em;text-transform:uppercase;color:var(--ink);
  margin-bottom:4px;
}

/* ==========================================================================
   3 · COMPLIANCE — slim blue strip, small print
   ⚠ NOT LEGAL COPY. Draft wording only — see the markup comment.
   ========================================================================== */
.compliance{background:var(--blue);border-top:1px solid var(--blue-edge);padding-block:clamp(44px,5vw,72px)}
.compliance-grid{display:grid;grid-template-columns:auto 1fr;gap:clamp(24px,4vw,64px);align-items:start}
.compliance h2{
  font-size:13px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;
  color:var(--white);white-space:nowrap;padding-top:2px;
}
.compliance p{margin:0 0 14px;color:var(--muted);font-size:14px;line-height:1.7;max-width:90ch}
.compliance p:last-child{margin-bottom:0}
.compliance .more{margin-top:18px}
.compliance .more a{color:#fff;border-bottom:1px solid rgba(255,255,255,.35)}
.compliance .more a:hover{border-bottom-color:#fff}

/* ==========================================================================
   4 · CLOSING — white ground, one line and the two calls to action
   ========================================================================== */
/* ⚠ padding-block floor dropped 84px -> 48px on 2026-10-09, owner: *"are we
   also able decrease the top and bottom buffer of the white section on that
   page?"* — meaning THIS section, the one reading "No batch is listed without
   its paperwork." (It was first read as `.about-body`; both are white and both
   are on About. If someone asks about "the white section" again, ask which.)

   Same shape as the `.about-body` change: the multiplier and cap are untouched,
   only the floor moves, so this is IDENTICAL TO THE OLD VALUE AT 764px AND
   WIDER, where 11vw passes 84px. Below that it was a flat 84px on every phone,
   on a block whose content is only ~218px tall — more than half the section was
   padding. Measured at 390: section 386px -> 314px. */
.closing{background:var(--white);color:var(--ink);padding-block:clamp(48px,11vw,150px);text-align:center}
.closing h2{
  font-size:clamp(26px,5vw,72px);
  font-weight:800;letter-spacing:-.04em;line-height:1.05;text-transform:uppercase;
}
.closing h2 span{display:block}
.closing h2 span:last-child{color:rgba(0,19,46,.40)}
.closing .actions{
  display:flex;gap:16px;justify-content:center;flex-wrap:wrap;
  margin-top:clamp(36px,4.5vw,56px);
}
/* .btn is white-on-blue and would vanish on this ground */
.btn--ink{background:var(--blue);color:var(--white)}
.btn--ink:hover{box-shadow:0 10px 30px rgba(0,19,46,.28)}
.btn--outline-ink{background:transparent;color:var(--ink);border:1px solid rgba(0,19,46,.28)}
.btn--outline-ink:hover{border-color:var(--ink);box-shadow:none}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */
@media (max-width:860px){
  .values{grid-template-columns:1fr;gap:26px}

  .nav{
    position:fixed;inset:calc(var(--ticker-h) + 64px) 0 auto 0;
    flex-direction:column;align-items:stretch;gap:0;
    background:var(--black);border-bottom:1px solid var(--blue-edge);
    padding:8px var(--pad-gutter) 24px;
    transform:translateY(-12px);opacity:0;pointer-events:none;
    transition:transform .25s var(--ease), opacity .25s var(--ease);
  }
  .nav.is-open{transform:none;opacity:1;pointer-events:auto}
  .nav a{padding:15px 0;border-bottom:1px solid rgba(255,255,255,.08);font-size:13px}
  .nav .cart{margin-top:18px;justify-content:center;border-color:rgba(255,255,255,.3)}
  .nav-toggle{display:flex}

  .compliance-grid{grid-template-columns:1fr;gap:18px}
  .compliance h2{white-space:normal}
}

@media (max-width:700px){
  /* ⚠⚠ THE PHOTOGRAPH STAYS FLOATED ON PHONES, 2026-10-09, ON THE OWNER'S
     INSTRUCTION: *"the only thing i really want to mess with is the picture, it
     is WAY too big and needs to like a quarter of the size and have the words
     go around it like the desktop version"*.

     UNTIL 2026-10-09 this rule read:
         .portrait{float:none;width:100%;max-width:420px;margin:28px 0}
     with the note *"a 34% float at phone width leaves ~150px of text beside it,
     which is unreadable — the photograph goes full width and stops wrapping"*.
     THAT JUDGEMENT WAS NOT WRONG, IT WAS OVERRULED. The owner has seen the
     full-width version and wants the desktop wrap. Don't reinstate `float:none`
     without being asked — but do keep the measured column below, because it is
     what keeps the overruled objection from coming true.

     ⚠⚠ THE WIDTH IS WRITTEN BACKWARDS ON PURPOSE: it sizes the TEXT, not the
     picture. `min(calc(100% - 175px), 175px)` with an 18px gutter pins the
     wrapping column at **157px** and lets the photograph take whatever is left,
     up to a 175px cap. Measured: 320 -> 105x131 picture / 157px column,
     360 -> 145x181 / 157, 390 -> 175x219 / 157, 430 -> 175x219 / 194,
     560 -> 175x219 / 311, 700 -> 175x219 / 437.
     A plain `width:50%` instead gives 140px at 320 and a 122px column, which is
     about 15 characters a line — that IS the unreadable case the old note meant.

     175x219 at 390 is a quarter of the area of the 350x438 it replaced, which
     is the size the owner asked for. The narrow column only lasts the height of
     the photograph — about six lines — and then the text returns to full width.

     ⚠ The 175px cap holds right up to 700, where the desktop rule takes over at
     189px. A 14px step at the breakpoint, verified at 700/701.

     ⚠ `float:right` is restated deliberately, not by accident. It is already on
     the base rule, but the declaration this block used to carry was `float:none`
     and the whole change is its removal; spelling the float out here means the
     intent survives if anything is ever reordered above it.

     ⚠ No dead white band: the wrapping run clears the photo by 899px at 320 down
     to 308px at 700, so `.about-body h2{clear:both}` never leaves a void. (On
     desktop that clearance is only 19px at 1440 — see the base rule's note. That
     is the tight case, and it is untouched by this.) */
  .portrait{float:right;width:min(calc(100% - 175px),175px);margin-left:18px}
}

@media (max-width:560px){
  :root{--header-h:66px;--ticker-h:38px}
  .header-inner{padding:14px var(--pad-gutter)}
  .btn{width:100%;justify-content:center}
  .closing .actions{flex-direction:column}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important;scroll-behavior:auto !important;
  }
  /* the ticker stops dead in here, so let it be swiped instead of clipped */
  .ticker{overflow-x:auto;scrollbar-width:none}
  .ticker::-webkit-scrollbar{display:none}
}
/* ==========================================================================
   ASTRA CONTENT WRAPPERS, added 2026-10-06

   Astra wraps page content in  article > .entry-content  and caps the width
   for the block editor. Our sections are full-bleed and manage their own
   gutters through .wrap, so both have to be released or every band sits in a
   1200px box with 107px of Astra padding beside it.

   ⚠ SCOPED TO .burly-page ON PURPOSE. Shop, cart, checkout and account need
   Astra's container exactly as it is.
   ========================================================================== */
.burly-page .site-main article{padding:0;margin:0;border:0;background:transparent}
.burly-page .entry-content{max-width:none}
.burly-page .entry-content>*{max-width:none;margin-left:0;margin-right:0}
.burly-page .entry-header{display:none}
.burly-page .site-content .ast-container{max-width:none;padding:0}

/* ==========================================================================
   PAGE STYLES: CERTIFICATES OF ANALYSIS, added 2026-10-06

   Lifted verbatim from coa.html's post-SHARED block. 78 selectors, all
   class based, checked before it went in.

   ⚠ THIS CSS IS A CONTRACT WITH THE PLUGIN. The register markup now lives
   in TWO places: coa.html (the design source) and the burly_coa_register
   shortcode. The sub-700px card layout is a grid keyed to the cell classes
   (.product .batch .purity .datecell .cert-cell) with data-label driving the
   ::before captions, and the lookup script reads .product and .batch text.
   A change here has to be made in the plugin too, and nothing warns you.
   ========================================================================== */
/* ==========================================================================
   1 · PAGE HERO — blue, type only
   The homepage hero earns a photograph and a diagonal; this one deliberately
   doesn't compete with it.
   ========================================================================== */
/* The .page-hero family is SHARED PAGE CHROME and is already defined above, in
   the About block. Every draft page carries its own copy, so importing a second
   page's CSS re-imports it. The COA copy was removed 2026-10-06: appended later,
   it silently reverted the hero padding reduction and the band grew back.
   ⚠ When porting the next page, STRIP its .page-hero rules before appending. */

/* ==========================================================================
   2 · BODY — white ground: the register, then what a certificate covers
   ========================================================================== */
.coa-body{background:var(--white);color:var(--ink);padding-block:var(--pad-section)}
.coa-body h2{
  font-size:clamp(20px,2vw,30px);
  font-weight:700;letter-spacing:-.025em;text-transform:uppercase;
  margin:0 0 20px;
}
.coa-body section + section{margin-top:clamp(64px,8vw,112px)}
.coa-body p{
  font-size:clamp(16px,1.15vw,17.5px);line-height:1.75;
  color:var(--muted-ink);margin:0 0 1.05em;
}
.coa-body strong{color:var(--ink);font-weight:600}
.coa-body a:not(.cert){color:var(--ink);border-bottom:1px solid rgba(0,19,46,.3)}
.coa-body a:not(.cert):hover{border-bottom-color:var(--ink)}

.visually-hidden{
  position:absolute !important;width:1px;height:1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;
}

/* ---------- lookup ---------- */
.lookup{margin:clamp(26px,2.8vw,38px) 0 12px;max-width:560px}
.lookup label{
  display:block;margin-bottom:10px;
  font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:.18em;text-transform:uppercase;color:var(--muted-ink);
}
.lookup input{
  width:100%;font:inherit;font-size:17px;color:var(--ink);
  background:var(--white);border:1px solid rgba(0,19,46,.28);border-radius:var(--radius);
  padding:15px 16px;outline:none;
  transition:border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.lookup input::placeholder{color:rgba(0,19,46,.40)}
.lookup input:focus{border-color:var(--ink);box-shadow:0 0 0 3px rgba(0,19,46,.12)}
.coa-body .lookup-count{font-size:14px;margin:0 0 clamp(18px,2vw,26px)}

/* ---------- the register ----------
   One row per batch, current and sold-out alike: About promises certificates
   are "kept on file after that batch has sold out", and this is where that
   promise is kept. Sold-out rows never come off. */
/* ⚠ ASTRA DRAWS A BOX ROUND EVERY TABLE. Measured 2026-10-06 on staging:
     main.min.css  table,td,th { border:1px solid var(--ast-border-color) }
     main.min.css  td,th       { border-width:0 1px 1px 0 }
   which put a black outline round the whole register and a rule between every
   column. The register declares only two lines, the thick one under the head
   and the faint one under each row, so everything is zeroed here first and
   those two are re-declared below. Removing this reset brings the box back. */
.register,.register caption,.register thead th,.register td,.register tbody tr{border:0}
.register{width:100%;border-collapse:collapse;font-size:15px}
.register thead th{
  font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:.16em;text-transform:uppercase;color:var(--muted-ink);
  text-align:left;white-space:nowrap;
  padding:0 16px 14px;border-bottom:2px solid var(--ink);
}
.register td{
  padding:19px 16px;border-bottom:1px solid rgba(0,19,46,.12);
  vertical-align:middle;color:var(--ink);
}
.register th:first-child,.register td:first-child{padding-left:0}
.register th:last-child,.register td:last-child{padding-right:0;text-align:right}
.register tbody tr{transition:background .2s var(--ease)}
.register tbody tr:hover{background:rgba(0,19,46,.025)}
.register .product{
  font-family:var(--font-display);font-size:15px;font-weight:700;
  letter-spacing:.02em;text-transform:uppercase;
}
/* batch numbers get read aloud down a phone and typed back in; monospace keeps
   0/O and 1/I apart */
/* nowrap added 2026-10-01. A lot number is ONE token: it is what someone reads
   off a vial, and it must never break across lines. The placeholders hid this
   because SAMPLE-01 is short enough never to wrap; tested against the real
   22-product catalogue with 12-character lot numbers, BL-2410-0101 broke into
   three lines at 700px, splitting at its hyphens. Verified 1 line at every
   width from 1440 down to 360 with no overflow. `.tnum` already does this for
   purity; the batch cell never carried that class. */
.register .batch{font-family:ui-monospace,'SF Mono',Menlo,Consolas,monospace;font-size:14.5px;white-space:nowrap}
.register .tnum{font-variant-numeric:tabular-nums;white-space:nowrap}

/* ---------- collapsed register ----------
   22 products is a wall of table, so it opens showing 6 rows at full strength
   and 2 faded under them, with a button for the rest. The fade is the cue that
   the list continues; without it a flat cut reads as the end of the data.

   ⚠ The row classes are applied by the register's own script, which also owns
   the search. See the comment in that script: both decide row visibility, so
   they share one apply(). */
.register tbody tr.is-fade1{opacity:.45}
.register tbody tr.is-fade2{opacity:.18}
.register-more-wrap{margin:clamp(20px,2.2vw,30px) 0 0;text-align:center}
/* ⚠ `button.` here is the same 0,1,1 trick as `p.eyebrow`, for the same reason.
   The hover reset near the top of this file is 0,1,1 and sets `color:inherit`
   to stop Astra recolouring button text on hover. A component rule at 0,1,0
   loses to it, so on hover this button's ink faded to the inherited muted
   colour. Qualifying with the element makes it 0,1,1 too, and it wins on load
   order because it sits later in this file. Added 2026-10-07. */
button.register-more{
  font-family:var(--font-display);font-size:11px;font-weight:700;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ink);
  background:none;border:1px solid rgba(0,19,46,.28);border-radius:var(--radius);
  padding:13px 28px;cursor:pointer;
  transition:border-color .2s var(--ease),background .2s var(--ease);
}
.register-more:hover{border-color:var(--ink);background:rgba(0,19,46,.04)}
.register-more[hidden]{display:none}

/* RETIRED 2026-10-01. The Status column (In stock / Sold out pills) was
   dropped from the register on request and replaced by the Tested date.
   Stock state lives in WooCommerce; duplicating it on a hand-maintained
   page meant two places to keep right. These rules are kept, unused, so
   reinstating the column is a markup-only change. Don't reinstate without
   being asked. */
.status{
  display:inline-block;white-space:nowrap;border-radius:999px;padding:5px 11px;
  font-family:var(--font-display);font-size:10.5px;font-weight:600;
  letter-spacing:.14em;text-transform:uppercase;
}
.status--in{background:var(--blue);color:var(--white)}
.status--out{border:1px solid rgba(0,19,46,.28);color:var(--muted-ink)}

.cert{
  display:inline-flex;align-items:center;gap:8px;white-space:nowrap;
  font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ink);
  border-bottom:1px solid rgba(0,19,46,.28);padding-bottom:3px;
}
.cert:hover{border-bottom-color:var(--ink)}
.cert .arw{transition:transform .22s var(--ease)}
.cert:hover .arw{transform:translateX(3px)}

.register-empty{
  border:1px dashed rgba(0,19,46,.3);border-radius:var(--radius);
  padding:clamp(22px,2.6vw,34px);text-align:center;
}
.coa-body .register-empty p{margin:0}
.coa-body .register-note{font-size:14px;margin:clamp(18px,2vw,24px) 0 0}

/* ---------- what a certificate covers — same grid as About's .values ---------- */
.covers{
  display:grid;grid-template-columns:repeat(3,1fr);
  gap:clamp(24px,3vw,52px);margin-top:clamp(30px,3.4vw,48px);
}
.cover{border-top:2px solid var(--ink);padding-top:clamp(16px,1.8vw,24px)}
.cover h3{
  font-family:var(--font-display);
  font-size:clamp(14px,1.05vw,16px);font-weight:700;
  letter-spacing:.1em;text-transform:uppercase;line-height:1.3;
  margin:0 0 8px;color:var(--ink);
}
.coa-body .cover-spec{
  font-family:var(--font-display);font-size:12px;font-weight:600;
  letter-spacing:.12em;text-transform:uppercase;color:var(--ink);
  margin:0 0 14px;
}
.coa-body .cover p:not(.cover-spec){font-size:clamp(15px,1vw,16px);line-height:1.7;margin:0}
.coa-body .more{margin-top:clamp(28px,3vw,40px);font-size:15px}

/* ==========================================================================
   3 · COMPLIANCE — slim blue strip, small print
   ⚠ NOT LEGAL COPY. Draft wording only — see the markup comment.
   ========================================================================== */
.compliance{background:var(--blue);border-top:1px solid var(--blue-edge);padding-block:clamp(44px,5vw,72px)}
.compliance-grid{display:grid;grid-template-columns:auto 1fr;gap:clamp(24px,4vw,64px);align-items:start}
.compliance h2{
  font-size:13px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;
  color:var(--white);white-space:nowrap;padding-top:2px;
}
.compliance p{margin:0 0 14px;color:var(--muted);font-size:14px;line-height:1.7;max-width:90ch}
.compliance p:last-child{margin-bottom:0}
.compliance .more{margin-top:18px}
.compliance .more a{color:#fff;border-bottom:1px solid rgba(255,255,255,.35)}
.compliance .more a:hover{border-bottom-color:#fff}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */
@media (max-width:860px){
  .covers{grid-template-columns:1fr;gap:26px}

  .nav{
    position:fixed;inset:calc(var(--ticker-h) + 64px) 0 auto 0;
    flex-direction:column;align-items:stretch;gap:0;
    background:var(--black);border-bottom:1px solid var(--blue-edge);
    padding:8px var(--pad-gutter) 24px;
    transform:translateY(-12px);opacity:0;pointer-events:none;
    transition:transform .25s var(--ease), opacity .25s var(--ease);
  }
  .nav.is-open{transform:none;opacity:1;pointer-events:auto}
  .nav a{padding:15px 0;border-bottom:1px solid rgba(255,255,255,.08);font-size:13px}
  .nav .cart{margin-top:18px;justify-content:center;border-color:rgba(255,255,255,.3)}
  .nav-toggle{display:flex}

  .compliance-grid{grid-template-columns:1fr;gap:18px}
  .compliance h2{white-space:normal}
}

/* Under 700 each row becomes a card: product across the top, batch and purity
   two-up, then the received date beside the certificate link.

   MOVED 620 -> 700 on 2026-10-06, and the old comment here called it: it warned
   that "a real batch number longer than the samples would tip it over" and said
   to re-probe if columns or product names grew. All three grew at once. The
   table's minimum width went 561 -> 652px, from 56-character blend names,
   14-character lot numbers, a date column of real dates, and purity carried
   to three decimals. That left a dead band where the table had not yet become
   cards but no longer fitted: measured overflow of 21px at 630, 12px at 640 and
   2px at 650, clean again from 656 up.

   700 leaves ~48px of margin over the 652px minimum. Re-probe 600 to 760 and
   move this again if the content grows further. `probe 500` and `probe 1440`
   BOTH PASS EITHER SIDE OF THIS BAND, so the standard two checks would have
   missed it entirely; sweep the intermediate widths after any content change. */
@media (max-width:700px){
  .register thead{display:none}
  .register,.register tbody{display:block;width:100%}
  /* ⚠⚠ A RULE BETWEEN BATCHES, NOT A BOX ROUND EACH — CHANGED 2026-10-09.
     Owner: *"just for mobile on the coa page, can we add super light gray line
     inbetween each product so its easier to diffirentiate which coa goes with
     which product"*.

     ⚠⚠ THE OLD BOX NEVER ONCE RENDERED, AND THAT IS THE REAL FINDING. This rule
     used to read
         .register tr{...;border:1px solid rgba(0,19,46,.14);
                      border-radius:var(--radius);padding:18px;margin-bottom:12px}
     and the border was DEAD ON ARRIVAL, because the Astra table reset near the
     top of this section is

         .register,...,.register tbody tr{border:0}          (0,1,2)

     against this rule's `.register tr`                      (0,1,1).

     The reset wins on specificity no matter which comes first, so every batch
     ran into the next with nothing between them — which is exactly what the
     owner was reporting. Confirmed by a CSSOM walk: the computed value on a row
     was `0px none`, with only those two rules matching. THE SELECTOR HERE IS NOW
     `.register tbody tr`, which ties the reset at (0,1,2) and wins on order.
     Don't shorten it back to `.register tr`.

     A rule rather than the box, because that is what was asked for and because
     it matches the desktop table, where `.register td` already carries
     `border-bottom:1px solid rgba(0,19,46,.12)` under every row. Same colour, so
     the two views now say the same thing.

     ⚠ `padding:18px 0` drops the horizontal padding the box needed. Without a
     box there is nothing to inset from, and the 18px was costing the product
     column 36px it badly wants: the longest name, "BPC-157 10mg, TB-500 10mg,
     GHK-Cu 50mg (Glow Blend 70mg)", goes from three lines to two. Cells now line
     up with the page gutter at x=20 like everything else on the page. */
  .register tbody tr{
    display:grid;grid-template-columns:1fr 1fr;gap:14px 18px;
    grid-template-areas:"product product" "batch purity" "date cert";
    border:0;border-bottom:1px solid rgba(0,19,46,.12);
    border-radius:0;
    padding:18px 0;margin-bottom:0;
  }
  /* Closes the list at the top, where the hidden thead used to carry the 2px
     rule that marks the head of the register on desktop.
     ⚠ ON THE TBODY, NOT ON `tr:first-child`, and that was caught by testing the
     search rather than the default view. The register's script hides rows with
     `display:none`, so after a filter THE FIRST VISIBLE ROW IS USUALLY NOT
     `:first-child` — searching "BPC" left three rows showing, none of them the
     first child, and the top line vanished. A border on the tbody draws at the
     top of whatever is visible. It also disappears correctly in the no-results
     state, because the script hides the whole table there, not just the rows. */
  .register tbody{border-top:1px solid rgba(0,19,46,.12)}
  .register tbody tr:hover{background:none}
  .register td{display:block;border:0;padding:0 !important;text-align:left !important}
  .register td[data-label]::before{
    content:attr(data-label);display:block;margin-bottom:5px;
    font-family:var(--font-display);font-size:10px;font-weight:600;
    letter-spacing:.16em;text-transform:uppercase;color:var(--muted-ink);
  }
  /* Product owns the top line of the card. It used to share it with the status
     pill, which needed no label; the Tested date does need one, so it gets its
     own row rather than sitting unlabelled beside the product name. */
  .register .product{grid-area:product;align-self:center}
  .register .product::before{display:none !important}
  .register .datecell{grid-area:date}
  .register .batch{grid-area:batch}
  .register .purity{grid-area:purity}
  /* sits on the Tested value baseline, not floating between its label and value */
  .register .cert-cell{grid-area:cert;align-self:end;justify-self:start}
}

@media (max-width:560px){
  :root{--header-h:66px;--ticker-h:38px}
  .header-inner{padding:14px var(--pad-gutter)}
  .footer-inner{flex-direction:column;align-items:flex-start;gap:22px}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important;scroll-behavior:auto !important;
  }
  /* the ticker stops dead in here, so let it be swiped instead of clipped */
  .ticker{overflow-x:auto;scrollbar-width:none}
  .ticker::-webkit-scrollbar{display:none}
}

/* ==========================================================================
   LEGAL PAGES - FDA Disclaimer, Terms, Privacy Policy, Refunds
   Ported 2026-10-07. One rule set serves all four: Terms and Privacy were
   byte-identical, FDA was a subset, and Refunds adds only the WPForms block.

   ⚠ The .page-hero rules each draft carried were STRIPPED before this was
   appended. Every draft page ships its own copy, and the copy in these four
   still had the ORIGINAL hero padding (clamp max 108) and h1 size (clamp max
   92). Appending them would have silently undone the reduction the owner
   asked for, which is exactly how the gap came back the first time.

   ⚠ The Refunds form is the real [wpforms id="2338"] shortcode, not the
   draft's markup. The draft carried a hand-written mock of WPForms output
   with a SWAP comment on it; shipping that would have put a form on the page
   that silently does nothing when submitted. These .wpforms-* rules exist to
   style the shortcode's real output.
   ========================================================================== */
/* ==========================================================================
   2 · BODY - same full-width measure and type as about.html
   ========================================================================== */
.legal{background:var(--white);color:var(--ink);padding-block:var(--pad-section)}

.legal h2{
  font-size:clamp(18px,1.7vw,26px);
  font-weight:700;letter-spacing:-.02em;text-transform:uppercase;
  margin:0 0 20px;
}
.legal section + section{margin-top:clamp(44px,5.4vw,78px)}

/* ---------- SHORT PAGES: Refunds and Contact ----------
   Added 2026-10-07 on the owner's note that Refunds was too spaced out.

   The .legal rhythm above is built for Terms and Privacy, which run to
   thousands of words and need the air. Refunds and Contact are one line of
   copy and a form, so the same padding reads as a mostly empty screen: Refunds
   measured 1846px tall for about 700px of content.

   These are modifiers, not changes to .legal, because the long legal pages
   still want the original spacing. Apply BOTH classes in the markup:
   `class="legal legal--compact"` and `class="page-hero page-hero--compact"`. */
.legal--compact{padding-block:clamp(48px,5vw,76px)}
.legal--compact section + section{margin-top:clamp(24px,2.6vw,36px)}
.legal--compact .refund-form,
.legal--compact .page-form{margin-top:clamp(18px,2vw,28px)}

.legal p{
  font-size:clamp(16px,1.15vw,17.5px);
  line-height:1.75;
  color:var(--muted-ink);
  margin:0 0 1.05em;
}
.legal p:last-child{margin-bottom:0}
.legal strong{color:var(--ink);font-weight:600}
.legal a{color:var(--ink);font-weight:500;border-bottom:1px solid rgba(0,19,46,.3)}
.legal a:hover{border-bottom-color:var(--ink)}

/* the opening statement carries the most weight, so it is set larger. This is
   typographic emphasis only: no summary or plain-language restatement is added
   anywhere on this page, because a summary that drifts from the operative text
   is worse than no summary. */
.legal .lead p{
  font-size:clamp(17px,1.35vw,20px);
  line-height:1.65;color:var(--ink);
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */
@media (max-width:860px){
  .nav{
    position:fixed;inset:calc(var(--ticker-h) + 64px) 0 auto 0;
    flex-direction:column;align-items:stretch;gap:0;
    background:var(--black);border-bottom:1px solid var(--blue-edge);
    padding:8px var(--pad-gutter) 24px;
    transform:translateY(-12px);opacity:0;pointer-events:none;
    transition:transform .25s var(--ease), opacity .25s var(--ease);
  }
  .nav.is-open{transform:none;opacity:1;pointer-events:auto}
  .nav a{padding:15px 0;border-bottom:1px solid rgba(255,255,255,.08);font-size:13px}
  .nav .cart{margin-top:18px;justify-content:center;border-color:rgba(255,255,255,.3)}
  .nav-toggle{display:flex}
}

@media (max-width:560px){
  :root{--header-h:66px;--ticker-h:38px}
  .header-inner{padding:14px var(--pad-gutter)}
  .footer-inner{flex-direction:column;align-items:flex-start;gap:22px}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important;scroll-behavior:auto !important;
  }
  /* the ticker stops dead in here, so let it be swiped instead of clipped */
  .ticker{overflow-x:auto;scrollbar-width:none}
  .ticker::-webkit-scrollbar{display:none}
}

/* Terms and Privacy both carry lists; the FDA page had none, so these rules
   live here rather than in the block copied from it. */
.legal ul{margin:0 0 1.15em;padding-left:1.35em}
.legal li{
  font-size:clamp(16px,1.15vw,17.5px);
  line-height:1.75;color:var(--muted-ink);margin:0 0 .45em;
  padding-left:.25em;
}
.legal li::marker{color:rgba(0,19,46,.35)}

/* ---------- refund form ----------
   WPForms class names, as on the homepage, so replacing this block with the
   [wpforms] shortcode inherits everything. The homepage form sits on --blue;
   this one is on white, so every colour is inverted. */
.refund-form,.page-form{max-width:640px;margin-top:clamp(30px,3.6vw,48px)}
.legal .wpforms-container{width:100%}
.legal .wpforms-field{margin-bottom:22px}
.legal .wpforms-field-label{
  display:block;margin-bottom:9px;
  font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:.16em;text-transform:uppercase;color:var(--muted-ink);
}
.legal .wpforms-required-label{color:#B4402F;margin-left:3px}
.legal .wpforms-field input[type="text"], .legal .wpforms-field input[type="email"], .legal .wpforms-field input[type="tel"], .legal .wpforms-field textarea{
  width:100%;padding:15px 18px;
  background:#fff;
  border:1px solid rgba(0,19,46,.22);
  border-radius:var(--radius);
  color:var(--ink);font-family:var(--font-body);font-size:15px;
  transition:border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.legal .wpforms-field input::placeholder, .legal .wpforms-field textarea::placeholder{color:rgba(0,19,46,.38)}
.legal .wpforms-field input:focus, .legal .wpforms-field textarea:focus{
  outline:none;border-color:var(--ink);box-shadow:0 0 0 3px rgba(0,19,46,.08);
}
.legal .wpforms-field textarea{resize:vertical;min-height:150px;line-height:1.6}
.legal .wpforms-submit-container{margin-top:30px}
.legal .wpforms-submit{
  font-family:var(--font-display);font-size:13px;font-weight:700;
  letter-spacing:.16em;text-transform:uppercase;
  background:var(--blue);color:var(--white);
  padding:17px 38px;border:0;border-radius:var(--radius);cursor:pointer;
  transition:transform .22s var(--ease), box-shadow .22s var(--ease);
}
.legal .wpforms-submit:hover{transform:translateY(-2px);box-shadow:0 10px 30px rgba(0,19,46,.28)}
@media (max-width:560px){ .legal .wpforms-submit{width:100%} }


/* ⚠ WPForms' OWN BOX SPACING, reset 2026-10-07. The plugin ships
     .wpforms-container .wpforms-field{padding:15px 0}   (0,2,0)
     .wpforms-container{margin-bottom:26px}              (0,1,0)
   The drafts' mock markup had neither, so our field rhythm (margin-bottom:22px)
   was designed on top of nothing and the real form came out 146px taller: 30px
   of padding on each of four fields, plus the container's 26px.

   That is most of why the homepage contact section measured 1026px against the
   draft's 749. The remainder is honest - the real form has a Phone field the
   mock did not, and the Name field splits into First/Last with sublabels.

   Scoped per form, like every other .wpforms rule in this file. The field rule
   is 0,3,0 so it clears the plugin's 0,2,0 outright rather than relying on
   load order. */
.legal .wpforms-container .wpforms-field,
.contact .wpforms-container .wpforms-field{padding:0}
.legal .wpforms-container,
.contact .wpforms-container{margin-bottom:0}

/* ==========================================================================
   HOMEPAGE - hero, best sellers, merch, trust, research, statement, contact
   Ported from index-tshirt.html 2026-10-07. That is the file that SHIPS; the
   parked index.html keeps the hats and cut-offs rack.

   The draft's own chrome rules were NOT imported. 12 responsive rules were
   dropped because the theme already owns them, and six of those were the
   draft's mobile .nav rules, which would have reverted the header fix.

   ⚠ THE SOURCE SLICE ENDS AT LINE 824, NOT 825. Line 825 opens the FOOTER
   section's banner comment. Slicing at 825 carried an opening comment marker
   in with no closing one, and that dangling comment then swallowed the
   max-width 860px media query opener on the line after it. Every mobile rule
   applied at EVERY width, and the research grid rendered one column wide on
   desktop. A brace count does NOT catch this, because braces inside a comment
   balance either way. Check comment markers when slicing CSS, not just braces.
   ========================================================================== */
/* ==========================================================================
   1 · HERO — blue on the left, dissolving across the middle into the slideshow
   ========================================================================== */
.hero{
  position:relative;
  min-height:min(92vh, 860px);
  background:var(--blue);
  display:flex;align-items:center;
  overflow:hidden;
}
/* starts left of the 2/8 mark so there's image under the fade at the top edge */
.hero-media{
  position:absolute;inset:0 0 0 22%;
  z-index:1;
}
.hero-slide{
  position:absolute;inset:0;
  opacity:0;transition:opacity 1.1s var(--ease);
}
.hero-slide.is-active{opacity:1}
.hero-slide .ph{height:100%}
/* real photo layers over its placeholder; if the file isn't there yet the
   inline onerror hides it and the labelled placeholder shows through */
.hero-slide img,
.trust-slide img,
.tile-media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* the tall tile's media had no positioning context of its own; the wide ones
   already did. Needed so a dropped-in photo can cover the placeholder. */
.tile-media{position:relative}

/* No hero slide carries a framing transform. Slide 1 is the vial lineup, which is
   composed for this slot (canvas matches the hero media box, group pushed right
   past the slate fade). The mirror+zoom that once lived here was tuned to the
   microscope photo and does not belong on any of the current images. */
/* ==========================================================================
   THE SLATE FADE
   The blue edge is a diagonal: it sits at 2/8 of the width at the top of the hero
   and 4/8 at the bottom, so it travels 2/8 across while spanning the full height.

   CHANGED 2026-09-21 on request ("make the fade more up and down"). It was
   2/8 -> 5/8: a 3/8 travel at 56/53/62deg, solid to 45.5%, corner at 72.7%.
   Halving the travel stands the boundary up without losing the diagonal.

   Write d for the travel (4/8 - 2/8 = 2/8) and f for the bottom fraction (4/8).
   The gradient must run PERPENDICULAR to the boundary, so angle = atan(H / d.W).
   Get that right and the boundary lands at f/(1+d) of the gradient axis at every
   hero size (H cancels out of the algebra), so only the ANGLE tracks the aspect
   ratio, never the stops.

   ANGLES COME FROM THE HERO'S REAL HEIGHT, and it has grown: measured 2026-09-21
   the hero is 1024x840, 1440x844, 1728x867, where the old angle set still assumed
   1024x719 and 1440x800. That stale assumption was putting the boundary a couple
   of degrees off at the narrow end. Re-measure the hero and recompute if the hero
   copy or the slide box ever changes height. Verified by rendering the gradient
   alone over white and sampling the PNG: travel came out 0.251 / 0.249 / 0.243
   against a 0.250 target. Note the edge reads ~2% right of nominal because the
   first stops are near-flat by design; judge the TRAVEL, not the absolute x.

   Changing the fractions is a THREE-PART edit: recompute the angle, move the
   solid-blue stop to f/(1+d), and rescale the rest into the space between that
   and 1/(1+d), where the bottom-right corner lands. Do one without the others
   and the boundary ends up in the wrong place.
   ========================================================================== */
.hero::before{
  content:'';position:absolute;inset:0;z-index:2;pointer-events:none;

  /* One stop list, three angles. Solid to 40% (the boundary), then an eased
     roll-off: nearly flat for the first few percent so there is no hard line,
     then falling away and interpolating blue -> slate, clear by 86%.
     It has to be nearly clear by 80%, because that is where the hero's
     bottom-right corner lands on this axis. */
  --fade:
    rgba(0,19,46,1) 0%,
    rgba(0,19,46,1) 40%,
    rgba(6,25,51,.985) 45.14%,
    rgba(13,32,57,.95) 51.02%,
    rgba(22,41,66,.87) 56.89%,
    rgba(31,50,74,.74) 64.24%,
    rgba(38,57,80,.60) 68.65%,
    rgba(45,64,85,.38) 74.52%,
    rgba(50,69,89,.16) 79.67%,
    rgba(52,71,91,.05) 86.28%,
    rgba(53,72,92,0) 98.03%,
    rgba(53,72,92,0) 100%;

  background:linear-gradient(67deg, var(--fade));
}
@media (min-width:1600px){ .hero::before{background:linear-gradient(64deg, var(--fade))} }
@media (max-width:1150px){ .hero::before{background:linear-gradient(73deg, var(--fade))} }

/* stacked layout only — see the mobile block at the bottom */
.hero-media::after{content:'';position:absolute;inset:0;pointer-events:none}
.hero-inner{
  position:relative;z-index:3;
  max-width:var(--maxw);margin-inline:auto;width:100%;
  padding:calc(160px + var(--ticker-h)) var(--pad-gutter) 90px;
}
.hero-copy{max-width:min(48ch, 560px)}
.hero h1{
  font-size:clamp(48px, 7.2vw, 108px);
  font-weight:800;letter-spacing:-.035em;text-transform:uppercase;
  margin-bottom:26px;
}
.hero p{
  color:var(--muted);font-size:clamp(15px,1.15vw,18px);
  max-width:44ch;margin:0 0 40px;
}
.btn{
  display:inline-flex;align-items:center;gap:12px;
  font-family:var(--font-display);
  font-size:13px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  padding:17px 32px;border-radius:var(--radius);
  background:var(--white);color:var(--blue);
  transition:transform .22s var(--ease), box-shadow .22s var(--ease);
}
.btn:hover{transform:translateY(-2px);box-shadow:0 10px 30px rgba(0,0,0,.4)}
.btn .arw{transition:transform .22s var(--ease)}
.btn:hover .arw{transform:translateX(4px)}
.btn--ghost{background:transparent;color:var(--white);border:1px solid rgba(255,255,255,.3)}
.btn--ghost:hover{border-color:var(--white);box-shadow:none}

.hero-dots{
  position:absolute;z-index:3;left:var(--pad-gutter);bottom:48px;
  display:flex;gap:10px;
}
.hero-inner .hero-dots{position:static;margin-top:56px}
/* padding:0 added 2026-10-07: Astra gives every button 10px, the drafts
   leave the UA default. These are sized by width/height, so 0 is correct. */
.dot{padding:0;
  width:34px;height:3px;background:rgba(255,255,255,.26);border-radius:2px;
  transition:background .3s var(--ease);
}
.dot[aria-current="true"]{background:var(--white)}

/* ==========================================================================
   2 · BEST SELLERS — ranked product cards, between the hero and the merch line
   --------------------------------------------------------------------------
   Ground is --blue and the merch section directly below is ALSO --blue, so the
   1px border-bottom is the only thing marking the seam between them. Same job
   --blue-edge already does at the merch/trust boundary. Don't remove it.

   ⚠ These are deliberately NOT the white .tile component. The merch rack is
   immediately below, and three white product tiles stacked above three more
   white product tiles read as one undifferentiated rack. Instead the cards are
   translucent panels LIGHTER than their ground, which is the same rule that
   stopped the research cards reading as holes cut in the section.
   ========================================================================== */
.best{
  background:var(--blue);
  border-bottom:1px solid var(--blue-edge);   /* the seam against the merch band */
  padding-block:clamp(54px,6vw,86px);         /* the research band's tighter rhythm */
}
.best-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(20px,3vw,40px)}
.best-card{
  position:relative;
  display:flex;flex-direction:column;
  background:rgba(255,255,255,.055);
  border-radius:var(--radius);overflow:hidden;
  color:var(--white);
  transition:transform .3s var(--ease), box-shadow .3s var(--ease), background .3s var(--ease);
}
.best-card:hover{
  transform:translateY(-6px);
  background:rgba(255,255,255,.085);
  box-shadow:0 18px 44px rgba(0,0,0,.38);
}
/* Removed 2026-09-22 ("delete the numbers"): a .best-rank numeral per card,
   absolutely positioned top-left, Archivo 800 at clamp(64px,11vw,178px) in
   rgba(255,255,255,.13), sitting BEHIND the vial so it read as part of the
   panel rather than a badge on it. Order is what expresses the ranking now.
   Going with it: .best-card:has(.ph) .best-rank, which brought the numeral
   forward and flipped it to ink on cards showing a placeholder, since an opaque
   placeholder buried a numeral designed to sit behind transparent art.
   Don't reinstate without being asked. */
/* object-fit is CONTAIN, not cover: these are transparent cut-outs and cover
   would crop the vial. There is also no .ph placeholder behind them, and that
   is deliberate rather than an oversight: a pale placeholder shows straight
   THROUGH a transparent PNG. See assets/img/README.md. */
/* ⚠ This ratio is tied to the COLUMN COUNT, and the pair has been retuned
   twice. The band has to stay "as big as the research paper section" (842px at
   1440) while the vials stay small, and the card body is a fixed ~129px, so the
   media box is the only real lever.

     3 columns, card 379 wide  ->  aspect-ratio 1/1   (media 379)
     2 columns, card 588 wide  ->  aspect-ratio 13/8  (media 362)

   Both land the vial at ~135px wide, which is the size that was signed off.
   Note the card width: .wrap's 1360px max INCLUDES its gutters, so the usable
   row is 1216px at 1440, not 1360. Two columns is 588 each, not 660.
   Change the column count and you MUST recompute this, or the cards get taller
   as they get wider and the band balloons. Measure both sections afterwards
   rather than eyeballing the card. */
.best-media{
  position:relative;z-index:1;
  aspect-ratio:1/1;width:100%;
}
/* A placeholder here has to be absolutely positioned like the image is, and for
   the same reason: in flow with height:100% it hits the percentage-height cycle
   and collapses to zero. Same fix .tile--wide .tile-media .ph already uses. */
.best-media .ph{position:absolute;inset:0}
/* ⚠ The image is absolutely positioned, and that is load-bearing rather than
   decorative. An in-flow child with height:100% inside an aspect-ratio box is a
   percentage-height cycle: the height resolves to auto, the img falls back to
   its natural 352x900 ratio, and the box grows to fit it. Measured when this
   section was built: the media box came out 379x862 instead of 379x505 and the
   vial filled the card edge to edge. Taking the img out of flow breaks the
   cycle, so aspect-ratio alone decides the height.
   The padding sits ON THE IMG, not on .best-media: an absolutely positioned
   child is placed against the PADDING box, so padding on the parent would not
   inset it. box-sizing:border-box is global, so the img's own padding shrinks
   the content box that object-fit measures against. */
.best-media img{
  position:absolute;inset:0;
  width:100%;height:100%;
  padding:clamp(14px,1.8vw,26px) clamp(14px,1.8vw,26px) 0;
  object-fit:contain;          /* NOT cover: cover crops a transparent cut-out */
  object-position:center bottom;
}
.best-body{
  position:relative;z-index:1;margin-top:auto;
  padding:clamp(14px,1.5vw,18px) clamp(14px,1.6vw,22px) clamp(16px,1.8vw,22px);
  display:flex;flex-direction:column;gap:5px;
}
.best-name{
  font-family:var(--font-display);font-size:clamp(14px,1.3vw,19px);
  font-weight:700;letter-spacing:-.01em;text-transform:uppercase;
}
.best-price{font-size:14px;font-weight:600;color:var(--muted)}
.best-cta{
  margin-top:9px;
  font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:.16em;text-transform:uppercase;color:var(--muted);
  display:flex;align-items:center;gap:8px;
  transition:color .2s var(--ease);
}
.best-card:hover .best-cta{color:var(--white)}
.best-card:hover .best-cta .arw{transform:translateX(4px)}
.best-cta .arw{transition:transform .22s var(--ease)}
/* Removed 2026-09-22: .best-note, the line under the rack pointing at the COA
   register. See the HTML for what it said. .papers-note under the research rack
   is a different thing and stays: that one carries a compliance statement. */

/* ==========================================================================
   2 · MERCH — white tiles on the blue ground
   ========================================================================== */
/* tighter than --pad-section on purpose: the whole section has to fit in one
   viewport, so the tall tile is square rather than 2:3 and the padding is cut */
.merch{background:var(--blue);padding-block:clamp(40px,3.6vw,52px)}
/* shared with .best-head above it, so the two section headings sit on exactly
   the same rhythm. Index-only CSS, not part of the SHARED chrome block. */
.merch-head,.best-head{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;margin-bottom:clamp(20px,2.2vw,32px);flex-wrap:wrap}
.merch-head h2,.best-head h2{font-size:clamp(30px,3.6vw,50px);text-transform:uppercase;letter-spacing:-.025em}
.merch-head a,.best-head a{
  font-family:var(--font-display);font-size:12px;font-weight:600;
  letter-spacing:.16em;text-transform:uppercase;color:var(--muted);
  border-bottom:1px solid rgba(255,255,255,.24);padding-bottom:4px;
  transition:color .2s var(--ease),border-color .2s var(--ease);
}
.merch-head a:hover,.best-head a:hover{color:#fff;border-color:#fff}

/* left tall tile | right column of two.
   The tall tile's 2:3 media sets the row height; the right column stretches to
   match it and its two media boxes absorb whatever is left. That keeps the two
   sides dead level no matter how the tile text wraps — no magic numbers. */
.merch-grid{display:grid;grid-template-columns:1fr 1.4fr;gap:var(--gap);align-items:stretch}
.merch-col{display:grid;grid-template-rows:1fr 1fr;gap:var(--gap);min-height:0}
/* --------------------------------------------------------------------------
   TEE COLOURWAY RAIL — 2026-09-30, replaced the static 3-up grid the same day.
   Asked for as: the active shirt centred and un-faded, its neighbours faded
   either side, moved with arrows or by scrolling.

   ⚠ IT IS A NATIVE SCROLL CONTAINER, NOT A TRANSFORMED TRACK. That is what
   makes "scroll / click to scroll" one mechanism instead of two: trackpad,
   touch swipe, keyboard and the arrow buttons all drive the same scrollLeft,
   and scroll-snap does the landing. The JS only reads the scroll position to
   decide which card is centred — it never owns the position.

   ⚠ .merch-grid and .merch-col above are LEFT INTACT and unused. index.html
   still carries the tee + hats + cut-offs rack and is the file to come back to
   when those land.
   -------------------------------------------------------------------------- */
.tee-railwrap{position:relative}
.tee-rail{
  /* one knob for card width */
  --tee-card:clamp(240px,38vw,440px);
  display:flex;gap:clamp(16px,2vw,28px);
  overflow-x:auto;overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory;scroll-behavior:smooth;
  /* NO centring padding since 2026-09-30. It used to be
     max(0px, calc(50% - var(--tee-card)/2)), which let the first and last card
     sit centred — and that is exactly what left 388px of dead navy beside the
     first card. The ends are now flush with the content edge; only the middle
     card is truly centred. Don't reinstate without being asked. */
  padding-block:8px;                 /* room for the .tile hover lift */
  scrollbar-width:none;-ms-overflow-style:none;
}
.tee-rail::-webkit-scrollbar{display:none}
.tee-card{
  flex:0 0 var(--tee-card);scroll-snap-align:center;
  /* .34 not lower: these stay real links to a real product, so they have to
     remain legible, not decorative. Measured on --blue ground, the white tile
     at .34 still clears 3:1 against it. */
  opacity:.34;transform:scale(.93);
  transition:opacity .45s var(--ease), transform .45s var(--ease);
}
.tee-card.is-active{opacity:1;transform:none}
/* a faded card is clickable, so it has to look it: lift the fade on hover and
   on keyboard focus inside it. Without this the only affordance is the cursor. */
.tee-card:not(.is-active):hover,
.tee-card:not(.is-active):focus-within{opacity:.66;transform:scale(.955)}
/* ⚠ The ends need their OWN snap alignment. With `center` on all three and no
   padding, the first card's snap position is off the left of the scrollport,
   i.e. unreachable, and a mandatory snap then parks on card 2 instead — card 1
   becomes impossible to swipe back to. start/end make both ends reachable. */
.tee-card:first-child{scroll-snap-align:start}
.tee-card:last-child{scroll-snap-align:end}
/* the lift lives on .tile, which is a CHILD of .tee-card, so the two transforms
   never fight each other */
button.tee-arrow{padding:0;
  position:absolute;top:calc(50% - 24px);z-index:3;
  width:48px;height:48px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  background:rgba(0,19,46,.74);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
  border:1px solid rgba(255,255,255,.38);color:var(--white);
  font-size:19px;line-height:1;cursor:pointer;
  transition:background .25s var(--ease), border-color .25s var(--ease), opacity .25s var(--ease);
}
.tee-arrow:hover:not(:disabled){background:rgba(0,19,46,.92);border-color:#fff}
.tee-arrow:focus-visible{outline:2px solid var(--white);outline-offset:3px}
/* ⚠ Disabled arrows HIDE rather than dim. With the ends flush, the arrow at a
   disabled end sits over the ACTIVE card instead of over empty ground, so a
   dimmed-but-present button would be a grey disc parked on the product. It only
   ever disappears at an end it cannot move toward, so nothing is lost. */
.tee-arrow:disabled{opacity:0;pointer-events:none}
.tee-arrow--prev{left:0}
.tee-arrow--next{right:0}

.tile{
  display:flex;flex-direction:column;min-height:0;
  background:var(--white);color:var(--ink);
  border-radius:var(--radius);overflow:hidden;
  transition:transform .3s var(--ease), box-shadow .3s var(--ease);
}
.tile:hover{transform:translateY(-6px);box-shadow:0 20px 50px rgba(0,0,0,.45)}
.tile-media{width:100%;background:#eef1f4}
.tile--tall .tile-media{aspect-ratio:1/1}         /* sets the row height */
.tile--wide .tile-media{flex:1;min-height:0}      /* fills what's left (~3:1.3) */
.tile--wide .tile-media .ph{position:absolute;inset:0}
.tile--wide .tile-media{position:relative}
.tile-body{padding:17px 22px 0;display:flex;align-items:baseline;justify-content:space-between;gap:16px}
.tile-name{font-family:var(--font-display);font-size:clamp(15px,1.3vw,19px);font-weight:700;letter-spacing:-.01em;text-transform:uppercase}
.tile-price{font-size:14px;font-weight:600;color:var(--muted-ink);white-space:nowrap}
.tile-cta{
  padding:9px 22px 18px;
  font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:.16em;text-transform:uppercase;color:var(--muted-ink);
  display:flex;align-items:center;gap:8px;
  transition:color .2s var(--ease);
}
.tile:hover .tile-cta{color:var(--ink)}
.tile:hover .tile-cta .arw{transform:translateX(4px)}
.tile-cta .arw{transition:transform .22s var(--ease)}

/* ==========================================================================
   3 · TRUST — brand blue, split by a white rule, ruled off at the bottom
   ========================================================================== */
.trust{
  /* Was --black until 2026-09-21, changed to brand blue on request. NOTE the
     merch section directly above is also --blue, so the hairline border-top is
     now the ONLY thing marking the boundary between them. Don't remove it. */
  background:var(--blue);
  border-top:1px solid var(--blue-edge);   /* the seam between two blue bands */
  border-bottom:1px solid var(--white);    /* the rule that separates this from Research */
  padding-block:var(--pad-section);
  position:relative;
}
/* left (picture) half is white, full-bleed to the viewport edge; the seam where
   it meets the black right half IS the line down the middle */
.trust::after{
  content:'';position:absolute;top:0;bottom:0;left:0;width:50%;
  background:var(--white);z-index:0;
}
.trust::before{
  content:'';position:absolute;top:0;bottom:0;left:50%;
  width:1px;background:var(--white);transform:translateX(-.5px);
  z-index:1;
}
.trust-grid{
  display:grid;grid-template-columns:1fr 1fr;
  gap:clamp(40px,6vw,96px);
  position:relative;z-index:2;
}
/* The picture is centred in the WHITE BOX, not in its grid column. Those are not
   the same rectangle: the white half is full-bleed to the viewport edge while the
   grid column starts at the page gutter, and the numbers list is taller than the
   picture, so a column-placed image sits off-centre both ways. Measured at 1440
   before this change: 112px of white to its left against 43px to its right, and
   141px above against 257px below.
   Taking .trust-left out of the grid and stretching it across the white half
   fixes both at once, and lets the picture size to the box instead of the column.
   `right:50%` is the seam: .wrap is centred, so the grid's own midpoint is the
   section's midpoint, which is where .trust::before draws the rule. Using that
   rather than 50vw keeps it exact when a scrollbar is present. */
.trust-left{
  position:absolute;z-index:2;
  left:calc(-1 * (var(--pad-gutter) + max(0px, (100vw - var(--maxw)) / 2)));
  right:50%;
  top:calc(var(--pad-section) * -1);
  bottom:calc(var(--pad-section) * -1);
  display:flex;align-items:center;justify-content:center;
  padding:clamp(20px,2.6vw,52px);
}
.trust-media{
  position:relative;aspect-ratio:4/3;background:#e9edf1;overflow:hidden;
  width:100%;max-width:660px;max-height:100%;
}
.trust-slide{position:absolute;inset:0;opacity:0;transition:opacity .7s var(--ease)}
.trust-slide.is-active{opacity:1}

/* .trust-left is absolutely positioned, so the list is the only in-flow grid
   child and auto-placement would drop it in column 1 — on the white half, where
   white type is invisible. Pin it. */
.trust-list{grid-column:2;display:flex;flex-direction:column;justify-content:center;gap:clamp(24px,3vw,44px)}
.trust-item{
  display:grid;grid-template-columns:auto 1fr;gap:22px;align-items:start;
  text-align:left;width:100%;padding:0;
}
.num{
  flex:0 0 auto;width:54px;height:54px;border-radius:50%;
  border:1px solid rgba(255,255,255,.45);
  display:flex;align-items:center;justify-content:center;
  font-family:var(--font-display);font-size:18px;font-weight:700;
  color:var(--white);
  /* fill and digit are NOT transitioned: the fill animates dark->light while the
     digit animates light->dark, so they cross at the same gray mid-way and the
     number disappears for ~150ms. Snapping both is the fix; the image crossfade
     and the heading colour carry the motion instead. */
  transition:border-color .3s var(--ease);
}
/* digit is --ink (the same hex as --blue) so the active pill reads as a hole
   punched in the section, which is what it was against the old black ground */
.trust-item[aria-current="true"] .num{background:var(--white);color:var(--ink);border-color:var(--white)}
.trust-item h3{
  font-size:clamp(19px,2vw,28px);text-transform:uppercase;letter-spacing:-.015em;
  margin-bottom:8px;color:rgba(255,255,255,.55);
  transition:color .3s var(--ease);
}
.trust-item[aria-current="true"] h3{color:var(--white)}
.trust-item p{margin:0;color:var(--muted);font-size:15px;max-width:38ch}

/* ==========================================================================
   4 · RESEARCH PAPERS — three equal 3:2 cards on the brand blue
   ========================================================================== */
/* deliberately tighter than --pad-section: this section is a link rack, not a
   destination, and shouldn't cost a full screen of scrolling to pass */
/* Ground is --blue, matching the rest of the site (user's call, 2026-09-21:
   "only change the main background color to the correct blue").

   It ran on --blue-band (#0d2444, one luminance step lighter) from 2026-09-17,
   added because the section did not register while scrolling past. That is no
   longer the same problem it was solving: back then Trust above was BLACK and
   this band sat a couple of percent off it. Trust is --blue now, and the other
   half of that fix is still in place and doing the work — the cards are lighter
   than their ground rather than darker, so they read as objects on the band
   instead of holes cut into it. Don't flip .paper back to a dark fill.
   No border-top here on purpose — .trust already draws a white border-bottom
   and a second hairline under it doubles the seam. */
.research{background:var(--blue);padding-block:clamp(54px,6vw,86px)}
.research h2{
  font-size:clamp(32px,4.4vw,62px);text-transform:uppercase;letter-spacing:-.03em;
  color:var(--white);margin-bottom:clamp(22px,2.6vw,36px);
}
.papers{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(20px,3vw,40px)}
.paper{
  display:flex;flex-direction:column;
  border:1px solid rgba(255,255,255,.22);border-radius:var(--radius);
  /* LIGHTER than the section ground, not darker. At rgba(0,0,0,.30) these read
     as holes cut into the band rather than objects sitting on it, which is the
     other half of why the section disappeared when scrolling past. */
  overflow:hidden;background:rgba(255,255,255,.055);
  transition:border-color .3s var(--ease), transform .3s var(--ease);
}
/* the shadow matters more now: a white block lifting off a navy ground needs
   somewhere to lift from. Lighter than .tile:hover — these are references, not
   products, and shouldn't shout as loudly as the merch row. */
.paper:hover{
  border-color:rgba(255,255,255,.55);background:rgba(255,255,255,.085);
  transform:translateY(-5px);box-shadow:0 16px 38px rgba(0,0,0,.34);
}
.paper-media{aspect-ratio:3/2;width:100%}
/* White foot under the dark plate. Colours are --ink / --muted-ink, the same
   pair the merch tiles use, so the two white-on-navy blocks on this page belong
   to one system rather than looking like two attempts at it. */
.paper-body{
  padding:18px 22px 22px;display:flex;flex-direction:column;gap:7px;flex:1;
  background:var(--white);color:var(--ink);
}
.paper h3{font-size:clamp(16px,1.5vw,21px);letter-spacing:-.01em;line-height:1.22}
.paper .cite{font-size:13px;color:var(--muted-ink);margin:0}
.paper .go{
  margin-top:auto;padding-top:12px;
  font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:.16em;text-transform:uppercase;color:var(--muted-ink);
  display:flex;align-items:center;gap:8px;transition:color .2s var(--ease);
}
.paper:hover .go{color:var(--ink)}
.paper:hover .go .arw{transform:translateX(4px)}
.paper .go .arw{transition:transform .22s var(--ease)}

/* Covers are set in TYPE, not photographed. A journal's cover art and its
   figures stay copyrighted even where the article text is CC BY, so there is no
   cover image here that could be licensed cleanly — and a generic stock "lab"
   photo on a citation card is worse than no picture at all. */
.paper-cover{
  position:relative;
  height:100%;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:9px;
  padding:16px;text-align:center;
  /* Two layers, topmost first: a fine diagonal hatch in --blue over the plate
     gradient. Low opacity on purpose — it reads as the plate receding, not as
     marks on top of it. If it ever competes with the journal name, drop the
     opacity rather than removing it.
     A DNA double helix sat above the hatch until 2026-09-17 and was taken off on
     request. Don't reinstate it without being asked. */
  background-image:
    repeating-linear-gradient(135deg, rgba(0,19,46,.10) 0 1px, transparent 1px 10px),
    linear-gradient(158deg, rgba(255,255,255,.10), rgba(255,255,255,.02));
  background-repeat:repeat, no-repeat;
}
.paper-cover-tag{
  /* 11px, not 9.5px, since 2026-09-29: 9.5 was below the 11px floor every
     other small label here respects, and the design detector flagged it.
     Now matches .eyebrow, .ticker-item and .paper-cover-y. */
  font-family:var(--font-display);font-size:11px;font-weight:600;
  /* .58 not .5 — on the lifted card ground .5 fell to 4.55:1, which passes AA
     by 0.05. This is 5.5:1. */
  letter-spacing:.2em;text-transform:uppercase;color:rgba(255,255,255,.58);
}
.paper-cover-j{
  font-family:var(--font-display);font-weight:600;
  font-size:clamp(13px,1.2vw,17px);letter-spacing:.11em;text-transform:uppercase;
  line-height:1.4;color:rgba(255,255,255,.9);max-width:24ch;
}
.paper-cover-y{
  font-family:var(--font-display);font-size:11px;font-weight:600;letter-spacing:.2em;
  color:var(--muted);padding-top:9px;border-top:1px solid rgba(255,255,255,.24);
}
/* Optional photographic ground behind the type plate. This slot takes OUR OWN
   photography and nothing else — not a figure, not a graphical abstract, not a
   rendered first page. assets/img/README.md has the reasoning, which is legal and
   not aesthetic; read it before putting anything from a paper here.
   Empty slot: the <img> fails, onerror hides the whole layer, and the plate falls
   back to its gradient. Dropping the file in is the only step. */
.paper-art{position:absolute;inset:0;z-index:0}
.paper-art img{width:100%;height:100%;object-fit:cover;display:block}
/* The scrim is what keeps the journal name legible over an arbitrary photograph,
   and it is heavy on purpose: this is a citation card, so the picture is texture
   underneath the type, never the subject. */
.paper-art::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(180deg, rgba(var(--blue-rgb),.64), rgba(var(--blue-rgb),.92));
}
.paper-cover-tag,.paper-cover-j,.paper-cover-y{position:relative;z-index:1}
.paper h3{text-wrap:pretty}
/* The standing caveat under the rack. Small, but it is the line that keeps a
   citation from reading as a product claim — see COPY RULE 3. */
p.papers-note{
  margin:clamp(22px,2.4vw,32px) 0 0;max-width:86ch;
  font-size:13.5px;line-height:1.65;color:var(--muted);
}
.papers-note a{color:rgba(255,255,255,.88);text-decoration:underline;text-underline-offset:3px}
.papers-note a:hover{color:var(--white)}

/* ==========================================================================
   5 · STATEMENT — white ground, brand blue type
   ========================================================================== */
/* Thinned 2026-10-08 on the owner's call, "make the white buffer thinner".
   Was clamp(100px,13vw,180px), which put 180px of padding above and below
   only 163px of text at 1440 — the band was more padding than content. Now
   roughly half, and in step with .best and .research, which are both 86px
   there. This is a DESIGN change, not a port fix: staging had matched the
   draft exactly. */
.statement{background:var(--white);color:var(--blue);padding-block:clamp(56px,6vw,88px)}
/* sized so "Defined by standards." — the longer line — stays on ONE line at
   every width; the brief calls for exactly two lines */
.statement h2{
  font-size:clamp(22px,5.4vw,78px);
  font-weight:800;letter-spacing:-.04em;line-height:1.05;text-transform:uppercase;
}
.statement h2 span{display:block;white-space:nowrap}
.statement h2 span:last-child{color:rgba(0,19,46,.40)}

/* ==========================================================================
   6 · CONTACT — WPForms-shaped markup
   ========================================================================== */
/* ⚠ padding-block is NOT --pad-section here, 2026-10-07, on the owner's note
   that this section still read as spaced out after WPForms' own box padding was
   reset. Every other band on the site pairs --pad-section with content that
   fills the width; this one is a two column grid whose LEFT column is much
   shorter than the form beside it, so a full 140px below the Submit button sits
   under an already empty half. Measured: 880px against the draft's 749, and the
   difference is real content, a Phone field and the First/Last sublabels that
   the draft's mock form did not have. Trimming the band is the honest way to
   close it rather than pretending the form is smaller than it is. */
.contact{background:var(--blue);border-top:1px solid var(--blue-edge);padding-block:clamp(64px,7vw,96px)}
.contact-grid{display:grid;grid-template-columns:1fr 1.15fr;gap:clamp(40px,7vw,110px);align-items:start}
.contact h2{font-size:clamp(34px,4.6vw,64px);text-transform:uppercase;letter-spacing:-.03em;margin-bottom:20px}
.contact-intro p{color:var(--muted);max-width:38ch;margin:0 0 28px}
.contact-intro .direct{
  font-size:14px;color:var(--muted);border-top:1px solid rgba(255,255,255,.14);padding-top:22px;
}
.contact-intro .direct a{color:#fff;border-bottom:1px solid rgba(255,255,255,.3)}

/* WPForms class names, so the real [wpforms] shortcode inherits all of this */
.contact .wpforms-container{width:100%}
.contact .wpforms-field{margin-bottom:22px}
.contact .wpforms-field-label{
  display:block;margin-bottom:9px;
  font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:.16em;text-transform:uppercase;color:var(--muted);
}
.contact .wpforms-required-label{color:#7FA9E8;margin-left:3px}
.contact .wpforms-field input[type="text"], .contact .wpforms-field input[type="email"], .contact .wpforms-field textarea{
  width:100%;padding:15px 18px;
  background:rgba(255,255,255,.06);
  border:1px solid rgba(255,255,255,.20);
  border-radius:var(--radius);
  color:var(--white);font-family:var(--font-body);font-size:15px;
  transition:border-color .2s var(--ease), background .2s var(--ease);
}
.contact .wpforms-field input::placeholder, .contact .wpforms-field textarea::placeholder{color:rgba(255,255,255,.38)}
.contact .wpforms-field input:focus, .contact .wpforms-field textarea:focus{
  outline:none;border-color:rgba(255,255,255,.8);background:rgba(255,255,255,.10);
}
.contact .wpforms-field textarea{resize:vertical;min-height:150px;line-height:1.6}
.contact .wpforms-submit-container{margin-top:30px}
.contact .wpforms-submit{
  font-family:var(--font-display);font-size:13px;font-weight:700;
  letter-spacing:.16em;text-transform:uppercase;
  background:var(--white);color:var(--blue);
  padding:17px 38px;border:0;border-radius:var(--radius);cursor:pointer;
  transition:transform .22s var(--ease), box-shadow .22s var(--ease);
}
.contact .wpforms-submit:hover{transform:translateY(-2px);box-shadow:0 10px 30px rgba(0,0,0,.4)}

@media (max-width:860px){

  /* HERO, REBUILT 2026-10-09 TO CARRY THE DESKTOP COMPOSITION.
     Owner: *"the home page - if we can make the first section exactly like
     desktop that would be amazing"*.

     UNTIL 2026-10-09 this block read "hero: image on top, blue panel below,
     fade rotates to vertical" and did:
         .hero{display:block;min-height:0}
         .hero-media{position:relative;inset:auto;height:56vw;max-height:420px}
         .hero::before{display:none}          <- the signature fade, OFF
         .hero-media::after{linear-gradient(172deg, slate .52 -> blue)}
         .hero-inner{padding:40px var(--pad-gutter) 72px}
     That was a deliberate design, not an accident, but it threw away the one
     thing that makes the desktop hero look like itself. Don't reinstate it
     without being asked.

     ⚠ WHY IT LOOKED BROKEN, MEASURED, not guessed. `height:56vw` is 218px at
     390 — and the FIXED CHROME IS 107px (38px ticker + 69px header). So more
     than half the image sat behind the header and the vials were beheaded. The
     desktop layout never shows this because its media is full-height and the
     chrome only ever covers the empty navy part of the frame.

     ⚠ THE IMAGE IS COMPOSED FOR THE DESKTOP SLOT. hero-1.jpg is 2246x2000
     with the vial group occupying roughly x 42%-93% and y 30%-73%; the left
     ~40% is deliberately empty black so the diagonal fade can sit over it. Two
     consequences:
       - `object-position` must pull RIGHT (90%) or the third vial is cut off.
         Measured: at 390 wide the media box crops horizontally to about 71% of
         the scaled image, and the vial group spans 51% of it, so the window has
         to be anchored right to contain all three.
       - the image carries ~27% black BELOW the vials. With `cover` and a box
         this shape there is no vertical crop, so that black always shows at the
         bottom. It reads as the navy ground continuing, which is why the media
         is anchored to the bottom rather than centred.

     The composition: navy at the top carrying the copy, the media anchored to
     the bottom 56%, and `.hero::before` RESTORED over all of it with the
     gradient rotated to 180deg so the vials emerge upward out of the navy.
     Same idea as desktop, turned 90 degrees for portrait. */
  /* ⚠⚠ THE PHONE HERO LIVES IN THE 560px BLOCK BELOW, NOT HERE — moved
     2026-10-09 and the reason is measured. It is sized by `padding-bottom:110%`
     and an `aspect-ratio` media box, both of which resolve against WIDTH so
     they cannot move when a phone's toolbar hides. The cost is that they also
     SCALE with width: at 768 that made the hero 1373px and at 860 it made it
     1481px, well over one and a half screens on a tablet.

     So 561-860 now falls through to the DESKTOP hero instead, and it is the
     better answer at those widths anyway — there is room for the real
     two-column composition. Measured at 600/768/860: hero 776px at all three,
     copy left, vials right, the 73deg fade from the `max-width:1150px` rule,
     button clear, no sideways scroll. Don't move the phone rules back up. */

  /* Best sellers stays 3-up rather than stacking like .papers does. The vials
     are tall and narrow, so three of them side by side still read at 500px
     (~145px per card) and stacking would add roughly 1400px of scroll before
     the merch line. The CTA row goes instead: the whole card is already a
     link, so it is redundant at this width. */
  .best-grid{gap:12px}
  .best-media{aspect-ratio:3/4}
  .best-media img{padding:10px 10px 0}
  .best-body{padding:10px 10px 14px;gap:3px}
  .best-name{font-size:12px;letter-spacing:0;line-height:1.25}
  .best-price{font-size:12px}
  .best-cta{display:none}

  .merch-grid{grid-template-columns:1fr}
  .merch-col{grid-template-rows:auto auto}
  /* the tee rail needs no breakpoint: --tee-card is a clamp and the centring
     padding follows it, so it reflows on its own. */
  /* stacked tiles are no longer in a stretched row, so flex:1 would collapse
     the media to zero height — give both tile types an explicit ratio back */
  .tile--tall .tile-media{aspect-ratio:4/5}
  .tile--wide .tile-media{flex:none;aspect-ratio:3/2}

  /* ⚠⚠ TRUST, REBUILT FOR PHONES 2026-10-09. Owner:
     *"lets add the '1' '2' and '3' with the respective headers into the white
     box with the blue color text. have the 1 2 3 be in the center but spaced
     out so user can press and the headings can form under the numbers but above
     the cliparts. with the blue section under thats where the explainantions of
     the numbers are. when their number isnt selceted or showing during the auto
     roll, it dispears and the respective explanation shows."*

     Result: white box = [1 2 3] → active heading → clipart. Navy band below =
     the active explanation. Everything else hidden.

     ✅ NO MARKUP AND NO JS CHANGED, which is the point. `home.js` already sets
     `aria-current="true"` on the active `.trust-item` and `is-active` on the
     matching `.trust-slide`, so the carousel drives all of this for free, and
     DESKTOP IS COMPLETELY UNTOUCHED. The markup stays
     `button > [span.num, span > [h3, p]]`, so the heading and the explanation
     live inside the same button and have to be pulled to opposite ends of the
     section by absolute positioning.

     ⚠⚠ BOTH h3 AND p RESOLVE AGAINST `.trust`, AND THAT TOOK TWO FIXES.
       1. `.trust-list` was made `position:relative` at first, to anchor the
          heading. That ALSO captured the paragraph, which landed in the white
          box on top of the numbers. A positioned ancestor captures every
          absolute descendant, not just the one you had in mind.
       2. With the list static, they still resolved 80px low, because
          `.trust-grid` carries `position:relative` FROM THE DESKTOP RULE. It is
          set to `static` here. Its `z-index:2` goes with it, which is safe only
          because `.trust::before/::after` are already `display:none` at this
          width.

     ⚠ THE SECTION RESERVES A FIXED BAND for the explanation, now `--band`.
     It was `padding-bottom: pad-section + 168px` with every paragraph anchored
     to one top line; see the 2026-10-09 note below for what replaced it and
     why. The band being fixed is deliberate either way: THE SECTION KEEPS A
     CONSTANT HEIGHT as the carousel advances, so the page never reflows under
     the reader.

     ⚠ `flex-direction:row` is declared explicitly. The desktop rule sets
     `column`, and without the override the three numbers stacked vertically.

     ⚠⚠ `gap:0` IS LOAD-BEARING, ADDED 2026-10-09. Owner: *"lets combine the
     two white boxes"*. There were never two boxes by design — .trust-list and
     .trust-left are both white and adjacent, but .trust-grid carries
     `gap:clamp(40px,6vw,96px)` FROM THE DESKTOP RULE, which at 390 put a 40px
     strip of navy between them and made them read as two panels. Removing it
     also takes the heading-to-clipart buffer from 73px to 33px (one-line
     heading) and from 53px to 13px (two lines), which is the rest of that same
     sentence: *"minimize the buffer between the words and clipart"*.

     ⚠ THE EXPLANATION BAND IS `--band`, AND IT IS A MEASURED WORST CASE.
     Owner: *"the buffer in the navy below the explanations lets also minimize
     as much as we can and center the whole text in the navy section"*. It was
     `--pad-section + 168px` = 248px with every paragraph pinned to a fixed top
     line, which left 26px above the longest one and 79-151px below it. 200px
     is the tallest paragraph at the narrowest width plus 16px each side:
     measured 168px at 320, 144 at 360/390, 120 at 414-480, 96 at 540/560.
     RE-MEASURE AND RAISE IT IF ITEM 1's COPY GROWS.

     ⚠ The band stays a FIXED height on purpose, as before: the section must not
     change height as the carousel advances or the page reflows under the
     reader. Centring is what varies per item, not the box. */
  .trust-grid{display:flex;flex-direction:column;position:static;gap:0}
  .trust{--band:200px;padding-bottom:var(--band)}
  .trust::before,.trust::after{display:none} /* no left/right halves once stacked */

  /* the numbers row moves ABOVE the picture and carries the white ground, so it
     and .trust-left read as one white box. It takes over the negative top
     margin that used to be on .trust-left. */
  .trust-list{
    order:-1;
    position:static;
    display:flex;
    flex-direction:row;
    justify-content:center;
    align-items:flex-start;
    gap:0;
    border-top:0;
    background:var(--white);
    margin-inline:calc(var(--pad-gutter) * -1);
    margin-top:calc(var(--pad-section) * -1);
    padding:34px var(--pad-gutter) 68px;
  }
  .trust-item{
    flex:1 1 0;
    min-width:0;
    display:flex;
    flex-direction:column;
    align-items:center;
    gap:0;
    padding:0;
    position:static;
  }
  /* inverted for the white ground: navy digit in an outlined pill, filling to
     solid navy when active. On desktop this is the other way round. */
  .num{
    width:48px;height:48px;font-size:17px;
    border-color:rgba(0,19,46,.28);
    color:var(--ink);
    background:transparent;
  }
  .trust-item[aria-current="true"] .num{
    background:var(--ink);color:var(--white);border-color:var(--ink);
  }
  /* full width of the white box, centred under the row of numbers. `top:96px`
     is measured from the section edge: 34px padding + 48px pill + 14px gap.
     The list's 68px bottom padding is what reserves room for it, including the
     two-line case ("Reliable Shipping & Customer Service"), which ends at 137px
     against the picture starting at 151px. */
  .trust-item h3{
    position:absolute;
    left:var(--pad-gutter);right:var(--pad-gutter);
    top:96px;
    margin:0;
    text-align:center;
    color:var(--ink);
    font-size:clamp(16px,4.4vw,20px);
    line-height:1.18;
    opacity:0;
  }
  .trust-item[aria-current="true"] h3{opacity:1;color:var(--ink)}

  .trust-left{
    position:static;inset:auto;display:block;
    background:var(--white);
    margin-inline:calc(var(--pad-gutter) * -1);
    margin-top:0;
    /* 26px -> 14px, 2026-10-09: *"the buffer below the clipart lets decrease"*.
       Safe to take this low because the art carries its own margin — measured
       ink bounds give trust-1 and trust-2 a 9.2% internal pad and trust-3 a
       24.4% one, so 14px of box padding reads as 35px or 69px of white. */
    padding:0 var(--pad-gutter) 14px;
  }
  /* ⚠ CAPPED, NOT RESHAPED. trust-1/2/3.png are all exactly 2000x1500 (4:3),
     matching `.trust-media`'s own aspect-ratio, and `.trust-slide img` is
     `object-fit:cover` — any other ratio CROPS the line art. Width is the only
     safe dimension to touch. */
  .trust-media{margin-bottom:0;max-width:300px;margin-inline:auto}

  /* ⚠ SPANS THE WHOLE BAND AND CENTRES INSIDE IT, 2026-10-09, rather than
     hanging off a top line. `top:calc(100% - var(--band))` with `bottom:0`
     makes the box exactly the padding-bottom region — 100% resolves against
     .trust's PADDING box, whose bottom edge is the inside of the white rule —
     so the two numbers can never disagree: change --band and this follows.
     ⚠ `display:flex` on a <p> is deliberate; the text becomes one anonymous
     flex item, which is what align-items can then centre. */
  .trust-item p{
    position:absolute;
    left:var(--pad-gutter);right:var(--pad-gutter);
    top:calc(100% - var(--band));
    bottom:0;
    display:flex;
    align-items:center;
    justify-content:center;
    margin:0;
    max-width:none;
    text-align:center;
    opacity:0;
  }
  .trust-item[aria-current="true"] p{opacity:1}

  /* ⚠⚠ A ONE-ROW SWIPE RAIL, NOT A STACK, 2026-10-09. Owner: *"research
     papers — if that can be like the desktop that'd be great, the images need to
     be smaller and sit in a singular row"*.

     Was `grid-template-columns:1fr; gap:20px`, which stacked the three cards
     into 1211px of scroll with a 218px image block on each.

     WHY A RAIL AND NOT A LITERAL 3-UP GRID: three columns inside the 350px
     content box is 108px a card. The titles here are real sentences —
     "Therapeutic Peptides in Aesthetic, Metabolic and Endocrine Conditions" —
     and at 108px that is about 14 characters a line. The row would be readable
     only as a column of slivers. The rail keeps the single row the owner asked
     for AND keeps the type legible, at 312px a card with the next one peeking
     past the right edge as the scroll affordance. `.best-grid` genuinely is
     3-up at this width (see its note above) because product names are short;
     paper titles are not the same problem.

     ⚠ THE RAIL BLEEDS TO THE VIEWPORT EDGES. `margin-inline` pulls it out past
     the gutter and `padding-inline` pays it back, so the first card still lines
     up with the heading while the row can run off-screen. Verified the PAGE has
     no sideways scroll — `overflow-x` is on the rail, not the document.

     ⚠ `overflow-x:auto`, never `hidden`. `hidden` on one axis forces the other
     to `auto` and breaks `position:sticky` in descendants — recorded elsewhere
     in this project and it cost a round. `scroll-snap-type` is `proximity`, not
     `mandatory`, so a half-swipe is not yanked back. */
  .papers{
    display:grid;
    grid-auto-flow:column;
    grid-template-columns:none;
    /* NARROWED 2026-10-09 from 76vw. Owner: *"make the three boxes alittle bit
       smaller and add buffer on the left side so the first box is more centered
       like the merch line above"*. The merch rail's active card measures 240px
       at 390 against the old 312px here, which is why this one read as a
       full-bleed panel rather than one card of several. */
    grid-auto-columns:62vw;
    gap:12px;
    overflow-x:auto;
    overscroll-behavior-x:contain;
    scroll-snap-type:x proximity;
    -webkit-overflow-scrolling:touch;
    scrollbar-width:none;
    margin-inline:calc(var(--pad-gutter) * -1);
    /* ⚠⚠ THE LEFT BUFFER IS PADDING PLUS `scroll-padding-left`, AND BOTH ARE
       REQUIRED. Padding alone did nothing visible: `scroll-snap-align:start` on
       `.paper` snaps the first card's edge flush to the scrollport start, so
       the browser immediately scrolled the rail by exactly the inset (measured
       `scrollLeft:38`) and cancelled it. `scroll-padding-left` moves the snap
       line itself. Verified `scrollLeft:0` with the card resting at x=38.

       ⚠ It is PADDING, not a margin on the first card. A margin is inside the
       scroller and gets snapped past in the same way.

       ⚠ Padding is also safe here where width is not: the rail's border box is
       already `100% + 2 x gutter` from the negative margin, and padding eats
       into the content box rather than widening it, so the page does not gain
       sideways scroll. Re-verified at 360/390/480. */
    padding:0 var(--pad-gutter) 6px calc(var(--pad-gutter) + 18px);
    scroll-padding-left:calc(var(--pad-gutter) + 18px);
  }
  .papers::-webkit-scrollbar{display:none}
  .paper{scroll-snap-align:start;min-width:0}
  .paper-media{aspect-ratio:16/9}
  /* ⚠ `p.papers-note` AT (0,1,1), NOT `.papers-note` AT (0,1,0). The base rule
     is `p.papers-note{font-size:13.5px}` and a bare class loses to it — the
     first attempt here silently did nothing. Owner wanted the FDA line as fine
     print: *"make the fda disclaimer below it, more of a fine text, decrease
     the font size"*. 13.5px -> 11px.
     ⚠ 11px is ABOVE the sub-11px type floor this project enforces, and the
     colour is unchanged at rgba(255,255,255,.66) on navy, which already
     measured over 4.5:1. Do not dim it as well as shrinking it — this is the
     research-use disclaimer and it has to stay readable. */
  p.papers-note{font-size:11px;line-height:1.6;margin-top:18px}

  .contact-grid{grid-template-columns:1fr;gap:44px}
}

@media (max-width:560px){
  /* ⚠ THIS WINS OVER THE 860px BLOCK — equal specificity, later in the file.
     Raised from clamp(38px,11vw,56px) on 2026-10-09: at 390 that resolved to
     42.9px, which read small under the rebuilt hero above. 13.5vw gives 52.6px
     and still fits "BURLY LABS" on one line at 360. */
  /* ⚠⚠ THIRD PASS 2026-10-09 — THE PICTURE MOVED TO THE TOP. Owner:
     *"lets have the pictures be above 'burly labs', delete 'third-party
     tested', and have the blue fade from the heroes into the Burly, the view
     catalog button below the text with the three picture light bars under."*

     So the order is now: image → fade → BURLY LABS → copy → VIEW CATALOG → dots.
     The previous pass had the copy on top and the image anchored to the bottom;
     this inverts it. What CARRIED OVER from that pass, and must not be undone:

     ⚠ NO VIEWPORT UNITS. `padding-top:130%` and `aspect-ratio:2/3` both resolve
     against WIDTH. `vh` grows when a phone's toolbar hides on scroll, which is
     what made the picture resize under the reader's thumb two passes ago.

     ⚠ THE CROP IS STILL THE SOLVED ONE. `aspect-ratio:2/3` + `object-position:95%`
     frames the vial group (measured at x 43.3-93.6%, y 33.8-78.5% of
     hero-1.jpg) with 4.5% to spare each side. Changing one of those two values
     means re-solving the other. See scratchpad/vialbounds.py.

     ⚠ THE IMAGE STILL CLEARS THE FIXED CHROME, which is the bug that started
     all of this. Media is top-anchored at `inset:0 0 auto 0`, so its top 107px
     sit behind the ticker+header — but the vial group starts at 33.8% of 585px
     = 198px, comfortably below them. Verified: vials 198-459, chrome ends 107.

     The geometry at 390, measured after deploy: hero 885px, media 585px,
     vials 198-459, h1 top 507, button bottom 810, dots bottom 839 — the whole
     hero lands inside an 844px viewport.

     ⚠ `padding-top:130%` (507px) is deliberately LESS than the media height
     (585px), so the copy overlaps the image's bottom 78px. That region is the
     image's own empty black, and the fade is already solid navy there, so
     nothing is covered. */
  /* ⚠⚠ FOURTH PASS 2026-10-09 — THE PICTURE IS CROPPED FROM THE TOP AND PINNED
     UNDER THE HEADER. Owner: *"lets crop the pictures so they are slimmer
     top-bottom, be careful with the vial picture, we can probably crop the
     pictures top-down, crop top 1/3 out ... and make sure 'Burly Labs' is in
     the fade and not the pictures"*.

     UNTIL 2026-10-09 this read:
         .hero{padding-top:130%}
         .hero-media{inset:0 0 auto 0;height:auto;aspect-ratio:2/3}
     At 390 that gave hero 885 / media 585, and the headline at y=507 sat INSIDE
     the media box. Harmless over hero-1, whose bottom 22% is pure black — but
     hero-2 and hero-3 are PHOTOGRAPHS with content to the last row, so on two
     slides in three the words landed on the picture at 12-45% fade. That is
     what the owner was looking at. Don't reinstate it.

     ⚠⚠ "CROP TOP 1/3" IS NOT AVAILABLE AT THE SCALE THIS IMAGE NEEDS, and the
     number is measured, not judged. In the 390x585 img box hero-1's content
     occupies rows 197-458 (scratchpad/boxscan.py); everything above 197 is pure
     black, and THE FIXED CHROME IS 107px, so only 90px of that black is even
     on screen. A literal 33% (195px) crop puts the vial caps at y=3, back under
     the header — the exact bug this whole mobile pass began with. What is done
     instead: crop everything above the caps bar a constant 36px.

     ⚠ THE CROP CANNOT BE A FIXED PERCENTAGE. The picture scales with width,
     the chrome does not: at 320 there is only 55px of black under the header to
     cut, at 430 there is 110px. So the geometry is written as `px + vw`, which
     pins the caps 36px below the header at EVERY width. Verified at
     320/360/390/414/430/480/540/560 — clearance 36px at all eight.

     ⚠ `vw`, NOT `vh`. The ban recorded two passes above is on vh, which GROWS
     when a phone's toolbar hides on scroll. vw does not move. Its one cost is
     the classic desktop scrollbar, which makes 100vw about 15px wider than the
     layout box and would shift the caps ~8px; phones use overlay scrollbars and
     agent-browser measured clientWidth === innerWidth at every width swept.

     ⚠⚠ THE THREE CONSTANTS ALL COME FROM hero-1.jpg AND MUST BE RE-DERIVED IF
     IT IS REPLACED. The img keeps aspect-ratio 2/3, so its height is 1.5x the
     viewport width and a row fraction f becomes 150*f vw:
         content top    197/585 = .33675  ->  50.51vw
         content bottom 458/585 = .78291  -> 117.44vw
         group height            .44616   ->  66.92vw   (--vial-h)
     --cap is where the caps are pinned: 107px of chrome + 36px of air.

     ⚠⚠ THE img IS DELIBERATELY TALLER THAN ITS BOX. It keeps aspect-ratio 2/3
     so `object-fit:cover` still crops HORIZONTALLY to the solved 95% framing,
     and the top is clipped by .hero-media's overflow instead. Shortening the
     box and letting `cover` do the vertical crop DOES NOT WORK: hero-1 is
     2246x2000, ratio 1.123, so any box wider than that stops cropping sideways,
     the scale drops, and the empty left 43% of the frame comes back as dead
     space — the thing the owner asked to be rid of two passes ago. Measured: a
     1/1.15 box shows image x from 21.4%, putting 110px of black on the left.

     Geometry at 390 after this pass: hero 838 (was 885), media 531 (was 585),
     caps 143, vial bases 404, h1 top 460 on fully solid navy. The whole hero
     now lands inside an 844px viewport with 6px to spare. */
  .hero{
    --cap:143px;               /* vial caps = 107px of chrome + 36px of air */
    --vial-h:66.92vw;          /* measured height of the vial group */
    --base:calc(var(--cap) + var(--vial-h));   /* where the vials end */
    display:block;
    min-height:0;
    padding-top:calc(var(--base) + 56px);
    padding-bottom:0;
    position:relative;
    overflow:hidden;
  }
  .hero-media{
    position:absolute;
    inset:0 0 auto 0;
    aspect-ratio:auto;                   /* cancels the 2/3 box of the last pass */
    height:calc(var(--cap) + 99.49vw);   /* = 150vw + the img's negative top */
    max-height:none;
    overflow:hidden;                     /* THIS is what crops the top */
  }
  .hero-slide img{
    inset:calc(var(--cap) - 50.51vw) auto auto 0;
    height:auto;
    aspect-ratio:2/3;
    object-position:95% 50%;
  }
  /* ⚠⚠ THE RAMP IS WRITTEN IN PIXELS OFF --base, NOT IN PERCENTAGES OF THE
     HERO, changed 2026-10-09. It was `clear 52% -> solid 71%`, which only ever
     landed on the vial bases because the hero happened to be 885px; the hero's
     height now moves with the copy's wrapping, so percentage stops would drift
     off the picture. Keyed to --base it holds at every width.

     The ramp STARTS EXACTLY AT THE VIAL BASES and reaches solid 58px later,
     which is 2px ABOVE the headline (padding-top is --base + 56px). That is the
     owner's actual request — *"make sure Burly Labs is in the fade and not the
     pictures"* — and it is what makes the two photographic slides work: their
     bottom edge at --cap + 99.49vw is far inside the solid region, so no slide
     shows a cut edge.

     ⚠ DO NOT START THE RAMP ABOVE --base. Tried 2026-10-09: a .05/.14 tail
     beginning 40px early does soften the dissolve on hero-2 and hero-3, and it
     visibly dulls the glass at the bottom of the vials — the same finding that
     pulled the old 84% stop back to 71%. The photographs read fine on 58px. */
  .hero::before{
    display:block;
    background:linear-gradient(180deg,
      rgba(0,19,46,0) 0,
      rgba(0,19,46,0)   var(--base),
      rgba(0,19,46,.30) calc(var(--base) + 14px),
      rgba(0,19,46,.62) calc(var(--base) + 26px),
      rgba(0,19,46,.85) calc(var(--base) + 38px),
      rgba(0,19,46,.96) calc(var(--base) + 48px),
      rgba(0,19,46,1)   calc(var(--base) + 58px),
      rgba(0,19,46,1) 100%);
  }
  .hero-media::after{background:none}
  /* ⚠ "THIRD-PARTY TESTED" IS HIDDEN ON PHONES ONLY, not deleted from the
     markup — the desktop hero still carries it and was not in scope. If the
     owner wants it gone everywhere, remove the <p class="eyebrow"> from the
     page content AND from index-tshirt.html / index.html, not from here. */
  .hero .eyebrow{display:none}
  .hero-inner{
    position:relative;z-index:3;
    padding:0 var(--pad-gutter) 46px;
  }
  .hero-copy{max-width:none}
  .hero p{max-width:34ch;margin-bottom:28px}
  .hero-inner .hero-dots{margin-top:26px}
  .hero h1{font-size:clamp(40px,13.5vw,56px);line-height:.96}
  /* ⚠ these two are LATER in the file than the 860 block and would undo it.
     The pill keeps the smaller size; the gap must stay 0 or the number and the
     absolutely-positioned heading separate. */
  .num{width:46px;height:46px;font-size:16px}
  .trust-item{gap:0}
  /* a touch wider on the narrowest phones so the title still breaks sanely */
  .papers{grid-auto-columns:66vw}

  /* ⚠⚠ "BUILT ON PRECISION." — BIGGER TYPE, TIGHTER BAND, 2026-10-09. Owner:
     *"we need to increase the font size on those two lines and decrease the
     above and below buffers so the words look bigger and take up more space"*.

     ⚠ THERE IS A HARD CEILING AND IT IS NOT A TASTE CALL.
     `.statement h2 span{white-space:nowrap}` means these two lines can NEVER
     wrap, so the longer one ("Defined by standards.") must fit the content box
     or it pushes the page sideways. The first attempt used
     `clamp(28px,8vw,34px)` = 31.2px at 390 and did exactly that — the page
     gained 18px of sideways scroll, which is half the 36px overflow because
     the heading is centred.

     Measured at 390 in Archivo 800: the text is 12.43px wide per 1px of font
     size, against a 350px content box, so 28px is the largest that fits.
     Expressed as a fraction of viewport width the limit is 7.23vw at 390,
     7.14vw at 360 and 7.03vw at 320, so **7vw is safe down to 320** and is what
     is used. Verified: 279/280 at 320, 314/320 at 360, 340/350 at 390.

     ⚠ Tightening the tracking does not buy a bigger size, which was tested
     rather than assumed. The heading already carries `letter-spacing:-0.88px`
     ABSOLUTE, so it does not scale with font-size and is already about -0.03em
     at 30px; the "tighter" em values tried were in fact looser and made the
     text wider.

     Padding 56px -> 34px takes the band from 158px to 128px at 390, so the
     words occupy more of it.

     ⚠ 34px -> 20px on 2026-10-09, owner: *"lets minimize the buffer at the
     bottom even more"*. The band is now 100px at 390 against a 60px heading,
     so 20px above and below. That is the floor: the two lines are 27.3px at
     390 and the band cannot go under the type without clipping descenders. */
  .statement{padding-block:20px}
  .statement h2{font-size:clamp(20px,7vw,30px);line-height:1.1}
}


/* ==========================================================================
   SHOP ARCHIVE  — added 2026-10-08
   Ported verbatim from wordpress/shop-lab/candidate.css, which is the local
   capture of staging's REAL rendered markup. There is no shop.html draft and
   there cannot be one: page 2001's post_content is 1 byte and WooCommerce
   renders the loop from its own template.

   CSS ONLY. No WooCommerce template overrides, and no rule here depends on a
   product's position, id, name, price or count, so the owner keeps adding
   products in Products > Add Product with no code change.

   ⚠ FIVE SEPARATE SPECIFICITY LOSSES were measured while writing this, and
   every long selector below is carrying one. The cascade resolves PER
   PROPERTY, so a rule can win its colour and lose its font-size in the same
   breath — that is exactly what happened to the product title. Read the
   comment before shortening any selector.
   ========================================================================== */
/* --------------------------------------------------------------------------
   SCOPE: `ast-woo-shop-archive`, NOT `woocommerce-shop`.
   ⚠ The first scope was `body.woocommerce-shop`, and that class is on /shop/
   ONLY. Product category archives carry `tax-product_cat term-apparel` instead,
   so /product-category/apparel/ and /product-category/lab-grade/ got NONE of
   these rules and rendered as bare Astra. Their markup is otherwise identical
   to /shop/ — same container, same products header and h1, same toolbar, same
   grid, same pagination — so one scope change styles them with no extra rules.

   `ast-woo-shop-archive` is Astra's own class for a shop archive. Measured
   present on /shop/, /product-category/apparel/ and /product-category/lab-grade/,
   and ABSENT from /cart/, /my-account/ and a single product page, which is
   exactly the reach wanted.

   ⚠ It is also a SINGLE CLASS, like `woocommerce-shop`, so every selector below
   keeps the specificity it was deliberately matched to. Do not swap it for
   something heavier: five of these rules sit one step above an Astra rule and
   a heavier scope would start winning fights it should not.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   1 · WooCommerce pages reserve no space for the fixed chrome.
   `.burly-page` is withheld from them on purpose, and the Trap 4 fix is
   scoped to it, so shop/cart/checkout/account had 57px of content sitting
   behind the header at 1440 (52px at 390) with the breadcrumb fully hidden.
   (1,1,1) beats Astra's re-asserted `#primary{margin:4em 0}` at (1,0,0).
   `overflow-x:clip` guards the full-bleed band in 4. CLIP, NOT HIDDEN:
   hidden forces the other axis to auto and breaks sticky descendants.
   -------------------------------------------------------------------------- */
body:not(.burly-page) #primary{
  margin:0;
  padding-top:calc(var(--ticker-h) + var(--header-h));
  overflow-x:clip;
}

/* --------------------------------------------------------------------------
   2 · Ground and gutter. There was NO gutter at all: the container, the grid
   and the cards all started at x=0 with max-width:none, because header.php
   replaces Astra's and `.ast-container` never exists. Constraining the
   container rather than each block keeps Astra's floated result-count/sort
   pair working exactly as it does now.
   -------------------------------------------------------------------------- */
body.ast-woo-shop-archive{background:var(--blue)}

body.ast-woo-shop-archive .ast-woocommerce-container{
  max-width:var(--maxw);
  margin-inline:auto;
  padding-inline:var(--pad-gutter);
  padding-bottom:clamp(48px,5vw,80px);
}

/* 3 · The breadcrumb reads "Home / Shop" directly under a heading that says
   the same thing, and it is invisible today anyway. Hiding it also avoids
   reordering the DOM with flex `order`, which would split visual order from
   reading order. Kept for single product pages, where it earns its place. */
body.ast-woo-shop-archive .woocommerce-breadcrumb{display:none}

/* --------------------------------------------------------------------------
   4 · The slim hero band. Owner's call: slim, and no eyebrow.
   Full-bleed out of the container it now sits inside. The maths: the band's
   containing block is the container's content box, so `50% - 50vw` lands its
   left edge at exactly 0 and 100vw carries it to the far edge.
   ⚠ 100vw INCLUDES THE SCROLLBAR on platforms with classic scrollbars, which
   is a textbook source of sideways scroll — hence the clip in rule 1.
   -------------------------------------------------------------------------- */
body.ast-woo-shop-archive .woocommerce-products-header{
  margin-inline:calc(50% - 50vw);
  margin-bottom:clamp(28px,3vw,44px);
  padding-block:clamp(26px,2.6vw,38px);
  border-bottom:1px solid var(--blue-edge);
}
body.ast-woo-shop-archive .woocommerce-products-header__title{
  max-width:var(--maxw);
  margin:0 auto;
  padding-inline:var(--pad-gutter);
  font-family:var(--font-display);
  font-weight:700;
  font-size:clamp(30px,3.4vw,46px);
  line-height:1.04;
  letter-spacing:-.01em;
  color:var(--white);
}

/* --------------------------------------------------------------------------
   5 · Cards, in the homepage's `.best-card` language.
   -------------------------------------------------------------------------- */
body.ast-woo-shop-archive ul.products{margin:0}

body.ast-woo-shop-archive ul.products li.product{
  position:relative;                 /* for the sale flash in rule 7 */
  background:rgba(255,255,255,.055);
  border-radius:var(--radius);
  overflow:hidden;
  transition:transform .3s var(--ease),
             background .3s var(--ease),
             box-shadow .3s var(--ease);
}
@media (hover:hover){
  body.ast-woo-shop-archive ul.products li.product:hover{
    transform:translateY(-6px);
    background:rgba(255,255,255,.085);
    box-shadow:0 18px 44px rgba(0,0,0,.38);
  }
}
@media (prefers-reduced-motion:reduce){
  body.ast-woo-shop-archive ul.products li.product{transition:none}
  body.ast-woo-shop-archive ul.products li.product:hover{transform:none}
}

/* Astra's on-card overlay: a dark circle with a cart icon and a "Select
   options" tooltip on every thumbnail. Removed on the owner's call, the same
   call as the homepage hover box. NOTE this is also the archive's add-to-cart
   shortcut for simple products, so buying now always goes through the product
   page. That is the intended consequence, not an oversight. */
body.ast-woo-shop-archive .ast-on-card-button{display:none}

/* Media box. ⚠ Deliberately `cover`, NOT the `contain` that `.best-card`
   uses. That card holds transparent vial cut-outs; every image in this
   catalogue was checked and ALL 26 are opaque RGB — the 10 PNGs included —
   so there is nothing to crop around and cover gives uniform tiles. */
body.ast-woo-shop-archive .astra-shop-thumbnail-wrap{margin:0;display:block}
/* ⚠ Target the IMAGE link by its class, not `> a`. The overlay in the rule
   above is ALSO a direct `a` child of this wrapper, so a `> a` selector here
   lands at (0,2,2) and beats the (0,2,1) `display:none` that removes it — the
   overlay came back as display:block and the hide looked like it had failed
   against Astra. It had failed against US. Match the specificity you need,
   do not exceed it. */
body.ast-woo-shop-archive a.woocommerce-LoopProduct-link{display:block}
/* ⚠ `a img` AND the ul/li chain are both load bearing. WooCommerce ships
   `.woocommerce-js ul.products li.product a img{margin-bottom:0.8em}` at
   (0,3,4) — 0.8em of the inherited 15px = exactly the 12px that was collapsing
   through the block anchor and making this wrap 301px tall around a 289px
   image. (0,3,5) here. aspect-ratio and object-fit were never contested; only
   the margin was, which is why the tile was square but the box was not. */
body.ast-woo-shop-archive ul.products li.product a img{
  display:block;
  width:100%;
  height:auto;
  margin:0;
  aspect-ratio:1/1;
  object-fit:cover;
}

body.ast-woo-shop-archive .astra-shop-summary-wrap{
  padding:15px 17px 18px;
  display:flex;
  flex-direction:column;
  gap:7px;
}
/* ⚠ THE FULL CHAIN IS LOAD BEARING on the next two rules. Astra ships
   `.woocommerce-js ul.products li.product .ast-woo-product-category` at
   (0,4,2) declaring font-size, margin-bottom, display, line-height AND
   opacity:.6, plus an inline rule at the same weight setting `color`. A
   shorter `body.ast-woo-shop-archive .ast-woo-product-category` is (0,2,1) and
   loses every one of those properties — the eyebrow rendered BLACK at
   1.19:1 on navy. Mirroring Astra's own chain lands us at (0,4,3).
   The cascade is per property: our `color` had actually applied on the title,
   where Astra declares no colour, and only `font-size` was lost there. */
body.ast-woo-shop-archive ul.products li.product .ast-woo-product-category{
  font-family:var(--font-body);
  font-size:11px;
  font-weight:600;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:rgba(255,255,255,.72);
  line-height:1;
  margin-bottom:0;
  opacity:1;                 /* Astra sets .6; mute with alpha instead, so the
                                measured contrast is the real contrast */
}
body.ast-woo-shop-archive .ast-loop-product__link{
  text-decoration:none;
  display:block;
}
body.ast-woo-shop-archive ul.products li.product h2.woocommerce-loop-product__title{
  font-family:var(--font-display);
  font-weight:700;
  font-size:16px;
  line-height:1.25;
  letter-spacing:-.005em;
  color:var(--white);
  margin:0;
  padding:0;
}
body.ast-woo-shop-archive ul.products li.product .price{
  font-family:var(--font-body);
  font-size:15px;
  font-weight:600;
  color:var(--white);
  font-variant-numeric:tabular-nums;
  margin:0;
  line-height:1.2;
}
body.ast-woo-shop-archive ul.products li.product .price del{
  color:rgba(255,255,255,.52);
  font-weight:400;
  margin-right:6px;
}
body.ast-woo-shop-archive ul.products li.product .price ins{
  text-decoration:none;
  color:var(--white);
}

/* --------------------------------------------------------------------------
   6 · Toolbar. Count left, sort right — Astra's float, left alone.
   -------------------------------------------------------------------------- */
body.ast-woo-shop-archive .woocommerce-result-count{
  font-family:var(--font-body);
  font-size:13px;
  color:rgba(255,255,255,.72);
  margin:0 0 clamp(16px,1.8vw,24px);
}
body.ast-woo-shop-archive .woocommerce-ordering select.orderby{
  font-family:var(--font-body);
  font-size:13px;
  color:var(--white);
  background:rgba(255,255,255,.06);
  border:1px solid rgba(255,255,255,.22);
  border-radius:var(--radius);
  padding:8px 12px;
}
/* ⚠ The dropdown LIST is drawn by the OS, not by this stylesheet. Without an
   explicit pair here the options inherit white-on-white on some platforms. */
body.ast-woo-shop-archive .woocommerce-ordering select.orderby option{
  color:var(--ink);
  background:var(--white);
}

/* ⚠ THE TOOLBAR OVERLAPS ITSELF BELOW 544px, and that is ASTRA's breakpoint.
   Astra un-floats the sort control at that width and gives it full width, but
   leaves `float:left` on the result count. The count is then out of flow, the
   sort block slides underneath it, and the two render ON TOP of each other.
   Measured overlapping at 360, 390, 414, 480, 500, 520, 540 and 544, and clean
   from 545 up, which is how the boundary was identified rather than guessed.
   Clearing the count's float puts both back in normal flow so they stack.
   Nothing is needed on the sort block: Astra already full-widths it here. */
@media (max-width:544px){
  body.ast-woo-shop-archive .woocommerce-result-count{
    float:none;
    margin-bottom:14px;
  }
}

/* --------------------------------------------------------------------------
   7 · States the OWNER can create that no product has today. All 26 are in
   stock, none on sale, none without an image — so these are written blind on
   purpose and verified by faking the markup in the lab.
   -------------------------------------------------------------------------- */
body.ast-woo-shop-archive ul.products li.product .onsale{
  position:absolute;
  /* ⚠ `right:auto` and `bottom:auto` are LOAD BEARING, not tidiness.
     WooCommerce pins this badge top-RIGHT with `right:0;left:auto` at (0,4,2).
     Declaring only `left:10px` here won that one property and left Woo's
     `right:0` standing, so the badge stretched to 279px across the whole card
     instead of being a corner pill. The cascade resolves PER PROPERTY: an
     inherited offset has to be cancelled, not just overridden on one side. */
  top:10px;
  left:10px;
  right:auto;
  bottom:auto;
  z-index:2;
  background:var(--white);
  color:var(--ink);
  font-family:var(--font-body);
  font-size:11px;
  font-weight:700;
  letter-spacing:.08em;
  text-transform:uppercase;
  padding:4px 8px;
  border-radius:var(--radius);
  margin:0;
  min-height:0;
  min-width:0;
  line-height:1.2;
}
body.ast-woo-shop-archive ul.products li.product.outofstock .astra-shop-thumbnail-wrap{
  opacity:.55;
}
body.ast-woo-shop-archive ul.products li.product .stock{
  font-family:var(--font-body);
  font-size:12px;
  color:rgba(255,255,255,.72);
  margin:0;
}
/* A product with no featured image gets Astra's placeholder; keep the tile
   square so the grid does not go ragged. */
body.ast-woo-shop-archive ul.products li.product .woocommerce-placeholder{
  width:100%;
  aspect-ratio:1/1;
  object-fit:cover;
}

/* --------------------------------------------------------------------------
   8 · Pagination
   -------------------------------------------------------------------------- */
body.ast-woo-shop-archive .woocommerce-pagination{
  margin-top:clamp(28px,3vw,44px);
}

/* The TOP pagination block, added by burly_shop_pagination_top() in
   functions.php. ⚠ `clear:both` IS REQUIRED, not cosmetic: the result count is
   `float:left` and the sort control `float:right`, so both are out of normal
   flow and this block slid straight up underneath them — measured all three
   sharing y=284, the pagination overlapping BOTH. Clearing drops it below the
   toolbar where it belongs. The margins are flipped from the bottom instance:
   nothing above, space below, because the grid follows it rather than the
   footer. */
body.ast-woo-shop-archive .burly-pagination-top{
  clear:both;
}
body.ast-woo-shop-archive .burly-pagination-top .woocommerce-pagination{
  margin-top:0;
  margin-bottom:clamp(20px,2.2vw,32px);
}
body.ast-woo-shop-archive .woocommerce-pagination ul.page-numbers{
  border:0;
  margin:0;
  padding:0;
  display:flex;
  flex-wrap:wrap;
  gap:8px;
  justify-content:center;
}
body.ast-woo-shop-archive .woocommerce-pagination ul.page-numbers li{
  border:0;
  margin:0;
  overflow:visible;
}
/* Astra clearfixes this list with ::before/::after{content:" ";display:table}.
   Harmless while it is a block, but the flex row above turns both into real
   flex items and they take a gap each. (0,3,2) beats Astra's (0,3,1). */
body.ast-woo-shop-archive .woocommerce-pagination ul.page-numbers::before,
body.ast-woo-shop-archive .woocommerce-pagination ul.page-numbers::after{
  display:none;
}
/* ⚠ `li` IS LOAD BEARING on the next three rules, and leaving it out was a
   real visible bug. WooCommerce puts the class `page-numbers` on the <ul> AND
   on every link and span inside it:

       nav.woocommerce-pagination > ul.page-numbers > li > a.page-numbers

   so `.woocommerce-pagination .page-numbers` matched the LIST as well as the
   items. The list then picked up the item background, height and border, and
   because :hover propagates to ancestors, hovering any page number lit the
   whole <ul> up as a pale bar 1216px wide — the full content width. The owner
   saw it as "a light box that goes the width of the page". Scoping to
   `li .page-numbers` targets only the real items. */
body.ast-woo-shop-archive .woocommerce-pagination li .page-numbers{
  font-family:var(--font-body);
  font-size:14px;
  font-weight:600;
  color:rgba(255,255,255,.78);
  background:none;
  border:1px solid rgba(255,255,255,.22);
  border-radius:var(--radius);
  min-width:40px;
  height:40px;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  padding:0 10px;
  text-decoration:none;
  transition:background .2s var(--ease),border-color .2s var(--ease);
}
body.ast-woo-shop-archive .woocommerce-pagination li .page-numbers:hover{
  background:rgba(255,255,255,.09);
  border-color:rgba(255,255,255,.38);
  color:var(--white);
}
body.ast-woo-shop-archive .woocommerce-pagination li .page-numbers.current{
  background:var(--white);
  border-color:var(--white);
  color:var(--ink);
}

/* ==========================================================================
   CART  — added 2026-10-08
   Owner's direction: mesh apple.com/shop/bag with the Marc Jacobs bag and the
   OFS cart, "have the header be blue but below with all the products and
   details be white background".

   So: the brand navy band at the top, then a WHITE sheet for the contents —
   a deliberate departure from the navy of every other page, and the one place
   on the site where ink-on-white is correct. Items left, summary right.

   CSS ONLY. Page 2002 holds just the [woocommerce_cart] shortcode, so the
   markup is WooCommerce's and nothing here is a template override.
   ========================================================================== */

/* 1 · Frame. Astra's separate-container pads the article 80/100; the band has
   to reach the viewport edges, so the padding moves inward to .entry-content.
   #primary already clears the fixed chrome via the body:not(.burly-page) rule. */
body.woocommerce-cart{background:var(--white)}
body.woocommerce-cart #primary article{padding:0;margin:0}

/* 2 · The navy band. article is full width once its padding is gone, so this
   is full-bleed with no 100vw trick and no scrollbar risk. */
body.woocommerce-cart .entry-header{
  background:var(--blue);
  border-bottom:1px solid var(--blue-edge);
  margin:0 0 clamp(28px,3vw,44px);
  padding-block:clamp(26px,2.6vw,38px);
}
body.woocommerce-cart h1.entry-title{
  max-width:var(--maxw);
  margin:0 auto;
  padding-inline:var(--pad-gutter);
  font-family:var(--font-display);
  font-weight:700;
  font-size:clamp(30px,3.4vw,46px);
  line-height:1.04;
  letter-spacing:-.01em;
  color:var(--white);
}

/* 3 · The white sheet */
body.woocommerce-cart .entry-content{
  max-width:var(--maxw);
  margin-inline:auto;
  padding-inline:var(--pad-gutter);
  padding-bottom:clamp(56px,6vw,88px);
  color:var(--ink);
}

/* 4 · Two columns. `div.woocommerce` is the parent of BOTH the items form and
   the collaterals, which is what makes this reachable without touching markup.
   Woo ships it at max-width:1200px with auto side margins; both are released. */
body.woocommerce-cart .entry-content .woocommerce{
  max-width:none;
  margin:0;
  display:grid;
  grid-template-columns:minmax(0,1fr) 360px;
  gap:clamp(28px,3.4vw,56px);
  align-items:start;
}
body.woocommerce-cart .woocommerce-notices-wrapper{grid-column:1 / -1}
body.woocommerce-cart form.woocommerce-cart-form{grid-column:1;margin:0}
body.woocommerce-cart .cart-collaterals{
  grid-column:2;
  float:none;
  width:auto;
  margin:0;
}

/* 5 · Items table.
   ⚠ Each row becomes a GRID so the remove control can sit LAST, as it does in
   all three references. Table cells cannot be reordered any other way: `order`
   does nothing on a td in table layout. The thead carries the same template so
   the labels stay over their columns. */
body.woocommerce-cart table.shop_table.cart{
  width:100%;
  margin:0;
  border:0;
  border-collapse:collapse;
  display:block;
}
body.woocommerce-cart table.shop_table.cart thead,
body.woocommerce-cart table.shop_table.cart tbody{display:block}

body.woocommerce-cart table.shop_table.cart thead tr,
body.woocommerce-cart table.shop_table.cart tbody tr.cart_item{
  display:grid;
  grid-template-columns:96px minmax(0,1fr) 92px 92px 92px 30px;
  align-items:center;
  gap:16px;
}
/* ⚠ `grid-row:1` IS LOAD BEARING. `td.product-remove` comes FIRST in
   WooCommerce's DOM and is placed last visually, so with auto-placement the
   grid put it at row 1 column 6, advanced its cursor past column 1, and then
   had to drop every other cell onto ROW 2 — measured the remove button at
   y=665 and the whole rest of the row at y=757. Pinning every cell to row 1
   makes placement explicit instead of order-dependent. The mobile block below
   overrides this with `grid-area`, which sets row and column together. */
body.woocommerce-cart table.shop_table.cart thead th,
body.woocommerce-cart table.shop_table.cart tbody tr.cart_item td{grid-row:1}

body.woocommerce-cart table.shop_table.cart th.product-thumbnail,
body.woocommerce-cart table.shop_table.cart td.product-thumbnail{grid-column:1}
body.woocommerce-cart table.shop_table.cart th.product-name,
body.woocommerce-cart table.shop_table.cart td.product-name{grid-column:2}
body.woocommerce-cart table.shop_table.cart th.product-price,
body.woocommerce-cart table.shop_table.cart td.product-price{grid-column:3}
body.woocommerce-cart table.shop_table.cart th.product-quantity,
body.woocommerce-cart table.shop_table.cart td.product-quantity{grid-column:4}
body.woocommerce-cart table.shop_table.cart th.product-subtotal,
body.woocommerce-cart table.shop_table.cart td.product-subtotal{grid-column:5}
body.woocommerce-cart table.shop_table.cart th.product-remove,
body.woocommerce-cart table.shop_table.cart td.product-remove{grid-column:6}

body.woocommerce-cart table.shop_table.cart thead th{
  font-family:var(--font-body);
  font-size:11px;
  font-weight:600;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:var(--muted-ink);
  background:none;
  border:0;
  padding:0 0 14px;
  text-align:left;
}
body.woocommerce-cart table.shop_table.cart thead tr{
  border-bottom:1px solid var(--rule);
}
body.woocommerce-cart table.shop_table.cart tbody tr.cart_item{
  border-bottom:1px solid var(--rule);
}
body.woocommerce-cart table.shop_table.cart tbody tr.cart_item td{
  background:none;
  border:0;
  padding:22px 0;
  vertical-align:middle;
  font-size:15px;
  color:var(--ink);
}

/* ⚠ Woo and Astra both size this image small; the chain has to be long enough
   to beat `.woocommerce table.shop_table_responsive ... .product-thumbnail img`. */
body.woocommerce-cart table.shop_table.cart td.product-thumbnail img{
  width:96px;
  height:96px;
  max-width:none;
  object-fit:cover;
  display:block;
  border-radius:var(--radius);
  margin:0;
}
body.woocommerce-cart table.shop_table.cart td.product-name a{
  font-family:var(--font-display);
  font-weight:700;
  font-size:16px;
  line-height:1.3;
  color:var(--ink);
  text-decoration:none;
}
body.woocommerce-cart table.shop_table.cart td.product-name a:hover{text-decoration:underline}

body.woocommerce-cart table.shop_table.cart td.product-price,
body.woocommerce-cart table.shop_table.cart td.product-subtotal{
  font-variant-numeric:tabular-nums;
}
body.woocommerce-cart table.shop_table.cart td.product-subtotal{font-weight:600}

/* quantity */
body.woocommerce-cart table.shop_table.cart td.product-quantity .quantity{margin:0}
body.woocommerce-cart table.shop_table.cart td.product-quantity input.qty{
  width:82px;
  padding:10px 8px;
  text-align:center;
  font-family:var(--font-body);
  /* ⚠ 15px IS ONE PIXEL UNDER THE iOS ZOOM THRESHOLD. Left at 15px here so
     the desktop cart is unchanged, and lifted to 16px at phone widths by
     rule 3.1 in the CHECKOUT section. Do not raise this to 16px without
     deleting the override there, and do not LOWER it anywhere. */
  font-size:15px;
  color:var(--ink);
  background:var(--white);
  border:1px solid var(--rule);
  border-radius:var(--radius);
}

/* remove, last in the row */
body.woocommerce-cart table.shop_table.cart td.product-remove{text-align:right}
body.woocommerce-cart table.shop_table.cart td.product-remove a.remove{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:30px;
  height:30px;
  border-radius:50%;
  color:var(--muted-ink);
  background:none;
  font-size:0;
  transition:background .2s var(--ease), color .2s var(--ease);
}
body.woocommerce-cart table.shop_table.cart td.product-remove a.remove:hover{
  background:rgba(0,19,46,.07);
  color:var(--ink);
}

/* the coupon / update row spans the whole table */
/* ⚠ The ROW needs display:block too, and the `.coupon` float has to be
   cancelled with a long enough chain. Two faults were stacked here:

   1. `td.actions` sat in a `tr` that was still a table-row inside a
      display:block table, so an anonymous table box sized it to its content:
      measured 391px of an available 807.
   2. WooCommerce ships `.coupon{float:left}`, which outranked a short reset,
      so the coupon div was 249px wide and its 150px Apply button wrapped onto
      a second line while Update cart sat beside the input.

   Making the row a flex strip fixes both at once and puts Apply on the left
   with Update cart pushed right, which is what the references do. */
body.woocommerce-cart table.shop_table.cart tbody tr:not(.cart_item){
  display:block;
}
body.woocommerce-cart table.shop_table.cart tbody tr:not(.cart_item) td.actions{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:16px;
  flex-wrap:wrap;
  width:100%;
  border:0;
  padding:24px 0 0;
  background:none;
}
body.woocommerce-cart table.shop_table.cart td.actions .coupon{
  display:flex;
  gap:10px;
  align-items:center;
  /* ⚠ nowrap is the operative declaration. Another sheet sets
     `flex-wrap:wrap` on .coupon, and a wrapping flex container is allowed to
     size itself BELOW its content, so this box settled at 249px and folded its
     own 150px Apply button onto a second line even with 807px available.
     The width:auto and float:none resets were not enough on their own. */
  flex-wrap:nowrap;
  /* ⚠ `0 0 auto`, NOT `0 1 auto`. As a shrinkable flex item this box gave way
     to 249px even with 807px free, and the input inside it collapsed to 80px —
     narrower than its own "Coupon code" placeholder. Refusing to shrink keeps
     the input at its 210px basis. The mobile block below gives it a full row
     instead, where shrinking is the right behaviour. */
  flex:0 0 auto;
  float:none;
  width:auto;
  margin:0;
  padding:0;
}
body.woocommerce-cart table.shop_table.cart td.actions .coupon button.button{
  flex:0 0 auto;
  white-space:nowrap;
}
/* ⚠ `flex:0 0 210px` PLUS an explicit `width`. With `flex:0 1 210px` the
   input kept resolving to 80px at every width above 1024: no stylesheet
   declares that number, it is the input's own min-content size winning a
   circular sizing pass — .coupon sized itself from its contents while the
   contents sized themselves from .coupon. Refusing to shrink and stating a
   width breaks the circle. The mobile block relaxes both. */
body.woocommerce-cart td.actions .coupon input#coupon_code{
  flex:0 0 210px;
  width:210px;
  min-width:0;
  padding:11px 12px;
  font-family:var(--font-body);
  /* ⚠ 14px IS UNDER THE iOS ZOOM THRESHOLD — see the note on input.qty above
     and rule 3.1 in the CHECKOUT section, which lifts it at phone widths. */
  font-size:14px;
  color:var(--ink);
  background:var(--white);
  border:1px solid var(--rule);
  border-radius:var(--radius);
}
body.woocommerce-cart td.actions button.button{
  font-family:var(--font-body);
  font-size:13px;
  font-weight:600;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:var(--ink);
  background:none;
  border:1px solid var(--ink);
  border-radius:var(--radius);
  padding:11px 18px;
  transition:background .2s var(--ease), color .2s var(--ease);
}
body.woocommerce-cart td.actions button.button:hover{
  background:var(--ink);
  color:var(--white);
}
body.woocommerce-cart td.actions button.button:disabled{
  opacity:.4;
  border-color:var(--rule);
  color:var(--muted-ink);
  background:none;
}

/* 6 · Summary panel. Sticky so it stays with a long cart, offset by the fixed
   chrome so it never slides underneath it. */
/* ⚠ The `.cart-collaterals` step is required: WooCommerce ships
   `.woocommerce .cart-collaterals .cart_totals{width:48%;float:right}` at
   (0,3,0), so a shorter `body.woocommerce-cart .cart_totals` at (0,2,1) lost
   both properties and the panel rendered 173px wide, floated, inside its own
   360px column. */
body.woocommerce-cart .cart-collaterals .cart_totals{
  float:none;
  width:auto;
  margin:0;
  padding:24px;
  background:var(--white);
  border:1px solid var(--rule);
  border-radius:var(--radius);
  position:sticky;
  top:calc(var(--ticker-h) + var(--header-h) + 20px);
}
body.woocommerce-cart .cart_totals h2{
  font-family:var(--font-display);
  font-size:13px;
  font-weight:700;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--ink);
  margin:0 0 6px;
  padding:0 0 16px;
  border-bottom:1px solid var(--rule);
}
body.woocommerce-cart .cart_totals table{
  width:100%;
  margin:0;
  border:0;
  border-collapse:collapse;
}
body.woocommerce-cart .cart_totals table th,
body.woocommerce-cart .cart_totals table td{
  background:none;
  border:0;
  border-bottom:1px solid var(--rule);
  padding:14px 0;
  font-size:14px;
  vertical-align:top;
}
body.woocommerce-cart .cart_totals table th{
  font-family:var(--font-body);
  font-weight:500;
  color:var(--muted-ink);
  text-align:left;
  width:40%;
}
body.woocommerce-cart .cart_totals table td{
  text-align:right;
  color:var(--ink);
  font-variant-numeric:tabular-nums;
}
body.woocommerce-cart .cart_totals tr.order-total th,
body.woocommerce-cart .cart_totals tr.order-total td{
  border-bottom:0;
  padding-top:18px;
  font-size:18px;
  font-weight:700;
  color:var(--ink);
}
body.woocommerce-cart .cart_totals tr.order-total th{font-family:var(--font-display)}

/* shipping choices read left, not right: they are a list, not a figure */
body.woocommerce-cart .cart_totals ul#shipping_method{
  list-style:none;
  margin:0;
  padding:0;
  text-align:left;
}
/* ⚠ DO NOT MAKE THESE LIST ITEMS FLEX. Astra ships
   `ul#shipping_method li{padding:.25em 0 .25em 22px; text-indent:-22px}` — a
   hanging indent that sits the radio at the border edge and aligns wrapped
   lines under the text. Turning the li into a flex container left the
   `text-indent:-22px` applying to the label, which dragged its text 22px LEFT,
   straight on top of the radio. Measured: label box at 1161 but its text
   rendering from 1139, a 13px overlap on every option.

   The selector carrying it is
   `ul#shipping_method li:not(.elementor-widget-woocommerce-cart #shipping_method li)`,
   which is (2,1,3) because `:not()` takes its most specific argument — beating
   it would need two ids. The layout already worked before we touched it, so
   only type and spacing are set here. Restructuring a working layout was the
   whole mistake. */
body.woocommerce-cart .cart_totals ul#shipping_method li{
  margin:0 0 9px;
  font-size:13px;
  line-height:1.45;
}
body.woocommerce-cart .cart_totals ul#shipping_method li:last-child{margin-bottom:0}
body.woocommerce-cart .cart_totals ul#shipping_method label{color:var(--ink);cursor:pointer}
body.woocommerce-cart .cart_totals .woocommerce-shipping-destination{
  font-size:12px;
  color:var(--muted-ink);
  margin:10px 0 0;
  text-align:left;
}

/* 7 · Checkout button */
body.woocommerce-cart .wc-proceed-to-checkout{padding:20px 0 0;margin:0}
body.woocommerce-cart .wc-proceed-to-checkout a.checkout-button{
  display:block;
  width:100%;
  margin:0;
  padding:17px 20px;
  text-align:center;
  text-decoration:none;
  font-family:var(--font-display);
  font-size:14px;
  font-weight:700;
  letter-spacing:.1em;
  text-transform:uppercase;
  color:var(--white);
  background:var(--ink);
  border:1px solid var(--ink);
  border-radius:var(--radius);
  transition:background .2s var(--ease);
}
body.woocommerce-cart .wc-proceed-to-checkout a.checkout-button:hover{
  background:#0b2a52;
  border-color:#0b2a52;
  color:var(--white);
}

/* 8 · Notices, toned to the brand. Woo's default is a green bar; on a white
   sheet under a navy band it reads as an error state rather than a receipt. */
/* No bottom margin: this wrapper is a GRID ITEM spanning both columns, so the
   grid's own row gap already separates it from the table. The margin was
   additive and left ~79px of dead space above the items. */
body.woocommerce-cart .woocommerce-notices-wrapper{margin:0}
/* ⚠ The left padding clears Woo's tick. `.woocommerce-message::before` is an
   absolutely positioned WooCommerce-font glyph at left:21px, so a symmetrical
   18px padding ran the message text straight under it. */
body.woocommerce-cart .woocommerce-message{
  background:rgba(0,19,46,.035);
  border:1px solid var(--rule);
  border-top:2px solid var(--ink);
  border-radius:var(--radius);
  color:var(--ink);
  font-family:var(--font-body);
  font-size:14px;
  padding:16px 18px 16px 48px;
  margin:0 0 10px;
}
body.woocommerce-cart .woocommerce-message::before{
  left:20px;
  top:17px;
  color:var(--ink);
}
body.woocommerce-cart .woocommerce-message a.button{
  font-family:var(--font-body);
  font-size:12px;
  font-weight:600;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:var(--ink);
  background:none;
  border:1px solid var(--ink);
  border-radius:var(--radius);
  padding:9px 14px;
}
body.woocommerce-cart .woocommerce-message a.button:hover{
  background:var(--ink);
  color:var(--white);
}

/* 9 · Cross-sells. ⚠ NOTHING RENDERS HERE TODAY: not one product has a
   cross-sell set (checked 2026-10-08, 0 of 26). The owner adds them per
   product under Products > Edit > Linked Products > Cross-sells, and the
   "You May Also Like" band in the reference then appears already styled. */
body.woocommerce-cart .cross-sells{
  grid-column:1 / -1;
  margin:clamp(40px,4.4vw,64px) 0 0;
  padding-top:clamp(28px,3vw,40px);
  border-top:1px solid var(--rule);
}
body.woocommerce-cart .cross-sells > h2{
  font-family:var(--font-display);
  font-size:clamp(20px,2.2vw,26px);
  font-weight:700;
  color:var(--ink);
  margin:0 0 clamp(18px,2vw,26px);
}
/* ⚠ THE FULL CHAIN IS REQUIRED ON EVERY RULE BELOW, and for a reason worth
   remembering: the catalog's card styling is scoped to
   `body.ast-woo-shop-archive`, WHICH IS NOT ON THE CART. These are the same
   Astra cards rendered in a different place, so every fight we already won on
   the catalog has to be won again here — including Astra's (0,4,2)
   `.ast-woo-product-category` rule, which otherwise puts the eyebrow back to
   BLACK at opacity .6, measured rgb(0,0,0) before this block existed.

   These sit on the WHITE sheet, so they are ink-on-white, not the catalog's
   translucent-white-on-navy. */
/* ⚠ `:not(.elementor-grid)` IS LOAD BEARING, and it is not decoration.
   WooCommerce ships
   `.woocommerce-page ul.products:not(.elementor-grid).columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}`
   which is (0,4,1) — FOUR classes, because `:not()` contributes its argument's
   specificity. A plain `body.woocommerce-cart .cross-sells ul.products` is
   (0,3,2), and CLASS COUNT OUTRANKS ELEMENT COUNT, so Woo won at every width
   and the cross-sells stayed three-up down to 360px with 96px cards. Our base
   rule had looked correct only because it declared the same `repeat(3)` value.
   Mirroring Woo's own `:not()` takes us to (0,4,2) without hard-coding a
   `.columns-N` class, so it survives a change to the column count in
   burly_cross_sells_three_up(). */
body.woocommerce-cart .cross-sells ul.products:not(.elementor-grid){
  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  gap:var(--gap);
  margin:0;
  padding:0;
  list-style:none;
}
body.woocommerce-cart .cross-sells ul.products li.product{
  float:none;
  width:auto;
  margin:0;
  padding:0;
  background:var(--white);
  border:1px solid var(--rule);
  border-radius:var(--radius);
  overflow:hidden;
  transition:transform .3s var(--ease), box-shadow .3s var(--ease);
}
@media (hover:hover){
  body.woocommerce-cart .cross-sells ul.products li.product:hover{
    transform:translateY(-4px);
    box-shadow:0 14px 30px rgba(0,19,46,.13);
  }
}
@media (prefers-reduced-motion:reduce){
  body.woocommerce-cart .cross-sells ul.products li.product{transition:none}
  body.woocommerce-cart .cross-sells ul.products li.product:hover{transform:none}
}

/* Astra's on-card overlay, removed here too — same owner decision as the
   catalog, and it needs saying again because the catalog rule cannot reach. */
body.woocommerce-cart .cross-sells .ast-on-card-button{display:none}

body.woocommerce-cart .cross-sells ul.products li.product a img{
  width:100%;
  height:auto;
  margin:0;
  aspect-ratio:1/1;
  object-fit:cover;
  display:block;
}
body.woocommerce-cart .cross-sells .astra-shop-thumbnail-wrap{margin:0}
body.woocommerce-cart .cross-sells .astra-shop-summary-wrap{
  padding:14px 16px 16px;
  display:flex;
  flex-direction:column;
  gap:6px;
}
body.woocommerce-cart .cross-sells ul.products li.product .ast-woo-product-category{
  font-family:var(--font-body);
  font-size:11px;
  font-weight:600;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:var(--muted-ink);
  opacity:1;
  margin-bottom:0;
  line-height:1;
}
body.woocommerce-cart .cross-sells ul.products li.product .ast-loop-product__link{
  text-decoration:none;
  display:block;
}
body.woocommerce-cart .cross-sells ul.products li.product h2.woocommerce-loop-product__title{
  font-family:var(--font-display);
  font-weight:700;
  font-size:15px;
  line-height:1.3;
  color:var(--ink);
  margin:0;
  padding:0;
}
body.woocommerce-cart .cross-sells ul.products li.product .price{
  font-family:var(--font-body);
  font-size:14px;
  font-weight:600;
  color:var(--ink);
  font-variant-numeric:tabular-nums;
  margin:0;
}

/* ⚠⚠ STAYS THREE-UP ON PHONES, 2026-10-09. Owner, on the bag page: *"the
   'Related products' the pictures need to be wayyyy smaller and sit in one
   row"*.

   These two blocks used to step the grid down to 2 columns at 760 and to a
   SINGLE column at 420. That single column was the whole complaint: at 390 it
   made each related-product photo 350x350, so three of them filled more than a
   screen of vertical scroll on the page where the customer is trying to get to
   checkout. Three-up puts them at 108x108 — the same linear thirding the owner
   asked for, about a tenth of the area.

   The card furniture has to come down with the picture or the text overflows a
   110px card, so the category eyebrow, title and price all step down here too.
   Measured at 390: 110px cards, 108px images, all three sharing one row
   (top=364 for all three), no sideways scroll.

   ⚠ `:not(.elementor-grid)` is still load bearing — see the note on the base
   rule above. WooCommerce's own `ul.products:not(.elementor-grid).columns-3` is
   (0,4,1) and would win at every width without it. */
@media (max-width:760px){
  body.woocommerce-cart .cross-sells ul.products:not(.elementor-grid){
    grid-template-columns:repeat(3,minmax(0,1fr));
    gap:10px;
  }
  body.woocommerce-cart .cross-sells .astra-shop-summary-wrap{padding:8px 8px 10px;gap:3px}
  body.woocommerce-cart .cross-sells ul.products li.product .ast-woo-product-category{font-size:9px;letter-spacing:.08em}
  body.woocommerce-cart .cross-sells ul.products li.product h2.woocommerce-loop-product__title{font-size:12px;line-height:1.25}
  body.woocommerce-cart .cross-sells ul.products li.product .price{font-size:12px}
}
@media (max-width:420px){
  /* ⚠ this was the single-column rule. It now RE-ASSERTS three-up, because it
     is later in the file than the 760 block and would otherwise undo it. */
  body.woocommerce-cart .cross-sells ul.products:not(.elementor-grid){
    grid-template-columns:repeat(3,minmax(0,1fr));
  }
}

/* 10 · Empty cart */
body.woocommerce-cart .cart-empty{
  font-family:var(--font-display);
  font-size:clamp(20px,2.2vw,26px);
  font-weight:700;
  color:var(--ink);
  margin:0 0 20px;
}
body.woocommerce-cart .return-to-shop a.button{
  display:inline-block;
  font-family:var(--font-display);
  font-size:14px;
  font-weight:700;
  letter-spacing:.1em;
  text-transform:uppercase;
  color:var(--white);
  background:var(--ink);
  border:1px solid var(--ink);
  border-radius:var(--radius);
  padding:15px 26px;
  text-decoration:none;
}

/* 11 · One column below 980. The summary moves under the items, and each row
   becomes thumbnail + stacked detail so nothing is squeezed. */
/* ⚠ 1200, not 980. The row's fixed columns come to 402px plus 80px of gaps,
   so the items column needs ~680px before the product name is left with a
   usable ~200px. At 981 the two-column layout turned on with only 553px there
   and the row blew out from 141px to 309px as cells wrapped. Measured, not
   guessed.

   ⚠ `.cart-collaterals` in the `position:static` selector is REQUIRED. The
   base rule had to become `.cart-collaterals .cart_totals` at (0,3,1) to beat
   WooCommerce's float, which then outranked this override at (0,2,1) and left
   the summary sticky while stacked. Raising one rule's specificity silently
   broke its own media query. */
@media (max-width:1200px){
  body.woocommerce-cart .entry-content .woocommerce{
    grid-template-columns:minmax(0,1fr);
  }
  body.woocommerce-cart form.woocommerce-cart-form,
  body.woocommerce-cart .cart-collaterals{grid-column:1}
  body.woocommerce-cart .cart-collaterals .cart_totals{position:static}
}
/* ⚠⚠ 768, AND IT MUST MATCH WOOCOMMERCE'S OWN BREAKPOINT.
   `woocommerce-smallscreen-grid.min.css` is enqueued with
   `media="only screen and (max-width:921px)"` — Astra overrides WooCommerce's
   own 768px default with its tablet width. ⚠ THE CONDITION IS ON THE LINK, not
   in an @media block, so a CSSOM walk reports these rules as `(all)`; the real
   number only turned up by reading `styleSheet.media.mediaText` after labels
   reappeared at 860. Below 921 it runs its own responsive table system:

     table.shop_table_responsive thead      {display:none}
     table.shop_table_responsive tr         {display:block}
     table.shop_table_responsive tr td      {display:block; text-align:right !important}
     table.shop_table_responsive tr td::before {content:attr(data-title)": "; float:left}

   Our block first stopped at 720 and then at 768; both left a band where that
   sheet hid the thead while our desktop grid was still running, and its
   ::before labels printed "Product:", "Price:", "Quantity:", "Subtotal:" on
   top of the real cells. At 390 every row was a pile of overlapping text, and
   at 860 four labels were still rendering. Match 921 exactly. */
@media (max-width:921px){
  body.woocommerce-cart table.shop_table.cart thead{display:none}

  /* Woo's duplicate data-title labels. We supply the layout, so they go. */
  body.woocommerce-cart table.shop_table.cart tbody tr.cart_item td::before{
    display:none;
  }

  body.woocommerce-cart table.shop_table.cart tbody tr.cart_item{
    grid-template-columns:76px minmax(0,1fr) 30px;
    grid-template-areas:
      "thumb name   remove"
      "thumb price  price"
      "thumb qty    subtotal";
    row-gap:8px;
    column-gap:14px;
    align-items:start;
    padding:18px 0;
  }

  /* ⚠ `justify-self` BEATS `text-align:right !important` WITHOUT an
     !important of our own. Woo forces the text right inside a stretched cell;
     shrinking the cell to its content with justify-self makes the alignment
     inside it stop mattering. Escalating would have started an !important war
     on a commerce page, which is exactly what we do not do here. */
  body.woocommerce-cart table.shop_table.cart tbody tr.cart_item td{
    padding:0;
    justify-self:start;
  }
  body.woocommerce-cart table.shop_table.cart tbody tr.cart_item td.product-thumbnail{grid-area:thumb}
  body.woocommerce-cart table.shop_table.cart tbody tr.cart_item td.product-name{grid-area:name}
  body.woocommerce-cart table.shop_table.cart tbody tr.cart_item td.product-price{grid-area:price}
  body.woocommerce-cart table.shop_table.cart tbody tr.cart_item td.product-quantity{grid-area:qty}
  body.woocommerce-cart table.shop_table.cart tbody tr.cart_item td.product-subtotal{
    grid-area:subtotal;
    justify-self:end;
    align-self:center;
    font-weight:700;
  }
  body.woocommerce-cart table.shop_table.cart tbody tr.cart_item td.product-remove{
    grid-area:remove;
    justify-self:end;
  }
  body.woocommerce-cart table.shop_table.cart td.product-thumbnail img{width:76px;height:76px}
  body.woocommerce-cart table.shop_table.cart td.product-quantity input.qty{width:72px}

  /* the coupon takes a full row of its own here, so shrinking is correct */
  body.woocommerce-cart table.shop_table.cart td.actions .coupon{flex:1 1 100%}
  body.woocommerce-cart td.actions .coupon input#coupon_code{flex:1 1 auto;width:auto}
}

/* ==========================================================================
   SINGLE PRODUCT PAGE — 2026-10-08

   Scope is `body.single-product`, ONE class, which is the same weight as
   `body.woocommerce-cart` and `body.ast-woo-shop-archive` and for the same
   reason: it is enough to beat Astra's page-level rules without starting a
   specificity war we then have to keep escalating. Where a rule below carries
   a long chain instead, there is a comment saying which rule forced it.

   MEASURED BASELINE BEFORE ANY OF THIS (staging, /product/bacteriostatic-water/,
   1440 wide):

     page background          rgb(220,220,220)   Astra's grey, not the brand
     .ast-woocommerce-container  white sheet, 80.1px / 100.05px padding
     .ast-article-single      white
     gallery                  620 x 620
     .summary                 570 x 290          <- 330px of DEAD SPACE
     h1                       Lexend 30px / 400 / rgb(0,0,0)
     .price                   Hind 22.5px / 700 / rgb(0,0,0)
     add to cart              transparent background, black text, radius 0
     category                 printed TWICE, as the eyebrow and in .product_meta

   The dead space is why the buy column gained the lab block, the facts and the
   specs; after those it measures 779px against the 620px gallery. See the long
   note above burly_pdp_summary_blocks() in functions.php for why the ORDER of
   this column is a customizer array and not hook priorities.
   ========================================================================== */

/* 1 · Page ground. The grey sheet is Astra's `ast-separate-container`, which
   paints BOTH the container and the article, so both have to be cleared or a
   white box shows through at one width and not another. */
body.single-product{background:var(--blue)}
body.single-product.woocommerce .ast-woocommerce-container{
  background:none;
  max-width:var(--maxw);
  margin-inline:auto;
  padding-inline:var(--pad-gutter);
  padding-block:clamp(26px,3.4vw,54px);
}
body.single-product.woocommerce .ast-article-single{
  background:none;
  border:0;
  box-shadow:none;
  padding:0;
  margin:0;
}

/* ⚠ THE THREE SELECTORS ABOVE CARRY AN EXTRA CLASS ON PURPOSE. Measured on
   staging 2026-10-08 with a CSSOM walk that reports computed specificity:

     .woocommerce.ast-separate-container .ast-woocommerce-container      (0,3,0)
     .ast-separate-container .ast-article-single:not(.ast-related-post)  (0,3,0)
     .woocommerce-js div.product p.price                                 (0,3,2)

   A plain `body.single-product .ast-woocommerce-container` is (0,2,1) and LOST
   its background while winning max-width and padding from the same rule, which
   is the per-property cascade: a rule can win one declaration and lose another.
   `body.single-product.woocommerce ...` is (0,3,1) and wins.

   The price was the clearer trap. Woo's rule declares colour, font-size AND
   font-weight at (0,3,2); ours at (0,3,1) tied on class count and lost on
   element count, so the price rendered BLACK at 22.5px (1.5rem of a 15px root)
   while keeping our Archivo, because nothing else declares font-family. Adding
   `div.product` and `p` takes it to (0,4,3).

   ⚠ AND THE INSTRUMENT NEEDED VALIDATING FIRST. The same walk run in the
   file:// lab found NONE of these: staging's stylesheets are cross-origin
   there, so `sh.cssRules` throws and every external sheet was silently
   skipped. It reported only Astra's inline <style> blocks and looked like a
   complete answer. Run specificity walks against STAGING, not the lab, and
   make the script report the sheets it could not read. */

/* Notices ("added to cart") sit above everything on navy, so they need their
   own colour; left alone they are dark text on a dark ground. */
body.single-product .woocommerce-notices-wrapper .woocommerce-message,
body.single-product .woocommerce-notices-wrapper .woocommerce-info,
body.single-product .woocommerce-notices-wrapper .woocommerce-error{
  background:rgba(255,255,255,.06);
  border-top:2px solid var(--white);
  color:var(--white);
  border-radius:var(--radius);
}
body.single-product .woocommerce-notices-wrapper a{color:var(--white)}

/* --------------------------------------------------------------------------
   2 · The image panel
   The owner's call was a light panel behind every image, because the 23 vial
   renders carry a mauve-grey background baked in while the three tee photos are
   on pure white, and on navy that difference reads as a mistake.

   ⚠ NO PADDING INSIDE THE PANEL, deliberately. The renders are already square
   with their own background, so padding would show the panel's white as a
   border around a mauve-grey square: a square inside a square. The image fills
   the panel edge to edge and the panel's white only shows through where an
   image is genuinely transparent.

   ⚠ `object-fit:cover`, matching the catalog and NOT `.best-card`'s `contain`.
   Every catalogue image was checked and all are opaque squares, so there is
   nothing to letterbox.
   -------------------------------------------------------------------------- */
body.single-product .woocommerce-product-gallery{
  background:var(--white);
  border-radius:var(--radius);
  overflow:hidden;
  margin-bottom:0;
}
body.single-product .woocommerce-product-gallery__wrapper{margin:0}
body.single-product .woocommerce-product-gallery__image{margin:0}
body.single-product .woocommerce-product-gallery img{
  display:block;
  width:100%;
  height:auto;
  margin:0;
  aspect-ratio:1/1;
  object-fit:cover;
}
/* Woo's zoom trigger is a white circle on a white panel. Give it an edge. */
body.single-product .woocommerce-product-gallery__trigger{
  background:var(--white);
  border:1px solid rgba(0,19,46,.18);
  border-radius:50%;
  box-shadow:0 2px 10px rgba(0,19,46,.14);
}

/* --------------------------------------------------------------------------
   3 · The buy column
   -------------------------------------------------------------------------- */
body.single-product .summary.entry-summary{margin-bottom:0}

/* Breadcrumb. Astra puts it INSIDE .summary rather than above the columns, so
   it reads as the first line of the buy column. Kept (unlike the catalog, where
   it was redundant at depth 1) because here it is the only way back up to the
   category. */
body.single-product .summary .woocommerce-breadcrumb{
  font-family:var(--font-body);
  font-size:12px;
  letter-spacing:.01em;
  color:rgba(255,255,255,.52);
  margin:0 0 16px;
  padding:0;
}
body.single-product .summary .woocommerce-breadcrumb a{
  color:rgba(255,255,255,.72);
  text-decoration:none;
}
body.single-product .summary .woocommerce-breadcrumb a:hover{
  color:var(--white);
  text-decoration:underline;
}

/* Category eyebrow */
body.single-product .single-product-category{
  display:block;
  font-family:var(--font-body);
  font-size:11px;
  font-weight:600;
  letter-spacing:.12em;
  text-transform:uppercase;
  line-height:1;
  margin:0 0 12px;
}
body.single-product .single-product-category a{
  color:rgba(255,255,255,.72);
  text-decoration:none;
}
body.single-product .single-product-category a:hover{color:var(--white)}

/* Title. Astra ships Lexend 30px weight 400; this is the brand display face at
   a size that reads as the page's subject rather than a list item. */
body.single-product h1.product_title{
  font-family:var(--font-display);
  font-size:clamp(27px,3.1vw,40px);
  font-weight:700;
  line-height:1.07;
  letter-spacing:-.012em;
  color:var(--white);
  margin:0 0 14px;
  padding:0;
}

/* Short description, above the price by the structure array. */
body.single-product .woocommerce-product-details__short-description{
  font-family:var(--font-body);
  font-size:15px;
  line-height:1.65;
  color:var(--muted);
  margin:0 0 18px;
  max-width:52ch;
}
body.single-product .woocommerce-product-details__short-description p{margin:0 0 10px}
body.single-product .woocommerce-product-details__short-description p:last-child{margin:0}

/* Price */
body.single-product div.product .summary p.price{
  font-family:var(--font-display);
  font-size:clamp(23px,2.3vw,29px);
  font-weight:700;
  line-height:1.1;
  color:var(--white);
  font-variant-numeric:tabular-nums;
  margin:0 0 6px;
}
body.single-product div.product .summary p.price del{
  color:rgba(255,255,255,.5);
  font-weight:400;
  margin-right:8px;
}
body.single-product div.product .summary p.price ins{text-decoration:none}
body.single-product div.product .summary p.price .woocommerce-Price-currencySymbol{
  font-size:.62em;
  vertical-align:.32em;
  margin-right:1px;
}

/* Availability. Astra's default green is unmeasured against navy; this one was
   measured at 9.8:1. The label is quieter than the value on purpose. */
body.single-product .ast-stock-detail{
  font-family:var(--font-body);
  font-size:13px;
  margin:0 0 20px;
  color:rgba(255,255,255,.55);
}
body.single-product .ast-stock-detail .ast-stock-avail{
  color:rgba(255,255,255,.55);
  font-weight:500;
}
body.single-product .ast-stock-detail .stock.in-stock{color:#67d492}
body.single-product .ast-stock-detail .stock.out-of-stock{color:#ff8c8c}

/* --------------------------------------------------------------------------
   4 · Quantity and Add to Bag, directly under the price
   The owner asked for "[1] add to bag right below the price", which is what the
   structure array now does; this makes the pair read as one control.
   -------------------------------------------------------------------------- */
/* ⚠ `:not(.variations_form)` IS LOAD BEARING. A variable product's form does
   not hold the quantity and the button as children at all: it wraps
   `table.variations` AND `.woocommerce-variation-add-to-cart` inside one
   anonymous div, so flexing the FORM shrink-wrapped that whole div to its
   max-content and the tee's Add to Bag measured 111px while a simple
   product's measured 442px. Measured, not guessed. The flex row for a
   variable product lives one level deeper, below.
   This is the shipping-radio lesson again: a `display:flex` that reads as an
   improvement can break a layout that already worked. */
body.single-product form.cart:not(.variations_form){
  display:flex;
  align-items:stretch;
  gap:10px;
}
body.single-product form.cart{margin:0 0 22px}
body.single-product form.cart.variations_form{display:block}
/* ⚠ THE CLEARFIX BECOMES TWO FLEX ITEMS. WooCommerce ships
   `.woocommerce div.product form.cart::before, ...::after{content:" ";display:table}`
   and on a flex container those pseudo-elements are FLEX ITEMS, each taking a
   10px gap of its own. That is where the buy row's 10px indent on BOTH sides
   came from: the quantity started at x=779 inside a form at x=769, and the
   button stopped 10px short of the right edge. Four items, three gaps, two of
   them invisible. (0,3,4) against WooCommerce's (0,3,3). */
body.single-product div.product form.cart::before,
body.single-product div.product form.cart::after{display:none}
/* The variable product's real buy row. (Its three hidden inputs were checked
   and are all `display:none`, so unlike the clearfix above they are not flex
   items and take no gap.)
   The button's own `margin:0` is what makes this row line up; see the
   specificity note on the button rule. Left alone WooCommerce's
   `margin-bottom:1em` made the flex line 52 + 13 = 65 tall and
   `align-items:stretch` stretched the QUANTITY to 65 beside a 52px button.
   The margin, not the alignment, was the fault. */
body.single-product .woocommerce-variation-add-to-cart{
  display:flex;
  align-items:stretch;
  gap:10px;
  flex-wrap:wrap;
}

/* ⚠ THE FULL CHAIN IS REQUIRED. WooCommerce ships
   `.woocommerce-js div.product form.cart div.quantity{margin:0 1em 0 0;float:left}`
   at (0,4,3). A `body.single-product form.cart .quantity` is (0,3,2) and lost
   the margin while winning `flex`, so a 15px margin sat on top of the 10px gap
   and the pair read as two controls instead of one. (0,4,4) here. */
body.single-product div.product form.cart div.quantity{
  margin:0;
  float:none;
  flex:0 0 auto;
  width:auto;
}
/* ⚠ `height:auto`, NOT `height:100%`. A percentage here resolved against a
   `.quantity` that `align-items:stretch` had sized from the TALLEST item in
   the row, so a sibling's margin changed this input's height. It sizes from
   its own min-height instead, which matches the button's. */
body.single-product form.cart .quantity input.qty{
  width:74px;
  height:auto;
  min-height:52px;
  padding:0 8px;
  background:rgba(255,255,255,.06);
  border:1px solid rgba(255,255,255,.22);
  border-radius:var(--radius);
  color:var(--white);
  font-family:var(--font-body);
  font-size:15px;
  font-weight:600;
  text-align:center;
  font-variant-numeric:tabular-nums;
}
body.single-product form.cart .quantity input.qty:focus{
  outline:2px solid var(--white);
  outline-offset:1px;
}
/* ⚠ THE CHAIN AND `flex:1` TOGETHER ARE THE FIX, not either alone. Astra emits
   `astra-settings[single-product-cart-button-width]` as a dynamic inline rule
   setting `width:50%` on this button, which is why it measured 285px of a 570px
   column. `flex:1` alone loses to an explicit width in a flex row, so the width
   is overridden here as well. The setting itself is left at 50 so the owner's
   customizer value is not silently contradicted in two places. */
/* ⚠ FIVE CLASSES AND FOUR ELEMENTS, AND EVERY ONE IS NEEDED. WooCommerce ships
   `.woocommerce-js div.product form.cart .button.single_add_to_cart_button`
   at (0,5,2) for padding, and
   `.woocommerce-js div.product form.cart .button` at (0,4,2) for
   `margin-bottom:1em`. A `body.single-product form.cart button.single_add_to_cart_button`
   is (0,3,3) and LOST BOTH while winning background, colour, font and width
   from the SAME rule -- the per-property cascade again. The button rendered
   with 10px of vertical padding instead of 17px and kept a 13px bottom margin,
   and `min-height:52px` hid the padding loss by propping the height up. On the
   VARIABLE product it also lost `border-radius`, measured 0px against the
   simple product's 2px, which is how one page can look right while another
   does not. This chain is (0,5,4). */
body.single-product div.product form.cart button.button.single_add_to_cart_button{
  flex:1 1 auto;
  width:auto;
  min-height:52px;
  margin:0;
  padding:17px 24px;
  border:1px solid var(--white);
  border-radius:var(--radius);
  background:var(--white);
  color:var(--blue);
  font-family:var(--font-display);
  font-size:13px;
  font-weight:700;
  letter-spacing:.14em;
  text-transform:uppercase;
  line-height:1;
  cursor:pointer;
  transition:transform .22s var(--ease), box-shadow .22s var(--ease);
}
@media (hover:hover){
  body.single-product div.product form.cart button.button.single_add_to_cart_button:hover{
    background:var(--white);
    color:var(--blue);
    transform:translateY(-2px);
    box-shadow:0 10px 30px rgba(0,0,0,.4);
  }
}
@media (prefers-reduced-motion:reduce){
  body.single-product div.product form.cart button.button.single_add_to_cart_button{transition:none}
  body.single-product div.product form.cart button.button.single_add_to_cart_button:hover{transform:none}
}
body.single-product div.product form.cart button.button.single_add_to_cart_button:disabled{
  opacity:.45;
  cursor:not-allowed;
}

/* --------------------------------------------------------------------------
   5 · Variations (the three tees are variable products)
   ⚠ `woo-variation-swatches` is a THIRD-PARTY PLUGIN and its swatches measured
   30 x 30 with `border-width:0`, which is both below the 44px touch target and
   invisible: white boxes with black labels on a white sheet. "L" and "XL" are
   the only two sizes on the black tee today.
   -------------------------------------------------------------------------- */
body.single-product .variations{
  margin:0 0 18px;
  border:0;
  width:100%;
}
body.single-product .variations th,
body.single-product .variations td{
  display:block;
  width:auto;
  padding:0;
  border:0;
  text-align:left;
}
body.single-product .variations th.label label,
body.single-product .variations .label label{
  font-family:var(--font-body);
  font-size:11px;
  font-weight:600;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:rgba(255,255,255,.72);
  margin:0 0 9px;
  display:block;
}
body.single-product .variable-items-wrapper{
  display:flex;
  flex-wrap:wrap;
  gap:8px;
  margin:0 0 6px;
  padding:0;
}
/* ⚠ SIZE THESE THROUGH THE PLUGIN'S OWN CSS VARIABLES, NOT BY OUT-SPECIFYING
   IT. `woo-variation-swatches` ships
   `.woo-variation-swatches .wvs-style-squared.variable-items-wrapper .variable-item`
   at (0,6,0) setting `width:auto;min-width:var(--wvs-single-product-item-width)`.
   Six classes is not a fight worth having, and winning it would leave the
   plugin's own `min-width` still resolving from a variable we had ignored.
   Custom properties inherit, so redefining them here changes what that rule
   resolves TO. The plugin's defaults were 30px / 30px / 16px, which is both
   below the 44px touch-target floor and, with `border-width:0`, invisible:
   white boxes with black labels on a white sheet. */
body.single-product .variable-items-wrapper{
  --wvs-single-product-item-width:48px;
  --wvs-single-product-item-height:48px;
  --wvs-single-product-item-font-size:13px;
}
body.single-product .variable-items-wrapper .variable-item{
  min-height:48px;
  height:auto;
  padding:0 14px;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  background:rgba(255,255,255,.06);
  border:1px solid rgba(255,255,255,.26);
  border-radius:var(--radius);
  box-shadow:none;
  color:var(--white);
  font-family:var(--font-display);
  font-size:13px;
  font-weight:700;
  letter-spacing:.06em;
  text-transform:uppercase;
  cursor:pointer;
  transition:border-color .2s var(--ease), background .2s var(--ease);
}
body.single-product .variable-items-wrapper .variable-item:hover{
  border-color:rgba(255,255,255,.6);
  background:rgba(255,255,255,.1);
}
body.single-product .variable-items-wrapper .variable-item.selected{
  background:var(--white);
  border-color:var(--white);
  color:var(--blue);
}
body.single-product .variable-items-wrapper .variable-item.disabled,
body.single-product .variable-items-wrapper .variable-item.out-of-stock{
  opacity:.34;
  cursor:not-allowed;
}
body.single-product .variable-item-span{color:inherit}
body.single-product a.reset_variations{
  font-family:var(--font-body);
  font-size:12px;
  color:rgba(255,255,255,.6);
  text-decoration:underline;
}
body.single-product a.reset_variations:hover{color:var(--white)}
body.single-product .woocommerce-variation-price{margin:0 0 14px}
body.single-product .woocommerce-variation-price .price{
  font-family:var(--font-display);
  font-size:clamp(20px,2vw,25px);
  font-weight:700;
  color:var(--white);
}
body.single-product .woocommerce-variation-availability p{
  color:rgba(255,255,255,.72);
  font-size:13px;
  margin:0 0 10px;
}
body.single-product .woocommerce-variation-description{
  color:var(--muted);
  font-size:14px;
  line-height:1.6;
}

/* --------------------------------------------------------------------------
   6 · The lab block: lot number and purity, read live from the COA register
   This is the reason the page was worth rebuilding. See burly_pdp_lab_block().
   The lot number is set in a system monospace on purpose: it is the string a
   buyer matches character by character against the certificate, and a
   proportional face makes that harder. No webfont is added for it.
   -------------------------------------------------------------------------- */
body.single-product .burly-lab{
  border:1px solid var(--blue-edge);
  border-radius:var(--radius);
  background:rgba(255,255,255,.04);
  padding:16px 18px;
  margin:0 0 20px;
}
body.single-product .burly-lab-rows{
  display:grid;
  grid-template-columns:auto 1fr;
  gap:7px 18px;
  margin:0;
}
body.single-product .burly-lab-rows dt{
  font-family:var(--font-body);
  font-size:11px;
  font-weight:600;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:rgba(255,255,255,.58);
  margin:0;
  line-height:1.5;
}
body.single-product .burly-lab-rows dd{
  margin:0;
  font-family:var(--font-body);
  font-size:14px;
  font-weight:600;
  color:var(--white);
  font-variant-numeric:tabular-nums;
  line-height:1.5;
}
body.single-product .burly-lab-rows dd.burly-lot{
  font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size:13.5px;
  letter-spacing:.04em;
}
body.single-product .burly-lab-links{
  display:flex;
  flex-wrap:wrap;
  gap:6px 20px;
  margin:14px 0 0;
  padding:12px 0 0;
  border-top:1px solid var(--blue-edge);
}
body.single-product .burly-lab-links a{
  font-family:var(--font-body);
  font-size:13px;
  font-weight:600;
  text-decoration:none;
  white-space:nowrap;
}
body.single-product .burly-lab-links a.burly-lab-cert{color:var(--white)}
body.single-product .burly-lab-links a.burly-lab-all{color:rgba(255,255,255,.62)}
body.single-product .burly-lab-links a::after{
  content:" \2192";
  display:inline-block;
  transition:transform .22s var(--ease);
}
body.single-product .burly-lab-links a:hover{color:var(--white);text-decoration:underline}
@media (hover:hover){
  body.single-product .burly-lab-links a:hover::after{transform:translateX(3px)}
}

/* --------------------------------------------------------------------------
   7 · The three standing facts
   ⚠ `padding-left:0` IS LOAD BEARING. Measured before it: the list sat at
   x=815 inside a column at x=770, indented 45px by the browser's default
   `ul` padding, so it hung off the left edge of everything above it.
   -------------------------------------------------------------------------- */
body.single-product ul.burly-facts{
  list-style:none;
  margin:0 0 22px;
  padding:0;
}
body.single-product ul.burly-facts li{
  position:relative;
  font-family:var(--font-body);
  font-size:13px;
  line-height:1.5;
  color:rgba(255,255,255,.7);
  padding:0 0 0 17px;
  margin:0 0 7px;
}
body.single-product ul.burly-facts li:last-child{margin-bottom:0}
body.single-product ul.burly-facts li::before{
  content:"";
  position:absolute;
  left:0;
  top:.52em;
  width:5px;
  height:5px;
  border-radius:50%;
  background:rgba(255,255,255,.4);
}
body.single-product ul.burly-facts a{
  color:rgba(255,255,255,.7);
  text-decoration:underline;
  text-underline-offset:2px;
}
body.single-product ul.burly-facts a:hover{color:var(--white)}

/* --------------------------------------------------------------------------
   8 · Specifications, lifted out of the retired "Additional information" tab
   ⚠ Astra gives this table `border-top:1px`, a 150px `th` and 1px borders on
   every cell, so the borders have to be cleared before new ones are drawn or
   the result is a double rule at every row.
   -------------------------------------------------------------------------- */
body.single-product .burly-specs{margin:0}
body.single-product h2.burly-specs-head{
  font-family:var(--font-body);
  font-size:11px;
  font-weight:600;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:rgba(255,255,255,.58);
  margin:0 0 10px;
  padding:0;
}
body.single-product .burly-specs table.shop_attributes{
  width:100%;
  margin:0;
  border:0;
  border-collapse:collapse;
}
body.single-product .burly-specs table.shop_attributes tr{
  border-bottom:1px solid var(--blue-edge);
}
body.single-product .burly-specs table.shop_attributes tr:last-child{border-bottom:0}
body.single-product .burly-specs table.shop_attributes th,
body.single-product .burly-specs table.shop_attributes td{
  border:0;
  padding:11px 0;
  background:none;
  font-style:normal;
  vertical-align:top;
  line-height:1.5;
}
body.single-product .burly-specs table.shop_attributes th{
  width:42%;
  text-align:left;
  font-family:var(--font-body);
  font-size:13px;
  font-weight:500;
  color:rgba(255,255,255,.58);
}
body.single-product .burly-specs table.shop_attributes td{
  font-family:var(--font-body);
  font-size:13px;
  font-weight:600;
  color:var(--white);
}
/* ⚠ `padding:0` AS WELL AS `margin:0`. WooCommerce gives this `<p>` 8px of
   PADDING, not margin, so clearing the margin alone left the value sitting
   exactly 8px below its own label: th text at y=886, td text at y=894, in a
   row that was 58px tall for one line of 13px type. */
body.single-product .burly-specs table.shop_attributes td p{margin:0;padding:0}

/* --------------------------------------------------------------------------
   9 · The description, as a faint panel that opens
   Owner's call 2026-10-08, combining two of the options offered: *"lets do the
   fade to view full des but also add the faint translucent white on navy like
   the lot block"*. The white card it replaces was disliked for being a bright
   slab, and the measure inside it was inset 228px from each edge at 1440,
   which is what made it read as mostly empty.

   ⚠ `clear:both` IS STILL THE RULE'S REASON FOR EXISTING. Astra floats the
   gallery left and the summary right; measured without it, this section's box
   reported y=197 h=1444 -- starting at the TOP of the article and running the
   full height of both columns, so its background painted behind the image and
   the buy column. `section.related` needs the same.
   -------------------------------------------------------------------------- */
/* ⚠ THE PANEL IS CAPPED SO ITS PADDING DEFINES THE MEASURE. Measured at 1440
   before this: the panel spanned the full 1216px content width while the text
   inside it ran 702px, leaving 446px of empty panel down the right-hand side.
   Expanded, that imbalance was the most obvious thing on the page, and it is
   the same complaint the white card drew.
   A single column of 15px text cannot fill 1148px and stay readable, so the
   answer is not a wider measure -- it is a narrower panel. Capped here, the
   padding does the work and there is no stranded space to explain.
   Left-aligned, so it starts on the gallery's left edge rather than floating. */
body.single-product .burly-desc{
  clear:both;
  position:relative;
  background:rgba(255,255,255,.04);   /* same value as .burly-lab */
  border:1px solid var(--blue-edge);
  border-radius:var(--radius);
  margin:clamp(34px,4vw,56px) 0 0;
  padding:clamp(24px,2.8vw,36px) clamp(22px,2.6vw,38px);
  max-width:min(860px, 100%);
}
/* No measure here: the capped panel above is the measure. Two caps would be
   two numbers to keep in agreement, and the white card's 760px against an
   86ch heading is exactly how they drift apart. */
body.single-product .burly-desc-inner{
  max-width:none;
  margin-inline:0;
}
body.single-product .burly-desc-inner > h2{
  font-family:var(--font-display);
  font-size:clamp(18px,1.9vw,22px);
  font-weight:700;
  color:var(--white);
  letter-spacing:-.008em;
  margin:0 0 14px;
}
body.single-product .burly-desc-body{
  font-family:var(--font-body);
  font-size:15px;
  line-height:1.72;
  color:rgba(255,255,255,.76);
  /* `max-height` is set inline by product.js and animated from there; the
     transition lives here so a no-JS page never has one to run. */
  transition:max-height .38s var(--ease);
}
@media (prefers-reduced-motion:reduce){
  body.single-product .burly-desc-body{transition:none}
}
body.single-product .burly-desc.is-clipped .burly-desc-body{overflow:hidden}
body.single-product .burly-desc.is-open .burly-desc-body{overflow:visible}

/* ⚠ THE FADE MUST FADE TO AN EXPLICIT COLOUR, NOT TO `transparent`.
   `transparent` computes to `rgba(0,0,0,0)` and a gradient to it travels
   through grey, which on navy shows as a dirty smear across the clip edge.
   rgb(10,28,54) is this panel's colour COMPUTED, not guessed: white at .04
   over #00132e is 255*.04 + 0*.96, 255*.04 + 19*.96, 255*.04 + 46*.96.
   ⚠ If the panel's alpha or the page's navy ever changes, recompute this. */
body.single-product .burly-desc.is-clipped::after{
  content:"";
  position:absolute;
  left:1px;
  right:1px;
  bottom:calc(clamp(24px,2.8vw,36px) + 46px);
  height:96px;
  pointer-events:none;
  background:linear-gradient(to bottom, rgba(10,28,54,0) 0%, rgb(10,28,54) 86%);
}
body.single-product .burly-desc-body p{margin:0 0 14px}
body.single-product .burly-desc-body p:last-child{margin:0}
body.single-product .burly-desc-body a{color:var(--white);text-decoration:underline}
body.single-product .burly-desc-body strong{color:var(--white)}
body.single-product .burly-desc-body ul,
body.single-product .burly-desc-body ol{margin:0 0 14px;padding-left:22px}
body.single-product .burly-desc-body li{margin:0 0 6px}
/* ⚠ A HIERARCHY INVERSION, MEASURED. Left unstyled the description's own
   headings inherit Astra's scale, and `h3` "Overview" rendered at 30px weight
   500 INSIDE an `h2` at 24px weight 700 -- the child larger than its parent.
   No level is skipped, so the outline is correct; only the sizes were wrong. */
body.single-product .burly-desc-body h2,
body.single-product .burly-desc-body h3,
body.single-product .burly-desc-body h4,
body.single-product .burly-desc-body h5,
body.single-product .burly-desc-body h6{
  font-family:var(--font-display);
  font-weight:700;
  color:var(--white);
  letter-spacing:-.004em;
  line-height:1.3;
  margin:24px 0 9px;
}
body.single-product .burly-desc-body h2{font-size:17px}
body.single-product .burly-desc-body h3{font-size:15px}
body.single-product .burly-desc-body h4,
body.single-product .burly-desc-body h5,
body.single-product .burly-desc-body h6{font-size:14px}
body.single-product .burly-desc-body > :first-child{margin-top:0}

/* The toggle. Quiet by design: it is a disclosure control, not a second call
   to action competing with Add to Bag a screen above it. */
body.single-product .burly-desc-toggle{
  position:relative;
  display:inline-flex;
  align-items:center;
  gap:8px;
  margin:18px 0 0;
  padding:11px 20px;
  background:transparent;
  border:1px solid rgba(255,255,255,.28);
  border-radius:var(--radius);
  color:var(--white);
  font-family:var(--font-display);
  font-size:12px;
  font-weight:700;
  letter-spacing:.12em;
  text-transform:uppercase;
  line-height:1;
  cursor:pointer;
  transition:border-color .2s var(--ease), background .2s var(--ease);
}
@media (hover:hover){
  body.single-product .burly-desc-toggle:hover{
    border-color:var(--white);
    background:rgba(255,255,255,.07);
  }
}
body.single-product .burly-desc-toggle:focus-visible{
  outline:2px solid var(--white);
  outline-offset:2px;
}
body.single-product .burly-desc-toggle::after{
  content:"\2193";
  font-size:13px;
  line-height:1;
  transition:transform .22s var(--ease);
}
body.single-product .burly-desc.is-open .burly-desc-toggle::after{transform:rotate(180deg)}
@media (prefers-reduced-motion:reduce){
  body.single-product .burly-desc-toggle,
  body.single-product .burly-desc-toggle::after{transition:none}
}

/* --------------------------------------------------------------------------
   10 · Related products
   ⚠ THE CARD RULES HAVE TO BE WRITTEN A THIRD TIME, and that is not an
   oversight. The catalog's cards are scoped `body.ast-woo-shop-archive` and the
   cart's cross-sells `body.woocommerce-cart .cross-sells`; NEITHER SCOPE IS ON
   A PRODUCT PAGE. These are the same Astra cards in a third place, so every
   fight already won has to be won again — above all Astra's (0,4,2)
   `.ast-woo-product-category` rule, which otherwise returns the eyebrow to
   black at opacity .6 on a navy ground.

   These match the CATALOG's values, not the cart's, because this ground is navy
   like the catalog and not white like the cart.

   ⚠ `:not(.elementor-grid)` IS LOAD BEARING for the same reason as on the
   cart: WooCommerce ships
   `.woocommerce-page ul.products:not(.elementor-grid).columns-4{...}` at
   (0,4,1), and CLASS COUNT OUTRANKS ELEMENT COUNT, so a chain with more
   elements but fewer classes loses. With the `:not()` mirrored this lands at
   (0,4,3).
   -------------------------------------------------------------------------- */
body.single-product section.related{
  clear:both;
  margin:clamp(44px,5vw,76px) 0 0;
  padding-top:clamp(28px,3vw,40px);
  border-top:1px solid var(--blue-edge);
}
body.single-product section.related > h2{
  font-family:var(--font-display);
  font-size:clamp(20px,2.2vw,27px);
  font-weight:700;
  letter-spacing:-.008em;
  color:var(--white);
  margin:0 0 clamp(18px,2vw,26px);
}
body.single-product section.related ul.products:not(.elementor-grid){
  display:grid;
  grid-template-columns:repeat(4,minmax(0,1fr));
  gap:var(--gap);
  margin:0;
  padding:0;
  list-style:none;
}
body.single-product section.related ul.products li.product{
  float:none;
  width:auto;
  margin:0;
  padding:0;
  position:relative;
  background:rgba(255,255,255,.055);
  border-radius:var(--radius);
  overflow:hidden;
  transition:transform .3s var(--ease),
             background .3s var(--ease),
             box-shadow .3s var(--ease);
}
@media (hover:hover){
  body.single-product section.related ul.products li.product:hover{
    transform:translateY(-6px);
    background:rgba(255,255,255,.085);
    box-shadow:0 18px 44px rgba(0,0,0,.38);
  }
}
@media (prefers-reduced-motion:reduce){
  body.single-product section.related ul.products li.product{transition:none}
  body.single-product section.related ul.products li.product:hover{transform:none}
}
/* Astra's on-card overlay, removed here too: third place, same owner decision. */
body.single-product section.related .ast-on-card-button{display:none}
body.single-product section.related .astra-shop-thumbnail-wrap{margin:0;display:block}
body.single-product section.related a.woocommerce-LoopProduct-link{display:block}
body.single-product section.related ul.products li.product a img{
  display:block;
  width:100%;
  height:auto;
  margin:0;
  aspect-ratio:1/1;
  object-fit:cover;
}
body.single-product section.related .astra-shop-summary-wrap{
  padding:15px 17px 18px;
  display:flex;
  flex-direction:column;
  gap:7px;
}
body.single-product section.related ul.products li.product .ast-woo-product-category{
  font-family:var(--font-body);
  font-size:11px;
  font-weight:600;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:rgba(255,255,255,.72);
  line-height:1;
  margin-bottom:0;
  opacity:1;
}
body.single-product section.related .ast-loop-product__link{
  text-decoration:none;
  display:block;
}
body.single-product section.related ul.products li.product h2.woocommerce-loop-product__title{
  font-family:var(--font-display);
  font-weight:700;
  font-size:16px;
  line-height:1.25;
  letter-spacing:-.005em;
  color:var(--white);
  margin:0;
  padding:0;
}
body.single-product section.related ul.products li.product .price{
  font-family:var(--font-body);
  font-size:15px;
  font-weight:600;
  color:var(--white);
  font-variant-numeric:tabular-nums;
  margin:0;
  line-height:1.2;
}
body.single-product section.related ul.products li.product .price del{
  color:rgba(255,255,255,.52);
  font-weight:400;
  margin-right:6px;
}
body.single-product section.related ul.products li.product .price ins{text-decoration:none}
/* Sale flash. Woo sets `right:0`, so declaring only `left` leaves the badge
   stretched across the card — the same fault the catalog hit. */
body.single-product section.related ul.products li.product .onsale{
  top:10px;
  left:10px;
  right:auto;
  bottom:auto;
  margin:0;
  padding:5px 9px;
  min-height:0;
  min-width:0;
  line-height:1;
  border-radius:var(--radius);
  background:var(--white);
  color:var(--blue);
  font-family:var(--font-body);
  font-size:10px;
  font-weight:700;
  letter-spacing:.1em;
  text-transform:uppercase;
}

/* --------------------------------------------------------------------------
   11 · Stacking
   ⚠ 921px, NOT 768. Astra overrides WooCommerce's smallscreen breakpoint with
   its own tablet width, declared on the `<link>` element's media attribute,
   which a CSSOM walk reports as `(all)`. Getting this wrong cost two rounds on
   the cart. Below it Astra un-floats the gallery and summary.
   -------------------------------------------------------------------------- */
@media (max-width:921px){
  body.single-product .woocommerce-product-gallery{margin-bottom:clamp(22px,3vw,30px)}
  /* ⚠ THE 3-UP STEP THAT STOOD HERE WAS REMOVED 2026-10-09. It read
     `grid-template-columns:repeat(3,minmax(0,1fr))`, and with FOUR related
     products that is not one row, it is 3 + 1 — a stranded single card on a
     second row. The base rule is already `repeat(4)`, so deleting the step
     keeps a single row from desktop all the way down. At 768 that is 161px a
     card and at 921 it is 195px, both comfortable. */
}
/* ⚠⚠ FOUR IN A SINGLE ROW ON PHONES, 2026-10-09. Owner: *"lets go to the
   individual product pages, for the related products lets make those in a
   singular row like the cart page"*.

   Was `repeat(2)` here and a SINGLE column below 420, so at 390 each related
   photo was 350x350 and the four of them ran to well over a screen of scroll.
   Now 82px cards with 82px images, all four on one row.

   ⚠ IT IS FOUR HERE, NOT THREE LIKE THE CART. WooCommerce outputs four related
   products on a product page against three cross-sells on the cart, so the same
   350px of content divides four ways instead of three: 82px a card rather than
   110px. The card furniture is a step smaller than the cart's for that reason.
   If the owner would rather have the cart's roomier 110px cards, the fix is to
   serve THREE related products via `woocommerce_output_related_products_args`,
   not to shrink the type further.

   ⚠ `overflow-wrap:break-word` ON THE TITLE IS LOAD BEARING AT THIS SIZE, and
   it was found by measuring rather than by looking. The cards never broke their
   bounds, but "Bacteriostatic" is a single unbreakable word measuring 73px in a
   68px text box, so the title overflowed its own box by 5px and ate the card's
   padding. Stress-tested afterwards against "Bacteriostatic Water",
   "Electrophoresis" and the longest name in the catalogue ("BPC-157 10mg,
   TB-500 10mg, GHK-Cu 50mg (Glow Blend 70mg)"): no title overflow, cards hold
   at 82px, no page-level sideways scroll in any of the three. */
@media (max-width:760px){
  /* ⚠ THE COLUMN COUNT FOLLOWS THE ITEM COUNT. `repeat(4,...)` was the first
     attempt and it was wrong on the three tee pages, which have only TWO
     related products (the other two colourways): the pair sat as two 82px cards
     with half the row empty. `grid-auto-flow:column` + `grid-auto-columns`
     creates exactly as many equal tracks as there are items, so two items share
     the row at 171px each and four share it at 82px. Verified on both.

     ⚠ THIS ROW NEVER WRAPS, BY DESIGN. WooCommerce outputs at most four
     related products (`posts_per_page => 4`), so four is the worst case. If
     that count is ever raised via
     `woocommerce_output_related_products_args`, the cards keep dividing instead
     of wrapping and will get too narrow — raise the count and this rule has to
     be reconsidered together. */
  body.single-product section.related ul.products:not(.elementor-grid){
    grid-template-columns:none;
    grid-auto-flow:column;
    grid-auto-columns:minmax(0,1fr);
    gap:8px;
  }
  body.single-product section.related .astra-shop-summary-wrap{padding:7px 7px 9px;gap:2px}
  body.single-product section.related ul.products li.product .ast-woo-product-category{font-size:8px;letter-spacing:.07em}
  body.single-product section.related ul.products li.product h2.woocommerce-loop-product__title{
    font-size:11px;
    line-height:1.22;
    overflow-wrap:break-word;
    hyphens:auto;
  }
  body.single-product section.related ul.products li.product .price{font-size:11px}
}
@media (max-width:600px){
  /* The buy button owns the row on a phone; the quantity keeps a thumb-sized
     box rather than shrinking with it. */
  body.single-product form.cart .quantity input.qty{width:64px}
  body.single-product .burly-lab{padding:14px 15px}
  body.single-product .burly-lab-links a{white-space:normal}
}
@media (max-width:420px){
  /* ⚠ this was the single-column rule — the one that made the photos 350x350.
     It now RE-ASSERTS four-up, because it is later in the file than the 760
     block and would otherwise undo it. */
  body.single-product section.related ul.products:not(.elementor-grid){
    grid-template-columns:none;
    grid-auto-flow:column;
    grid-auto-columns:minmax(0,1fr);
  }
}

/* --------------------------------------------------------------------------
   12 · Astra's sticky add-to-cart bar
   `astra-settings[single-product-sticky-add-to-cart]` is ON, position bottom,
   so this slides up from the foot of the window as you scroll past the buy box.

   ⚠ THE QUANTITY NUMBER WAS WHITE ON WHITE, AND IT WAS OUR DOING. Reported by
   the owner. This bar carries its OWN `form.cart`, so the navy-column rules
   above landed inside it: white text, a 6% white fill and a 22% white border,
   all on a `rgb(255,255,255)` bar. The input was legible only while empty.

   The rest of the bar was never styled at all: Hind 18px black title, Hind
   price, and a Lexend Deca Add to Bag that was TRANSPARENT WITH BLACK TEXT --
   the same ghost-button fault the main CTA had. It is the most prominent
   conversion control on the page once you scroll, so it is brought onto the
   brand here rather than left half-fixed.

   The bar stays WHITE, which is Astra's own treatment and what the owner is
   keeping. That makes it the same inverted surface as the cart page, so the
   button matches the cart's checkout button: navy fill, white text.
   -------------------------------------------------------------------------- */
body.single-product .ast-sticky-add-to-cart{
  background:var(--white);
  border-top:1px solid var(--rule);
  box-shadow:0 -6px 26px rgba(0,19,46,.1);
}
/* ⚠ THE BAR HAD NO GUTTER AND TWO WIDGETS SAT ON ITS BUTTON. Measured at 1440
   with the bar open: the Add to Bag button ran x1287 to 1440, flush with the
   window edge, while `ast-scroll-top-icon` (z-index 9999, against this bar's
   1200) occupied x1379-1410 y699-730 -- ENTIRELY INSIDE the button. A click in
   that 32px square scrolled to top instead of adding to the bag. The floating
   cart bubble overlapped the bar's top edge by 11px as well.

   Constraining the bar's own container to the site measure fixes both at once:
   the content now ends at 1328 and the widgets sit in clear space beyond it,
   and the bar lines up with the header and the page instead of bleeding to the
   edge. `--pad-gutter` alone was not enough on the right, hence the extra
   clearance, which is sized to the widgets and not to a guess. */
body.single-product .ast-sticky-add-to-cart .ast-container{
  max-width:var(--maxw);
  margin-inline:auto;
  padding-inline:var(--pad-gutter);
  padding-right:calc(var(--pad-gutter) + 76px);
}
@media (max-width:600px){
  /* On a phone the widgets stack in the same corner but the bar is shorter;
     keep the clearance, drop the gutter to the page's own. */
  body.single-product .ast-sticky-add-to-cart .ast-container{
    padding-inline:16px;
    padding-right:72px;
  }
}
body.single-product .ast-sticky-add-to-cart .ast-sticky-add-to-cart-title{
  font-family:var(--font-display);
  font-size:16px;
  font-weight:700;
  letter-spacing:-.005em;
  color:var(--ink);
}
body.single-product .ast-sticky-add-to-cart .ast-sticky-add-to-cart-action-price{
  font-family:var(--font-display);
  font-size:17px;
  font-weight:700;
  color:var(--ink);
  font-variant-numeric:tabular-nums;
}
/* ⚠ THE FIX THE OWNER ASKED FOR. Ink on white, with a visible border, and the
   chain has to be long enough to beat our own navy rule above rather than
   relying on source order: that rule is
   `body.single-product form.cart .quantity input.qty` at (0,3,3), and this is
   (0,4,4). `--ink` is #00132e, the brand's black. */
body.single-product .ast-sticky-add-to-cart form.cart .quantity input.qty{
  background:var(--white);
  border:1px solid rgba(0,19,46,.26);
  color:var(--ink);
  min-height:48px;
}
body.single-product .ast-sticky-add-to-cart form.cart .quantity input.qty:focus{
  outline:2px solid var(--ink);
  outline-offset:1px;
}
/* ⚠ THIS BUTTON NEEDS ITS TYPOGRAPHY RESTATED IN FULL, and the reason is
   structural: `.ast-sticky-add-to-cart` is a DIRECT CHILD OF <body>, outside
   `div.product` entirely. The main button rule requires `div.product` to reach
   (0,5,4) against WooCommerce, so it cannot match here at all, and this button
   was left at the Astra default -- Lexend Deca 14px WEIGHT 400, 1px tracking,
   capitalised, square corners -- while the main one was correct. Checked with
   `closest('div.product')`, which returns null.
   Navy fill rather than the main CTA's white one: this sits on a white bar, so
   it inverts for the same reason the cart's checkout button does. */
body.single-product .ast-sticky-add-to-cart form.cart button.button.single_add_to_cart_button{
  background:var(--ink);
  border:1px solid var(--ink);
  border-radius:var(--radius);
  color:var(--white);
  min-height:48px;
  margin:0;
  padding:15px 26px;
  font-family:var(--font-display);
  font-size:13px;
  font-weight:700;
  letter-spacing:.14em;
  text-transform:uppercase;
  line-height:1;
  cursor:pointer;
}
@media (hover:hover){
  body.single-product .ast-sticky-add-to-cart form.cart button.button.single_add_to_cart_button:hover{
    background:#0b2a52;
    border-color:#0b2a52;
    color:var(--white);
    transform:none;
    box-shadow:none;
  }
}
@media (max-width:600px){
  body.single-product .ast-sticky-add-to-cart .ast-sticky-add-to-cart-title-wrap{display:none}
  body.single-product .ast-sticky-add-to-cart form.cart .quantity input.qty{width:62px}
}

/* ==========================================================================
   MY ACCOUNT — 2026-10-08

   Deliberately the SMALLEST possible change, on the owner's instruction:
   *"lets just make it simple and add a header with the blue with white
   my account and keep everything else the same"*.

   So this styles the heading band and NOTHING else. The login form, the
   account tables, the white sheet, the grey page ground and every spacing
   value below the band are untouched and should stay that way until asked.

   ⚠ THE BAND BLEEDS OUT WITHOUT MOVING THE HEADING. Measured first: the
   article carries `padding-inline:100.05px`, so `.entry-header` sat at x=100
   w=1240 while the content below sits at x=120. The cart solves the same
   problem by zeroing the article padding and re-applying a gutter to both the
   title and the content -- but that MOVES the content, which is exactly what
   was asked not to happen here. Pulling the band out by the article's own
   padding and then paying it back as padding keeps the heading on its original
   x and leaves everything below untouched.

   Both halves are expressed against the SAME containing block, so there is no
   hard-coded 100px to drift: percentages in margin and padding both resolve
   against the article's content width (1240), giving 620 - 720 = -100 and
   720 - 620 = +100.

   ⚠ `50vw` INCLUDES THE SCROLLBAR, which is the standard way this trick causes
   sideways scroll. It is safe here because `body:not(.burly-page) #primary`
   already sets `overflow-x:clip` (verified computed as `clip` on this page),
   and it is `clip` rather than `hidden` so no scroll container is created.
   Check the width sweep anyway if this is ever copied elsewhere.
   ========================================================================== */
/* ⚠ ZERO THE ARTICLE'S TOP PADDING OR THE BAND FLOATS. Measured: the article
   carries `padding:80.1px 100.05px`, so the band started at y=197 against a
   chrome bottom of 117 and left an 80px strip of white sheet above it. A navy
   band with a white gap over it reads as a mistake, not a design. The cart has
   the same band and no gap because it zeroes the article padding entirely
   (`padding:0`), but doing that here would also drop the 100px side padding and
   move every row of the account page sideways, which is what was asked not to
   happen. Only the TOP is zeroed; the band's own margin-bottom then supplies
   the space to the content below.
   The bottom padding is deliberately left alone -- it is the gap above the
   footer and nothing was wrong with it. */
body.woocommerce-account #primary article{padding-top:0}

body.woocommerce-account .entry-header{
  background:var(--blue);
  border-bottom:1px solid var(--blue-edge);
  margin-inline:calc(50% - 50vw);
  padding-inline:calc(50vw - 50%);
  padding-block:clamp(26px,2.6vw,38px);
  margin-bottom:clamp(26px,2.8vw,40px);
}
/* ⚠ THE HEADING WAS ALREADY MISALIGNED WITH ITS CONTENT ABOVE ~1400px, and
   this did not cause it -- it revealed it. Measured at 1920 BEFORE the band
   existed: `.woocommerce` carries Astra's `max-width:1200px` and is auto
   centred (margin-left 259.95px), while `h1.entry-title` is `max-width:none`
   and fills the article's 1720px content box from x=100. The heading therefore
   sat 260px to the LEFT of the form beneath it. Black text on the same white
   sheet gave it no edge, so nobody saw it; a navy band has an edge, and a
   heading hanging off the side of its own content reads as a bug.

   Capping it against ASTRA'S OWN custom property rather than a literal 1200
   means it keeps tracking the owner's content-width setting if that is ever
   changed in the customizer. The fallback is only for the case where the
   property is absent. Below ~1400 the cap is wider than the column, so it is
   inert and nothing moves. */
body.woocommerce-account h1.entry-title{
  max-width:var(--wp--custom--ast-content-width-size, 1200px);
  margin-inline:auto;
  font-family:var(--font-display);
  font-weight:700;
  font-size:clamp(30px,3.4vw,46px);
  line-height:1.04;
  letter-spacing:-.01em;
  color:var(--white);
  margin-block:0;
}

/* ==========================================================================
   CHECKOUT — 2026-10-08

   ⚠⚠ READ THE SAFETY RULES BEFORE ADDING ANYTHING TO THIS SECTION. ⚠⚠

   The owner's instruction was explicit: *"we dont want to break the
   processor"*. A previous change — making the phone field required — did break
   payment, and their developer called it a bug in the PHP. That diagnosis is
   correct and it is worth recording WHY, because it is the opposite of a
   styling problem:

     wc-cart-bridge/core.php:173-174 registers
       add_filter('woocommerce_billing_fields',  'make_phone_optional',          99)
       add_filter('woocommerce_checkout_fields', 'make_checkout_phone_optional', 99)
     and core.php:4031/4038 then force
       $fields['billing_phone']['required'] = false

   So Audience Pulse FORCES the phone optional at priority 99, overriding the
   `woocommerce_checkout_phone_field = 'required'` setting, and the rendered
   field is left internally inconsistent: label "Phone *", aria-required="true",
   a `validate-required` row class that WooCommerce's JS enforces, and the HTML
   `required` attribute absent. All PHP. CSS cannot cause or cure it.

   ── WHAT THIS SECTION MAY NOT DO ────────────────────────────────────────────
   CSS cannot touch form submission, validation or the gateway. It CAN break a
   checkout in exactly three ways, so none of these appear anywhere below:

     1. NO `display`, `visibility`, `opacity`, `position`, `z-index`,
        `pointer-events`, `overflow` or `transform` on anything inside
        `form.checkout`, `#order_review`, `.payment_box`, `.payment_methods`,
        or on `#place_order`.
        ⚠ A plugin ships `form.has-custom-place-order-button #place_order
        {display:none!important}` — a gateway HIDES the standard button and
        supplies its own. Forcing it visible would give the customer two submit
        paths. `#place_order` is therefore not styled here AT ALL.
     2. NO rule that could cover or offset an input, radio or button.
     3. NO `!important` anywhere in this section.

   ── WHAT CANNOT BE SEEN FROM STAGING ────────────────────────────────────────
   ⚠ Staging's licence is dormant, so Audience Pulse registers NO checkout
   assets here. On LIVE it adds, all AFTER our stylesheet:
       wp_enqueue_scripts 99, 100, 110, 112   its own stylesheets
       wp_head 99  inline_pay_hide_checkout_css   HIDES parts of the checkout
       wp_footer 5 render_inline_pay_panel_footer  its own payment panel
   and core.php:4788 carries a COMPLETE alternative checkout UI (`.ssf-wrap`,
   `.ssf-form`, `.ssf-btn`). On live it can replace much of this page.
   Consequences: rules below may simply not match on live, which is harmless;
   and nothing here may ever target `.ssf-*`, which is the plugin's own.
   ⚠ VERIFY ON LIVE AFTER DEPLOY with one real order. Staging cannot prove it.

   ── SCOPE NOTE ──────────────────────────────────────────────────────────────
   ⚠ `body.woocommerce-checkout` is ALSO on the order-received (thank you)
   page. The band rules below are page chrome and are correct there too, but
   that page has not been seen — it needs a completed order.
   ========================================================================== */

/* 1 · The navy band. Identical treatment to MY ACCOUNT, and identical reason:
   the article carries `padding:80.1px 100.05px`, so the band is pulled out by
   that padding and pays it straight back, which bleeds the background to the
   viewport edges WITHOUT moving the checkout content one pixel sideways.
   Both halves resolve against the same containing block, so there is no
   hard-coded 100px to drift.
   ⚠ `50vw` is safe only because `body:not(.burly-page) #primary` already sets
   `overflow-x:clip`. Verified computed as `clip` on this page. */
body.woocommerce-checkout #primary article{padding-top:0}
body.woocommerce-checkout .entry-header{
  background:var(--blue);
  border-bottom:1px solid var(--blue-edge);
  margin-inline:calc(50% - 50vw);
  padding-inline:calc(50vw - 50%);
  padding-block:clamp(26px,2.6vw,38px);
  margin-bottom:clamp(26px,2.8vw,40px);
}
/* Capped against ASTRA'S OWN content-width property, not a literal, so the
   heading keeps tracking the owner's customizer setting. Without it the
   heading sits left of its own content above ~1400px, which the navy band
   would make obvious. Same fault, same fix, as MY ACCOUNT. */
body.woocommerce-checkout h1.entry-title{
  max-width:var(--wp--custom--ast-content-width-size, 1200px);
  margin-inline:auto;
  margin-block:0;
  font-family:var(--font-display);
  font-weight:700;
  font-size:clamp(30px,3.4vw,46px);
  line-height:1.04;
  letter-spacing:-.01em;
  color:var(--white);
}

/* --------------------------------------------------------------------------
   2 · "Other things people buy" — REMOVED 2026-10-08, the day it shipped

   A cross-sell band below the checkout form, rendered by
   burly_checkout_cross_sells(), with ~24 rules of `.burly-xs*` card styling,
   stood here. The owner deleted it the same day:
     *"on my end one browser shows it and another doesnt show it, so lets
       delete it and get rid of the risk."*

   ⚠ THAT WAS NOT A RENDERING BUG, and the distinction matters if this is ever
   reconsidered. The band was driven by `WC()->cart->get_cross_sells()`, so it
   depended on WHAT WAS IN THE CART, never on the browser. Two browsers are two
   sessions and two carts, and the renderer returned early when the cart's
   products had no cross-sells configured.

   Measured on staging 2026-10-08, because the figure recorded earlier in
   CLAUDE.md (5 of 26) was stale and pointed the wrong way: 20 of 26 published
   products carry `_crosssell_ids`, three ids each. The SIX without are
   DSIP 5mg, IGF-1 LR3, MOTS-c 10mg — and all three tees. So a merch-only cart
   showed no band and a peptide cart showed one. That is the whole of it: a
   working implementation, correctly reflecting the owner's own Linked Products
   data, not a fault to debug.

   It went anyway, for a sound reason: checkout is the one page where the owner
   asked for no avoidable moving parts, and a band that appears conditionally
   is a thing to re-explain every time someone sees a cart without one.

   The PHP renderer went with it — see the matching note in functions.php, and
   read it first if this is ever reinstated. Don't reinstate either without
   being asked.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   3 · THE PHONE-WIDTH PASS — 2026-10-08

   Measured on staging at a TRUE 390 and 360 with agent-browser, not a clamped
   headless window. Every rule below fixes something that was measured, and
   every cause was traced to the exact competing rule before anything here was
   written.

   ⚠ THE SAFETY RULES AT THE TOP OF THIS SECTION STILL APPLY IN FULL. Nothing
   below sets display, visibility, opacity, position, z-index, pointer-events
   or overflow on anything, and there is no `!important` anywhere in it. These
   rules move type size, padding, width and colour. Nothing else.
   -------------------------------------------------------------------------- */

/* 3.1 · ⚠⚠ THE WORST DEFECT ON THIS PAGE, AND IT IS INVISIBLE IN A SCREENSHOT.
   Every form control measured 13.68px. iOS Safari ZOOMS THE VIEWPORT when a
   control smaller than 16px takes focus, and it does not zoom back out. The
   customer is left on a magnified, sideways-scrolling checkout from the first
   tap into First name onward. 18 controls were affected, including every
   billing field, the order notes and the terms checkbox.

   The cause is Astra's cascade working as designed, not a bug:
     `button, input, optgroup, select, textarea{font-size:inherit}`
     `.ast-button, .button, button, input, select, textarea{font-size:100%}`
   make every control inherit the body size, and Astra's mobile body size is
   0.855rem = 13.68px.

   16px is a THRESHOLD, not a preference — at 16px iOS does not zoom, at 15.9px
   it does. Scoped to the checkout so Astra's global type scale is untouched,
   and taken to 921px so tablets are covered too. (0,2,1) beats the (0,0,1) and
   (0,1,0) compounds above. */
@media (max-width:921px){
  body.woocommerce-checkout .woocommerce input,
  body.woocommerce-checkout .woocommerce select,
  body.woocommerce-checkout .woocommerce textarea{font-size:16px}
  /* ⚠⚠ THE CART IS INCLUDED, AND IT IS NOT SCOPE CREEP — WITHOUT IT THE FIX
     ABOVE DOES NOT WORK. iOS does not zoom back out. The customer meets the
     quantity box and the coupon box FIRST, on the cart, and arrives at the
     checkout already magnified, so fixing only the checkout fixes nothing
     they would notice.

     ⚠ TWO OF THE THREE ARE OURS. The CART section above sets `input.qty` to
     15px and `#coupon_code` to 14px — one and two pixels under the threshold.
     Both carry a warning comment at the source. They are lifted here rather
     than changed there so the desktop cart stays exactly as signed off.

     Radios and checkboxes are deliberately NOT in this list: iOS zooms on
     controls that take a text caret, and those do not. */
  body.woocommerce-cart table.shop_table.cart td.product-quantity input.qty,
  body.woocommerce-cart td.actions .coupon input#coupon_code{font-size:16px}
  /* Labels were 13.68px. Left alone they would sit noticeably smaller than the
     16px fields they caption, so they move with them. */
  body.woocommerce-checkout .woocommerce label{font-size:14px}
}

/* 3.2 · THE SUMMARY STARTED 29px RIGHT OF THE FIELDS ABOVE IT.
   Billing fields began at x=14, the order summary, payment box and Place Order
   at x=43 — a visible step down the left edge of the page.

   Cause: Astra's `form #order_review:not(.elementor-widget-woocommerce-checkout-page)
   {padding:2em}` at (1,1,1), with 1.5em on the heading. 2em of a 13.68px root
   is 27.36px a side. At 360 that left the summary 274px wide inside a 333px
   form: 59px, a sixth of the screen, spent on padding that only earns its keep
   in the desktop two-column layout.

   ⚠ ONLY THE INLINE PADDING GOES. The heading and the review together draw a
   single box — `#order_review_heading` carries the top/left/right border and
   `#order_review` the left/right/bottom — so zeroing it would push the text
   against a 2px rule. 14px matches the page gutter and keeps the box intact.
   Vertical padding is untouched, which is why this sets `padding-inline` and
   not the shorthand.

   ⚠⚠ AN ID INSIDE A `:not()` COSTS AN ID. This rule FAILED on the first deploy
   and the reason is worth keeping. Astra's full selector is

       form #order_review:not(.elementor-widget-woocommerce-checkout-page #order_review)

   and the `:not()` argument is not a bare class — it is a DESCENDANT SELECTOR
   CONTAINING AN ID. `:not()` takes the specificity of its most specific
   argument, so that argument contributes (1,1,0) and the whole selector scores
   (2,1,1): TWO IDs.

   The first attempt was `body.woocommerce-checkout form.checkout #order_review`
   at (1,2,2). It matched — confirmed by walking the live CSSOM — and still lost,
   because ID count is compared BEFORE class count and no quantity of classes
   can make up one ID. Adding the real `#primary` ancestor takes it to (2,2,2),
   which ties on IDs and wins on classes.

   The lesson already in this file was "`:not()` contributes its argument's
   specificity". The sharper version is: READ THE WHOLE ARGUMENT. A `:not()`
   holding an ID is an ID. */
@media (max-width:921px){
  body.woocommerce-checkout #primary form.checkout #order_review,
  body.woocommerce-checkout #primary form.checkout #order_review_heading{
    padding-inline:14px;
  }
}

/* 3.3 · ⚠ A FLEX CONTAINER DROPS THE WHITESPACE BETWEEN ITS ITEMS.
   "Returning customer?Click here to login" and "Have a coupon?Click here to
   enter your code" rendered with no space at phone widths. The markup has one:
   `Returning customer? <a class="showlogin">…`.

   Astra sets `.woocommerce-message, .woocommerce-error, .woocommerce-info
   {display:flex}` inside `@media (max-width:544px)`. The bare text node and
   the link become two flex items, and the space between them stops existing.
   THIS IS THE THIRD TIME IN THIS FILE A `display:flex` HAS BROKEN SOMETHING
   THAT WORKED — see the `form.cart::before` clearfix note in the product
   section, which failed the same way for the same reason.

   `column-gap` restores it and is INERT on a non-flexed element, so it needs
   no media query of its own and cannot drift if Astra moves its breakpoint.
   Carried to the cart as well, which shows the identical coupon notice. */
body.woocommerce-checkout .woocommerce-info,
body.woocommerce-checkout .woocommerce-message,
body.woocommerce-checkout .woocommerce-error,
body.woocommerce-cart .woocommerce-info,
body.woocommerce-cart .woocommerce-message,
body.woocommerce-cart .woocommerce-error{column-gap:.35em}

/* 3.4 · 13px TAP TARGETS ON CONTROLS THAT DECIDE THE ORDER.
   Checkboxes and radios measured 13px square — the browser default, with no
   CSS rule on them anywhere in any stylesheet. WCAG 2.5.8 asks for 24px.

   These are not decorative: `#terms` is MANDATORY to place an order, and the
   shipping-method radios decide what the customer pays. At 13px on glass they
   are a genuine miss-and-retry.

   22px plus the label's own hit area clears 24px. `accent-color` tints the
   NATIVE control to brand navy instead of replacing it with a styled span, so
   it stays a real checkbox for assistive technology, for the keyboard and for
   WooCommerce's own validation. */
@media (max-width:921px){
  body.woocommerce-checkout .woocommerce input[type="checkbox"],
  body.woocommerce-checkout .woocommerce input[type="radio"]{
    width:22px;
    height:22px;
    accent-color:var(--ink);
    vertical-align:-5px;
    margin-right:6px;
  }
}

/* 3.5 · ⚠⚠ THIS DELIBERATELY AMENDS THE RULE AT THE TOP OF THIS SECTION.
   That rule said `#place_order` is "not styled here AT ALL". The hazard it
   guarded against is real and unchanged: a plugin ships
   `form.has-custom-place-order-button #place_order{display:none!important}`,
   because a gateway HIDES the standard button and supplies its own, and
   forcing it visible would hand the customer two submit paths.

   NOTHING BELOW CAN DO THAT. It sets no display, visibility, opacity,
   position, z-index or pointer-events property, and carries no `!important`,
   so `display:none!important` still wins unconditionally wherever the gateway
   applies it. On that path these declarations style a hidden element and have
   no effect whatsoever. The properties here have no bearing on visibility.

   What they fix, on the Zelle path and on staging: the primary CTA measured
   35px tall, 12.77px type, on a TRANSPARENT background with a hairline border.
   It read as a secondary button at the one moment the customer is asked to
   buy, and it sat under the 44px tap guideline.

   Matched to the cart's `a.checkout-button` (style.css, CART section) so the
   Cart -> Checkout journey speaks one button language. (2,1,1) against the
   (0,1,0) list rules in Astra that were winning. */
@media (max-width:921px){
  body.woocommerce-checkout #payment #place_order{
    min-height:54px;
    padding:17px 20px;
    font-family:var(--font-display);
    font-size:14px;
    font-weight:700;
    letter-spacing:.1em;
    text-transform:uppercase;
    line-height:1;
    color:var(--white);
    background:var(--ink);
    border:1px solid var(--ink);
    border-radius:var(--radius);
  }
}
