/* ==========================================================================
   Theme Name: Montserrat Noir
   Description: One Montserrat family voices the whole store — weight separates display, chrome and body. The navigation bar rests as a lightly lifted solid brown and deepens into true frosted glass as the page scrolls; the dropdown freezes the page behind it the same way. The announcement bar keeps the default stylesheet's exact row, and content keeps the default palette untouched.
   Tone: Dark brown nav / Modern / One family
   Fonts: Montserrat variable (wght 100-900), weights per role
   Theme Color: #463B3A
   Font URL: https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap
   Base: Deerseeker Archery storefront (assets/css/styles.css)
   --------------------------------------------------------------------------
   Drop-in replacement stylesheet. Upload it in admin -> Site -> Theme
   stylesheet, then pick "Montserrat Noir" from the list. R181: app/header.php reads the
   "Font URL" line above and emits a <link>, so the font downloads in
   parallel with this stylesheet instead of blocking on a CSS @import.
   ========================================================================== */

/* ==========================================================================
   Deerseeker Archery — Storefront stylesheet
   A premium European outdoor aesthetic: deep forest greens, warm wood amber,
   cream paper tones, serif display type and frosted-glass navigation.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Design tokens
   -------------------------------------------------------------------------- */
:root {
  --forest-900: #161b16;
  --forest-800: #1f261f;
  --forest-700: #2c352c;
  --forest-600: #3a463a;
  --pine:       #4d5c4a;
  --moss:       #77846b;
  --sage:       #aeb9a2;

  --amber:      #c98a3d;
  --amber-600:  #a96f2a;
  --amber-100:  #f3e3cb;
  /* R87 — used since long before it was defined, and nowhere else: the
     "Default" address tag, the pending-message pill and the active item in the
     account menu all asked for it and silently inherited the body ink instead
     (an undeclared custom property makes the whole declaration invalid). It is
     the same value admin.css already carried as its fallback. */
  --amber-700:  #8a5418;

  /* R60 — price accent. Hardcoded (not derived from --amber) so every theme
     paints prices with the exact same brand amber, per the merchant brief. */
  --price:      #c98a3d;

  --cream:      #f6f3ec;
  --paper:      #fdfbf6;
  --sand:       #ece7dc;

  --ink:        #211f1a;
  --ink-soft:   #4c493f;
  --muted:      #7a7669;

  --line:       #e3ddd0;
  --line-dark:  rgba(255, 255, 255, 0.12);

  --danger:     #b5402f;
  --success:    #3f7a4a;

  --font-display: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  --radius:     3px;
  --radius-sm:  3px;
  --shadow-sm:  0 1px 2px rgba(22, 27, 22, 0.06);
  --shadow-md:  0 10px 30px rgba(22, 27, 22, 0.10);
  --shadow-lg:  0 24px 60px rgba(22, 27, 22, 0.18);

  --container:  1400px;
  --header-h:   72px;
  --ease:       cubic-bezier(0.22, 0.61, 0.36, 1);
  /* R180 - Shared scale. The storefront, the admin panel and every generated
     theme now declare the same tokens with the same values. These are
     declarations only: no existing rule is re-pointed in this pass, so no
     element changes size, colour or position. */
  --fs-2xs: 0.72rem;
  --fs-xs:  0.82rem;
  --fs-sm:  0.92rem;
  --fs-md:  1rem;
  --fs-lg:  1.15rem;
  --fs-xl:  1.45rem;
  --fs-2xl: clamp(1.9rem, 1.35rem + 1.6vw, 2.5rem);
  --fs-3xl: clamp(2.25rem, 1.5rem + 2.6vw, 3.35rem);
  --leading-body: 1.72;
  --leading-display: 1.14;
  --radius-pill: 999px;
  --on-dark: #fff;
  /* R180 - tokens the storefront already shipped; kept in sync so a skin or
     an override behaves the same on both sides of the site. */
  --control-h: 2.5rem;
  --control-h-sm: 2.2rem;
  --gap-form: 1.1rem;

  /* R103 — the hero's frosted panel, tunable from Admin → Styles.
     These four are the R101b values verbatim, so the default look is
     unchanged. The alpha ladder they were chosen from, measured against a
     pure-white photograph, is: 0.35 → 2.20:1, 0.46 → 3:1 (headline only),
     0.58 → 4.5:1 (body copy AA). Below 0.30 the panel no longer carries
     contrast and the text shadow under .hero h1 does the work instead. */
  --hero-ink:       0.10;
  --hero-blur:      10px;
  --hero-saturate:  125%;
}

/* --------------------------------------------------------------------------
   2. Reset & base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  /* R181 — no root clipping any more. R80 put overflow-x:clip here because
     a visibility:hidden mega-dropdown still lays out, and its four-column
     box (up to 850px) made the document wider than the window on every
     desktop page: measured scrollWidth 1305 on a 1024 viewport, 1473 on
     1440. But clipping the root also cuts off genuinely over-wide content
     with no way to scroll to it. Closed panels are now display:none (the
     .nav-list rule below), so the cause is gone and the document is exactly
     as wide as the viewport. If a future component does overflow, the
     visitor can now pan to it instead of losing it. */
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-md);
  line-height: 1.65;
  color: var(--ink);
  background: var(--cream);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { display: block; max-width: 100%; height: auto; }

a { color: inherit; text-decoration: none; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.12;
  color: var(--forest-900);
  margin: 0 0 0.5em;
}

p { margin: 0 0 1em; }

ul { margin: 0; padding: 0; list-style: none; }

button { font-family: inherit; cursor: pointer; }

input, select, textarea { font-family: inherit; font-size: var(--fs-md); }

::selection { background: var(--amber); color: #fff; }

:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 2px;
  border-radius: 2px;
}

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 2.5rem);
}

.skip-link {
  position: absolute;
  left: 1rem;
  top: -100px;
  z-index: 200;
  background: var(--forest-900);
  color: #fff;
  padding: 0.6rem 1rem;
  border-radius: var(--radius-sm);
  transition: top 0.2s var(--ease);
}
.skip-link:focus { top: 1rem; }

/* --------------------------------------------------------------------------
   3. Announcement bar
   -------------------------------------------------------------------------- */
.announcement {
  background: var(--forest-900);
  color: var(--sage);
  text-align: center;
  font-size: var(--fs-xs);
  letter-spacing: 0.06em;
  padding: 0.5rem 1rem;
}
.announcement p { margin: 0; }

/* --------------------------------------------------------------------------
   3b. Cookie notice (R46)
   A slim bar pinned to the bottom of the viewport until the visitor clicks
   "Agree". The markup is only rendered while no choice has been stored
   (app/footer.php), so someone who already agreed never sees it. It follows
   the announcement bar's colours; every generated theme re-paints the pair
   with a contrast-checked foreground (see gen_themes.py).
   -------------------------------------------------------------------------- */
.cookie-notice {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 250;
  /* R182 — a merchant can type any length of notice text. On a short
     landscape viewport a very long notice would otherwise grow past the top
     of the screen; cap the bar and let its own content scroll instead. */
  max-height: 100vh;
  max-height: 100dvh;
  overflow-y: auto;
  background: var(--forest-900);
  color: var(--sage);
  /* R72 — no top hairline: on a dark bar any light border reads as a
     stray white line. The drop shadow alone separates bar from page. */
  box-shadow: 0 -8px 28px rgba(0, 0, 0, 0.22);
  padding: 0.85rem 0;
  transition: opacity 0.3s ease, transform 0.3s ease;
}
.cookie-notice.is-hidden { opacity: 0; transform: translateY(100%); pointer-events: none; }
.cookie-notice-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem;
}
/* R182 — the notice text is a flex item beside two nowrap buttons. Without
   min-width:0 its automatic minimum is the width of the longest unbreakable
   word, so a merchant who pastes a long URL into the notice pushed the whole
   bar (and both buttons) off-screen from 641px upwards. `anywhere`, unlike
   the global `break-word`, also shrinks min-content, so the text wraps and
   the actions stay reachable. Ordinary sentences are unaffected. */
.cookie-notice-text {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.6;
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
  /* Same overflow, vertical edition: only the WORDING scrolls, never the
     whole bar. Without this a very long notice filled a short landscape
     viewport from top to bottom and pushed the Agree button out of reach.
     40dvh is inert for any normal notice (the text is shorter). */
  max-height: 40vh;
  max-height: 40dvh;
  overflow-y: auto;
}

/* R47 — the two actions are now proper buttons. The privacy-policy link
   ("ghost") and the Agree button ("agree") are sized like the storefront's
   pill buttons so the bar reads as a deliberate, elegant prompt instead of
   an afterthought. Text colours are contrast-audited per theme in
   gen_themes.py. */
.cookie-notice-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex: 0 0 auto;
}
.cookie-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.66rem 1.5rem;
  border-radius: 999px;
  border: 1px solid transparent;
  font-family: inherit;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.25s var(--ease), color 0.25s var(--ease),
    border-color 0.25s var(--ease), box-shadow 0.25s var(--ease),
    transform 0.25s var(--ease);
}
.cookie-btn:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
.cookie-btn-agree {
  background: var(--amber);
  color: var(--forest-900);
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.3);
}
.cookie-btn-agree:hover {
  filter: brightness(1.08);
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.36);
  transform: translateY(-1px);
}
.cookie-btn-ghost {
  color: inherit;
  border-color: rgba(255, 255, 255, 0.38);
  background: transparent;
}
.cookie-btn-ghost:hover {
  border-color: rgba(255, 255, 255, 0.7);
  background: rgba(255, 255, 255, 0.1);
  transform: translateY(-1px);
}

/* The toasts also live at the bottom centre — lift them clear of the bar. */
body.has-cookie-notice .toast { bottom: 6.5rem; }

@media (max-width: 640px) {
  .cookie-notice { padding: 1rem 0; }
  .cookie-notice-inner { flex-direction: column; align-items: stretch; gap: 0.85rem; }
  .cookie-notice-text { font-size: var(--fs-sm); }
  /* Both buttons share one full-width row, evenly split — keeps the bar
     compact (two stacked buttons would crowd small screens). */
  .cookie-notice-actions { width: 100%; gap: 0.55rem; }
  .cookie-btn { flex: 1 1 0; padding: 0.78rem 0.9rem; }
  body.has-cookie-notice .toast { bottom: 11rem; }
}

/* --------------------------------------------------------------------------
   4. Header — frosted glass, sticky
   -------------------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  /* R40 — the merchant can add navigation buttons, so the bar may need a
     second row on medium screens: grow instead of clipping the buttons. */
  min-height: var(--header-h);
  height: auto;
  display: flex;
  align-items: center;
  background: transparent;
  border-bottom: 1px solid var(--line-dark);
  transition: box-shadow 0.3s var(--ease);
}

/* R141f — the frost lives on a ::before underlay, never on the header
   itself. An element carrying backdrop-filter becomes a backdrop ROOT, and
   every dropdown hanging below it would then be unable to blur the real
   page (Chromium scopes a descendant's backdrop-filter to the nearest
   ancestor that carries a filter — measured: with the frost on the header,
   no panel blurred the page at all). The same trick the panels use below. */
.site-header::before {
  content: '';
  position: absolute;
  /* the extra 1px at the bottom deliberately reaches INTO the header's own
     border strip: without it the pale hairline (border-bottom) sits on the
     page's paper background and reads as a white line between the bar and
     the hero — the frost used to paint beneath it when the background lived
     on the element itself (background-clip: border-box). */
  inset: 0 0 -1px 0;
  z-index: -1;
  background: rgba(22, 27, 22, 0.68);
  backdrop-filter: blur(16px) saturate(150%);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
  transition: background 0.3s var(--ease);
  pointer-events: none;
}

.site-header.is-scrolled::before { background: rgba(22, 27, 22, 0.86); }

.site-header.is-scrolled {
  box-shadow: 0 8px 30px rgba(10, 14, 10, 0.25);
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  width: 100%;
}

.logo {
  font-family: var(--font-display);
  /* R53 — the text wordmark reads too timid at 1.7rem inside the 72px bar;
     step it up to 2rem (mobile steps back down at the bottom of the file). */
  font-size: var(--fs-2xl);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: #fff;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
}
.logo img {
  display: block;
  /* R40 — the logo area now accepts a long wordmark: up to 390px wide, capped
     at 52px tall so it always fits the 72px sticky header. Narrower screens
     step it back down again (see the bottom of this file). */
  max-height: 52px;
  max-width: 390px;
  width: auto;
  height: auto;
  border-radius: 3px;
}

/* --- Navigation --- */
.main-nav { flex: 1 1 auto; min-width: 0; }
.logo, .header-actions { flex: 0 0 auto; }

.nav-list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  padding: 0.35rem 0;
}

.nav-list > li { position: relative; }

.nav-list > li > a {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.55rem 0.85rem;
  color: var(--sage);
  font-size: var(--fs-sm);
  font-weight: 500;
  /* R40 — page titles become nav buttons too; a wrapped two-line button reads
     as broken, so labels stay on one line and the compact step below buys
     extra room on medium screens. */
  white-space: nowrap;
  letter-spacing: 0.01em;
  border-radius: var(--radius-sm);
  transition: color 0.2s var(--ease), background 0.2s var(--ease);
}

.nav-list > li > a:hover,
.nav-list > li > a:focus-visible {
  color: #fff;
  background: rgba(255, 255, 255, 0.08);
}

.chevron {
  font-size: 0.7em;
  transition: transform 0.2s var(--ease);
}

.has-dropdown:hover .chevron,
.has-dropdown:focus-within .chevron { transform: rotate(180deg); }

/* Dropdown menu — appears on hover.
   R66 — the panel hugs its longest label (no wrapping of long category
   names).
   R68 — the panel opens with a full-width head row ("All <category>"); the
   level-2 entries sit BELOW it in fixed columns of at most four rows, and a
   level-3 group stays folded until its row is clicked. */
.dropdown {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  width: max-content;
  min-width: 230px;
  /* R68 — four equal columns of long names need more room than the old
     two; past this the columns simply stop being equal (graceful), they
     never overlap or wrap. */
  max-width: min(92vw, 1200px);
  padding: 0.5rem;
  background: rgba(255, 253, 248, 0.92);
  /* R134 — the glass, and how opaque it is. The plain rgba() above is the
     fallback; the second line reads the merchant's choice from Admin →
     Styles (--nav-menu-alpha). Unset = 92%, i.e. exactly the old value. */
  background: rgb(255 253 248 / var(--nav-menu-alpha, 92%));
  /* R141f — the frost itself lives on the ::before underlay (see below), NOT
     here: an element with backdrop-filter is a backdrop root, and the flyout
     third level is a DOM descendant of this panel — its own frost could only
     ever sample this panel's empty interior, never the page behind it.
     Measured before the move: level 2 frosted the page, level 3 sat crisp.
     isolation keeps the negative-z underlay inside the panel. */
  isolation: isolate;
  /* the hairline rides the panel's own alpha: an opaque border around a
     nearly transparent pane read as a white line (measured at 20%). The
     rgb(from …) form keeps the theme's --line token and just borrows its
     alpha from the same knob that drives the glass; browsers without
     relative color syntax keep the solid line above. */
  border: 1px solid var(--line);
  border-color: rgb(from var(--line) r g b / var(--nav-menu-alpha, 92%));
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  /* NO opacity transition on the panel: any ancestor at opacity < 1 becomes
     a backdrop root, and during the fade the ::before frost simply cannot
     sample the page — the panel opened transparent and snapped to frosted
     one frame later (measured). R149: NO transition on the base (closed)
     state either — closing is instant, so switching to another nav button
     swaps menus in the same frame instead of leaving the old panel sliding
     out for a beat. The open slide lives on the hover state below. */
  visibility: hidden;
  transform: translateY(8px);
  /* R157 (M-3) — a CLOSED panel must not catch the mouse. visibility:hidden
     alone is not enough: a third-level group the visitor unfolded keeps its
     own visibility:visible (inherited-hidden loses to the child), and
     opacity:0 hides only the PAINT, not the hit test. Without this, the
     closed panel under the nav still swallowed clicks and could re-enter
     its own :hover and pop back open — the same class of fault the navmenu
     panels already guard against (see .dropdown.navmenu below). Like
     pointer-events everywhere: it cannot transition, it flips instantly,
     which is exactly what we want. */
  pointer-events: none;
  /* R154b — visibility is INHERITED, so a descendant that declares its own
     visibility:visible overrides it and keeps painting. Every panel has
     such descendants: the always-shown third level of the mega and
     list-static styles, and any third-level group the visitor unfolded
     (.dropdown-group.is-open, .navmenu-item.is-open) — after the panel
     closed, that text stayed on the page. opacity:0 hides the WHOLE subtree
     no matter what the children declare. It flips in the same frame as
     visibility (NO opacity transition anywhere on this pair) so the frost
     never has a mid-fade frame to mis-sample — the R149 beat cannot come
     back. */
  opacity: 0;
}

.has-dropdown:hover .dropdown,
.has-dropdown:focus-within .dropdown {
  visibility: visible;
  opacity: 1;
  transform: translateY(0);
  /* R157 (M-3) — restore hit-testing while the panel is open (the closed
     base state sets none). */
  pointer-events: auto;
  /* the slide belongs to the OPEN state only: transitions read their
     property from the destination state, so on open the panel glides in
     (fully frosted from the first painted frame — transform never creates
     a backdrop root), and on close the destination has no transition and
     the panel is gone immediately. */
  transition: transform 0.22s var(--ease);
}

/* --------------------------------------------------------------------------
   R181 — a CLOSED dropdown must not lay out at all.

   visibility:hidden still produces an absolutely positioned box. Four of
   them widened .nav-list -> .header-inner -> .site-header, which is why R80
   had to clip the root. Taking the closed panel out of layout fixes the
   cause rather than the symptom, and lets the root go back to visible
   overflow.

   The child selector is deliberate: it targets ONLY the top-level panels
   (.nav-list > li.has-dropdown > .dropdown). The navmenu flyout's third
   level also carries .dropdown, but lives under .navmenu-item and keeps its
   own grid/0fr fold and sideways transform untouched.
   -------------------------------------------------------------------------- */
.nav-list > .has-dropdown > .dropdown { display: none; }

@keyframes nw-dropdown-in {
  from { transform: translateY(8px); }
  to   { transform: translateY(0); }
}

.nav-list > .has-dropdown:hover > .dropdown,
.nav-list > .has-dropdown:focus-within > .dropdown {
  display: block;
  /* display:none -> block cannot transition, so the R149 8px glide is
     restored with a transform-only animation. opacity stays at 1 throughout:
     an opacity < 1 ancestor becomes a backdrop root and the panel's frost
     would stop sampling the page (R141f). */
  animation: nw-dropdown-in 0.22s var(--ease) both;
}

@media (prefers-reduced-motion: reduce) {
  .nav-list > .has-dropdown:hover > .dropdown,
  .nav-list > .has-dropdown:focus-within > .dropdown { animation: none; }
}

/* R212 — the "All <category>" head row was removed from every panel by
   request: the menu label above the panel already links to that category.
   Its rules (R68/R69/R136) went with it — nothing renders the class now. */


/* R68 — the level-2 list. The entry count alone decides the layout: every
   column carries at most four rows, so five entries make two columns, nine
   make three, and so on. Columns are equally wide (the widest one wins) and a
   hairline runs down each gully. A single column needs no class at all. */
.dropdown-body {
  display: grid;
  grid-template-columns: 1fr;
  column-gap: 1.5rem;
}

.dropdown-body.cols-2 { grid-template-columns: repeat(2, 1fr); }
.dropdown-body.cols-3 { grid-template-columns: repeat(3, 1fr); }
.dropdown-body.cols-4 { grid-template-columns: repeat(4, 1fr); }

/* Columns are stacked flex boxes, not a flowing text column: opening a
   level-3 list below a row grows THAT column only — every other row keeps its
   place, so nothing jumps under the pointer. */
.dropdown-col {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.dropdown-col + .dropdown-col::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: -0.75rem;
  width: 1px;
  background: var(--line);
  background: rgb(from var(--line) r g b / var(--nav-menu-alpha, 92%));
}

/* R67 — the hover state is PURELY a soft colour crossfade: the old
   padding-left shift reflowed the row and made a long category name look
   like it was punching the panel wider as the pointer crossed it. The accent
   glides in from a pseudo-element instead, so nothing moves.

   R68 — the level-2 row of a group is a <button> (it unfolds the level-3 list
   rather than navigating), so the shared rule now carries the button reset
   too; a <button> would otherwise keep its UA font, line box and chrome. */
.dropdown-link {
  position: relative;
  display: block;
  width: 100%;
  padding: 0.55rem 0.85rem;
  border: 0;
  /* R136 — follows the merchant's Corner rounding (--radius), like the panel
     around it, so one slider shapes the whole dropdown. */
  border-radius: var(--radius);
  background: none;
  color: var(--ink-soft);
  font-family: inherit;
  font-size: var(--fs-sm);
  line-height: inherit;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.26s var(--ease), color 0.26s var(--ease);
}

.dropdown-link::before {
  content: '';
  position: absolute;
  left: 0.3rem;
  top: 50%;
  width: 2px;
  height: 1.05em;
  margin-top: -0.525em;
  border-radius: 2px;
  background: currentColor;
  opacity: 0;
  transform: scaleY(0.35);
  transition: opacity 0.26s var(--ease), transform 0.26s var(--ease);
}

.dropdown-link:hover,
.dropdown-link:focus-visible {
  background: var(--sand);
  color: var(--forest-900);
}

.dropdown-link:hover::before,
.dropdown-link:focus-visible::before {
  opacity: 0.5;
  transform: scaleY(1);
}

/* R33 — invisible hover bridge over the 8px gap between the nav link and
   its dropdown, so moving the mouse down doesn't close the menu. */
/* R141f — the panel's frost, on an underlay: blurring (tint over page) is
   mathematically the same as tinting the blurred page — a constant colour
   commutes with the blur — so the panel looks exactly as before, but no
   backdrop root forms here and a nested level-3 panel can frost the real
   page too. Every .dropdown surface (level 2 AND the flyout level 3) gets
   this, which is what makes the two levels read as the same glass. */
.dropdown::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  pointer-events: none;
}

/* the hover-trap bridge over the 8px header-to-panel gap — the same strip
   the old .dropdown::before carried, moved to ::after now that ::before is
   the frost underlay. Top-level panels only: a flyout level 3 opens from a
   row inside the level-2 panel and bridges sideways instead (its own
   ::after, over in the flyout section). */
.has-dropdown > .dropdown::after {
  content: '';
  position: absolute;
  top: -10px;
  left: 0;
  right: 0;
  height: 10px;
}

/* R66/R67 — the third level lives INSIDE the panel: a sub-category with
   children becomes a titled group, its children indented beneath a
   hairline rule. One calm surface instead of a second flying panel —
   hierarchy comes from weight, size and indentation, never from colour, so
   dark themes restyle it by touching only .dropdown-link.

   The ranking is deliberately legible at a glance:
     "All <category>"  — panel head, bold, a full row of its own
     level 2 (a child) — normal weight, full-strength ink
     level 3 (a grandchild) — smaller, softened, behind the guide rule

   R68 — and level 3 is FOLDED AWAY until it is asked for. */
.dropdown-group {
  display: flex;
  flex-direction: column;
}

/* the row that opens the level-3 list: the label keeps the left edge, the
   arrow parks on the right of its own column */
/* R212 — a group row is now TWO targets in a 2-column grid: the name is a
   link to the category's own page (left, fills the width) and the caret is a
   button that only unfolds the third level (right, at its natural size).
   The grid is what keeps the button the full height of the row without a
   magic number — `stretch` is the default, and the row height follows the
   link's own padding. */
.dropdown-group-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: stretch;
}

.dropdown-group .group-title {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-weight: 600;
}

/* the caret button: no chrome of its own, just the same hit area the old
   full-row button offered on its right-hand side */
.group-expand {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.4rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: none;
  /* R215 — same neutral pair as .navmenu-expand (see the token block on
     .navmenu): the caret button answers in charcoal, not in the theme accent. */
  color: var(--navmenu-btn-ink, var(--navmenu-ink, inherit));
  font: inherit;
  cursor: pointer;
  transition: background 0.26s var(--ease), color 0.26s var(--ease);
}
.group-expand:hover,
.group-expand:focus-visible {
  background: var(--navmenu-btn-wash, rgba(128, 128, 128, 0.16));
  color: var(--navmenu-btn-wash-ink, var(--navmenu-ink, inherit));
}
.group-expand:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: -2px;
}

.group-caret {
  flex: none;
  font-size: 0.7em;
  line-height: 1;
  opacity: 0.7;
  transition: transform 0.28s var(--ease), opacity 0.28s var(--ease);
}

.dropdown-group.is-open .group-caret {
  transform: rotate(180deg);
  opacity: 1;
}

/* The unfold itself. A 0fr grid row collapses to nothing and animates to the
   list's natural height, so a level-3 list of ten entries opens just as
   cleanly as one of two — no max-height guesswork, and never a scrollbar.
   The clip wrapper holds the overflow; the padding and the guide rule sit one
   level deeper so the collapsed state is a true zero, not a sliver. */
.dropdown-sublinks {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  visibility: hidden;
  transition: grid-template-rows 0.3s var(--ease), opacity 0.3s var(--ease), visibility 0.3s;
}

.dropdown-group.is-open .dropdown-sublinks {
  grid-template-rows: 1fr;
  opacity: 1;
  visibility: visible;
}

.dropdown-sublinks-clip {
  overflow: hidden;
  min-height: 0;
}

.dropdown-sublinks-list {
  margin: 0.1rem 0 0.35rem 0.95rem;
  padding-left: 0.55rem;
  border-left: 1px solid var(--line);
  /* rides the panel's alpha like every other dropdown hairline */
  border-left-color: rgb(from var(--line) r g b / var(--nav-menu-alpha, 92%));
}


.dropdown-sublinks .sub-link {
  padding: 0.4rem 0.7rem;
  font-size: var(--fs-xs);
  opacity: 0.78;
  transition: background 0.26s var(--ease), color 0.26s var(--ease), opacity 0.26s var(--ease);
}

/* the guide rule already marks the level — no second accent needed */
.dropdown-sublinks .sub-link::before {
  display: none;
}

.dropdown-sublinks .sub-link:hover,
.dropdown-sublinks .sub-link:focus-visible {
  opacity: 1;
}

/* --- Header actions --- */
.header-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.header-search input {
  width: 150px;
  padding: 0.5rem 0.9rem;
  border: 1px solid var(--line-dark);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font-size: var(--fs-sm);
  transition: width 0.25s var(--ease), background 0.2s var(--ease), border-color 0.2s var(--ease);
}
.header-search input::placeholder { color: var(--sage); }
.header-search input:focus {
  width: 190px;
  background: rgba(255, 255, 255, 0.14);
  border-color: var(--amber);
  outline: none;
}

.btn-icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: #fff;
  transition: background 0.2s var(--ease);
}
.btn-icon:hover { background: rgba(255, 255, 255, 0.12); }

.cart-toggle::after {
  content: attr(data-cart-count);
  position: absolute;
  top: -2px;
  right: -4px;
  min-width: 19px;
  height: 19px;
  padding: 0 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--amber);
  color: #fff;
  font-size: var(--fs-2xs);
  font-weight: 700;
  border-radius: 999px;
  border: 2px solid var(--forest-800);
}
.cart-toggle[data-cart-count="0"]::after { display: none; }

/* Hamburger */
.menu-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 42px;
  height: 42px;
  padding: 10px;
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
}
.menu-toggle span {
  display: block;
  height: 2px;
  width: 100%;
  background: #fff;
  border-radius: 2px;
  transition: transform 0.25s var(--ease), opacity 0.25s var(--ease);
}

/* --- Mobile menu --- */
.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 150;
  padding: 1.5rem;
  background: rgba(22, 27, 22, 0.97);
  /* R136 — the drawer follows the merchant's dropdown-opacity setting too
     (--nav-menu-alpha, Admin → Styles). max(60%, …) is a readability floor:
     white labels over a nearly transparent drawer would vanish on bright
     pages, so the drawer never goes below 60% however low the glass is set.
     Browsers without max() in the alpha slot keep the 0.97 fallback above. */
  background: rgb(22 27 22 / max(60%, var(--nav-menu-alpha, 97%)));
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  transform: translateX(100%);
  transition: transform 0.3s var(--ease);
  overflow-y: auto;
}
.mobile-menu.is-open { transform: translateX(0); }

.mobile-menu-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1.5rem;
}

.menu-close {
  width: 42px;
  height: 42px;
  font-size: var(--fs-2xl);
  line-height: 1;
  color: #fff;
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
}
.menu-close:hover { background: rgba(255,255,255,0.1); }

.mobile-menu nav > ul > li > a,
.mobile-menu .mobile-cat > a {
  display: block;
  padding: 0.9rem 0;
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: 600;
  color: #fff;
  border-bottom: 1px solid var(--line-dark);
}

/* R79 — each top-level group is a row: the category link on the left, a
   caret button on the right. The row (not the link) carries the bottom
   border, so the border lines up under both the label and the button. */
.mobile-cat-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  border-bottom: 1px solid var(--line-dark);
}
.mobile-cat-row > a {
  display: block;
  flex: 1;
  padding: 0.9rem 0;
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: 600;
  color: #fff;
}
.mobile-cat-toggle {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-right: -8px;
  /* R113c — the caret used to be a bare pale glyph on the drawer background;
     on a phone it read as decoration, not as "this row opens". The toggle is
     now a visible badge: a ringed, slightly filled circle with a white
     chevron, still 44px for the thumb. */
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 50%;
  color: #fff;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.mobile-cat-toggle:hover,
.mobile-cat-toggle:focus-visible { background: rgba(255, 255, 255, 0.22); color: #fff; }
.mobile-cat-caret {
  font-size: var(--fs-xl);
  line-height: 1;
  transition: transform 0.25s var(--ease);
}
/* .is-open sits on the <li> while the panel is expanded */
.mobile-cat.is-open .mobile-cat-caret { transform: rotate(180deg); }

.mobile-sub {
  padding: 0.35rem 0 0.6rem 1rem;
}
.mobile-sub a {
  display: block;
  padding: 0.45rem 0;
  color: var(--sage);
  font-size: var(--fs-md);
}
.mobile-sub a:hover { color: #fff; }

/* R33 — third level in the mobile drawer: deeper indent, smaller type,
   subtle left rule so the hierarchy stays readable on small screens. */
.mobile-sub--deep {
  padding: 0.1rem 0 0.3rem 1rem;
  margin-top: 0.1rem;
  border-left: 2px solid var(--line-dark);
}
.mobile-sub--deep a {
  font-size: var(--fs-md);
  color: var(--sage);
  opacity: 0.92;
}

.mobile-menu .btn { margin-top: 1.5rem; }

body.menu-open { overflow: hidden; }

/* --------------------------------------------------------------------------
   5. Buttons
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.72rem 1.5rem;
  border: 1px solid transparent;
  /* R211 — Admin → Styles → Buttons. Unset keeps the shipped pill; the
     token is only written when the merchant changes the shape. */
  border-radius: var(--nw-btn-radius, 999px);
  font-weight: 600;
  font-size: var(--fs-sm);
  letter-spacing: 0.01em;
  line-height: 1.2;
  text-align: center;
  transition: transform 0.2s var(--ease), box-shadow 0.2s var(--ease),
              background 0.2s var(--ease), color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.btn:active { transform: translateY(1px); }

.btn-primary {
  background: var(--nw-btn-primary-bg, var(--amber));
  color: var(--nw-btn-primary-fg, #fff);
  box-shadow: 0 6px 18px rgba(201, 138, 61, 0.32);
  /* the glow follows the button, not the old amber, once one is chosen */
  box-shadow: 0 6px 18px color-mix(in srgb, var(--nw-btn-primary-bg, var(--amber)) 32%, transparent);
}
.btn-primary:hover {
  background: var(--nw-btn-primary-hover, var(--amber-600));
  box-shadow: 0 10px 26px rgba(201, 138, 61, 0.4);
  box-shadow: 0 10px 26px color-mix(in srgb, var(--nw-btn-primary-hover, var(--amber-600)) 40%, transparent);
  transform: translateY(-2px);
}

.btn-secondary {
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  border-color: rgba(255, 255, 255, 0.4);
  backdrop-filter: blur(6px);
}
.btn-secondary:hover { background: rgba(255, 255, 255, 0.2); transform: translateY(-2px); }

.btn-outline {
  background: transparent;
  color: var(--nw-btn-outline-fg, var(--forest-800));
  border-color: var(--nw-btn-outline-border, var(--forest-600));
}
.btn-outline:hover {
  background: var(--nw-btn-outline-hover-bg, var(--forest-800));
  color: var(--nw-btn-outline-hover-fg, #fff);
  transform: translateY(-2px);
}

.btn-text {
  background: none;
  border: none;
  padding: 0.4rem 0;
  color: var(--amber-600);
  font-weight: 600;
}
.btn-text:hover { text-decoration: underline; }

.btn-large { padding: 0.95rem 2.1rem; font-size: var(--fs-md); }
.btn-block { width: 100%; }
.btn-sm { padding: 0.4rem 0.9rem; font-size: var(--fs-xs); }

.btn-danger {
  background: transparent;
  color: var(--nw-btn-danger-fg, var(--danger));
  border-color: rgba(181, 64, 47, 0.4);
  border-color: color-mix(in srgb, var(--nw-btn-danger-fg, var(--danger)) 40%, transparent);
}
.btn-danger:hover {
  background: var(--nw-btn-danger-hover, var(--danger));
  color: #fff;
}

.btn-paypal {
  background: #ffc439;
  color: #003087;
  box-shadow: 0 6px 18px rgba(255, 196, 57, 0.32);
}
.btn-paypal:hover {
  background: #f2ba36;
  box-shadow: 0 10px 26px rgba(255, 196, 57, 0.4);
  transform: translateY(-2px);
}
.btn-paypal:disabled { opacity: 0.7; cursor: not-allowed; transform: none; }

.btn-add {
  background: var(--nw-btn-cta-bg, var(--forest-800));
  color: var(--nw-btn-cta-fg, #fff);
  padding: 0.6rem 1.1rem;
  font-size: var(--fs-sm);
}
.btn-add:hover { background: var(--nw-btn-cta-hover, var(--amber)); transform: translateY(-2px); }

.btn-quick-add {
  background: var(--nw-btn-icon-bg, var(--amber));
  color: var(--nw-btn-icon-fg, #fff);
  width: 42px;
  height: 42px;
  /* R211 — see --nw-btn-shape: only written when the merchant chose a
     rounding, so the shipped square corner is untouched by a pill setting. */
  border-radius: var(--nw-btn-shape, var(--radius-sm));
}
.btn-quick-add:hover { background: var(--nw-btn-icon-hover, var(--amber-600)); transform: translateY(-2px); }

/* --------------------------------------------------------------------------
   6. Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: min(88vh, 780px);
  display: flex;
  align-items: center;
  color: #fff;
  overflow: hidden;
}

.hero-media, .hero-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Background video or iframe (added in the admin Site settings). Sized to
   cover the hero area the same way as the image fallback. */
.hero-media .hero-bg-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.hero-media .hero-bg-iframe {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 177.77vh;          /* 16:9 cover at viewport height */
  height: 56.25vw;          /* 16:9 cover at viewport width  */
  min-width: 100%;
  min-height: 100%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  border: 0;
}
.hero.has-video .hero-overlay {
  /* When a background video is active the overlay needs to be slightly heavier
     so the text remains readable regardless of the footage. */
  background:
    linear-gradient(180deg, rgba(22, 27, 22, 0.55) 0%, rgba(22, 27, 22, 0.25) 40%, rgba(22, 27, 22, 0.9) 100%);
}

.hero-overlay {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(22, 27, 22, 0.15) 0%, rgba(22, 27, 22, 0.05) 40%, rgba(22, 27, 22, 0.85) 100%),
    linear-gradient(90deg, rgba(22, 27, 22, 0.55) 0%, transparent 60%);
}

.hero-content {
  position: relative;
  z-index: 2;
  padding-block: clamp(2rem, 6vh, 4rem);
  /* R115e — this box is a full-frame column sitting above the pictures
     (z-index 2 over the deck's 1), and it carries nothing interactive of its
     own: the buttons live in `.hero-text`, which sets `pointer-events` back to
     auto on itself. Without this line the column hands every hovering pointer
     to whichever element is under it, so a hero whose content is centred and a
     hero whose content is at the left produce completely different results:
     `mouseenter` on the hero fires once in the second case and continuously —
     enter/leave/enter, each pair a real crossing between text, buttons and the
     bare panel — in the first. The rotation pauses and resumes on that pair,
     so whether the hero kept moving while the pointer rested depended on where
     the merchant's content happened to sit. */
  pointer-events: none;
}

/* The text block sits at the content column's left edge (aligned with the rest
   of the page), never flush against the viewport edge, and never exceeds
   900px in width.

   R101 — it also sits on a frosted-glass panel. The picture behind the hero is
   whatever the merchant uploaded (or the slideshow, or a video), so the words
   cannot assume the photograph is calm exactly where they land: a bright sky,
   a sunlit clearing or a busy treeline used to run straight through the
   headline. The panel uses the same ink and the same blur-and-saturate frost
   as the sticky navigation bar, just a great deal fainter, so the two still
   read as one system rather than two unrelated pieces of chrome.

   R101b — the merchant asked for this panel to be a whisper, not a slab, so
   the ink is now 0.10. At that alpha the panel no longer carries WCAG
   contrast, and this is a deliberate trade-off, not an oversight: over a
   pure-white photograph white body copy measures 1.22:1, body copy holds AA
   (4.5:1) only while the photo behind stays at or below roughly level 128/255,
   and the headline's large-text floor (3:1) only to about 162/255. Brighter
   photos rely on the frost smearing the detail flat and on the text shadow
   below keeping the glyph edges crisp. Should the panel ever need to carry
   contrast again, the measured ladder is: 0.35 → 2.20:1, 0.46 → 3:1
   (headline only), 0.58 → 4.5:1 (full AA).

   The hairline is back at 1px while the ink stays at 0.10: at this alpha the
   pane is almost invisible against a dark photograph, and the edge is what
   tells the eye a panel is there at all. It adds no contrast — a 14% white
   line is decoration — so it does not change the numbers above.

   R103 — the ink and the frost moved into three custom properties so the
   merchant can tune the panel from Admin → Styles instead of editing this
   file. The starting values are exactly the R101b ones, so every existing
   store looks identical until somebody moves a slider. The properties are
   declared in :root above; see --hero-ink / --hero-blur / --hero-saturate. */
.hero-text {
  /* R217 — the pane's box is the merchant's now: a max width on a desktop
     screen and an optional minimum height. Both arrive as custom properties
     written by Admin → Styles; the fallbacks are the shipped 900px and the
     shipped "as tall as the copy". min-height: 0 is a no-op, so a store that
     never moves the sliders renders exactly the R216 panel. */
  max-width: var(--hero-panel-w, 900px);
  min-height: var(--hero-panel-h, 0px);
  /* Vertically centring only does anything once a height is set — at
     min-height 0 the box is its content, so the shipped look is untouched. */
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(1.4rem, 3vw, 2.5rem);
  border-radius: var(--radius);
  /* R115e — `.hero-content` now passes the pointer through (see above), so the
     panel takes it back: its headline is selectable and its buttons are real
     links. The glass effect itself needs nothing from the pointer. */
  pointer-events: auto;
  background: rgba(22, 27, 22, var(--hero-ink, 0.10));
  backdrop-filter: blur(var(--hero-blur, 10px)) saturate(var(--hero-saturate, 125%));
  -webkit-backdrop-filter: blur(var(--hero-blur, 10px)) saturate(var(--hero-saturate, 125%));
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 18px 50px rgba(10, 14, 10, 0.30);
}

/* R103 — a panel that is asked to carry contrast again needs an edge that is
   visible on paper-white as well as on a dark photograph. The alpha is what
   decides that, so the hairline follows it: below 0.30 the pane is a whisper
   and the 14% line above is enough to hint at it; at or above 0.30 the pane is
   a surface, and 14% white would read as a gap rather than an edge. The
   attribute is set by app/header.php only when the merchant moved the slider
   past that point, so the default page carries no attribute at all. */
.hero-text[data-hero-panel="solid"] {
  border-color: rgba(255, 255, 255, 0.28);
}

/* R213 — where the pane sits inside the content column.
 *
 * Left is the shipped position and needs no rule: the pane is a block child of
 * the column, so it already starts at the column's left edge. Centre and right
 * are that same block pushed across by an auto margin — which means the offset
 * follows the column's real width, padding and the pane's own max-width with no
 * magic numbers, and nothing has to be special-cased at any breakpoint (on a
 * phone the pane fills the column, so all three positions land in the same
 * place, which is the only sane answer at that width).
 *
 * The column itself is not centred or moved: only the pane is. Keeping the
 * column in place is what lets "right" mean "the right end of the page grid"
 * rather than "the right end of the viewport", so the pane stays aligned with
 * the header, the product grid and the footer at every width. */
.hero-content.is-align-center .hero-text { margin-inline: auto; }
.hero-content.is-align-right .hero-text { margin-inline-start: auto; }

/* R213 — the panel switched off. The `<h1>` is still in the document (see
 * index.php) but out of sight; `.visually-hidden` already does that, so there
 * is nothing to add here. This comment exists so the next person reading the
 * hero rules does not go looking for a `.is-panel-hidden` selector that was
 * deliberately not written. */

/* R101 — the glyph-edge shadow. Since R101b the panel is too faint to carry
   contrast itself, so this shadow is what actually keeps the copy readable
   over a bright photograph — it is the defence, not a second line of it.
   Scoped to the hero: `.eyebrow` is also used on account.php and the account
   sidebar, which sit on cream paper where a shadow would be nothing but dirt. */
.hero .eyebrow,
.hero h1,
.hero-lead {
  text-shadow: 0 1px 3px rgba(10, 14, 10, 0.55);
}

.eyebrow {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--amber);
  margin: 0 0 1rem;
}

.hero h1 {
  color: #fff;
  font-size: var(--fs-3xl);
  font-weight: 600;
  margin-bottom: 0.9rem;
}

.hero-lead {
  color: rgba(255, 255, 255, 0.85);
  font-size: var(--fs-lg);
  max-width: 34em;
  margin-bottom: 2rem;
}

.hero-actions { display: flex; flex-wrap: wrap; gap: 0.9rem; }

/* --------------------------------------------------------------------------
   7. Trust bar
   -------------------------------------------------------------------------- */
.trust-bar {
  background: var(--forest-800);
  color: var(--sage);
}

.trust-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.5rem;
  padding-block: 1.6rem;
}

.trust-item {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  font-size: var(--fs-sm);
  font-weight: 500;
}
.trust-item svg { flex: none; color: var(--amber); }

/* --------------------------------------------------------------------------
   8. Section scaffolding
   -------------------------------------------------------------------------- */
.section-header { text-align: center; margin-bottom: 2.75rem; }
.section-header h2 { font-size: var(--fs-2xl); margin-bottom: 0; }

.categories, .featured, .craft, .testimonials, .listing {
  padding-block: clamp(3rem, 7vw, 5.5rem);
}
.categories { background: var(--paper); }

/* Editable content pages (Support, Shipping & Returns, Privacy Policy, etc.)
   R47 — magazine-grade typography: ruled section headings, generous leading
   and amber list markers, so an HTML page composed in the rich editor lands
   looking deliberate and well organised. R216 dropped the image frame: the
   editor now inserts a whole batch as one flush run, and a merchant who wants
   breathing room presses Enter. */
.content-page {
  max-width: 54em;
  margin-inline: auto;
  font-size: var(--fs-md);
}
.content-page > :first-child { margin-top: 0; }
.content-page h2 {
  color: var(--forest-900);
  font-size: var(--fs-xl);
  line-height: 1.25;
  margin: 2.4rem 0 0.8rem;
}
.content-page h3 {
  color: var(--forest-800);
  font-size: var(--fs-lg);
  line-height: 1.3;
  margin: 2.2rem 0 0.75rem;
  padding-top: 1.35rem;
  border-top: 1px solid var(--line);
}
.content-page h3:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
/* R216 — authored rich text no longer invents vertical space. See the matching
   rule for .product-description below and .rich-area in admin.css: a stack of
   pictures sits flush and a blank line appears only where Enter was pressed. */
.content-page p { color: var(--ink-soft); line-height: 1.85; margin: 0; }
.content-page .lead { color: var(--ink); font-size: var(--fs-lg); }
.content-page ul, .content-page ol {
  margin: 0 0 1.25rem 1.5rem;
  padding: 0;
  color: var(--ink-soft);
  line-height: 1.8;
}
.content-page ul { list-style: disc; }
.content-page ol { list-style: decimal; }
.content-page li { margin-bottom: 0.45rem; padding-left: 0.2rem; }
.content-page li::marker { color: var(--amber-600); font-weight: 700; }
.content-page strong { color: var(--forest-800); font-weight: 600; }
.content-page a {
  color: var(--amber-600);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
.content-page a:hover { color: var(--forest-800); }
.content-page img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0;
  border-radius: 0;
  box-shadow: none;
}
.content-page blockquote {
  margin: 1.4rem 0;
  padding: 0.85rem 1.3rem;
  border-left: 3px solid var(--amber);
  background: var(--paper);
  border-radius: 0 var(--radius) var(--radius) 0;
  color: var(--ink-soft);
  font-style: italic;
}
.content-page blockquote p:last-child { margin-bottom: 0; }
.content-page table {
  width: 100%;
  border-collapse: collapse;
  margin: 1.4rem 0;
  font-size: var(--fs-sm);
}
.content-page th, .content-page td {
  border: 1px solid var(--line);
  padding: 0.55rem 0.8rem;
  text-align: left;
  color: var(--ink-soft);
}
.content-page th { background: var(--sand); color: var(--forest-800); font-weight: 600; }
.content-page hr { border: 0; border-top: 1px solid var(--line); margin: 2.2rem 0; }

/* R48 — policy-page presentation: a "last updated" line in the hero, numbered
   sections (pure CSS counters, so admin-authored HTML needs no markup) and a
   sticky "On this page" index that main.js builds from the article's own
   headings. It only opens on wide screens once JS confirms enough sections,
   so narrow viewports keep the plain article they had before. Everything is
   variable-driven, so the bundled themes inherit it unchanged. */
.hero-meta {
  color: var(--sage);
  font-size: var(--fs-sm);
  letter-spacing: 0.02em;
  margin: 0.85rem 0 0;
  display: flex;
  align-items: center;
  gap: 0.55rem;
}
.hero-meta::before {
  content: "";
  flex: none;
  width: 1.7rem;
  height: 1px;
  background: var(--amber);
}

.content-page h2, .content-page h3 { scroll-margin-top: calc(var(--header-h) + 1.75rem); }

.content-page-numbered { counter-reset: nw-section; }
.content-page-numbered h3 { counter-increment: nw-section; }
.content-page-numbered h3::before {
  content: counter(nw-section, decimal-leading-zero);
  display: block;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.18em;
  color: var(--amber-600);
  margin-bottom: 0.3rem;
}

.page-toc { display: none; }
.page-toc-title {
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 0.85rem;
}
.page-toc-nav a {
  display: block;
  padding: 0.4rem 0 0.4rem 0.95rem;
  border-left: 2px solid var(--line);
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  line-height: 1.45;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.page-toc-nav a:hover { color: var(--forest-800); }
.page-toc-nav a.is-active {
  color: var(--amber-600);
  border-left-color: var(--amber);
  font-weight: 600;
}
.page-toc-num {
  margin-right: 0.55rem;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted);
}
.page-toc-nav a.is-active .page-toc-num { color: var(--amber-600); }

@media (min-width: 1024px) {
  .content-wrap.has-toc.toc-ready {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 15.5rem;
    gap: 4rem;
    align-items: start;
  }
  .content-wrap.has-toc.toc-ready .page-toc {
    display: block;
    position: sticky;
    top: calc(var(--header-h) + 2.5rem);
    max-height: calc(100vh - var(--header-h) - 4.5rem);
    overflow: auto;
    padding-top: 0.4rem;
  }
}

.section-actions { text-align: center; margin-top: 2.75rem; }

/* --------------------------------------------------------------------------
   9. Shop by Category — uniform image tiles
   -------------------------------------------------------------------------- */
.category-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.5rem;
}

.category-card {
  position: relative;
  display: block;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  isolation: isolate;
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
.category-card:hover {
  transform: translateY(-6px);
  box-shadow: var(--shadow-lg);
}

/* Uniform size: every category image is a fixed 1:1 square, cropped to fill. */
.category-card img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  transition: transform 0.6s var(--ease);
}
.category-card:hover img { transform: scale(1.06); }

/* R210 — a category with no card picture yet. A typographic tile on the
   brand's own greens, at exactly the same 1:1 footprint as a photograph, so
   the grid keeps its rhythm whether or not every shelf has a picture. */
.category-card--empty {
  aspect-ratio: 1 / 1;
  display: grid;
  place-items: center;
  background: linear-gradient(140deg, var(--forest-800) 0%, var(--forest-700) 55%, #4a5a45 100%);
}
.category-initial {
  position: relative;
  z-index: 1;
  font-family: var(--font-display);
  font-size: var(--fs-3xl);
  font-weight: 600;
  color: rgba(255, 255, 255, 0.7);
  letter-spacing: 0.02em;
  transform: translateY(-0.6rem);
}

.category-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 45%, rgba(16, 20, 16, 0.85) 100%);
}

.category-title {
  position: absolute;
  left: 1.25rem;
  bottom: 2.1rem;
  z-index: 2;
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: 600;
  color: #fff;
  transition: bottom 0.3s var(--ease);
}

.category-count {
  position: absolute;
  left: 1.25rem;
  bottom: 1.1rem;
  z-index: 2;
  color: var(--sage);
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.category-card:hover .category-count { opacity: 1; transform: translateY(0); }
.category-card:hover .category-title { bottom: 2.6rem; }

/* --------------------------------------------------------------------------
   10. Product grid & cards
   -------------------------------------------------------------------------- */
.product-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.75rem;
}

/* Homepage featured products: fewer columns = larger, more prominent cards. */
.product-grid--featured {
  grid-template-columns: repeat(3, 1fr);
  gap: 2.25rem;
}
.product-grid--featured .product-info { padding: 1.5rem; }
.product-grid--featured .product-info h3 { font-size: var(--fs-xl); }
.product-grid--featured .product-price { font-size: var(--fs-xl); }
.product-grid--featured .btn-add { padding: 0.72rem 1.3rem; font-size: var(--fs-sm); }

.product-card {
  display: flex;
  flex-direction: column;
  background: var(--paper);
  border-radius: var(--radius);
  border: 1px solid var(--line);
  overflow: hidden;
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
.product-card:hover {
  transform: translateY(-6px);
  box-shadow: var(--shadow-md);
}

.product-image {
  position: relative;
  display: block;
  overflow: hidden;
  /* R138d — listing thumbnails sit on pure white like the detail gallery.
     The tile is now lighter than the card body below it, so a single hairline
     at its foot keeps the two areas legible instead of letting them bleed. */
  background: #fff;
  border-bottom: 1px solid var(--line);
}
.product-image img {
  width: 100%;
  aspect-ratio: 1 / 1;
  /* R138 — product photos are not always square: show the whole picture
     (letterboxed on the card's white tile) instead of cropping it. */
  object-fit: contain;
  transition: transform 0.6s var(--ease);
}
.product-card:hover .product-image img { transform: scale(1.05); }

.badge {
  position: absolute;
  top: 0.9rem;
  left: 0.9rem;
  padding: 0.25rem 0.7rem;
  background: var(--amber);
  color: #fff;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: 999px;
}
.badge-soldout { background: var(--ink-soft); }

.product-info { display: flex; flex-direction: column; flex: 1; padding: 1.15rem; }

.product-cat {
  font-size: var(--fs-2xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 0.3rem;
}

.product-info h3 { font-size: var(--fs-lg); margin-bottom: 0.35rem; }
.product-info h3 a:hover { color: var(--amber-600); }

.product-price-row {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin-bottom: 1rem;
}

.product-price { font-size: var(--fs-lg); font-weight: 700; color: var(--price); }
.product-compare { font-size: var(--fs-sm); color: var(--muted); text-decoration: line-through; }

/* Inline "Save $X" pill on the product detail page. Unlike the absolutely
   positioned `.badge` (used on product-card images), this stays in normal
   flow inside the price row so it never overlaps the title. */
.save-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.22rem 0.65rem;
  background: var(--amber);
  color: #fff;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-radius: 999px;
  white-space: nowrap;
}

.product-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  margin-top: auto;
}

/* R211 — Admin → Styles → Product card. With one button off, the remaining
   one takes the space it left; with both off there is no footer at all and the
   card closes up to its own height instead of holding an empty strip. */
.product-footer.is-details-only .btn-add { flex: 1 1 auto; }
.product-footer.is-actions-end { justify-content: flex-end; }
.product-card.is-no-cta .product-price-row { margin-bottom: 0; }
.product-card.is-no-cta .product-info { padding-bottom: 1.15rem; }

/* --------------------------------------------------------------------------
   11. Craft section
   -------------------------------------------------------------------------- */
.craft { background: var(--sand); }

.craft-grid {
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}

.craft-media { border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-md); }
.craft-media img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }

.craft-content h2 { font-size: var(--fs-2xl); }

.checklist { margin: 1.4rem 0 1.8rem; }
.checklist li {
  position: relative;
  padding-left: 1.7rem;
  margin-bottom: 0.55rem;
  color: var(--ink-soft);
}
.checklist li::before {
  content: '✓';
  position: absolute;
  left: 0;
  color: var(--amber-600);
  font-weight: 700;
}

/* --------------------------------------------------------------------------
   12. Testimonials
   -------------------------------------------------------------------------- */
.testimonial-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.75rem;
}

.testimonial-grid blockquote {
  margin: 0;
  padding: 1.9rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
.testimonial-grid blockquote:hover { transform: translateY(-5px); box-shadow: var(--shadow-md); }

.testimonial-grid blockquote p {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  line-height: 1.45;
  font-style: italic;
  color: var(--ink);
  margin-bottom: 1.4rem;
}

.testimonial-grid cite { display: block; font-style: normal; font-weight: 700; color: var(--forest-800); }
.testimonial-grid .role { display: block; font-size: var(--fs-xs); color: var(--muted); margin-top: 0.15rem; }

/* --------------------------------------------------------------------------
   13. Page hero & breadcrumb (inner pages)
   -------------------------------------------------------------------------- */
.page-hero {
  position: relative;
  background-color: var(--forest-800);
  background-image:
    linear-gradient(180deg, rgba(22, 27, 22, 0.84) 0%, rgba(22, 27, 22, 0.68) 100%),
    url('../images/hero.webp');
  background-size: cover;
  background-position: center;
  color: #fff;
  padding-block: clamp(3.4rem, 6.5vw, 5.2rem) clamp(2.4rem, 4.5vw, 4rem);
}
.page-hero h1 {
  color: #fff;
  font-size: var(--fs-3xl);
  margin-bottom: 0.2rem;
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.4);
}
.page-hero .breadcrumb a { color: rgba(255, 255, 255, 0.72); }
.page-hero .breadcrumb a:hover { color: var(--amber); }
.page-lead { color: var(--sage); max-width: 46em; margin: 0.6rem 0 0; text-shadow: 0 1px 6px rgba(0, 0, 0, 0.3); }

.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--fs-xs);
  color: var(--sage);
  margin-bottom: 1.4rem;
}
.breadcrumb a { color: var(--sage); transition: color 0.2s var(--ease); }
.breadcrumb a:hover { color: var(--amber); }
.breadcrumb .current { color: #fff; font-weight: 500; }

.breadcrumb-top {
  padding-top: 2.2rem;
  color: var(--muted);
  margin-bottom: 0.4rem;
}
.breadcrumb-top a, .breadcrumb-top .current { color: var(--muted); }
.breadcrumb-top a:hover { color: var(--amber-600); }
.breadcrumb-top .current { color: var(--ink); font-weight: 600; }

/* --------------------------------------------------------------------------
   14. Subcategory chips
   -------------------------------------------------------------------------- */
.subcat-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-bottom: 2.25rem;
}

.subcat-chip {
  padding: 0.45rem 1.1rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  font-weight: 500;
  transition: background 0.2s var(--ease), color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.subcat-chip:hover, .subcat-chip.active {
  background: var(--forest-800);
  color: #fff;
  border-color: var(--forest-800);
}

/* --------------------------------------------------------------------------
   15. Product detail
   -------------------------------------------------------------------------- */
.product-detail { padding-top: 2rem; padding-bottom: clamp(2.5rem, 6vw, 5rem); }

.product-detail-grid {
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}

.product-gallery {
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--line);
  background: #fff;
}

.gallery-main {
  position: relative;
  overflow: hidden;
  background: #fff;
  /* R79 — vertical scrolling with a finger on the photo keeps working, but
     the browser must not claim the pinch (page zoom): the JS pinch on the
     photo needs those moves untouched. */
  touch-action: pan-y;
}
.gallery-main img {
  width: 100%;
  aspect-ratio: 1 / 1;
  /* R138 — non-square product photos must show in full, never cropped. */
  object-fit: contain;
  transition: opacity 0.25s var(--ease), transform 0.25s var(--ease);
}
.gallery-main img.is-switching { opacity: 0.35; }
/* R155 — the slide changes by cross-fading a second copy over the first, so
   there is never a blank frame between two pictures (the old code dimmed the
   single <img> to 35% and swapped its src underneath). The layer is inert:
   clicks, swipes and the pinch gesture stay on the stage below it. */
.gallery-main img.gallery-main-fade {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.34s var(--ease);
}
.gallery-main img.gallery-main-fade.is-on { opacity: 1; }

.gallery-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 44px;
  height: 44px;
  border: none;
  border-radius: 50%;
  background: rgba(22, 27, 22, 0.55);
  color: #fff;
  font-size: var(--fs-xl);
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  opacity: 0;
  transition: background 0.2s var(--ease), opacity 0.2s var(--ease);
}
.product-gallery:hover .gallery-nav,
.gallery-nav:focus-visible { opacity: 1; }
.gallery-nav:hover { background: var(--amber); }
.gallery-nav.prev { left: 0.85rem; }
.gallery-nav.next { right: 0.85rem; }

.gallery-thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  padding: 0.85rem;
  background: var(--paper);
  border-top: 1px solid var(--line);
}
.gallery-thumbs button {
  flex: 0 0 72px;
  width: 72px;
  height: 72px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--radius);
  overflow: hidden;
  background: #fff;
  cursor: pointer;
  transition: border-color 0.2s var(--ease);
}
.gallery-thumbs button.is-active { border-color: var(--amber); }
.gallery-thumbs button img { width: 100%; height: 100%; object-fit: contain; }

/* --------------------------------------------------------------------------
   Product image lightbox
   -------------------------------------------------------------------------- */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(10, 13, 10, 0.94);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s var(--ease), visibility 0.25s var(--ease);
}
.lightbox.is-open { opacity: 1; visibility: visible; }

.lightbox-stage {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  cursor: zoom-out;
  /* R72 — the stage owns its gestures: without this the browser turns a
     swipe into page scroll / back-navigation before the handlers see it. */
  touch-action: none;
}
/* R160 — two layers, cross-faded. The stage used to hold one <img> whose src
   was replaced on every step: the browser painted the old picture, then the
   new one, with no transition (and a blank frame while the new file decoded).
   Both layers are absolutely centred so they occupy the same box and carry the
   same transform; only opacity differs. */
.lightbox-img {
  position: absolute;
  inset: 0;
  margin: auto;
  width: auto;
  height: auto;
  max-width: 92vw;
  max-height: 88vh;
  object-fit: contain;
  transform-origin: center center;
  opacity: 0;
  transition: opacity 0.24s var(--ease), transform 0.2s var(--ease);
  user-select: none;
  -webkit-user-drag: none;
}
.lightbox-img.is-front { opacity: 1; }
/* R79 — during a pinch the transform follows the fingers frame by frame;
   the 0.2s transition would smear it into rubber. */
.lightbox-img.is-pinching { transition: none; }

.lightbox-close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  z-index: 3;
  width: 46px;
  height: 46px;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: var(--fs-2xl);
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.2s var(--ease);
}
.lightbox-close:hover { background: var(--amber); }

.lightbox-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  width: 52px;
  height: 52px;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: var(--fs-2xl);
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.2s var(--ease);
}
.lightbox-nav:hover { background: var(--amber); }
.lightbox-nav.lb-prev { left: 1rem; }
.lightbox-nav.lb-next { right: 1rem; }

.lightbox-bar {
  position: absolute;
  bottom: 1rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 1rem;
  background: rgba(0, 0, 0, 0.45);
  border-radius: 999px;
  padding: 0.4rem 0.9rem;
  color: #fff;
}
.lightbox-counter { font-size: var(--fs-xs); font-variant-numeric: tabular-nums; }
.lightbox-zoom { display: flex; gap: 0.4rem; }
.lightbox-zoom button {
  width: 34px;
  height: 34px;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  background: transparent;
  color: #fff;
  font-size: var(--fs-lg);
  cursor: pointer;
  transition: background 0.2s var(--ease), border-color 0.2s var(--ease);
}
.lightbox-zoom button:hover { background: var(--amber); border-color: var(--amber); }
/* R155 — "1:1": one image pixel per screen pixel, for reading a detail the
   fitted view shrinks. A desktop affordance: on a touch screen the same view
   is a double-tap on the photo, so the button is hidden there. */
.lightbox-zoom .lb-actual {
  width: auto;
  min-width: 46px;
  padding: 0 0.6rem;
  border-radius: 999px;
  font-size: var(--fs-2xs);
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}
@media (hover: none) {
  .lightbox-zoom .lb-actual { display: none; }
}

/* R155 — a photo past its retention window: a tile that still says what it
   was, and a click that explains why there is nothing to open. */
.msg-image-expired {
  width: 92px;
  height: 92px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.15rem;
  padding: 0;
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--muted);
  font: inherit;
  font-size: var(--fs-2xs);
  line-height: 1.1;
  text-align: center;
  cursor: pointer;
}
.msg-image-expired:hover { border-color: var(--amber); color: var(--ink); }
.msg-image-expired-mark { font-size: var(--fs-lg); }
.nw-chat-msg-imgs .msg-image-expired { width: 150px; height: 110px; }

@media (prefers-reduced-motion: reduce) {
  .gallery-main img,
  .gallery-main img.gallery-main-fade,
  .lightbox-img { transition: none; }
}

body.lightbox-open { overflow: hidden; }

.product-detail-info h1 { font-size: var(--fs-2xl); margin-bottom: 0.4rem; }

.product-price-row.detail .product-price { font-size: var(--fs-xl); }

.product-stock { font-size: var(--fs-sm); margin-bottom: 1.3rem; }
.product-stock.in { color: var(--success); font-weight: 600; }
.product-stock.out { color: var(--danger); font-weight: 600; }

.product-description {
  color: var(--ink-soft);
  font-size: var(--fs-md);
  line-height: 1.7;
  letter-spacing: 0.005em;
  max-width: none;
  width: 100%;
  /* R186 — reading copy is ranged left, not justified.
     `justify` bought a tidy right edge by stretching every space to a
     different width, which in a 66-character measure leaves rivers of white
     running down the paragraph; it also made the last line of every paragraph
     look detached from the rest. `hyphens: auto` then paid for the narrower
     gaps with hyphens the merchant never typed — and in a product description
     the words are the product ("45-lb draw weight"), so a break at
     "45-lb draw wei-ght" is a typographic defect, not a nicety. Browsers only
     hyphenate when the dictionary has the language, which is why the defect
     appeared for some merchants and not others.
     Ranged left, no hyphenation. Long unbroken strings (a pasted URL in a
     description) still break rather than overflow the column — `break-word`
     only acts when the word does not fit on a line of its own, so ordinary
     text is untouched. */
  text-align: left;
  hyphens: none;
  -webkit-hyphens: none;
  word-break: normal;
  overflow-wrap: break-word;
  font-weight: 400;
}
/* R216 — see .content-page p / .rich-area p. Zero margins are load-bearing:
   with them, a run of <img> tags inserted in one shot has no gaps at all, and
   pressing Enter (which the browser turns into <p><br></p>) is the one and
   only way to open a blank line. */
.product-description p { margin: 0; }

/* R186 — the rich-text "emphasis" preset.
 *
 * The editor's B button is bold in the body colour, which in a long
 * description is easy to skim past: the merchant ends up bolding a whole
 * sentence to make one clause stand out. This preset is the middle volume —
 * bold AND in the store's own accent (#a96f2a, the same tone the buttons and
 * links use), so a highlighted phrase reads as highlighted without shouting.
 *
 * Why a class: R186 shipped before the editor had a colour picker, and a
 * class keeps this one-click preset tied to the store's own accent no matter
 * which theme is active. R216 added a restricted `style` attribute for the
 * merchant's OWN colour/font choices (nw_sanitize_rich_style() rebuilds it
 * from a closed allow-list), but the preset still deliberately uses a class —
 * a hand-picked colour should follow the merchant, a brand accent should
 * follow the theme.
 *
 * The two-element selectors are load-bearing: `.content-page strong` and the
 * Noir theme's `.product-description strong` both set their own colour, and
 * both are (0,1,1). `.content-page .nw-accent` is (0,2,0) and wins without
 * `!important`, so the accent stays the accent in either theme.
 *
 * `<strong class="nw-accent">` and `<span class="nw-accent">` both work:
 * the editor writes the first, and a merchant typing the source view may write
 * either. */
.nw-accent,
.product-description .nw-accent,
.content-page .nw-accent,
.rich-area .nw-accent {
  color: #a96f2a;
  font-weight: 700;
}
/* R47 — the description now accepts admin-authored HTML from the rich text
   editor, so headings, lists and inline images need real styles here too. */
.product-description h3 {
  color: var(--forest-800);
  font-size: var(--fs-lg);
  margin: 1.5rem 0 0.55rem;
}
.product-description ul, .product-description ol {
  margin: 0 0 1rem 1.4rem;
  padding: 0;
  color: var(--ink-soft);
}
.product-description ul { list-style: disc; }
.product-description ol { list-style: decimal; }
.product-description li { margin-bottom: 0.35rem; }
.product-description li::marker { color: var(--amber-600); font-weight: 700; }
/* R216 — flush stack. The old 1rem margins, radius and shadow drew a visible
   seam between consecutive pictures even when the editor had inserted them
   with no separator at all. Keep display:block so each full-width picture
   starts its own line without relying on a <br>. */
.product-description img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0;
  border-radius: 0;
  box-shadow: none;
}
.product-description a { color: var(--amber-600); text-decoration: underline; text-underline-offset: 3px; }
.product-description a:hover { color: var(--forest-800); }
.product-description strong { color: var(--forest-800); }

/* Product details box — a full-width panel below the gallery + buy columns */
.product-info-section {
  padding-bottom: clamp(2.5rem, 6vw, 5rem);
}
.product-info-box {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(1.6rem, 4vw, 3rem);
}
.product-info-box > .eyebrow {
  margin-bottom: 0.55rem;
}
.product-info-box > h2 {
  font-size: var(--fs-2xl);
  margin-bottom: 1.5rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--line);
}

/* Warranty / shipping / returns — modern, readable feature cards */
.product-meta {
  margin-top: 2rem;
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 1rem;
}
.product-meta li {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 1.1rem 1.2rem;
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.product-meta-label {
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--amber-600);
}
.product-meta-value {
  color: var(--ink-soft);
  font-size: var(--fs-md);
  line-height: 1.6;
}

.product-video {
  margin-top: 2.2rem;
  padding-top: 1.6rem;
  border-top: 1px solid var(--line);
}
.product-video h3 {
  font-size: var(--fs-lg);
  margin-bottom: 0.9rem;
}
.product-video-frame {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: none;
  border-radius: var(--radius);
  background: var(--sand);
}

.variant-selectors { display: grid; gap: 1rem; margin-bottom: 1.4rem; }
.variant-group label {
  display: block;
  font-weight: 600;
  color: var(--ink-soft);
  margin-bottom: 0.45rem;
  font-size: var(--fs-sm);
}
.variant-group select {
  width: 100%;
  padding: 0.75rem 2.75rem 0.75rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background-color: var(--paper);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-family: var(--font-body);
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1rem center;
  background-size: 12px;
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.variant-group select:focus {
  outline: none;
  border-color: var(--pine);
  box-shadow: 0 0 0 3px rgba(77, 92, 74, 0.14);
}

.qty-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1.2rem;
}
.qty-row label { font-weight: 600; color: var(--ink-soft); }

.qty-stepper {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper);
  overflow: hidden;
}
.qty-stepper button {
  width: 42px;
  height: 44px;
  border: none;
  background: transparent;
  font-size: var(--fs-lg);
  color: var(--ink-soft);
  transition: background 0.15s var(--ease);
}
.qty-stepper button:hover { background: var(--sand); }
.qty-stepper input {
  width: 52px;
  text-align: center;
  border: none;
  background: transparent;
  font-weight: 600;
  color: var(--ink);
  -moz-appearance: textfield;
}
.qty-stepper input::-webkit-outer-spin-button,
.qty-stepper input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.qty-stepper.small button { width: 34px; height: 34px; }
.qty-stepper.small input { width: 44px; height: 34px; }

.buy-actions { display: flex; gap: 0.8rem; margin-bottom: 1.5rem; }
.btn-add-to-cart { flex: 1; }

.paypal-divider {
  display: flex;
  align-items: center;
  gap: 1rem;
  color: var(--muted);
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 1rem;
}
.paypal-divider::before, .paypal-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--line);
}

.paypal-buttons { min-height: 45px; }
.paypal-powered { font-size: var(--fs-xs); color: var(--muted); text-align: center; margin-top: 0.5rem; }

.soldout-note {
  padding: 1rem 1.2rem;
  background: var(--sand);
  border-left: 3px solid var(--amber);
  color: var(--ink-soft);
  border-radius: var(--radius-sm);
}

.related { background: var(--paper); }

/* --------------------------------------------------------------------------
   16. Cart & checkout
   -------------------------------------------------------------------------- */
.cart-layout, .checkout-layout {
  display: grid;
  grid-template-columns: 1.7fr 1fr;
  gap: 2rem;
  align-items: start;
}

.cart-line-items { display: flex; flex-direction: column; gap: 1rem; }

.cart-line {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 1.25rem;
  padding: 1.1rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  align-items: center;
}

.cart-line-img img {
  width: 92px;
  height: 92px;
  /* R138 — show the full product photo, letterboxed on pure white. */
  object-fit: contain;
  background: #fff;
  border-radius: var(--radius-sm);
}

.cart-line-info h3 { font-size: var(--fs-lg); margin-bottom: 0.2rem; }
.cart-line-variant { display: block; color: var(--muted); font-size: var(--fs-xs); margin-bottom: 0.25rem; }
/* R62 — a cart line whose variant sold out after it went in keeps a badge;
   checkout refuses the line until it is removed. */
.cart-soldout-badge { margin-left: 0.4rem; background: var(--danger); }
.cart-line-price { color: var(--price); font-size: var(--fs-sm); font-weight: 600; }

.cart-line-form { display: flex; align-items: center; gap: 0.75rem; margin-top: 0.75rem; }

.cart-line-right { text-align: right; }
.cart-line-total { display: block; font-size: var(--fs-lg); font-weight: 700; color: var(--forest-800); margin-bottom: 0.5rem; }

.cart-remove {
  background: none;
  border: none;
  color: var(--danger);
  font-size: var(--fs-xs);
  padding: 0;
}
.cart-remove:hover { text-decoration: underline; }

.cart-summary {
  position: sticky;
  top: calc(var(--header-h) + 1.5rem);
  padding: 1.6rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.cart-summary h2 { font-size: var(--fs-xl); margin-bottom: 1.2rem; }

.summary-row, .summary-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}
.summary-row.total {
  border-bottom: none;
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--ink);
  padding-top: 0.9rem;
  margin-top: 0.4rem;
}
.cart-summary .btn { margin-top: 0.9rem; }

/* Checkout form */
.checkout-form { padding: 0; }
.checkout-form > h2 { font-size: var(--fs-xl); margin-bottom: 1.2rem; }

.form-grid {
  display: grid;
  /* R87 — same automatic minimum as .contact-layout: the two tracks could not
     shrink below their content. minmax(0, 1fr) drops it, and the children are
     allowed to shrink too. This is the storefront twin of the admin fix. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.1rem;
  margin-bottom: 1.2rem;
}
.form-grid > * { min-width: 0; }
.form-grid .full { grid-column: 1 / -1; }

.field { display: flex; flex-direction: column; gap: 0.4rem; }
.field label { font-size: var(--fs-sm); font-weight: 600; color: var(--ink-soft); }
.field input, .field select, .field textarea {
  padding: 0.7rem 0.95rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink);
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--amber);
  box-shadow: 0 0 0 3px rgba(201, 138, 61, 0.15);
  outline: none;
}

/* R73 — every storefront dropdown shares one look. The country selector on
   checkout (and any other .field select) used the native arrow while the
   product-page variant selects carried the custom chevron; both now render
   identically: no native arrow, chevron background, rounded corners. */
.field select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  padding: 0.75rem 2.75rem 0.75rem 1rem;
  border-radius: var(--radius);
  background-color: var(--paper);
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1rem center;
  background-size: 12px;
}
.field select:focus {
  border-color: var(--amber);
  box-shadow: 0 0 0 3px rgba(201, 138, 61, 0.15);
  outline: none;
}

.form-note { font-size: var(--fs-xs); color: var(--muted); margin-bottom: 1.2rem; }

/* Contact page */
.contact-layout {
  display: grid;
  /* R87 — `1fr` carries an AUTOMATIC MINIMUM of min-content, so at 390px the two
     tracks measured 366 + 305 = 671px inside a 390px container and the contact
     page scrolled 326px sideways (docScroll 716 at a 390px viewport, in BOTH
     themes). The ≤900px block now collapses it to one column, the way this site
     already collapses .cart-layout / .checkout-layout / .account-layout —
     .contact-layout was simply missed when those were added. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: start;
}
/* R87 — the sidebar cards carry an unbreakable address (support@deerseekerarchery.com
   measures 266px) and a phone number. Noir's Montserrat is wider than the default
   face, so at 390px a card's min-content came to 378px inside a 350px column and
   the contact page scrolled sideways — in theme 15 only, which is exactly the kind
   of difference a per-theme check exists to find. Same lesson as the R84 footer:
   let the grid track shrink, let the flex child shrink, and let a long token break
   (overflow-wrap:anywhere also lowers min-content; break-word does not). */
.contact-info { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.2rem; }
.contact-card { overflow-wrap: anywhere; }
.contact-list li > div { min-width: 0; }
.contact-card,
.contact-form-card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 1.75rem;
  box-shadow: var(--shadow-sm);
}
.contact-card h2,
.contact-form-card h2 { font-size: var(--fs-xl); margin-bottom: 1.1rem; }
.contact-card p { color: var(--muted); margin: 0; }

/* R131 — notice cards: a heading, one sentence, then a button (the chat
   invalid-link card is the case in point). The paragraph rule above exists
   for the merchant-authored card bodies, which carry their own rhythm via
   .contact-card-body; in a hand-built card it left the sentence and the
   button touching. One vertical step between direct children keeps the
   notice readable — and where a heading's own bottom margin is already
   bigger, adjacent-margin collapsing keeps the bigger of the two, so the
   contact-page cards do not change. */
.contact-card > * + * { margin-top: 1.05rem; }

/* R132 — the card's call-to-action sits in its own paragraph and needs a
   clearly larger step than the sentence rhythm above: a button that hangs
   right under the explanation reads as cramped, not as the card's answer.
   The explicit class (chat.php's invalid-link card) lifts it to roughly two
   rhythm steps; every other card keeps the base spacing. */
.contact-card > * + *.notice-actions { margin-top: 2.1rem; }
.contact-card > .notice-actions:first-child { margin-top: 0; }

/* R112c — the merchant-owned card bodies (one rich-text field per card) keep
   ONE vertical rhythm: every block after the first sits at the same distance,
   so headings, paragraphs and lists never hug each other. The `> * + *`
   pattern means a single-paragraph card looks exactly like it did before. */
.contact-card-body > * { margin: 0; }
.contact-card-body > * + * { margin-top: 0.9rem; }
.contact-card-body p,
.contact-card-body li { color: var(--muted); line-height: 1.6; }
.contact-card-body strong { color: var(--ink); font-weight: 600; }
.contact-card-body a { color: var(--amber-600); }
.contact-card-body a:hover { text-decoration: underline; }
.contact-card-body ul,
.contact-card-body ol { padding-left: 1.25rem; }
.contact-card-body li + li { margin-top: 0.35rem; }

.contact-list { display: flex; flex-direction: column; gap: 1.1rem; }
.contact-list li { display: flex; gap: 0.9rem; align-items: flex-start; }
.contact-icon {
  flex: 0 0 40px;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--amber-100);
  color: var(--amber-600);
  font-size: var(--fs-lg);
}
.contact-list strong { display: block; color: var(--ink); font-weight: 600; margin-bottom: 0.1rem; }
.contact-list a { color: var(--amber-600); }
.contact-list a:hover { text-decoration: underline; }
.contact-list address { font-style: normal; color: var(--ink-soft); line-height: 1.5; }

.contact-form { display: flex; flex-direction: column; gap: 1.1rem; }
.contact-form .btn { align-self: flex-start; }
.honeypot { position: absolute; left: -9999px; opacity: 0; height: 0; overflow: hidden; }

/* R52 — contact page: the general message form plus a pointer to the two
   after-sales routes (My Orders, or the order link in the order email).
   The R49 general/after-sales tab strip was retired with the tab itself. */
.contact-list-plain { margin-top: 0.4rem; }
.contact-list-plain > li + li { margin-top: 0.7rem; }
.contact-list-plain p { margin: 0.2rem 0 0; color: var(--ink-soft); font-size: var(--fs-sm); line-height: 1.55; }
.contact-card-after-sales .form-note { margin-top: 0.9rem; }
.msg-upload-preview { margin-top: 0.6rem; }
.msg-upload-preview img {
  max-width: 180px;
  max-height: 180px;
  display: block;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  margin-bottom: 0.4rem;
}
/* R49 — after-sales tag + attachment preview in message threads. */
.message-tag.is-after-sales { background: rgba(201, 138, 61, 0.16); color: var(--amber-600); }
.msg-order-ref { font-weight: 700; }
.msg-attachment { margin: 0 0 1rem; }
.msg-attachment img {
  max-width: 220px;
  max-height: 220px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  display: block;
}
.msg-attachment-label { display: block; color: var(--muted); font-size: var(--fs-xs); margin-top: 0.3rem; }
@media (max-width: 560px) {
  .msg-attachment img, .msg-upload-preview img { max-width: 100%; }
}

/* --------------------------------------------------------------------------
   17. Status pages (success / cancel)
   -------------------------------------------------------------------------- */
.status-page {
  display: flex;
  align-items: center;
  min-height: 62vh;
  padding-block: 3rem;
}
.status-inner { text-align: center; max-width: 560px; }
/* R81 — .status-inner is a flex child of .status-page, which is itself a
   flex container. Without an explicit width every child of the status page
   would shrink to its own text (a flex item's default size is max-content,
   and the page's own `align-items: center` cannot stretch it), so the
   centred column below has to be given one. */
.status-page > .status-inner { width: 100%; }

.status-icon {
  display: inline-flex;
  width: 96px;
  height: 96px;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(63, 122, 74, 0.14);
  color: var(--success);
  margin-bottom: 1.5rem;
}
.status-icon.warn { background: rgba(201, 138, 61, 0.14); color: var(--amber-600); }

.status-inner h1 { font-size: var(--fs-2xl); }
.order-number { font-size: var(--fs-md); }
.order-number strong { color: var(--amber-600); }
.status-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.9rem; margin-top: 1.8rem; }

/* R81 — the small print that closes a status page. Without it the buttons sat
   flush against the last line of copy; a hairline and a deliberate gap give
   the block a bottom edge and make the two read as separate sections. */
.status-note {
  margin: 2.2rem 0 0;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
  font-size: var(--fs-xs);
  line-height: 1.7;
  color: var(--muted);
}
.status-note a { color: var(--amber-600); font-weight: 600; }
.status-note a:hover { text-decoration: underline; }

@media (max-width: 560px) {
  .status-actions { flex-direction: column; align-items: stretch; gap: 0.7rem; }
  .status-note { margin-top: 1.8rem; padding-top: 1.25rem; }
}

/* --------------------------------------------------------------------------
   18. Empty state
   -------------------------------------------------------------------------- */
.empty-state {
  text-align: center;
  padding: 2.6rem 1.8rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin: 0 0 1.5rem;
}
.empty-state h1, .empty-state h2 { font-size: var(--fs-2xl); }
.empty-state p { color: var(--muted); margin: 0.4rem 0; }
.empty-state .btn { margin-top: 0.8rem; }
.empty-state-hint { font-size: var(--fs-sm); color: var(--muted); }

/* --------------------------------------------------------------------------
   18b. Storefront pagination (R72 — category / search listings, 20 per page)
   Same class names as the admin lists so app/pagination.php stays shared;
   painted with the storefront palette.
   -------------------------------------------------------------------------- */
.listing .list-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.7rem;
  margin: 2rem 0 0.5rem;
}
.listing .list-pager-info { color: var(--muted); font-size: var(--fs-sm); }
.listing .list-pager-info strong { color: var(--ink); }
.listing .list-pager-pages { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; }
.listing .list-pager-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.35rem;
  padding: 0.42rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
  transition: border-color 0.2s var(--ease), color 0.2s var(--ease), background 0.2s var(--ease);
}
.listing .list-pager-btn:hover { border-color: var(--amber); color: var(--amber-600); }
.listing .list-pager-btn.is-active { background: var(--forest-800); border-color: var(--forest-800); color: #fff; }
.listing .list-pager-btn.is-disabled { opacity: 0.45; cursor: not-allowed; }
.listing .list-pager-gap { color: var(--muted); padding: 0 0.15rem; }
/* R163 — on a phone the pager reads best as one centred block: the "showing"
   line and the page buttons stack in the middle instead of hugging the left
   edge (the summary sat left, the buttons wrapped under it, both off-centre). */
@media (max-width: 640px) {
  .listing .list-pager { justify-content: center; text-align: center; }
  .listing .list-pager-info { width: 100%; }
  .listing .list-pager-pages { justify-content: center; width: 100%; }
}

/* --------------------------------------------------------------------------
   19. Toast notification
   -------------------------------------------------------------------------- */
.toast {
  position: fixed;
  bottom: 1.5rem;
  left: 50%;
  transform: translate(-50%, 20px);
  z-index: 300;
  padding: 0.85rem 1.4rem;
  background: var(--forest-900);
  color: #fff;
  font-size: var(--fs-sm);
  font-weight: 500;
  /* R137 — a pill (999px) turns into a giant rounded slab the moment a long
     error wraps over several lines on a phone. The panel radius follows the
     site's own Corner rounding instead, and the toast can never be wider
     than the viewport minus its gutters. */
  border-radius: var(--radius);
  max-width: calc(100vw - 2rem);
  text-align: center;
  box-shadow: var(--shadow-lg);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.toast.is-visible { opacity: 1; transform: translate(-50%, 0); }
.toast.is-error { background: var(--danger); }

/* --------------------------------------------------------------------------
   20. Footer
   -------------------------------------------------------------------------- */
.site-footer {
  background: var(--forest-900);
  color: var(--sage);
  padding-top: 3.5rem;
}

.footer-grid {
  display: grid;
  grid-template-columns: 1.4fr 2fr;
  gap: 3rem;
  padding-bottom: 3rem;
}
/* R84 — same trap one level up: the brand column must be allowed to shrink
   below its longest word before it can push the link columns narrower. */
.footer-grid > * { min-width: 0; }

.footer-brand .logo { color: #fff; }
.footer-brand p { max-width: 26em; margin: 0.9rem 0 1.2rem; color: var(--sage); }

/* R130 — the store address under the tagline. Same muted body colour as the
   tagline, but a normal (not italic) face: a postal address is data, not an
   aside. The right margin keeps the two- and three-line block from crowding
   the link columns on a narrow footer. */
.footer-brand address {
  max-width: 22em;
  margin: 0 0 1.2rem;
  font-style: normal;
  line-height: 1.7;
  color: var(--sage);
}

/* R79 — "Follow us" social row. Monochrome line icons that inherit the
   footer palette: muted by default, brand amber on hover/focus. Each target
   is a 40px tap area (comfortably above the 24px minimum) even though the
   glyph itself stays small and quiet. */
.footer-social { margin-top: 1.4rem; }
.footer-social-title {
  margin: 0 0 0.75rem;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--moss);
}
.footer-social-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.footer-social-list a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--line-dark);
  color: var(--sage);
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease),
              background-color 0.2s var(--ease), transform 0.2s var(--ease);
}
.footer-social-list a:hover,
.footer-social-list a:focus-visible {
  color: var(--forest-900);
  background: var(--amber);
  border-color: var(--amber);
  transform: translateY(-2px);
}
.footer-social-list svg {
  width: 19px;
  height: 19px;
  fill: currentColor;
  display: block;
}

.footer-links {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 2rem;
}
/* R84 — a long, unbreakable line (the Contact column's email address) used to
   blow this grid up: `minmax(120px, 1fr)` sets a floor but no ceiling, and a
   grid item's automatic minimum size is its min-content width, so the email's
   ~241px forced its own column wide and squeezed Support down to 85px — which
   then wrapped "My Account", "Track My Order" and "Shipping & Returns" onto
   two lines each on a phone. Letting every item shrink past its min-content
   width is what makes the `1fr` shares equal again. */
.footer-links > div { min-width: 0; }
.footer-links h3 { color: #fff; font-family: var(--font-body); font-size: var(--fs-xs); letter-spacing: 0.14em; text-transform: uppercase; margin-bottom: 1.1rem; }
.footer-links h3 a { color: #fff; transition: color 0.2s var(--ease); }
.footer-links h3 a:hover { color: var(--amber); }
.footer-links li { margin-bottom: 0.6rem; }
.footer-links a { color: var(--sage); font-size: var(--fs-sm); transition: color 0.2s var(--ease); }
.footer-links a:hover { color: #fff; }
/* R84 — contact details are long single tokens (email, phone). Let them break
   onto a second line rather than widen the column, and keep them from ever
   poking out of the footer on a narrow screen. */
.footer-links a[href^="mailto:"],
.footer-links a[href^="tel:"] { overflow-wrap: anywhere; }
.footer-links li > a { display: inline-block; max-width: 100%; }

.footer-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding-block: 1.4rem;
  border-top: 1px solid var(--line-dark);
  font-size: var(--fs-xs);
}
.footer-legal { display: flex; gap: 1.4rem; }
.footer-legal a { color: var(--sage); }
.footer-legal a:hover { color: #fff; }

/* --------------------------------------------------------------------------
   21. Scroll reveal
   -------------------------------------------------------------------------- */
.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}
.reveal.visible { opacity: 1; transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0.001ms !important; animation-duration: 0.001ms !important; }
  .reveal { opacity: 1; transform: none; }
  html { scroll-behavior: auto; }
}

/* --------------------------------------------------------------------------
   22. Account, auth & order tracking
   -------------------------------------------------------------------------- */
.account-layout {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 2rem;
  align-items: start;
}

.account-sidebar { position: sticky; top: calc(var(--header-h) + 1.5rem); }

.account-card {
  padding: 1.6rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.account-card h2 { font-size: var(--fs-xl); margin-bottom: 0.3rem; }
.account-card .eyebrow { margin-bottom: 0.4rem; }
.account-email { color: var(--muted); font-size: var(--fs-sm); margin-bottom: 1.1rem; }

.account-menu { border-top: 1px solid var(--line); padding-top: 0.8rem; }
.account-menu li { margin-bottom: 0.2rem; }
.account-menu a {
  display: block;
  padding: 0.45rem 0;
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  transition: color 0.15s var(--ease);
}
.account-menu a:hover { color: var(--amber-600); }

.cta-card p { color: var(--muted); margin-bottom: 1.2rem; }
.cta-card .btn { margin-bottom: 0.6rem; }

.account-main { display: flex; flex-direction: column; gap: 2.5rem; min-width: 0; }

.section-title {
  font-size: var(--fs-xl);
  margin-bottom: 1.2rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--line);
}

.order-list { display: flex; flex-direction: column; gap: 1rem; }

.order-card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}

.order-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 1.1rem 1.3rem;
  background: var(--sand);
  border-bottom: 1px solid var(--line);
}
.order-card-head h3 { font-size: var(--fs-lg); margin-bottom: 0.1rem; }
.order-date { font-size: var(--fs-xs); color: var(--muted); }
.order-card-meta {
  display: flex;
  align-items: center;
  gap: 0.8rem;
}
.order-total { font-size: var(--fs-lg); font-weight: 700; color: var(--forest-800); }

.order-card-body { padding: 1rem 1.3rem 1.2rem; }
.order-card-actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 0.7rem;
  flex-wrap: wrap;
  padding: 0 1.3rem 1.2rem;
}

.order-items { display: flex; flex-direction: column; }
.order-item-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0;
  border-bottom: 1px dashed var(--line);
}
.order-item-line:last-child { border-bottom: none; }
.order-item-name { color: var(--ink-soft); }
.order-item-price { font-weight: 600; color: var(--price); white-space: nowrap; }
/* R51 — an ordered product that has since been deleted stays as text, with a
   short explainer instead of a (dead) product link. */
.order-item-gone { color: var(--muted); }
.order-item-note { display: block; color: var(--muted); font-size: var(--fs-xs); line-height: 1.5; margin-top: 0.15rem; }
.order-items .cart-unavailable-badge { margin-left: 0.4rem; }

.order-note { color: var(--muted); font-size: var(--fs-sm); margin: 0.8rem 0 0; }

.order-tracking {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}
.order-tracking span {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.order-tracking strong {
  font-size: var(--fs-md);
  color: var(--forest-800);
  font-variant-numeric: tabular-nums;
}

/* Status pills (storefront) */
.status-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.18rem 0.65rem;
  border-radius: 999px;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.status-pending { background: rgba(122, 118, 105, 0.16); color: var(--muted); }
.status-paid, .status-shipped, .status-delivered, .status-completed { background: rgba(63, 122, 74, 0.14); color: var(--success); }
.status-processing { background: rgba(201, 138, 61, 0.16); color: var(--amber-600); }
.status-cancelled, .status-refunded { background: rgba(181, 64, 47, 0.12); color: var(--danger); }

/* Shipping progress timeline */
.shipping-steps {
  display: flex;
  align-items: flex-start;
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}
.step {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.45rem;
  text-align: center;
}
.step::before {
  content: '';
  position: absolute;
  top: 14px;
  left: -50%;
  width: 100%;
  height: 2px;
  background: var(--line);
  z-index: 0;
}
.step:first-child::before { display: none; }
.step-dot {
  position: relative;
  z-index: 1;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--sand);
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 700;
  border: 2px solid var(--line);
}
.step.is-done .step-dot { background: var(--success); border-color: var(--success); color: #fff; }
.step.is-current .step-dot { background: var(--amber); border-color: var(--amber); color: #fff; }
.step.is-done::before { background: var(--success); }
.step-label { font-size: var(--fs-xs); color: var(--muted); }
.step.is-done .step-label, .step.is-current .step-label { color: var(--ink); font-weight: 600; }

/* Auth pages */
.auth-wrap { display: flex; justify-content: center; }
.auth-card {
  width: 100%;
  max-width: 480px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 2rem;
  box-shadow: var(--shadow-sm);
}
.auth-lead { color: var(--muted); margin-bottom: 1.5rem; }
.auth-form { display: flex; flex-direction: column; gap: 1.1rem; }
.auth-form .btn-block { margin-top: 0.5rem; }
.auth-switch { text-align: center; color: var(--muted); margin-top: 1.4rem; font-size: var(--fs-sm); }
.auth-switch a { color: var(--amber-600); font-weight: 600; }
/* R31: "resend verification" row on the sign-in card. */
.auth-resend-row { margin-top: 0.9rem; }
/* R49 — deliverability hints beside the verification flows. */
.resend-hint {
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: 1.6;
  margin: 0.5rem 0 0.75rem;
}
.auth-resend-row .resend-hint { margin-bottom: 0.6rem; }
.verify-tip { display: inline-block; margin-top: 0.45rem; opacity: 0.92; }
/* R51 — the two confirmation stories ("Almost there…" on the register card
   and the unconfirmed-address notice on the sign-in card) get a calmer
   reading rhythm: one idea per line, real paragraph spacing and the theme's
   own success/danger colours carried through links. */
.alert-verify { line-height: 1.65; font-size: var(--fs-sm); padding: 1.05rem 1.2rem; }
.alert-verify p { margin: 0; }
.alert-verify p + p { margin-top: 0.5rem; }
.alert-verify .verify-lead { font-size: var(--fs-md); }
.alert-verify .verify-tip {
  display: block;
  opacity: 0.85;
  font-size: var(--fs-sm);
  line-height: 1.6;
  margin-top: 0.65rem;
}
.alert-verify .verify-resend { margin-top: 0.65rem; }
.alert-verify a { color: inherit; font-weight: 600; text-underline-offset: 2px; }
.field-hint { color: var(--muted); font-weight: 400; }
.account-note a { color: var(--amber-600); font-weight: 600; }

.alert {
  padding: 0.85rem 1.1rem;
  /* R137 — the warning surface follows the site-wide Corner rounding (Shape
     & type), like every other card; and a long line can never push the box
     past the screen edge on a phone. */
  border-radius: var(--radius);
  overflow-wrap: break-word;
  margin-bottom: 1.25rem;
  font-size: var(--fs-sm);
}
.alert-error { background: rgba(181, 64, 47, 0.1); color: var(--danger); border: 1px solid rgba(181, 64, 47, 0.25); }
.alert-success { background: rgba(63, 122, 74, 0.12); color: var(--success); border: 1px solid rgba(63, 122, 74, 0.25); }
/* R164 — the profile page's "confirmation pending" banner. */
.alert-warning { background: rgba(176, 120, 32, 0.12); color: var(--ink-soft); border: 1px solid rgba(176, 120, 32, 0.32); }
/* R164 — one form layout for the member profile page (account-profile.php). */
.profile-form { display: flex; flex-direction: column; gap: 1.1rem; }
.profile-form .form-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.profile-form .form-actions .btn { align-self: flex-start; }
/* R164 — the "find your order" card on Track My Order. */
.track-lookup {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
  margin-bottom: 2rem;
}
.track-lookup h2 { margin: 0 0 0.35rem; font-size: var(--fs-lg); }
.track-lookup .alert { margin: 0.9rem 0; }
.track-lookup .field-hint { margin-top: 0.9rem; }

.empty-state.compact { padding: 2rem 1.5rem; }
.empty-state.compact h3 { font-size: var(--fs-xl); }

/* --------------------------------------------------------------------------
   22b. Address book, order messages, checkout extras
   -------------------------------------------------------------------------- */
.check-field { display: flex; align-items: center; gap: 0.6rem; font-size: var(--fs-sm); color: var(--ink-soft); margin: 0.5rem 0 0.2rem; }
.check-field input { width: 17px; height: 17px; accent-color: var(--amber); }
.save-address-field { margin: 1rem 0 0; }

.inline-form { display: inline; }
.danger-link { color: var(--danger); }
.danger-link:hover { text-decoration: underline; }

.address-list { display: flex; flex-direction: column; gap: 1rem; margin-bottom: 1.4rem; }
.address-card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.1rem 1.3rem 1.2rem;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.address-card.is-default {
  border-color: var(--amber);
  box-shadow: 0 0 0 2px rgba(201, 138, 61, 0.12);
  background: linear-gradient(180deg, rgba(201, 138, 61, 0.04) 0%, var(--paper) 35%);
}
.address-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.address-card-title { display: flex; flex-direction: column; gap: 0.45rem; min-width: 0; }
.address-card-name {
  margin: 0;
  font-size: var(--fs-md);
  font-weight: 700;
  color: var(--forest-900);
  word-break: break-word;
}
.default-tag {
  display: inline-block;
  width: fit-content;
  background: rgba(201, 138, 61, 0.16);
  color: var(--amber-700);
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0.18rem 0.55rem;
  border-radius: 999px;
  border: 1px solid rgba(201, 138, 61, 0.3);
}
.default-tag.is-muted {
  background: var(--cream);
  color: var(--muted);
  border-color: var(--line);
}

.address-card-actions { display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap; }
.address-card-actions .btn-sm { font-size: var(--fs-xs); padding: 0.3rem 0.7rem; }

.address-card-grid {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.55rem 1.5rem;
  padding: 0.9rem 1.1rem;
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.address-card-row {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}
.address-card-row dt {
  font-size: var(--fs-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}
.address-card-row dd {
  margin: 0;
  color: var(--ink);
  font-size: var(--fs-sm);
  word-break: break-word;
  line-height: 1.4;
}
.address-card-row dd strong { color: var(--forest-800); font-weight: 700; }

.address-form-wrap { background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius); padding: 0 1.4rem; margin-top: 1.5rem; }
.address-form-wrap summary { cursor: pointer; padding: 1.1rem 0; font-weight: 600; color: var(--forest-800); }
.address-form-wrap summary:hover { color: var(--amber-600); }
.address-form-wrap[open] { padding-bottom: 1.4rem; }
.address-form-wrap[open] summary { border-bottom: 1px solid var(--line); margin-bottom: 1.2rem; }
.address-form-wrap .auth-form { margin-top: 0.4rem; }
.address-form { padding-bottom: 1.4rem; }
.address-form .btn { align-self: flex-start; }

/* Order message thread */
.order-messages { margin-top: 1.25rem; padding-top: 1.25rem; border-top: 1px solid var(--line); }
.order-messages-title { font-size: var(--fs-md); margin-bottom: 0.75rem; }
.msg-thread { display: flex; flex-direction: column; gap: 0.6rem; max-height: 360px; overflow-y: auto; padding-right: 0.25rem; }
.msg { max-width: 78%; padding: 0.65rem 0.9rem; border-radius: var(--radius); }
.msg-customer { align-self: flex-end; background: var(--amber-100); }
.msg-admin { align-self: flex-start; background: var(--sand); }
.msg-meta { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.2rem; }
.msg-sender { font-size: var(--fs-2xs); font-weight: 700; color: var(--ink-soft); }
.msg-time { font-size: var(--fs-2xs); color: var(--muted); }
.msg-text { margin: 0; font-size: var(--fs-sm); color: var(--ink); word-break: break-word; }
.msg-empty { color: var(--muted); font-size: var(--fs-sm); }
.msg-form { display: flex; flex-direction: column; gap: 0.6rem; margin-top: 1rem; }
.msg-form textarea {
  min-height: 76px;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--cream);
  font-family: inherit;
  resize: vertical;
}
.msg-form textarea:focus { border-color: var(--amber); outline: none; }
.msg-form .btn { align-self: flex-end; }

.msg-badge {
  display: inline-flex;
  align-items: center;
  background: var(--danger);
  color: #fff;
  font-size: var(--fs-2xs);
  font-weight: 700;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
}

/* Contact-page messages shown in My Account (with admin replies) */
.account-messages { display: flex; flex-direction: column; gap: 1.5rem; margin-top: 2rem; }
.account-message {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.4rem 1.6rem;
  box-shadow: var(--shadow-sm);
}
.account-message-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1.1rem;
  padding-bottom: 0.8rem;
  border-bottom: 1px solid var(--line);
}
.account-message-head strong { color: var(--forest-800); font-size: var(--fs-md); }
.account-message-time { color: var(--muted); font-size: var(--fs-xs); white-space: nowrap; }
.account-message-meta { display: flex; align-items: center; gap: 0.9rem; flex-wrap: wrap; }
.account-message-meta .inline-form { display: inline-flex; }
.account-message-meta .btn-sm { font-size: var(--fs-2xs); padding: 0.25rem 0.6rem; }

/* Thread bubbles (R14) — each entry sits in its own card with a comfortable
   gap so a long conversation stays readable. */
.account-message-thread { display: flex; flex-direction: column; gap: 1rem; }
.account-message-bubble {
  padding: 0.95rem 1.15rem;
  border-radius: var(--radius-sm);
  background: var(--sand);
  border: 1px solid var(--line);
}
.account-message-bubble.is-customer { background: rgba(201, 138, 61, 0.10); border-color: rgba(201, 138, 61, 0.25); }
.account-message-bubble.is-team { background: var(--paper); border-color: var(--line); border-left: 3px solid var(--amber); }
.account-bubble-head {
  display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap;
  margin-bottom: 0.5rem; color: var(--ink-soft); font-size: var(--fs-xs);
}
.account-bubble-head strong { color: var(--forest-800); }
.account-message-bubble p { margin: 0; color: var(--ink); line-height: 1.65; white-space: pre-wrap; }

.account-message-reply {
  margin-top: 1.2rem;
  padding: 1rem 1.2rem;
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.account-message-reply summary {
  font-weight: 600; color: var(--forest-800); cursor: pointer;
  padding: 0.3rem 0; font-size: var(--fs-sm);
}
.account-message-reply summary:hover { color: var(--amber-600); }
.account-message-reply .auth-form { margin-top: 0.9rem; }
.account-message-reply textarea {
  font: inherit; width: 100%; resize: vertical;
  min-height: 90px; padding: 0.7rem 0.9rem;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: #fff;
}
.account-message-reply textarea:focus { border-color: var(--amber); outline: none; }
.account-message-pending { margin: 0; color: var(--muted); font-size: var(--fs-sm); }

.account-thread-reply {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-top: 1.2rem;
  padding: 1rem 1.2rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.account-thread-reply .btn { align-self: flex-start; }

/* --------------------------------------------------------------------------
   Mail-pattern message list — shared between admin (admin.css) and user
   (account-messages.php). R25 promoted the previously `.account-mail-list`
   scoped variants to root-level so both sides render identical cards.
   -------------------------------------------------------------------------- */
.mail-list {
  list-style: none;
  margin: 1.2rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
}
.mail-row { background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; }
.mail-summary { padding: 1.1rem 1.3rem; display: block; color: inherit; text-decoration: none; transition: background 0.15s ease; }
.mail-summary:hover { background: var(--sand); }
.mail-row.is-unread { border-left: 3px solid var(--amber); }
.mail-row.is-expanded { border-color: var(--amber); box-shadow: 0 0 0 2px rgba(201, 138, 61, 0.08); }
.mail-summary-line1 { display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap; margin-bottom: 0.4rem; }
.mail-subject { font-weight: 700; color: var(--forest-900); font-size: var(--fs-md); word-break: break-word; }
.mail-row.is-unread .mail-subject { color: var(--forest-900); }
.mail-time { margin-left: auto; font-size: var(--fs-xs); color: var(--muted); white-space: nowrap; }
.mail-unread { font-size: var(--fs-2xs); padding: 0.1rem 0.5rem; }
.mail-summary-line2 { display: flex; gap: 0.6rem; align-items: baseline; flex-wrap: wrap; margin-bottom: 0.4rem; color: var(--ink-soft); font-size: var(--fs-sm); }
.mail-sender { font-weight: 600; color: var(--forest-800); }
.mail-sender-email { color: var(--muted); font-size: var(--fs-xs); }
.mail-preview { margin: 0.3rem 0 0.5rem; font-size: var(--fs-xs); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mail-summary-line3 { margin-top: 0.6rem; padding-top: 0.6rem; border-top: 1px dashed var(--line); display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.mail-status { display: inline-block; font-size: var(--fs-xs); font-weight: 700; padding: 0.28rem 0.75rem; border-radius: 999px; letter-spacing: 0.01em; line-height: 1.3; }
.mail-status.is-replied { background: rgba(70, 121, 90, 0.12); color: #2f5a3c; }
.mail-status.is-pending { background: rgba(201, 138, 61, 0.16); color: var(--amber-700); }
.mail-status.is-member { background: rgba(63, 122, 74, 0.12); color: var(--success); }
.mail-card-actions { display: flex; align-items: center; gap: 0.6rem; margin-left: auto; flex-wrap: wrap; }
.mail-card-actions .btn { padding: 0.34rem 0.85rem; font-size: var(--fs-xs); }
.mail-card-actions .inline-form { display: inline-flex; margin: 0; }

/* R25: unread chip on a mail-row summary card (mirror of admin.css). */
.unread-badge {
  display: inline-block;
  font-size: var(--fs-2xs);
  font-weight: 700;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: var(--amber);
  color: #fff;
}

.msg-detail-actions { display: flex; justify-content: flex-end; align-items: center; gap: 0.7rem; padding: 0.6rem 0 0.8rem; border-bottom: 1px dashed var(--line); margin-bottom: 0.9rem; }
.msg-detail-actions .inline-form { display: inline-flex; margin: 0; }
.msg-detail-actions .btn { padding: 0.3rem 0.8rem; font-size: var(--fs-xs); }

.mail-detail { border-top: 1px solid var(--line); padding: 1.1rem 1.2rem 1.25rem; background: var(--cream); display: flex; flex-direction: column; gap: 1rem; }
.mail-detail-head h3 { margin: 0 0 0.35rem; color: var(--forest-900); font-size: var(--fs-lg); word-break: break-word; }
.mail-detail-meta { display: flex; flex-wrap: wrap; gap: 0.4rem 1.2rem; font-size: var(--fs-xs); color: var(--ink-soft); }
.mail-detail-meta strong { color: var(--forest-800); font-weight: 700; }
.mail-detail-body { display: flex; flex-direction: column; gap: 0.65rem; }

/* R25: chat bubbles in the expanded thread — identical to admin/messages.php
   so the user side renders the same `msg` / `msg-admin` / `msg-customer`
   blocks. */
.msg { max-width: 78%; padding: 0.65rem 0.9rem; border-radius: var(--radius-sm); }
.msg-customer { align-self: flex-end; background: rgba(201, 138, 61, 0.14); }
.msg-admin { align-self: flex-start; background: var(--sand); }
.msg-meta { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.2rem; }
.msg-sender { font-size: var(--fs-2xs); font-weight: 700; color: var(--ink-soft); }
.msg-time { font-size: var(--fs-2xs); color: var(--muted); }
.msg-text { margin: 0; font-size: var(--fs-sm); color: var(--ink); word-break: break-word; white-space: pre-wrap; }

.message-reply-form { display: flex; flex-direction: column; gap: 0.7rem; margin-top: 0.9rem; padding-top: 0.9rem; border-top: 1px dashed var(--line); }
.message-reply-form textarea {
  min-height: 60px;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--paper);
  font-family: inherit;
  resize: vertical;
}
.message-reply-form textarea:focus { border-color: var(--amber); outline: none; }
.message-reply-form .btn { align-self: flex-end; }
.message-reply-form .panel-note { font-size: var(--fs-xs); color: var(--muted); margin: 0; }
.panel-note { font-size: var(--fs-xs); color: var(--muted); margin: 0.4rem 0 0; }
.mail-row .message-reply-form { margin-top: 0.6rem; padding-top: 0.9rem; }

.message-reply-sent {
  margin-top: 0.9rem;
  padding: 0.85rem 1rem;
  background: var(--sand);
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}
.message-reply-sent p { margin: 0.4rem 0 0; white-space: pre-wrap; }

.message-tag {
  display: inline-block;
  font-size: var(--fs-2xs);
  font-weight: 600;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: rgba(63, 122, 74, 0.12);
  color: var(--success);
}
.message-tag.is-guest { background: var(--sand); color: var(--muted); }
.message-tag.is-muted { background: rgba(122, 118, 105, 0.16); color: var(--muted); }
/* R128 — live-chat tag on a member-panel conversation card. */
.message-tag.is-chat { background: rgba(46, 125, 50, 0.16); color: #2e7d32; }
/* R128 — chat start-form honeypot: off-screen (not display:none, so naive
   bulk mailers that skip display:hidden inputs still find and fill it),
   out of the tab order, invisible to humans on every real browser. */
.nw-chat .nw-hp { position: absolute; left: -9999px; top: -9999px; width: 1px; height: 1px; opacity: 0; border: 0; padding: 0; }

/* R17 — Hotmail-style My Messages inbox + account dashboard */
.account-menu a.is-active {
  color: var(--amber-700);
  font-weight: 700;
}
.account-menu a:hover.is-active { color: var(--amber-700); }

.account-welcome { margin-bottom: 0.6rem; }
.account-welcome .section-title { border: none; padding: 0; }

.account-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 0.9rem;
  margin: 0.5rem 0 1.4rem;
}
.account-stat {
  display: flex; flex-direction: column; gap: 0.25rem;
  padding: 1rem 1.1rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-decoration: none;
  color: inherit;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
.account-stat:hover { border-color: var(--amber); box-shadow: 0 4px 14px rgba(0,0,0,0.05); transform: translateY(-1px); }
.account-stat.is-highlighted { border-color: var(--amber); background: rgba(201, 138, 61, 0.06); }
.account-stat-num { font-size: var(--fs-xl); font-weight: 700; color: var(--forest-800); font-variant-numeric: tabular-nums; line-height: 1.1; }
.account-stat-label { font-size: var(--fs-xs); color: var(--muted); display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.account-stat-badge {
  font-size: var(--fs-2xs); font-weight: 700; padding: 0.12rem 0.55rem;
  background: var(--amber); color: #fff; border-radius: 999px;
}

.account-quicklinks { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1rem; }

/* Toolbar above the inbox list */
.mail-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 0.8rem; flex-wrap: wrap; margin-bottom: 0.9rem; }
.mail-search { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.mail-search input[type=search] { padding: 0.55rem 0.9rem; border: 1px solid var(--line); border-radius: 999px; min-width: 220px; background: var(--paper); }
.mail-search input[type=search]:focus { border-color: var(--amber); outline: none; }
.mail-list-note { font-size: var(--fs-xs); margin: 0 0 0.8rem; }

/* R26 — compact rows: the whole card is clickable (View button stays as the
   no-JS fallback), plus the dedicated "+ New message" compose panel. */
.mail-summary[data-href] { cursor: pointer; }
.mail-summary:focus-visible { outline: 2px solid var(--amber); outline-offset: -2px; }
.compose-card {
  background: var(--paper);
  border: 1px solid var(--amber);
  border-radius: var(--radius-sm);
  padding: 1.15rem 1.25rem 1.3rem;
  box-shadow: 0 0 0 2px rgba(201, 138, 61, 0.08);
}
.compose-card h2 { margin: 0 0 0.25rem; font-size: var(--fs-lg); color: var(--forest-900); }
.compose-hint { margin: 0 0 1.1rem; font-size: var(--fs-sm); color: var(--muted); }
.compose-actions { display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap; }
.field-optional { font-weight: 400; font-size: 0.85em; color: var(--muted); }

/* R13 — compact Order History table + unified My Messages */
.order-history-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.order-history-table th,
.order-history-table td {
  text-align: left;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-sm);
  vertical-align: middle;
}
.order-history-table thead th {
  background: var(--sand);
  color: var(--forest-800);
  font-weight: 600;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.order-history-table tbody tr:last-child td { border-bottom: none; }
.order-history-table tbody tr:hover { background: var(--cream); }
.order-history-actions { text-align: right; white-space: nowrap; }

.back-to-history { margin-top: 1rem; }
.back-to-history a { color: var(--amber-600); font-weight: 600; }

.message-compose-form { margin-bottom: 0.6rem; }
.message-compose-form .field { margin-bottom: 0.95rem; }
.message-compose-form textarea {
  font: inherit;
  width: 100%;
  resize: vertical;
  min-height: 110px;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: #fff;
}

/* R91 - .nav-badge sets display, which beats the user agent's [hidden]
   rule: a badge that is meant to be gone has to say so again here. This
   is the third time a component with its own display value has needed
   that restatement, so it is written once, for all of them. */
.nav-badge[hidden] {
  display: none;
}

.nav-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.35em;
  height: 1.35em;
  padding: 0 0.4em;
  border-radius: 999px;
  background: var(--amber);
  color: #fff;
  font-size: var(--fs-2xs);
  font-weight: 700;
  line-height: 1;
  margin-left: 0.35rem;
}

.account-message.is-unread { border-left: 3px solid var(--amber); }

.visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }

.order-item-name a { color: var(--forest-800); }
.order-item-name a:hover { color: var(--amber-600); }
.checkout-summary .summary-line a { color: var(--forest-800); }
.checkout-summary .summary-line a:hover { color: var(--amber-600); }

/* --------------------------------------------------------------------------
   23. Responsive
   -------------------------------------------------------------------------- */
@media (max-width: 1080px) {
  .category-grid, .product-grid { grid-template-columns: repeat(3, 1fr); }
  .product-grid--featured { grid-template-columns: repeat(2, 1fr); }
  /* Nav labels are single-line now; the tighter metrics buy room for a few
     more buttons between 900px and 1080px, before the hamburger takes over. */
  .nav-list > li > a { padding: 0.55rem 0.55rem; font-size: var(--fs-xs); }
  .header-search input { width: 150px; }
}

@media (max-width: 900px) {
  .main-nav { display: none; }
  .menu-toggle { display: flex; }
  .header-search { display: none; }

  .category-grid, .product-grid { grid-template-columns: repeat(2, 1fr); }
  .trust-grid { grid-template-columns: repeat(2, 1fr); }
  .testimonial-grid { grid-template-columns: 1fr; }
  .craft-grid { grid-template-columns: 1fr; }
  .product-detail-grid { grid-template-columns: 1fr; }
  .cart-layout, .checkout-layout, .contact-layout { grid-template-columns: minmax(0, 1fr); }
  .cart-summary { position: static; }
  .account-layout { grid-template-columns: 1fr; }
  .account-sidebar { position: static; }
  /* R49 — the sidebar card used to stack a tall vertical menu above the page
     content, pushing the real content below the fold. On phones it collapses
     into a compact strip: identity line only, menu as wrap-around pills. */
  .account-card { padding: 1rem 1rem 0.95rem; }
  .account-card .eyebrow { display: none; }
  .account-card h2 { font-size: var(--fs-md); margin-bottom: 0.1rem; }
  .account-email { margin-bottom: 0.75rem; font-size: var(--fs-xs); }
  .account-menu {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    padding-top: 0.75rem;
  }
  .account-menu li { margin-bottom: 0; }
  .account-menu a {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.36rem 0.8rem;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--sand);
    font-size: var(--fs-xs);
    line-height: 1.25;
  }
  .account-menu a.is-active {
    background: var(--amber-600);
    border-color: var(--amber-600);
    color: #fff;
  }
  .account-menu a:hover.is-active { color: #fff; }
  .footer-grid { grid-template-columns: 1fr; }
}

@media (max-width: 560px) {
  .category-grid, .product-grid { grid-template-columns: 1fr; }
  .product-grid--featured { grid-template-columns: 1fr; }
  .trust-grid { grid-template-columns: 1fr; }
  /* R84 — two equal columns on a phone. With the long contact lines now able
     to wrap (see .footer-links > div), `1fr 1fr` really means two equal
     tracks instead of one column eating the other's width. */
  .footer-links { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .form-grid { grid-template-columns: minmax(0, 1fr); }
  .cart-line { grid-template-columns: auto 1fr; }
  .cart-line-right { grid-column: 1 / -1; text-align: left; display: flex; align-items: center; gap: 1rem; justify-content: space-between; }
  /* R101 — the glass panel keeps a tighter gutter on a phone. The container
     already spends its own padding-inline, and the desktop step (up to
     2.5rem) would eat the line length the headline needs at 360px. */
  .hero-text { padding: 1.15rem; }
  .hero-actions { flex-direction: column; align-items: stretch; }
  .buy-actions { flex-direction: column; }
  .auth-form .btn-block { margin-top: 1.75rem; }
  .variant-selectors { gap: 1.25rem; }
  .variant-group label {
    font-size: var(--fs-md);
    margin-bottom: 0.6rem;
  }
  .variant-group select {
    min-height: 56px;
    padding: 0.95rem 3rem 0.95rem 1.15rem;
    font-size: var(--fs-md);
    line-height: 1.4;
    border-radius: var(--radius);
    background-size: 14px;
    background-position: right 1.1rem center;
  }
  .gallery-thumbs button { flex-basis: 58px; width: 58px; height: 58px; }
  .lightbox-img { max-width: 100vw; max-height: 84vh; }
  .lightbox-nav { width: 44px; height: 44px; font-size: var(--fs-xl); }
  .lightbox-nav.lb-prev { left: 0.5rem; }
  .lightbox-nav.lb-next { right: 0.5rem; }
  .lightbox-close { top: 0.6rem; right: 0.6rem; width: 42px; height: 42px; font-size: var(--fs-xl); }
  .lightbox-bar { bottom: 0.8rem; gap: 0.7rem; }
  .shipping-steps { flex-direction: column; align-items: stretch; gap: 0.75rem; }
  .step { flex-direction: row; align-items: center; gap: 0.75rem; text-align: left; }
  .step::before { display: none; }
  .order-card-head { flex-direction: column; }
  .order-card-meta { justify-content: space-between; width: 100%; }

  /* R18 — order history table (account dashboard + My Orders) on mobile.
     Collapse into stacked label/value cards so the table never overflows. */
  .responsive-table thead { display: none; }
  .responsive-table,
  .responsive-table tbody,
  .responsive-table tr,
  .responsive-table td { display: block; width: 100%; }
  .responsive-table tr {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    margin-bottom: 0.85rem;
    padding: 0.4rem 0.8rem;
  }
  .responsive-table tr:last-child { margin-bottom: 0; }
  .responsive-table td {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.8rem;
    padding: 0.6rem 0;
    border-bottom: 1px dashed var(--line);
    text-align: right;
    font-size: var(--fs-sm);
  }
  .responsive-table td:last-child { border-bottom: none; padding-bottom: 0.3rem; }
  .responsive-table td::before {
    content: attr(data-label);
    font-weight: 600;
    color: var(--muted);
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    text-align: left;
    flex-shrink: 0;
  }
  /* Hide low-priority columns on small screens to keep the card clean. */
  .responsive-hide-sm { display: none !important; }
  .responsive-table .order-history-actions,
  .responsive-table .actions-cell {
    text-align: right;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 0.4rem;
  }
  .responsive-table .order-history-actions::before,
  .responsive-table .actions-cell::before { content: ''; }

  /* R26 — My Messages on mobile: tighter mail cards, wider chat bubbles,
     full-width send button, comfortable touch targets. */
  .mail-toolbar { flex-direction: column; align-items: stretch; }
  .mail-search input[type=search] { min-width: 0; width: 100%; flex: 1 1 auto; }
  .mail-summary { padding: 0.85rem 0.9rem; }
  .mail-summary-line1 { gap: 0.45rem; margin-bottom: 0.25rem; }
  .mail-subject { font-size: var(--fs-sm); }
  .mail-summary-line2 { margin-bottom: 0.2rem; font-size: var(--fs-xs); }
  .mail-preview { font-size: var(--fs-xs); margin: 0.2rem 0 0.35rem; }
  .mail-summary-line3 { margin-top: 0.45rem; padding-top: 0.5rem; gap: 0.5rem; }
  .mail-status { font-size: var(--fs-2xs); padding: 0.22rem 0.6rem; }
  .mail-card-actions { gap: 0.45rem; }
  .mail-card-actions .btn { padding: 0.42rem 0.9rem; font-size: var(--fs-xs); }
  .mail-detail { padding: 0.9rem 0.9rem 1rem; gap: 0.85rem; }
  .msg { max-width: 88%; }
  .message-reply-form .btn { width: 100%; padding: 0.6rem 1rem; }
  .msg-detail-actions { flex-wrap: wrap; gap: 0.45rem; }
  .compose-card { padding: 0.95rem 0.95rem 1.05rem; }
}

/* --------------------------------------------------------------------------
   R40 — logo image sizing, guest-checkout prompt, public order page
   -------------------------------------------------------------------------- */

/* A wordmark may run up to 390px wide on a desktop header (52px tall); the
   smaller the screen, the less room it gets, so the cap steps down. */
@media (max-width: 1080px) {
  .logo img { max-width: 250px; max-height: 46px; }
}
@media (max-width: 560px) {
  .logo img { max-width: 168px; max-height: 40px; }
  /* R53 — the larger wordmark steps back down on phones */
  .logo { font-size: var(--fs-xl); }
}
.footer-brand .logo img { max-width: 300px; max-height: 48px; }

/* Shown to guests while they fill in their shipping details (and again on the
   public order page) — registering is optional, this is the gentle nudge. */
.checkout-account-note {
  display: flex;
  align-items: flex-start;
  gap: 0.9rem;
  padding: 1rem 1.1rem;
  margin: 0 0 1.4rem;
  background: var(--amber-100);
  border: 1px solid var(--line);
  border-left: 3px solid var(--amber);
  border-radius: var(--radius);
}
.checkout-account-note > svg { flex: 0 0 auto; margin-top: 0.15rem; color: var(--amber-600); }
.checkout-account-note strong { display: block; color: var(--forest-900); font-size: var(--fs-sm); }
.checkout-account-note p { margin: 0.35rem 0 0; font-size: var(--fs-xs); line-height: 1.65; color: var(--ink-soft); }
.checkout-account-note p + p { margin-top: 0.45rem; }
.checkout-account-note a { color: var(--amber-600); font-weight: 600; text-decoration: underline; text-underline-offset: 0.15em; }
.checkout-account-note a:hover { color: var(--forest-900); }

/* Public order page (the target of the "view order" / "continue payment"
   links in the order emails). */
.order-public { max-width: 820px; margin: 0 auto; }
.order-public .order-card { margin-bottom: 1.5rem; }
.order-public-note { margin-top: 1.5rem; }
.order-public-actions { display: flex; flex-wrap: wrap; gap: 0.85rem; margin-top: 1.25rem; }
.order-public-status {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.55rem 0.9rem;
  margin: 0 0 1.25rem; padding: 0.95rem 1.15rem;
  background: var(--paper); border: 1px solid var(--line); border-left: 3px solid var(--amber);
  border-radius: var(--radius);
}
.order-public-status .order-total { font-size: var(--fs-lg); font-weight: 700; color: var(--forest-900); }
.order-public-status p { margin: 0; font-size: var(--fs-sm); color: var(--ink-soft); }

/* R44 — linking a guest order to an account.
   My Orders: the panel is a <details> so it stays tucked away until needed.
   Public order page: one-click "Add to my account" under the order card. */
.guest-order-claim { margin-top: 1.5rem; }
.guest-order-claim .form-note { margin-bottom: 1.1rem; }
.guest-order-claim .form-note:last-child { margin-bottom: 0; }
.order-claim-form { margin-top: 0.85rem; }
.order-claim-form .btn { margin: 0; }

@media (max-width: 560px) {
  .checkout-account-note { padding: 0.9rem 1rem; }
  .order-public-actions .btn { width: 100%; }
}

/* R42 — inline notice shown by the Buy Now form (e.g. missing options). */
.buy-notice {
  margin: 0 0 1rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid rgba(201, 138, 61, 0.35);
  border-radius: 8px;
  background: rgba(201, 138, 61, 0.08);
  color: var(--ink, #2b2b26);
  font-size: var(--fs-sm);
}

/* R42 — `display:flex` on form rows used to override the [hidden] attribute,
   so JS-toggled fields stayed visible. Make [hidden] win. */
[hidden] { display: none !important; }

/* ==========================================================================
   R52 — list chrome for the member-side lists (My Messages): the "per page"
   selector, pagination, sortable headers and the bulk-action bar. Mirrors
   the admin panel components so both surfaces behave the same way.
   ========================================================================== */
.list-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 0.9rem;
  margin: 0 0 0.9rem;
}
.list-per-page {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--muted);
}
.list-per-page-label { font-weight: 600; color: var(--ink-soft); }
.list-per-page select {
  padding: 0.42rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-xs);
  cursor: pointer;
}
.list-per-page select:focus { outline: 2px solid var(--amber); outline-offset: 1px; }

.list-sort {
  color: inherit;
  text-decoration: none;
  white-space: nowrap;
}
.list-sort:hover { color: var(--amber-600); }
.list-sort.is-active { color: var(--amber-600); }
.list-sort-arrow { margin-left: 0.25rem; font-size: 0.8em; }

.list-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem 1rem;
  margin: 1rem 0 0;
  font-size: var(--fs-xs);
}
.list-pager-info { color: var(--muted); }
.list-pager-info strong { color: var(--ink); }
.list-pager-pages { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem; }
.list-pager-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.1rem;
  height: 2.1rem;
  padding: 0 0.65rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink-soft);
  text-decoration: none;
  font-weight: 600;
}
.list-pager-btn:hover { border-color: var(--amber); color: var(--amber-600); }
.list-pager-btn.is-active { background: var(--forest-800); border-color: var(--forest-800); color: #fff; }
.list-pager-btn.is-disabled { opacity: 0.45; cursor: not-allowed; }
.list-pager-gap { color: var(--muted); padding: 0 0.15rem; }

.bulk-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 0.9rem;
  padding: 0.7rem 0.9rem;
  margin: 0 0 0.9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--sand);
  font-size: var(--fs-xs);
}
.bulk-select-all { display: inline-flex; align-items: center; gap: 0.45rem; font-weight: 600; color: var(--ink-soft); cursor: pointer; }
.bulk-count { color: var(--muted); }
.bulk-count strong { color: var(--ink); }
.bulk-spacer { flex: 1 1 auto; }
.bulk-action-label { color: var(--muted); font-weight: 600; }
.bulk-hint { flex-basis: 100%; color: var(--muted); font-size: var(--fs-xs); }
.bulk-check { width: 1rem; height: 1rem; accent-color: var(--amber); cursor: pointer; }
.mail-row.is-selected { border-color: var(--amber); box-shadow: 0 0 0 2px rgba(201, 138, 61, 0.12); }
.mail-row-select { display: inline-flex; align-items: center; padding-right: 0.6rem; }

@media (max-width: 640px) {
  .bulk-bar { align-items: stretch; }
  .bulk-spacer { display: none; }
  .bulk-bar select { flex: 1 1 auto; }
  .list-pager { justify-content: center; }
  .list-pager-info { flex-basis: 100%; text-align: center; }
}

/* R52 — filter chips + sort links on the member-side list pages (the admin
   panel has the same components in admin.css).
   R132 — the customer asked for rectangular chips: 3px corners instead of
   the full pill, and tighter horizontal padding, so a row of filters takes
   noticeably less width and reads as a control strip rather than a row of
   buttons. Counts follow the same 3px shape so a chip is one object. */
.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.38rem 0.7rem;
  border-radius: 3px;
  background: var(--sand);
  color: var(--ink-soft);
  font-size: var(--fs-xs);
  font-weight: 500;
  border: 1px solid transparent;
  text-decoration: none;
  transition: background 0.15s ease, color 0.15s ease;
}
.filter-chip:hover { background: var(--cream); color: var(--ink); }
.filter-chip.is-active { background: var(--forest-800); color: #fff; }
.filter-chip--accent:not(.is-active) { background: rgba(201, 138, 61, 0.14); color: var(--amber-600); }
.filter-chip--accent.is-active { background: var(--amber); color: #fff; }
.filter-chip-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.4rem;
  height: 1.35rem;
  padding: 0 0.35rem;
  border-radius: 3px;
  background: rgba(122, 118, 105, 0.16);
  font-size: var(--fs-2xs);
  font-weight: 700;
}
.filter-chip.is-active .filter-chip-count,
.filter-chip--accent.is-active .filter-chip-count { background: rgba(255, 255, 255, 0.22); }
.admin-filter-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; }

.record-sort-group {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--fs-xs);
}
.record-sort-label { color: var(--muted); font-weight: 600; }
.record-sort-group .list-sort {
  padding: 0.32rem 0.7rem;
  border: 1px solid var(--line);
  /* R132 — the sort switches are filter tags too: same 3px rectangle as the
     chips above, so the whole member-panel control strip reads as one family. */
  border-radius: 3px;
  background: var(--paper);
  color: var(--ink-soft);
  font-weight: 500;
}
.record-sort-group .list-sort:hover { border-color: var(--amber); color: var(--amber-600); }
.record-sort-group .list-sort.is-active { background: var(--forest-800); border-color: var(--forest-800); color: #fff; }

a.mail-subject { color: inherit; text-decoration: none; }
a.mail-subject:hover { color: var(--amber-600); }
.mail-order-ref { color: var(--muted); font-size: var(--fs-xs); margin-left: 0.5rem; }
.mail-order-ref strong { color: var(--ink-soft); }
.mail-row-select { display: inline-flex; align-items: center; margin-right: 0.6rem; vertical-align: middle; }

/* ==========================================================================
   R52 — photo upload queue + photo viewer. Shared by the member pages and
   the admin panel (identical component in admin.css).
   ========================================================================== */
.upload-drop {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 0.9rem;
  padding: 0.85rem 0.95rem;
  border: 1.5px dashed var(--line);
  border-radius: var(--radius-sm);
  background: var(--cream);
  transition: border-color 0.15s ease, background 0.15s ease;
}
.upload-drop.is-dragover { border-color: var(--amber); background: rgba(201, 138, 61, 0.08); }
.upload-input { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
.upload-add {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 1rem;
  border: 1px solid var(--amber);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--amber-600);
  font-weight: 600;
  font-size: var(--fs-sm);
  cursor: pointer;
  user-select: none;
}
.upload-add:hover { background: rgba(201, 138, 61, 0.1); }
.upload-add.is-disabled { opacity: 0.5; cursor: not-allowed; }
.upload-meta { color: var(--muted); font-size: var(--fs-xs); }
.upload-meta [data-upload-counter] { font-weight: 700; color: var(--ink-soft); }
.msg-upload-preview { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 0.7rem; }
.msg-upload-preview:empty { margin-top: 0; }
.upload-thumb {
  position: relative;
  width: 84px;
  height: 84px;
  margin: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--paper);
}
.upload-thumb-view { display: block; width: 100%; height: 100%; padding: 0; border: 0; background: none; cursor: zoom-in; }
.upload-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.upload-thumb-tools {
  position: absolute;
  top: 0; right: 0;
  display: flex;
  gap: 2px;
  padding: 3px;
}
.upload-thumb-btn {
  width: 22px; height: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; border: 0; border-radius: 4px;
  background: rgba(20, 26, 20, 0.72);
  color: #fff;
  font-size: var(--fs-xs); line-height: 1;
  cursor: pointer;
}
.upload-thumb-btn:hover { background: var(--amber-600); }
.upload-thumb-remove:hover { background: var(--danger); }

/* Photo viewer overlay (z-index above the header, below the cookie bar's
   toast layer is not required — it is a modal, so it sits on top). */
.photo-viewer { position: fixed; inset: 0; z-index: 1200; display: none; }
.photo-viewer.is-open { display: block; }
.photo-viewer-backdrop { position: absolute; inset: 0; background: rgba(12, 15, 12, 0.88); }
.photo-viewer-inner {
  position: relative;
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  padding: 3.2rem 3.6rem;
}
/* R163 — the zoom stage: the picture and the expired panel share one flex
   centre, and the gestures (wheel / pinch / drag-pan) live on this box. */
.photo-viewer-stage {
  flex: 1;
  align-self: stretch; /* R163 — fill the inner's height, or the img's
                          max-height:100% resolves against an auto box and
                          the picture never fills the viewer */
  min-width: 0; min-height: 0;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.photo-viewer-img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 6px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
  background: #fff;
  touch-action: none; /* R163 — the stage owns every touch gesture */
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
  transition: transform 0.16s ease-out, opacity 0.18s ease;
}
.photo-viewer-img.is-pinching { transition: none; } /* fingers lead, CSS must not lag */
.photo-viewer-img.is-loading { opacity: 0; } /* R163 — fade a stepped photo in on load */
.photo-viewer-stage.is-dragging { cursor: grabbing; }
/* R155 — the overlay's "this photo has expired" state. Same box, same
   typography as the rest of the viewer, no broken-image icon anywhere. */
.photo-viewer-expired {
  display: none;
  max-width: min(92vw, 460px);
  padding: 2rem 1.6rem;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: #f4f1ea;
  text-align: center;
}
.photo-viewer-expired h3 { margin: 0 0 0.5rem; font-size: var(--fs-lg); }
.photo-viewer-expired p { margin: 0; font-size: var(--fs-sm); line-height: 1.5; color: rgba(255, 255, 255, 0.78); }
.photo-viewer.is-expired .photo-viewer-img,
.photo-viewer.is-expired .photo-viewer-nav,
.photo-viewer.is-expired .photo-viewer-open,
.photo-viewer.is-expired .photo-viewer-counter { display: none; }
.photo-viewer.is-expired .photo-viewer-expired { display: block; }
.photo-viewer-close,
.photo-viewer-nav {
  position: absolute;
  border: 0;
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  cursor: pointer;
  border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
}
.photo-viewer-close { top: 1rem; right: 1rem; width: 42px; height: 42px; font-size: var(--fs-xl); line-height: 1; }
.photo-viewer-nav { top: 50%; transform: translateY(-50%); width: 46px; height: 46px; font-size: var(--fs-2xl); line-height: 1; }
.photo-viewer-nav.pv-prev { left: 1rem; }
.photo-viewer-nav.pv-next { right: 1rem; }
.photo-viewer-close:hover, .photo-viewer-nav:hover { background: var(--amber-600); }
.photo-viewer-bar {
  position: absolute;
  bottom: 1.1rem; left: 0; right: 0;
  display: flex; align-items: center; justify-content: center; gap: 1rem;
  color: rgba(255, 255, 255, 0.85);
  font-size: var(--fs-xs);
}
.photo-viewer-open { color: #fff; text-decoration: underline; }
body.photo-viewer-open { overflow: hidden; }

@media (max-width: 640px) {
  .photo-viewer-inner { padding: 3.4rem 0.75rem; }
  .photo-viewer-nav { width: 38px; height: 38px; font-size: var(--fs-xl); }
  .photo-viewer-nav.pv-prev { left: 0.4rem; }
  .photo-viewer-nav.pv-next { right: 0.4rem; }
}

/* ==========================================================================
   R52 — conversation page: thread header, live indicator, field helpers.
   ========================================================================== */
.field .field-label,
.field-label {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink-soft);
  margin-bottom: 0.35rem;
}
.field-warning {
  margin: 0.5rem 0 0;
  padding: 0.7rem 0.9rem;
  border: 1px solid rgba(181, 64, 47, 0.3);
  border-left: 3px solid var(--danger);
  border-radius: var(--radius-sm);
  background: rgba(181, 64, 47, 0.06);
  color: var(--ink-soft);
  font-size: var(--fs-xs);
  line-height: 1.6;
}
.conversation-view { padding: 1.4rem 1.5rem; }
.conversation-subject { margin: 0 0 0.4rem; font-family: var(--font-display); font-size: var(--fs-xl); }
.mail-detail-body { display: flex; flex-direction: column; gap: 0.75rem; margin: 1rem 0 0; }
.conversation-view .msg-attachment { margin: 0.9rem 0 0; }
.conversation-view .message-reply-form { margin-top: 1.4rem; padding-top: 1.2rem; border-top: 1px solid var(--line); }
.conversation-view .message-reply-form textarea {
  width: 100%;
  padding: 0.7rem 0.95rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  resize: vertical;
}
.conversation-view .compose-actions { margin-top: 1rem; }

.live-indicator {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0.9rem 0 0;
  font-size: var(--fs-xs);
  color: var(--muted);
}
.live-dot {
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--success);
  box-shadow: 0 0 0 0 rgba(63, 122, 74, 0.5);
  animation: livePulse 2.2s infinite;
}
@keyframes livePulse {
  0%   { box-shadow: 0 0 0 0 rgba(63, 122, 74, 0.45); }
  70%  { box-shadow: 0 0 0 7px rgba(63, 122, 74, 0); }
  100% { box-shadow: 0 0 0 0 rgba(63, 122, 74, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .live-dot { animation: none; }
}

@media (max-width: 640px) {
  .conversation-view { padding: 1.1rem 1rem; }
  .conversation-subject { font-size: var(--fs-lg); }
}

/* ==========================================================================
   R52 — order page after-sales: the order number is shown prominently and
   the conversation sits under the order card.
   ========================================================================== */
.msg-images {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin-top: 0.5rem;
}
.msg-images a { display: block; line-height: 0; }
.msg-images img {
  width: 92px;
  height: 92px;
  object-fit: cover;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--paper);
  transition: transform 0.15s var(--ease), border-color 0.15s var(--ease);
}
.msg-images a:hover img { transform: translateY(-2px); border-color: var(--amber); }

.order-after-sales { margin-top: 2rem; }
.order-as-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}
.order-as-head h2 { margin: 0 0 0.35rem; font-family: var(--font-display); font-size: var(--fs-xl); }
.order-as-lede { margin: 0; color: var(--ink-soft); font-size: var(--fs-sm); line-height: 1.6; max-width: 62ch; }
.order-as-empty { margin: 0; color: var(--muted); font-size: var(--fs-sm); }

.order-as-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1.25rem 1.35rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.order-as-form.order-as-reply { margin-top: 1.2rem; }
.order-as-ref {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: baseline;
  gap: 0.2rem 0.8rem;
  padding: 0.85rem 1rem;
  border: 1px solid rgba(201, 138, 61, 0.45);
  border-left: 4px solid var(--amber);
  border-radius: var(--radius-sm);
  background: rgba(201, 138, 61, 0.08);
}
.order-as-ref.compact { padding: 0.5rem 0.75rem; }
.order-as-ref-label {
  grid-column: 1;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--amber-600);
}
.order-as-ref-value {
  grid-column: 2;
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--ink);
}
.order-as-ref.compact .order-as-ref-value { font-size: var(--fs-md); }
.order-as-ref-note {
  grid-column: 1 / -1;
  font-size: var(--fs-xs);
  color: var(--ink-soft);
}
.order-as-form textarea {
  width: 100%;
  padding: 0.7rem 0.95rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  resize: vertical;
}

.order-as-thread {
  margin-top: 1.5rem;
  padding: 1.25rem 1.35rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.order-as-thread-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.order-as-thread-head h3 { margin: 0; font-family: var(--font-display); font-size: var(--fs-lg); }
.order-as-when { color: var(--muted); font-size: var(--fs-xs); }

@media (max-width: 640px) {
  .order-as-head { flex-direction: column; align-items: stretch; }
  .order-as-form, .order-as-thread { padding: 1rem; }
  .msg-images img { width: 78px; height: 78px; }
}

/* ==========================================================================
   R54 — polish pass. Dropdowns on the member lists, the conversation page and
   the account "email" pages (reset password / verify email) all speak one
   language. Everything here deliberately sits at the end of the stylesheet so
   it refines the R25/R52 rules above instead of duplicating them — and so the
   generated themes inherit it automatically.
   ========================================================================== */

/* --- 1. Dropdowns on the member lists ------------------------------------
   The admin panel already settled on one dropdown language (the products.php
   category filter set the standard): the native arrow is replaced by a drawn
   chevron pinned to the right edge, and the control is at least 44px tall so
   it can be hit with a thumb. Same chevron here, inline SVG so no extra
   request is needed. */
.list-per-page select,
.bulk-bar select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  min-height: 2.75rem;
  padding: 0.5rem 2.6rem 0.5rem 0.85rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background-color: var(--paper);
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%237a7669' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.9rem center;
  background-size: 12px 8px;
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-sm);
  line-height: 1.35;
  cursor: pointer;
  transition: border-color 0.18s ease, box-shadow 0.18s ease, background-color 0.18s ease;
}
.list-per-page select:hover,
.bulk-bar select:hover { border-color: var(--moss); background-color: var(--cream); }
.list-per-page select:focus,
.bulk-bar select:focus {
  border-color: var(--amber);
  box-shadow: 0 0 0 3px rgba(201, 138, 61, 0.16);
  outline: none;
}
.bulk-bar { gap: 0.65rem 0.85rem; padding: 0.8rem 1rem; }
.bulk-bar .bulk-action-label { white-space: nowrap; }
.bulk-bar select { min-width: 13rem; }
.bulk-bar [data-bulk-apply] { min-height: 2.75rem; padding-inline: 1.15rem; }
.bulk-select-all input { width: 1.15rem; height: 1.15rem; }
.mail-row-select .bulk-check { width: 1.2rem; height: 1.2rem; }

/* On a phone the bar stacks into full-width rows: who is selected, then what
   to do with them, then the button that does it. */
@media (max-width: 720px) {
  .list-toolbar { row-gap: 0.7rem; }
  .list-per-page { width: 100%; justify-content: space-between; }
  .record-sort-group { flex: 1 1 100%; }
  .bulk-bar { align-items: stretch; }
  .bulk-bar .bulk-spacer { display: none; }
  .bulk-bar .bulk-action-label { flex: 0 0 100%; }
  .bulk-bar select { flex: 1 1 100%; min-width: 0; }
  .bulk-bar [data-bulk-apply] { flex: 1 1 100%; }
  .bulk-hint { flex-basis: 100%; line-height: 1.55; }
}

/* --- 2. The conversation page ("Open") -----------------------------------
   The thread is this page's only job, so it gets a real card: one frame
   around the header strip, the bubbles, the live note and the reply box —
   the borders the R52 layout lost. */
.conversation-view {
  padding: 0;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.conversation-view .mail-detail-head {
  padding: 1.15rem 1.4rem 1rem;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, #fffdf8 0%, var(--cream) 100%);
}
.conversation-subject { margin: 0 0 0.45rem; font-size: var(--fs-xl); line-height: 1.25; }
.mail-detail-meta { gap: 0.3rem 1.1rem; font-size: var(--fs-xs); }
.conversation-view .msg-attachment { margin: 0; padding: 1rem 1.4rem 0; }
.conversation-view .mail-detail-body { margin: 0; padding: 1.1rem 1.4rem; gap: 0.55rem; }
.conversation-view .live-indicator { margin: 0; padding: 0 1.4rem 0.35rem; }
.conversation-view .message-reply-form {
  margin: 0;
  padding: 1.1rem 1.4rem 1.25rem;
  border-top: 1px solid var(--line);
  background: var(--cream);
}
.conversation-view .message-reply-form textarea { background: var(--paper); }
.conversation-view .compose-actions { margin-top: 0.85rem; }

/* Bubbles: one accent edge shows at a glance who wrote what, and the frame
   keeps every message a readable block instead of a floating colour patch.
   Padding is trimmed so more of the conversation fits on one screen. */
.conversation-view .mail-detail-body .msg {
  max-width: 82%;
  padding: 0.62rem 0.9rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  box-shadow: 0 1px 1px rgba(22, 27, 22, 0.03);
}
.conversation-view .mail-detail-body .msg-admin {
  border-left: 3px solid var(--moss);
  background: var(--sand);
}
.conversation-view .mail-detail-body .msg-customer {
  border-right: 3px solid var(--amber);
  background: rgba(201, 138, 61, 0.13);
}
.conversation-view .mail-detail-body .msg-meta { gap: 0.5rem; margin-bottom: 0.3rem; }
.conversation-view .mail-detail-body .msg-text { font-size: var(--fs-sm); line-height: 1.62; }
.conversation-view .mail-detail-body .msg-images { gap: 0.4rem; margin-top: 0.5rem; }
.conversation-view .mail-detail-body .msg-images img { width: 74px; height: 74px; }

@media (max-width: 640px) {
  .conversation-view .mail-detail-head { padding: 1rem 1rem 0.85rem; }
  .conversation-subject { font-size: var(--fs-lg); }
  .conversation-view .msg-attachment { padding: 0.85rem 1rem 0; }
  .conversation-view .mail-detail-body { padding: 0.9rem 1rem; gap: 0.5rem; }
  .conversation-view .live-indicator { padding: 0 1rem 0.3rem; }
  .conversation-view .message-reply-form { padding: 1rem; }
  .conversation-view .mail-detail-body .msg { max-width: 92%; }
  .conversation-view .mail-detail-body .msg-images img { width: 64px; height: 64px; }
}

/* --- 3. Account "email" pages --------------------------------------------
   Forgot password, choose a new password, verify your email, resend the
   verification link: they are all the same moment — a mail from the store has
   just been sent (or opened) — so they share one card that echoes the HTML
   mail shell (email_shell()): a dark wordmark strip on top, the message in
   the middle, the actions and the small print below. */
.auth-wrap:has(> .mail-card) { align-items: flex-start; padding-top: 0.5rem; }
.mail-card {
  width: 100%;
  max-width: 560px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}
.mail-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.95rem 1.5rem;
  background: var(--forest-900);
  border-bottom: 3px solid var(--amber);
}
.mail-card-brand {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  line-height: 1.1;
  color: #fff;
  letter-spacing: 0.02em;
}
.mail-card-kicker {
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sage);
  white-space: nowrap;
}
.mail-card-body { padding: 1.55rem 1.5rem 0.35rem; }
.mail-card-title {
  margin: 0 0 0.5rem;
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  line-height: 1.2;
  color: var(--forest-900);
}
.mail-card-lead {
  margin: 0 0 1.15rem;
  font-size: var(--fs-sm);
  line-height: 1.7;
  color: var(--ink-soft);
}
.mail-card .alert { margin-bottom: 1.1rem; }
.mail-card .auth-form { margin-top: 0.15rem; }
.mail-card-actions {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  margin: 1.3rem 0 0;
}
.mail-card-actions .btn { width: 100%; }
/* R60 — primary actions on register / verify-email cards must read like the
   "Send reset link" button on forgot-password: full btn-large, not the compact
   member-inbox sizing above (that rule keeps applying only to small buttons). */
.mail-card-actions .btn.btn-large { padding: 0.95rem 2.1rem; font-size: var(--fs-md); }
.mail-card-note {
  margin: 1.15rem 0 0;
  padding: 0.85rem 1rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--amber);
  border-radius: var(--radius-sm);
  background: var(--cream);
  font-size: var(--fs-xs);
  line-height: 1.65;
  color: var(--ink-soft);
}
.mail-card-note p { margin: 0; }
.mail-card-note p + p { margin-top: 0.45rem; }
.mail-card-note strong { color: var(--forest-800); }
.mail-card-foot {
  padding: 1.1rem 1.5rem 1.4rem;
  font-size: var(--fs-xs);
  line-height: 1.7;
  color: var(--muted);
}
.mail-card-foot a { color: var(--amber-600); font-weight: 600; }
.mail-card-foot a:hover { text-decoration: underline; }

@media (max-width: 560px) {
  .mail-card-head { padding: 0.85rem 1.1rem; }
  .mail-card-brand { font-size: var(--fs-md); }
  .mail-card-kicker { font-size: var(--fs-2xs); letter-spacing: 0.12em; }
  .mail-card-body { padding: 1.2rem 1.1rem 0.3rem; }
  .mail-card-title { font-size: var(--fs-xl); }
  .mail-card-foot { padding: 1rem 1.1rem 1.2rem; }
}

/* --------------------------------------------------------------------------
   R57 — reduced transparency: the same layout with fully opaque surfaces.
   Additive and opt-in: only users whose OS asks for less transparency ever
   see this block, and nothing else in the stylesheet changes for them.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-transparency: reduce) {
  .site-header,
  .site-header.is-scrolled {
    background: #161b16;
  }
  /* R141f — the frost moved onto underlay pseudos, so silencing it means
     silencing the pseudos and painting the elements solid. */
  .site-header::before,
  .site-header.is-scrolled::before {
    background: #161b16;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .dropdown {
    background: #fffdf9;
  }
  .dropdown::before {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .mobile-menu {
    background: #161b16;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  /* R101 — the hero copy panel joins the list: same pane, frost switched off.
     The hero is the one place where losing the frost could cost legibility
     rather than just polish, so it goes fully opaque here. */
  .hero-text {
    background: #161b16;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

/* ==========================================================================
   R81 — one voice for the "a mail is on its way" moments.

   success.php closes a purchase with a centred icon, a heading, the copy,
   a row of buttons and a small print line underneath. register.php?sent=1,
   verify-email.php and both states of forgot-password.php say exactly the
   same kind of thing ("we have just emailed you — go do the thing"), so they
   now borrow that composition. Only the arrangement is shared: the centred
   column, the panel width, the icon, the button row and the note. The card,
   the brand strip, the fields and the form still come from the component
   each page already used, so a generated theme keeps repainting them through
   the normal tokens.
   ========================================================================== */

/* 1. The column. Matches .status-inner (560px) so the two page families line
   up, and the card fills it instead of stopping at 560px of its own. */
.mail-card--status { max-width: 620px; }
.auth-wrap:has(> .mail-card--status) { padding-top: 0.5rem; }

/* 2. The centred column itself. `display: flex` on a block element keeps
   text-align working for the copy while the parts below are laid out in
   order; the children are stretched so the form and the notes stay full
   width. */
.mail-card-status {
  display: flex;
  flex-direction: column;
  text-align: center;
}
.mail-card-status > * { min-width: 0; }

/* 3. The icon. Same 96px circle as the payment pages, in the theme's success
   colour; .mail-card-status-icon--warn swaps in the amber used by a payment
   that has not settled yet, and .alert states draw from the .alert they sit
   in, so a form that failed still reads as a form that failed. */
.mail-card-status-icon {
  display: flex;
  width: 96px;
  height: 96px;
  align-items: center;
  justify-content: center;
  margin: 0 auto 1.4rem;
  border-radius: 50%;
  background: rgba(63, 122, 74, 0.14);
  color: var(--success);
}
.mail-card-status-icon--warn { background: rgba(201, 138, 61, 0.14); color: var(--amber-600); }

/* 4. Heading and copy: the status page's own scale, one step down so it fits
   the narrower card (h1 is clamp(2rem, 4vw, 2.7rem) / 1rem lead over there). */
.mail-card-status .mail-card-title {
  margin: 0 0 0.6rem;
  font-size: var(--fs-2xl);
  line-height: 1.2;
}
.mail-card-status .mail-card-lead {
  margin: 0 auto 1.15rem;
  max-width: 46ch;
  font-size: var(--fs-md);
  line-height: 1.75;
}
.mail-card-status .order-number { margin: 0 0 0.6rem; }

/* 5. The button row: identical geometry to .status-actions, including the
   same stacking behaviour on a phone. */
.mail-card-status-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.9rem;
  margin: 1.7rem 0 0;
}
.mail-card-status-actions .btn { width: auto; }
/* A single full-width action stays the exception: it is the equivalent of the
   status page's lone primary button. */
.mail-card-status-actions .btn-block { width: 100%; }

/* 6. On a phone the card and the payment page land on the same numbers. */
@media (max-width: 560px) {
  .mail-card-status-actions { flex-direction: column; align-items: stretch; gap: 0.7rem; }
  .mail-card-status-icon { width: 80px; height: 80px; margin-bottom: 1.2rem; }
}

/* 7. Anything a page inserts between the copy and the buttons — the field
   grid of a short form, a callout — is centred and full width so it cannot
   break the column. */
.mail-card-status .auth-form,
.mail-card-status .alert,
.mail-card-status .mail-card-note { width: 100%; }
.mail-card-status .alert { text-align: left; }
.mail-card-status .field { text-align: left; }

/* ==========================================================================
   R82/R83 — the status column, without the card around it.

   The merchant asked register.php?sent=1, verify-email.php and both states of
   forgot-password.php to look exactly like success.php — and success.php has
   no box and no band: the icon, the heading, the copy and the buttons sit
   straight on the page, with nothing above the icon at all.

   R82 removed the card: background, border, radius, shadow and the big
   content padding. R83 removed what was left of the card's chrome —
   `.mail-card-head`, the dark "Deerseeker Archery / Account email" strip with
   its 3px amber rule. The markup no longer emits that div on these three
   pages; the defensive rule below makes the intent explicit and keeps a
   stray copy from ever re-introducing a band on a bare column.

   Held behind `.mail-card--bare` rather than restyling `.mail-card`, so the
   member inbox (My Messages) and the admin panel keep their framed cards and
   their brand strips.

   This block sits at the END of the stylesheet on purpose. `.mail-card` and
   `.mail-card--status` are both single-class selectors, so specificity ties
   and the LAST declaration of max-width wins. `.mail-card--status` sets 620px
   (R81) and this rule needs success.php's own 560px, so it has to come after
   it. Moving this block above R81 silently restores the 620px column — which
   is exactly the bug this note exists to prevent.
   ========================================================================== */
.mail-card--bare {
  max-width: 560px;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
/* R83 — no band, no rule, no label: the column opens on the icon, the way
   success.php does. */
.mail-card--bare .mail-card-head { display: none; }
/* With the head gone the body is the first thing on the page, so it supplies
   the whole top gap. 4.2rem matches the breathing room the page-hero above
   used to leave under the band. */
.mail-card--bare .mail-card-body { padding: 4.2rem 0 0; }
/* success.php's .status-note earns its distance with `margin: 2.2rem 0 0`,
   which also stops the margin from collapsing into the column above. The card
   foot is the last child of the CARD, not of the status column, so without a
   margin of its own it butts straight up against the buttons. Mirror the
   status note's number here so the two page families end identically. */
.mail-card--bare .mail-card-foot {
  margin: 2.2rem 0 0;
  padding: 1.6rem 0 0;
  border-top: 0;
  text-align: center;
}
/* The callout narrows to the measure of the lead — full width here would run
   to the viewport edges once the card's own padding is gone. */
.mail-card--bare .mail-card-note {
  max-width: 46ch;
  margin-left: auto;
  margin-right: auto;
  text-align: left;
}

@media (max-width: 560px) {
  .mail-card--bare .mail-card-body { padding: 2.6rem 0 0; }
  .mail-card--bare .mail-card-foot { padding: 1.3rem 0 0; }
}

/* ==========================================================================
   R85 — "Not for sale": a product the store keeps on display but does not sell
   --------------------------------------------------------------------------
   Delisting is deliberately NOT hiding. A delisted product stays in category
   pages, in search results and on the home page as a showcase entry, and its
   own page keeps the full description and photo gallery. What changes is that
   PHP prints no price row and no buy form at all, so nothing here has to
   blank a price out or hide a button — these rules only give the replacement
   text its own styling and keep a delisted card's height honest next to its
   priced neighbours. Appended at the end on purpose: a new override block
   added earlier in the sheet loses to any later rule of equal specificity.
   ========================================================================== */

/* Product page — replaces the price row. Mirrors .product-price-row's own
   margin (0 top, 1rem below) so the title block and the stock line below it
   do not jump between a sellable and a delisted product. */
.product-delisted-badge {
  margin: 0 0 1rem;
}

/* Product page — the showcase notice. A quiet sand panel rather than the
   amber .alert strip: this is information, not a warning about something the
   shopper did, and it must not inflate the amber-strip geometry other pages
   are measured against. */
.delisted-note {
  margin-top: 1.1rem;
  padding: 0.95rem 1.1rem;
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  line-height: 1.6;
}
.delisted-note p { margin: 0; }
.delisted-note p + p { margin-top: 0.5rem; }
.delisted-note strong { color: var(--forest-800); }
.delisted-note a { color: var(--amber-600); font-weight: 600; }

/* Product card — the price line is swapped for this. Sized and weighted to
   sit in the same place as the price it replaces. */
.product-delisted {
  display: inline-block;
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--muted);
}

/* Delisted badge on the card image — deliberately darker and calmer than the
   amber "Sale" badge, so a showcase entry never reads as a promotion. */
.badge-delisted { background: rgba(22, 27, 22, 0.82); color: #f0e6d2; }

/* Held back a notch, so a wall of cards still leads the eye to what is
   actually for sale. */
.product-card.is-delisted .product-image img { filter: saturate(0.7); }

@media (prefers-reduced-transparency: reduce) {
  /* Opaque rather than translucent, for readers who asked the system for it. */
  .badge-delisted { background: #161b16; }
}
/* ===========================================================================
   R86 — wholesale pricing, delivery terms and the uploaded logo
   ---------------------------------------------------------------------------
   Appended last on purpose: at equal specificity the source order decides, so
   these rules must be able to override the price / summary / status rules
   defined further up (R82 taught that lesson the hard way).
   =========================================================================== */

/* The drawer carries the same mark as the desktop bar, but a 52px-capped
   image is too tall for its 60px header row. */
.mobile-menu-header .logo-img {
  max-height: 38px;
  max-width: 210px;
}

/* ---------------------------------------------------------------------------
   Wholesale pricing
   --------------------------------------------------------------------------- */

/* Inline "Wholesale" pill in the price row on the product page. Same geometry
   as .save-badge, but in the quiet dark tone: it labels the pricing model, it
   is not a promotion.

   R88 — this pill used to be rendered with class="badge badge-wholesale", and
   .badge is POSITION:ABSOLUTE with top/left offsets because it is meant for
   product-card photos. Nothing in the product-detail column is positioned, so
   the pill escaped to the nearest positioned ancestor — `.reveal`, which
   carries a transform — and landed 14px inside that box, straight on top of
   the product title (measured: badge 101.5 × 26 at y 219.5 against an <h1> at
   y 205.1 – 253.5). It is now a normal-flow flex item in the price row, next
   to the "From …" figure, exactly like .save-badge: nothing can overlap the
   title because the title is no longer in the same layer. The explicit
   position/top/left reset is what makes the class safe even if it is still
   written alongside .badge. */
.badge-wholesale {
  position: static;
  top: auto;
  left: auto;
  display: inline-flex;
  align-items: center;
  padding: 0.22rem 0.65rem;
  background: var(--forest-800);
  color: var(--cream);
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: 999px;
  white-space: nowrap;
}

/* The same label at card size, where there is far less room and the price
   above it already says "From". */
.price-tag-wholesale {
  display: inline-flex;
  align-items: center;
  padding: 0.16rem 0.5rem;
  background: var(--sand);
  color: var(--forest-800);
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: 999px;
  white-space: nowrap;
}

/* The step table. Reads like a rate card rather than a form, so it borrows the
   storefront's line/paper tokens and keeps the money in the price accent. */
.tier-table {
  margin: 0 0 1.5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  overflow: hidden;
}
.tier-table table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}
.tier-table th {
  padding: 0.6rem 1rem;
  text-align: left;
  background: var(--sand);
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.tier-table td {
  padding: 0.55rem 1rem;
  border-bottom: 1px solid var(--line);
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}
.tier-table tr:last-child td { border-bottom: none; }
.tier-table th:last-child, .tier-table td:last-child { text-align: right; }
.tier-table [data-tier-amount] { font-weight: 700; color: var(--price); white-space: nowrap; }
/* The step the current quantity has reached — a quiet highlight, not a badge. */
.tier-table tr.is-active td { background: var(--amber-100); }

.tier-note {
  margin: 0;
  padding: 0.7rem 1rem;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: var(--fs-xs);
  line-height: 1.55;
}
/* "At 12 units: $80.00 per unit · $960.00 in total" — painted in by main.js. */
.tier-live {
  margin: 0;
  padding: 0 1rem 0.75rem;
  color: var(--forest-800);
  font-size: var(--fs-xs);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.tier-live:empty { display: none; }

/* ---------------------------------------------------------------------------
   Delivery terms on the product page
   --------------------------------------------------------------------------- */
.product-shipping {
  margin: 0 0 0.9rem;
  padding-left: 1.1rem;
  position: relative;
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  line-height: 1.55;
}
.product-shipping::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.5em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--moss);
}
.product-shipping.is-quote { color: var(--amber-600); font-weight: 600; }
.product-shipping.is-quote::before { background: var(--amber); }

/* R138b — "Free shipping": bold #670099 words with a small deep-orange check
   badge in front, directly under the price. No tinted box, no border, no site
   radius on the words themselves — the disc is the only chrome. */
.product-shipping.is-free {
  padding-left: 1.5rem;
  color: #670099;
  font-weight: 700;
  letter-spacing: 0.01em;
}
.product-shipping.is-free::before {
  content: '\2713';
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 1.05rem;
  height: 1.05rem;
  border-radius: 50%;
  background: #ff8c00;
  color: #fff;
  font-size: var(--fs-2xs);
  font-weight: 700;
  line-height: 1.05rem;
  text-align: center;
}

/* ---------------------------------------------------------------------------
   Basket: the tiered line and the carriage line
   --------------------------------------------------------------------------- */
.cart-line-tier {
  display: block;
  margin-top: 0.15rem;
  color: var(--forest-700);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* Sits under the summary rows it explains — the basket is not free and we will
   come back with a figure. */
.summary-note {
  margin: 0.9rem 0 0;
  padding: 0.75rem 0.9rem;
  background: var(--amber-100);
  border-left: 3px solid var(--amber);
  border-radius: var(--radius-sm);
  color: var(--ink-soft);
  font-size: var(--fs-xs);
  line-height: 1.55;
}

/* ---------------------------------------------------------------------------
   Order views: the carriage line, the pending-quote notice, the pill
   --------------------------------------------------------------------------- */
.order-shipping-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}
.order-shipping-line span {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.order-shipping-line strong {
  font-size: var(--fs-md);
  color: var(--forest-800);
  font-variant-numeric: tabular-nums;
}

/* The quote notice is an explanation, not a footnote — it outranks the small
   muted .order-item-note it extends, in colour and in size.
   R88 — .order-shipping-note (the merchant's own "Note to the customer" about
   the delivery) wears the same callout, but under its own name: the two are
   different messages and one selector must not stand for both. */
.order-quote-note,
.order-shipping-note,
.checkout-quote-note {
  margin: 1.1rem 0 0;
  padding: 1rem 1.15rem;
  background: var(--amber-100);
  border-left: 3px solid var(--amber);
  border-radius: var(--radius-sm);
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  line-height: 1.6;
}
.checkout-quote-note { margin: 0 0 1.2rem; }
.order-quote-note p, .order-shipping-note p, .checkout-quote-note p { margin: 0; }
.order-quote-note p + p, .order-shipping-note p + p, .checkout-quote-note p + p { margin-top: 0.5rem; }
.order-quote-note strong, .order-shipping-note strong, .checkout-quote-note strong { color: var(--forest-800); }

/* "Awaiting shipping quote" — amber, because somebody has to act before this
   order can move. */
.status-quote { background: rgba(201, 138, 61, 0.16); color: var(--amber-600); }

@media (max-width: 560px) {
  /* The rate card loses its padding before it loses a column. */
  .tier-table th, .tier-table td { padding: 0.5rem 0.7rem; }
  .tier-note, .tier-live { padding-left: 0.7rem; padding-right: 0.7rem; }
  .order-shipping-line { flex-direction: column; gap: 0.2rem; }
}

/* --------------------------------------------------------------------------
   R88 — home-page hero slideshow
   --------------------------------------------------------------------------
   Every slide ships in the DOM with the first one already `.is-active`, so the
   hero renders fully without JavaScript; the script only moves the class. That
   is why the base rule hides the inactive slides rather than the script doing
   it — a no-JS visitor sees picture one, exactly like the static hero.

   The effect is chosen by the `data-effect` attribute the template prints from
   `hero_slide_effect()`, so all five transitions share one set of rules and
   the CSS never has to guess which one is live.

   Positioned to match `.hero-media` (absolute, inset 0) so the slideshow and
   the plain `<img>` occupy the same box. The base `.hero-media img` rule keeps
   working for the images inside the slides, which is what we want: they stay
   covered and cropped the same way as before.

   The dark backing matters more than it looks: every effect except "slide"
   cross-dissolves two pictures over each other, and half way through, the top
   picture is at 50% opacity over a bottom picture at 50% — which lets a little
   of the page behind show through. Without this line that flash is the cream
   body of the default theme; with it, the hero merely dips a shade darker for
   a few frames, which nobody can see. The token keeps that true under Noir. */
.hero-slides { position: absolute; inset: 0; background: var(--forest-900); }

.hero-slide {
  position: absolute;
  inset: 0;
  /* No transition here: each effect sets its own below, because "slide" must
     not cross-fade (both pictures have to stay opaque while they trade
     places) while "fade" must not move anything.

     Deliberately no `will-change`: the hero keeps every picture in the DOM, so
     promising each one its own compositor layer would hold up to eight
     full-screen layers in memory on a phone. Browsers promote a layer for the
     duration of an opacity/transform transition anyway. */
  opacity: 0;
  z-index: 0;
}
.hero-slide.is-active { opacity: 1; z-index: 1; }

/* --- Fade (the default) -------------------------------------------------- */
.hero-slides[data-effect="fade"] .hero-slide { transition: opacity 900ms ease; }

/* --- Slide: a push across, incoming from the right -----------------------
   Two pictures move at once here, so the fading model does not fit: both stay
   fully opaque and position does all the work. The one on its way out needs to
   be named, because once it loses `.is-active` the base rule would send it back
   to +100% — the right-hand side — and it would fly the wrong way.

   `.is-leaving` keeps it travelling left; `.is-instant` lets the script snap it
   back to +100% afterwards with no transition, which is invisible because by
   then the new picture covers the frame completely and the old one is off the
   left edge. */
.hero-slides[data-effect="slide"] .hero-slide {
  opacity: 1;                       /* no dissolve — motion does the work */
  transform: translateX(100%);
  transition: transform 900ms cubic-bezier(0.4, 0, 0.2, 1);
}
.hero-slides[data-effect="slide"] .hero-slide.is-active { transform: translateX(0); z-index: 1; }
.hero-slides[data-effect="slide"] .hero-slide.is-leaving { transform: translateX(-100%); z-index: 0; }
.hero-slides[data-effect="slide"] .hero-slide.is-instant { transition: none; }

/* --- Zoom: scale in from slightly large ---------------------------------- */
.hero-slides[data-effect="zoom"] .hero-slide {
  transition: opacity 800ms ease, transform 1500ms cubic-bezier(0.2, 0, 0.2, 1);
  transform: scale(1.08);
}
.hero-slides[data-effect="zoom"] .hero-slide.is-active { transform: scale(1); }

/* --- Ken Burns: fade in, then drift for the whole dwell time ------------- */
.hero-slides[data-effect="kenburns"] .hero-slide { transition: opacity 1100ms ease; }
.hero-slides[data-effect="kenburns"] .hero-slide.is-active img {
  animation: nw-hero-kenburns var(--hero-kb, 14s) ease-out both;
}
@keyframes nw-hero-kenburns {
  from { transform: scale(1) translate3d(0, 0, 0); }
  to   { transform: scale(1.14) translate3d(-1.6%, -1.2%, 0); }
}

/* --- None: swap with no transition at all -------------------------------- */
.hero-slides[data-effect="none"] .hero-slide { transition: none; }

/* --- Dots ---------------------------------------------------------------- */
.hero-dots {
  position: absolute;
  z-index: 3;
  left: 50%;
  bottom: clamp(0.9rem, 2.5vh, 1.6rem);
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
  padding: 0;
}
/* A 28px hit target around a 9px dot: big enough for a thumb, invisible as a
   row of blobs. */
.hero-dot {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.hero-dot > span {
  display: block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.45);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
  transition: background 200ms ease, transform 200ms ease;
}
.hero-dot:hover > span { background: rgba(255, 255, 255, 0.75); }
.hero-dot.is-active > span { background: var(--amber); transform: scale(1.3); }
.hero-dot:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; border-radius: 50%; }

@media (prefers-reduced-motion: reduce) {
  /* The script stops rotating outright for these visitors; this is the belt to
     that pair of braces, so a mid-flight picture still lands instantly if the
     preference flips while the page is open.
     
     The `[data-effect]` in these selectors is load-bearing, not decoration. The
     per-effect rules above are `.hero-slides[data-effect="fade"] .hero-slide`
     — three simple selectors — so a bare `.hero-slide` here (one) would LOSE,
     and the fade really would keep its 900ms transition under reduced motion.
     Matching the count ties, and living later in the sheet wins the tie. The
     `.is-leaving` and `img` selectors are repeated for the same reason. */
  .hero-slides[data-effect] .hero-slide,
  .hero-slides[data-effect] .hero-slide.is-leaving,
  .hero-slides[data-effect="kenburns"] .hero-slide.is-active img {
    transition: none;
    animation: none;
  }
  .hero-slides[data-effect="slide"] .hero-slide,
  .hero-slides[data-effect="slide"] .hero-slide.is-leaving,
  .hero-slides[data-effect="zoom"] .hero-slide { transform: none; }
}

/* --------------------------------------------------------------------------
   R88 — member tiers

   The badge a member sees on their account page, and the card that says how
   far the next rung is.

   The four pills carry their own background and ink instead of leaning on
   theme tokens. A tier is a fact about the customer, not about the shop's
   colour scheme: it has to read identically in the cream default and under the
   dark Noir chrome, and a gold badge that turned into a dark smudge when the
   merchant changed theme would be worse than no badge at all. The card around
   them is ordinary content, so that part does follow the theme.

   This block sits at the very END of the sheet on purpose — `.summary-row` is
   defined above with the same specificity as the discount row below, and at
   equal specificity the later rule wins.
   -------------------------------------------------------------------------- */
.tier-pill {
  display: inline-block;
  padding: 0.2rem 0.6rem;
  border: 1px solid transparent;
  border-radius: 999px;
  font-family: var(--font-body);
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.09em;
  line-height: 1.5;
  text-transform: uppercase;
  vertical-align: middle;
  white-space: nowrap;
}
.tier-member  { background: #ece7dc; color: #4c493f; border-color: #ddd5c4; }
.tier-silver  { background: #e8eaee; color: #3f4756; border-color: #d2d7e0; }
.tier-gold    { background: #f7ecd6; color: #7a5312; border-color: #e8d5ac; }
.tier-diamond { background: #e2eef3; color: #22505f; border-color: #c5dce4; }

.tier-card {
  margin-top: 1.1rem;
  padding: 1.1rem 1.2rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--cream);
}
.tier-card-head {
  display: flex;
  flex-flow: row wrap;
  align-items: center;
  gap: 0.6rem;
}
.tier-card-spend {
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}
.tier-card-line {
  margin: 0.7rem 0 0;
  font-size: var(--fs-sm);
  line-height: 1.65;
  color: var(--ink-soft);
}
.tier-card-line strong { color: var(--ink); }

.tier-progress {
  margin-top: 0.8rem;
  height: 8px;
  border-radius: 999px;
  background: var(--sand);
  overflow: hidden;
}
.tier-progress > span {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--price);
  transition: width 0.6s var(--ease);
}

/* The discount line in the cart and the checkout summary. `.is-discount` is
   two classes, so it outranks the shared `.summary-row` colour above. */
.summary-row.is-discount { color: var(--success); font-weight: 600; }
.summary-row.is-discount > span:last-child { color: var(--success); }

@media (prefers-reduced-motion: reduce) {
  .tier-progress > span { transition: none; }
}

@media (max-width: 560px) {
  .tier-card { padding: 1rem 1.05rem; }
  .tier-card-head { gap: 0.45rem; }
  .summary-row.is-discount { font-size: var(--fs-sm); }
}
/* ------------------------------------------------------------------ */
/* R90 - presence                                                      */
/*                                                                     */
/* The dot that says whether the person on the other end is at their    */
/* screen. Two states, and it never leans on colour alone: the word is  */
/* always there, so it still reads for anybody who cannot separate the  */
/* green from the grey.                                                 */
/* ------------------------------------------------------------------ */
.presence-badge {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: .01em;
  line-height: 1.4;
  white-space: nowrap;
  vertical-align: middle;
}

.presence-dot {
  flex: 0 0 auto;
  width: .5rem;
  height: .5rem;
  border-radius: 50%;
  background: currentColor;
}

.presence-badge.is-online {
  color: var(--success);
}

.presence-badge.is-offline {
  color: var(--muted);
}

.presence-badge.is-online .presence-dot {
  box-shadow: 0 0 0 3px rgba(63, 122, 74, .18);
}

.presence-badge.is-offline .presence-dot {
  box-shadow: 0 0 0 3px rgba(122, 118, 105, .16);
}

/* The "is the team there?" line. It carries a sentence as well as the dot,
   and that sentence is the longest token on the row on a narrow screen - the
   usual squeeze rules apply: the text is allowed to break, and the flex item
   is allowed to shrink below its content width. */
.order-support-line,
.account-presence {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .5rem .6rem;
  margin: 0 0 1rem;
  padding: .7rem .9rem;
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}

.order-support-note,
.account-presence-note {
  min-width: 0;
  overflow-wrap: anywhere;
}
/* R90 - the read marker on a bubble you wrote. It sits in the bubble's meta
   line next to the clock, so it answers "did they see it?" in the place the
   reader is already looking. */
.msg-read {
  display: inline-block;
  margin-left: .45rem;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  white-space: nowrap;
}

.msg-read.is-read {
  color: var(--success);
}

.msg-read.is-unread {
  color: var(--muted);
}
/* ------------------------------------------------------------------ */
/* R90 - the tariff note under the wholesale price steps               */
/*                                                                     */
/* Deliberately the loudest line in the price block: a buyer weighing  */
/* this table against an overseas quote has to know whether the import */
/* charge is already inside these figures before they decide which one */
/* is cheaper. Text is the merchant's (product note, else shop         */
/* default), so the rule here is only about weight and colour - and    */
/* the usual squeeze guard, because a note can be a whole sentence.    */
/* ------------------------------------------------------------------ */
.tariff-note {
  margin: 0;
  padding: .65rem 1rem;
  border-top: 1px solid var(--line);
  background: var(--amber-100);
  color: var(--amber-700);
  font-size: var(--fs-xs);
  font-weight: 700;
  line-height: 1.5;
  overflow-wrap: anywhere;
}

@media (max-width: 560px) {
  .tariff-note { padding: .6rem .7rem; font-size: var(--fs-xs); }
}

/* -------------------------------------------------------------------------- */
/*  R121 — live chat: the floating launcher on the home page and the          */
/*  conversation surface it opens (shared with chat.php, the emailed link).   */
/* -------------------------------------------------------------------------- */

.nw-chat-launcher {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 90;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  border: 0;
  cursor: pointer;
  background: var(--amber, #c98a3d);
  color: #ffffff;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
  transition: transform 0.15s ease, background 0.15s ease;
}
.nw-chat-launcher:hover { transform: translateY(-2px); background: var(--amber-600, #a96f2a); }

.nw-chat-panel {
  position: fixed;
  right: 1.25rem;
  bottom: 5.75rem;
  z-index: 91;
  width: min(360px, calc(100vw - 2rem));
  max-height: min(560px, calc(100vh - 8rem));
  display: flex;
  flex-direction: column;
  background: var(--paper, #fdfbf6);
  border: 1px solid var(--line, #e3ddd0);
  /* R137 — the chat window follows the site's own Corner rounding. */
  border-radius: var(--radius);
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.25);
  overflow: hidden;
}
.nw-chat.is-open .nw-chat-launcher { background: var(--ink, #211f1a); }

.nw-chat-head {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.8rem 1rem;
  background: var(--forest-900, #161b16);
  color: #f5efdf;
}
.nw-chat-title { font-weight: 700; }
.nw-chat-status { font-size: var(--fs-xs); opacity: 0.85; }
.nw-chat-close {
  margin-left: auto;
  border: 0;
  background: transparent;
  color: inherit;
  font-size: var(--fs-xl);
  line-height: 1;
  cursor: pointer;
}

/* R123 — the start form breathes: taller inputs with real separation, and a
   focus ring in the site's amber so the three fields read as one calm form
   instead of three lines pressed together. */
.nw-chat-start { padding: 1.1rem 1.1rem 1.2rem; display: flex; flex-direction: column; gap: 0.95rem; }
/* R124 — the fields live INSIDE the form, and the outer gap never reached
 * them: the three inputs sat flush against each other (0 px apart). The form
 * is its own column now, with an explicit 14 px gap so every control keeps a
 * clear band of air (the brief asked for at least 10 px). */
.nw-chat-start form { display: flex; flex-direction: column; gap: 14px; row-gap: 14px; }
.nw-chat-start input,
.nw-chat-start textarea {
  width: 100%;
  padding: 0.72rem 0.85rem;
  border: 1px solid var(--line, #e3ddd0);
  /* R137 — same radius as every other site text field (.field input). */
  border-radius: var(--radius-sm);
  font: inherit;
  background: #ffffff;
  color: var(--ink, #211f1a);
  resize: vertical;
}
.nw-chat-start input:focus,
.nw-chat-start textarea:focus { outline: none; border-color: var(--amber, #c98a3d); box-shadow: 0 0 0 3px rgba(201, 138, 61, 0.15); }
.nw-chat-lead { margin: 0; font-size: var(--fs-sm); color: var(--ink-soft, #4c493f); }
.nw-chat-error { margin: 0; color: var(--danger, #b3261e); font-size: var(--fs-xs); }

.nw-chat-room { display: flex; flex-direction: column; min-height: 0; flex: 1; }
.nw-chat-log {
  flex: 1;
  min-height: 180px;
  max-height: 340px;
  overflow-y: auto;
  padding: 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  background: var(--cream, #f6f3ec);
}
.nw-chat-msg { max-width: 85%; display: flex; flex-direction: column; }
.nw-chat-msg.is-mine { align-self: flex-end; align-items: flex-end; }
.nw-chat-msg.is-theirs { align-self: flex-start; align-items: flex-start; }
.nw-chat-msg-body {
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  background: #ffffff;
  border: 1px solid var(--line, #e3ddd0);
  color: var(--ink, #211f1a);
}
.nw-chat-msg.is-mine .nw-chat-msg-body { background: var(--amber, #c98a3d); border-color: var(--amber, #c98a3d); color: #ffffff; }
.nw-chat-msg-time { font-size: var(--fs-2xs); color: var(--muted, #7a7669); margin-top: 0.15rem; }

/* R122 — collapsed pre-today history in every conversation view. The toggle
   is a quiet pill above the folded wrapper; inside the chat log the wrapper
   re-stacks the bubbles it holds, so left/right alignment keeps working. */
.msg-history-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0 0 0.65rem;
  padding: 0.35rem 0.85rem;
  border: 1px solid var(--line, #e3ddd0);
  border-radius: 999px;
  background: var(--paper, #fdfbf6);
  color: var(--ink-soft, #4c493f);
  font: inherit;
  font-size: var(--fs-xs);
  cursor: pointer;
}
.msg-history-toggle:hover { border-color: var(--amber, #c98a3d); color: var(--ink, #211f1a); }
.msg-history-arrow {
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 6px solid currentColor;
  transition: transform 0.15s ease;
}
.msg-history-toggle[aria-expanded="true"] .msg-history-arrow { transform: rotate(180deg); }
.msg-history { min-width: 0; }
.msg-history[hidden] { display: none; }
.nw-chat-log .msg-history { display: flex; flex-direction: column; gap: 0.5rem; width: 100%; }

.nw-chat-compose {
  flex-direction: column;
  gap: 0.45rem;
  padding: 0.7rem;
  border-top: 1px solid var(--line, #e3ddd0);
  background: var(--paper, #fdfbf6);
}
/* R124 — the composer row: paperclip, text field, send, on one line. */
.nw-chat-compose-row { display: flex; gap: 0.5rem; align-items: center; }
.nw-chat-compose-row input[type="text"] { flex: 1; min-width: 0; }
.nw-chat-attach {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border: 1px solid var(--line, #e3ddd0);
  border-radius: var(--radius-sm);
  background: #ffffff;
  color: var(--ink-soft, #4c493f);
  cursor: pointer;
}
.nw-chat-attach:hover { border-color: var(--amber, #c98a3d); color: var(--amber, #c98a3d); }
/* R124 — photos queued under the composer, removable until the line goes. */
/* R163 — the strip needs its own air below the compose row: flush against it
   the freshly picked thumbnail read as part of the input, not as a queue. */
.nw-chat-pending { display: flex; flex-wrap: wrap; gap: 0.45rem; margin-top: 0.7rem; }
.nw-chat-pending[hidden] { display: none; }
.nw-chat-pending-thumb {
  position: relative;
  display: inline-block;
  width: 54px;
  height: 54px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: 1px solid var(--line, #e3ddd0);
}
.nw-chat-pending-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.nw-chat-pending-remove {
  position: absolute;
  top: 1px;
  right: 1px;
  width: 18px;
  height: 18px;
  border: 0;
  border-radius: 50%;
  background: rgba(33, 31, 26, 0.75);
  color: #ffffff;
  font-size: var(--fs-2xs);
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
/* R124 — photos inside a delivered bubble: click to enlarge in a new tab. */
.nw-chat-msg-imgs { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.3rem; }
.nw-chat-msg-imgs a { display: inline-block; line-height: 0; border-radius: 8px; overflow: hidden; border: 1px solid rgba(0, 0, 0, 0.08); }
.nw-chat-msg-imgs img { max-width: 180px; max-height: 140px; display: block; }
/* R124 — links inside a chat line: keep reading against both bubble colours. */
.nw-chat-msg-body a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.nw-chat-msg.is-theirs .nw-chat-msg-body a { color: var(--amber-dark, #a4681f); }
.nw-chat-compose input[type="text"] {
  flex: 1;
  min-width: 0;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--line, #e3ddd0);
  /* R137 — a text field, not a pill: matches the site's inputs. */
  border-radius: var(--radius-sm);
  font: inherit;
  background: #ffffff;
  color: var(--ink, #211f1a);
}
.nw-chat-note { margin: 0; padding: 0 0.9rem 0.8rem; font-size: var(--fs-2xs); color: var(--muted, #7a7669); }

/* chat.php — the continuation page reuses the same room styles inside a card */
.nw-chat-page-card .nw-chat-room { border: 1px solid var(--line, #e3ddd0); border-radius: var(--radius); overflow: hidden; }
.nw-chat-page-sub { color: var(--ink-soft, #4c493f); font-size: var(--fs-sm); }

@media (max-width: 560px) {
  /* R137 — phone: the panel becomes an edge-to-edge sheet. The old 360px
     floating box left ~1rem of dead margin on each side while the log and
     the composer were still squeezed to phone width; a sheet gives the
     conversation the full screen and keeps the thumb-reachable compose bar. */
  .nw-chat-panel {
    left: 0.75rem;
    right: 0.75rem;
    bottom: 5.25rem;
    width: auto;
    max-height: calc(100vh - 7.5rem);
  }
  .nw-chat-log { max-height: none; min-height: 160px; }
  .nw-chat-compose { padding-bottom: max(0.7rem, env(safe-area-inset-bottom)); }
}

/* R121 — the cookie notice is a fixed bottom bar (z-index 250) and would sit
   on top of the launcher. While the notice is visible the widget lifts above
   it; chat-widget.js removes the class the moment the notice goes away. */
.nw-chat.nw-chat-above-cookie .nw-chat-launcher { bottom: 6.5rem; }
.nw-chat.nw-chat-above-cookie .nw-chat-panel { bottom: 11rem; }

/* ==========================================================================
   R133/R134 — per-category dropdown menu effects (navmenu)
   --------------------------------------------------------------------------
   A top-level category may carry one of six preset dropdown styles, chosen
   per category in Admin → Categories. An EMPTY stored style renders the
   shipped panel above — none of the rules here touch it.

   The panels reuse the .dropdown surface and the .dropdown-link voice on
   purpose: themes repaint those two, so every generated theme dresses these
   panels for free. Only the LAYOUT is new. The third level of every style
   lists the level-3 categories AND the products filed under that level-2
   category, directories first.

   R134 — the whole block got quieter. The hairlines that split columns and
   rows are now soft tinted blocks with real gaps between them, every reveal
   runs 0.34-0.4s on the store's own ease curve instead of snapping in, and
   a closed panel stops taking clicks the moment it starts to fade (that
   invisible-but-live box is what made a wide panel pop back open while the
   visitor was reaching for the next menu).
   ========================================================================== */

/* --- shared skeleton ------------------------------------------------------ */
.navmenu {
  /* Neutral, low-alpha surfaces instead of the theme's --line. A linen
     hairline reads as a bright streak across frosted glass; these two greys
     sit correctly on the light sheet AND on Montserrat Noir's dark pane
     without ever becoming "a colour" of their own. One variable each, so a
     theme that wants warmer separators only has to repaint these. */
  --navmenu-rule: rgba(128, 128, 128, 0.22);
  --navmenu-block: rgba(128, 128, 128, 0.075);
  /* R136 — the colour a level-2 block turns while the pointer is on it: one
     step deeper than the resting wash, still a neutral grey on both sheets.
     R138e — the step was widened (0.16 → 0.20) after the block wash proved
     too close to its resting value to separate it from untouched siblings. */
  --navmenu-block-hover: rgba(128, 128, 128, 0.20);
  /* R138e — the ink a block's own heading takes while the pointer is on the
     block. Themes with a dark dropdown pane repaint this to white. */
  --navmenu-block-ink: var(--forest-900);

  /* R212 — the five values the full-bleed bar paints with, named once here so
     the bar can inherit whatever theme is active instead of guessing.
     A theme that repaints its dropdown pane (Montserrat Noir does: cream text
     on a dark brown glass) restates exactly this block of properties and the
     bar follows — no per-theme rule inside the bar itself.
     These are the LIGHT theme's answers: body ink for headings, the softer ink
     the other presets already use for links, the same grey wash for hover, and
     the same frosted pane .dropdown paints. On the dark theme all five are
     restated; see assets/css/themes/15-nunito-noir.css. */
  --navmenu-ink: var(--ink);
  --navmenu-ink-soft: var(--ink-soft);
  --navmenu-hover-bg: var(--navmenu-block-hover);
  --navmenu-hover-ink: var(--forest-900);
  --navmenu-surface: rgb(255 253 248 / var(--nav-menu-alpha, 92%));

  /* R215 — the panel's own CONTROLS are neutral by request.
     "下拉按钮继承的样式颜色背景颜色全部改成黑灰色系" — the two caret buttons and
     the two scroll arrows used to answer the pointer with the theme's amber
     film (`--navmenu-hover-bg`), which is exactly the accent the bars use: a
     control then read as decoration. They now run on a charcoal scale of their
     own, so the amber stays the panel's way of saying "this row is a
     destination" and the grey is its way of saying "this is a button".

     Nine values, because a control has two faces (a flat caret inside a row, a
     floating disc over the row) and each needs a rest and a pressed state.
     Light theme answers here; a theme whose pane is dark restates the same
     nine in assets/css/themes/*.css (gen_themes.py writes them). */
  --navmenu-btn-ink: #211f1a;               /* caret glyph, resting */
  --navmenu-btn-wash: rgb(33 31 26 / 10%);  /* caret hover: charcoal film */
  --navmenu-btn-wash-ink: #0d0c0a;          /* …and the glyph on top of it */
  --navmenu-btn-face: rgb(255 255 255 / 92%); /* the scroll arrow's disc */
  --navmenu-btn-face-ink: #211f1a;
  --navmenu-btn-face-hover: #2b2721;        /* pressed: solid charcoal */
  --navmenu-btn-face-hover-ink: #ffffff;
  --navmenu-btn-edge: rgb(33 31 26 / 14%);  /* the disc's hairline */
  --navmenu-scroll-thumb: rgb(33 31 26 / 30%); /* the bar's own scrollbar */
}

.navmenu .navmenu-body {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.navmenu .navmenu-item {
  position: relative;
  display: flex;
  flex-direction: column;
  /* R136 — the block wash crossfades to its deeper hover shade. */
  transition: background 0.34s var(--ease);
}

/* R136 — a level-2 block with its own background (grid, accordion-grid,
   mega) deepens while the pointer is over it, so the row the visitor is
   aiming at reads as "the one I am on" against its siblings. */
/* R138e — style 2 (three-column grid) was missing from this list: its blocks
   carried the resting wash but never deepened, so the row under the pointer
   was the only thing that moved and the block itself stayed indistinguishable
   from its neighbours. All three tinted styles now deepen together. */
.navmenu-grid-hover .navmenu-item:hover,
.navmenu-grid-hover .navmenu-item:focus-within,
.navmenu-accordion-grid .navmenu-item:hover,
.navmenu-accordion-grid .navmenu-item:focus-within,
.navmenu-mega .navmenu-item:hover,
.navmenu-mega .navmenu-item:focus-within {
  background: var(--navmenu-block-hover);
}

/* R138e — inside a tinted style the BLOCK is the highlight. The shared
   .dropdown-link:hover wash used to paint a second, brighter box on the row
   inside the block, which split one selection into two surfaces and made the
   block look barely touched. The row keeps its colour shift (strong ink, and
   white on a dark pane) and drops only the nested background. The heading is
   coloured from the ITEM's hover, not the row's, so the whole block reads as
   one selected surface even when the pointer is on its padding. */
.navmenu-grid-hover .navmenu-item:hover > .navmenu-row,
.navmenu-grid-hover .navmenu-item:focus-within > .navmenu-row,
.navmenu-accordion-grid .navmenu-item:hover > .navmenu-row,
.navmenu-accordion-grid .navmenu-item:focus-within > .navmenu-row,
/* R212 — in accordion-grid the row sits inside .navmenu-row-wrap now, so the
   direct-child form above no longer reaches it; the heading would have kept
   its resting ink while the block behind it went dark. */
.navmenu-accordion-grid .navmenu-item:hover > .navmenu-row-wrap > .navmenu-row,
.navmenu-accordion-grid .navmenu-item:focus-within > .navmenu-row-wrap > .navmenu-row,
.navmenu-mega .navmenu-item:hover > .navmenu-row,
.navmenu-mega .navmenu-item:focus-within > .navmenu-row {
  background: transparent;
  color: var(--navmenu-block-ink);
}

/* and its level-3 entries come up to full strength, so the selected block is
   legible as a whole rather than only at its heading */
.navmenu-grid-hover .navmenu-item:hover .navmenu-sub .sub-link,
.navmenu-grid-hover .navmenu-item:focus-within .navmenu-sub .sub-link,
.navmenu-accordion-grid .navmenu-item:hover .navmenu-sub .sub-link,
.navmenu-accordion-grid .navmenu-item:focus-within .navmenu-sub .sub-link,
.navmenu-mega .navmenu-item:hover .navmenu-sub .sub-link,
.navmenu-mega .navmenu-item:focus-within .navmenu-sub .sub-link {
  opacity: 1;
}

/* R134 — the panel itself opens more slowly than the shipped 0.22s, which
   reads as a snap once the surface is a full screen wide. */
/* R149 — no transition on the closed state: the panel disappears the same
   frame the pointer lands on the next nav button, and the new menu opens
   into fully frosted glass with no transparent-to-frosted beat. (The old
   `transition: opacity 0.4s …` here was exactly that beat: while opacity
   was still < 1 the panel was a backdrop root, so its frost could not see
   the page and only snapped on at the end of the fade.) The open glide is
   transform-only, declared on the hover state below. */
.dropdown.navmenu { /* closed: instant */ }

/* R134 — a closed panel must stop existing for the pointer the moment it
   starts to fade. `visibility` only flips at the END of the transition, so
   for those 0.4s an invisible panel is still hit-testable: the mega panel
   covers the viewport, swallows the click aimed at the next nav button — and
   even re-enters its own :hover, which is exactly the "it pops open by
   itself when I click another menu" report. pointer-events flips at once. */
.dropdown.navmenu,
.navmenu .navmenu-sub.dropdown { pointer-events: none; }

.has-dropdown:hover > .dropdown.navmenu,
.has-dropdown:focus-within > .dropdown.navmenu,
.navmenu .navmenu-item:hover > .navmenu-sub.dropdown,
.navmenu .navmenu-item:focus-within > .navmenu-sub.dropdown {
  pointer-events: auto;
  /* R149 — transform-only glide on the way in (works for both the 8px rise
     of a top-level panel and the flyout's sideways slide); closing reads no
     transition from the closed state and is instant. */
  transition: transform 0.28s var(--ease);
}

/* R212 — in the two click-to-expand presets the row is a LINK and the caret
   is a separate button beside it, so the category page stays one click away
   now that the "All <name>" row is gone. Same 2-column grid as the shipped
   groups: the link takes the width, the button takes its own hit area. */
.navmenu .navmenu-row-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: stretch;
}

.navmenu .navmenu-expand {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.4rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: none;
  /* R215 — neutral ink, and a charcoal film while pressed; see the token block
     on .navmenu. The amber the row's own hover uses is not repeated here. */
  color: var(--navmenu-btn-ink, var(--navmenu-ink));
  font: inherit;
  cursor: pointer;
  transition: background 0.34s var(--ease), color 0.34s var(--ease);
}
.navmenu .navmenu-expand:hover,
.navmenu .navmenu-expand:focus-visible {
  background: var(--navmenu-btn-wash, rgba(128, 128, 128, 0.16));
  color: var(--navmenu-btn-wash-ink, var(--navmenu-ink));
}
.navmenu .navmenu-expand:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: -2px;
}

/* the level-2 row: same voice as .dropdown-link, plus the caret slot */
.navmenu .navmenu-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  transition: background 0.34s var(--ease), color 0.34s var(--ease), padding 0.34s var(--ease);
}

.navmenu .navmenu-caret {
  flex: none;
  font-size: 0.7em;
  line-height: 1;
  opacity: 0.7;
  transition: transform 0.34s var(--ease), opacity 0.34s var(--ease);
}

/* the unfold mechanism — a 0fr grid row collapses to a true zero and
   animates to the list's natural height (same trick as the shipped groups) */
.navmenu .navmenu-sub {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  visibility: hidden;
  transition: grid-template-rows 0.4s var(--ease), opacity 0.34s var(--ease), visibility 0.4s;
}

.navmenu .navmenu-item.is-open > .navmenu-sub {
  grid-template-rows: 1fr;
  opacity: 1;
  visibility: visible;
}

/* R212 — the caret lives in the expand button, not inside the row any more,
   so the open state is matched on the item rather than on `.navmenu-row`. */
.navmenu .navmenu-item.is-open .navmenu-caret {
  transform: rotate(180deg);
  opacity: 1;
}

.navmenu .navmenu-sub-clip {
  overflow: hidden;
  min-height: 0;
}

.navmenu .navmenu-sub-list {
  margin: 0.1rem 0 0.35rem 0.95rem;
  padding-left: 0.55rem;
  /* the guide rule stays a rule — but a whisper of one, not the theme line */
  border-left: 1px solid var(--navmenu-rule);
}

.navmenu .navmenu-sub .sub-link {
  padding: 0.4rem 0.7rem;
  font-size: var(--fs-xs);
  white-space: nowrap;
  opacity: 0.78;
  transition: background 0.34s var(--ease), color 0.34s var(--ease),
              opacity 0.34s var(--ease), padding 0.34s var(--ease);
}


/* the guide rule already marks the level, as in the shipped panel */
.navmenu .navmenu-sub .sub-link::before { display: none; }

/* --- style 1 · flyout: vertical list, third level slides out right -------- */
/* The third level IS a .dropdown surface, so themes paint it for free; the
   deeper rules below take the reveal away from the shared `.has-dropdown:
   hover .dropdown` pair (which would otherwise open every sub at once) and
   hand it to each item instead. */
.navmenu-flyout .navmenu-item.has-sub > .navmenu-row { padding-right: 2.1rem; }

.navmenu-flyout .navmenu-item.has-sub > .navmenu-sub.dropdown {
  left: calc(100% + 14px);
  top: -0.45rem;
  min-width: 230px;
  /* a fly-out is a panel of its own: the list is never clipped, the whole
     panel fades (unlike the inline styles, which grow a 0fr row) */
  grid-template-rows: 1fr;
  transform: translateX(-10px);
  opacity: 0;
  visibility: hidden;
  /* R149 — no transition here (closed = instant); the hover state below
     carries the transform glide, and opacity snaps on the first frame so
     the flyout's frost never shows the transparent-to-frosted beat. */
}

/* 14px transparent bridge over the gap, so the pointer can travel across */
.navmenu-flyout .navmenu-item.has-sub > .navmenu-sub.dropdown::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: -14px;
  width: 14px;
}

.navmenu-flyout .navmenu-item.has-sub:hover > .navmenu-sub.dropdown,
.navmenu-flyout .navmenu-item.has-sub:focus-within > .navmenu-sub.dropdown {
  transform: translateX(0);
  opacity: 1;
  visibility: visible;
  transition: transform 0.3s var(--ease);
}

/* --- style 2 · 3-column grid, hover expands the third level underneath ---- */
/* R134 — columns are separated by GAPS between soft blocks, never by a drawn
   line: each level-2 directory (heading + its list) sits on its own barely
   there wash of grey, which reads as a region without ever shouting. */
.navmenu-grid-hover .navmenu-body,
.navmenu-accordion-grid .navmenu-body {
  display: grid;
  grid-template-columns: repeat(var(--navmenu-cols, 3), minmax(0, 1fr));
  grid-auto-columns: minmax(0, 1fr);
  gap: 0.55rem 0.7rem;
  /* R137 — STRETCH, not start. When one block unfolds its third level the
     row grows to that block's height; with `start` the two neighbouring
     blocks stopped at their own height and left a bare hole in the panel on
     both sides. Stretched, every block in a row shares the row's height, so
     the panel stays a clean card grid and — just as usefully — moving the
     pointer between two blocks of the same row no longer resizes anything. */
  align-items: stretch;
  align-content: start;
}

/* R134 — "N rows per column": the grid fills DOWN each column first, so the
   column count follows ceil(items / rows) (computed in PHP and handed over as
   --navmenu-cols). Without that class the layout is byte-identical to R133. */
.navmenu-rows.navmenu-grid-hover .navmenu-body,
.navmenu-rows.navmenu-accordion-grid .navmenu-body {
  grid-template-rows: repeat(var(--navmenu-rows, 3), auto);
  grid-auto-flow: column;
}

.navmenu-grid-hover .navmenu-item,
.navmenu-accordion-grid .navmenu-item {
  padding: 0.3rem 0.35rem 0.45rem;
  /* R136 — the block takes the merchant's Corner rounding as-is (the old
     +7px bump made one slider unable to shape the whole panel). */
  border-radius: var(--radius);
  background: var(--navmenu-block);
}

/* transparent bridge over the gap between the row and its list */
.navmenu-grid-hover .navmenu-sub { margin-top: 0.45rem; }
.navmenu-grid-hover .navmenu-sub::before {
  content: '';
  position: absolute;
  top: -10px;
  left: 0;
  right: 0;
  height: 10px;
}

.navmenu-grid-hover .navmenu-item.has-sub:hover > .navmenu-sub,
.navmenu-grid-hover .navmenu-item.has-sub:focus-within > .navmenu-sub {
  grid-template-rows: 1fr;
  opacity: 1;
  visibility: visible;
}

/* --- style 3 · vertical list, third level always shown -------------------- */
.navmenu-list-static .navmenu-row { font-weight: 600; }

.navmenu-list-static .navmenu-sub {
  grid-template-rows: 1fr;
  opacity: 1;
  visibility: visible;
}

/* --- style 4 · vertical accordion (click to expand) ----------------------- */
.navmenu-accordion .navmenu-row { font-weight: 600; }

/* --- style 5 · 3-column accordion (click to expand) ----------------------- */
.navmenu-accordion-grid .navmenu-row { font-weight: 600; }

/* --- style 6 · full-width mega menu, third level always shown ------------- */
/* R134 — the GLASS spans the viewport, the CONTENT does not. The panel keeps
   width:100vw (main.js lines its left edge up with the nav button), but the
   head row and the column grid are pinned to the page's own container width
   and gutters, so the menu reads as part of the page instead of a strip of
   text glued to the far left screen edge. */
.navmenu-mega {
  left: 0;
  width: 100vw;
  max-width: none;
  padding: 0.6rem 0 1rem;
}

.navmenu-mega .navmenu-body {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 2.5rem);
}

.navmenu-mega .navmenu-body {
  display: grid;
  grid-template-columns: repeat(var(--navmenu-cols, 4), minmax(0, 1fr));
  grid-auto-columns: minmax(0, 1fr);
  gap: 0.55rem 0.7rem;
  align-items: start;
}

/* R134 — same rows-per-column switch as the grid styles */
.navmenu-rows.navmenu-mega .navmenu-body {
  grid-template-rows: repeat(var(--navmenu-rows, 4), auto);
  grid-auto-flow: column;
}

/* R135 — BALANCED COLUMNS. The grid above fills row by row, so a category
   whose neighbours are taller drags its whole row down and a remainder row
   leaves a dead strip across the bottom of the panel — very visible when
   directories carry very different numbers of products. When JS is present
   main.js re-deals the items into per-column flex stacks, choosing the
   column splits that keep the tallest column as short as possible (a small
   sequential-partition search over the REAL rendered heights), and stretches
   each column's last block over whatever slack is left, so every column
   ends on the same baseline. Without JS nothing changes: the grid below is
   the fallback, and the media queries further down keep governing it.
   Column counts for the balanced path are chosen in main.js and MUST stay
   in sync with those media queries. */
.navmenu-mega .navmenu-body.is-balanced {
  display: flex;
  flex-direction: row; /* the base .navmenu-body is a column; win it back */
  align-items: stretch;
  gap: 0 0.7rem;
}

.navmenu-mega .navmenu-body.is-balanced .navmenu-col {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

/* the slack each column has left over goes to its LAST block: the soft
   background stretches, the contents stay at the top, and the panel ends
   on one straight bottom line instead of a staircase. */
.navmenu-mega .navmenu-body.is-balanced .navmenu-item:last-child {
  flex: 1 0 auto;
}

/* each level-2 directory is its own soft block; the gaps ARE the dividers */
.navmenu-mega .navmenu-item {
  padding: 0.3rem 0.35rem 0.45rem;
  /* R136 — Corner rounding, same as every other dropdown block. */
  border-radius: var(--radius);
  background: var(--navmenu-block);
}

/* the third level is printed, never folded */
.navmenu-mega .navmenu-sub {
  grid-template-rows: 1fr;
  opacity: 1;
  visibility: visible;
}

/* the level-2 hover: a 3px indicator bar fades up from centre height while
   the row breathes a few pixels to the right. R134 — the bar is a whisper of
   the ink (28%), not the accent: at the old weight it read as a bright chip
   stuck to the left of every heading. */
.navmenu-mega .navmenu-row {
  padding: 0.5rem 0.7rem 0.5rem 1rem;
}

.navmenu-mega .navmenu-row::before {
  left: 0;
  top: 0.4rem;
  bottom: 0.4rem;
  height: auto;
  margin-top: 0;
  width: 3px;
  border-radius: 3px;
  background: currentColor;
  opacity: 0;
  transform: scaleY(0);
  transition: opacity 0.34s var(--ease), transform 0.34s var(--ease);
}

.navmenu-mega .navmenu-row:hover,
.navmenu-mega .navmenu-row:focus-visible { padding-left: 1.35rem; }

.navmenu-mega .navmenu-row:hover::before,
.navmenu-mega .navmenu-row:focus-visible::before { opacity: 0.28; }

/* the level-3 entries: a 4px dot, the text takes the strong ink, and the row
   breathes from 1.9rem to 2.2rem */
.navmenu-mega .navmenu-sub-list {
  margin: 0.1rem 0 0.3rem;
  padding-left: 0;
  border-left: 0;
}

.navmenu-mega .navmenu-sub .sub-link {
  position: relative;
  padding: 0.4rem 0.6rem 0.4rem 1.9rem;
}

.navmenu-mega .navmenu-sub .sub-link::before {
  display: block;
  content: '';
  position: absolute;
  left: 1.2rem;
  top: 50%;
  width: 4px;
  height: 4px;
  margin-top: -2px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0;
  transform: scale(0);
  transition: opacity 0.34s var(--ease), transform 0.34s var(--ease);
}

.navmenu-mega .navmenu-sub .sub-link:hover,
.navmenu-mega .navmenu-sub .sub-link:focus-visible { padding-left: 2.2rem; }

.navmenu-mega .navmenu-sub .sub-link:hover::before,
.navmenu-mega .navmenu-sub .sub-link:focus-visible::before {
  opacity: 0.42;
  transform: scale(1);
}

/* responsive column count for the mega panel: 4 -> 3 -> 2 -> 1. The literal
   counts win over the --navmenu-cols default, so a merchant's rows setting
   can never squeeze four columns onto a phone.
   R134 — and once the breakpoint drops the column count, the row count is
   abandoned too: 8 entries at "3 rows" would otherwise overflow into a
   FOURTH implicit column on a 2-column screen. Below 1200px the panel simply
   flows left to right in 3, 2 or 1 column — the promise is the column count,
   and it holds at every width. */
@media (max-width: 1200px) {
  .navmenu-mega .navmenu-body,
  .navmenu-rows.navmenu-mega .navmenu-body {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-rows: none;
    grid-auto-flow: row;
  }
}
@media (max-width: 860px) {
  .navmenu-mega .navmenu-body,
  .navmenu-rows.navmenu-mega .navmenu-body {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 560px) {
  .navmenu-mega .navmenu-body,
  .navmenu-rows.navmenu-mega .navmenu-body { grid-template-columns: 1fr; }
}

/* --- style 7 · full-bleed bar, one column per category, scrolls sideways --- */
/* R211 — the merchant's brief, in their words: a full-width menu that shows
   EVERY directory at once, laid out in columns side by side, each column
   holding that level-2 category's level-3 directories and its products. The
   columns never wrap — when the window is narrower than the row, the bar
   scrolls left and right instead of dropping to a second line.
   Size hierarchy is the point: the category heading and the entries beneath it
   are separate knobs in Admin → Styles (--nw-fullbar-l1 / -l2), so "primary"
   and "secondary" can never drift back to looking the same.

   R212 — three corrections, after the merchant used it on the live dark theme:
   1. NO GUESSED COLOURS. The first cut fell back to `inherit`, which on a dark
      pane resolves to the LIGHT theme's body ink: near-black text on dark
      brown, i.e. a bar nobody can read. Every colour here now reads a
      --navmenu-* property the ACTIVE THEME declares (see `.navmenu` above), so
      the bar is legible on the cream pane and on Montserrat Noir's brown glass
      without a single per-theme rule in this block.
   2. NO ALPHA-ON-TEXT. The two lower levels used opacity (0.82 / 0.68) on top
      of an already soft ink — on the cream pane that lands near 2.6:1, under
      any readable floor. Hierarchy is weight + size now; every link sits at
      full opacity.
   3. NO SNAP SCROLL. `scroll-snap-type: x proximity` fired at the end of a
      trackpad flick and yanked the row to the nearest column edge, which reads
      as "the menu jumped by itself". The bar scrolls freely; the arrows and a
      visible scrollbar are the affordances instead.
*/
.dropdown.navmenu-fullbar {
  left: 0;
  width: 100vw;
  max-width: none;
  padding: 0;
  border-radius: 0;
  border-left: 0;
  border-right: 0;
  /* R212 — NO background declaration here, on purpose.
     The bar IS the .dropdown panel (one element, two classes), so "inherit the
     theme's surface" cannot be written as `background: inherit` — there is
     nothing above it to inherit from. Leaving the property alone is what makes
     the theme show through: Montserrat Noir paints a dark brown glass, the
     built-in theme a cream one, and this rule never has to know which.
     The merchant's "通栏底色" is emitted from PHP instead, as a rule of its own
     that loads after the theme (see nw_ui_css) — so an explicit choice wins on
     every theme, and no choice changes nothing. */
}


/* the positioned box the arrows hang off; the scroller itself must NOT be the
   positioning context, or the arrows would scroll away with the content.
   R212 — the side gutters live on the WRAPPER, not on the scroller. That is
   what keeps the arrows off the text: the scrolling box starts and ends
   inside the gutters, so a column is clipped at the gutter's edge and the
   arrow always has empty space of its own to sit in. */
.navmenu-fullbar .navmenu-scrollwrap {
  --wfb-gutter: clamp(2.75rem, 5vw, 3.5rem);
  position: relative;
  padding: 0 var(--wfb-gutter);
}

/* R212 — a soft edge fade where the row is cut off. It starts exactly at the
   scroller's edge and fades inward, so the column that is half out of view
   dissolves instead of stopping mid-letter. Each side only appears when there
   is somewhere left to scroll: at the start nothing is dimmed on the left. */
.navmenu-fullbar .navmenu-scrollwrap::before,
.navmenu-fullbar .navmenu-scrollwrap::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 3;
  width: 3.5rem;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.24s var(--ease);
}
.navmenu-fullbar .navmenu-scrollwrap::before {
  left: var(--wfb-gutter);
  background: linear-gradient(to right, var(--navmenu-surface), transparent);
}
.navmenu-fullbar .navmenu-scrollwrap::after {
  right: var(--wfb-gutter);
  background: linear-gradient(to left, var(--navmenu-surface), transparent);
}
.navmenu-fullbar .navmenu-scrollwrap.has-overflow:not(.is-at-start)::before { opacity: 1; }
.navmenu-fullbar .navmenu-scrollwrap.has-overflow:not(.is-at-end)::after { opacity: 1; }

.navmenu-fullbar .navmenu-body {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;              /* the row is the row: it scrolls, not wraps */
  align-items: flex-start;
  gap: 0;
  width: 100%;
  max-height: min(72vh, 640px);
  overflow-x: auto;
  overflow-y: auto;
  /* R212 — no side padding here: the gutters belong to the wrapper, so the
     scrolling box itself starts and ends inside them. */
  /* R214 — the bottom padding was 1.05rem, sized to leave the 9px scrollbar a
     strip of its own. R215 puts the bar back into that strip (it is only
     visible on hover now), so the padding gives it the room again and the
     panel's total height stays where R214 left it. */
  padding: 0.35rem 0 0.15rem;
  /* R212 — free scrolling. No snap points: a snap fired at the tail of a
     trackpad flick and moved the row on its own, which is exactly the
     "误操作" the merchant reported. */
  overscroll-behavior-x: contain;   /* a sideways flick stays in the bar */
  -webkit-overflow-scrolling: touch;
  /* R215 — the scrollbar shows itself only while the pointer is over the bar.
   *
   * The strip is still RESERVED at all times, which is the whole trick: a
   * scrollbar whose `height` goes from 0 to 9px on hover changes the content
   * box and pushes the last row of product names up by 9px — the panel would
   * twitch every time a mouse crossed it. So the strip is always 9px and only
   * the THUMB changes paint, transparent at rest and grey on hover. Nothing
   * moves; a bar appears.
   *
   * Firefox cannot fade a thumb, so `scrollbar-color` does the same job with
   * `transparent` for both track and thumb, and the hover rule repaints it.
   * Chromium/Safari go through the pseudo-elements below. */
  scrollbar-width: thin;
  scrollbar-color: transparent transparent;
}
.navmenu-fullbar .navmenu-body:hover,
.navmenu-fullbar .navmenu-body:focus-within {
  scrollbar-color: var(--navmenu-scroll-thumb, rgba(33, 31, 26, 0.3)) transparent;
}
/* The 9px strip exists whether or not the thumb is painted: `height` stays
   fixed here, only the thumb's background flips from transparent to grey. */
.navmenu-fullbar .navmenu-body::-webkit-scrollbar {
  width: 0;
  height: 9px;
}
.navmenu-fullbar .navmenu-body::-webkit-scrollbar-track { background: transparent; }
.navmenu-fullbar .navmenu-body::-webkit-scrollbar-thumb {
  background: transparent;
  border-radius: 999px;
}
.navmenu-fullbar .navmenu-body:hover::-webkit-scrollbar-thumb,
.navmenu-fullbar .navmenu-body:focus-within::-webkit-scrollbar-thumb {
  background: var(--navmenu-scroll-thumb, rgba(33, 31, 26, 0.3));
}

/* R214 — the columns sit in the middle of the bar.
 *
 * `justify-content: center` is the obvious answer and the wrong one: on a
 * scrolling flex container it centres the OVERFLOW too, so when the row is
 * wider than the screen the first column ends up at a negative offset that
 * scrollLeft can never reach — the leftmost category becomes unclickable.
 *
 * Auto margins on the two end items do the same job with none of that: free
 * space is handed to the margins when there is any (row narrower than the bar →
 * centred), and collapses to zero when there is not (row wider → starts at 0
 * and every column stays reachable). No measurement, no JS, no magic offset —
 * it keeps working when a column count or a font size changes. */
.navmenu-fullbar .navmenu-body > .navmenu-item:first-child { margin-left: auto; }
.navmenu-fullbar .navmenu-body > .navmenu-item:last-child { margin-right: auto; }
/* R212 — while a mouse drag is in progress the whole surface is a handle: no
   text selection, no text cursor, no link hit-testing. JS removes the class on
   pointerup and swallows the one click that follows. */
.navmenu-fullbar .navmenu-body.is-dragging {
  cursor: grabbing;
  user-select: none;
  -webkit-user-select: none;
}
/* R212 — an anchor is draggable by default, and the native link drag (the
   ghost thumbnail) cancels our scroll the moment the pointer travels with the
   button down. Turning that off is what lets a drag across a link scroll the
   row instead of dragging the link's URL around. */
.navmenu-fullbar .navmenu-body a {
  -webkit-user-drag: none;
  user-drag: none;
}

/* R212 — the two arrows. Pointer affordance, not a tab stop: tabindex="-1" in
   the markup keeps Tab moving through the links only, so a keyboard visitor
   never lands on a control that just scrolls. */
.navmenu-fullbar .navmenu-scroll-btn {
  position: absolute;
  top: 50%;
  z-index: 4;
  display: none;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  transform: translateY(-50%);
  /* R215 — a neutral disc instead of the warm frosted surface: white (or, on a
     dark theme, near-black) with a grey hairline, and a solid charcoal fill
     while pressed. It is a control, not a fragment of the panel, so it no
     longer wears the panel's tint. */
  border: 1px solid var(--navmenu-btn-edge, var(--navmenu-rule));
  border-radius: 999px;
  background: var(--navmenu-btn-face, var(--navmenu-surface));
  color: var(--navmenu-btn-face-ink, var(--navmenu-ink));
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.16);
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  backdrop-filter: blur(8px) saturate(140%);
  cursor: pointer;
  transition: opacity 0.2s var(--ease), background 0.2s var(--ease), color 0.2s var(--ease);
}
.navmenu-fullbar .navmenu-scrollwrap.has-overflow .navmenu-scroll-btn { display: flex; }
.navmenu-fullbar .navmenu-scroll-btn.is-prev { left: 0.4rem; }
.navmenu-fullbar .navmenu-scroll-btn.is-next { right: 0.4rem; }
/* At either end the arrow fades out instead of disappearing: the button keeps
   its place, so the row behind it never reflows mid-scroll. */
.navmenu-fullbar .navmenu-scroll-btn[disabled] {
  opacity: 0;
  pointer-events: none;
}
.navmenu-fullbar .navmenu-scroll-btn:hover,
.navmenu-fullbar .navmenu-scroll-btn:focus-visible {
  background: var(--navmenu-btn-face-hover, #2b2721);
  color: var(--navmenu-btn-face-hover-ink, #ffffff);
}

/* one column per category; a hairline between them, never a wrap */
.navmenu-fullbar .navmenu-item {
  flex: 0 0 auto;
  /* R215 — the column is as wide as its longest name.
   *
   * A fixed width (`clamp(196px, 16vw, 248px)` until R214) meant a long product
   * name wrapped onto a second line, and a wrapped name in a list of single-line
   * names reads as a paragraph wedged between labels — the merchant's "列宽太窄，
   * 导致文字换行". A width that follows the content removes the problem at its
   * source: every row is one line (white-space: nowrap below), and the column
   * grows to hold the longest of them instead of squeezing it.
   *
   * `min-width` keeps the short columns from collapsing into a ragged ribbon, so
   * a directory with three-word children still reads as a column rather than a
   * narrow slip. There is deliberately no max-width: a cap would reintroduce the
   * wrap it was meant to avoid, and the bar scrolls sideways by design. */
  width: max-content;
  min-width: clamp(208px, 16vw, 252px);
  padding: 0.5rem 1.5rem 0.7rem 0;
  margin-right: 1.5rem;
  border-right: 1px solid var(--nw-fullbar-rule, var(--navmenu-rule));
  /* R217 — the whole column is the hover target. R214 only lit the heading
     row, which left the level-3 names looking unattached to the directory
     they belong to; the merchant asked for the BLOCK to deepen, so the
     transition and the paint live on the item now. */
  border-radius: var(--radius);
  transition: background 0.18s var(--ease), border-color 0.18s var(--ease);
}
/* R217 — pointer over a directory: the block deepens one step past the bar's
   own surface and the divider beside it steps out of the way, so the column
   reads as one raised card rather than a tinted strip behind a hairline. */
.navmenu-fullbar .navmenu-item:hover,
.navmenu-fullbar .navmenu-item:focus-within {
  background: var(--nw-fullbar-hover-bg, var(--navmenu-block-hover));
  border-right-color: transparent;
}
.navmenu-fullbar .navmenu-item:last-child {
  border-right: 0;
  margin-right: 0;
}

/* the level-2 heading: the same size as the names under it by request (R218),
   told apart by weight, ink and the amber bar — never by a second size ramp */
.navmenu-fullbar .navmenu-row {
  font-size: var(--nw-fullbar-l1, 0.85rem);
  font-weight: 700;
  line-height: 1.4;
  padding: 0.4rem 0.5rem;
  /* R215 — no negative side margins. The row and the level-3 links below it
     used to overhang the column by 8px on each side so their hover pills looked
     wider; `.navmenu-sub-clip` is `overflow:hidden`, so the overhang was clipped
     and `max-content` under-measured the column by the same 8px. The longest
     product names were then CUT OFF by 4–8px instead of wrapping — the fix for
     "不换行" had produced a worse symptom. Both levels now start at the column
     edge, which also puts their hover pills on one clean vertical. */
  margin: 0 0 0.3rem;
  border-radius: var(--radius);
  color: var(--nw-fullbar-fg, var(--navmenu-ink));
  /* R215 — one line, always. A directory name that wrapped under its own accent
     bar put the bar against the second line and made the column look broken. */
  white-space: nowrap;
  /* R214/R215 — room for the accent bar, which sits at the column's left edge
     (see ::before). 1.5rem = 24px puts the words on the same vertical as the
     level-3 names, leaving the 3px bar a 21px gap — it reads as a marker, not
     the first letter of the heading. The row starts at the column edge now, so
     this padding is the whole distance (no negative margin to subtract). */
  padding-left: 1.5rem;
}
/* R214 — the amber rule in front of every level-2 directory name.
   The merchant asked for it so a column reads as "a section with a heading"
   rather than "a slightly bolder line that happens to be first". It uses the
   brand amber rather than a text colour on purpose: it must stay the same
   accent on the cream theme and on the dark one, where the ink flips but the
   brand does not. `top: 50%` + a negative half-height keeps it vertically
   centred on the first line of a heading that may wrap to two. */
.navmenu-fullbar .navmenu-row::before {
  content: '';
  position: absolute;
  left: 0;                   /* R215 — the column's left edge; the row no longer overhangs */
  top: 50%;
  width: 3px;
  height: 1.05em;
  margin-top: -0.525em;
  border-radius: 999px;
  background: var(--amber);
  /* R214 — `.dropdown-link::before` already exists: it is the shared "this is a
     link" 2px tick, drawn in currentColor but held at `opacity: 0` and squeezing
     to 35% height until the row is hovered. Re-declaring the geometry without
     those two properties inherits the invisible starting state, which is exactly
     how the first attempt at this bar rendered nothing at all. */
  /* R217 — the block behind the heading already carries the hover paint (see
     .navmenu-item above), so the row itself stays transparent: two stacked
     washes would make the heading darker than the names underneath it and
     break the block back into two surfaces. */
  opacity: 1;
  transform: none;
  transition: transform 0.18s var(--ease), opacity 0.18s var(--ease);
}
/* R218 — the bar is a permanent marker again. R217 had it at rest as a 60%
   tick at 65% opacity so the hover could "draw it out"; on the live pane that
   read as "橙色小竖条没加" — a 3px sliver at two thirds of a rem is not a
   marker, it is a smudge. Solid at rest, full cap height, and the interaction
   is carried by WIDTH plus the block's own deepening (4px on hover), which
   cannot be mistaken for an artifact and does not move the words: the bar is
   absolutely positioned in the row's 24px gutter, so nothing reflows. */
.navmenu-fullbar .navmenu-row::before {
  width: 3px;
  opacity: 1;
  transform: none;
  transition: width 0.16s var(--ease), opacity 0.16s var(--ease);
}
.navmenu-fullbar .navmenu-item:hover .navmenu-row::before,
.navmenu-fullbar .navmenu-item:focus-within .navmenu-row::before {
  width: 4px;
  opacity: 1;
}
/* R217 — the heading answers the block, not just its own row. */
.navmenu-fullbar .navmenu-item:hover > .navmenu-row,
.navmenu-fullbar .navmenu-item:focus-within > .navmenu-row,
.navmenu-fullbar .navmenu-row:hover,
.navmenu-fullbar .navmenu-row:focus-visible {
  background: transparent;
  color: var(--nw-fullbar-hover-fg, var(--navmenu-hover-ink));
}

/* the level-3 list is printed, never folded, and never opened as a fourth
   level — the markup for this style stops at the products on purpose */
.navmenu-fullbar .navmenu-sub {
  grid-template-rows: 1fr;
  opacity: 1;
  visibility: visible;
}
.navmenu-fullbar .navmenu-sub-list {
  margin: 0;
  padding: 0;
  border-left: 0;
}

/* the level-3 entries and product names: the smaller size, dense enough to
   scan but with a line box tall enough that two-line names do not collide */
.navmenu-fullbar .navmenu-sub .sub-link {
  display: block;
  /* R215 — 1.5rem of left padding puts the words 24px inside the column edge,
     on the same vertical as the heading, with the hover dot in the gutter.
     The old `margin: 0 -0.5rem` overhang is gone: the clip cut it away and
     `max-content` then under-sized the column, cropping the longest names. */
  padding: 0.36rem 0.55rem 0.36rem 1.5rem;
  margin: 0;
  border-radius: var(--radius);
  font-size: var(--nw-fullbar-l2, 0.85rem);
  line-height: 1.55;
  /* R215 — one line, by request. A name is a name: it is not a paragraph, and a
     wrapped one broke the rhythm of the list (and, at the old fixed width, most
     of them wrapped). The column now sizes itself to the longest name, so
     nothing overflows and nothing is cut off. */
  white-space: nowrap;
  color: var(--nw-fullbar-sub, var(--navmenu-ink-soft));
  /* R212 — full opacity. The old 0.82 / 0.68 stacked on an already soft ink
     and pushed the products down to roughly 2.6:1 on the cream pane. */
  opacity: 1;
}
/* R214 — the level-3 rows light up under the pointer.
 *
 * This is the interaction the merchant asked to borrow from the 3-column grid
 * style ("只参考交互效果"): in that style every level-2 block sits on a wash and
 * the third level answers the pointer, so the panel tells you what is
 * clickable before you click it. The bar had the same rule already, but it was
 * answered by the theme's generic grey — `--navmenu-hover-bg`, i.e.
 * `rgba(128,128,128,0.20)` — which on a cream pane is roughly 4 levels out of
 * 255 different from the pane itself. Visible in a diff, invisible in use.
 *
 * So the bar's level-3 rows get their own answer: a brand-amber film plus a 2px
 * rule at the left edge of the hovered row. The amber is what the level-2
 * accent bar is drawn in, so the two states read as one system, and it holds up
 * on the dark theme too (amber over dark brown is as clear as amber over
 * cream). The merchant's own "Menu bar — hover background" token still wins
 * where it is set; only the theme's generic grey is dropped here.
 *
 * 0.16s instead of the sheet's 0.34s: this is a pointer tracking a row, not a
 * transition someone is supposed to watch. A slow fade under a moving mouse
 * reads as lag. */
.navmenu-fullbar .navmenu-sub .sub-link {
  transition: background 0.16s var(--ease), color 0.16s var(--ease),
              box-shadow 0.16s var(--ease);
}
.navmenu-fullbar .navmenu-sub .sub-link:hover,
.navmenu-fullbar .navmenu-sub .sub-link:focus-visible {
  /* R217 — the row under the pointer deepens once more on top of the block it
     sits in (the two translucent layers compound), so "the directory I am in"
     and "the name I am about to click" are two distinguishable shades of the
     same grey instead of two different accents. The merchant's own hover
     colour still wins; only the theme's amber film is dropped here. */
  background: var(--nw-fullbar-hover-bg, rgba(0, 0, 0, 0.16));
  color: var(--nw-fullbar-hover-fg, var(--navmenu-hover-ink));
}
/* R215 — the accent at the left of the hovered row is a DOT, not a rule.
 *
 * R214 drew a 2px bar here and put it on the clip's edge — which was also
 * where the words started, because the row's own left padding cancelled its
 * negative margin to zero. A marker drawn on top of the first letter of the
 * text is what the merchant saw as "紧贴在了文字上，很混乱".
 *
 * Two changes fix that properly rather than by nudging a number:
 *   1. the row keeps 24px of left padding (see the base rule), so the words
 *      start well inside the pill and there is a gutter to put a marker in;
 *   2. the marker is a 5px disc, centred in that gutter — it reads as a bullet
 *      on the row being pointed at, and it cannot collide with a glyph on any
 *      theme, whatever the ink.
 *
 * Geometry (measured in a real render): the row starts at the column edge, so
 * `left: 0.85rem` (13.6px) puts the 5px disc 13.6px in, ending at 18.6px;
 * the words begin at 24px — a 5.4px gap between dot and text.
 *
 * It is a ::before rather than an `inset` box-shadow because R214 measured
 * that an inset shadow painted nothing on the old overhanging row. A disc also
 * cannot be drawn by a shadow at all: a shadow is a straight edge, and the
 * merchant asked for a dot. */
.navmenu-fullbar .navmenu-sub .sub-link::before {
  content: '';
  display: block;              /* the shared `.navmenu-sub .sub-link::before` is display:none */
  position: absolute;
  left: 0.85rem;
  top: 50%;
  width: 5px;
  height: 5px;
  margin-top: -2.5px;
  border-radius: 50%;
  background: var(--amber);
  opacity: 0;
  transform: none;             /* inherits the shared tick's scaleY(0.35) otherwise */
  transition: opacity 0.14s var(--ease);
}
.navmenu-fullbar .navmenu-sub .sub-link:hover::before,
.navmenu-fullbar .navmenu-sub .sub-link:focus-visible::before {
  opacity: 1;
}
/* products are names, not directories: they step back by WEIGHT, not by
   fading — a read product name is worth more than a tidy ramp */
.navmenu-fullbar .navmenu-sub .navmenu-product {
  font-weight: 400;
}

@media (prefers-reduced-motion: reduce) {
  .navmenu,
  .navmenu *,
  .navmenu *::before,
  .navmenu *::after {
    transition-duration: 0.001ms !important;
    animation-duration: 0.001ms !important;
  }
}


/* R135 — the head inline script adds this class before first paint when
   either client-side mirror says the visitor already agreed, so a page
   delivered from a cache (or any other stale copy) never flashes the
   notice back on. */
.nw-consent-given #cookie-notice { display: none !important; }

/* ==========================================================================
   R144 — customer product reviews & ratings.
   Same tokens as the rest of the sheet: amber for stars, --line for the
   empty ones, --forest-800 ink, --sand panels. Nothing new to theme.
   ========================================================================== */
.product-rating {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0.35rem 0 0.6rem;
}
.stars {
  position: relative;
  display: inline-block;
  font-size: var(--fs-sm);
  letter-spacing: 0.12em;
  line-height: 1;
  color: var(--line);
}
.stars-fill {
  position: absolute;
  top: 0;
  left: 0;
  overflow: hidden;
  white-space: nowrap;
  color: var(--amber);
}
.product-rating .rating-num { font-weight: 700; color: var(--forest-800); }
.product-rating .rating-count { font-size: var(--fs-sm); color: var(--forest-600); }
.product-rating .rating-count:hover { color: var(--forest-900); }

.product-reviews { padding: 3.5rem 0; border-top: 1px solid var(--line); }
.product-reviews .reviews-summary {
  display: flex;
  align-items: center;
  /* R168 — the heading above is centred; the summary line follows it on the
     same axis instead of hanging left while everything else centres. */
  justify-content: center;
  gap: 0.5rem;
  color: var(--forest-600);
}
.review-flash {
  /* R172 — the notice spans the SAME width as the review cards / the form
     card: the identical inline padding + transparent 1px border trick the
     list and the compose row use, no 640px cap — one width everywhere. */
  margin: 0 auto 1.4rem;
  padding: 0.75rem calc(clamp(1.6rem, 4vw, 3rem) + 1px);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  /* R168 — the moderation flash reads as a centred notice, not a left rail */
  text-align: center;
}
.review-flash-success { background: var(--sand); color: var(--success); font-weight: 600; }
.review-flash-error { background: var(--sand); color: #b3261e; font-weight: 600; }

.review-list {
  list-style: none;
  /* R150 — the cards span exactly the product-video width: the video lives
     inside .product-info-box (same container, padding clamp(1.6rem,4vw,3rem)),
     so mirroring that padding here makes every card equal to the frame at
     every viewport — no magic 960px any more. The inset doubles as the
     "slightly indented, well-margined" gutter the cards keep on phones. */
  padding-inline: clamp(1.6rem, 4vw, 3rem);
  /* and the info-box's 1px border is mirrored as transparent, or the cards
     would run 2px wider than the frame (measured 1104 vs 1102) */
  border: 1px solid transparent;
  margin: 0 auto 2.25rem;
  /* R147 — one card per review, evenly divided by the gaps, not by hairlines */
  display: grid;
  gap: 0.9rem;
}
/* R150 — the pager hangs with the cards, not with the bare container edge */
.review-pager { padding-inline: clamp(1.6rem, 4vw, 3rem); }
/* R149 — each review is a standalone card riding --paper (the theme's own
   card surface, so Noir repaints it for free), no border, the shared
   --radius token; separation comes from the grid gap alone. */
.review-item {
  padding: 1.15rem 1.3rem 1.25rem;
  background: var(--paper);
  border-radius: var(--radius);
}
.review-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.55rem;
  margin-bottom: 0.55rem;
}
.review-author { font-weight: 700; color: var(--forest-800); font-size: var(--fs-md); }
.review-verified {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--success);
  padding: 0.12rem 0.55rem;
  border-radius: var(--radius-sm);
  background: rgba(63, 122, 74, 0.12);
}
.review-date { font-size: var(--fs-xs); color: var(--muted); margin-left: auto; }
.review-item .stars { font-size: var(--fs-md); }
.review-body { color: var(--ink-soft); line-height: 1.65; font-size: var(--fs-md); max-width: 78ch; }

.review-nudge { text-align: center; color: var(--muted); }
.review-nudge a { font-weight: 600; }

/* R167 — the form is a white card riding --paper, the same surface as the
   review cards above it (was --sand). The horizontal insets live on the
   .review-compose row, so the form spans exactly the review-list width —
   the same clamp the cards use, plus the transparent 1px border trick that
   keeps them level with the product frame. A gentle hover lift matches the
   card grammar. */
.review-form {
  margin: 1.4rem 0 0;
  padding: 1.6rem 1.6rem 1.5rem;
  background: var(--paper);
  border: 1px solid transparent;
  border-radius: var(--radius);
  transition: box-shadow 0.25s var(--ease), transform 0.25s var(--ease);
  /* R168 — fields, labels and the uploader keep their reading alignment;
     only the button and the notes below opt back into the centred axis. */
  text-align: left;
}
.review-form:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}
/* R167 — the compose row: state line first, then the disclosure buttons.
   The insets here align everything with the review cards above.
   R168 — the row itself is centred on the section's axis (the heading and
   the summary line above it both centre), so the invite reads as a proper
   call to action rather than a left-cornered afterthought. */
.review-compose {
  margin-top: 1.75rem;
  padding-inline: calc(clamp(1.6rem, 4vw, 3rem) + 1px);
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  text-align: center;
  gap: 0.8rem;
}
.review-compose .review-state {
  flex-basis: 100%;
  margin: 0;
}
/* <details>/<summary> is the no-JS disclosure: the summary wears the site's
   own button clothes, the native marker is stripped. */
.review-disclose { display: inline-block; }
/* R172 — an OPEN disclosure becomes a full-width row: the button keeps the
   centred axis above its card (was hugging the card's left edge), and the
   card below spans the compose row — the exact width of the review form
   and the flash notices, so every operation box reads as one family. */
.review-disclose[open] {
  display: block;
  flex-basis: 100%;
}
.review-disclose[open] .review-disclose-btn {
  display: flex;
  width: max-content;
  margin-inline: auto;
}
.review-disclose-btn {
  list-style: none;
  cursor: pointer;
  user-select: none;
}
.review-disclose-btn::-webkit-details-marker { display: none; }
.review-disclose-btn::marker { content: ''; }
.review-form .review-state { margin: -0.4rem 0 1rem; text-align: center; }
/* the guarded delete: a quiet warning card under its own outline button.
   R172 — full row width (matches the form card); the danger disclosure is
   block + flex-basis 100% while open, so this just fills the row. */
.review-disclose--danger .review-disclose-btn { color: var(--danger); border-color: var(--danger); }
.review-delete-box {
  width: 100%;
  margin-top: 0.9rem;
  margin-inline: auto;
  padding: 1rem 1.15rem;
  background: var(--sand);
  border-radius: var(--radius);
}
.review-delete-box p { margin: 0 0 0.8rem; font-size: var(--fs-sm); color: var(--ink-soft); }
/* R172 — the confirm button joins the centred axis like every other
   button in the compose area. */
.review-delete-form { text-align: center; }
.review-delete-form .btn-danger { display: inline-flex; }
.review-form h3 { margin-top: 0; margin-bottom: 1.05rem; }
/* R147 — breathing room down the form: each field ends with a real gap, the
   submit button keeps its distance from the uploader above it, and the
   moderation note reads as a footnote, not a stuck-on line. */
.review-form .field { margin-bottom: 1.15rem; }
/* R168 — the submit button and the moderation footnote join the centred
   axis (block-level flex + auto margins; full width on phones still wins). */
.review-form .btn-primary { display: flex; margin: 0.4rem auto 0; }
.review-form .review-note { text-align: center; }
.review-state { color: var(--muted); font-size: var(--fs-sm); margin-bottom: 1rem; }
.review-note { font-size: var(--fs-xs); color: var(--muted); margin-top: 0.75rem; margin-bottom: 0; }

/* the star picker: five radios, filled up to the checked one.
   R167 — empty stars were the hairline colour (nearly invisible on any
   surface); they now read as clearly hollow grey against the amber fill.
   The control rides a sand pill so it cannot be missed, every star is a
   real touch target, and the hovered/checked star grows. */
.star-input {
  display: inline-flex;
  flex-direction: row-reverse;
  gap: 0.3rem;
  padding: 0.35rem 0.8rem;
  background: var(--sand);
  border-radius: 999px;
}
.star-input input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.star-input label {
  font-size: var(--fs-2xl);
  line-height: 1;
  padding: 0.15rem;
  color: color-mix(in srgb, var(--ink) 25%, transparent);
  cursor: pointer;
  transition: color 0.15s var(--ease), transform 0.18s var(--ease);
}
.star-input input:checked + label,
.star-input input:checked ~ label,
.star-input label:hover,
.star-input label:hover ~ label { color: var(--amber); }
.star-input label:hover,
.star-input input:checked + label { transform: scale(1.15); }
.star-input input:focus-visible + label {
  outline: 2px solid var(--forest-600);
  outline-offset: 2px;
  border-radius: 2px;
}
/* the field is a column flex box — without this the pill stretches to the
   full form width and the stars drift to its far end */
.review-form .star-input { align-self: flex-start; }

/* ==========================================================================
   R146 — reviews, second pass: buyer photos, calmer motion, touch targets.
   Everything rides the existing tokens; nothing new for a theme to learn.
   ========================================================================== */
/* the review card lifts a touch on hover: border deepens, shadow appears —
   same grammar as the product cards, no repaint of the whole surface */
/* R149 — the card lifts on hover: shadow + a 1px rise, no border to deepen
   any more (the card has none). Same grammar as the product cards. */
.review-item {
  border-radius: var(--radius);
  transition: box-shadow 0.25s var(--ease), transform 0.25s var(--ease);
}
.review-item:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

/* the buyer's photos of the product — square crops in a tidy row */
.review-images {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem;
  margin-top: 0.75rem;
}
.review-image {
  display: block;
  width: 104px;
  height: 104px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #fff;
  border: 1px solid var(--line);
  transition: box-shadow 0.22s var(--ease), transform 0.22s var(--ease), border-color 0.22s var(--ease);
}
.review-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.3s var(--ease);
}
.review-image:hover,
.review-image:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 6px 16px rgba(22, 27, 22, 0.14);
}
.review-image:hover img { transform: scale(1.04); }
.review-image:focus-visible { outline: 2px solid var(--forest-600); outline-offset: 2px; }

/* "Update your review": photos already attached, untick to remove */
.review-keep {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}
.review-keep-item {
  position: relative;
  display: grid;
  grid-template-rows: auto auto;
  justify-items: center;
  gap: 0.25rem;
  cursor: pointer;
}
.review-keep-item input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.review-keep-thumb {
  display: block;
  width: 76px;
  height: 76px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: 2px solid var(--success);
  background: #fff;
  transition: border-color 0.2s var(--ease), opacity 0.2s var(--ease), transform 0.2s var(--ease);
}
.review-keep-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.review-keep-keep { font-size: var(--fs-xs); color: var(--success); font-weight: 600; }
.review-keep-item input:not(:checked) + .review-keep-thumb {
  border-color: var(--line);
  opacity: 0.4;
  transform: scale(0.96);
}
.review-keep-item input:not(:checked) ~ .review-keep-keep { color: var(--muted); }
.review-keep-item input:focus-visible + .review-keep-thumb {
  outline: 2px solid var(--forest-600);
  outline-offset: 2px;
}

/* R167 — the picker's touch targets, pill and grow now live in the main
   .star-input block above (one rule set, not two competing ones). */

/* inputs: 1rem stops iOS zoom-on-focus; the amber focus ring matches the site */
.review-form textarea,
.review-form input[type="text"] { font-size: var(--fs-md); }
.review-form textarea {
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.review-form textarea:focus {
  border-color: var(--amber);
  box-shadow: 0 0 0 3px rgba(201, 138, 61, 0.14);
  outline: none;
}
.review-form .btn-primary {
  transition: transform 0.18s var(--ease), box-shadow 0.18s var(--ease);
}
.review-form .btn-primary:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 14px rgba(201, 138, 61, 0.28);
}

@media (max-width: 640px) {
  /* the head wraps: date drops onto its own right-aligned row instead of
     squeezing the name and badge */
  .review-date { flex-basis: 100%; margin-left: 0; text-align: right; }
  .review-item { padding: 1.05rem 0.85rem; }
  .review-image { width: 84px; height: 84px; }
  .review-compose { padding-inline: clamp(0.85rem, 4vw, 1.6rem); }
  .review-form { padding: 1.15rem 1rem; }
  .review-form .btn-primary { width: 100%; }
  .star-input label { font-size: var(--fs-2xl); }
}

/* ==========================================================================
   R180 - Overflow containment (additive only)

   Long unbroken strings - order numbers, transaction ids, email addresses,
   product names - must wrap instead of being cut off by the root element's
   overflow-x clip. `break-word` does not affect min-content sizing, so no
   column, grid track or flex item changes width. Nothing here sets a width,
   height, position, display value or new breakpoint.
   ========================================================================== */
h1, h2, h3, h4, p, li, dd, dt, td, th,
.detail-list dd, .mail-card-title, .conversation-subject,
.product-card-title, .order-item-title, .account-message-body,
.dropdown-link, .status-pill, .badge {
  overflow-wrap: break-word;
}
.table-scroll { overscroll-behavior-x: contain; }

/* Phones only: short buttons reach a 44px thumb target. Uses the 560px
   breakpoint the storefront already ships; desktop is untouched. */
@media (max-width: 560px) {
  .btn, .btn-sm { min-height: 44px; }
}

/* ==========================================================================
   R181 - Long labels on phones (additive only)

   These three carry merchant- or visitor-supplied text (order status,
   presence text, action links). Their desktop nowrap is unchanged; on a
   560px screen it is released so a long value wraps instead of pushing the
   row past the edge. The storefront's existing 560px breakpoint is reused.
   ========================================================================== */
@media (max-width: 560px) {
  .status-pill, .presence-badge, .order-history-actions { white-space: normal; }
}

/* ==========================================================================
   R211 — administrator maintenance preview
   --------------------------------------------------------------------------
   A signed-in administrator sees the real shop while it is closed; this strip
   is the only thing that says so. It sits above the announcement bar, uses the
   brand ink rather than a warning red (nothing is broken), and disappears the
   moment the shop is reopened.
   ========================================================================== */
.nw-maint-preview {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.4rem 1rem;
  padding: 0.5rem 1.25rem;
  background: var(--forest-900);
  color: #fff;
  font-size: var(--fs-xs);
  line-height: 1.4;
  text-align: center;
}
.nw-maint-preview strong { font-weight: 700; }
.nw-maint-preview a {
  color: var(--amber);
  text-decoration: underline;
  text-underline-offset: 2px;
  white-space: nowrap;
}
.nw-maint-preview a:hover { color: var(--amber-100); }

/* ==========================================================================
   THEME OVERRIDES — Montserrat Noir
   Dark brown nav / Modern / One family
   Every value below wins over the baseline by source order only, so this
   file behaves identically in any standards-compliant browser.
   ========================================================================== */

/* --- 1. Design tokens ------------------------------------------------- */
:root {
  --forest-900: #161b16;
  --forest-800: #1f261f;
  --forest-700: #2c352c;
  --forest-600: #3a463a;
  --pine: #4d5c4a;
  --moss: #77846b;
  --sage: #aeb9a2;
  --amber: #c98a3d;
  --amber-600: #a96f2a;
  --amber-100: #f3e3cb;
  --cream: #f6f3ec;
  --paper: #fdfbf6;
  --sand: #ece7dc;
  --ink: #211f1a;
  --ink-soft: #4c493f;
  --muted: #7a7669;
  --line: #e3ddd0;
  --line-dark: rgba(255, 255, 255, 0.12);
  --danger: #b5402f;
  --success: #3f7a4a;
  --font-display: 'Montserrat', 'Avenir Next', 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
  --font-body: 'Montserrat', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --radius: 3px;
  --radius-sm: 3px;
  --shadow-sm: 0 1px 2px rgba(22, 27, 22, 0.06);
  --shadow-md: 0 10px 30px rgba(22, 27, 22, 0.10);
  --shadow-lg: 0 24px 60px rgba(22, 27, 22, 0.18);
}

/* --- 2. Chrome (header bar, drawers, dropdown panels) ----------------- */
.site-header {
  background: rgba(59, 49, 48, 0.72);
  border-bottom-color: rgba(255, 255, 255, 0.12);
}
.site-header.is-scrolled {
  background: rgba(59, 49, 48, 0.94);
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.28);
}
.mobile-menu { background: rgba(59, 49, 48, 0.985); }
.dropdown {
  background: rgba(42, 42, 42, 0.97);
  border-color: #e3ddd0;
  box-shadow: var(--shadow-md);
}
.dropdown-link:hover, .dropdown-link:focus-visible {
  background: rgba(201, 138, 61, 0.12);
  color: var(--forest-900);
}
.navmenu {
  --navmenu-block-ink: #FFFFFF;
  --navmenu-block: rgba(255, 255, 255, 0.06);
  --navmenu-block-hover: rgba(0, 0, 0, 0.26);
  --navmenu-ink: #FFFFFF;
  --navmenu-ink-soft: rgba(255, 255, 255, 0.86);
  --navmenu-hover-bg: rgba(255, 255, 255, 0.10);
  --navmenu-hover-ink: #FFFFFF;
  --navmenu-surface: rgb(42 42 42 / var(--nav-menu-alpha, 72%));
  --navmenu-btn-ink: #F5F5F5;
  --navmenu-btn-wash: rgba(255, 255, 255, 0.12);
  --navmenu-btn-wash-ink: #FFFFFF;
  --navmenu-btn-face: rgba(28, 28, 28, 0.94);
  --navmenu-btn-face-ink: #F5F5F5;
  --navmenu-btn-face-hover: #EFEFEF;
  --navmenu-btn-face-hover-ink: #141414;
  --navmenu-btn-edge: rgba(255, 255, 255, 0.18);
  --navmenu-scroll-thumb: rgba(255, 255, 255, 0.32);
}
.header-search input { background: rgba(255, 255, 255, 0.09); }
.header-search input:focus { background: rgba(255, 255, 255, 0.15); }

/* --- 3. Buttons ------------------------------------------------------- */
.btn { border-radius: var(--nw-btn-radius, 999px); }
.btn-primary {
  background: var(--nw-btn-primary-bg, var(--amber));
  color: var(--nw-btn-primary-fg, #14120E);
  box-shadow: 0 6px 18px color-mix(in srgb, var(--nw-btn-primary-bg, var(--amber)) 30%, transparent);
}
.btn-primary:hover {
  background: var(--nw-btn-primary-hover, var(--amber-600));
  color: var(--nw-btn-primary-fg, #14120E);
  box-shadow: 0 10px 26px color-mix(in srgb, var(--nw-btn-primary-hover, var(--amber)) 38%, transparent);
}
.btn-outline { color: var(--nw-btn-outline-fg, var(--forest-800)); border-color: var(--nw-btn-outline-border, var(--forest-600)); }
.btn-outline:hover { background: var(--nw-btn-outline-hover-bg, var(--forest-800)); color: var(--nw-btn-outline-hover-fg, #FFFFFF); }
.btn-add { background: var(--nw-btn-cta-bg, var(--forest-800)); color: var(--nw-btn-cta-fg, #FFFFFF); }
.btn-add:hover { background: var(--nw-btn-cta-hover, var(--amber)); color: var(--nw-btn-cta-fg, #14120E); }
.btn-quick-add { background: var(--nw-btn-icon-bg, var(--amber)); color: var(--nw-btn-icon-fg, #14120E); }
.btn-quick-add:hover { background: var(--nw-btn-icon-hover, var(--amber-600)); color: var(--nw-btn-icon-fg, #14120E); }
.btn-danger:hover { background: var(--nw-btn-danger-hover, var(--danger)); color: #FFFFFF; }
.badge { background: var(--amber); color: #14120E; }
.badge-soldout { background: var(--ink-soft); color: #FFFFFF; }
.cart-toggle::after { background: var(--amber); color: #14120E; }
.btn-paypal { box-shadow: 0 6px 18px rgba(255, 196, 57, 0.28); }

/* --- 4. Accent tints used across account & messaging ------------------ */
.account-message-bubble.is-customer {
  background: rgba(201, 138, 61, 0.12);
  border-color: rgba(201, 138, 61, 0.30);
}
.msg-customer { background: rgba(201, 138, 61, 0.14); }
.compose-card { border-color: var(--amber); box-shadow: 0 0 0 2px rgba(201, 138, 61, 0.09); }
.mail-status.is-replied { background: rgba(63, 122, 74, 0.14); color: var(--success); }
.order-status.is-completed, .order-status.is-paid { background: rgba(63, 122, 74, 0.14); }

/* --- 5. Surfaces & type rhythm ---------------------------------------- */
.product-card { background: var(--paper); border-color: var(--line); }
.subcat-chip { background: var(--paper); color: var(--ink-soft); border-color: var(--line); }
.subcat-chip:hover, .subcat-chip.is-active {
  background: var(--forest-800); color: #FFFFFF; border-color: var(--forest-800);
}
::selection { background: rgba(201, 138, 61, 0.28); color: #161b16; }
.skip-link { background: var(--forest-900); color: #FFFFFF; }
/* R46/R47 cookie bar: same pairing as the skip link so a dark theme cannot
   leave light text on its near-white --forest-900. The two pill buttons
   (Agree / Privacy Policy) are repainted with audited foregrounds. */
.cookie-notice { background: var(--forest-900); color: #FFFFFF; border-top: 0; }
.cookie-btn-agree { background: var(--amber); color: #14120E; }
.cookie-btn-ghost { color: #FFFFFF; border-color: rgba(255, 255, 255, 0.4); }
.cookie-btn-ghost:hover { background: rgba(255, 255, 255, 0.1); border-color: rgba(255, 255, 255, 0.7); }
input, select, textarea {
  background: var(--paper); color: var(--ink); border-color: var(--line);
}
input::placeholder, textarea::placeholder { color: var(--muted); }

/* --- 6. Theme-specific styling ---------------------------------------- */
/* --- 6a. ONE type scale for the whole store ------------------------------
   These tokens are what every font-size in this stylesheet now points at
   (_tmp/gen_themes.py rewrites them for themes that ask for it). Sizes up to
   --fs-xl are fixed rem steps so UI chrome never wobbles; the two display
   steps are fluid. */
:root {
  --fs-2xs: 0.72rem;
  --fs-xs:  0.82rem;
  --fs-sm:  0.92rem;
  --fs-md:  1rem;
  --fs-lg:  1.15rem;
  --fs-xl:  1.45rem;
  --fs-2xl: clamp(1.9rem, 1.35rem + 1.6vw, 2.5rem);
  --fs-3xl: clamp(2.25rem, 1.5rem + 2.6vw, 3.35rem);
  --leading-body: 1.72;
  --leading-display: 1.14;
  color-scheme: light;
  accent-color: var(--amber);
}

html { font-size: 100%; -webkit-text-size-adjust: 100%; }

body {
  font-size: var(--fs-md);
  line-height: var(--leading-body);
  letter-spacing: -0.002em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 {
  line-height: var(--leading-display);
  text-wrap: balance;
}
h1, .hero h1, .page-hero h1 { font-size: var(--fs-3xl); }
h2, .section-header h2 { font-size: var(--fs-2xl); }
h3 { font-size: var(--fs-xl); }
h4 { font-size: var(--fs-lg); }
.content-page h2 { font-size: var(--fs-xl); }
.content-page h3 { font-size: var(--fs-lg); }
.content-page .lead, .hero-lead { font-size: var(--fs-lg); line-height: 1.6; }

p, li, dd, dt, blockquote { font-size: var(--fs-md); }
p, .content-page p, .content-page li { text-wrap: pretty; }
small, .form-help, .field-help { font-size: var(--fs-xs); }

/* R53's wordmark voice survives the rewrite: the base 2rem rule lands on
   --fs-2xl during normalization, so it is re-pinned here at its designed
   size (phones step back down via the generator's 560px rule). */
.announcement { font-size: 0.8rem; }
.logo { font-size: 2rem; }

/* --- 6b. Buttons & controls: one UI step -------------------------------- */
/* R181 — the .btn pin above was dead code: the design block near the end of
   this file declares .btn again and always won. One declaration now. */
.btn-large { font-size: var(--fs-md); }
.btn-small { font-size: var(--fs-xs); }
.badge, .cart-toggle::after, .subcat-chip, .tag { font-size: var(--fs-2xs); }
label, .field label, .admin-form-row label { font-size: var(--fs-xs); }
/* 16px controls: one step for the whole form AND no iOS focus zoom */
input, select, textarea { font-size: var(--fs-md); }

/* Montserrat Noir — one family, four voices. Weight (never a second family)
   separates the roles: 700 display headings, 600 chrome (nav, buttons,
   labels, table heads), 500 leads and card titles, 400 body copy. */
h1, .hero h1, .page-hero h1, .content-page h1,
h2, .section-header h2, h3, h4 { font-weight: 700; letter-spacing: -0.01em; }
h5, h6 { font-weight: 600; }
.logo { font-weight: 700; letter-spacing: 0.01em; }
.hero-lead, .content-page .lead { font-weight: 500; }
label, .field label, .admin-form-row label, legend { font-weight: 600; }
th, .eyebrow { font-weight: 600; }
.footer-brand .logo, .footer-links h3 { font-weight: 600; }

/* --- announcement bar: exactly the default row ---------------------------
   Same background, same 0.8rem size (pinned by the shared type scale block
   above) and the same line box as the default stylesheet — only the
   letterforms are Montserrat. */
.announcement {
  background: var(--forest-900);
  color: var(--sage);
  font-weight: 400;
  line-height: 1.65;
  letter-spacing: 0.06em;
  text-transform: none;
  padding: 0.5rem 1rem;
}
/* the ramp's "p { font-size: …md }" rule would inflate the line box — the
   paragraph follows the bar's own size, exactly like the default sheet. */
.announcement p { font-size: inherit; }

/* --- navigation bar: light solid at rest, deep frosted while scrolling
   Two states you can tell apart at a glance: at rest the underlay is a
   slightly lifted solid brown (#463b3a), once the store scrolls it deepens
   to a darker rgba(22,17,16,.90) pane behind a strong 24px blur, so the bar
   reads DARKER and the frost becomes plainly visible while the page slides
   beneath it. The frost lives on a ::before underlay — never on the header
   itself — so the header does not become a backdrop root and the dropdown
   below can blur the real page. No border: the pane meets the hero
   cleanly, with no light hairline. */
.site-header {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom: 0;
  box-shadow: none;
}
.site-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: #463b3a;
  backdrop-filter: blur(24px) saturate(170%);
  -webkit-backdrop-filter: blur(24px) saturate(170%);
  transition: background 0.3s var(--ease);
  pointer-events: none;
}
.site-header.is-scrolled::before { background: rgba(22, 17, 16, 0.90); }
/* the generated chrome pin paints .site-header.is-scrolled itself at 0.94 —
   that higher-specificity fill would sit ON TOP of the ::before underlay and
   hide the frost, so it is pinned back to transparent here. */
.site-header.is-scrolled { background: transparent; box-shadow: 0 12px 40px rgba(20, 15, 12, 0.38); }
.logo { color: #FFFFFF; }

/* --- navigation voice: Montserrat, two clear levels ----------------------
   Level 1 (bar buttons) reads at the SAME size as the default stylesheet
   (0.92rem) but one weight up; level 2 (dropdown links) steps down to the
   0.82rem token at weight 500, so parent and child menus are unmistakably
   ranked — primary first, secondary quieter. No uppercase: mixed case keeps
   the glyphs at the default sheet's optical size. */
.nav-list > li > a {
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.02em;
  border-radius: 0;
  background: transparent;
}
.nav-list > li > a::after {
  content: '';
  position: absolute;
  left: 0.9rem;
  right: 0.9rem;
  bottom: 0.2rem;
  height: 2px;
  background: var(--amber);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.28s var(--ease);
}
.nav-list > li > a:hover,
.nav-list > li > a:focus-visible {
  color: #FFFFFF;
  background: transparent;
}
.nav-list > li > a:hover::after,
.nav-list > li > a:focus-visible::after { transform: scaleX(1); }
.chevron { color: rgba(255, 255, 255, 0.75); }

/* --- buttons: default voice ----------------------------------------------
   Match the default stylesheet's button type exactly — 0.95rem, weight 600,
   no uppercase, the default 0.01em tracking — so labels sit at the same
   proportion inside the pill shape. Amber surfaces take WHITE labels like
   the default sheet does (best_on falls back to near-black there because
   white misses AA on #c98a3d; the default look wins). */
.btn {
  /* 0.95rem — the default sheet's .btn size. Written as a calc() because the
     ramp rewriter snaps absolute rem sizes onto the nearest step (0.92rem);
     calc()/var() are left alone by design. */
  font-size: calc(var(--fs-sm) + 0.03rem);
  font-weight: 600;
  letter-spacing: 0.01em;
  text-transform: none;
}
/* R215 — these white labels used to be literals. That silently disconnected
   Admin → Styles → Buttons: the merchant saved --nw-btn-primary-fg (etc.) into
   the page's <style>, but this later rule painted #FFFFFF over the top of it,
   so every "text" colour in the Buttons section did nothing on a themed shop.
   Each rule now reads its token first and keeps the theme's white as the
   fallback, so an untouched store renders exactly as before. */
.btn-primary, .btn-primary:hover { color: var(--nw-btn-primary-fg, #FFFFFF); }
.btn-add:hover { color: var(--nw-btn-cta-fg, #FFFFFF); }
.btn-quick-add, .btn-quick-add:hover { color: var(--nw-btn-icon-fg, #FFFFFF); }
.badge, .cart-toggle::after { color: #FFFFFF; }

/* --- frosted-glass dropdown (always frosted, even at rest) ----------------
   R217 — the pane is NEUTRAL GREY, not brown. The bar above it keeps its
   brown so the brand still reads at the top of the page; everything that
   drops down from it is a grey scale of one hue, with depth carried by
   OPACITY rather than by a second colour:
     · the glass itself   rgb(42 42 42 / 72%)  behind a 24px frost …
     · its hairline       19% white of that same alpha
     · a resting link     pure white
     · a hovered link     +10% white film
     · a hovered block    −26% black film (deeper than the glass)
   Every layer is the same grey, so nothing on this pane can read as brown,
   and the white copy clears AA on light and dark backdrops alike. The frost
   still rides the ::before underlay (see the base sheet). */
.dropdown {
  background: rgb(42 42 42 / var(--nav-menu-alpha, 72%));
  border: 1px solid rgba(255, 255, 255, 0.12);
  /* R146 — the hairline rides the panel's alpha (see the base sheet): an
     opaque border on a near-transparent pane read as a white line. 0.19 is
     the resting ratio (0.14 border over 0.72 glass). */
  border-color: rgb(255 255 255 / calc(var(--nav-menu-alpha, 72%) * 0.19));
  box-shadow: 0 14px 44px rgba(0, 0, 0, 0.44);
}
/* R141f — the frost rides the panel's ::before underlay (see the base
   sheet): the element itself must stay filter-free, or the flyout third
   level nested inside it could never frost the real page.
   R217 — the saturation lift is 110%, not 170%. A 170% boost does not just
   freeze the page behind the pane, it AMPLIFIES whatever colour it finds
   there: over the warm photograph the homepage opens with, a neutral grey
   panel came out looking brown again. 110% keeps the frosted depth the
   merchant asked to keep and lets the grey stay grey. */
.dropdown::before {
  backdrop-filter: blur(24px) saturate(110%);
  -webkit-backdrop-filter: blur(24px) saturate(110%);
}
/* R67/R68 — the panel's hairlines (the gully between the columns, the level-3
   guide) are the pale-theme --line by default, which reads as a cream streak on
   the dark pane; they become a whisper of white here. R68 moved the column rule
   from the multi-column `column-rule` onto a pseudo-element, so it is a
   background, not a border. R212 — the "All <category>" head row is gone, so
   its selector went with it. */
.dropdown,
.dropdown-sublinks-list { border-color: rgb(255 255 255 / calc(var(--nav-menu-alpha, 72%) * 0.19)); }
.dropdown-col + .dropdown-col::before { background: rgb(255 255 255 / calc(var(--nav-menu-alpha, 72%) * 0.19)); }
/* R217 — hierarchy by SIZE and WEIGHT, never by fading the words:
   level 2 is the louder line (0.94rem / 600) and level 3 the quieter one
   (0.82rem / 400). Both are pure white, as the merchant asked — a grey
   hierarchy on a grey pane is exactly what made the old panel unreadable.
   calc() and var() are used on purpose: the type-scale rewriter snaps bare
   rem values onto the ramp, which would flatten the two steps together. */
.dropdown-link {
  font-size: calc(var(--fs-sm) + 0.02rem);
  font-weight: 600;
  letter-spacing: 0.015em;
  color: #FFFFFF;
}
.dropdown-sublinks .sub-link {
  font-size: var(--fs-xs);
  font-weight: 400;
  letter-spacing: 0.01em;
  color: #FFFFFF;
  opacity: 1;
}
/* the navmenu presets print their third level through the same class */
.navmenu .navmenu-sub .sub-link { color: #FFFFFF; opacity: 1; }
.dropdown-link:hover,
.dropdown-link:focus-visible {
  background: rgba(255, 255, 255, 0.10);
  color: #FFFFFF;
}
.dropdown-link:hover::before,
.dropdown-link:focus-visible::before { background: var(--amber); opacity: 1; }

/* --- header controls stay light on the brown pane ------------------------ */
.header-search input { background: rgba(255, 255, 255, 0.10); color: #FFFFFF; }
.header-search input::placeholder { color: rgba(255, 255, 255, 0.60); }
.header-search input:focus { background: rgba(255, 255, 255, 0.16); }
.btn-icon { color: rgba(255, 255, 255, 0.88); }
.btn-icon:hover { background: rgba(201, 138, 61, 0.20); color: #FFFFFF; }
.menu-toggle span { background: #FFFFFF; }

/* --- mobile drawer follows the navigation bar ---------------------------- */
.mobile-menu {
  background: rgba(43, 37, 36, 0.97);
  /* R136 — same opacity knob as the dropdown glass, with a 60% readability
     floor so white labels never float over a bright page. */
  background: rgb(43 37 36 / max(60%, var(--nav-menu-alpha, 97%)));
  color: rgba(255, 255, 255, 0.80);
}
.mobile-menu nav > ul > li > a,
.mobile-menu .mobile-cat > a {
  color: rgba(255, 255, 255, 0.85);
  font-weight: 600;
  letter-spacing: 0.02em;
}
.mobile-sub a { font-weight: 500; }
.menu-close { color: rgba(255, 255, 255, 0.85); }

/* --- reduced transparency: same surfaces, no frost ------------------------
   When the OS asks for less transparency the glass turns fully opaque —
   same colours, same layout, nothing becomes unreadable. */
@media (prefers-reduced-transparency: reduce) {
  .site-header::before {
    background: #463b3a;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .dropdown {
    background: #2A2A2A;
  }
  .dropdown::before {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .mobile-menu {
    background: #2b2524;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

/* --- 7. Responsive safeguards ---------------------------------------- */
/* A letterspaced or heavy wordmark can outgrow a 390px header and push
   the action icons off-screen, so the logo is reined in below 560px
   no matter what the theme does further up this file. */
@media (max-width: 560px) {
  .logo { font-size: 1.05rem; letter-spacing: 0.015em; }
  .header-inner { gap: 0.75rem; }
}