/* rdctd theme — chrome + components. Consumes tokens.css only. */

@font-face { font-family:'Inter';  src:url('../fonts/inter-400.woff2') format('woff2'); font-weight:400; font-display:swap; }
@font-face { font-family:'Inter';  src:url('../fonts/inter-700.woff2') format('woff2'); font-weight:700; font-display:swap; }
@font-face { font-family:'UNMono'; src:url('../fonts/mono-400.woff2')  format('woff2'); font-weight:400; font-display:swap; }

/* --- ground ------------------------------------------------------------ */
html { font-size: clamp(10px, 0.7vw, 12px); }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-text);
  font-size: var(--text-m);
  line-height: 1.4;
  letter-spacing: var(--track-body);
  -webkit-font-smoothing: antialiased;
}
* { box-sizing: border-box; }
img { max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 0.2em; }
a:focus-visible, button:focus-visible, input:focus-visible {
  outline: 1px solid var(--ink); outline-offset: 2px;
}

h1, h2, h3 {
  font-weight: 700;
  letter-spacing: var(--track-display);
  text-transform: lowercase;
  line-height: 1.05;
  margin: 0 0 1em;
  text-wrap: balance;
}
h1 { font-size: var(--text-l); }
h2 { font-size: var(--text-l); }
h3 { font-size: var(--text-m); }
p  { max-width: 62ch; }

.mono, .price, .machine { font-family: var(--font-mono); letter-spacing: var(--track-small); }
.meta { color: var(--ink-2); font-size: var(--text-xs); letter-spacing: var(--track-small); }

/* Full-bleed, one inset. NOT max-width capped: the catalogue bleeds to the
 * viewport, so capping the wrap made the header pull away from the grid on
 * anything wider than 1320px. */
.wrap { margin: 0 auto; padding: 0 var(--edge); }

.skip-link {
  position: absolute; left: -999px; top: 0; background: var(--ink); color: var(--paper);
  padding: 0.8rem 1.2rem; z-index: 100; font-size: var(--text-s);
}
.skip-link:focus { left: 0; }

/* --- header ------------------------------------------------------------ */
.site-head {
  display: flex; align-items: center; justify-content: space-between;
  height: var(--nav-h);
  border-bottom: 1px solid var(--hairline);
}
.site-head .wordmark { font-weight: 700; font-size: var(--text-m); letter-spacing: var(--track-small); }
.site-nav { display: flex; gap: 2.4rem; }
.site-nav a { font-size: var(--text-s); text-transform: lowercase; }
.cart-link { font-family: var(--font-mono); font-size: var(--text-s); }

/* --- footer ------------------------------------------------------------ */
.site-foot {
  margin-top: var(--space-l);
  border-top: 1px solid var(--hairline);
  padding: 2.4rem 0 4rem;
  display: flex; flex-wrap: wrap; gap: 1.2rem 3.6rem;
  align-items: baseline; justify-content: space-between;
}
.site-foot nav { display: flex; flex-wrap: wrap; gap: 1.2rem 2.4rem; }
/* The legal links are machine layer like everything else down here — the
 * contact line and the year beside them are `.mono .meta`, and these carried
 * size and colour but no family, so they rendered in Inter next to two mono
 * siblings. Set on the selector rather than by adding classes in footer.php, so
 * a link added later cannot arrive in the wrong face. */
.site-foot a {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--track-small);
  color: var(--ink-2);
}

/* inline-flex, not the footer's baseline alignment — an SVG aligned on the text
 * baseline sits low against the mono cap-height and reads as a typo. */
.foot-x { display: inline-flex; align-items: center; gap: 0.7rem; }
.foot-x:hover { color: var(--ink); text-decoration: none; }
.foot-x:hover span { text-decoration: underline; text-underline-offset: 0.2em; }
.foot-x .x-mark { flex: none; display: block; }

/* --- homepage: caption + CTA under the hero -------------------------------
 * Sentence case and the text family, deliberately. Everything machine-generated
 * on this site is lowercase mono; this block is the site TALKING to you, and
 * the reference makes the same distinction. */
.promo { margin-top: var(--space-l); max-width: 62ch; }
.promo-head {
  font-size: var(--text-l); font-weight: 700;
  letter-spacing: var(--track-display);
  text-transform: none;              /* h2 is lowercased globally; this is a sentence */
  margin: 0 0 0.6em;
}
.promo-sub { margin: 0 0 2.4rem; font-size: var(--text-m); color: var(--ink); }
/* Bordered box, not a filled button — the filled black bar is reserved for the
 * one committing action on a page (add to basket, request clearance). This is
 * navigation. */
.promo-cta {
  display: inline-block;
  border: 1px solid var(--ink);
  padding: 1.2rem 2.8rem;
  font-size: var(--text-xs);
  transition: background var(--t-fast), color var(--t-fast);
}
.promo-cta:hover { background: var(--ink); color: var(--paper); text-decoration: none; }

/* Open state only. The hero above carries no type once the window is open, so
 * there is no left edge for this block to agree with and a centred column reads
 * as deliberate rather than orphaned. Before the open the hero type sits
 * bottom-left and this stays left with it.
 *
 * The button matches the crystal's width so the two register as the same
 * object — `inline-block` plus `text-align: center` on the parent does the
 * centring, and min-width holds the shape at the same 26rem. */
.promo-centred {
  margin-inline: auto;
  text-align: center;
}
.promo-centred .promo-cta {
  min-width: min(26rem, 100%);
  padding: 1.15rem 2.4rem;
  letter-spacing: 0.08em;
}
@media (max-width: 40rem) {
  .promo-centred .promo-cta { min-width: 0; width: 100%; padding: 1.05rem 1.4rem; }
}

/* --- homepage: the second hero, as a band --------------------------------- */
.band-hero {
  position: relative;
  margin-top: var(--space-section);
  /* Break the page inset — the image runs edge to edge like the catalogue does,
     while the type inside stays centred on the viewport. */
  margin-left: calc(var(--edge) * -1);
  margin-right: calc(var(--edge) * -1);
  display: grid; place-items: center;
  min-height: 38svh;
  overflow: hidden;
}
.band-hero picture, .band-hero img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block;
}
/* Same argument as .state-media: the hero is a per-drop asset and legibility
   cannot depend on one photograph happening to be dark. */
.band-hero::after {
  content: ""; position: absolute; inset: 0;
  background: rgba(0,0,0,0.42);
}
.band-hero-body { position: relative; z-index: 1; text-align: center; padding: var(--space-m) var(--edge); }
.bh-title {
  margin: 0 0 0.6rem; color: var(--paper);
  font-size: var(--text-l); font-weight: 700; letter-spacing: var(--track-display);
}
.bh-sub { margin: 0; color: var(--paper); font-size: var(--text-xs); opacity: 0.85; }

/* --- homepage: signup band ------------------------------------------------
 * CENTRED, not flush left. It follows a centred band-hero, and left-aligning it
 * there made it read as having slipped rather than as placed — the eye comes
 * off a centred title straight onto a column pinned to the page edge.
 *
 * The column is centred; the type inside it stays left-aligned. Centre-setting
 * a four-item dash list gives four different left edges and nothing to read
 * down. */
.signup-band { margin: var(--space-l) auto 0; max-width: 64ch; }
.sb-head {
  font-size: var(--text-m); font-weight: 700;
  letter-spacing: var(--track-display); text-transform: none;
  margin: 0 0 0.4em;
}
.sb-terms { margin: 0 0 2.4rem; }
.sb-terms a { text-decoration: underline; text-underline-offset: 0.2em; }

.sb-list { list-style: none; margin: 0 0 2.4rem; padding: 0; }
.sb-list li {
  position: relative; padding-left: 3.2rem;
  font-size: var(--text-s); line-height: 2;
}
/* Em dash as the marker, matching the reference and the machine layer's own
   punctuation. A disc bullet is the one shape this system does not own. */
.sb-list li::before {
  content: "—"; position: absolute; left: 0; color: var(--ink-2);
}

/* The signup markup is shared with the holding page and the footer, so it is
   restyled HERE by context rather than forked — one form, one set of MailPoet
   plumbing, three appearances. */
.signup-band .signup { display: flex; flex-direction: column; gap: 0; max-width: none; }
.signup-band .signup input[type="email"] {
  font-family: var(--font-mono); font-size: var(--text-s);
  padding: 1.6rem 1.8rem;
  border: 1px solid var(--hairline); background: var(--paper); color: var(--ink);
}
.signup-band .signup input[type="email"]::placeholder { color: var(--ink-2); }
.signup-band .signup button {
  border: 0; background: var(--ink); color: var(--paper);
  font-family: var(--font-mono); font-size: var(--text-xs);
  padding: 1.6rem 1rem; cursor: pointer;
  transition: opacity var(--t-fast);
}
.signup-band .signup button:hover { opacity: 0.82; }
.signup-band .signup-done { margin: 0; }

/* --- checkout --------------------------------------------------------------
 * STYLED, NOT RE-TEMPLATED. There is not one checkout template override in this
 * theme, deliberately. Everything that matters on this page — form.checkout,
 * #customer_details, #order_review, #payment, #place_order, the nonce — is what
 * WooCommerce's own checkout.js binds to, and re-forking those templates means
 * re-forking them on every Woo release, on the one page where a silent break
 * costs an order. So Woo owns the markup and we own the appearance.
 *
 * Zaprite is a plain WC_Payment_Gateway, so it renders inside .wc_payment_methods
 * like any other and needs no special handling — only the list below styled.
 * ------------------------------------------------------------------------- */

.woocommerce-checkout .site-main,
.woocommerce-checkout .checkout { max-width: 100rem; }

.checkout-head { margin: var(--space-m) 0 var(--space-s); }
.checkout-head h1 { font-size: var(--text-l); margin: 0 0 0.4em; }
.checkout-head .mono { color: var(--ink-2); font-size: var(--text-xs); }

/* notices — Woo's own classes, restyled. Square, hairline, no icons. */
.woocommerce-notices-wrapper:empty { display: none; }
.woocommerce-error, .woocommerce-info, .woocommerce-message {
  list-style: none; margin: 0 0 2.4rem; padding: 1.4rem 1.8rem;
  border: 1px solid var(--hairline); background: var(--paper);
  font-family: var(--font-mono); font-size: var(--text-xs);
}
.woocommerce-error { border-color: var(--error); color: var(--error); }
.woocommerce-error li { margin: 0; }

/* two columns on desktop, one on anything narrow. Woo's own col2-set grid is a
   float layout; this replaces it outright. */
.checkout #customer_details { display: grid; grid-template-columns: 1fr; gap: var(--space-s); }
@media (min-width: 62rem) {
  .checkout #customer_details { grid-template-columns: 1fr 1fr; column-gap: var(--space-s); }
}
.checkout .col-1, .checkout .col-2 { float: none; width: auto; }

.checkout h3, #order_review_heading {
  font-size: var(--text-m); font-weight: 700;
  text-transform: lowercase; letter-spacing: var(--track-display);
  margin: 0 0 1.6rem; padding-bottom: 1.2rem;
  border-bottom: 1px solid var(--ink);
}
#order_review_heading { margin-top: var(--space-s); }

/* fields. form-row-first / -last are Woo's 50-50 pair; everything else is full
   width. Grid rather than floats so the two halves cannot drift apart. */
.checkout .woocommerce-billing-fields__field-wrapper,
.checkout .woocommerce-shipping-fields__field-wrapper,
.checkout .woocommerce-additional-fields__field-wrapper {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1.6rem;
}
.checkout .form-row { margin: 0; padding: 0; grid-column: 1 / -1; }
.checkout .form-row-first { grid-column: 1 / 2; }
.checkout .form-row-last  { grid-column: 2 / -1; }
@media (max-width: 40rem) {
  .checkout .woocommerce-billing-fields__field-wrapper,
  .checkout .woocommerce-shipping-fields__field-wrapper { grid-template-columns: 1fr; }
  .checkout .form-row-first, .checkout .form-row-last { grid-column: 1 / -1; }
}

.checkout .form-row label {
  display: block; margin-bottom: 0.6rem;
  font-family: var(--font-mono); font-size: var(--text-xs); color: var(--ink-2);
  letter-spacing: var(--track-small);
}
.checkout .form-row .required { color: var(--ink); text-decoration: none; }
.checkout .form-row .optional { color: var(--ink-2); }

.checkout input[type="text"], .checkout input[type="email"], .checkout input[type="tel"],
.checkout input[type="password"], .checkout textarea, .checkout select {
  width: 100%; box-sizing: border-box;
  font-family: var(--font-mono); font-size: var(--text-s); color: var(--ink);
  background: var(--paper); border: 1px solid var(--hairline);
  padding: 1.2rem 1.4rem; border-radius: var(--radius);
  appearance: none; -webkit-appearance: none;
}
.checkout textarea { min-height: 10rem; resize: vertical; }
.checkout select {
  /* a caret, drawn rather than imported — no icon font, no SVG request */
  background-image: linear-gradient(45deg, transparent 50%, var(--ink) 50%),
                    linear-gradient(135deg, var(--ink) 50%, transparent 50%);
  background-position: calc(100% - 1.8rem) 55%, calc(100% - 1.3rem) 55%;
  background-size: 0.5rem 0.5rem, 0.5rem 0.5rem;
  background-repeat: no-repeat;
  padding-right: 3.6rem;
}
.checkout input:focus, .checkout select:focus, .checkout textarea:focus { border-color: var(--ink); }
.checkout .woocommerce-invalid input, .checkout .woocommerce-invalid select { border-color: var(--error); }

/* select2, defensively. Woo's select2 stylesheet is not enqueued because the
   theme drops Woo's CSS, so if a country field ever renders as select2 rather
   than a native select it would arrive completely unstyled. This makes that
   case look like every other field instead of like a broken page. */
.checkout .select2-container { width: 100% !important; }
.checkout .select2-container .select2-selection--single {
  height: auto; border: 1px solid var(--hairline); border-radius: var(--radius);
  background: var(--paper); padding: 1.2rem 1.4rem;
}
.checkout .select2-container .select2-selection__rendered {
  font-family: var(--font-mono); font-size: var(--text-s); color: var(--ink);
  line-height: 1.2; padding: 0;
}
.checkout .select2-container .select2-selection__arrow { top: 50%; transform: translateY(-50%); right: 1.2rem; }

/* order review */
.woocommerce-checkout-review-order-table {
  width: 100%; border-collapse: collapse;
  font-family: var(--font-mono); font-size: var(--text-xs);
}
.woocommerce-checkout-review-order-table th,
.woocommerce-checkout-review-order-table td {
  padding: 1.2rem 0; text-align: left; border-bottom: 1px solid var(--hairline);
  vertical-align: top;
}
/* Right-aligned, but NOT nowrap on the whole cell. The last column holds prices
 * — short — and also the shipping method label, which is
 * "Flat Rate (Estimated delivery: Aug 14-15)". Pinned nowrap, that one string
 * held the table at 384px inside a 335px column and pushed the checkout 29px
 * off a 375px phone. Only the amount itself must stay unbroken. */
.woocommerce-checkout-review-order-table td:last-child,
.woocommerce-checkout-review-order-table th:last-child { text-align: right; }
.woocommerce-checkout-review-order-table .woocommerce-Price-amount { white-space: nowrap; }
.woocommerce-checkout-review-order-table { table-layout: fixed; max-width: 100%; }
.woocommerce-checkout-review-order-table thead th { color: var(--ink-2); font-weight: 400; }
.woocommerce-checkout-review-order-table .product-name { font-family: var(--font-text); }
.woocommerce-checkout-review-order-table .order-total th,
.woocommerce-checkout-review-order-table .order-total td {
  border-top: 1px solid var(--ink); border-bottom: 0; font-weight: 700; padding-top: 1.6rem;
}
.woocommerce-checkout-review-order-table .variation { margin: 0.4rem 0 0; color: var(--ink-2); }
.woocommerce-checkout-review-order-table .variation p { margin: 0; }

/* payment */
#payment { margin-top: var(--space-s); }
#payment .wc_payment_methods { list-style: none; margin: 0 0 2.4rem; padding: 0; border: 1px solid var(--hairline); }
#payment .wc_payment_method { border-bottom: 1px solid var(--hairline); }
#payment .wc_payment_method:last-child { border-bottom: 0; }
#payment .wc_payment_method > label {
  display: flex; align-items: center; gap: 1.2rem;
  padding: 1.6rem 1.8rem; cursor: pointer;
  font-family: var(--font-mono); font-size: var(--text-s);
}
#payment .wc_payment_method > label img { max-height: 2.4rem; width: auto; margin-left: auto; }
#payment .input-radio {
  appearance: none; -webkit-appearance: none;
  width: 1.4rem; height: 1.4rem; margin: 0; flex: none;
  border: 1px solid var(--ink-2); background: var(--paper); border-radius: var(--radius);
}
#payment .input-radio:checked { background: var(--ink); border-color: var(--ink); }
#payment .payment_box {
  margin: 0; padding: 0 1.8rem 1.8rem;
  font-size: var(--text-xs); color: var(--ink-2);
}
#payment .payment_box p:last-child { margin-bottom: 0; }

#payment .place-order { margin: 0; padding: 0; }
#payment #place_order {
  display: block; width: 100%;
  border: 0; border-radius: var(--radius);
  background: var(--ink); color: var(--paper);
  font-family: var(--font-mono); font-size: var(--text-s);
  padding: 1.8rem 1rem; cursor: pointer;
  transition: opacity var(--t-fast);
}
#payment #place_order:hover { opacity: 0.82; }
#payment #place_order:disabled { background: var(--hairline); color: var(--ink-2); cursor: default; }

.woocommerce-terms-and-conditions-wrapper { margin-bottom: 1.6rem; font-size: var(--text-xs); }
.woocommerce-privacy-policy-text { font-size: var(--text-xs); color: var(--ink-2); }
.woocommerce-privacy-policy-text a { text-decoration: underline; text-underline-offset: 0.2em; }

/* Woo's blocking overlay during AJAX totals refresh */
.blockUI.blockOverlay { background: var(--paper) !important; opacity: 0.6 !important; }

/* --- order received -------------------------------------------------------
 * Reads as a receipt, not a celebration. Same machine layer as the rest of the
 * site: the sentence is human, the facts are mono. */
.receipt { max-width: 74ch; margin: var(--space-m) 0 var(--space-l); }
.receipt-head { border-bottom: 1px solid var(--ink); padding-bottom: 1.6rem; margin-bottom: var(--space-s); }
.receipt-head h1 { font-size: var(--text-l); margin: 0 0 0.6rem; }
.receipt-head .meta { color: var(--ink-2); }
.receipt-line { font-size: var(--text-s); line-height: 1.7; margin: 0 0 2.4rem; max-width: 62ch; }

.receipt-kv { border: 1px solid var(--hairline); padding: 1.8rem 2rem; margin-bottom: 2.4rem; }
.receipt-kv > div + div { margin-top: 0.8rem; }
.receipt-note { margin: 0 0 var(--space-s); }

.receipt-actions { margin: 0 0 var(--space-s); }
.receipt-cta {
  display: inline-block; background: var(--ink); color: var(--paper);
  font-family: var(--font-mono); font-size: var(--text-xs);
  padding: 1.4rem 2.8rem; border-radius: var(--radius);
  transition: opacity var(--t-fast);
}
.receipt-cta:hover { opacity: 0.82; text-decoration: none; }
.receipt-back { display: inline-block; margin-top: var(--space-s); }

/* Woo's own order-details and customer-details tables render below, from
 * templates this theme does not fork. Same treatment as the checkout review. */
.receipt .woocommerce-order-details__title,
.receipt .woocommerce-column__title {
  font-size: var(--text-m); font-weight: 700; text-transform: lowercase;
  letter-spacing: var(--track-display);
  margin: var(--space-s) 0 1.6rem; padding-bottom: 1.2rem;
  border-bottom: 1px solid var(--ink);
}
.receipt .woocommerce-table--order-details,
.receipt .woocommerce-table--customer-details {
  width: 100%; border-collapse: collapse;
  font-family: var(--font-mono); font-size: var(--text-xs);
}
.receipt .woocommerce-table--order-details th,
.receipt .woocommerce-table--order-details td,
.receipt .woocommerce-table--customer-details th,
.receipt .woocommerce-table--customer-details td {
  padding: 1.2rem 0; text-align: left; vertical-align: top;
  border-bottom: 1px solid var(--hairline);
}
.receipt .woocommerce-table--order-details td:last-child,
.receipt .woocommerce-table--order-details th:last-child { text-align: right; white-space: nowrap; }
.receipt .woocommerce-table--order-details tfoot tr:last-child th,
.receipt .woocommerce-table--order-details tfoot tr:last-child td {
  border-top: 1px solid var(--ink); border-bottom: 0; font-weight: 700;
}
.receipt .woocommerce-customer-details { margin-top: var(--space-s); }
.receipt address { font-style: normal; font-family: var(--font-mono); font-size: var(--text-xs); line-height: 2; }

/* --- catalogue controls ---------------------------------------------------
 * A three-part bar: the view toggle and filter button hard left, the drop title
 * centred, the record count right. Grid rather than space-between, so the title
 * is centred on the PAGE and not on whatever is left over between its
 * neighbours — those two are different widths and always will be. */
.cat-heading { margin-top: var(--space-m); }

.cat-controls {
  margin-top: var(--space-s);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 1.6rem;
}
.cat-controls .cat-title { margin: 0; text-align: center; }
.cat-controls .cat-count { margin: 0; text-align: right; }

.cat-switch { display: flex; justify-self: start; border: 1px solid var(--hairline); }
.cat-switch button {
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: var(--track-small);
  background: var(--paper); color: var(--ink);
  border: 0; padding: 1rem 1.6rem; cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
}
.cat-switch button + button { border-left: 1px solid var(--hairline); }
.cat-switch button:hover { background: var(--ink); color: var(--paper); }

/* The toggle names both states at once and marks the live one. A label that
 * only says the destination ("look") is unreadable at a glance — you cannot
 * tell whether it reports where you are or where it will take you. */
.cat-view .cv-x { padding: 0 0.6rem; color: var(--ink-2); }
.cat-view .cv-a { font-weight: 700; }
.cat-view[aria-pressed="true"] .cv-a { font-weight: 400; color: var(--ink-2); }
.cat-view[aria-pressed="true"] .cv-b { font-weight: 700; }
.cat-view:hover .cv-x, .cat-view:hover .cv-a, .cat-view:hover .cv-b { color: inherit; }

/* LOOK MODE — invert which frame rests. Both images are already in the DOM and
 * decoded, so this is an opacity swap, not a load. */
.catalogue.view-look .p-card .frame .worn { opacity: 1; }
.catalogue.view-look .p-card:hover .frame .worn,
.catalogue.view-look .p-card:focus-within .frame .worn { opacity: 0; }

.cat-empty { margin: var(--space-m) 0 0; }
.cat-empty-clear {
  margin-left: 1.2rem; font-family: var(--font-mono); font-size: var(--text-xs);
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--ink); text-decoration: underline; text-underline-offset: 0.2em;
}

/* --- filter + sort panel ---------------------------------------------------
 * Same motion language as the basket drawer — right-hand slide, scrim,
 * --t-drawer — because they are the same kind of surface and a second easing
 * would read as a second design. */
.filters { position: fixed; inset: 0; z-index: 200; }
.filters[hidden] { display: none; }
body.admin-bar .filters { top: 32px; }
@media screen and (max-width: 782px) { body.admin-bar .filters { top: 46px; } }
@media screen and (max-width: 600px) { body.admin-bar .filters { top: 0; } }

.filters-scrim {
  position: absolute; inset: 0;
  background: rgba(0,0,0,0.45); opacity: 0;
  transition: opacity var(--t-drawer);
}
.filters.is-open .filters-scrim { opacity: 1; }

.filters-panel {
  position: absolute; top: 0; right: 0; bottom: 0;
  width: min(38ch, 100%);
  background: var(--paper);
  border-left: 1px solid var(--ink);
  display: flex; flex-direction: column;
  transform: translateX(100%);
  transition: transform var(--t-drawer);
}
.filters.is-open .filters-panel { transform: translateX(0); }

.filters-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 2rem; border-bottom: 1px solid var(--hairline);
  font-size: var(--text-xs);
}
.filters-close {
  background: none; border: 0; padding: 0 0.2rem; line-height: 1;
  font-size: var(--text-m); color: var(--ink-2); cursor: pointer;
}
.filters-close:hover { color: var(--ink); }

/* flex:1 + min-height:0 — without the min-height an overflowing flex child
 * refuses to shrink and the pinned footer is pushed off the bottom. */
.filters-body { flex: 1; min-height: 0; overflow-y: auto; }

.fgroup { border-bottom: 1px solid var(--hairline); }
.fgroup summary {
  list-style: none; cursor: pointer;
  padding: 1.8rem 2rem; font-size: var(--text-s);
  display: flex; align-items: center; justify-content: space-between;
}
.fgroup summary::-webkit-details-marker { display: none; }
.fgroup summary::after {
  content: '+'; color: var(--ink-2); font-size: var(--text-m); line-height: 1;
}
.fgroup[open] summary::after { content: '–'; }

.fopts { padding: 0 2rem 1.8rem; display: flex; flex-direction: column; gap: 1.1rem; }
.fopts-2col { display: grid; grid-template-columns: 1fr 1fr; gap: 1.1rem 1.6rem; }

.fopt { display: flex; align-items: center; gap: 1rem; cursor: pointer; font-size: var(--text-xs); }
/* Square, and square when checked. --radius is 0, and that includes the one
 * control where every other site on earth uses a rounded tick. */
.fopt input {
  appearance: none; -webkit-appearance: none;
  width: 1.4rem; height: 1.4rem; margin: 0; flex: none;
  border: 1px solid var(--ink-2); background: var(--paper);
  cursor: pointer; transition: background var(--t-fast), border-color var(--t-fast);
}
.fopt input:checked { background: var(--ink); border-color: var(--ink); }
.fopt:hover input { border-color: var(--ink); }

.filters-foot {
  display: grid; grid-template-columns: 1fr 1fr;
  border-top: 1px solid var(--ink);
}
.filters-foot button {
  font-family: var(--font-mono); font-size: var(--text-xs);
  padding: 1.6rem 1rem; border: 0; cursor: pointer;
  transition: opacity var(--t-fast);
}
.fbtn-clear { background: var(--paper); color: var(--ink); border-right: 1px solid var(--ink) !important; }
.fbtn-view  { background: var(--ink); color: var(--paper); }
.filters-foot button:hover { opacity: 0.82; }

@media (max-width: 48rem) {
  /* The title is the first thing to go — the controls and the count are doing
     work, and it is repeated in the page heading directly above. */
  .cat-controls { grid-template-columns: 1fr auto; }
  .cat-controls .cat-title { display: none; }
}

@media (max-width: 30rem) {
  /* Full sheet on a phone. At 320px the 38ch panel came to 307 and left a 13px
     strip of scrim down the side, which reads as a panel that failed to reach
     the edge rather than as a margin. Either the gap is meaningful or there is
     no gap. */
  .filters-panel { width: 100%; border-left: 0; }

  /* The switch and the count stop fitting on one line somewhere under 380px —
     "product ⇌ look" plus "⇵ filter + sort" plus "records : 7" is more than a
     320px column holds. Stack rather than shrink the type. */
  .cat-controls { grid-template-columns: 1fr; justify-items: start; gap: 1.2rem; }
  .cat-controls .cat-count { text-align: left; }
  .cat-switch { width: 100%; }
  .cat-switch button { flex: 1; padding-inline: 1rem; }
}

@media (prefers-reduced-motion: reduce) {
  .filters-scrim, .filters-panel { transition: none; }
}

/* --- product grid (the first component) -------------------------------- */
.catalogue {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  column-gap: var(--gap-card);
  row-gap: var(--gap-row);
  list-style: none;
  /* The wrap already sits at --edge, so the grid needs no bleed of its own.
     It used to break out with calc(50% - 50vw) and re-pad, which is what put it
     on a different left edge from the header. */
  margin: var(--space-m) 0 0;
}
@media (min-width: 48rem) { .catalogue { grid-template-columns: repeat(4, 1fr); } }

/* The editorial tile matches a card's frame exactly — same 3/4 box, same
 * placeholder ground while it loads — but has no caption, so the row's baseline
 * is set by the real cards either side of it. */
.p-editorial { position: relative; aspect-ratio: 3/4; overflow: hidden; background: var(--placeholder); }
.p-editorial img { width: 100%; height: 100%; object-fit: cover; display: block; }

.p-card { margin: 0; }
.p-card .frame {
  display: block;
  position: relative; aspect-ratio: 3/4; overflow: hidden;
  background: var(--placeholder); cursor: crosshair;
}
.p-card .frame img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}

/* Flat garment at rest, worn on hover — both of the main side, so the swap
   reads as the same product picked up rather than a different view.
   Opacity, not display: the worn shot must already be decoded when the
   pointer arrives, or the first hover flashes empty. */
.p-card .frame .worn { opacity: 0; transition: opacity var(--t-fast); }
.p-card:hover .frame .worn,
.p-card:focus-within .frame .worn { opacity: 1; }

/* NO TOUCH OVERRIDE HERE ANY MORE — the product/look toggle owns this now.
 *
 * This used to force `.worn { opacity: 1 }` under `@media (hover: none)`, on the
 * reasoning that a swap needing a pointer is invisible on a phone. That was
 * right before the toggle existed. Once it shipped, the two fought: the touch
 * rule pinned the worn shot on, so both toggle states rendered identically and
 * the control did nothing at all on mobile while still looking pressable.
 *
 * Leaving it out means `product` shows the flat shot and `look` shows the
 * on-model one, on every device — and on touch the toggle is the ONLY way to
 * see the other frame, which makes it more useful there than on desktop, not
 * less. */

@media (prefers-reduced-motion: reduce) {
  .p-card .frame .worn { transition: none; }
}
.p-card canvas.mosaic {
  /* 1px overscan: subpixel rounding can leave a sliver of the sharp image
     visible at the frame edge, which breaks the redaction illusion. */
  position: absolute; inset: -1px;
  width: calc(100% + 2px); height: calc(100% + 2px);
  image-rendering: pixelated;
  transition: opacity var(--t-decrypt);
}
.p-card.clear canvas.mosaic { opacity: 0; }
/* --- bands: the imagery sells ---------------------------------------- */
/* .bleed, .hero, .hero-mid and .hero-line were the pre-state-machine hero.
 * No template has referenced them since .state-media / .state-body took over,
 * and they were the last things holding --gutter up — which is why a second
 * page-edge token looked load-bearing when it was only propping up dead CSS. */
.band { margin-top: var(--space-section); }
.band .catalogue { margin-top: 2rem; }

.p-card { position: relative; }
.p-card .p-tag {
  position: absolute; top: 0; left: 0; z-index: 2;
  padding: 0.5rem 0.6rem;
  font-size: var(--text-xs); color: var(--ink-2);
}
/* Card caption — name, fit, price, stacked and flush left to the image edge.
 *
 * The previous version was a space-between row with the price pushed right.
 * In a grid whose column gap is 5px that put one card's price hard against the
 * next card's name, and the whole row read as continuous text. Stacking is
 * also what the spec actually describes; the row was my invention.
 *
 * Each line gets its own measure so a long product name cannot push the fit
 * annotation into the neighbouring column. */
.p-card .p-meta {
  margin-top: var(--gap-caption);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.3rem;
  padding-right: 1.6rem;   /* keeps the longest line clear of the next card */
}
/* One rule each. 0.7.0 added a second .pname and .price block above the
 * originals instead of editing them, so the later (older) declarations won on
 * equal specificity and the name never actually moved to --text-xs. It should
 * not have: the spec puts product names in the BOLD label voice at 11-14px,
 * which --text-s + 700 already was. Merged here so the file says what renders. */
.p-card .pname {
  font-weight: 700; font-size: var(--text-s); line-height: 1.3;
  text-transform: lowercase; letter-spacing: var(--track-small);
}
/* #8c8c8c in the reference — --ink-2 is the same role in this palette. */
.p-card .p-fit { font-size: var(--text-xs); color: var(--ink-2); line-height: 1.35; }
.p-card .price { font-size: var(--text-xs); white-space: nowrap; }

/* Visible to screen readers only. The quick-add button reads as "+" otherwise,
 * which in a grid of seven cards is seven identical unlabelled buttons.
 *
 * `.screen-reader-text` IS WOOCOMMERCE'S CLASS AND WE HAVE TO PROVIDE IT.
 * The theme drops all three WooCommerce stylesheets, but Woo still emits its
 * own markup — and `get_price_html()` on a variable product appends
 * `<span class="screen-reader-text">Price range: £25.50 through £29.00</span>`
 * inside the price. With no rule for it that span stayed in the flow, and
 * `.p-card .price` sets `white-space: nowrap`, so it became a single
 * unbreakable ~316px run.
 *
 * That is what broke mobile. A grid track is `minmax(auto, 1fr)` and cannot
 * shrink below its content's min-content, so every column was forced to 332px
 * and the catalogue overflowed a 320px viewport by 406px. The cards looked
 * fine because the text was pushed out of sight — the damage was to the track
 * sizing, not to anything you could see.
 *
 * Kill Woo's CSS and you inherit its utility classes as a debt. */
.u-sr,
.screen-reader-text {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* --- quick add ------------------------------------------------------------
 * The control and its panel both position against .p-media — the image box —
 * so neither has to guess where the caption starts. `overflow: hidden` here is
 * what makes the panel slide out from under the bottom edge rather than
 * appearing from nowhere. */
/* `clip`, not `hidden`, with hidden as the fallback for anything that predates
 * it. Both crop the panel identically, but `hidden` creates a scroll container
 * the browser is allowed to scroll programmatically — which is exactly what it
 * did when focus moved into the panel while the panel was still translated
 * below the crop line, jolting the image up and back. `clip` creates no scroll
 * container at all, so there is nothing to scroll. */
.p-card .p-media { position: relative; overflow: hidden; overflow: clip; }

.p-card .qb-dock {
  position: absolute; left: 0; right: 0; bottom: var(--qb-inset); z-index: 3;
  display: flex; justify-content: center;
  pointer-events: none;                 /* the dock is a rail, not a target */
}
/* SQUARE. about:blank's is a circle and this is the one thing not taken from
 * it — --radius is 0 and nothing in this system is rounded. Position and
 * behaviour are borrowed; the shape stays ours. */
.p-card .qb-open {
  display: grid; place-items: center;
  width: 2.8rem; height: 2.8rem; padding: 0; line-height: 1;
  border: 0; background: var(--paper); color: var(--ink);
  font-size: var(--text-m);
  opacity: 0.7;
  transition: opacity var(--t-fast);
  pointer-events: auto; cursor: pointer;
}
.p-card:hover .qb-open,
.p-card .qb-open:hover,
.p-card .qb-open:focus-visible { opacity: 1; }
.p-card .quickbuy.open .qb-dock { opacity: 0; pointer-events: none; }

.p-card .qb-panel {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 4;
  background: var(--paper); border-top: 1px solid var(--ink);
  padding: 1rem;
  transform: translateY(100%);
  /* visibility, not display — display:none cannot transition, and it also has
   * to leave the tab order while the panel is shut. Delayed on the way out so
   * it stays visible for the full slide. */
  visibility: hidden;
  transition: transform var(--t-drawer), visibility 0s linear var(--t-drawer);
}
.p-card .quickbuy.open .qb-panel {
  transform: translateY(0);
  visibility: visible;
  transition: transform var(--t-drawer), visibility 0s;
}

.p-card .qb-close {
  display: block; margin-left: auto;
  border: 0; background: none; padding: 0 0.2rem; line-height: 1;
  font-size: var(--text-m); color: var(--ink-2); cursor: pointer;
  transition: color var(--t-fast);
}
.p-card .qb-close:hover { color: var(--ink); }

.p-card .qb-sizes { display: flex; flex-wrap: wrap; gap: 1px; margin: 0.6rem 0 1rem; }
.p-card .qb-size {
  flex: 1 1 auto; border: 1px solid var(--hairline); background: var(--paper); color: var(--ink);
  padding: 0.8rem 0.4rem; font-size: var(--text-xs); cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.p-card .qb-size:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.p-card .qb-size[aria-pressed="true"] { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.p-card .qb-size[disabled] { color: var(--ink-2); cursor: default; }

/* The action bar. Full-bleed black, as in the reference — it is the only
 * committed action on the card and should look like it. */
.p-card .qb-add {
  display: block; width: 100%;
  border: 0; background: var(--ink); color: var(--paper);
  padding: 1.1rem 0.8rem; font-size: var(--text-xs);
  font-family: var(--font-mono);
  cursor: pointer;
  transition: opacity var(--t-fast);
}
.p-card .qb-add:hover:not([disabled]) { opacity: 0.82; }
.p-card .qb-add[disabled] { background: var(--hairline); color: var(--ink-2); cursor: default; }
/* Confirmed state. Stays black — a green tick would be the first interface
 * colour in the entire system. */
.p-card .qb-add.is-added { background: var(--ink); color: var(--paper); opacity: 1; }
.p-card .qb-add.is-error { background: var(--error); color: var(--paper); }
.p-card .qb-out { margin: 0.6rem 0 0; font-size: var(--text-xs); color: var(--ink-2); }

@media (prefers-reduced-motion: reduce) {
  .p-card .qb-panel { transition: visibility 0s; }
}

.pagination { margin-top: var(--space-m); display: flex; gap: 1.6rem; }
.pagination .page-numbers { color: var(--ink-2); }
.pagination .page-numbers.current { color: var(--ink); }

/* --- product single: 50/50 split, sticky details ------------------------- */
.product-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: var(--gap-card);
  align-items: start;              /* required — sticky dies in a stretched track */
  margin-top: var(--gap-card);
}
.product-media { display: flex; flex-direction: column; gap: var(--gap-card); }
.product-media .pm-img { display: block; width: 100%; height: auto; background: var(--placeholder); }
.pm-empty { aspect-ratio: 1; display: grid; place-items: center; }

.product-info {
  position: sticky; top: 2.4rem;
  padding: 2rem 0 2rem 3rem;
}
.pi-head { display: flex; justify-content: space-between; align-items: baseline; gap: 2rem; }
.pi-head h1 { font-size: var(--text-m); margin: 0; }
.pi-head .price { font-size: var(--text-s); white-space: nowrap; }
.pi-record { margin-top: 3rem; }
.pi-cart { margin-top: 3rem; }
/* Short description. Sentence case and sans, per the spec's rule for prose —
 * but sized and greyed as a footnote, because on this site it is always a
 * scarcity note under the button, never something to read first. */
.pi-desc { margin-top: 2.4rem; font-size: var(--text-xs); color: var(--ink-2); }
.pi-desc p { margin: 0; }
.pi-back { display: inline-block; margin-top: 4rem; }

/* Manufacturer spec — real, but incidental. Collapsed so it cannot push the
   buy button down the page; the hairline is the only affordance it needs. */
.pi-spec { margin-top: 3rem; border-top: 1px solid var(--hairline); }
.pi-spec > summary {
  padding: 1.2rem 0; cursor: pointer; list-style: none;
  display: flex; justify-content: space-between; align-items: center;
}
.pi-spec > summary::-webkit-details-marker { display: none; }
.pi-spec > summary::after { content: '+'; }
.pi-spec[open] > summary::after { content: '\2212'; }  /* true minus, not a hyphen */
.pi-spec > .kv { padding-bottom: 1.6rem; }

/* WooCommerce form, reduced to the system: plain selects, one black button.
 *
 * THE BUY BLOCK SETS ITS OWN SCALE. The base rule below gives every control
 * `font: inherit`, which sounds like it unifies them and does the opposite:
 * it inherits from <body>, which is --text-m. Everything else on this page is
 * --text-s or --text-xs, so the select, the quantity box and the button were
 * the only 17px sans elements in a panel of 14px mono — a jump in size AND in
 * face, in the one block that has to feel like part of the record. Declaring
 * the size once here means every control follows without being named. */
.pi-cart { font-size: var(--text-s); }

/* Woo emits the attribute picker as a <table>. Only `td` was made a block, so
 * `th` stayed a table cell and the "size" label sat in a narrow column beside
 * the select instead of above it. All three go to block, flush left. */
.pi-cart .variations { display: block; border: 0; width: 100%; }
.pi-cart .variations tbody,
.pi-cart .variations tr { display: block; }
.pi-cart .variations th,
.pi-cart .variations td { display: block; padding: 0; text-align: left; }
.pi-cart .variations th.label { margin-bottom: 0.6rem; }

/* Label voice matches the machine layer's keys — same face, size and grey, so
 * "size" reads as one more line of the record rather than as form furniture. */
.pi-cart .variations label {
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: var(--track-small); color: var(--ink-2);
  text-transform: lowercase;
}

/* appearance:none because the native control is the one rounded, shaded thing
 * on a site where nothing is rounded. Caret redrawn as a hairline chevron. */
.pi-cart select {
  width: 100%; text-transform: lowercase;
  appearance: none; -webkit-appearance: none;
  padding-right: 3.2rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%23000' stroke-width='1.2'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1.2rem center;
  background-size: 1rem auto;
}
.pi-cart .reset_variations {
  display: inline-block; margin-top: 0.8rem;
  font-family: var(--font-mono); font-size: var(--text-xs); color: var(--ink-2);
}

/* Printful's size-guide link ships as a bare <a> with an inline
 * style="color:#1164A9" — the one blue thing on the site, and inline so no
 * ordinary rule reaches it. !important is the correct tool here precisely
 * because the competing declaration is inline and not ours to edit; setting
 * the plugin's colour option instead would work until someone opens its
 * settings page and saves the default back. */
.pi-cart .single_variation_wrap > a[onclick] {
  color: var(--ink-2) !important;
  font-family: var(--font-mono); font-size: var(--text-xs);
  text-transform: lowercase;
  text-decoration: underline; text-underline-offset: 0.3em;
}
.pi-cart .single_variation_wrap > a[onclick]:hover { color: var(--ink) !important; }

.pi-cart form.cart { display: block; }

/* Quantity and button on one row. Alone above a full-width button, the qty box
 * read as an orphaned input with no label; paired, the row is legibly one
 * control plus one action. */
.pi-cart .woocommerce-variation-add-to-cart,
.pi-cart form.cart:not(.variations_form) {
  display: flex; align-items: stretch; gap: 0.8rem; margin-top: 2rem;
}
.pi-cart .quantity { flex: 0 0 auto; }
.pi-cart .quantity input {
  width: 6rem; height: 100%; text-align: center;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
}
.pi-cart .single_add_to_cart_button {
  flex: 1 1 auto; margin: 0; padding: 1.2rem;
  font-size: var(--text-s); letter-spacing: var(--track-small);
}
.pi-cart .woocommerce-variation-price {
  margin: 1.6rem 0 0; font-family: var(--font-mono); font-size: var(--text-s);
  letter-spacing: var(--track-small);
}
.pi-cart .woocommerce-variation-availability p,
.pi-cart .stock {
  margin: 1.2rem 0 0;
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: var(--track-small); color: var(--ink-2);
}

@media (max-width: 640px) {
  .product-split { grid-template-columns: 1fr; }
  .product-info { position: static; padding: 3rem 0 0; }
}

/* --- drop clock --------------------------------------------------------- */
/* .dropbanner was the old standalone countdown section, and .dropwindow its
 * date line. The clock now sits inside .state-clock on the three state
 * templates, so only .clock itself is still reachable. .dropbanner's
 * margin-top was --space-m (60px) where a section break wants 120-144px —
 * a genuine spec violation, but on a rule nothing renders. */
.clock {
  font-family: var(--font-mono); font-size: var(--text-l);
  font-variant-numeric: tabular-nums; letter-spacing: var(--track-small);
}
.clock .unit { color: var(--ink-2); font-size: var(--text-xs); margin-right: 2rem; }

/* --- machine layer ------------------------------------------------------ */
.kv { font-family: var(--font-mono); font-size: var(--text-s); letter-spacing: var(--track-small); line-height: 2.1; }
.kv .k { color: var(--ink-2); }

/* --- forms (validation red is the only red) ----------------------------- */
input, textarea, select, button {
  font: inherit; letter-spacing: inherit;
  border: 1px solid var(--hairline); border-radius: var(--radius);
  background: var(--paper); color: var(--ink); padding: 0.8rem 1.2rem;
}
button, .button, input[type=submit] {
  border-color: var(--ink); background: var(--ink); color: var(--paper);
  cursor: pointer; text-transform: lowercase; transition: opacity var(--t-fast);
}
button:hover, .button:hover, input[type=submit]:hover { opacity: 0.85; }
.field-error, .woocommerce-error { color: var(--error); font-size: var(--text-xs); }

@media (prefers-reduced-motion: reduce) {
  .p-card canvas.mosaic { transition: none; }
}

/* ---- legal pages -------------------------------------------------------- */
/* Returns, privacy, cookies, gpsr. The only body copy on the site, so this is
 * the only place a reading measure matters — everything else is images, a spec
 * block or a countdown. Capped at 68ch because policy text is read once, under
 * mild duress, and long lines are where people give up. */
.legal {
  max-width: 68ch;
  margin: var(--space-m) auto var(--space-l);
  padding: 0;
}
.legal-head { border-bottom: 1px solid var(--ink); padding-bottom: 1.6rem; margin-bottom: var(--space-s, 3rem); }
.legal-head h1 { font-size: var(--text-l); margin: 0 0 0.8rem; }
.legal-head .meta { color: var(--ink-2); }

.legal-body { font-size: var(--text-s); line-height: 1.75; }
.legal-body > * + * { margin-top: 1.6em; }
.legal-body h2 { font-size: var(--text-m); margin-top: 2.4em; }
.legal-body h3 { font-size: var(--text-s); margin-top: 2em; text-transform: lowercase; }
.legal-body ul, .legal-body ol { padding-left: 2.4rem; }
.legal-body li + li { margin-top: 0.6em; }
.legal-body a { text-decoration: underline; text-underline-offset: 0.2em; }
/* Policy pages are where a stray Elementor table or wide <pre> would break the
 * page — they came from a page builder and carry its markup. */
.legal-body img, .legal-body table, .legal-body pre { max-width: 100%; }
.legal-body table { display: block; overflow-x: auto; }

.legal-back {
  display: inline-block;
  margin-top: var(--space-m);
  border-top: 1px solid var(--hairline);
  padding-top: 1.6rem;
}




/* ---- site states: full-bleed hero, minimal type over it ------------------ */
/* The about:blank treatment. An earlier revision put a bordered panel under a
 * 52vh image band — a nice object, the wrong page. The reference sells with a
 * photograph filling the viewport and a few lines of type on top. */
.state {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
  /* Run under the header. The token is --nav-h; this said --header-h, which is
     defined nowhere, so it silently used the 6rem fallback. That matches
     --nav-h on desktop by coincidence and is wrong on mobile, where --nav-h is
     4rem — the hero was pulling up 20px too far and running past the header. */
  margin-top: calc(var(--nav-h) * -1);
}
/* The image bleeds past the page inset; only the type respects it. */
.state-media { position: absolute; inset: 0 calc(var(--edge) * -1); z-index: 0; }
.state-media img, .state-media video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The scrim is structural, not decorative: the hero is a PER-DROP asset, so the
 * next one may be light where this one is dark. Legibility cannot be allowed to
 * depend on one particular photograph. */
.state-media::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(0,0,0,0.82) 0%, rgba(0,0,0,0.45) 34%, rgba(0,0,0,0.05) 62%);
}

/* THE CRYSTAL BUTTON. Open state only — the one CTA, on the hero, right-hand
 * side and vertically centred.
 *
 * Absolute rather than a flex child: .state is `align-items: flex-end` for the
 * type block in the other three states, and giving this its own coordinates
 * keeps those layouts untouched. `right: 0` lands on the page inset, not the
 * bleed — .state-media deliberately overhangs by --edge, the content does not.
 *
 * The frost is a backdrop-filter so the button shows the PHOTOGRAPH blurred,
 * not a flat wash. brightness(0.78) is structural: white type has to hold on a
 * per-drop image nobody has chosen yet, and the scrim below is transparent this
 * far up the frame. Browsers without backdrop-filter get a flat dark panel —
 * same contrast, no blur. */
.state-crystal {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 1;

  display: flex;
  align-items: center;
  justify-content: center;
  min-width: min(26rem, 100%);
  padding: 1.15rem 2.4rem;

  color: var(--paper);
  font-size: var(--text-s);
  letter-spacing: 0.08em;
  text-decoration: none;

  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.42);
  backdrop-filter: blur(14px) saturate(120%) brightness(0.78);
  -webkit-backdrop-filter: blur(14px) saturate(120%) brightness(0.78);
  transition: background 160ms ease, border-color 160ms ease;
}
.state-crystal:hover,
.state-crystal:focus-visible {
  background: rgba(255,255,255,0.20);
  border-color: rgba(255,255,255,0.82);
  text-decoration: none;
}
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .state-crystal { background: rgba(0,0,0,0.52); }
  .state-crystal:hover, .state-crystal:focus-visible { background: rgba(0,0,0,0.66); }
}
/* Below 40rem, 26rem of button plus the page inset does not fit and the thing
 * would either overflow or shrink to an odd stub. It spans the inset instead —
 * still the only CTA, still vertically centred, and now a thumb-sized target. */
@media (max-width: 40rem) {
  .state-crystal { left: 0; right: 0; min-width: 0; padding: 1.05rem 1.4rem; }
}

/* Bottom-left, clear of the centred figure in the campaign imagery. */
.state-body {
  position: relative;
  z-index: 1;
  color: var(--paper);
  padding: var(--space-m) 0 var(--space-s);
  max-width: 52ch;
  display: flex;
  flex-direction: column;
  gap: 1.6rem;
}
.state-body h1 {
  font-size: var(--text-xl);
  line-height: 0.96;
  letter-spacing: -0.02em;      /* negative on display — spec item 1 */
  margin: 0;
  color: var(--paper);
}
.state-eyebrow { font-size: var(--text-xs); color: rgba(255,255,255,0.72); margin: 0; }
.state-line { font-size: var(--text-s); color: rgba(255,255,255,0.82); margin: 0; max-width: 44ch; }
.state-body .meta { color: rgba(255,255,255,0.6); }

/* One bar, in the eyebrow. A table of them was the panel wearing the brand
 * rather than being it. */
.state-eyebrow .bar {
  display: inline-block; height: 0.9em; background: var(--paper);
  vertical-align: -0.1em; opacity: 0.9;
}

.state-clock .clock { font-size: var(--text-clock); color: var(--paper); }
.state-clock .meta { margin: 0.4rem 0 0; }

.state-form { display: flex; flex-direction: column; gap: 0.8rem; }
.state-entry { display: flex; gap: 0.8rem; max-width: 40ch; }
.state-entry input {
  flex: 1;
  font-family: var(--font-mono);
  font-size: var(--text-s);
  letter-spacing: var(--track-code);
  padding: 1.1rem 1.4rem;
  border: 1px solid rgba(255,255,255,0.5);
  background: rgba(0,0,0,0.25);
  color: var(--paper);
}
.state-entry input::placeholder { color: rgba(255,255,255,0.4); letter-spacing: var(--track-code); }
.state-entry input[aria-invalid="true"] { border-color: var(--error); }
.state-entry button {
  padding: 1.1rem 2.2rem; white-space: nowrap;
  background: var(--paper); color: var(--ink); border: 1px solid var(--paper);
}
.state-alt a { color: var(--paper); text-decoration: underline; text-underline-offset: 0.2em; }
.state-signup { max-width: 40ch; }

/* The header sits over the hero on these pages. */
.state ~ .site-foot, body:has(.state) .site-head { position: relative; z-index: 2; }
body:has(.state) .site-head { color: var(--paper); }
body:has(.state) .site-head a { color: var(--paper); }

/* Admin-only preview marker. Deliberately ugly — it must never be mistaken for
 * part of the design, and it must be impossible to miss. */
.un-state-badge {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 9999;
  background: #ff3b00; color: var(--paper); font-size: 1.1rem;
  padding: 0.6rem 1.2rem; text-align: center;
}
.un-state-badge a { color: var(--paper); text-decoration: underline; }

/* ---- signup ------------------------------------------------------------- */
.signup { display: flex; gap: 0.8rem; max-width: 40ch; }
.signup input[type="email"] {
  flex: 1;
  font-family: var(--font-mono);
  font-size: var(--text-s);
  padding: 1.1rem 1.4rem;
  border: 1px solid rgba(255,255,255,0.5);
  background: rgba(0,0,0,0.25);
  color: var(--paper);
}
.signup input[type="email"]::placeholder { color: rgba(255,255,255,0.45); }
.signup button {
  font-family: var(--font-mono);
  padding: 1.1rem 1.6rem; white-space: nowrap;
  background: var(--paper); color: var(--ink); border: 1px solid var(--paper);
}
/* Honeypot. Off-screen rather than display:none — some bots skip hidden fields
 * but happily fill positioned ones, which is the point of the trap. */
.signup .hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }
.signup-done { font-size: var(--text-xs); line-height: 1.7; margin: 0; }

/* On the light legal/shop pages the field inverts. */
body:not(:has(.state)) .signup input[type="email"] {
  border-color: var(--ink); background: transparent; color: var(--ink);
}
body:not(:has(.state)) .signup button { background: var(--ink); color: var(--paper); border-color: var(--ink); }

/* ---- basket drawer ------------------------------------------------------ */
/* Slides from the right. 75ms cubic-bezier(.165,.84,.44,1) is the house
 * transition from 04-aboutblank-spec — very fast, sharply decelerating. The
 * panel gets a longer duration than that because it travels a real distance;
 * 75ms on a 42ch slide reads as a jump-cut rather than a movement. */
.drawer { position: fixed; inset: 0; z-index: 200; }
.drawer[hidden] { display: none; }

/* The admin bar is itself fixed to the viewport, and WordPress clears it by
 * putting margin-top on <html> — which does nothing for a fixed element, so
 * the drawer's own header was sliding underneath it. Logged-in views only;
 * customers never hit this. WP's breakpoints are in px, not our rem scale. */
body.admin-bar .drawer { top: 32px; }
@media screen and (max-width: 782px) {
  body.admin-bar .drawer { top: 46px; }
}
@media screen and (max-width: 600px) {
  /* Below 600px the bar switches to position:absolute and scrolls away with
     the page, so a standing offset would leave a permanent white gap. */
  body.admin-bar .drawer { top: 0; }
}
.drawer-scrim {
  position: absolute; inset: 0;
  background: rgba(0,0,0,0.45);
  opacity: 0;
  transition: opacity var(--t-drawer);
}
.drawer.is-open .drawer-scrim { opacity: 1; }

.drawer-panel {
  position: absolute; top: 0; right: 0; bottom: 0;
  width: min(42ch, 100%);
  background: var(--paper);
  border-left: 1px solid var(--ink);
  display: flex; flex-direction: column;
  transform: translateX(100%);
  transition: transform var(--t-drawer);
}
.drawer.is-open .drawer-panel { transform: translateX(0); }

.drawer-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 2rem;
  border-bottom: 1px solid var(--ink);
  font-size: var(--text-xs);
}
.drawer-close { background: none; border: 0; padding: 0; cursor: pointer; color: var(--ink-2); font-size: var(--text-xs); }
.drawer-close:hover { color: var(--ink); }

.drawer-body { flex: 1; overflow-y: auto; padding: 2rem; }
.drawer-empty { margin: 0; }
.drawer-items { list-style: none; margin: 0; padding: 0; }
.drawer-item {
  display: grid;
  grid-template-columns: 7rem 1fr auto;
  gap: 1.6rem;
  padding-bottom: 2rem;
}
.drawer-item + .drawer-item { border-top: 1px solid var(--hairline); padding-top: 2rem; }
.di-thumb img { width: 100%; height: auto; display: block; }
.di-detail { display: flex; flex-direction: column; gap: 0.4rem; min-width: 0; }
.di-name { font-size: var(--text-xs); line-height: 1.35; }
.di-meta, .di-line { font-size: var(--text-xs); }
.di-remove { font-size: var(--text-xs); color: var(--ink-2); align-self: start; }
.di-remove:hover { color: var(--error); }

.drawer-foot { border-top: 1px solid var(--ink); padding: 2rem; display: flex; flex-direction: column; gap: 1.2rem; }
.drawer-total { display: flex; justify-content: space-between; font-size: var(--text-s); }
.drawer-note { margin: 0; font-size: var(--text-xs); }
.drawer-cta {
  display: block; text-align: center;
  padding: 1.3rem; font-family: var(--font-mono); font-size: var(--text-xs);
  border: 1px solid var(--ink);
}
.drawer-cta--alt { background: var(--ink); color: var(--paper); }

/* Stop the page scrolling behind the panel. */
body.drawer-locked { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .drawer-scrim, .drawer-panel { transition: none; }
}

/* ---- cart page ---------------------------------------------------------- */
/* The drawer's contents with room to breathe. Two columns on desktop: the list
 * scrolls, the summary stays put — same anatomy as the product page, so the two
 * transactional surfaces behave identically. */
.cart-page {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-m);
  max-width: 110rem;
  margin: var(--space-m) auto var(--space-l);
  padding: 0;
}
@media (min-width: 62rem) {
  .cart-page { grid-template-columns: 1fr 34rem; align-items: start; }
  .cart-head { grid-column: 1 / -1; }
  .cart-summary { position: sticky; top: var(--space-s, 4rem); }
}

.cart-head { border-bottom: 1px solid var(--ink); padding-bottom: 1.6rem; }
.cart-head h1 { font-size: var(--text-l); margin: 0 0 0.6rem; }
.cart-head .meta { margin: 0; }

.cart-items { list-style: none; margin: 0; padding: 0; }
/* basket and checkout are ordinary pages, so page.php hands them here instead of
 * wrapping them in the legal template's 68ch reading measure. */
.shop-page { margin: 0 0 var(--space-l); }

.cart-item {
  display: grid;
  /* minmax(0,1fr) on the detail column, not 1fr. A bare 1fr will not shrink
     below its content's min-content, so a long product name held the column
     open and squeezed the other four until the name itself wrapped one word per
     line. This lets it shrink and wrap normally. */
  grid-template-columns: 9rem minmax(0, 1fr) auto auto auto;
  gap: 2rem;
  align-items: start;
  padding: 2.4rem 0;
  border-bottom: 1px solid var(--hairline);
}
@media (max-width: 48rem) {
  /* thumb down the left, everything else stacked beside it */
  .cart-item { grid-template-columns: 7rem minmax(0, 1fr); gap: 1.4rem; }
  .cart-item .ci-qty, .cart-item .ci-total, .cart-item .ci-remove { grid-column: 2; }
  .cart-item .ci-thumb { grid-row: 1 / span 4; }
}
.ci-thumb img { width: 100%; height: auto; display: block; }
.ci-detail { display: flex; flex-direction: column; gap: 0.5rem; min-width: 0; }
.ci-name { font-size: var(--text-s); line-height: 1.3; }
.ci-meta, .ci-unit { font-size: var(--text-xs); }
.ci-total { font-size: var(--text-s); white-space: nowrap; }
.ci-remove { font-size: var(--text-xs); color: var(--ink-2); }
.ci-remove:hover { color: var(--error); }

/* --- quantity stepper (drawer + basket page) ------------------------------ */
.qty {
  display: inline-flex; align-items: stretch;
  border: 1px solid var(--hairline);
}
.qty-btn {
  width: 2.8rem; border: 0; background: var(--paper); color: var(--ink);
  font-family: var(--font-mono); font-size: var(--text-s); line-height: 1;
  cursor: pointer; padding: 0;
  transition: background var(--t-fast), color var(--t-fast);
}
.qty-btn:hover:not([disabled]) { background: var(--ink); color: var(--paper); }
.qty-btn[disabled] { color: var(--hairline); cursor: default; }
.qty-n {
  min-width: 3rem; padding: 0.8rem 0.2rem;
  display: grid; place-items: center;
  font-size: var(--text-xs);
  border-left: 1px solid var(--hairline); border-right: 1px solid var(--hairline);
}
/* In flight. Not a spinner — the row dims and stops taking clicks, which is
   enough feedback for an operation that resolves in well under a second. */
.is-busy { opacity: 0.45; pointer-events: none; }
.drawer-item .qty { margin-top: 0.6rem; }
.di-qty-one { margin-top: 0.6rem; }

/* Woo's quantity input, restyled rather than replaced — its markup carries the
 * name attributes the update handler depends on. */
.ci-qty .quantity input {
  width: 6rem; text-align: center;
  font-family: var(--font-mono); font-size: var(--text-xs);
  padding: 0.8rem; border: 1px solid var(--hairline); background: transparent; color: var(--ink);
}

.cart-actions { padding-top: 2rem; }
.cart-update {
  background: none; border: 1px solid var(--hairline); cursor: pointer;
  padding: 1rem 1.8rem; font-size: var(--text-xs); color: var(--ink-2);
}
.cart-update:hover { border-color: var(--ink); color: var(--ink); }

.cart-summary { border: 1px solid var(--ink); padding: 2.4rem; display: flex; flex-direction: column; gap: 1.6rem; }
.cart-total { display: flex; justify-content: space-between; font-size: var(--text-m); }
.cart-note { margin: 0; font-size: var(--text-xs); line-height: 1.6; }
.cart-checkout {
  display: block; text-align: center;
  background: var(--ink); color: var(--paper);
  padding: 1.4rem; font-family: var(--font-mono); font-size: var(--text-xs);
}
.cart-back { display: inline-block; border-top: 1px solid var(--hairline); padding-top: 1.6rem; }

/* Empty cart — Woo renders its own template; keep it in the same measure. */
.wc-empty-cart-message, .cart-empty { max-width: 52ch; margin: var(--space-m) auto; padding: 0; }

/* ---- front page hero ----------------------------------------------------
 * Same .state treatment as the holding page and the 403, so all three entry
 * points read as one site rather than three designs. The front page adds a CTA
 * and is the only one that leads somewhere. */
.state-front { min-height: 88svh; }   /* shorter than 100 — the grid below should peek */
.state-cta {
  display: inline-block;
  align-self: flex-start;
  color: var(--paper);
  border-bottom: 1px solid rgba(255,255,255,0.6);
  padding-bottom: 0.4rem;
  font-size: var(--text-xs);
  transition: border-color var(--t-fast);
}
.state-cta:hover { border-color: var(--paper); }

/* The header sits over the hero on the front page too. body:has() covers the
 * state templates; .state-front is the same case. */
body:has(.state-front) .site-head, body:has(.state-front) .site-head a { color: var(--paper); }

/* The old stacked banner. Removed from the template — this clears any cached
 * markup and stops it reappearing if a stale page is served. */
.dropbanner { display: none; }

/* --- touch targets ---------------------------------------------------------
 * Measured on a 375px phone with touch emulation: the quick-add was 28x28, the
 * quantity steppers 28x31, the filter checkboxes 14x14, WooCommerce's payment
 * radio 26x18 and its terms checkbox 13x13. WCAG 2.5.8 puts the floor at 24x24
 * CSS px and the last three were under it.
 *
 * Scoped to `pointer: coarse` rather than a width breakpoint — this is about
 * fingers, not screens. A small window on a desktop still has a mouse and does
 * not need 44px controls; a large tablet does.
 *
 * MIND THE ROOT CLAMP: html is font-size: clamp(10px, 0.7vw, 12px), so 1rem is
 * 10px on every phone. 4.4rem is 44px here, not 70. */
@media (pointer: coarse) {
  .p-card .qb-open { width: 4.4rem; height: 4.4rem; }
  .qty-btn { width: 4.4rem; height: 4.4rem; }
  .qty-n { min-width: 4rem; }

  /* Our own controls */
  .fopt input { width: 2.4rem; height: 2.4rem; }
  .fopt { gap: 1.2rem; padding: 0.4rem 0; }

  /* WooCommerce's, which the theme does not template */
  .checkout input[type="checkbox"],
  .checkout input[type="radio"],
  #payment .input-radio { width: 2.4rem; height: 2.4rem; flex: none; }
  .checkout .woocommerce-form__label-for-checkbox { display: flex; align-items: center; gap: 1.2rem; }

  /* Inline text actions get vertical padding rather than a box — `remove` was
     15px tall, and it deletes a line from a basket. */
  .ci-remove, .di-remove { padding: 1rem 0; display: inline-block; }

  /* THE SIZE BUTTONS ARE THE POINT OF THE QUICK-ADD, and they were the worst
     targets on the page: `flex: 1 1 auto` split a ~168px card six ways, giving
     buttons 14 to 22px wide. Choosing a size is the one thing this panel exists
     to do, and on a phone it was a lottery.
     A three-column grid gives every size the same ~48px box and wraps to two
     rows, which also stops S and 3XL being different widths. */
  .p-card .qb-sizes { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; }
  .p-card .qb-size { padding: 1.2rem 0.4rem; }

  /* The panel's close control was a bare glyph at 14x15. */
  .p-card .qb-close {
    width: 3.4rem; height: 3.4rem; padding: 0;
    display: grid; place-items: center;
  }
}
