/*
Theme Name: Grace Shop
Theme URI: https://gracemerch.com
Author: Jesse Lee
Author URI: https://gracemerch.com
Description: A normal WooCommerce shop for personalized keepsakes. Home, shop archive, single product, cart, checkout and account each render their own page. Every product can carry a personalization form.

Version: 1.0.139

Requires at least: 6.5
Requires PHP: 8.0
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: grace-shop
Tags: e-commerce
*/

/* These values are the shop's. They were measured off one source store's live
   render in 2026-10, and the comments below say "the source" from here on. The
   store is named in PERSONALIZATION.md; the tables live in DESIGN.md,
   and each deliberate departure in the changelog at the version that made it. */

/* Instrument Sans is the source store's body and heading face. It ships as
   one variable file covering 400 through 700, so the weight range below is what
   asks for the right instance; bundling it means the page never calls out to a
   font CDN. There is no italic cut and none is declared, so nothing may ask for
   one — the browser would synthesise an oblique and it looks wrong. */
@font-face { font-family: "Instrument Sans"; font-style: normal; font-weight: 400 700; font-display: swap; src: url("assets/fonts/instrument-sans-var.woff2") format("woff2"); }

/* The palette and type are the source store's own, read off the live page.
   Colours are RGB triplets so one token works at full strength or behind an
   alpha, and a section repaints itself by re-declaring the triplet. --muted,
   --line, --line-strong and --card derive from --color-foreground, so they
   follow that re-declaration into a nested scheme.
   The audience is mostly 60+, so reds stay rare and each one means one thing:
   --danger is field errors, --color-bought is the social-proof line, and
   --color-price-sale is a discount. */
:root {
  /* The product page's own scheme. */
  --color-background: 255,255,255;
  --color-foreground: 14,14,14;
  --color-button: 252,181,73;
  /* The label on the gold fill is the foreground dark, not the source's
     off-white. Off-white on this gold measures 1.55:1, and AA wants 4.5:1 for
     an 18px label; the dark measures 10.92:1. Owner decision 2026-10-13 — the
     gold itself is untouched and so is the source's `rgb(253,181,73)`.
     Recorded in DESIGN.md's button table and in the 1.0.x changelog entry.
     `--color-button-ring` is the hover halo, kept apart from the label because
     it is read against the PAGE, not against the fill: with the label dark, a
     dark halo on the black banner would be no halo at all. */
  --color-button-text: 14,14,14;
  --color-button-ring: 14,14,14;
  /* A gold dark enough to be read as type on white: the button gold above is a
     fill, and as a background it is 1.9:1 — under AA even for large text. 180,83,9
     is 5.02:1 on white and 4.4:1 on `rgb(var(--chip))`. Used by the hero's
     eyebrow and by the highlighted tail of its headline. */
  --color-gold-ink: 180,83,9;
  /* The secondary tier's fill. The source carries these as a pair so a scheme
     can repaint the secondary button without touching the primary. */
  --color-secondary-button: 255,255,255;
  --color-secondary-button-text: 8,8,8;
  /* Type or icon printed ON a filled accent — the discount chip and the sort
     key's hover fill. White because the fill is the contrast, not the page. */
  --color-on-accent: 255,255,255;
  --color-shadow: 8,8,8;

  --muted: rgba(var(--color-foreground), .75);
  --line: rgba(var(--color-foreground), .08);
  --line-strong: rgba(var(--color-foreground), .55);
  --card: rgb(var(--color-background));
  --chip: 239,240,245;
  --danger: 163,58,43;
  /* The source's social-proof line: 1K+ bought, printed in this red. */
  --color-bought: 224,63,63;
  /* The source's discounted price and its saving chip, both measured off the
     live page (#f34028 / #ff2b2b). Two reds besides --danger, and deliberately
     distinct from it: a discount must not read as a field error. The price red is
     text only; the chip is the one red fill on the page, and it appears only
     where a struck price does. */
  --color-price-sale: 243,64,40;
  --color-price-off: 255,43,43;

  /* Layout and controls. The source store is the standard for every control
     value below, measured off its live render with `getComputedStyle`.

     --page-width is a ceiling, not the width you see: the container is
     min(this, viewport) and the gutters come off it. 1800px is the source's
     own `--page-width: 150rem`. The gutters themselves live on `.page-width`
     below — 18px under 750px, 60px above — so the content is 1350px on a 1470px
     screen and 1680px on a 1920px one. */
  --page-width: 1800px;
  --grid-desktop-horizontal-spacing: 28px;
  --grid-desktop-vertical-spacing: 28px;
  /* The source's own mobile pair, read off its page: `--grid-mobile-horizontal-
     spacing: 6px` and `--grid-mobile-vertical-spacing: 14px`. Its columns sit much
     tighter than its rows. Both were 14px here, and the vertical one was declared
     but never used — the grid rule took the horizontal value for both axes. */
  --grid-mobile-horizontal-spacing: 6px;
  --grid-mobile-vertical-spacing: 14px;

  /* Button geometry, from the source's `.button` (Shopify Dawn baseline). It
     declares these in rem against its own 12px root, and this theme's root is 12px
     too, so a copied rem lands on the source's own pixel. `--buttons-radius`
     is the INSIDE of the two rings the button draws (see the ::after note in the
     buttons section below); the outset is the outside of the outer one. */
  --buttons-radius: 4px;
  --buttons-radius-outset: 5px;
  --buttons-border-width: 1px;

  /* Inputs, from the source's own scale. Its `.quantity` is
     `min-height: calc((var(--inputs-border-width) * 2) + 4.5rem)` and its measured
     `.field__input` is 54px, against a 56px button — inputs and buttons are one
     control height in that design, two pixels apart, not two scales. The shop had
     four input heights (42 / 45 / 46 / 48px) and none of them was that, which is
     why the cart's coupon field read as misaligned beside its own button. */
  --inputs-height: 54px;
  --inputs-radius: 26px;
  --inputs-radius-outset: 27px;
  --inputs-border-width: 1px;
  --inputs-border-opacity: .55;

  /* The shipping card, from the product page's own render. */
  --color-subtle: 248,248,248;
  --color-shipping-type: 34,34,34;
  --color-shipping-date: 68,68,68;
  --color-shipping-strong: 17,17,17;
  /* The product card is its own scheme, whose ink is #080808. */
  --color-card-ink: 8,8,8;
  /* The review widget's fill for its rating bars, and the inversion of its sort
     button. Its text does NOT have a token of its own: the source runs the
     whole widget on its foreground and its muted colour, so `--muted` and
     `--color-foreground` do that work here too. There used to be a
     `--color-review-ink: 51,51,51` for it, which was a grey the source never
     uses. */
  --color-review-button: 0,0,0;

  /* Type — the source's own tokens, read off its stylesheet and confirmed
     with getComputedStyle. Instrument Sans is a variable font, so one file
     covers every weight; the two scales below are what turn its rem steps into
     the pixel sizes it actually renders. */
  --font-body-family: "Instrument Sans", sans-serif;
  --font-heading-family: "Instrument Sans", sans-serif;
  --font-body-weight: 400;
  --font-heading-weight: 400;
  --font-body-scale: 1.2;
  --font-heading-scale: 1.0833333333;
  /* Headings carry a FIXED .78px of tracking and a 1.277 line box at every size;
     body text carries .06rem and a 1.6667 box. The tracking is deliberately px and
     not em: the source's own value is px, so at a 23.4px heading it is .78px
     (.033em) and at its 52px h1 the same .78px is only .015em. Writing it as em
     would make every small heading tighter than the source's by more than
     half. Small bold captions step down to .48px. */
  --font-heading-letter-spacing: .78px;
  --font-heading-line-height: 1.277;
  --font-body-letter-spacing: .06rem;
  --font-body-line-height: 1.6667;

  /* The product column's own box is settled in the product page rework, so the
     panel radius below stays at its current value until then. */
  --radius: 14px;

  /* The small-radius families, named so a new panel takes a token and not a
     fresh number: controls and fields (chips, inputs, swatch frames, step
     panels, the preview key), badges and thumbnails, the soft plates (arrival
     card, review bars, the preview canvas) and the standing panels (hero
     caption, order-review card, search dropdown). The one-offs keep their own
     values at their rules — the card plate 18px, card media 7px, the price chip
     14.4px, the sticky bar's top corners 20px, circles 50% — and nothing here
     may be "unified" by a later pass. */
  --radius-control: 8px;
  --radius-badge: 6px;
  --radius-plate: 10px;
  --radius-panel: 12px;
}

/* The top band — the announcement bar and the two-row header bar — wears this
   scheme. It was black, `#080808`, until owner decision 2026-10-13; both the bar
   and the band are now white.
   Its ink is `#111827`, the owner's own value, and it is deliberately NOT the
   page's `14,14,14`. Nothing sets the two inks against each other: the band is
   separated from the page by its own edge and by the hero's whitespace, and its
   top and bottom halves share one ink. The pair, and why the page's ink was
   left alone, are recorded at 1.0.82 in the changelog.
   Only what differs from :root is declared. The derived tokens are repeated
   anyway, and that is not redundancy: a custom property whose value holds var()
   resolves where it is declared, so an element that inherits :root's
   `--color-foreground` would keep the muted and the hairlines that derive from
   the PAGE's ink. `--color-button-ring` is the halo, and it is read against the
   page, which here is the white band, so it is the band's ink and not a light
   value. The secondary button needs no repaint: on a white ground the root's
   white fill and dark label are already right. */
.color-scheme-topbar {
  --color-foreground: 17,24,39;
  --color-button-ring: 17,24,39;

  --muted: rgba(var(--color-foreground), .75);
  --line: rgba(var(--color-foreground), .08);
  --line-strong: rgba(var(--color-foreground), .55);

  /* The seam of the white top band: the owner's `#F3F4F6` (2026-10-13), 1.10:1
     against the white on either side. It is lighter than the ink's 8% hairline
     above on purpose, and it is written here rather than as `var(--line)`
     because it answers a different question. Two elements draw it — the
     announcement bar, under its own line, and the checkout's bare header, which
     has no announcement bar above it — so the value has one home. */
  --seam: #F3F4F6;
}

* { box-sizing: border-box; }

/* 1rem is 12px, which is the source's own root. This matters: it is what makes
   a rem copied out of the source's stylesheet render at the same pixel value here
   as it does there. Its `font-size: 1.5rem` button is an 18px label because 1rem
   is 12px — at an 18px root the same declaration would be a 27px label sitting
   in a layout that was never scaled to match, and a label that long wraps and
   breaks the box height. Anything else written in rem here is derived from a
   measured pixel and scaled to keep that pixel (see each value). */
html { font-size: 75%; }

body {
  margin: 0;
  color: rgb(var(--color-foreground));
  background: rgb(var(--color-background));
  font-family: var(--font-body-family);
  font-size: 1.6rem;
  font-weight: var(--font-body-weight);
  letter-spacing: var(--font-body-letter-spacing);
  line-height: var(--font-body-line-height);
}

/* The source's heading scale, verbatim: `calc(var(--font-heading-scale) * Nrem)`.
   Desktop first, the 3rem/2rem/1.7rem/1.2rem mobile steps underneath. Headings are
   weight 400 here, not the 700 the shop used — the source's scale is a large
   light face, and a bold one at these sizes reads as a different design. The
   exceptions are written per element where the source is bold: its small
   section labels and captions.
   A size is spelled by its source: the heading steps below are calc()s on the
   scale; a value that lands exactly on the 12px root grid is written in rem
   (1.5rem = 18px, 1.7rem = 20.4px); a measured value that does not — 16px, 14px,
   13px, 23.2px — stays px. Line-heights keep their ratios or their measured px,
   and tracking is always px: the source's own convention, a fixed .78px. */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading-family);
  font-weight: var(--font-heading-weight);
  letter-spacing: var(--font-heading-letter-spacing);
  line-height: var(--font-heading-line-height);
}
h1 { font-size: calc(var(--font-heading-scale) * 4rem); }
h2 { font-size: calc(var(--font-heading-scale) * 2.4rem); }
h3 { font-size: calc(var(--font-heading-scale) * 1.8rem); }
h4 { font-size: calc(var(--font-heading-scale) * 1.5rem); }
h5 { font-size: calc(var(--font-heading-scale) * 1.3rem); }
h6 { font-size: calc(var(--font-heading-scale) * 1rem); }
@media (max-width: 749px) {
  h1 { font-size: calc(var(--font-heading-scale) * 3rem); }
  h2 { font-size: calc(var(--font-heading-scale) * 2rem); }
  h3 { font-size: calc(var(--font-heading-scale) * 1.7rem); }
  h5 { font-size: calc(var(--font-heading-scale) * 1.2rem); }
}

button, textarea, select, input { font: inherit; }
button { cursor: pointer; }
/* Checkboxes and radios keep their native shapes and take the page ink as their
   accent — the gold cannot serve here: a white check on the button gold measures
   1.9:1. */
input[type="checkbox"], input[type="radio"] { accent-color: rgb(var(--color-foreground)); }
a { color: rgb(var(--color-foreground)); text-decoration: underline; text-underline-offset: 2px; }

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

/* The container every page uses: the page width, with 50px gutters. */
/* The page gutter is the source's own two values, copied as rem because this
   theme's root is 12px and the source's is too: `0 1.5rem` (18px) up to 750px,
   then `0 5rem` (60px). It was a flat 50px, which was neither.
   Measured on its live render: at 1470px its `.page-width` carries `padding: 0 60px`
   and its header's content box is 1350px. This comment, and the note in DESIGN.md,
   used to say 50px gutters and 1370px of content — that was wrong.
   The desktop change is not cosmetic: content at 1470px goes 1370px -> 1350px, which
   moves every grid, the footer and both product columns 10px inboard per side. */
.page-width { max-width: var(--page-width); margin: 0 auto; padding: 0 1.5rem; }
@media (min-width: 750px) {
  .page-width { padding: 0 5rem; }
}

/* ---------- skip link ---------- */

/* Hidden until it takes focus, then it sits over the bar. */
.skip-link:focus { position: fixed !important; top: 10px; left: 10px; z-index: 300; width: auto; height: auto; margin: 0; clip: auto; overflow: visible; white-space: normal; }

/* ---------- header ---------- */

/* The two-row bar, as the source lays it out: the drawer button and the
   search icon on the left, the wordmark centred, the account and cart icons on
   the right, and the menu on a row of its own underneath. */
.header-wrapper {
  position: relative;
  z-index: 5;
  background: rgb(var(--color-background));
  color: rgb(var(--color-foreground));
}

.header {
  display: grid;
  grid-template-areas: "left-icons heading icons" "navigation navigation navigation";
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 8px 50px;
}

.header__left { grid-area: left-icons; display: flex; align-items: center; }
.header__icons { grid-area: icons; display: flex; align-items: center; justify-content: flex-end; }
.header__heading { grid-area: heading; text-align: center; }
.header__inline-menu { grid-area: navigation; margin-top: 10.5px; }

.header__icon {
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0; border: 0; background: none;
  color: inherit; text-decoration: none; cursor: pointer;
}
.header__icon svg { width: 20px; height: 20px; }
/* The source draws its cart bag larger than the other icons. */
.header__icon--cart svg { width: 44px; height: 44px; }
/* The drawer button only appears on narrow screens. */
.header__icon--menu { display: none; }

.header__heading-link { display: inline-block; color: inherit; text-decoration: none; }
/* The wordmark is type, not the source's image. The source draws its own
   mark 200x36.5 (letters carry 28.5px of ink, the cross more); ours measured
   13.5px of ink at 20px, which read visibly smaller — owner's step of
   2026-10-13, to 24px: 16.2px of ink in a 30px box, 207px wide. The phone rule
   below drops it back to 20px. 900 is clamped to 700 by the face's 400-700 range. */
.header__heading-logo {
  display: block; font-size: 2rem; line-height: 30px;
  font-weight: 900; letter-spacing: .12em; text-transform: uppercase;
}

/* The cart count, drawn only while the cart holds something. Its type is the
   reference bubble's own — 10.8px at weight 400 (measured 2026-10-14; ours was
   10px at 700). */
.header__icon--cart { position: relative; }
.cart-count-bubble {
  position: absolute; left: 60%; bottom: 6px;
  display: flex; align-items: center; justify-content: center;
  min-width: 17px; height: 17px; padding: 0 4px; border-radius: 50%;
  background: rgb(var(--color-button)); color: rgb(var(--color-button-text));
  font-size: .9rem; font-weight: 400;
}

/* Order tracking, as a text link in the header row. The source draws it
   here rather than in the menu; ours is muted so it does not compete with the
   account and cart icons, and it is dropped below 990px, where the drawer
   carries it instead. */
.header__track {
  display: none;
  margin-right: 20px;
  color: var(--muted); text-decoration: none; white-space: nowrap;
  font-size: 1.2rem; line-height: 27px; letter-spacing: .72px;
}
.header__track:hover { color: rgb(var(--color-foreground)); }

/* ---------- the menu row ---------- */

.list-menu { list-style: none; margin: 0; padding: 0; }
.list-menu--inline { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; }
.list-menu--inline > li { display: flex; }

/* The label type is the source's own 1.8rem = 21.6px at the 12px root; it was
   1.2rem here until 2026-10-13, at the owner's request to follow. The source's
   live page sizes this element with a section-level inline rule,
   `#shopify-section-…__header .header__menu-item { font-size: 1.8rem }` — its
   linked base.css never sizes it, and `.link` gives 1.4rem. Line box 27px and
   tracking .72px are the same measure. The ink is still ours: the source
   paints `rgba(14,14,14,.75)` and darkens it on hover, ours inherits the band. */
.header__menu-item {
  display: flex; align-items: center; gap: 6px;
  padding: 12px; border: 0; background: none;
  font-size: 1.8rem; font-weight: 400; line-height: 27px; letter-spacing: .72px;
  color: inherit; text-decoration: none; cursor: pointer;
}

/* ---------- the search panel ---------- */

/* It opens across the whole bar, as the source's does. */
.search-modal-container { position: static; }
.search-modal {
  position: absolute; inset: 0; z-index: 4;
  display: flex; align-items: center;
  background: rgb(var(--color-background));
  color: rgb(var(--color-foreground));
}
.search-modal__content { position: relative; width: 100%; padding: 0 60px; }
.search-modal__form { display: flex; align-items: center; gap: 8px; max-width: 828px; margin: 0 auto; }
.search-modal__input {
  flex: 1; height: var(--inputs-height); padding: 0 15px;
  font-size: 16px; border-radius: var(--inputs-radius);
  border: var(--inputs-border-width) solid rgba(var(--color-foreground), var(--inputs-border-opacity));
  background: transparent; color: inherit;
}
/* The two panel buttons are 44×44 like every other icon key. `flex: none` is
   what keeps them there: they are flex children of the form, and the default
   `flex-shrink: 1` let the input squeeze them down to their box's content width
   (measured 18–20px instead of 44px). The input is `flex: 1` and gives up the
   space instead, so the field narrows and the buttons do not. */
.search-modal__submit, .search-modal__close {
  flex: none;
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0; border: 0; background: none;
  color: inherit; cursor: pointer;
}
.search-modal__submit svg { width: 20px; height: 20px; }
.search-modal__close svg { width: 18px; height: 17px; }

/* ---------- the drawer ---------- */

/* A panel from the left, as the source's menu drawer is. It carries the
   reference's white scheme, so it re-declares the triplets and their derived
   tokens together and escapes the dark bar it sits inside. */
.menu-drawer-container { position: static; }
.menu-drawer {
  --color-background: 255,255,255;
  --color-foreground: 14,14,14;
  --muted: rgba(var(--color-foreground), .75);
  --line: rgba(var(--color-foreground), .08);
  --line-strong: rgba(var(--color-foreground), .55);
  --card: rgb(var(--color-background));
  position: fixed; top: 0; bottom: 0; left: 0; z-index: 10;
  width: min(360px, 90vw); padding: 20px;
  background: rgb(var(--color-background)); color: rgb(var(--color-foreground));
  overflow-y: auto;
}
.menu-drawer__top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.menu-drawer__title { font-weight: 900; letter-spacing: .12em; text-transform: uppercase; }
/* The close key is a 44×44 icon button like the rest. The flex centring is what
   it was missing: as a `display: block` box the icon sat at the top-left corner
   of the 44px square (measured a 6,3px offset instead of 6,7). */
.menu-drawer__close {
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0; border: 0; background: none; cursor: pointer;
}
.menu-drawer__menu-item {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 0; color: inherit; text-decoration: none;
}

/* The source swaps the menu row for the drawer button below 990px. */
.header__inline-menu { display: none; }
@media (min-width: 990px) {
  .header__inline-menu { display: block; }
  .header__icon--menu, .menu-drawer-container { display: none; }
  .header__track { display: inline-flex; align-items: center; }
}
@media (max-width: 989px) {
  .header { grid-template-columns: 1fr auto 1fr; padding: 8px 16px; }
  .header__icon--menu { display: flex; }
}
/* The heading column is 185px wide at a 393px viewport, so the 24px wordmark
   (207px on one line) would fold into two lines. It steps back to the 20px size
   instead: 173px, one line, and the band keeps its height. */
@media (max-width: 749px) {
  .header__heading-logo { font-size: 20px; line-height: 25px; }
}

/* ---------- buttons ----------
   The standard is the source's `.button`, measured off its live render. Three
   tiers, and nothing else on the site may invent a fourth:

     .btn / .button / button.gs-add   primary   56px, gold, 400, sentence case
     .btn--secondary                  secondary same box, white ground
     .btn--tertiary                   tertiary 44px, transparent ground

   `.button` is WooCommerce's own class and is what its templates print, so the
   rule has to carry both. Use `.btn` in theme markup.

   THE BORDER IS TWO RINGS, NOT A `border`. the source draws them with pseudo
   elements: `::before` is the outer ring at the outset radius and sits behind
   the fill, `::after` is the inner ring inset by one border width and sits in
   front. At rest both rings are transparent, so the button reads as a bare gold
   box; hover raises the outer's opacity and a light ring appears around the
   fill. Do not replace this with `border: 1px solid` — it changes what hover
   does, and hover is the whole point of the mechanism.

   Hover does NOT change the background. That is the one behaviour worth being
   strict about: the gold stays the gold and the ring grows instead. */

.btn, .button, button.gs-add,
.woocommerce a.button, .woocommerce button.button, .woocommerce input.button,
.woocommerce #respond input#submit, .wc-block-components-button {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 146px;
  min-height: 56px;
  padding: 0 36px;
  border: 0;
  border-radius: var(--buttons-radius);
  background: rgb(var(--color-button));
  color: rgb(var(--color-button-text));
  font: inherit;
  font-weight: 400;
  font-size: 1.5rem;
  letter-spacing: .1rem;
  line-height: 1.2;
  text-decoration: none;
  text-transform: none;
  /* A button label never wraps. `min-width: 146px` plus 36px of padding either
     side leaves only 74px for the type, which is narrower than "Apply coupon" at
     18px — so on a 393px screen the label broke onto a second line and spilled
     out of the 56px box, because `min-height` is a floor and not a cap. The
     longest label in the shop ("Open A Support Ticket", 211px at 18px) still fits
     its 293px footer column with the padding, so nothing has to wrap anywhere. */
  white-space: nowrap;
  cursor: pointer;
  transition: box-shadow .1s ease;
}

/* The outer ring, behind the fill. Its shadow is transparent at rest because
   --buttons-shadow-visible is 0 upstream; it stays here rather than being
   dropped so a future shadow is one token away. */
.btn::before, .button::before, button.gs-add::before,
.woocommerce a.button::before, .woocommerce button.button::before, .woocommerce input.button::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: var(--buttons-radius-outset);
  box-shadow: 0 4px 5px 0 rgba(245, 245, 245, 0);
}

/* The inner ring, in front of the fill: a transparent ring on the outside of a
   solid one, so the fill reads as bordered only once a ring turns opaque.
   Both rings read against the page, so the halo takes the ring token and not
   the label's colour. */
.btn::after, .button::after, button.gs-add::after,
.woocommerce a.button::after, .woocommerce button.button::after, .woocommerce input.button::after {
  content: "";
  position: absolute;
  inset: var(--buttons-border-width);
  z-index: 1;
  border-radius: var(--buttons-radius);
  box-shadow:
    0 0 0 1px rgba(var(--color-button-ring), 0),
    0 0 0 calc(var(--buttons-border-width) + 1px) rgba(var(--color-button), 1);
}

.btn:hover::after, .button:hover::after, button.gs-add:hover::after,
.woocommerce a.button:hover::after, .woocommerce button.button:hover::after, .woocommerce input.button:hover::after {
  box-shadow:
    0 0 0 calc(var(--buttons-border-width) + 1.3px) rgba(var(--color-button-ring), 1),
    0 0 0 calc(var(--buttons-border-width) + 1px) rgba(var(--color-button), 1);
}

/* A disabled button keeps the scale's box. WooCommerce's own disabled rule hands
   it `color: inherit`, which on the gold fill means black type, and core's
   em-based padding, which is not the scale's.

   THE `[disabled]` IN EACH SELECTOR IS NOT REDUNDANT. Core's list is
   `… .woocommerce button.button:disabled[disabled]`, and that attribute selector
   is what puts core at 0,4,1. A plain `:disabled` here is 0,3,1 and loses — which
   is why an earlier version of this rule was present, matching, and still had no
   effect on the disabled *Update cart*. The `:disabled[disabled]` pair below ties
   core and wins on source order. Do not "tidy" the attribute off. */
.btn:disabled[disabled], .button:disabled[disabled], button.gs-add:disabled[disabled],
.btn:disabled, .button:disabled, button.gs-add:disabled,
.btn[aria-disabled="true"], .button[aria-disabled="true"],
.woocommerce a.button:disabled[disabled], .woocommerce button.button:disabled[disabled], .woocommerce input.button:disabled[disabled],
.woocommerce a.button:disabled, .woocommerce button.button:disabled, .woocommerce input.button:disabled {
  cursor: not-allowed;
  opacity: .5;
  color: rgb(var(--color-button-text));
  padding: 0 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Keyboard focus is three stacked box-shadows — a background-coloured pad so the
   ring reads on any fill, then two foreground rings. It is deliberately NOT an
   outline, because the ::after ring sits on top and an outline would be drawn
   under it. A plain `:focus` would paint this ring on mouse clicks too, which
   is why the `:not(:focus-visible)` reset exists. */
.btn:focus-visible, .button:focus-visible, button.gs-add:focus-visible,
.woocommerce a.button:focus-visible, .woocommerce button.button:focus-visible, .woocommerce input.button:focus-visible {
  outline: 0;
  box-shadow:
    0 0 0 .3rem rgb(var(--color-background)),
    0 0 0 .5rem rgba(var(--color-foreground), .5),
    0 0 .5rem .4rem rgba(var(--color-foreground), .3);
}
.btn:focus:not(:focus-visible), .button:focus:not(:focus-visible) { box-shadow: inherit; }

/* The field focus, measured off the reference's own input on 2026-10-14: a 2px
   ink outline 3.5px out, a background-coloured pad at .3rem, and the soft 30%
   glow at .5rem/.4rem — the same glow the button stack above carries. Fields
   had no focus style of their own until now and drew the browser's default
   ring. */
input[type="text"]:focus-visible, input[type="email"]:focus-visible,
input[type="tel"]:focus-visible, input[type="number"]:focus-visible,
input[type="password"]:focus-visible, input[type="search"]:focus-visible,
input[type="url"]:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid rgb(var(--color-foreground));
  outline-offset: 3.5px;
  box-shadow:
    0 0 0 .3rem rgb(var(--color-background)),
    0 0 .5rem .4rem rgba(var(--color-foreground), .3);
}

/* Secondary: the same box, meant for a NON-white ground — its resting ring is
   drawn in its own fill colour, so on a white page it shows no box at all (the
   trap recorded at `.gs-personalize-sticky`). The shop has had no non-white
   ground since 1.0.84, so nothing wears this tier today; when the promo
   sections come back, their CTA takes the primary tier (owner call 2026-10-14). */
.btn--secondary, .button--secondary {
  background: rgb(var(--color-secondary-button));
  color: rgb(var(--color-secondary-button-text));
}
.btn--secondary::after, .button--secondary::after {
  box-shadow:
    0 0 0 1px rgba(var(--color-secondary-button-text), 0),
    0 0 0 calc(var(--buttons-border-width) + 1px) rgba(var(--color-secondary-button), 1);
}
.btn--secondary:hover::after, .button--secondary:hover::after {
  box-shadow:
    0 0 0 calc(var(--buttons-border-width) + 1.3px) rgba(var(--color-secondary-button-text), 1),
    0 0 0 calc(var(--buttons-border-width) + 1px) rgba(var(--color-secondary-button), 1);
}

/* Tertiary: the small tier — icon buttons and light actions.

   THE THREE NOTICE ANCESTORS WEAR THIS BOX AND NOT THIS TIER'S GROUND. `View
   cart` in the added-to-cart bar, and any action a notice prints, comes out of
   WooCommerce's own template as `<a class="button wc-forward">` — markup this
   theme does not write, so there is no class to add.

   The box is here because of how the cascade counts. The base above carries
   `.woocommerce a.button` at (0,2,1) — that rule's own `a` is a type selector —
   so an element with both classes inside `.woocommerce` reads 56px and gold;
   measured 2026-10-15 by injecting one. `:is(a, button)` is what ties it: the
   notice ancestor is (0,1,0), `:is()` takes its most specific argument (0,0,1)
   and `.button` is (0,1,0). A bare `.woocommerce-message .button` is (0,2,0)
   and loses — that was the first version of this rule, and the button stayed
   56px. This rule is later in the file than the base, so the tie goes to it.

   The FILL AND THE RINGS STAY THE PRIMARY'S. A notice bar is a non-white ground,
   where this tier's transparent ground draws a ghost of a button — the owner's
   screenshot of 2026-10-15 — so the action keeps the gold, the ink and both rings
   it inherits from the base, and takes only the small box. Nothing here re-states a
   colour: the two things this rule blocks are the 56px box and the type. */
.btn--tertiary, .button--tertiary,
.woocommerce-message :is(a, button).button, .woocommerce-info :is(a, button).button, .woocommerce-error :is(a, button).button {
  min-width: 110px;
  min-height: 44px;
  padding: 12px 18px;
  font-size: 1.2rem;
}
.btn--tertiary, .button--tertiary {
  background: transparent;
  color: rgb(var(--color-button-text));
}
.btn--tertiary::after, .button--tertiary::after {
  box-shadow:
    0 0 0 1px rgba(var(--color-button-ring), 0),
    0 0 0 calc(var(--buttons-border-width) + 1px) rgba(var(--color-button), .2);
}
.btn--tertiary:hover::after, .button--tertiary:hover::after {
  box-shadow:
    0 0 0 calc(var(--buttons-border-width) + 1.3px) rgba(var(--color-button-ring), .2),
    0 0 0 calc(var(--buttons-border-width) + 1px) rgba(var(--color-button), .2);
}

/* ---------- home ---------- */

/* The hero: the owner's demo layout (`personalized_keepsakes_hero_demo.html`,
   2026-10-13) rebuilt on this theme's grid — the copy column, then a wall of
   three photos with the dominant one first. Every string, the design key and
   the photo index live in gs_home_hero(); this file only decides how they sit.
   The demo's own split is 5/12 copy to 7/12 photos. Ours comes out of the tiles
   instead of a fixed fraction: the two stacked photos are square because the
   sources are (1200x1200) and a landscape crop of them takes the bunny's head
   off — four bunny photos were cropped and checked — so the stack column is a
   third of the wall and the lead photo takes the rest. */
.hero { display: grid; gap: 28px; padding: 48px 0 8px; }
.hero__copy { min-width: 0; }

/* The headline is the demo's: 48px, weight 700, tight tracking, with its tail in
   the deep gold. That is a divergence from the source's collection hero —
   52px, weight 400 — and it is recorded in DESIGN.md. 700 is this face's
   ceiling; the demo asks for 800 and would be clamped here.
   The demo's eyebrow pill above it was dropped at the owner's request. */
.hero h1 { margin: 0 0 16px; font-size: 4rem; font-weight: 700; line-height: 1.15; letter-spacing: -.02em; }
.hero__accent { color: rgb(var(--color-gold-ink)); }
.hero__body { margin: 0 0 24px; color: var(--muted); max-width: 46ch; }
.hero__cta { margin: 0 0 28px; }

/* The three promises as chips above a hairline, the shape the demo draws them
   in. The emoji are part of each line's copy, so nothing is generated here. */
.hero__badges { display: flex; flex-wrap: wrap; gap: 10px; margin: 0; padding: 22px 0 0; border-top: 1px solid var(--line); list-style: none; }
.hero__badges li { padding: 5px 10px; border: 1px solid var(--line); border-radius: var(--radius-badge); background: rgb(var(--chip)); font-size: 1.2rem; font-weight: 600; }

/* The photo wall. The lead tile spans both rows; the two beside it are square,
   which is the sources' own shape and crops nothing. */
.hero__tiles { display: grid; grid-template-columns: minmax(0, 1fr) 33%; gap: 18px; margin: 0; padding: 0; list-style: none; }
.hero__tile { position: relative; overflow: hidden; border: 1px solid var(--line); border-radius: 18px; background: rgb(var(--chip)); transition: box-shadow .3s ease; }
.hero__tile:hover { box-shadow: 0 12px 28px rgba(var(--color-shadow), .18); }
.hero__tile--lead { grid-row: 1 / 3; }
.hero__tile-link { display: block; height: 100%; color: inherit; text-decoration: none; }
.hero__tile-img { display: block; width: 100%; height: 100%; object-fit: cover; transition: transform .5s ease-out; }
.hero__tile:not(.hero__tile--lead) .hero__tile-img { aspect-ratio: 1 / 1; }
.hero__tile:hover .hero__tile-img { transform: scale(1.05); }

/* The plate names what the photo shows, in the demo's frosted pill; the round
   chip beside it is the demo's arrow, drawn from the theme's own caret. The
   demo's pill carried a second, descriptive line — it is gone, because its
   wording did not match these products' own fields. */
.hero__plate { position: absolute; left: 12px; bottom: 12px; max-width: calc(100% - 72px); padding: 8px 12px; border: 1px solid rgba(var(--color-background), .7); border-radius: var(--radius-panel); background: rgba(var(--color-background), .88); box-shadow: 0 2px 10px rgba(var(--color-shadow), .12); backdrop-filter: blur(8px); font-size: 1.2rem; font-weight: 700; line-height: 1.3; }
.hero__tag { position: absolute; top: 12px; left: 12px; padding: 4px 9px; border-radius: 100px; background: rgb(var(--color-button)); color: rgb(var(--color-button-text)); font-size: 1.05rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
.hero__arrow { position: absolute; right: 12px; bottom: 12px; display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 50%; background: rgba(var(--color-background), .9); color: rgb(var(--color-foreground)); box-shadow: 0 2px 8px rgba(var(--color-shadow), .12); transition: transform .3s ease; }
.hero__arrow svg { width: 14px; height: 14px; transform: rotate(-90deg); }
.hero__tile:hover .hero__arrow { transform: translateX(4px); }

/* The reduced-motion overrides — hero, buttons, the sticky bar, the accordion
   caret — live in one block at the end of this file, where source order lets
   them outrank every rule that declares a transition. */

/* Below 750px the photos follow the copy: the lead tile takes the full width at
   4:5 and the two squares sit side by side under it — the demo's own phone
   arrangement. From 750px the copy and the wall sit side by side, 1 : 1.28,
   which is the 569/727 the tiles work out to at a 1350px container. */
@media (max-width: 749px) {
  .hero h1 { font-size: 2.5rem; }
  .hero__tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .hero__tile--lead { grid-column: 1 / 3; grid-row: auto; aspect-ratio: 4 / 5; }
}
@media (min-width: 750px) {
  .hero { grid-template-columns: minmax(0, 1fr) minmax(0, 1.28fr); gap: 54px; align-items: center; }
}

/* A section heading is a bare `h2` in the source — 31.2px, the step the base
   rule above already sets — with the browser's own 0.83em block margins (25.9px at
   this size, measured off their render).
   The 1.95rem this used to carry is the source's FOOTER-block heading, not its
   section heading: it was copied into the wrong slot, which is why every section
   title here was two-thirds the size it should be. No `font-size` on purpose — the
   two steps must not drift apart again.
   Weight 700 is a deliberate divergence: the source sets every ordinary section
   heading at its `--font-heading-weight` of 400 and bolds only "WHY CHOOSE US?".
   The owner asked for all of them bold, 2026-10-10. 700 is also the ceiling — the
   face is one variable file declared `font-weight: 400 700`.
   Both selectors share this rule so the four section headings cannot drift apart.
   The 700 belongs to this family and no other: the headings WooCommerce prints
   into its own cards — the cart's "Cart totals", the checkout's "Your order",
   the account page's sections — stay on the base tier's 400, and the 2026-10-14
   audit over ten pages confirmed that is how they render. Do not "unify" them
   by reflex. */
.section-title,
.related.products > h2 { text-align: center; font-weight: 700; }
.section-title { margin: .83em 0; }

/* ---------- product grid ---------- */

/* The source's four-column product grid. WooCommerce's own float layout is
   replaced here, so the archive and the related-products row agree. The
   selectors match WooCommerce's own, which is what it takes to win: its rules
   are `.woocommerce ul.products li.product` and carry the same weight. */
.woocommerce ul.products,
.woocommerce-page ul.products,
ul.products { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--grid-desktop-horizontal-spacing); list-style: none; margin: 0; padding: 0; clear: none; }
.woocommerce ul.products::before,
.woocommerce ul.products::after,
ul.products::before,
ul.products::after { content: none; }
.woocommerce ul.products li.product,
.woocommerce-page ul.products li.product,
ul.products li.product { width: auto; margin: 0; float: none; clear: none; }
/* WooCommerce's smallscreen sheet — loaded only below 768px — sets
   `ul.products[class*="columns-"] li.product { width: 48%; float: left; clear: both }`
   and floats every even item right. That is three classes deep, so the rule above at
   two lost to it on a phone and the RELATED products cards drew at half width inside
   the theme's single-column grid: 141px in a 293px cell, hugging the left edge.
   It reached only that row, because its selector needs a `columns-` class in the list
   and the home and archive grids carry a bare `products` class.
   `:nth-child(n)` looks redundant and is not: it matches every item and lifts this
   rule to four classes, which is what core's `:nth-child(2n) { float: right !important }`
   sits at. Without it, even-numbered cards stay floated right. */
.woocommerce ul.products[class*="columns-"] li.product:nth-child(n),
.woocommerce-page ul.products[class*="columns-"] li.product:nth-child(n),
ul.products[class*="columns-"] li.product:nth-child(n) { width: auto; margin: 0; float: none; clear: none; }
/* Two columns below 990px, all the way down — the source's `grid--2-col-tablet-down`
   holds two columns at every width under its desktop breakpoint, so a phone shows two
   cards side by side. This used to drop to ONE column under 560px. The row gap is the
   mobile vertical spacing and the column gap the horizontal one: the source's 14px
   and 6px are different numbers, and this rule used to pass the same one to both axes. */
@media (max-width: 989px) {
  .woocommerce ul.products, ul.products {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--grid-mobile-vertical-spacing) var(--grid-mobile-horizontal-spacing);
  }
}

/* The source clamps card names to ONE line everywhere. Its related-products
   section carries an ID-scoped clamp of its own, but the same one line is what
   its /collections/all grid draws — measured 2026-10-14. An earlier note here
   said "everywhere else keeps two" and clamped only `.related`; the note was
   wrong, the clamp lives on the base rule now, and the scoped rule is gone. */

/* The source's card: a rounded, hairline-bordered plate around the picture,
   with the name and the price beneath it. */
.product-card { display: block; color: inherit; text-decoration: none; }
.product-card-inner {
  display: block;
  padding: 10px;
  background: rgb(var(--color-background));
  border: 1px solid rgba(var(--color-card-ink), .5);
  border-radius: 18px;
}
/* The picture fills the plate. The ratio is declared on the image as well as on
   the plate: a percentage height does not resolve against a box whose height
   comes from aspect-ratio, and the ratio keeps the crop honest either way. */
.product-card-media { position: relative; display: block; aspect-ratio: 232 / 295; border-radius: 7px; overflow: hidden; background: rgb(var(--color-background)); }
.product-card-media img { display: block; width: 100%; aspect-ratio: 232 / 295; object-fit: cover; }
.product-card-body { display: block; padding: 8px 0 0; }
/* The reference card's own name: 19.2px/400 on a 23.2px line, tracked .78px,
   one line (measured 2026-10-14). Was 16px on a 20px line, tracked .69px, two
   lines.
   An `<h3>` since 1.0.107 — the reference's card title is `<h3 class="card__heading">`
   — because as a span the shop's cards appeared in no page's heading outline.
   The second selector is what makes that swap move no pixel: core ships
   `.woocommerce ul.products li.product h3 { padding: .5em 0; font-size: 1em }`
   at (0,3,2), which a bare class cannot reach, and it turned on the moment the
   element became an h3 — measured on the archive, the name box went 23.2 → 42.39px
   (two .5em pads around a clamped line) while the home grid, whose list carries no
   `.woocommerce` wrapper, stayed at 23.2. The chain here is core's own plus this
   class, so the values keep one home and win on both. */
.product-card-name,
.woocommerce ul.products li.product .product-card-name {
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
  margin: 0;
  padding: 0;
  font-size: 1.6rem; font-weight: 400; line-height: 23.2px; letter-spacing: .78px; color: rgb(var(--color-card-ink));
}
/* The card's price, on the source's own card figures: 1.6rem at 600, tracked
   .1rem and set on a 1.4167 line box. The size is the card's own, NOT the product
   page's 31.2px — the source keeps them apart too (19.2px on its cards against
   31.2px on the product page), and 31.2px here would be nearly double the 19.2px
   product name directly above it. What the card shares with the product page is
   the treatment: the sale price is red and the saving chip appears beside it.
   `display` comes from the shared `.price-row, .product-card-price` rule. */
.product-card-price { margin-top: 7px; font-size: 1.6rem; font-weight: 600; letter-spacing: .1rem; line-height: 1.4167; color: rgb(var(--color-foreground)); }

/* ---------- shop archive ---------- */

.archive-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 12px; margin: 32px 0 20px; }
.archive-head h1 { font-size: calc(var(--font-heading-scale) * 4rem); margin: 0; }
.woocommerce-ordering select { min-height: var(--inputs-height); padding: 0 12px; border: 1px solid var(--line-strong); border-radius: var(--radius-control); background: rgb(var(--color-background)); color: rgb(var(--color-foreground)); }

/* ---------- single product ---------- */

/* Two columns, as the source lays them out: the media takes 55% of the
   container and the buy column 45%, the buy column carrying its own 50px
   gutter. That split is measured off the source's render, not off its CSS:
   its stylesheet has a `.product--large` rule of 65/35, but the product pages
   it ships are `product--medium`, where that rule does not apply, and the
   photo actually draws 742px beside a 547px text column. Ours was 45/55,
   holding the photo at 495px. */
.product-layout { display: grid; grid-template-columns: 55% 45%; gap: 0; align-items: start; padding: 24px 0 0; }
.product-layout { display: grid; grid-template-columns: 55% 45%; gap: 0; align-items: start; padding: 24px 0 0; }
/* A grid item's automatic minimum size would let the thumbnail strip inside the
   media column stretch that column instead of scrolling within it. */
.product-layout > * { min-width: 0; }
@media (max-width: 940px) {
  .product-layout { grid-template-columns: minmax(0, 1fr); gap: 24px; }
}

.breadcrumb { margin: 20px 0 0; color: var(--muted); font-size: 1.425rem; }
.breadcrumb a { color: var(--muted); }

/* The source's media: a plain square, no frame and no rounding. */
.gallery-main { position: relative; background: rgb(var(--color-background)); }
.gallery-main img { display: block; width: 100%; }

/* The cover is one thing at a time: the store photo, or the live render. The
   `display: block` above would otherwise beat the [hidden] attribute and leave
   both laid out, stacking the photo under the render. */
.gallery-main [hidden] { display: none; }

/* The thumbnail strip, as the source draws it: one scrolling row of 116px
   squares with a bare 44x44 caret button in each gutter. Measured on its own
   render — the buttons are transparent, borderless and square, 1px 6px padding,
   the ink is the foreground at .75, and the caret is a 12x7.2 glyph turned a
   quarter turn so it points along the row. */
.gallery-thumbs-nav { display: flex; align-items: center; margin-top: 12px; }
.gallery-thumbs { flex: 1; display: flex; gap: 12px; overflow-x: auto; padding-bottom: 4px; }
.gallery-thumbs-arrow {
  flex: none; display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 1px 6px; border: 0; background: none;
  color: rgba(var(--color-foreground), .75); cursor: pointer;
}
.gallery-thumbs-arrow .icon-caret { width: 12px; height: 7.2px; }
.gallery-thumbs-arrow--prev .icon-caret { transform: rotate(90deg); }
.gallery-thumbs-arrow--next .icon-caret { transform: rotate(-90deg); }
/* The strip has no visible disabled state on the source either; the ends are
   simply not clickable. */
.gallery-thumbs-arrow[disabled] { cursor: default; }
.gallery-thumbs-arrow[hidden] { display: none; }
.gallery-thumbs button { flex: none; width: 116px; height: 116px; padding: 0; border: 1px solid var(--line); background: rgb(var(--color-background)); overflow: hidden; }
.gallery-thumbs button[aria-current="true"] { border-color: rgb(var(--color-foreground)); }
.gallery-thumbs img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The buy column is not a card: the source lays its contents straight on the
   page, offset by its own gutter. */
.product-column { padding: 0 0 0 50px; }
/* That gutter exists to separate the buy column from the photo. Once the two are
   stacked there is nothing to separate, and at 393px it was costing a seventh of
   the column — this has to come after the rule above, or the same specificity
   leaves it losing. */
@media (max-width: 940px) { .product-column { padding-left: 0; } }
/* Weight 400 — the source's own, and deliberately NOT a `100`. The face is one
   variable file declared `font-weight: 400 700`, so a 100 would be clamped straight
   back up to 400: the declaration would claim a value that never renders. The
   reference computes 400 here too, and loads no 100 face at all. */
.product-column h1 { font-size: 1.625rem; font-weight: 400; margin: 0 0 15px; }
/* Wraps when the row cannot fit. The product page's row and the product card's
   share this one declaration so the two cannot drift apart. */
.price-row,
.product-card-price { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; }
/* The product page's row. Scoped: the card keeps its own 1.6rem beneath this. */
.price-row { font-size: 1.5rem; font-weight: 400; color: rgb(var(--color-foreground)); }
/* The price is the largest thing under the title, as on the source page.
   Written as the heading step it is: `31.2px` here was the same value the scale
   block derives for 2.4rem, spelled a second way — one value with two homes. */
.price-row .price-now { font-size: calc(var(--font-heading-scale) * 2.4rem); font-weight: 600; letter-spacing: 1.56px; }
/* Red only ever means "this is discounted", so it is applied by a modifier class
   that PHP writes beside a struck price, never by .price-now on its own.
   Deliberately NOT scoped to .price-row: the product card carries the same red, and
   scoping it there is what left the card's sale price looking like a full price. */
.price-now--sale { color: rgb(var(--color-price-sale)); }
/* The saving, as a chip: -14%. Withheld on a price range, which has no single
   saving to report. */
.price-off {
  margin-left: 2px; padding: 4.6px 10px; border-radius: 14.4px;
  background: rgb(var(--color-price-off)); color: rgb(var(--color-on-accent));
  font-size: 1.2rem; font-weight: 700; line-height: 1;
}

/* The arrival card under the price. */
.shipping-options { margin: 15px 0; }
.shipping-option { display: inline-block; padding: 12px 18px; border-radius: var(--radius-plate); background: rgb(var(--color-subtle)); }
.shipping-option__type { margin: 0 0 2px; font-size: 16px; font-weight: 600; color: rgb(var(--color-shipping-type)); }
/* The date row's own tracking is .5px — measured 2026-10-14; size and weight
   already matched the reference's. */
.shipping-option__date { font-size: 14px; line-height: 25.2px; letter-spacing: .5px; color: rgb(var(--color-shipping-date)); }
.shipping-option__date strong { font-weight: 700; color: rgb(var(--color-shipping-strong)); text-decoration: underline; }

.section-label { margin: 20px 0 6px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; font-size: 1.275rem; color: var(--muted); }

.personalizer-actions { display: flex; flex-direction: column; gap: 12px; margin-top: 16px; }

/* "Preview Your Design" is the one deliberate exception to the button scale, and
   the owner has said twice to leave it. It is a full-width white bar, not an
   action button: 60px tall at an 8px radius, 16px at weight 500, sentence case,
   hairline border. Do not "fix" it to the 56px / 4px primary tier — it reads as
   a different kind of control precisely because it is one. */
.preview-btn {
  width: 100%; min-height: 60px; padding: 8px 16px;
  border: 1px solid rgb(229, 229, 229); border-radius: var(--radius-control);
  background: rgb(var(--color-background)); color: rgb(var(--color-foreground));
  font-size: 16px; font-weight: 500; letter-spacing: normal; text-transform: none;
}
.preview-btn:hover:not(:disabled) { background: rgb(var(--color-subtle)); }

/* The scale's disabled treatment, which this key did not have (1.0.132). Every
   render disables it through `gsBusyUntil` — 80–500 ms when the design draws,
   the full 5 s when a capture gives up — and until now it looked exactly like an
   idle button for all of it. The same two values the scale's own disabled rule
   uses (opacity .5, `not-allowed`), without the tier's `padding: 0 36px`: this
   key is a written exception and does not share the tier's box. */
.preview-btn:disabled { opacity: .5; cursor: not-allowed; }

/* The sticky shortcut back to the form: the action tier, bottom left, with
   only its top corners rounded. Its shape is the point — it is pinned to the
   viewport edge, so it is deliberately outside the button scale too.
   It was black, `rgb(0,0,0)` on `rgb(239,240,245)`, the source's own bar;
   the black went with the black header (owner decision 2026-10-13). What it
   takes instead is the gold, for two reasons: it floats over a white page, where
   a white or chip fill draws almost no box at all — the trap `.btn--secondary`
   already paid for — and it is an action ("Personalize it"), not a control. */
.gs-personalize-sticky {
  position: fixed; left: 16px; bottom: 0; z-index: 6;
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: min(50%, 735px); height: 40px; padding: 1px 6px;
  border: 0; border-radius: 20px 20px 0 0;
  background: rgb(var(--color-button)); color: rgb(var(--color-button-text));
  font-size: 16px; font-weight: 600; cursor: pointer;
}
/* The source draws this over a modal; ours sits on the page, so it is hidden
   once the buy button has been scrolled past and is covering nothing but copy. */
.gs-personalize-sticky.is-past-buy { opacity: 0; visibility: hidden; pointer-events: none; }
.gs-personalize-sticky { transition: opacity .18s ease; }
.gs-personalize-sticky svg { width: 20px; height: 20px; }

.secure-note { margin: 12px 0 0; text-align: center; color: var(--muted); font-size: 1.35rem; }

/* Sticky media column on wide screens, matching the source store. */
@media (min-width: 941px) {
  .product-media, .product-column { position: sticky; top: 20px; }
}

/* ---------- product column details ---------- */

/* For screen readers only, exactly as the source store marks its price labels. */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* The social-proof line at the reference's own figures — 18px/600 on a 30px
   box, tracked .72px (measured 2026-10-14; ours was 14/500/.6). */
.bought-badge { margin: 8px 0; font-size: 1.5rem; line-height: 30px; letter-spacing: .72px; font-weight: 600; color: rgb(var(--color-bought)); }
.product-tax { margin: 0; min-height: 0; }
.price-was { color: var(--muted); font-weight: 400; }

/* Quantity stepper and accepted-payment row. */
.gs-quantity { margin-top: 14px; }
/* The reference's selector labels are one family — its own `.form__label`:
   20.4px/700 on a 1.4167 line box, tracked .48px, in the muted ink (measured
   2026-10-14). The Quantity label, the size legend and the colour legend all
   take it; ours was 1.35rem/700 with the page's inherited tracking. */
.qty-label { display: block; margin-bottom: 6px; font-weight: 700; font-size: 1.7rem; letter-spacing: .48px; line-height: 1.4167; color: var(--muted); }
.gs-quantity .qty { display: inline-flex; align-items: center; border: 1px solid var(--line-strong); border-radius: var(--inputs-radius-outset); height: var(--inputs-height); padding: 0 6px; background: rgb(var(--color-background)); }
/* The stepper's own keys carry no colour of their own, so the UA's default
   `buttontext` painted the − and + pure black — a fourth ink on a page whose
   text is `14,14,14`. Name the page's ink instead. */
.gs-quantity .qty button { width: 44px; height: 100%; border: 0; background: none; color: rgb(var(--color-foreground)); font-size: 20px; cursor: pointer; }
.gs-quantity .qty input {
  width: 64px; height: 40px; text-align: center; border: 0; background: transparent;
  color: rgb(var(--color-foreground));
  font-weight: 700; -moz-appearance: textfield;
}
.gs-quantity .qty input::-webkit-outer-spin-button,
.gs-quantity .qty input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

.gs-payments { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 14px; }
.gs-payment { display: block; width: 38px; height: 24px; }
/* A method with no brand asset (Amazon Pay, Link) shows as a text chip. */
.gs-payment--text {
  width: auto; height: auto; padding: 6px 12px; border: 1px solid var(--line); border-radius: var(--radius-badge);
  background: rgb(var(--color-background)); font-size: 1.17rem; font-weight: 700; letter-spacing: .03em; color: var(--muted);
}

/* "View full details" is not rendered: the source keeps it hidden. */

/* ---------- promo sections ---------- */

.promo { margin: 56px 0 0; padding: 32px 0 0; border-top: 1px solid var(--line); }
.promo h2 { font-size: 1.95rem; margin: 0 0 8px; }
.promo-intro { color: var(--muted); margin: 0 0 16px; max-width: 52ch; }
.promo-blocks { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; margin-top: 24px; }
@media (max-width: 760px) { .promo-blocks { grid-template-columns: minmax(0, 1fr); } }
.promo-block h3 { margin: 0 0 6px; font-size: 1.6rem; }
.promo-block p { margin: 0; color: var(--muted); }

/* The announcement bar. The source gives it the page's own scheme, a 1200px
   inner width and a hairline underneath. Its message sizes are the values the
   live page renders; the source derives them from its own rem base.
   The 400 weight is measured on the source store, which sets
   .announcement-bar__message at font-weight 400. The line was 700 here and read
   louder than the header below it. Only the promo code is bolded, so the
   one part a shopper has to remember still stands out.
   The seam is the owner's `#F3F4F6` (2026-10-13), not the ink's 8% hairline.
   That hairline resolved to `#ECEDEE` here — 1.18:1 against the white under it —
   and the specified line is lighter still at 1.10:1, which is what was asked
   for. The value itself lives in the top-bar scheme (`--seam`), because the
   checkout's bare header draws the same line and there must be one source for
   it. */
.announcement-bar {
  padding: 10px 0;
  background: rgb(var(--color-background));
  color: rgb(var(--color-foreground));
  text-align: center;
  border-bottom: 1px solid var(--seam);
}
.announcement-bar__message { margin: 0; font-size: 1.6rem; font-weight: 400; }
.announcement-bar__message strong { font-weight: 700; }

/* The bar prints both lines and these two rules pick one. The full sentence is
   the owner's, word for word, and below 750px it wrapped to two lines — 85px of
   the first screen on every page of the shop, measured. The phone gets the same
   offer in one line, from gs_announcement_text_short(). */
.announcement-bar__short { display: none; }
@media (max-width: 749px) {
  .announcement-bar__long { display: none; }
  .announcement-bar__short { display: inline; }
}

/* ---------- accordions ---------- */

/* The source's accordions: its h4 heading tier — 19.5px/400 on a 1.277 line
   box, tracked .78px (measured 2026-10-14; this was a plain 16px row until
   1.0.107) — with its own mark before the heading, and a hairline above the
   first and below the last. */
.accordion { margin: 25px 0 0; }
.accordion details { border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.accordion details + details { border-top: 0; }
/* The source's row: 18px of padding, the title in the full foreground colour
   rather than the muted one, a 26px mark before it, and a caret at the far end
   that flips as the panel opens. The flip is the source's own
   --duration-default, written out as .2s. This theme has no motion tokens: its
   six transitions each write their own duration — the button's box-shadow, the
   hero's three, the sticky bar's opacity and this caret — and the reduced-motion
   block at the end of this file quiets them all. */
.accordion summary {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  cursor: pointer; list-style: none;
  padding: 18px 0;
  font-size: calc(var(--font-heading-scale) * 1.5rem);
  line-height: var(--font-heading-line-height);
  letter-spacing: var(--font-heading-letter-spacing);
  color: rgb(var(--color-foreground));
}
.accordion summary::-webkit-details-marker { display: none; }
.accordion .summary__title { display: flex; align-items: center; gap: 12px; flex: 1; }
.accordion .summary__title svg { width: 26px; height: 26px; flex: none; }
.accordion .summary__caret { display: block; flex: none; width: 10px; height: 6px; transition: transform .2s ease; }
.accordion .summary__caret svg { width: 10px; height: 6px; display: block; }
.accordion details[open] > summary .summary__caret { transform: rotate(180deg); }
/* The source indents the panel body by .6rem. */
.accordion .accordion-body { padding: 0 0 18px 7px; color: var(--muted); }
.accordion .accordion-body h4 { color: rgb(var(--color-foreground)); }

/* ---------- the star rating under the title ---------- */

/* Hidden entirely until a product has a review: an empty "0 reviews" says less
   than nothing. The filled stars use the button colour, which is also the star
   colour the source's review widget uses. The text is the source badge's
   own: 19.2px/400 on a 1.4 line box (measured 2026-10-14; ours was 15px on a
   20px box). */
.gs-rating {
  display: inline-flex; align-items: center; gap: 7px;
  margin: 0 0 4px; color: var(--muted);
  font-size: 1.6rem; line-height: 1.4; text-decoration: none;
}
.gs-rating:hover { color: rgb(var(--color-foreground)); }
.gs-rating__stars { display: inline-flex; gap: 2px; }
.gs-rating__star { display: block; width: 16px; height: 16px; color: var(--line-strong); }
.gs-rating__star svg { display: block; width: 100%; height: 100%; }
.gs-rating__star.is-on { color: rgb(var(--color-button)); }

/* ---------- the delivery timeline ---------- */

/* Three points on a line, in the source's own shape: a cart, a box and a
   house, each inside a 50px circle, a 2px connector between them and the step
   names below. Every number is the component's, read out of the captured
   product page — 50px circle, 35px icon, 2px line, 1.6rem step type with the
   date at weight 600, the end steps left- and right-aligned, and its own
   600px break where the type goes to 1rem and the line to 1px.
   The artwork is Bootstrap Icons (MIT), inlined by gs_icon() so it inherits
   currentColor; the source paints its <img> copies with a CSS filter.
   The circle is that component's rgb(237,237,237) — 7% black on white —
   written as a token so a repainting scheme darkens it too. */
.gs-timeline { margin: 25px 0 0; }
.gs-timeline__icons { display: flex; align-items: center; justify-content: space-between; padding-top: 10px; }
.gs-timeline__badge {
  display: flex; align-items: center; justify-content: center;
  width: 50px; height: 50px; border-radius: 50%;
  background: rgba(var(--color-foreground), .07);
}
.gs-timeline__badge svg { display: block; width: 35px; height: 35px; }
.gs-timeline__line { flex: 1 1 auto; height: 2px; background: rgb(var(--color-foreground)); }
.gs-timeline__steps { display: flex; justify-content: space-between; list-style: none; margin: 0; padding: 0; }
.gs-timeline__step { padding: 10px 0; font-size: 1.6rem; text-align: center; }
.gs-timeline__step:first-child { text-align: left; }
.gs-timeline__step:last-child { text-align: right; }
.gs-timeline__time { display: block; font-weight: 600; }
.gs-timeline__label { display: block; }
.gs-timeline__note { margin: 14px 0 0; font-size: 13px; line-height: 18px; color: var(--muted); }
@media (max-width: 600px) {
  .gs-timeline__step { font-size: 1rem; }
  .gs-timeline__line { height: 1px; }
}

/* ---------- reviews ---------- */

/* The review block, in the source's shape: a 1200px column centred in the page,
   a centred heading, then one row holding what the reviews score, how they are
   distributed, and the key that opens the form. The 1200px cap and the 24px/16px
   padding are the source's own — its Judge.me widget is `max-width: 1200px`
   inside a wider page and carries `padding: 24px 16px`.
   Line-height 1.4 for the whole block: every one of the source's widget sizes
   runs on that ratio (28.8px title on 40.32px, 19.2px body on 26.88px, 15.36px
   count on 21.5px), against the 1.6667 the shop's body copy uses. */
.gs-reviews { max-width: 1200px; margin: 0 auto; padding: 24px 16px; line-height: 1.4; }
/* The 1.4 has to be restated here: headings carry their own line-height on the
   base rule (`--font-heading-line-height`, 1.277), so they do not inherit the
   block's. Left unstated the title's box came out 36.78px against the source's
   40.32px. */
.gs-reviews__title { margin: 0 0 24px; font-size: 2.4rem; line-height: 1.4; text-align: center; color: rgb(var(--color-foreground)); }

/* The summary row: score column 350px, distribution 467px, action key 350px — the
   reference's own three widths, which together fill its 1168px content box.
   `align-items: stretch` is what makes the three the same height, and the centring
   is what the source does: with all three columns present it is a no-op, and
   with the distribution hidden it moves the pair into the middle. */
.gs-reviews__summary { display: flex; align-items: stretch; justify-content: center; padding: 0 0 24px; }
.gs-reviews__score-column { flex: 0 1 350px; }

/* 19.2px at weight 400, and the stars sit ON the line rather than above it: the
   reference draws "★★★★★ 5.00 out of 5" as one run at one size, with the stars
   ahead of the words. Neither part is bold. */
.gs-reviews__score { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 1.6rem; font-weight: 400; color: var(--muted); }
.gs-reviews__based { margin: 0; font-size: 1.6rem; font-weight: 400; color: var(--muted); }
.gs-reviews__empty { margin: 0; font-size: 1.6rem; font-weight: 400; color: var(--muted); }

/* The star rows are the same `.gs-rating__star` atoms the rating link under the
   product title uses — one star implementation in the theme, three sizes. Each
   size comes from the source's own glyph pitch rather than from eye: its
   summary stars advance 28px each, its histogram's five span 84px (16.8px each),
   and its empty state's stars advance 21px each.
   The source's stars are gold whether they are lit or not — an unlit one is the
   same gold drawn hollow, not a grey star. Ours is one filled glyph, so the hollow
   state is the glyph stroked with no fill, scoped here and left alone in the
   rating row under the title. */
.gs-reviews__stars { display: inline-flex; align-items: center; gap: 8px; margin: 0; }
.gs-reviews__stars .gs-rating__star { width: 20px; height: 20px; color: rgb(var(--color-button)); }
.gs-reviews__stars .gs-rating__star:not(.is-on) svg { fill: none; stroke: currentColor; }
.gs-reviews__stars--empty { gap: 6px; }
.gs-reviews__stars--empty .gs-rating__star { width: 15px; height: 15px; }

/* The distribution. The track's grey, its 14px height, its 10px radius, its fixed
   140px width and the count's grey are all taken from the source verbatim; the
   fill is black here, not the source's brand navy.
   The bars column fills whatever the two 350px columns leave (467px of the
   reference's 1168), but each row inside it is content-sized and centred — the
   reference's rows measure 303px in a 467px cell, not 467. Its row is 14px tall
   because its line box is pinned to the bar's height, not to the text's. */
.gs-reviews__bars { flex: 1 1 467px; list-style: none; margin: 0; padding: 0; }
.gs-reviews__bar-row { display: flex; align-items: center; width: fit-content; margin: 0 auto 10px; font-size: 1.28rem; line-height: 14px; color: var(--muted); }
.gs-reviews__bar-label { display: inline-flex; flex: 0 0 84px; gap: 3px; }
.gs-reviews__bar-label .gs-rating__star { width: 14px; height: 14px; }
.gs-reviews__bar-track { flex: 0 0 140px; height: 14px; margin: 0 24px; border-radius: var(--radius-plate); background: rgba(224, 224, 224, .5); overflow: hidden; }
.gs-reviews__bar-fill { display: block; height: 100%; border-radius: var(--radius-plate); background: rgb(var(--color-review-button)); }
.gs-reviews__bar-count { flex: 0 0 24px; color: rgb(123, 123, 123); font-variant-numeric: tabular-nums; }

/* The row's third column, where the source puts the key that opens the form. */
.gs-reviews__actions { flex: 0 1 350px; }

/* Below the width where the three columns stop fitting, they stack: the score
   column, then the distribution, then the action key, each full width. The 350px
   and 467px figures above are the source's, and 350 + 467 + 350 cannot fit a
   phone. 989px is this theme's existing two-column breakpoint. */
@media (max-width: 989px) {
  .gs-reviews__summary { flex-wrap: wrap; }
  .gs-reviews__score-column,
  .gs-reviews__bars,
  .gs-reviews__actions { flex: 1 1 100%; min-width: 0; }
  .gs-reviews__bars { margin-top: 16px; }
  .gs-reviews__actions { margin-top: 16px; }
  /* The row's parts are 84 + 24 + 140 + 24 + 24 = 296px, which does not fit a
     phone's column. Left alone the row keeps that width and refuses to shrink —
     `min-width: auto` on a flex item is its content width — so it broke out through
     the block's own 16px padding: measured at 393px it drew 296px wide from x49 in a
     261px column starting at x66. The row now takes the column's width and the track
     gives up its fixed 140px and flexes. Above this breakpoint the 140px stays, and
     that is the source's own value. */
  .gs-reviews__bar-row { width: auto; margin: 0 auto 10px; }
  .gs-reviews__bar-track { flex: 1 1 auto; min-width: 0; }
}

/* Sorting. WooCommerce 11 cannot sort reviews, so gs_order_product_reviews() in
   inc/woocommerce.php does it and this form only carries the choice. */
.gs-reviews__sort { display: flex; align-items: center; gap: 10px; margin: 0 0 20px; }
/* The caret is drawn, not the browser's: `appearance: none` drops the native
   arrow and the theme's own glyph sits 14px in from the pill's edge, the same
   10x6px caret the gallery arrows and the accordions use. It takes no pointer
   events, so a click on it opens the select underneath. */
.gs-reviews__sort-select { position: relative; display: inline-flex; align-items: center; }
.gs-reviews__sort-select .icon-caret {
  position: absolute; right: 14px; width: 10px; height: 6px;
  color: rgb(var(--color-foreground)); pointer-events: none;
}
.gs-reviews__sort select {
  /* 40px, NOT --inputs-height. This control sits beside its own 40px pill
     submit, and the source's matching pair is 40px as well (its Judge.me
     sort dropdown measures 39.6px). An input has to match the control it is used
     with, not the shop-wide figure. */
  appearance: none; -webkit-appearance: none;
  min-height: 40px; padding: 0 34px 0 12px;
  border: 1px solid rgb(var(--color-foreground)); border-radius: 20px;
  background: rgb(var(--color-background)); color: rgb(var(--color-foreground));
  font-family: inherit; font-size: 14px; font-weight: 400;
}
.gs-reviews__sort-submit {
  min-height: 40px; padding: 0 20px;
  border: 2px solid rgb(var(--color-review-button)); border-radius: 25px;
  background: rgb(var(--color-background)); color: rgb(var(--color-review-button));
  font-family: inherit; font-size: 14px; font-weight: 700; letter-spacing: .6px; cursor: pointer;
}
/* The sort Apply is a tertiary-tier control: it sits beside a 40px select, so it
   takes the small box rather than the 56px one. Its own hover inverts the fill,
   which is what a control of this weight does on this page. */
.gs-reviews__sort-submit:hover { background: rgb(var(--color-review-button)); color: rgb(var(--color-on-accent)); }

/* A primary button, so it is the primary tier. It carries `width: auto` only —
   everything else (the 56px box, the rings, the hover, the focus) comes from the
   button base, and nothing here may restate it: a hand-copied baseline is how
   the site ended up with two button styles. */
.gs-reviews__write { width: auto; }
#review_form_wrapper[hidden] { display: none; }

/* The gap above the related row. The source's own section carries an inline
   `padding-top: 80px; padding-bottom: 48px` from 750px up, `60px / 36px` below,
   and its `--spacing-sections-desktop` and `-mobile` are both `0px` — so that
   padding IS the whole gap there. Only the top is taken here: the bottom would
   also move the space above the reviews. A deliberate departure, recorded at
   1.0.59 in the changelog. */
.related.products { padding: 60px 0 0; }
@media (min-width: 750px) { .related.products { padding-top: 80px; } }

/* The heading above the related products. A bare `h2` in the source, whose own
   rule is only `margin: 0 0 3rem` — the size comes from the element. Centred and
   bold come from the shared section-heading rule near `.section-title`; only the
   bottom gap differs here.
   The centring is the source's, not ours: its `section-related-products.css`
   sets no text-align, but the section carries a per-section inline rule —
   `#shopify-section-…__related-products .related-products__heading { text-align:
   center }` — which is what actually paints it. Weight 700 IS ours: the source
   sets this one at its `--font-heading-weight` of 400 like every other section
   heading. That divergence is deliberate. */
.related.products > h2 { margin: 0 0 36px; }

.star-rating { color: rgb(var(--color-foreground)); letter-spacing: 2px; }

/* ---------- footer ---------- */

/* The source's footer: four link columns on the page's own scheme, then a
   hairline and a bottom bar carrying the payment marks and the copyright. */
.site-footer { padding: 44px 0 60px; background: rgb(var(--color-background)); color: var(--muted); }
.footer__content-top { padding: 0 50px 50px; }
.footer__blocks-wrapper { display: flex; flex-wrap: wrap; gap: 60px 28px; }
.footer-block { flex: 1 1 254px; min-width: 0; }
.footer-block__heading { margin: 0 0 20px; font-size: 1.95rem; color: rgb(var(--color-foreground)); }
.footer-block__details-content { font-size: 16px; line-height: 28.8px; }
.footer-block__details-content p { margin: 0; }
.footer-block__links { margin: 0; padding: 0; list-style: none; }
/* The generic footer link. `.btn` is excluded on purpose: this selector is a
   descendant plus a type, which outranks the bare `.btn` class, so without the
   `:not()` it would hand the footer buttons a 14px font, a muted colour and a
   bottom pad — silently, and only in the footer.
   The pad is what carries the link to 44px, this shop's control height: the line
   box is 28px, so 16px under it makes the whole box 44px tall. Measured 33px
   before (28 + a 5px pad). */
.footer-block__details-content a:not(.btn) { display: inline-block; padding: 0 0 16px; font-size: 1.4rem; line-height: 28px; color: var(--muted); text-decoration: none; }
.footer-block__details-content a:not(.btn):hover { text-decoration: underline; }

/* The two support buttons in the footer's "Get in touch" column. The only thing
   that is theirs is the width and the top margin — everything else, including the
   centring, comes from the base.

   `display: block` used to be declared here to beat the generic
   `.footer-block__details-content a` rule. That rule now excludes `.btn`, so the
   override is not only unnecessary but actively harmful: it overrides the base's
   `inline-flex`, which is what centres the label. With it in place the label fell
   back to `text-align: start` and sat hard against the left edge of a 293px
   button. Do not put `display` back. */
.footer-block__details-content .btn.footer-block__button {
  width: 100%; margin: 14px 0 0; text-decoration: none;
}

.footer__content-bottom { padding: 30px 0 0; border-top: 1px solid var(--line); }
.footer__content-bottom-wrapper { display: flex; flex-wrap: wrap; align-items: flex-start; padding: 0 50px; }
.footer__column--info { width: 50%; margin-left: auto; }
.footer__copyright { width: 100%; margin: 15px 0 0; font-size: 1.2rem; line-height: 22.8px; color: var(--muted); }
/* These policy links sit inside a sentence, so they are lifted to WCAG 2.2's 24px
   floor rather than to the 44px a real control gets here. Measured 17.5px before:
   an inline box is only as tall as its font, whatever the line-height says.
   `inline-block` is what lets the pad count at all, and the pad is symmetric so
   the text keeps its place on the baseline. The line box does grow by the pad —
   8px a line, and the row wraps at 393px — which is why 4px and not 11px. */
.footer__copyright a { display: inline-block; padding: 4px 0; color: var(--muted); }

/* ---------- notices and generic pages ---------- */

.pb-page { padding: 32px 0 60px; }
.pb-page h1 { font-size: calc(var(--font-heading-scale) * 4rem); }

/* The source steps h1 down to 3rem below 749px, which the bare `h1` in the
   scale block does — but these two rules carry a font-size of their own, and a
   class beats the element whatever media query the element's rule sits in.
   Restated here so a page title is 39px on a phone and not 52px; measured at
   393px before this rule: 52px/66.4px on every page but the home page, whose
   hero already declares its own 2.5rem step. */
@media (max-width: 749px) {
  .archive-head h1,
  .pb-page h1 { font-size: calc(var(--font-heading-scale) * 3rem); }
}

/* ---------- home sections ---------- */

.home-trust {
  margin: 34px 0 0; padding: 28px 0;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
/* The gold disc both trust rows draw: the badge row above the categories and the
   why-cards inside them. The ink is here and the mark declares `currentColor`,
   so one rule paints every one of them; the two rows differ only in size, which
   is each row's own business. */
.gs-disc { display: flex; align-items: center; justify-content: center; flex: none; border-radius: 50%; background: #fbc54c; color: #fff; }

.home-trust__list { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 24px; margin: 0; padding: 0; list-style: none; }
.home-trust__item { display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center; }
.home-trust__icon { width: 64px; height: 64px; }
.home-trust__icon svg { width: 30px; height: 30px; }
.home-trust__label { font-size: 1.425rem; font-weight: 700; letter-spacing: .02em; color: rgb(var(--color-foreground)); }

/* The anniversary banner that used to sit here — `.home-banner`, its heading at
   `2.3333rem`/800 and its gold `CELEBRATE WITH US` button — was removed on
   2026-10-13 at the owner's request. It carried the home page's only dark ground
   and the only `--color-button-ring` override for one; with it gone, every
   ground in the shop is light and the ring is read against a light page
   everywhere. The rules are in this file's git history. */

/* One section per product category: its name, every product in it and a button to
   its archive page. Only the gap and the button's row are set here — the heading
   is the shared `.section-title`, the grid is the shop's own `ul.products`, and the
   button is the primary tier, which is the one that paints a box on a white ground
   (`.btn--secondary` fills white and its resting ring is white too, so on a white
   page it drew no box at all). Nothing about a card, a heading or a button is
   restated here; the gap matches the other home sections' 44px. */
.home-category { margin-top: 44px; }
.home-category__cta-wrap { margin: 28px 0 0; text-align: center; }

/* The review photos: a strip that moves, two rows of them, the way the source
   draws its own. The track holds the list twice, and 50% of the track is exactly
   one list, so the end frame of the animation is its start frame and the seam
   cannot show.

   Every measure below is the source's strip as its own page computes it:
   a column 288px wide at this window holding two cards, each card a square
   cover-cropped photo over a caption band; card radius 14.4px, a 1px #d1d5db
   edge, the band padded 9.6px 12px 12px and filled rgba(14,14,14,.07) — not
   --line, which is this theme's .08; 19px between photos and between rows; and
   every other column pushed 24px down, which is what makes the top edge of the
   reference's strip ragged. Its caption prints `@handle` there because its
   photos come from handles; this shop's reviews carry names, so the name is what
   the caption says. The column falls to 160px below about 755px, where the
   clamp's own step takes it there.

   121s is ours, and it is a speed rather than a number pulled from the
   reference: the source's strip is scrolled by its own script, so its speed
   could not be read from outside. 15 columns make a 4,624px list, so 121s moves
   it 38px/s — a third of the 115px/s the one-row draft ran at. */
.home-review-photos { margin-top: 44px; }
.home-review-photos__viewport { margin-top: 24px; overflow: hidden; }
.home-review-photos__track { display: flex; width: max-content; animation: gs-review-strip 121s linear infinite; }
.home-review-photos__list { display: flex; align-items: flex-start; gap: 19px; margin: 0; padding: 0 19px 0 0; list-style: none; }
.home-review-photos__column { flex: none; display: flex; flex-direction: column; gap: 19px; width: clamp(160px, 0.75rem + 20vw, 288px); }
/* The odd columns drop 24px, so the two rows are not one flat block. */
.home-review-photos__column:nth-child(odd) { margin-top: 24px; }
.home-review-photos__tile { margin: 0; border: 1px solid #d1d5db; border-radius: 14.4px; overflow: hidden; background: rgb(229,231,235); }
.home-review-photos__tile img { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; }
.home-review-photos__name { padding: 9.6px 12px 12px; background: rgba(var(--color-foreground), .07); color: rgb(17,24,39); font-size: 13.2px; font-weight: 700; line-height: 22px; letter-spacing: .72px; }
/* The strip stops for a pointer over it and for a keyboard that has tabbed onto
   it — WCAG 2.2.2 wants a way to stop movement that runs on its own, and a strip
   is not something a keyboard can hover. Focusing it also hands the row back as
   a scrollable one, so the photos it hid behind the animation stay reachable. */
.home-review-photos__viewport:hover .home-review-photos__track,
.home-review-photos__viewport:focus-within .home-review-photos__track { animation-play-state: paused; }
.home-review-photos__viewport:focus-within { overflow-x: auto; }
@keyframes gs-review-strip { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) {
  .home-review-photos__track { animation: none; }
  .home-review-photos__viewport { overflow-x: auto; }
}

.home-why { margin-top: 44px; }
.home-why__list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin: 0; padding: 0; list-style: none; }
/* The source's why-card, value for value: white ground inside a 1.5px black
   edge at a 20px radius, padding 20px 16px, contents centred — and the lift on
   hover (its own #f5f5f5, a -4px rise and that shadow, over .3s). */
.home-why__item { display: flex; flex-direction: column; align-items: center; padding: 20px 16px; border: 1.5px solid #000; border-radius: 20px; background: #fff; text-align: center; transition: .3s; }
.home-why__item:hover { background: #f5f5f5; transform: translateY(-4px); box-shadow: 0 8px 24px rgba(0, 0, 0, .1); }
/* Its 48px gold disc, its 22px mark and the 12px under the disc. The disc itself
   is `.gs-disc`, shared with the badge row above the categories. */
.home-why__icon { width: 48px; height: 48px; margin-bottom: 12px; }
.home-why__icon svg { width: 22px; height: 22px; }
/* The source's why-card title is a `div` in the body context, not a heading:
   18px at weight 700 with the BODY tracking (.06rem = 0.72px) and the body line
   height (1.6667 = 30px), sitting 6px above its paragraph. It is an `h3` here, so
   the heading tracking has to be undone explicitly — .48px read tighter than the
   reference by a third. */
.home-why__heading { margin: 0 0 6px; font-size: 1.5rem; font-weight: 700; letter-spacing: var(--font-body-letter-spacing); line-height: 1.6667; }
/* And its paragraph is small print, not body copy: 14px in #555 on a 1.5 line
   height, which is well under this theme's own body step. */
.home-why__body { margin: 0; font-size: 14px; color: #555; line-height: 1.5; }

/* Under 768px the source stacks the cards and turns each one on its side —
   the disc on the left, the text beside it, left-aligned — and steps every size
   down one notch. 768px is the source's own breakpoint, not this theme's
   749px: the section is kept as it draws it. */
@media (max-width: 768px) {
  .home-why__list { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .home-why__item { flex-direction: row; gap: 14px; padding: 16px; text-align: left; }
  .home-why__icon { margin-bottom: 0; }
  .home-why__heading { font-size: 16px; }
  .home-why__body { font-size: 13px; }
}

@media (max-width: 989px) {
  .home-trust__list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 749px) {
  .home-trust__list { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- predictive search results ---------- */

.search-modal__results {
  position: absolute; top: 100%; left: 60px; right: 60px; z-index: 30;
  max-width: 828px; margin: 8px auto 0; max-height: 60vh; overflow: auto;
  background: rgb(var(--color-background)); border: 1px solid var(--line); border-radius: var(--radius-panel);
  box-shadow: 0 12px 30px rgba(0, 0, 0, .12);
}
.search-modal__results[hidden] { display: none; }
.search-results-list { list-style: none; margin: 0; padding: 6px; }
.search-result__link { display: flex; align-items: center; gap: 12px; padding: 8px; border-radius: var(--radius-control); text-decoration: none; color: rgb(var(--color-foreground)); }
.search-result__link:hover { background: rgb(var(--chip)); }
.search-result__thumb { width: 48px; height: 48px; object-fit: cover; border-radius: var(--radius-badge); }
.search-result__body { display: flex; flex-direction: column; }
.search-result__title { font-weight: 700; }
.search-result__price { font-size: 1.275rem; color: var(--muted); }
.search-results__all { display: block; padding: 10px 14px; border-top: 1px solid var(--line); font-weight: 700; text-decoration: none; color: rgb(var(--color-foreground)); }

/* Reduced motion: no zoom, no nudge, no flip, no fade — the six transitions
   above (buttons, the hero's three, the sticky bar, the accordion caret) each
   declare their own duration and win ties by source order, so this block sits
   last on purpose. Keep it the last rule in this file. */
@media (prefers-reduced-motion: reduce) {
  .btn, .button, button.gs-add, .woocommerce a.button, .woocommerce button.button,
  .woocommerce input.button, .woocommerce #respond input#submit, .wc-block-components-button,
  .hero__tile, .hero__tile-img, .hero__arrow,
  .gs-personalize-sticky, .accordion .summary__caret,
  .home-why__item { transition: none; }
  .hero__tile:hover .hero__tile-img, .hero__tile:hover .hero__arrow { transform: none; }
  /* The why cards' lift is a transform as well as a shadow, and 1.0.92 gave them
     a `transition: .3s` on both — the seventh transition, added after this round
     was written, so it is named here too. */
  .home-why__item:hover { transform: none; }
}
