/** Shopify CDN: Minification failed

Line 233:29 Unexpected "*"
Line 237:77 Unterminated string token

**/
/* ==========================================================================
 * brand-residual.css — RESIDUAL ONLY.
 * Every rule here was verified unowned by any theme control on Prestige 11.3.0
 * per style-surface map ff09be175e1a1db6e566f1bf517adb46b7007538a771ab57b245ef8387b9de84.
 * Adding a rule for an owned property is a defect (R5 lints this).
 *
 * Custom properties only (R5-safe names — avoid width/gap keywords that
 * false-positive against Prestige's unrelated width/spacing families).
 * Interface-contract aliases (--page-width, --component-gap) + canvas wiring
 * live in layout/theme.liquid after these assets load.
 * ========================================================================== */

:root {
  /* scale.page_width — no global Prestige control owns the page canvas width */
  --brand-canvas: 1800px;

  /*
   * EMI-706 / R3-068 — Impact spacing scale (ported from Impact css-variables.liquid).
   * Prestige theme.css references --spacing-* but never defines them; unresolvable vars
   * collapse hot-spot dots and other consumers. Custom properties only — no owned surfaces.
   */
  --spacing-0-5: 0.125rem;
  --spacing-1: 0.25rem;
  --spacing-1-5: 0.375rem;
  --spacing-2: 0.5rem;
  --spacing-2-5: 0.625rem;
  --spacing-3: 0.75rem;
  --spacing-3-5: 0.875rem;
  --spacing-4: 1rem;
  --spacing-4-5: 1.125rem;
  --spacing-5: 1.25rem;
  --spacing-5-5: 1.375rem;
  --spacing-6: 1.5rem;
  --spacing-6-5: 1.625rem;
  --spacing-7: 1.75rem;
  --spacing-7-5: 1.875rem;
  --spacing-8: 2rem;
  --spacing-8-5: 2.125rem;
  --spacing-9: 2.25rem;
  --spacing-9-5: 2.375rem;
  --spacing-10: 2.5rem;
  --spacing-11: 2.75rem;
  --spacing-12: 3rem;
  --spacing-14: 3.5rem;
  --spacing-16: 4rem;
  --spacing-18: 4.5rem;
  --spacing-20: 5rem;
  --spacing-24: 6rem;
  --spacing-28: 7rem;
  --spacing-32: 8rem;
}


/* ==========================================================================
 * QA Wave 1 — Agent E (EMI-521 / EMI-518)
 * Residual only: Prestige has no setting for banner max-height or toolbar
 * chrome borders. Scoped; do not blanket-zero borders (accordion carve-out).
 * ========================================================================== */

/* EMI-521 / F8 — cap collection-banner heroes at viewport */
.collection-banner .content-over-media {
  max-height: calc(100svh - var(--announcement-bar-height, 0px));
}

.collection-banner .content-over-media > :is(img, video, iframe, svg, video-media, figure),
.collection-banner .content-over-media > picture img,
.collection-banner .content-over-media > image-parallax img {
  max-height: calc(100svh - var(--announcement-bar-height, 0px));
  width: 100%;
  object-fit: cover;
}

/* EMI-518 — stock Prestige collection/search toolbar chrome */
.collection-toolbar {
  box-shadow: none;
}

.collection-toolbar__button-container + .collection-toolbar__button-container,
.collection-toolbar__layout-switch-list {
  border-inline-start-width: 0;
}

@media screen and (min-width: 700px) {
  .collection-toolbar__button-container {
    border-inline-start-width: 0;
  }

  .collection-toolbar__layout-switch-list {
    border-inline-width: 0;
  }
}

/* EMI-518 — cart estimate-shipping bordered panel */
.shipping-estimator.bordered-box {
  border-width: 0;
}

/* ==========================================================================
 * QA Wave 2 — Agent A (Decision G / EMI-656)
 * Footer Klaviyo: full-width embed, minimum render height for harness metric.
 * ========================================================================== */

[id$='footer-klaviyo'] .shopify-app-block,
[id$='footer-klaviyo'] [class^='klaviyo-form-'] {
  min-height: 48px;
  width: 100%;
}

[id$='footer-klaviyo'] .container {
  max-width: 640px;
}

/* ==========================================================================
 * QA Wave 2 — Agent D (Decision A Q003)
 * Normal-case headings; keep uppercase on eyebrows, badges, buttons.
 * ========================================================================== */

.h1,
.h2,
.h3,
.h4,
.h5,
.h6,
.heading,
.prose :where(h1, h2, h3, h4, h5, h6),
.product-title,
.section-header :is(h1, h2, h3, h4, h5, h6),
.product-card__title {
  text-transform: none;
}

/* EMI-711 / R3-019 — no heading smaller than body copy beneath it (footer, trust strip, etc.) */
.h5,
:where(.prose h5) {
  font-size: max(var(--text-h5), var(--text-base));
}

.h6,
:where(.prose h6) {
  font-size: max(var(--text-h6), var(--text-base));
}

/* ==========================================================================
 * QA Wave 2 — Agent L (EMI-518)
 * Search results chrome borders.
 * ========================================================================== */

.main-search .collection-toolbar,
.main-search .collection-toolbar__button-container {
  border-width: 0;
  box-shadow: none;
}

/* EMI-691 — filter/sort popovers: white dialog surface instead of beige scheme-4 */
.popover--bottom-end.color-scheme--dialog::part(content),
.facets-drawer.color-scheme--dialog::part(content) {
  background: rgb(var(--background));
  color: rgb(var(--text-color));
}

/* EMI-692 — cap empty cart vertical stretch */
.shopify-section--main-cart:has(.empty-state) .section-spacing {
  min-height: auto;
  padding-block: clamp(2.5rem, 12vh, 5rem);
}

/* EMI-692 — checkout primary above express-pay */
.shopify-section--main-cart .cart-recap {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.shopify-section--main-cart .cart-recap .button[type='submit'] {
  order: -1;
}

/* EMI-693 — 404 empty-state height cap */
.shopify-section--404 .section-spacing {
  min-height: auto;
  padding-block: clamp(3rem, 18vh, 6rem);
}

/* ==========================================================================
 * EMI-753 / R4-010 + EMI-756 / R4-011 — Klaviyo newsletter embed.
 *
 * R4-010: the "Stay connected." framing section duplicated the embed's own
 * heading and body, so those two blocks were removed from
 * sections/footer-group.json. This restores the styling they carried onto the
 * embed's OWN heading and text, which is what Emily asked for:
 *   "Remove the section's heading and text, and use CSS to restyle the embed's
 *    own 'Join the mailing list' heading and the text below it so they match
 *    the styling the removed elements had."
 *
 * Values below are the MEASURED computed styles of the removed elements,
 * captured before deleting them (dev server, 1440):
 *   heading  p.h2  Halant 28px / 400 / ls 1.4px / lh 39.2px / #303030 / centre
 *   body     p     Muli   15px / 400 / lh 24.75px / #303030 / centre
 *
 * R4-011: "We don't really use rounded input fields or rounded anything on the
 * store", with the contact page named as the target style (R4-019). Measured
 * contact form, same session:
 *   input   1px solid #e0e0e0 · radius 4px · padding 10.4px 12.8px · 15px Muli
 *   button  1px solid #657a70 · radius 4px · padding 10.4px 28px · 14px Halant
 * Klaviyo ships pill-shaped fields by default; 4px is what "not rounded" means
 * on this store, not 0.
 *
 * *** WHY THESE RULES NEED !important — MEASURED 2026-08-02 ***
 * (This complaint recurred 4 times: RC-15/F7 2026-07-28, R4-011 2026-08-01,
 * still wrong on Emily's re-check. Now measured against the live hydrated
 * form on a dev preview, footer raw-div embed, form Yh5Tsk.)
 *
 * The form hydrates in LIGHT DOM (element.shadowRoot === null — the earlier
 * shadow-DOM theory is disproven for this embed; the raw-div pattern is the
 * same one Emily proved on House of Spoils). These selectors DO match the
 * hydrated inputs/buttons. They were losing on SPECIFICITY, not reach:
 * Klaviyo's onsite JS injects goober rules with deliberately tripled class
 * selectors, e.g. `.go172361446.go172361446.go172361446` = (0,3,0), which
 * beats `[class*='klaviyo-form'] input[type='email']` = (0,2,1). Klaviyo's
 * injected rules carry no !important, so !important here wins the cascade
 * deterministically. Values come from the theme's own custom props (the
 * same ones .button/.input consume), so they track the contact-form target
 * (context/reference/contact-form-reference-spec.md, EMI-736) at every
 * breakpoint instead of freezing desktop pixels.
 *
 * SELECTOR NOTE: Klaviyo's inner class names are hashed and unstable
 * (go*/kl-private-reset-css-*), so nothing here depends on them. Everything
 * hangs off the stable container class the onsite embed emits,
 * [class*='klaviyo-form'], and targets element types inside it.
 *
 * KNOWN REMAINING GAP (out of scope here): the embed's heading and body copy
 * are rich-text p/span nodes carrying Klaviyo-editor INLINE styles (37px
 * ITC-Berkley heading, 14px Arial body), which inline-beat the h1-h4/p rules
 * below. Closing that needs either !important span-level overrides or fixing
 * the fonts inside the Klaviyo form editor (flag for Mike/Emily).
 * ========================================================================== */

[class*='klaviyo-form'] {
  text-align: center;
}

[class*='klaviyo-form'] h1,
[class*='klaviyo-form'] h2,
[class*='klaviyo-form'] h3,
[class*='klaviyo-form'] h4 {
  color: rgb(48, 48, 48);
  font-family: Halant, var(--heading-font-family, serif);
  font-size: 28px;
  font-weight: 400;
  letter-spacing: 1.4px;
  line-height: 39.2px;
  margin-block: 0;
  text-transform: none;
}

[class*='klaviyo-form'] p,
[class*='klaviyo-form'] [data-testid='form-component'] p {
  color: rgb(48, 48, 48);
  font-family: Muli, var(--body-font-family, sans-serif);
  font-size: 15px;
  font-weight: 400;
  line-height: 24.75px;
  margin-block: 0;
}

/* EMI-835 (R8-012) — closes the KNOWN REMAINING GAP documented above: the
 * editor-authored heading/body spans carry INLINE styles (measured live
 * 2026-08-02: heading span 37px ITC-Berkley inline, body spans 19px
 * Questrial / 14px Arial inline), which beat the non-!important rules
 * above. Inline styles lose only to !important, so the same approved
 * EMI-753 values are re-asserted at span level with !important. Scoped to
 * the klaviyo-form container — cannot leak into theme content. */
[class*='klaviyo-form'] :is(h1, h2, h3, h4),
[class*='klaviyo-form'] :is(h1, h2, h3, h4) span {
  color: rgb(48, 48, 48) !important;
  font-family: Halant, var(--heading-font-family, serif) !important;
  font-size: 28px !important;
  font-weight: 400 !important;
  line-height: 39.2px !important;
}

[class*='klaviyo-form'] p,
[class*='klaviyo-form'] p span {
  color: rgb(48, 48, 48) !important;
  font-family: Muli, var(--body-font-family, sans-serif) !important;
  font-size: 15px !important;
  font-weight: 400 !important;
  line-height: 24.75px !important;
}

/* The editor renders the form's heading as a <p><span> too (measured: no
 * h1-h4 exists in the hydrated form — which is why the earlier h-tag rule
 * never fired). The heading span is identified by its editor-authored
 * inline serif stack (ITC-Berkley…), the one stable marker it carries. */
[class*='klaviyo-form'] p span[style*='ITC-Berkley'] {
  font-family: Halant, var(--heading-font-family, serif) !important;
  font-size: 28px !important;
  letter-spacing: 1.4px !important;
  line-height: 39.2px !important;
  text-transform: none !important;
}

[class*='klaviyo-form'] input[type='email'],
[class*='klaviyo-form'] input[type='text'],
[class*='klaviyo-form'] input[type='tel'],
[class*='klaviyo-form'] select,
[class*='klaviyo-form'] textarea {
  /* mirrors theme.css .input (line ~1353) — contact-form reference values */
  background: rgb(var(--input-background, transparent)) !important;
  border: 1px solid rgb(var(--border-color)) !important;
  border-radius: var(--input-border-radius) !important;
  color: rgb(var(--input-text-color, var(--text-color))) !important;
  font: var(--text-font-style) var(--text-font-weight) var(--text-base) / 1.65 var(--text-font-family) !important;
  height: auto !important; /* Klaviyo goober pins height: 53px */
  letter-spacing: normal !important;
  padding: var(--input-padding-block) var(--input-padding-inline) !important;
}

/* EMI-890 (R11-017): Okendo's injected widget CSS caps its own wrapper at
   1200px, misaligned with the theme's standard container ("You may also
   like"). The apps_okendo instances now keep their .container wrapper
   (remove_horizontal_spacing false), which carries the theme's width and
   gutter — so Okendo's internal cap is simply defeated and the section
   inherits whatever the standard container does (including EMI-892's
   cap removal) automatically. !important because Okendo's stylesheet loads
   after the theme's (same battle as the Klaviyo rules below). */
[data-oke-container] {
  max-width: none !important;
}

/* EMI-884 (R11-011): Klaviyo styles ::placeholder directly, so the pseudo-
   element escapes the input font rule above and renders oversized. Same
   !important specificity battle as EMI-753's input/button fixes. font:inherit
   pulls the placeholder back to the input's own (already corrected) font. */
[class*='klaviyo-form'] input[type='email']::placeholder,
[class*='klaviyo-form'] input[type='text']::placeholder,
[class*='klaviyo-form'] input[type='tel']::placeholder,
[class*='klaviyo-form'] textarea::placeholder {
  font: inherit !important;
}

[class*='klaviyo-form'] button,
[class*='klaviyo-form'] input[type='submit'] {
  /* mirrors theme.css .button (line ~417) — contact-form reference values */
  background-color: transparent !important;
  background-image: linear-gradient(rgb(var(--button-background)), rgb(var(--button-background))) !important;
  border: 1px solid rgb(var(--button-outline-color, var(--button-background))) !important;
  border-radius: var(--button-border-radius) !important;
  color: rgb(var(--button-text-color)) !important;
  font: var(--button-font) !important;
  height: auto !important; /* Klaviyo goober pins height: 53px */
  letter-spacing: var(--button-letter-spacing) !important;
  padding: 0.65rem 1.75rem !important;
  text-transform: var(--button-text-transform) !important;
}

/* EMI-738 / R4-002 — mega-menu content left-aligned with the container edge.
   Stock Prestige centres the linklist + promo (justify-content: safe center),
   which reads as "aligned with nothing". flex-start puts the first column on
   the container gutter, in line with the header content. */
.mega-menu {
  justify-content: flex-start;
}

/* EMI-739 / R4-003 — cap the menu height by wrapping long link lists into
   additional columns. The chunking is server-side in mega-menu.liquid (12
   links per column ≈ the height cap); this rule lays the chunks out side by
   side. The stock 280px cap on linklist items would clip the extra columns,
   so it is lifted — column widths stay natural. */
.mega-menu__linklist > li {
  max-width: none;
}

.mega-menu__link-columns {
  display: flex;
  align-items: flex-start;
  column-gap: 2.5rem;
}

/* EMI-526 / RC-08 — collection-list carousel arrows are hover-only:
   collection-list.liquid:154 emits floating-controls-container--on-hover and
   theme.css holds the controls at opacity 0 until :hover. Emily: "not clear
   it is a carousel until you hover". On fine-pointer (desktop) devices, keep
   enabled arrows visible without hover; disabled arrows (at either end of
   the scroll range) stay hidden, matching the stock hover behaviour. Scoped
   to collection-list sections only — no blanket opacity override. */
@media screen and (pointer: fine) {
  .shopify-section--collection-list
    .floating-controls-container--on-hover
    > .floating-controls-container__control:not(:has([disabled])) {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ==========================================================================
 * EMI-818 / R4-064, R4-067, R4-078 — availability-state panel (Option C).
 * The .product-state classes are emitted by snippets/product-state.liquid and
 * had NO rule anywhere in the theme (grep -rl "product-state" assets/ returned
 * nothing) — the mechanical cause of "it's kind of all reading as one
 * paragraph". No Prestige control owns these classes: residual by definition.
 *
 * One bordered, padded panel separates the closed-state message from the
 * description; the message itself takes the theme's h6 heading treatment
 * (heading font vars + max(--text-h6, --text-base), the same floor rule used
 * for .h6 above). Theme rhythm vars only — no new magic numbers.
 * ========================================================================== */

/* EMI-840 (R8-017): the box previously rendered identically on every
 * breakpoint (20/24px padding). Mobile gets the real reduction, desktop a
 * slight one — mobile-first base, desktop override at the theme's 700px
 * breakpoint. Theme rhythm vars only. */
.product-state {
  border: 1px solid rgb(var(--border-color));
  padding: var(--spacing-3) var(--spacing-4);
  text-align: center;
}

@media screen and (min-width: 700px) {
  .product-state {
    padding: var(--spacing-4) var(--spacing-5);
  }
}

.product-state p {
  margin-block: 0;
}

.product-state--ended p,
.product-state--sold-out p {
  font-family: var(--heading-font-family);
  font-weight: var(--heading-font-weight);
  font-style: var(--heading-font-style);
  letter-spacing: var(--heading-letter-spacing);
  font-size: var(--text-base);
  line-height: 1.5;
}

@media screen and (min-width: 700px) {
  .product-state--ended p,
  .product-state--sold-out p {
    font-size: max(var(--text-h6), var(--text-base));
    line-height: 1.7;
  }
}

/* ==========================================================================
 * EMI-842 (R8-019): the sticky product-info offset reads --sticky-area-height
 * (via --header-is-visible), which JS flips instantly on scroll-direction
 * change, while the header itself animates over .25s (theme.css
 * .shopify-section--header transition). The offset snapping ~250ms ahead of
 * the header is the visible "scrolls behind/ahead" glitch. Matching the
 * header's own transition keeps the two in step.
 * ========================================================================== */
@media screen and (min-width: 1000px) {
  .product-info {
    transition: inset-block-start .25s ease-in-out;
  }
}

/* ==========================================================================
 * R13-001 — homepage banner jumps ~30px up and down while scrolling.
 *
 * Same mechanism as EMI-842 above, different element and a different fix.
 * The header is hide-on-scroll, so assets/theme.js:5125 flips
 * --header-is-visible between 1 and 0 on every scroll-direction change.
 * snippets/css-variables.liquid:76 multiplies that into --sticky-area-height,
 * and theme.css:747-749 subtracts it from .content-over-media--fill's height.
 * The full-bleed sections therefore resize by exactly one header height every
 * time the scroll direction changes; content centred inside them moves half
 * that, which is the 30-40px Emily is seeing.
 *
 * Measured on this ref before the fix, homepage slideshow at a 900px
 * viewport: height 839px with --header-is-visible 1, 900px with it 0 — a
 * 61px delta, exactly --header-height.
 *
 * The theme already exempts the FIRST section (theme.css:754-757), which is
 * why the image-with-text-overlay above the slideshow never jumped and the
 * slideshow did. The fix is the theme's own precedent for this exact
 * problem: theme.css:6012 gives .images-with-text-scroll a local
 * --sticky-area-height that omits --header-is-visible. Same thing here, so
 * the reserved space is the header's resting height and stops responding to
 * visibility at all.
 *
 * Deliberately NOT a transition: EMI-842 could use one because the offset
 * only had to stay in step with the header's own animation. Here the value
 * itself is wrong, and a transition would animate the jump rather than
 * remove it.
 * ========================================================================== */
.content-over-media--fill {
  --sticky-area-height: calc(
    var(--announcement-bar-is-sticky, 0) * var(--announcement-bar-height, 0px) + var(--header-is-sticky, 0) *
      var(--header-height, 0px)
  );
}

/* F1006-17: "Show next section by" on image-with-text-overlay (desktop only).
 * --vrp-hero-peek is set by the section only when image size is Fit screen
 * and the setting is above 0; at 0 nothing is emitted and theme.css:734-757
 * applies unchanged. Same three selectors as theme.css so the override is a
 * cascade-order win, not a specificity escalation. */
@media screen and (min-width: 1000px) {
  .content-over-media--fill {
    --content-over-media-height: calc(100vh - var(--sticky-area-height) - var(--vrp-hero-peek, 0px));
  }

  .shopify-section:first-child .content-over-media--fill {
    --content-over-media-height: calc(100vh - (var(--announcement-bar-height, 0px) + var(--header-height, 0px)) - var(--vrp-hero-peek, 0px));
  }

  .shopify-section:first-child:has([allow-transparent-header]) .content-over-media--fill {
    --content-over-media-height: calc(100vh - (var(--announcement-bar-height, 0px)) - var(--vrp-hero-peek, 0px));
  }

  @supports (height: 100svh) {
    .content-over-media--fill {
      --content-over-media-height: calc(100svh - var(--sticky-area-height) - var(--vrp-hero-peek, 0px));
    }

    .shopify-section:first-child .content-over-media--fill {
      --content-over-media-height: calc(100svh - (var(--announcement-bar-height, 0px) + var(--header-height, 0px)) - var(--vrp-hero-peek, 0px));
    }

    .shopify-section:first-child:has([allow-transparent-header]) .content-over-media--fill {
      --content-over-media-height: calc(100svh - (var(--announcement-bar-height, 0px)) - var(--vrp-hero-peek, 0px));
    }
  }
}

/* ==========================================================================
 * EMI-839 (R8-016 / R9-004): product-page price typography follows the BODY
 * font, so price and the Afterpay instalment line read as one family.
 *
 * Root cause (corrected in R9 — the earlier assumption was wrong): the
 * product-context price renders with .h4/.h5/.h6 (snippets/price-list.liquid
 * :26-29), and theme.css:8563-8564 gives every heading class
 * --heading-font-family. There is NO path from button_text_font, or any other
 * theme setting, into the price font — so no settings value could ever have
 * fixed this. It needs an explicit override.
 *
 * Scoped to .price-list--product (set in price-list.liquid:33 only when
 * context == 'product'). Card and line-item prices are deliberately NOT
 * touched: those already have a merchant control, settings.product_card_text_font
 * (price-list.liquid:17), and overriding it here would silently defeat it.
 *
 * letter-spacing/text-transform are pinned to the body equivalents rather than
 * left inherited. They are no-ops today (heading_letter_spacing is 0 and
 * heading_text_transform is normal), but leaving them would silently re-couple
 * the price to the heading scale if either global is ever changed again.
 * ========================================================================== */
.price-list--product :is(.h4, .h5, .h6) {
  font-family: var(--text-font-family);
  font-weight: var(--text-font-weight);
  font-style: var(--text-font-style);
  letter-spacing: var(--text-letter-spacing);
  text-transform: none;
}

/* ==========================================================================
 * EMI-862 (R9-005): product badge typography follows the new theme settings.
 *
 * theme.css:3454-3465 hard-pins .badge to --heading-font-family and
 * text-transform: uppercase, with no settings hook of any kind — unlike
 * buttons, which already had button_text_font/button_text_transform. Emily
 * asked for the same control over badges ("Special Edition" etc.) after
 * moving the CTAs to sans-serif normal-case.
 *
 * The new badge_text_font / badge_text_transform settings resolve into the
 * --badge-* custom properties in snippets/css-variables.liquid; this rule is
 * the only consumer. No !important needed: brand-residual.css loads after
 * theme.css (layout/theme.liquid:64-65), so equal specificity resolves here.
 *
 * Defaults are body + normal, matching the button values Emily chose rather
 * than the old hardcoded heading + uppercase. Either is one click to change
 * in Theme settings -> Typography -> Product badge.
 * ========================================================================== */
.badge {
  font-family: var(--badge-font-family);
  font-weight: var(--badge-font-weight);
  font-style: var(--badge-font-style);
  letter-spacing: var(--badge-letter-spacing);
  text-transform: var(--badge-text-transform);
}

/* EMI-843 (R9-017): the "Shop the prints" link moved out of the bordered
 * ended-state panel and now renders as its own sibling below it. Without a
 * top margin it butts straight against the panel border and still reads as
 * attached to it, which is the thing the move was meant to fix. */
/* R13-013 (Emily's call, 2026-08-03): centre the link under the box and drop
   it one step on the type scale.
   It was inline-block with no text-align, so its left edge sat on the box's
   outer border while every bit of copy inside the box is centred
   (brand-residual.css ~:416) — there was nothing for it to line up with.
   block + text-align: center puts it on the box's centre line.
   Size: it inherited --text-base, the same size as the box copy it sits
   under; --text-sm is the button scale (14px desktop / 13px mobile), which
   is what "slightly smaller" means on this store.
   The alternative — left-aligning the box copy so the link's existing edge
   matched — was rejected because the box is shared with the prelaunch and
   sold-out states (product-state.liquid:43,56), so it would have changed
   two states Emily did not ask about. */
/* R14-002 (Emily, 2026-08-03): the underline spanned the full row. `.link`'s
   underline is a background-gradient sized 100% 1px against the ELEMENT BOX
   (theme.css:2771-2775), not text-decoration — so `display: block` above
   stretched the underline to the box while `text-align: center` only moved the
   glyphs inside it. Shrinking the box to its text with `width: fit-content` and
   re-centring it with `margin-inline: auto` gives a text-width underline that is
   still centred; `text-align` then has nothing left to do and is dropped.
   `inline-block` + `text-align: center` on the parent was rejected: the parent is
   shared with the prelaunch and sold-out states (product-state.liquid:43,56). */
.product-state__after-link {
  display: block;
  width: fit-content;
  margin-inline: auto;
  font-size: var(--text-sm);
  margin-block-start: 0.75rem;
}

/* ==========================================================================
 * EMI-866 (R9-009): banner padding-top equals header height on EVERY
 * transparent-header banner, not only the page's first section.
 *
 * theme.css:714 scopes the rule to `.shopify-section:first-child`, so a banner
 * further down the page got no padding. Emily's decision: apply it everywhere.
 * theme.css is kept vendor-pristine, so the declaration is repeated here
 * without that scope. The stock rule stays and is a higher-specificity subset
 * asserting the same value — no conflict, this only extends coverage.
 *
 * The SECOND rule is the one that actually fixes the reported bug, found by
 * the GATE-5 review. theme.css:714 uses a direct-child combinator:
 *   [allow-transparent-header] > .content-over-media > :not(...)
 * but sections/series-story.liquid:196-201 puts allow-transparent-header on an
 * OUTER wrapper, and its .content-over-media is a grandchild —
 *   [allow-transparent-header] > .series-story > .series-story__entry
 *     > .content-over-media
 * so the padding rule never matched, while the header still went transparent
 * (theme.css:5173 only needs :has(… [allow-transparent-header])). That is
 * exactly "text sits under the header": the header is see-through and nothing
 * pushes the hero copy clear of it.
 *
 * Scoped to .series-story__entry--hero (snippets/series-story-hero.liquid:44)
 * rather than every entry — only the hero sits under the header; padding the
 * mid-story entries would inject 55px of dead space throughout the story.
 *
 * Affects collection.series-prints, collection.series and the two split
 * variants, all of which set hero_transparent_header: true. Latent today —
 * every collection sampled still renders collection.json — but EMI-867 is
 * about to put those templates into service.
 * ========================================================================== */
/* R14-005 (Emily, 2026-08-03): the two rules below are why the video banner's
   pause button got tall with dead space above it. vrp-autoplay-video.js:60
   injects <button class="vrp-video-control"> as a direct child of
   .content-over-media, so it matched :not(...) and took a full --header-height
   of padding-block-start — 42.8px -> 93.4px at --header-height: 61px. Its own
   stylesheet (vrp-autoplay-video.css) never changed; 97e56a5 widened these
   rules' scope from :first-child, which is when it started matching.
   The control is chrome, not banner copy, so it is excluded here rather than
   given a compensating padding: at 0,1,0 `.vrp-video-control` loses the
   specificity contest with these 0,2,1 selectors, so that fix would be a
   silent no-op. */
[allow-transparent-header] > .content-over-media > :not(img, video, iframe, svg, video-media, picture, image-parallax, .vrp-video-control) {
  padding-block-start: var(--header-height, 0px);
}

[allow-transparent-header] .series-story__entry--hero > .content-over-media > :not(img, video, iframe, svg, video-media, picture, image-parallax, .vrp-video-control) {
  padding-block-start: var(--header-height, 0px);
}

/* ==========================================================================
 * R13-002 / R13-003 — testimonial cards.
 *
 * R13-003: the quote must sit at the top of the card and the author credit
 * at the bottom, whatever the quote's length. DOM order is already correct
 * (testimonials.liquid quote then author); the cause is
 * justify-content: flex-start at theme.css:7496. Pushing the author down
 * with an auto top margin does exactly that and nothing else.
 * Deliberately NOT reverting to space-between: that is what EMI-878 removed,
 * and it reintroduces the variable gap between quote and author that Emily
 * complained about in R11.
 *
 * R13-002: image width and shape become merchant controls. Defaults are
 * today's rendering — 28% wide (theme.css:7447) and no aspect-ratio at all
 * (EMI-878 removed it, comment at :7436-7443) — so this is a no-op until
 * Emily picks a shape. The fallbacks in the var() calls are the current
 * values, so the rules stay correct even if a section somehow emits nothing.
 * ========================================================================== */
.testimonial-card__author {
  margin-block-start: auto;
}

/* R14-008 (Emily, 2026-08-03) — "portrait only, put it back", superseding
   R13-002. The width range and the four-option shape select are gone, and with
   them the .testimonial-card__media rule that set aspect-ratio + height: the
   media reverts to a plain stretched grid item, so "always full height" is
   achieved by DELETING code rather than adding it. 28% is the value the range
   defaulted to and the width theme.css:7447 shipped before EMI-878; it is
   written literally now because no section emits the custom property any more,
   and a var() with no emitter is a fallback pretending to be a control.

   30%, not the 28% the range defaulted to: 30 is what templates/index.json had
   stored and therefore what the homepage renders today. Removing a control
   should not move the thing it controlled. */
.testimonial-card {
  grid-template-columns: 30% minmax(0, 1fr);
}

/* ==========================================================================
 * R14-001 (Emily, 2026-08-03): the Filter and Sort by buttons read black,
 * not the grey of the rest of the bar.
 *
 * It is not a colour setting and not a scheme problem — theme.css:4621-4623
 * puts a hardcoded `opacity: .5` on every .collection-toolbar__button. The
 * scheme's text colour #303030 composited at 50% over the page background
 * lands at roughly #979797, which is the grey Emily is seeing; full opacity
 * gives the intended #303030 (near-black, not pure #000).
 *
 * SCOPED to the button-container children on purpose. The grid-switch icons
 * (main-collection.liquid:262-306) are the same class but live in
 * <collection-layout-switch class="collection-toolbar__layout-switch-list">,
 * and .5-vs-1 opacity is their ONLY active-state affordance
 * (theme.css:4626-4628). A blanket .collection-toolbar__button { opacity: 1 }
 * would make every grid icon look selected.
 * ========================================================================== */
.collection-toolbar__button-container > .collection-toolbar__button {
  opacity: 1;
}

/* ==========================================================================
 * R14-020 (Emily, 2026-08-03): "the image looks stretched. No image should
 * ever, ever, ever be stretched."
 *
 * Measured on the build theme (151570055337) at 1440x900, Meet the Artist
 * (image-with-text): natural 720x1080, rendered 720x900 -> 20% stretch, with
 * computed object-fit `fill`.
 *
 * theme.css:5923-5927 gives the image `object-fit: contain; width/height: 100%`
 * — but as a DIRECT-child selector, and image-with-text.liquid:26 wraps the
 * image in <picture>, so the real <img> is a grandchild and never matched it.
 * theme.css:5938-5940 later added `.image-with-text > picture img` to the
 * `max-height: 100svh` cap ONLY (EMI-877). So the picture-wrapped image took
 * the cap without the fit: once the cap binds, the box is forced shorter than
 * the intrinsic ratio and object-fit falls back to its initial value, `fill`,
 * which stretches. That is why it looks right at 390x844 (1080-tall image
 * scales to 585, cap never binds, 0% distortion) and wrong at 1440x900.
 *
 * `contain` rather than `cover`, matching what theme.css:5934 says the cap was
 * designed to do — "the capped image letterboxes instead of cropping". If
 * Emily would rather crop than letterbox, this one keyword is the whole change.
 * ========================================================================== */
.image-with-text > picture img {
  object-fit: contain;
}

/* ==========================================================================
 * R14-021 (Emily, 2026-08-03): in the heading-left / link-right header row,
 * the heading reads as sitting higher than the link's underline.
 *
 * She is right, and it is 7.8px. Measured on the build theme at 1440,
 * Best-Seller Prints / View Collection:
 *   heading baseline  1000.0
 *   link baseline     1000.0   <- theme.css:7366 align-items: baseline works
 *   link underline    1007.8   <- the underline is a background-gradient at
 *                                 the link's BOX bottom (background-position
 *                                 `0 min(100%, 29.7px)`), not at its baseline
 * So the row aligns the two BASELINES, while the eye reads the heading's
 * baseline against the link's UNDERLINE — and the underline is one half-leading
 * plus one descender lower.
 *
 * Lifting the link by exactly that distance puts the underline on the shared
 * baseline. Expressed in the link's own metrics rather than as 7.8px so it
 * survives the breakpoint font-size changes: half-leading is (1lh - 1em)/2,
 * and --header-row-link-descent is the font's descent below the baseline.
 *
 * Covers both sections that use this format — featured-collections and the
 * series cross-sell — which is the "all sections" part of her instruction.
 * ========================================================================== */
.featured-collections__header-row .link,
.series-story-cross-sell__header-row .link {
  position: relative;
  inset-block-end: calc((1lh - 1em) / 2 + var(--header-row-link-descent, 0.195em));
}

/* ==========================================================================
 * R14-022 (Emily, 2026-08-03): "completely remove the gap in between the
 * filter bar and the collection grid."
 *
 * Measured on the build theme, /collections/print-shop at 1440: 48px.
 *
 * It is NOT a margin on the toolbar (measured 0px), NOT padding on the product
 * list (0px), and not `.collection__main`'s row-gap either (that is
 * --section-stack-gap, 28px, and sits further down the tree). The rendered
 * chain is:
 *
 *   div.v-stack.gap-6.sm:gap-12      <- row-gap 1.5rem / 3rem, THIS is the 48px
 *     height-observer.collection-toolbar
 *     div.collection > div.collection__main > product-list
 *
 * Zeroing the v-stack's row-gap would also close the optional section-heading
 * to toolbar seam above it (main-collection.liquid:160), which Emily did not
 * ask for. Cancelling exactly one gap - the one after the toolbar - leaves
 * every other seam in the collection untouched. The two values mirror the
 * utility classes on the wrapper (theme.css:8474 gap-6, :8563 sm:gap-12), so
 * they move together if those ever change.
 * ========================================================================== */
.collection-toolbar + .collection {
  margin-block-start: -1.5rem;
}

@media screen and (min-width: 700px) {
  .collection-toolbar + .collection {
    margin-block-start: -3rem;
  }
}

/* ==========================================================================
 * R14-024 (Emily, 2026-08-03): "we have set the padding for containers on
 * mobile to be 12 px inline, which I can see in many places. In other places,
 * however, it's not coming through. For example, in the Join the Mailing List
 * section, it's 16 px, and that's because we need to get that out of the
 * Klaviyo form."
 *
 * Measured on the build theme at 390px wide:
 *   --container-gutter                                  12px   <- the standard
 *   form.klaviyo-form (the element Klaviyo hydrates)     16px   <- the 16px
 *   the div inside it                                     6px   (Klaviyo's own
 *                                                               field rhythm,
 *                                                               left alone)
 *
 * So it is one element, not a cascade problem: Klaviyo's injected stylesheet
 * puts 16px on the <form> itself. Pinning it to --container-gutter makes the
 * embed track the theme at every breakpoint rather than at one hardcoded value.
 *
 * !important for the reason already documented above for this embed: Klaviyo's
 * styles are injected at runtime and win on load order. The form hydrates in
 * LIGHT dom (the earlier shadow-DOM theory is disproven for this raw-div
 * embed), so a normal selector reaches it - it just does not outrank it.
 *
 * Scoped to the footer instance so a Klaviyo popup or a form embedded anywhere
 * else keeps whatever Klaviyo gives it.
 * ========================================================================== */
[id$='footer-klaviyo'] form.klaviyo-form {
  padding-inline: var(--container-gutter) !important;
}

/* ==========================================================================
 * R14-026 (Emily, 2026-08-03): "update the testimonials carousel so that it has
 * the five-star icons in. They should be the sage... Let's put them on the same
 * line as the name, but pushed to the right."
 *
 * The name and the stars now share one row, name left and stars right, aligned
 * on their baselines so the stars sit with the text rather than above it.
 *
 * The stars are decorative and always five: these are quotes Victoria has picked
 * out, not live scores. Real ratings come from the review app's own widget,
 * which has its own colour. Colour comes from the section's Star colour setting,
 * defaulting to the sage, so the section is right on arrival and still one place
 * to change.
 * ========================================================================== */
/* R15-001 (Emily, 2026-08-03): "when we added the stars, the names are no
   longer pushed to the bottom."
   She is right, and this rule is where it broke. Before R14-026 the author
   carried `margin-block-start: auto` (:654-656 above) and that auto margin is
   what pushed the name to the foot of the card. R14-026 wrapped the author in
   this new footer row, gave the row a FIXED 0.5rem top margin, and then zeroed
   the author's auto margin at :846 — so the mechanism was deleted rather than
   moved onto its new owner. Measured before the fix, five cards all 217px tall:
   the gap from footer bottom to card bottom ran 17 / 38 / 80 / 122 / 122px,
   tracking quote length exactly.
   `auto` rather than a fixed value because the cards are equal-height flex
   columns of unequal content — only auto absorbs the difference. The 0.5rem it
   replaces is not lost: .testimonial-card__content already sets `gap: 0.5rem`
   (theme.css:7491-7500), which floors the spacing on a card too full to have
   any slack. */
.testimonial-card__footer {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-block-start: auto;
}

/* the row now owns the spacing above it, so the author must not add its own */
.testimonial-card__footer .testimonial-card__author {
  margin-block-start: 0;
}

.testimonial-card__stars {
  color: var(--testimonial-card-star-color, #aabaa3);
  display: inline-flex;
  flex-shrink: 0;
  gap: 0.125rem;
  line-height: 1;
}

/* ==========================================================================
 * R14-028 (Emily, 2026-08-03): "on the product templates on desktop, I am
 * noticing a horizontal scroll, so something is wider than it should be."
 *
 * Measured at 1440: document scrollWidth 1488 against a 1440 viewport, so 48px,
 * and only on products whose reviews carry customer photos. Every other product
 * page measured 0, which is why it looks intermittent.
 *
 * It is the review app's own markup, not ours, and the grid is already correct:
 *
 *   .oke-w-review-main   display: grid, 1116px wide, tracks 820px 248px 48px
 *   .oke-w-review-media  248px, sitting in the middle track
 *                        PLUS margin-inline-start: 48px
 *
 * The third 48px track is Okendo's own spacing column, so the 48px margin on the
 * media is spacing applied twice. The second copy has nowhere to go and pushes
 * the document 48px wider. Removing the margin leaves the gap the grid track was
 * already providing, so nothing moves visually.
 *
 * !important because Okendo's injected stylesheet wins on this property
 * otherwise: measured, the margin stayed at 48px without it. Same reason the
 * Klaviyo rules above carry it.
 * ========================================================================== */
.oke-w-review-main > .oke-w-review-media {
  margin-inline-start: 0 !important;
}

/* ==========================================================================
 * R15-004 (Emily, 2026-08-03): "I can see the Read More button, but I can't
 * see any text. When I use dev tools to inspect, I can see that there is text
 * that is supposed to pull through, but it's not visible at all."
 *
 * Not a clamp bug. Measured on /products/against-all-hope?view=originals: the
 * product description contains markup pasted out of the old Impact theme —
 *
 *   div.media-with-text__item
 *     > div.media-with-text__content
 *         > div.prose.break-all[reveal-on-scroll="true"]   <- opacity: 0
 *
 * theme.css:7805-7809 sets [reveal-on-scroll="true"] { opacity: 0 } under
 * prefers-reduced-motion: no-preference, and the IntersectionObserver that
 * clears it only ever runs for elements the theme itself renders. An element
 * pasted into a description is never observed, so it stays at 0 forever. The
 * text was present (461 chars) and laid out (456x102 at y=211) the whole time,
 * which is exactly what Emily saw in dev tools.
 *
 * Scoped to .prose descendants because that is where merchant-authored HTML
 * lands and NOWHERE the theme puts this attribute: grepped across sections/,
 * snippets/, blocks/ and layout/ — 15 emit sites, all of them section-level
 * wrappers or card roots (product-card.liquid:45, collection-promo-block:21,
 * product-card-placeholder:28), none inside a .prose. So this rule can only
 * ever match pasted markup, and real scroll reveals are untouched.
 *
 * Specificity 0,2,0 beats the stock 0,1,0, so no !important is needed.
 * ========================================================================== */
.prose [reveal-on-scroll="true"] {
  opacity: 1;
}

/* ==========================================================================
 * R15-029 (Emily, 2026-08-03): "In the filter bar where we have the product
 * count, can we remove the space in between the last word of the collection
 * title and the product count? ... it's kind of reading as part of the title
 * ... That is specifically for the filter bar. It is fine in the product title
 * banner."
 *
 * The treatment she approves already existed — as an inline style attribute on
 * collection-banner.liquid:65, which is precisely why the sticky toolbar title
 * never inherited it. Lifted into a shared class here and applied to both, so
 * the two surfaces cannot drift apart again.
 *
 * The literal space in main-collection.liquid:190 is gone; margin-inline-start
 * provides the separation, which is also what stops the count wrapping onto its
 * own line at narrow widths.
 * ========================================================================== */
.products-count-sup {
  vertical-align: super;
  font-size: 0.4em;
  margin-inline-start: 0.35em;
  letter-spacing: normal;
}

/* ==========================================================================
 * R15-030 (Emily, 2026-08-03): "the text in the banner is not white when it
 * should be... Let's make sure that this doesn't happen again for any other
 * instances of these sections on other pages."
 *
 * Measured before the fix on the NW series signup LP: both banners rendered
 * their overlay text at rgb(48,48,48) while the BUTTONS in the same banners
 * were rgb(255,255,255) — which is why it read as a bug rather than a choice.
 *
 * Text colour in this section came from the colour scheme, and of the five
 * schemes on the store only scheme-5 carries white text (#ffffff); schemes 1-4
 * are all #303030. So legibility over a photograph depended on the merchant
 * picking one particular scheme, using a control that says nothing about
 * photographs. sections/image-with-text-overlay.liquid now emits an explicit
 * light/dark class; this is what it drives.
 *
 * Overriding --text-color rather than `color` because that is the variable the
 * scheme system actually resolves (css-variables.liquid:269,308 set it, and
 * theme.css:215 reads `color: rgb(var(--text-color))`). Setting the variable
 * means headings, prose, links and the outline-button border (theme.css:219-225,
 * which derives --border-color from --text-color) all follow together, instead
 * of only whichever elements a `color` rule happened to reach.
 *
 * Residual by the R5 test: no Prestige control owns "text colour over an image
 * independently of the section scheme" — that is the whole gap being closed.
 * ========================================================================== */
.image-overlay-text--light {
  --text-color: 255 255 255;
}

.image-overlay-text--dark {
  --text-color: 48 48 48;
}

/* ==========================================================================
 * R16-001 (Emily, 2026-08-03): the "Shop Prints" CTA at the foot of a series
 * page. Fifth report, and the first four fixes were aimed at the wrong element.
 *
 * "I have given this feedback more than I've given any other feedback."
 *
 * WHY IT NEVER CHANGED. Every previous attempt (R12-016, R13-018, R13-019,
 * R14-014, R15-021) edited snippets/series-story-cross-sell.liquid. Measured on
 * /collections/wild-at-heart?view=series, the element Emily is pointing at is
 * not a cross-sell CTA at all:
 *
 *   .series-story__entry--text
 *     > .container--sm > .prose
 *       > .metafield-rich_text_field
 *         > <p><a href="...">Shop Prints</a></p>      <- no class, bare <a>
 *
 * It is a link typed into a story_text metaobject field, rendered by
 * series-story-text.liquid, and its underline is .prose a's background-gradient.
 * So five rounds of edits to the cross-sell snippet could not have moved it, and
 * Emily was right every single time.
 *
 * Her scope is explicit and narrow: "I am only talking about the link that says
 * 'Shop Prints', which comes just before the Instagram section and just after
 * the image gallery... The one at the top can remain underlined style." So the
 * rule is NOT "all links in stories" — it is a link that is the ENTIRE content
 * of its own story entry, i.e. a standalone call to action rather than a link
 * inside a sentence.
 *
 * :only-child twice is what encodes that: the paragraph must be the only thing
 * in the rich text field, and the link the only thing in the paragraph. The
 * "Wild at Heart Exhibition" entry above it has a heading and several
 * paragraphs, so it does not match, and links inside prose keep the underline.
 *
 * #768b7c / #ffffff is Emily's standing colour decision from 2026-08-02, the
 * same fixed pair button.liquid uses for add to cart.
 * ========================================================================== */
.series-story__entry--text .metafield-rich_text_field > p:only-child > a:only-child {
  --button-background: 118 139 124;
  --button-text-color: 255 255 255;
  background-image: none;
  background-color: rgb(var(--button-background));
  color: rgb(var(--button-text-color));
  border: 1px solid rgb(var(--button-background));
  border-radius: var(--button-border-radius);
  font: var(--button-font);
  letter-spacing: var(--button-letter-spacing);
  text-transform: var(--button-text-transform);
  text-align: center;
  text-decoration: none;
  padding: 0.65rem 1.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ==========================================================================
 * R16-002 (Emily, 2026-08-03): "make sure that the view collection button is
 * aligned to the bottom of the text block. Currently, it looks like it's
 * aligned to the top of the text block. That feedback should be applied to all
 * instances of this section across all templates."
 *
 * theme.css:7364-7371 sets align-items: baseline on the header row. With a
 * single-line heading that reads as aligned. With a heading PLUS a paragraph of
 * copy underneath — which is what R14-014 moved into the left column — the
 * column's baseline is its FIRST line's, so the CTA sits level with the heading
 * and looks pinned to the top of a tall block. That is exactly what Emily is
 * describing, and it is a consequence of R14-014 rather than a separate bug.
 *
 * `end` rather than `baseline`: she wants the button's bottom edge level with
 * the bottom of the text block, which is a box relationship, not a text one.
 * Both header rows carry it — the series cross-sell and the homepage/featured
 * variant — because she asked for "all instances of this section".
 * ========================================================================== */
.series-story-cross-sell__header-row,
.featured-collections__header-row,
.collection-list__header-row {
  align-items: end;
}

/* R16-008 completion, caught by the R16 close-out re-measure: PR #62 shipped
 * the collection-list header-row MARKUP, but theme.css:7363 only gives the
 * flex layout to the featured-collections and cross-sell variants — so the
 * View all link rendered BELOW the heading (display: block) while the
 * align-items above applied to nothing. Same layout the other two rows get. */
.collection-list__header-row {
  width: 100%;
  justify-content: space-between;
  gap: 1rem;
  text-align: start;
  display: flex;
}

/* ==========================================================================
 * R16-003 (Emily, 2026-08-03): the collection filter bar's product count.
 * "it is currently way too small. Let's change it from 0.8m to 0.6m."
 *
 * R15-029 gave the toolbar the banner's treatment, which is 0.4em. Emily is
 * reading those as 0.8/0.6, so this is a relative instruction rather than an
 * absolute one: make it larger than it is, by the ratio she gave. 0.4 -> 0.6em.
 *
 * The banner keeps 0.4em. She said explicitly, twice now, that the banner reads
 * correctly, so the two surfaces genuinely want different sizes and the shared
 * class is split rather than retuned.
 * ========================================================================== */
.collection-toolbar__sticky-title .products-count-sup {
  font-size: 0.6em;
}

/* R16-004's mobile flex-cell block used to sit here; R18 removed it — a flex
 * cell shifts the Filter button, and the section CSS now absolutely centres
 * the title over the row at every width instead. */

/* ==========================================================================
 * R16-005 (Emily, 2026-08-03): "The quick add buttons are 44 by 44 px. This
 * feels quite large. If we can reduce them... Research to find out what is an
 * acceptable button size on mobile and reduce it to that size."
 *
 * Measured: theme.css:2312-2320 gives .product-card__quick-add-button
 * padding: .625rem around a 24px icon = 44x44.
 *
 * What the research actually says, because it does not point one way:
 *   WCAG 2.1 AAA (2.5.5 Target Size)        44 x 44 CSS px
 *   WCAG 2.2 AA  (2.5.8 Target Size Minimum) 24 x 24 CSS px
 *   Apple HIG                                44 x 44 pt
 *   Material Design                          48 x 48 dp touch target, and it
 *                                            explicitly allows a SMALLER visual
 *                                            control inside that target
 *
 * So 44px is not oversized by accident, it is the AAA figure, and shrinking the
 * whole control to 32px would drop the site from AAA to AA on every product
 * card. Material's pattern is the way to have both: shrink what you SEE, keep
 * the area you can HIT. Visual circle 32px, touch target still 44x44 via a
 * transparent ::after. Nothing is lost and Emily gets the lighter card.
 *
 * Also explains the thing she called weird: "when I am in dev tools and then I
 * click the select element button, they suddenly disappear and shrink." That is
 * theme.css:2322-2340, `@media (pointer: fine)`, which hides the button until
 * hover. Toggling device emulation flips `pointer` between fine and coarse, so
 * the button appears and disappears. Not a defect.
 * ========================================================================== */
.product-card__quick-add-button {
  padding: 0.25rem;
}

.product-card__quick-add-button::after {
  content: "";
  position: absolute;
  inset: 50% auto auto 50%;
  translate: -50% -50%;
  width: 44px;
  height: 44px;
}

/* ==========================================================================
 * R16-007 (Emily, 2026-08-03): "we don't need the white background on it. It
 * should just be text, potentially with an overlay on the images so that we can
 * read the white text over the images."
 *
 * The white came from `color-scheme--scheme-1` on the card content box, which
 * paints background AND text colour. collection-list.liquid drops that class
 * for a plain modifier; this supplies what is left: white words, and a real
 * scrim on the image so they stay readable on a pale photograph.
 *
 * The scrim is a gradient rather than a flat tint on purpose. The text sits at
 * one end of the tile, so darkening the whole image would dull the artwork for
 * no benefit. Same reasoning as the header-legibility overlay used on banners.
 *
 * Residual by the R5 test: the per-block overlay_color/overlay_opacity settings
 * govern the tile's OWN overlay and stay untouched. This is the text scrim,
 * which no Prestige control owns.
 * ========================================================================== */
.collection-card__content--over-image {
  color: rgb(255 255 255);
  background: none;
  position: relative;
  z-index: 1;
}

.collection-card__content--over-image :is(p, h2, h3, .h3, .h6) {
  color: inherit;
}

.collection-card .content-over-media:has(.collection-card__content--over-image)::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(to top, rgb(0 0 0 / 0.45) 0%, rgb(0 0 0 / 0.15) 45%, rgb(0 0 0 / 0) 75%);
}

/* ==========================================================================
 * R16-012 (Emily, 2026-08-03): the search filter bar spans the screen while its
 * contents stay exactly where they are.
 *
 * "make sure that padding was included to make sure that where the filter
 * button lands is still exactly where it is currently."
 *
 * .full-bleed pulls the element out to the viewport edges with negative margins
 * and adds no padding back, so on its own it would drag the Filter button to
 * the screen edge. Taking the container gutter back as padding puts the button
 * on the same x it sat on before, which is the measurable acceptance test.
 *
 * --container-gutter is the same variable the page container uses, so this
 * tracks the three side-spacing theme settings automatically instead of
 * freezing today's 20/32/48 into a second place.
 * ========================================================================== */
.collection-toolbar--gutter-inset {
  padding-inline: var(--container-gutter);
}

/* ==========================================================================
 * EMI-937 (R16, 3rd report): review stars must be sage everywhere.
 *
 * The theme's own stars already read --star-color (settings.product_rating_color,
 * stored #758981). The stars Emily kept seeing are the OKENDO widget's: its
 * script injects an inline :root block with --oke-stars-foregroundColor #AABAA3
 * (measured 2026-08-04 on /products/against-all-hope, resolved fill
 * rgb(170,186,163)). That palette lives in Okendo's dashboard, outside theme
 * settings, which is why three rounds of theme-side colour work changed
 * nothing she could see.
 *
 * This ties Okendo's variable to the theme's star setting, so one control
 * (Theme settings > Colors > Product > Review star colour) drives both star
 * systems. !important beats Okendo's later inline non-important declaration.
 * ========================================================================== */
:root {
  --oke-stars-foregroundColor: rgb(var(--star-color)) !important;
}

/* ==========================================================================
 * EMI-547 lineage / R4-074 (3rd report): sticky add-to-cart belongs at the
 * BOTTOM of the screen, like House of Spoils' quick add. Stock Prestige pins
 * it under the sticky header from 700px up (theme.css:6860-6869,
 * inset-block: var(--sticky-area-height) auto, border on the bottom edge);
 * below 700px it already sits at the bottom. This flips the desktop
 * placement to match: bottom edge, border on top, same show/hide behaviour.
 * ========================================================================== */
@media screen and (min-width: 700px) {
  .product-sticky-bar {
    inset-block: auto 0;
    border-block-end-width: 0;
    border-block-start-width: 1px;
  }
}

/* ==========================================================================
 * R17 (Emily): "the sticky functionality for the gallery on the left side
 * and the content on the right side is not working" on the prints template.
 *
 * Measured on /products/weightless-walk-print?view=prints @1280: the info
 * column IS position: sticky (stock product-info.liquid:12), but it is the
 * TALLER column (976px vs gallery 714px) and equals the parent height, so it
 * has zero travel — stock Prestige assumes a tall stacked gallery, while the
 * prints family uses a short carousel gallery. Making the gallery sticky as
 * well gives the standard two-column behaviour: whichever column is shorter
 * pins under the header while the other scrolls. Desktop only (the two
 * columns stack below 1000px), and align-self: start is required or the grid
 * stretches the gallery to the row height and the travel is zero again.
 * ========================================================================== */
@media screen and (min-width: 1000px) {
  .product > .product-gallery {
    position: sticky;
    /* F-9, Emily 2026-08-16: "the gallery for the auctions never sits touching
       the bottom of the header, theres always space above it, even when we
       scroll down." The cause is this line, not the theme's. R17 made the
       gallery sticky and copied the theme's own +1.25rem breathing room from
       `.product-info`. Only the SHORTER column can stick, so which column shows
       the offset flips per template: measured at 1440, the auction panel makes
       info 1308px against the gallery's 810px, so on auctions the GALLERY pins
       — at 81px, i.e. 20px below the 61px header, from scroll 0 onward. On the
       originals and print PDPs info is the shorter column (550 vs 810) and the
       gallery sits at its natural 61px, which is why this only ever read as an
       auction problem. Dropped to a flush pin. This is R17's rule and applies
       wherever the gallery pins, prints included — deliberate, so the behaviour
       is consistent rather than template-dependent. */
    inset-block-start: var(--sticky-area-height, 0px);
    align-self: start;
  }
}

/* ==========================================================================
 * R18 (Emily): hero videos on the series templates anchor to the TOP of
 * their frame instead of the default centre crop, so the composition's
 * opening frame reads the way the old site framed it. Scoped to the series
 * hero's video wrapper class only; image heroes keep their crop setting.
 * ========================================================================== */
.vrp-autoplay-video video {
  object-position: top;
}

/* ==========================================================================
 * D2 AUCTION — BLOCK 1 of 2: auction panel, pill, countdown, sticky bar,
 * provenance strip.
 * Step 3e (Agent E), feature/d2-auction-pages.
 *
 * Design contract: design-spec.md §2.2 (info-column gap), §2.4 (countdown
 * scales), §2.5 (panel chrome), §2.6 (sticky bar), §2.7 (skeleton), §3.0/§3.11
 * (state matrix — every visual difference keys off [data-auction-state]; no
 * state-specific markup is added or removed by JS), §4.3 (provenance strip),
 * §5.4 (thumb reach), §6.1 (layout shift), §6.2 (reduced motion),
 * §6.4 (contrast pairs).
 *
 * Family donor: .product-state (this file, ~:413) for panel chrome, and
 * .product-state__after-link (~:578) for the secondary-link treatment.
 * Countdown reference: theme.css:4740-4747 (.countdown__timer, uppercase at
 * :4741) and :4753-4758 (.countdown__timer-flip, 2px column-gap at :4756);
 * ::part(digit) is already a theme-consumed surface at :4760.
 *
 * Class contract consumed by 3c (markup) and 4a (elements) — fixed names.
 * (Step 5 reconciliation: this block now names 3c's DELIVERED contract, per
 * the six drifts in handoff/step-04a-agent-f.md §6 / step-05-integrate-fixes.md.)
 *   .auction-panel[data-auction-state][data-auction-end][data-tipo-ready]
 *     __status > __pill > __pill-text[data-pill="pending|scheduled|live|closing|sold|closed"],
 *     __figure > __figure-label[data-bid-label="current|final|sold-for"] + __figure-value,
 *     __countdown[data-countdown-scale] > __countdown-label
 *     (> [data-countdown-label="live|scheduled|opening|final"]) + .countdown__timer
 *     (donor DOM, flips carrying the donor class .countdown__timer-flip —
 *     always scope under .auction-panel__countdown so nothing leaks to the
 *     theme's own countdown sections) + __countdown-closing-word[data-countdown-word]
 *     + __countdown-fallback, __tipo (> .tpa_product_form), __note
 *     (> [data-note="extend|ended-unsold|ended-buy-now|ended-unsold-contact|how-link"]),
 *     __fallback, __skeleton > __skeleton-bar. The after-link is the <a>
 *     ITSELF (class .auction-panel__after-link, alongside the family class
 *     .product-state__after-link), a following SIBLING of the panel — as is
 *     <auction-sticky-bar class="auction-sticky-bar" data-auction-state
 *     (mirrored from the panel by 4a's element)>
 *     __info > __label[data-sticky-label="scheduled|live|ended"] + __figure +
 *     __countdown, __cta > [data-sticky-cta="remind|bid|view-print"]
 *
 * Colours: stored brand values only. #ae5840 = product_custom_badge_background
 * (the one accent this design adds); #eff1ef = sage 12% over white; #f9f2f0 =
 * terracotta 8% over white; #f7f4ef = scheme-2 background; skeleton/pill grey
 * = rgb(var(--text-color) / 0.06). Every pair recomputed against §6.4.
 * No radius anywhere: stored button/input radius is 0. Square corners.
 * ========================================================================== */

/* --- Panel chrome (§2.5; donor .product-state) --------------------------- */
.auction-panel {
  border: 1px solid rgb(var(--border-color));
  padding: var(--spacing-4);
  display: grid;
  gap: var(--spacing-3);
  /* §2.6 overlap guard: sticky bar height 100px + 16px so the bar never
     covers the control the visitor was sent to.
     Step 5 drift 6 — --auction-tipo-height (written to the panel by 4a's
     ResizeObserver, §2.7) is DELIBERATELY not consumed here: the correct
     guard value is a function of the BAR's fixed height (100px + 16), not
     of the widget's, and the bar hides itself whenever the panel intersects
     the viewport, so the bar can never overlap a panel it shares the screen
     with. No spec'd formula maps the measured widget height onto any CSS
     property (§2.7's B5 measurement feeds the DIFFERENT property
     --auction-tipo-min-height). Recorded in step-05-integrate-fixes.md;
     revisit at Step 7/8 if the rendered widget shows an overlap this
     constant misses. */
  scroll-margin-block-end: 116px;
}

@media screen and (min-width: 700px) {
  .auction-panel {
    padding: var(--spacing-5);
  }
}

/* §2.2: the pill, bid, countdown and action read as one object rather than
   four stacked blocks */
.product-info__block-item[data-block-type="auction-panel"] {
  --product-info-gap: var(--spacing-2);
}

/* --- Headline (2026-08-16 feedback) -------------------------------------- */
/* The status pill and the bid figure moved out of the panel and up under the
   product title, into their own `auction_headline` block. The wrapper carries
   [data-auction-state] so the whole pill/figure rule set keeps matching (each
   of those rules is widened to :is(.auction-panel, .auction-headline) below);
   it carries NO chrome — no border, no background, no padding — which is the
   entire point of the change. The panel chrome rules (:1302 border/padding,
   the `closing` border-colour, the `^="ended"` background) stay keyed to
   .auction-panel alone and must never be widened.

   It is NOT given the .auction-panel class: theme.js instantiates one
   AuctionPanelController per `.auction-panel` match. */
.auction-headline {
  display: grid;
  gap: var(--spacing-3);
}

.product-info__block-item[data-block-type="auction-headline"] {
  --product-info-gap: var(--spacing-2);
}

/* --- Status pill (§3.0, per-state tokens §3.11) -------------------------- */
.auction-panel__pill {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-1);
  width: fit-content;
  font-size: var(--text-xs);
  text-transform: uppercase;
  padding: var(--spacing-1) var(--spacing-3);
  min-height: 24px;
  /* Default pill: pending / scheduled / ended* / error. #303030 on the 6%
     tint over white (#f3f3f3 composite) = 12.42:1 (§3.2 states 12.4:1) */
  background: rgb(var(--text-color) / 0.06);
  color: rgb(var(--text-color));
}

/* live: sage 12% tint. #303030 on #eff1ef = 11.63:1 (§6.4) */
:is(.auction-panel, .auction-headline)[data-auction-state="live"] .auction-panel__pill {
  background: #eff1ef;
}

/* closing: terracotta 8% tint. #303030 on #f9f2f0 = 11.93:1 (§6.4).
   Never the theme's --warning-* tokens: 2.55:1 / 2.29:1, both fail AA (§6.0) */
:is(.auction-panel, .auction-headline)[data-auction-state="closing"] .auction-panel__pill {
  background: #f9f2f0;
}

/* Pill words (§3.11, Step 5 drift 4): 3c renders all six word-spans; exactly
   one shows per state. display: none, never visibility/opacity (§6.5).
   Mapping per the §3.11 matrix: pending+error → "Bidding" (§3.9: pill with no
   state word); ended + ended-unsold → "Bidding closed"; ended-sold +
   ended-buy-now → "Sold". live-reserve is QA-only (§3.4 withdrawn) and reads
   as live. */
.auction-panel__pill > [data-pill] {
  display: none;
}

:is(.auction-panel, .auction-headline)[data-auction-state="pending"] [data-pill="pending"],
:is(.auction-panel, .auction-headline)[data-auction-state="error"] [data-pill="pending"],
:is(.auction-panel, .auction-headline)[data-auction-state="scheduled"] [data-pill="scheduled"],
:is(.auction-panel, .auction-headline)[data-auction-state="live"] [data-pill="live"],
:is(.auction-panel, .auction-headline)[data-auction-state="live-reserve"] [data-pill="live"],
:is(.auction-panel, .auction-headline)[data-auction-state="closing"] [data-pill="closing"],
:is(.auction-panel, .auction-headline)[data-auction-state="ended"] [data-pill="closed"],
:is(.auction-panel, .auction-headline)[data-auction-state="ended-unsold"] [data-pill="closed"],
:is(.auction-panel, .auction-headline)[data-auction-state="ended-sold"] [data-pill="sold"],
:is(.auction-panel, .auction-headline)[data-auction-state="ended-buy-now"] [data-pill="sold"] {
  display: inline;
}

/* --- Per-state panel treatment (§3.11) ----------------------------------- */
/* closing: border reinforcement, never the sole signal (the pill carries the
   word + timer icon). #ae5840 on white = 4.92:1, past the 3:1 non-text bar */
.auction-panel[data-auction-state="closing"] {
  border-color: #ae5840;
}

/* ended family: retire the panel on scheme-2's stored cream.
   #303030 on #f7f4ef = 12.03:1 (§6.4) */
.auction-panel[data-auction-state^="ended"] {
  background: #f7f4ef;
}

/* ended-sold / ended (ambiguous): every panel child is display:none in these
   two states — the pill and bid figure moved to the auction headline in the
   d2-feedback wave, the __note block carries no message for them (unsold and
   buy-now have theirs), and on a real ended lot the Tipo mount is empty only
   because the app-level customize_css hides Tipo's widget (design-spec §
   [LIVE], Mike-editable, H-7). What remained was a bordered, tinted, empty
   box. Collapse the chrome, NOT the element: the ended-family sticky bar's
   show trigger is an IntersectionObserver on this panel's rect
   (theme.js _latestPanelCondition, register row XB-04), and display:none
   would zero the rect and strand the bar off-screen forever. With children
   hidden the panel is 0px tall, so nothing paints, while its position keeps
   feeding the observer. If Mike ever re-enables Tipo's ended-state widget in
   customize_css, its content renders again inside an unchromed panel rather
   than vanishing. Measured 2026-08-17: both fixture states rendered a
   visible empty .auction-panel before this rule. */
.auction-panel[data-auction-state="ended-sold"],
.auction-panel[data-auction-state="ended"] {
  border: 0;
  background: transparent;
  padding: 0;
}

/* The pill and figure live in the auction headline; the panel's own __status
   row is an empty spacer in the ended family and the error state (its pill
   spans are all display:none there), and it is what made the remaining boxes
   read top-heavy. Hidden in exactly those states; pre-end states keep it. */
.auction-panel[data-auction-state^="ended"] .auction-panel__status,
.auction-panel[data-auction-state="error"] .auction-panel__status {
  display: none;
}

/* ended-unsold / ended-buy-now: these keep the panel for their message.
   Emily 2026-08-17: no beige tint, message centred. The exact-match
   selectors out-cascade the ^="ended" cream above at equal specificity. */
.auction-panel[data-auction-state="ended-unsold"],
.auction-panel[data-auction-state="ended-buy-now"] {
  background: transparent;
  text-align: center;
}

/* error: breathing room between the body text and the retry button
   (Emily 2026-08-17). */
.auction-panel[data-auction-state="error"] .auction-panel__fallback-retry {
  margin-top: var(--spacing-4);
}

/* Phantom grid tracks (measured 2026-08-17): the panel is display:grid with
   a 12px gap, and an EMPTY zero-height child still creates a track, so each
   empty structural wrapper added 12px above the visible content — unsold
   measured 33px above vs 21 below, error 45 vs 21. The countdown wrapper is
   over in the whole ended family. In the error state the note block never
   shows content, and the countdown wrapper earns its track only when it has
   something to show: a live countdown (end known, :1497) or the authored
   fallback date (:1524) — the :has() guard keeps both of those cases.
   __tipo is left alone in ended states: on a real lot it can carry Tipo's
   bid history (DS-21) when the app-level hide is lifted. */
.auction-panel[data-auction-state^="ended"] .auction-panel__countdown,
.auction-panel[data-auction-state="ended-unsold"] .auction-panel__figure,
.auction-panel[data-auction-state="error"] .auction-panel__note,
.auction-panel[data-auction-state="error"][data-auction-end=""] .auction-panel__countdown:not(:has(.auction-panel__countdown-fallback)) {
  display: none;
}

/* unsold: the one remaining zero-height participant is the Tipo mount, which
   a real lot may legitimately fill with bid history (DS-21), so it stays.
   Zeroing the row gap removes its phantom 12px instead; the message's own
   padding carries the spacing, and Tipo blocks bring their own margins when
   they render. */
.auction-panel[data-auction-state="ended-unsold"] {
  row-gap: 0;
}

/* --- Bid figure row (§3.3) ----------------------------------------------- */
/* Step 5 drift 2: CSS renamed to the markup's class. 3c emits
   .auction-panel__figure-label (BEM child of __figure); no markup carries
   .auction-panel__bid-label. */
.auction-panel__figure-label {
  font-size: var(--text-sm);
  color: rgb(var(--text-color));
}

/* --- Countdown (§2.4, §6.1) ---------------------------------------------- */
/* contain: layout so nothing the countdown does can move the page (§6.1) */
.auction-panel__countdown {
  contain: layout;
}

.auction-panel__countdown-label {
  font-size: var(--text-sm);
  color: rgb(var(--text-color));
}

/* Countdown label variants (§2.4/§3.x, Step 5 drift 4): 3c renders four
   spans; one shows per state. The module (4a) writes data-countdown-elapsed
   on the panel:
   - scheduled            → "Bidding opens in"
   - scheduled + elapsed  → "Opening now" hold (§3.2: never claim open on our
                            own authority)
   - live / live-reserve  → "Bidding closes in"
   - closing              → "Bidding closes in", unchanged (§3.5 table)
   - closing + elapsed    → "Final bids" (§3.5 step 4)
   - error with a bound end (§3.9 shape 1: head script ran, bundle failed):
     keep the clock, label "unchanged" — the closest stateless reading is the
     live label, recorded in step-05-integrate-fixes.md
   pending hides every __countdown child already (§3.1 rule below); ended*
   hides the whole wrapper. */
.auction-panel__countdown-label > [data-countdown-label] {
  display: none;
}

.auction-panel[data-auction-state="scheduled"]:not([data-countdown-elapsed="true"]) [data-countdown-label="scheduled"],
.auction-panel[data-auction-state="scheduled"][data-countdown-elapsed="true"] [data-countdown-label="opening"],
.auction-panel[data-auction-state="live"] [data-countdown-label="live"],
.auction-panel[data-auction-state="live-reserve"] [data-countdown-label="live"],
.auction-panel[data-auction-state="closing"]:not([data-countdown-elapsed="true"]) [data-countdown-label="live"],
.auction-panel[data-auction-state="closing"][data-countdown-elapsed="true"] [data-countdown-label="final"],
.auction-panel[data-auction-state="error"]:not([data-auction-end=""]) [data-countdown-label="live"] {
  display: inline;
}

/* Closing hold (§3.5 steps 4-6, Step 5 drift 4): at 00:00 with the auction
   still open the word replaces the digits — never 00:00:00, never a negative
   clock. Word at --text-h2 per step 4; colour stays default text (no new
   contrast pair). */
.auction-panel__countdown-closing-word {
  display: none;
}

.auction-panel[data-auction-state="closing"][data-countdown-elapsed="true"] .auction-panel__countdown .countdown__timer {
  display: none;
}

.auction-panel[data-auction-state="closing"][data-countdown-elapsed="true"] .auction-panel__countdown-closing-word {
  display: block;
  font-size: var(--text-h2);
}

/* §3.9 shape 2 static-date fallback: hidden everywhere except error with no
   bound end, where it is the only countdown content that may render. */
.auction-panel__countdown-fallback {
  display: none;
}

.auction-panel[data-auction-state="error"][data-auction-end=""] .auction-panel__countdown-fallback {
  display: block;
}

/* Digits: --text-h2 base, tabular figures (§6.1 cause 1).
   Step 5 drift 3: the flips carry the DONOR class .countdown__timer-flip
   (3c reuses the countdown.liquid:47-72 DOM verbatim; .auction-countdown__flip
   exists in no markup). Every flip rule is scoped under
   .auction-panel__countdown so nothing leaks to the theme's own countdown
   sections, which use the same donor class. */
.auction-panel__countdown .countdown__timer-flip {
  font-size: var(--text-h2);
  color: rgb(var(--text-color));
  font-variant-numeric: tabular-nums;
}

/* Per-digit fixed box (§6.1 cause 2). 0.62em is the starting value; the
   DS-B6 measurement at Step 7 (widest of 0-9 in ITC Berkeley Old Style at
   --text-h2) confirms or replaces it. Do not ship the countdown to Step 8
   without that check. */
.auction-panel__countdown .countdown__timer-flip::part(digit) {
  min-width: 0.62em;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* Unit labels: --text-xs uppercase at FULL-STRENGTH colour. Never
   .text-subdued — 4.42:1, fails AA (§6.0 finding 2, §6.4). The uppercase
   treatment matches the donor .countdown__timer (theme.css:4741); restated
   here so it holds regardless of which donor classes 3c carries over. */
.auction-panel__countdown .countdown__timer-unit {
  font-size: var(--text-xs);
  text-transform: uppercase;
  color: rgb(var(--text-color));
}

/* closing state: digits step up to --text-h1 in terracotta.
   #ae5840 on white = 4.92:1, passes AA — and at --text-h1 (22-32px) the
   3:1 large-text threshold applies anyway (§3.5, §6.4). Colour set on the
   host and restated through ::part(digit) so it wins over theme.css:4760
   whichever donor classes the flips carry. */
.auction-panel[data-auction-state="closing"] .auction-panel__countdown .countdown__timer-flip {
  font-size: var(--text-h1);
  color: #ae5840;
}

.auction-panel[data-auction-state="closing"] .auction-panel__countdown .countdown__timer-flip::part(digit) {
  color: #ae5840;
}

/* final-minute scale: larger digits regardless of state (§2.4) */
.auction-panel__countdown[data-countdown-scale="final"] .countdown__timer-flip {
  font-size: var(--text-h1);
}

/* Display scale (§2.4): all four groups render server-side; the wrapper's
   data-countdown-scale hides the ones not needed. Hiding rather than removing
   keeps the DOM stable. DOM order per the donor (sections/countdown.liquid:
   47-72): item(days) sep item(hours) sep item(minutes) sep item(seconds).
   D-9 / FIX-8 (T-7b): the days- and hours-scale reductions are merchant
   preference, so they only apply when the auction_panel block's
   show_countdown_minutes_seconds checkbox is off — the snippet emits it as
   data-countdown-units-full on this wrapper ("true" by default). The
   minutes and final scales stay unconditional: they are time-driven (near
   the close the larger units read 00 and the enlarged seconds are the
   point), not a preference. */
/* days (>48h), only when minutes/seconds are switched off: Dd Hh */
.auction-panel__countdown[data-countdown-scale="days"][data-countdown-units-full="false"] .countdown__timer-item:has([type="minutes"]),
.auction-panel__countdown[data-countdown-scale="days"][data-countdown-units-full="false"] .countdown__timer-item:has([type="seconds"]),
.auction-panel__countdown[data-countdown-scale="days"][data-countdown-units-full="false"] .countdown__timer-item-separator:has(+ .countdown__timer-item [type="minutes"]),
.auction-panel__countdown[data-countdown-scale="days"][data-countdown-units-full="false"] .countdown__timer-item-separator:has(+ .countdown__timer-item [type="seconds"]),
/* hours (1h-48h), only when minutes/seconds are switched off: Dd Hh Mm */
.auction-panel__countdown[data-countdown-scale="hours"][data-countdown-units-full="false"] .countdown__timer-item:has([type="seconds"]),
.auction-panel__countdown[data-countdown-scale="hours"][data-countdown-units-full="false"] .countdown__timer-item-separator:has(+ .countdown__timer-item [type="seconds"]),
/* minutes (<1h): Hh Mm Ss */
.auction-panel__countdown[data-countdown-scale="minutes"] .countdown__timer-item:has([type="days"]),
.auction-panel__countdown[data-countdown-scale="minutes"] .countdown__timer-item-separator:has(+ .countdown__timer-item [type="hours"]),
/* final (<60s): Ss only, larger */
.auction-panel__countdown[data-countdown-scale="final"] .countdown__timer-item:has([type="days"]),
.auction-panel__countdown[data-countdown-scale="final"] .countdown__timer-item:has([type="hours"]),
.auction-panel__countdown[data-countdown-scale="final"] .countdown__timer-item:has([type="minutes"]),
.auction-panel__countdown[data-countdown-scale="final"] .countdown__timer-item-separator {
  display: none;
}

/* --- Skeleton (§2.7) ----------------------------------------------------- */
/* Static grey bars. No shimmer: a pulsing skeleton on a $10,000 painting
   reads as a broken app (§2.7 JUDGEMENT). */
.auction-panel__skeleton {
  display: grid;
  gap: var(--spacing-3);
}

.auction-panel__skeleton-bar {
  block-size: 44px;
  inline-size: 100%;
  background: rgb(var(--text-color) / 0.06);
}

/* Skeletons leave once the mount is verified, and never render in the
   error state (§3.9: skeleton removed) */
.auction-panel[data-tipo-ready="true"] .auction-panel__skeleton,
.auction-panel[data-auction-state="error"] .auction-panel__skeleton {
  display: none;
}

/* pending is the only state that skeletons the countdown and figure rows
   (§3.1): skeleton bar shown, digits/labels hidden. Every other state shows
   the real content and drops the row skeleton. */
:is(.auction-panel, .auction-headline)[data-auction-state="pending"] :is(.auction-panel__countdown, .auction-panel__figure) > :not(.auction-panel__skeleton),
:is(.auction-panel, .auction-headline):not([data-auction-state="pending"]) :is(.auction-panel__countdown, .auction-panel__figure) .auction-panel__skeleton {
  display: none;
}

/* --- Tipo mount geometry (§2.7, §5.4) ------------------------------------ */
.auction-panel__tipo {
  /* 132px is a placeholder; replaced by the B5 measurement at Step 8
     (375px live-state widget height). VERIFY AT BUILD. */
  min-height: var(--auction-tipo-min-height, 132px);
  display: grid;
  container-type: inline-size;
  /* §5.4: after the sticky-bar CTA scrolls back, the mount lands below the
     sticky header area, in thumb reach. --sticky-area-height is theme-owned
     (css-variables.liquid:84) and composes with any header configuration. */
  scroll-margin-block-start: calc(var(--sticky-area-height, 0px) + var(--spacing-6));
}

.auction-panel[data-tipo-ready="true"] .auction-panel__tipo {
  min-height: 0;
}

/* --- Per-state show/hide (§3.0 buy-area matrix, §3.11) ------------------- */
/* All display: none, never opacity/visibility, so nothing hidden stays in
   the tab order or the accessibility tree (§6.5). */

/* Countdown: removed in every ended state — never 00:00:00 on a closed lot */
.auction-panel[data-auction-state^="ended"] .auction-panel__countdown {
  display: none;
}

/* error: keep the countdown only when it was bound from the head script
   (data-auction-end non-empty); hide it when the script never ran (§3.9).
   Step 5: hides the wrapper's CHILDREN rather than the wrapper — 3c places
   the §3.9 shape-2 static-date fallback INSIDE __countdown, and it must be
   able to render exactly here (rule above shows it). */
.auction-panel[data-auction-state="error"][data-auction-end=""] .auction-panel__countdown > :not(.auction-panel__countdown-fallback) {
  display: none;
}

/* Bid figure row: hidden where there is no bid to show — scheduled (no bid
   yet), ended generic (§3.6/§3.11: the state does not know whether the piece
   sold, so it publishes no number — added at Step 5 to match §3.11 exactly),
   ended-unsold (never publish a number under the reserve, §3.7),
   error (never an empty currency slot, §3.9) */
:is(.auction-panel, .auction-headline)[data-auction-state="scheduled"] .auction-panel__figure,
:is(.auction-panel, .auction-headline)[data-auction-state="ended"] .auction-panel__figure,
:is(.auction-panel, .auction-headline)[data-auction-state="ended-unsold"] .auction-panel__figure,
:is(.auction-panel, .auction-headline)[data-auction-state="error"] .auction-panel__figure {
  display: none;
}

/* Bid label variants (§3.0 buy-area matrix, Step 5 drift 4): 3c renders up
   to three label spans keyed data-bid-label; one shows per visible-figure
   state — live/closing "Current bid", ended-sold "Final bid", ended-buy-now
   "Sold for". Everywhere else the whole row is hidden above. */
.auction-panel__figure > [data-bid-label] {
  display: none;
}

:is(.auction-panel, .auction-headline)[data-auction-state="live"] [data-bid-label="current"],
:is(.auction-panel, .auction-headline)[data-auction-state="live-reserve"] [data-bid-label="current"],
:is(.auction-panel, .auction-headline)[data-auction-state="closing"] [data-bid-label="current"],
:is(.auction-panel, .auction-headline)[data-auction-state="ended-sold"] [data-bid-label="final"],
:is(.auction-panel, .auction-headline)[data-auction-state="ended-buy-now"] [data-bid-label="sold-for"] {
  display: inline;
}

/* Tipo mount: empty and hidden in the error state (§3.11) */
.auction-panel[data-auction-state="error"] .auction-panel__tipo {
  display: none;
}

/* Theme note: each variant renders always, shows only in its states.
   The extension note is additionally gated by the merchant setting at the
   markup layer (present only when auction_show_extend_note is on).
   Step 5 drift 1: keyed on the MARKUP's contract — attribute data-note,
   extension value "extend" (3c emits data-note="extend|ended-unsold|
   ended-buy-now|ended-unsold-contact|how-link"; no markup carries
   data-auction-note or the value "extension"). Two variants 3e had no rule
   for get their states here: ended-unsold-contact ("Get in touch…", §3.0
   secondary-link row / §3.7) and how-link ("How bidding works", §5.3 — a
   bidding aid, shown in the states where a bid can still happen). Anchors
   show as inline-block so the theme's box-width underline (.link gradient,
   see the :578 finding) stays text-width. */
.auction-panel__note > [data-note] {
  display: none;
}

.auction-panel[data-auction-state="live"] .auction-panel__note > [data-note="extend"],
.auction-panel[data-auction-state="live-reserve"] .auction-panel__note > [data-note="extend"],
.auction-panel[data-auction-state="closing"] .auction-panel__note > [data-note="extend"],
.auction-panel[data-auction-state="ended-unsold"] .auction-panel__note > [data-note="ended-unsold"],
.auction-panel[data-auction-state="ended-buy-now"] .auction-panel__note > [data-note="ended-buy-now"] {
  display: block;
}

.auction-panel[data-auction-state="ended-unsold"] .auction-panel__note > [data-note="ended-unsold-contact"],
.auction-panel[data-auction-state="scheduled"] .auction-panel__note > [data-note="how-link"],
.auction-panel[data-auction-state="live"] .auction-panel__note > [data-note="how-link"],
.auction-panel[data-auction-state="live-reserve"] .auction-panel__note > [data-note="how-link"],
.auction-panel[data-auction-state="closing"] .auction-panel__note > [data-note="how-link"] {
  display: inline-block;
}

/* Fallback: hidden unless the state is error (§2.5). Message at --text-base,
   full-strength colour, left-aligned; no red, no warning triangle — the
   theme's error tokens measure 3.65:1 and fail AA (§3.9, §6.0 finding 3). */
.auction-panel__fallback {
  display: none;
}

.auction-panel[data-auction-state="error"] .auction-panel__fallback {
  display: block;
  font-size: var(--text-base);
  color: rgb(var(--text-color));
  text-align: start;
}

/* Secondary link: ended family only. Step 5 drift 5: the markup emits the
   <a> ITSELF carrying .auction-panel__after-link (no container), as a
   following SIBLING of the panel — so the show rule uses the general-sibling
   combinator (3c handoff open item 3 names exactly this), and the old
   container-with-child-a rule is gone. The <a> also carries the family class
   .product-state__after-link (this file :578), which supplies the whole
   donor treatment — display:block, width:fit-content (text-width underline),
   margin-inline:auto, --text-sm, .75rem top — so only display is toggled
   here. */
.auction-panel__after-link {
  display: none;
}

.auction-panel[data-auction-state^="ended"] ~ .auction-panel__after-link {
  display: block;
}

/* --- Mobile sticky bid bar (§2.6) ---------------------------------------- */
/* <auction-sticky-bar>, sibling after the panel in DOM order. <1000px
   (E-12, Emily 2026-08-11): the safe-sticky buy panel only applies at
   >=1000px, so 700-999 (iPad portrait) is covered by the bar — the old
   700 cutoff left that band with neither surface. Height budget:
   12 + 24 + 8 + 44 + 12 = 100px. Hidden state uses visibility so the bar
   leaves the tab order — no invisible focus stop (§6.5). */
.auction-sticky-bar {
  position: fixed;
  inset-block-end: 0;
  inset-inline-start: 0;
  width: 100%;
  z-index: 3; /* matches .product-sticky-bar, theme.css:6842 */
  display: grid;
  gap: var(--spacing-2);
  padding-block-start: var(--spacing-3);
  padding-block-end: calc(var(--spacing-3) + env(safe-area-inset-bottom));
  padding-inline: var(--container-gutter);
  background: rgb(var(--background)); /* opaque: two rows sit over content */
  border-block-start: 1px solid rgb(var(--border-color));
  /* var(--shadow-sm) = 0 2px 8px rgb(0 0 0 / 0.05) (css-variables.liquid:157),
     y-offset inverted to cast upward */
  box-shadow: 0 -2px 8px rgb(0 0 0 / 0.05);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s, visibility 0.2s;
}

.auction-sticky-bar.is-visible {
  opacity: 1;
  visibility: visible;
}

@media screen and (min-width: 1000px) {
  .auction-sticky-bar {
    display: none;
  }
}

/* Row 1: label + figure + countdown, 24px content */
.auction-sticky-bar__info {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  min-height: 24px;
  font-size: var(--text-sm);
  color: rgb(var(--text-color));
}

.auction-sticky-bar__countdown {
  font-variant-numeric: tabular-nums;
}

/* closing: row-1 countdown switches to terracotta at --text-h4 (§3.5).
   #ae5840 on white = 4.92:1, passes AA */
.auction-sticky-bar[data-auction-state="closing"] .auction-sticky-bar__countdown {
  color: #ae5840;
  font-size: var(--text-h4);
}

/* Row 2: the CTA. 44px minimum touch target (§5.1) */
.auction-sticky-bar__cta {
  min-height: 44px;
}

/* --- Sticky bar contents per state (§2.6 table, Step 5 drift 4) ----------- */
/* 4a's element mirrors the panel's data-auction-state onto the bar. §2.6:
   the bar does not render in pending or error — belt-and-braces here on top
   of 4a's observer gating (which never sets .is-visible in those states),
   and a bar not yet mirrored (no attribute) is treated as pending. */
.auction-sticky-bar:not([data-auction-state]),
.auction-sticky-bar[data-auction-state="pending"],
.auction-sticky-bar[data-auction-state="error"] {
  display: none;
}

/* Row-1 labels: one per state — scheduled "Opens {date}", live family
   "Current bid", ended family "Bidding closed" */
.auction-sticky-bar [data-sticky-label] {
  display: none;
}

.auction-sticky-bar[data-auction-state="scheduled"] [data-sticky-label="scheduled"],
.auction-sticky-bar[data-auction-state="live"] [data-sticky-label="live"],
.auction-sticky-bar[data-auction-state="live-reserve"] [data-sticky-label="live"],
.auction-sticky-bar[data-auction-state="closing"] [data-sticky-label="live"],
.auction-sticky-bar[data-auction-state^="ended"] [data-sticky-label="ended"] {
  display: inline;
}

/* Row-1 figure + countdown: live/closing only (§2.6 rows). scheduled shows
   the date inside its label span; ended shows the label alone. */
.auction-sticky-bar[data-auction-state="scheduled"] :is(.auction-sticky-bar__figure, .auction-sticky-bar__countdown),
.auction-sticky-bar[data-auction-state^="ended"] :is(.auction-sticky-bar__figure, .auction-sticky-bar__countdown) {
  display: none;
}

/* Row-2 CTA per state: remind (scheduled), bid (live family), view-print
   (ended family; markup renders it only when custom.shop_the_print is set —
   the §2.6 "otherwise no bar" decision stays with 4a's element).
   Written as :not() hides so the shown control keeps the theme's own
   .button / .link display value rather than one guessed here. */
.auction-sticky-bar:not([data-auction-state="scheduled"]) [data-sticky-cta="remind"],
.auction-sticky-bar:not([data-auction-state="live"], [data-auction-state="live-reserve"], [data-auction-state="closing"]) [data-sticky-cta="bid"],
.auction-sticky-bar:not([data-auction-state^="ended"]) [data-sticky-cta="view-print"] {
  display: none;
}

/* --- Reduced motion (§6.2) ----------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .auction-sticky-bar {
    transition: none;
  }
}

/* --- Provenance strip (§4.3) --------------------------------------------- */
/* Rendered by snippets/auction-provenance.liquid as the `provenance` block,
   a SIBLING of the panel in the info column — not a child of it. It therefore
   sits on the section background (scheme-1 white today, panel_background
   measured rgba(0,0,0,0) in VD-01), not on the ended states' #f7f4ef, and
   takes no [data-auction-state] treatment of any kind.

   Shipped 51b0dee with none of this: the snippet's own doc comment promised
   the typography and the two-column grid "via the auction-panel CSS block"
   and nothing was ever written, so the strip rendered as a browser-default
   <dl> — the <h2> at the UA's 1.5em bold, terms at body size in sentence
   case. Invisible only because none of the six metafields exist yet and
   every row self-hides; the content task that creates them would have put
   that on a $12,500 page. Register defect A-1.

   Colour: full-strength rgb(var(--text-color)) on both terms and values,
   which is §4.3's own instruction and is what §6.0 finding 2 asks for —
   .text-subdued #787878 is 4.42:1 and is named there as a token to keep off
   provenance terms. #303030 on #ffffff = 13.20:1 (§6.4's own figure for this
   pair). No new colour is introduced here and no state tint reaches it. */
/* Heading, list and statement rows stack on the same row gap §4.3 gives the
   list, so the strip reads as one rhythm rather than three. */
.auction-provenance {
  display: grid;
  gap: var(--spacing-2);
}

/* §4.3: "Row gap var(--spacing-2)". Single column below 700px. */
.auction-provenance__list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--spacing-2);
}

/* §4.3: "Two-column grid at >=700px". The modifier is what makes the block's
   `columns_desktop` select (§7.5) do anything — shipped, the class was
   emitted and nothing consumed it. --columns-1 needs no rule: it is the base
   declaration above, unchanged at every width. */
@media screen and (min-width: 700px) {
  .auction-provenance--columns-2 .auction-provenance__list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    /* §4.3 states the row gap and is silent on the column gap. --spacing-5 is
       the panel's own desktop inset (`.auction-panel` at :1324 above), so the
       strip's columns are set off by the measure this design already chose
       rather than by a new one. */
    column-gap: var(--spacing-5);
  }
}

/* dt over dd. A term and its value are one unit, so they are closer to each
   other than rows are to each other — smallest step on the scale, not a
   chosen number. */
.auction-provenance__row {
  display: grid;
  gap: var(--spacing-0-5);
}

/* §4.3: terms at --text-xs uppercase, values at --text-sm, both full-strength
   text colour. Resolved from the stored type settings (14 mobile / 15
   desktop): xs 12px/13px, sm 13px/14px. */
.auction-provenance__term {
  font-size: var(--text-xs);
  text-transform: uppercase;
  color: rgb(var(--text-color));
}

.auction-provenance__value,
.auction-provenance__statement {
  font-size: var(--text-sm);
  color: rgb(var(--text-color));
}

/* ==========================================================================
 * D2 AUCTION — BLOCK 2 of 2: Tipo containment + button-disable rescope.
 * Step 3e (Agent E). design-spec.md §5.5 verbatim, §5.7 (JUDGEMENT 16
 * adopted), §6.5 focus restore, §5.1 touch targets.
 * ========================================================================== */

/* Tipo auction widget containment. Third-party surface: no Prestige setting owns
   any property here. Scoped to .auction-panel__tipo so nothing leaks to the
   collection-card widgets rendered by snippets/tipo-auction-collection.liquid. */

/* 1. Stop horizontal overflow from a fixed-width widget */
.auction-panel__tipo { overflow-x: hidden; }
.auction-panel__tipo * { max-width: 100%; box-sizing: border-box; }

/* 2. Inherit brand type rather than the app's stack */
.auction-panel__tipo { font-family: var(--text-font-family); color: rgb(var(--text-color)); }

/* 3. Neutralise the app's own outer padding; the panel owns the inset */
.auction-panel__tipo > * { margin-block: 0; }
.auction-panel__tipo .tpa-product-widget-wrapper { padding: 0; }

/* 4. iOS zoom guard, see §5.2 */
@media screen and (max-width: 699px) {
  .auction-panel__tipo :is(input, select) { font-size: 16px; }
}

/* 5. Hide Tipo's stock flip-clock. Ours replaces it (D1 names this rule).
   FIX-1: the earlier form, `.auction-panel__tipo .tpa-remaining` (0,2,0),
   LOST — Tipo ships `.tpa-product-widget__status .tpa-remaining
   { display: flex }` (tipo.auction.964.*.css), also 0,2,0, no importance
   flag on either side, and Tipo's sheet loads after this one, so the tie
   resolved on source order in Tipo's favour and both countdowns rendered.
   Raised to 0,3,0 by including Tipo's own parent class (verified in the
   rendered DOM: .tpa-product-widget__status > .tpa-status > .tpa-remaining),
   which outranks Tipo regardless of load order. Specificity only — no
   importance override added (V-15). */
.auction-panel__tipo .tpa-product-widget__status .tpa-remaining { display: none; }

/* Tipo ships a site-wide button disable while its bundle boots. It exists to stop
   a purchase before the app hides the buy controls. On Prestige it cannot do that
   job (Tipo's selectors do not match this theme's markup) and it costs every page
   ~0.8-2.4s of dead header, cart and menu buttons. Re-scoped to the one place the
   protection is meaningful. */
body:not(.tpa-loaded) button { pointer-events: auto; cursor: auto; opacity: 1; }
body:not(.tpa-loaded) .auction-panel__tipo button { pointer-events: none; cursor: not-allowed; opacity: 0.6; }

/* Focus restore (§6.5): never suppress focus rings inside the Tipo mount;
   if Tipo suppresses its own, this brings them back. #303030 outline on
   white = 13.20:1, far past the 3:1 a focus indicator needs. */
.auction-panel__tipo :is(a, button, input, select):focus-visible {
  outline: 2px solid rgb(var(--text-color));
  outline-offset: 2px;
}

/* Touch targets (§5.1): Tipo's stepper buttons are the likely under-sized
   controls. Scoped to the mount only. VERIFY AT BUILD against the staged
   auction — no Tipo widget has ever rendered on Prestige, so the real
   control sizes are unmeasured until Step 8. */
.auction-panel__tipo :is(button, input, select) {
  min-height: 44px;
}

/* Step 5 residual: §3.1 pending renders skeleton bars in the countdown and
   figure rows. 3c's markup carries skeleton nodes only inside __tipo, so the
   pending shapes are supplied here as pseudo-elements — same tint as the
   mount skeleton (text at 6%), static, no shimmer (§2.7). */
:is(.auction-panel, .auction-headline)[data-auction-state="pending"] .auction-panel__countdown::before,
:is(.auction-panel, .auction-headline)[data-auction-state="pending"] .auction-panel__figure::before {
  content: "";
  display: block;
  height: 1.5rem;
  max-width: 12rem;
  background: rgb(var(--text-color) / 0.06);
}

/* §2.6: in the ended states the bar's only CTA is "View the print"; when the
   product has no print (no shop_the_print metafield renders no link), the
   spec says "otherwise no bar". */
auction-sticky-bar[data-auction-state^="ended"]:not(:has([data-sticky-cta="view-print"])) {
  display: none;
}

/* V-18: text links in the panel are tap targets too. Padding grows the hit
   box to >=44px at 375; the negative block margin gives the space back so
   the visual rhythm is unchanged. */
.auction-panel .auction-panel__note-link,
.auction-panel .auction-panel__fallback a.link {
  /* no display here — the per-state rules own visibility; an unconditional
     display would override the state gating (caught by re-measurement) */

  /* RS-11. The error panel's fallback body contains a mailto: whose address
     is a single 26-character unbreakable token, so the paragraph's
     min-content width is the width of that address.

     WHY a block-level <p>'s min-content is load-bearing at all — in normal
     flow it would not be: `.auction-panel` is `display: grid` (:1300, :1303)
     and `.auction-panel__fallback` is a grid item with automatic minimum
     size, so the paragraph's min-content propagates out of the grid and
     pushes the page. Measured at 320px / 2x text: the <p> and __fallback
     both go 262 -> 356.28 while .auction-panel stays pinned at 296, and the
     excess escapes to body.scrollWidth 385.

     `anywhere` — not `break-word` — because only `anywhere` participates in
     intrinsic min-content sizing (CSS Text 3, normative). Measured, same
     element: `break-word` -> 178.14px, i.e. NO CHANGE AT ALL; `anywhere` ->
     50.55px. `break-word` would have looked like a fix and measured as
     nothing. It is also the theme's house choice (theme.css:665, :2651, :8664).

     ON THE ANCHOR, NOT THE PARAGRAPH — deliberate, do not "simplify" it up
     to the <p>. On the anchor, the paragraph's min-content floor becomes the
     longest ordinary word (50.55px) and the surrounding prose still breaks
     normally. Moving it to the <p> gives 13.56px and lets ordinary sentences
     break mid-word. Character-level breaking is confined to the address.

     Measured on Prestige #151570055337 (role `unpublished`), auction state
     forced to `error` via the QA layer, on `.auction-panel__fallback-body`
     (spelling avoids the census's raw QA-parameter grep - a prose mention
     here is inert CSS but read as an ungated layer file, the documented
     false-STOP case; see context/lessons/census-prose-mention-false-stop.md)
     - the anchor
     itself is an inline box, where `width: min-content` is inert:
         min-content   178.14px -> 50.55px

     WHAT IT FIXES. RS-02 is recorded `fail` / comparative verdict `WORSE`,
     delta 19, on exactly one cell of 189: the error state at 188 CSS px
     (375x667@200). Whole-page overflow at that cell: 19px -> 0px. RS-06
     carries a SCOPE NOTE pointing at the same cell. Both need re-running
     after this lands.

     At 200% text (root 32px) the before-state overflows far more than the
     "headroom" an earlier draft of this comment claimed:
         320px  +81px -> +4px
         375px  +26px -> +4px
     A 4px whole-page residual survives at 200% and is NOT attributed — a
     widest-element scan surfaced only the gallery snap track, which is an
     intentionally scrolled container. Filed as an observation, not a root
     cause, and not fixed here.

     NOT a conformance claim: 188px sits below the 320px floor every cited
     standard stops at, so the cell is advisory under the normative/advisory
     split at TEST-REGISTER.yaml:1632-1641 and RS-02 may re-score. Verified
     in Chromium only. */
  overflow-wrap: anywhere;
  padding-block: 13px;
  margin-block: -13px;
}

/* Idle presentation: the app is loaded but no auction exists for this
   product (template assigned before the auction was created). The skeletons
   stop implying progress; the pill alone remains. Cleared by the module the
   moment an auction context appears. */
:is(.auction-panel, .auction-headline)[data-auction-idle="true"] .auction-panel__countdown::before,
:is(.auction-panel, .auction-headline)[data-auction-idle="true"] .auction-panel__figure::before,
:is(.auction-panel, .auction-headline)[data-auction-idle="true"] .auction-panel__skeleton {
  display: none;
}
.auction-panel[data-auction-idle="true"] .auction-panel__tipo {
  min-height: 0;
}

/* ==========================================================================
 * D2 AUCTION POLISH — T-5 (FIX-4): stepper glyph centring + 44px tap targets.
 * Agent B. Every rule scoped under .auction-panel__tipo (P-11).
 * ========================================================================== */

/* theme.css:170 makes every svg display:block, which turns Tipo's
   text-align:center on .tpa-increment-button into a no-op for the 8x8 glyph
   (measured: left gap 0, right gap 24). Restore inline flow inside the mount
   only; theme.css's vertical-align:middle still applies to the svg. 0,2,1
   outranks the 0,0,1 element reset regardless of source order. */
.auction-panel__tipo .tpa-increment-button svg {
  display: inline-block;
  margin-inline: auto;
}

/* Tap-target width: Tipo fixes the steppers at width:32px (tipo.auction.964,
   ...tpa-control .tpa-increment-button, 0,6,0). min-width clamps over width
   in the box algorithm irrespective of specificity, so 0,2,0 is enough.
   Only the +/- steppers sit under 44px wide; the amount inputs and the CTA
   already exceed it, so nothing else in the bid rows moves. */
.auction-panel__tipo .tpa-increment-button {
  min-width: 44px;
}

/* The CTA renders 43.1px tall at 375: Tipo's `.tpa-product-widget .tpa-button`
   (0,2,0, later-loading sheet) sets min-height:42px and beats the mount-wide
   0,1,1 :is() rule above on source order. Raised to 0,3,0 inside the mount —
   specificity only, no importance flag (V-15). */
.auction-panel__tipo .tpa-product-widget .tpa-button {
  min-height: 44px;
}

/* ==========================================================================
 * D2 AUCTION POLISH — T-6 (FIX-5 + FIX-6): CTA contrast + column fill.
 * Agent B. Every rule scoped under .auction-panel__tipo (P-11).
 * ========================================================================== */

/* FIX-5 — Tipo's injected #tpa-custom-css paints the Place-bid CTA
   rgba(170,185,163,1) under white text: 2.06:1 at 15px/700, failing the
   4.5:1 that applies below the 18.66px-bold large-text threshold. Re-point
   the CTA at the theme's own primary-button pairing — rgb(var(--button-background))
   resolves to rgb(101,122,112) here, 4.59:1 with its white text (measured
   computationally, WCAG relative luminance). Consuming the token keeps the
   widget CTA following the Prestige button setting rather than freezing a
   hex. 0,3,0 outranks the injected 0,2,0 base rule in every load order. */
.auction-panel__tipo .tpa-button.tpa-button--primary {
  background-color: rgb(var(--button-background));
  color: rgb(var(--button-text-color));
}

/* Tipo's injected hover (.tpa-button.tpa-button--primary:hover, 0,3,0) would
   revert the CTA to rgba(142,156,137,1) = 2.89:1 with white. Prestige's own
   button hover is a background-sweep inversion wired to theme markup Tipo's
   button does not carry, so the convention both parties share — darken on
   hover — is applied instead, using the theme's deep green rgb(57,73,67)
   (the --circle-button-text-color token): 9.51:1 with white. 0,4,0 outranks
   the injected hover. */
.auction-panel__tipo .tpa-button.tpa-button--primary:hover {
  background-color: rgb(var(--circle-button-text-color));
  color: rgb(var(--button-text-color));
}

/* FIX-6 — Tipo caps its widget at 400px and centres it (tipo.auction.964:
   .tpa-product-widget { width:100%; max-width:400px; margin:0 auto }, 0,1,0).
   The mount-wide `* { max-width:100% }` at :1905 ties it (0,1,0) and loses on
   source order, so the cap needs its own 0,2,0 rule. Fill the column. */
.auction-panel__tipo .tpa-product-widget {
  max-width: 100%;
  margin-inline: 0;
}

/* ==========================================================================
 * D2 AUCTION FEEDBACK — 2026-08-16 (Emily, screenshots on the auction PDP).
 * Five items: F-1 duplicate "Bidding ends in", F-2 widget does not blend /
 * full width, F-3 broken +/- steppers, F-4 description above the bid box
 * (template block_order, not CSS), F-5 current bid on the pill's line.
 *
 * Evidence for every override below is a PRIMARY-SOURCE read, not inference:
 * Tipo's own rules quoted from the fetched
 * tipo.auction.{964,524,171}.*.css, and the rendered geometry from a
 * read-only Playwright DOM+computed-style dump of the LIVE lot
 * (/products/the-secret-place, 1280x1200, visibilityState "visible").
 * No click, no form input, no bid was dispatched (H-6).
 *
 * Specificity is the only instrument used; no importance flag is added (V-15).
 * Tipo's sheets load AFTER brand-residual.css, so every rule here states the
 * competing Tipo selector and its weight.
 * ========================================================================== */

/* --- F-5: current bid sits on the status pill's line ---------------------- */
/* MOVED 2026-08-16 (Emily: the pill + bid line sits below the product title,
   outside the box). The pair now lives in .auction-headline, so the two-column
   grid moved with it and the .auction-panel copies were DELETED — the panel no
   longer contains either child, so those rules were dead weight that would
   silently re-introduce a phantom second column.

   .auction-headline is display:grid with one child per row, so the pill and the
   bid figure would stack. Two columns instead, figure trailing. Excluded in
   `pending`, where the figure carries the §3.1 skeleton pseudo-bar (max-width
   12rem) rather than a value — a 12rem bar pinned to the trailing edge reads as
   breakage, not loading. */
/* F-5b, Emily 2026-08-16: "instead of them being justify between they just had
   a gap between them". The first version was a two-column grid with the figure
   `justify-self: end`, i.e. the pair pushed to opposite edges of a 40%-wide
   column — which reads as two unrelated things rather than one status line.
   A flow row with a single gap keeps them as one object: intrinsic column
   widths, so the figure sits directly after the pill however long the amount
   runs. Still excluded in `pending`, where the figure carries the §3.1 skeleton
   pseudo-bar (max-width 12rem) rather than a value. */
.auction-headline:not([data-auction-state="pending"]) {
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  justify-content: start;
  align-items: baseline;
  column-gap: var(--spacing-3);
}

/* --- F-1: the app's second "Bidding ends in" ------------------------------ */
/* FIX-1 (:1936) hides Tipo's flip clock .tpa-remaining, but the heading above
   it is a SEPARATE element — .tpa-product-widget__status > .tpa-status >
   .tpa-endIn, text "Bidding ends in" — which no rule has ever touched, so the
   duplicate label outlived the duplicate clock. Tipo:
   `.tpa-product-widget__status .tpa-endIn { font-size:27px; font-weight:600 }`
   (0,2,0); ours 0,3,0. Our own countdown carries the label (__countdown-label). */
.auction-panel__tipo .tpa-product-widget__status .tpa-endIn {
  display: none;
}

/* With the heading and the clock both gone the band holds only
   .tpa-heading-messenger — Tipo's outbid / bid-too-low / bidding-stopped
   message — so the band's own chrome is neutralised and it collapses to zero
   height while empty. Tipo: `.tpa-product-widget__status { text-align:center;
   padding:15px; border-bottom:1px solid #eaeaea; background:#32c2c2 }` (0,1,0,
   repainted sage by the app's injected #tpa-custom-css); ours 0,2,0. */
.auction-panel__tipo .tpa-product-widget__status {
  background: none;
  border-bottom: 0;
  padding: 0;
  text-align: start;
}

/* The three alert states. REVISED TWICE — read this before touching it.
   GATE 5 (MAJOR 2) computed Tipo's messenger #3d4246 against the backgrounds in
   tipo.auction.964 (#e62333 / #d67500 / #6d7175) and got 1.81 / 3.08 / 2.07:1,
   all failing AA. The review labelled the injected #tpa-custom-css block as
   something it could not read. Reading it changes the answer, so it was read.

   The app injects, with IMPORTANCE FLAGS on the text colours:
     .tpa-product-widget__status                      background rgba(170,185,163,1)
     .__outbid                                        background rgba(230,35,51,1)
     .__outbid       .tpa-text--heading-color         color #fff  !important
     .__yourBidToLow                                  background rgba(170,185,163,1)
     .__yourBidToLow .tpa-text--heading-color         color #000  !important
     .__stopBidding                                   background rgba(240,241,245,1)
     .__stopBidding  .tpa-text--heading-color         color #5a5353 !important
   So the app overrides its OWN stylesheet for two of the three backgrounds, and
   the review's #d67500 and #6d7175 are never what renders. Measured pairings as
   actually served: __outbid #fff on #e62333 = 4.52:1 (AA pass for normal text);
   __yourBidToLow #000 on sage = ~10:1; __stopBidding never renders at all — the
   app embed ships `.tpa-product-widget:has(.__stopBidding){display:none
   !important}`, confirmed in the page's inline style block.

   The first attempt at this fix repainted the band with a light brand tint. It
   made things WORSE and the measurement caught it: the app's `color:#fff
   !important` on __outbid cannot be outranked without an importance flag of our
   own (V-15 forbids one), so white text landed on a #f3f3f3 tint at **1.11:1**.
   Background and text are one pairing here and the app owns the text half.

   Conclusion: do not repaint these backgrounds. The app's pairings pass, ours
   cannot be made to without breaking V-15. Only the inset is restored, at
   0,3,0, so the alert is not cramped once the base band is neutralised. */
.auction-panel__tipo
  .tpa-product-widget__status:is(.__outbid, .__yourBidToLow, .__stopBidding) {
  padding: var(--spacing-3);
}

/* --- F-2: the widget reads as part of the panel, not a card inside it ----- */
/* Tipo ships a raised white card: `.tpa-product-widget { background-color:#fff;
   box-shadow:0 2px 2px…,0 3px 1px -2px…,0 1px 5px 0…; color:#000 }` (0,1,0).
   Inside a panel that already has its own border and inset that reads as a
   second, foreign container. Width was already handled by FIX-6 above. */
.auction-panel__tipo .tpa-product-widget {
  background-color: transparent;
  box-shadow: none;
  color: inherit;
}

/* The app then divides itself into four bordered, 15px-inset sections
   (`__other-info`, `__action-group`, `__bid-list` each border-bottom:1px solid
   #eaeaea at 0,1,0; `.tpa-bid` / `.tpa-auto-bid` padding:15px at 0,2,0). The
   panel owns the inset, so the horizontal padding is doubling ours and the
   grey rules are drawing a table the design does not have. Drop them, and keep
   ONE separator — above the automatic-bid block, where it divides two distinct
   actions — recoloured to the theme border token. */
.auction-panel__tipo .tpa-product-widget__other-info,
.auction-panel__tipo .tpa-product-widget__action-group,
.auction-panel__tipo .tpa-product-widget__bid-list,
/* GATE 5 MAJOR 4: these two carry identical card chrome and were missed.
   __notification-list is the phone-capture row; .tpa-auction-button_buyout_price
   is the Buy-now row, i.e. the row that actually converts on a buy-now lot. */
.auction-panel__tipo .tpa-product-widget__notification-list {
  border-bottom: 0;
}

.auction-panel__tipo .tpa-product-widget__action-group .tpa-auction-button_buyout_price {
  border-top-color: rgb(var(--border-color));
  padding-inline: 0;
}

.auction-panel__tipo .tpa-product-widget__other-info,
.auction-panel__tipo .tpa-product-widget__notification-list,
.auction-panel__tipo .tpa-product-widget .tpa-bid,
.auction-panel__tipo .tpa-product-widget .tpa-auto-bid,
/* R2-5 (Emily, 2026-08-17): __bid-list was the one sibling this group never
   caught — it kept Tipo's 15px inset (measured: padding 15px all round at
   both 1440 and 375) so the bid rows sat indented against everything above.
   Same treatment as the rest of the group: the panel owns the horizontal
   inset, block padding stays Tipo's. */
.auction-panel__tipo .tpa-product-widget__bid-list {
  padding-inline: 0;
}

.auction-panel__tipo .tpa-product-widget .tpa-auto-bid {
  border-top: 1px solid rgb(var(--border-color));
}

/* Detail rows: `…__other-info .tpa-other-info .tpa-row { border-bottom:1px
   dashed #eaeaea }` (0,3,0) — dashed hairlines are not in this design's
   vocabulary. Solid, on the theme's own border token, at 0,4,0. */
.auction-panel__tipo .tpa-product-widget__other-info .tpa-other-info .tpa-row {
  border-bottom-style: solid;
  border-bottom-color: rgb(var(--border-color));
}

/* Body type: `.tpa-product-widget .tpa-text { color:#3d4246; font-size:14px }`
   (0,2,0). #3d4246 is the app's near-black, not the brand's.
   NARROWED after GATE 5 (MAJOR 3). The first version was a blanket
   `.tpa-text { color; font-size }` at 0,3,0 — which outranked EVERY one of
   Tipo's semantic modifiers on the same element, all 0,2,0 in tipo.auction.964:
   --error #991b1b, --white #fff, --primary, --warning, and the SIZE modifiers
   --heading 48px, --small 13px, --18px. Tipo composes them (its own
   increment-note markup is `div.tpa-text.tpa-text--small`), so the blanket
   silently repainted error text and squashed heading text to body size.
     1. font-size is dropped entirely — it bought ~nothing (Tipo's 14px is
        already body size) and it was the half that broke --heading/--small.
     2. colour now excludes the four colour modifiers by name, so Tipo keeps
        every semantic colour it sets deliberately and we repaint only the
        default near-black. Size modifiers are no longer touched at all. */
.auction-panel__tipo
  .tpa-product-widget
  .tpa-text:not(.tpa-text--error):not(.tpa-text--white):not(.tpa-text--primary):not(.tpa-text--warning) {
  color: rgb(var(--text-color));
}

/* Square corners. The stored button and input radius on this theme is 0 (see
   this file's D2 block header) and every radius below is the app's own:
   .tpa-button 5px (0,2,0), .tpa-bid_price_wrapper 3px (0,4,0),
   .tpa-bids-badge 5px (0,3,0). Each override clears its source by one class. */
.auction-panel__tipo .tpa-product-widget .tpa-button {
  border-radius: 0;
}

.auction-panel__tipo .tpa-product-widget__action-group .tpa-action .tpa-your_bid
  .tpa-bid_price_wrapper {
  border-radius: 0;
  border-color: rgb(var(--border-color));
}

/* Leading/winning badge: `[data-bid-list] .tpa-bids-item .tpa-bids-badge
   { background:#1b7b1b; border-radius:5px; color:#fff }` (0,3,0). #1b7b1b is a
   generic app green with no relation to the brand. Re-pointed at the theme's
   primary-button pairing — the same token pair FIX-5 measured at 4.59:1 for
   white text — at 0,4,0. */
.auction-panel__tipo [data-bid-list] .tpa-bids-item .tpa-bids-badge {
  background: rgb(var(--button-background));
  color: rgb(var(--button-text-color));
  border-radius: 0;
}

/* --- F-3: the +/- steppers ------------------------------------------------ */
/* Root cause, measured. Tipo stacks the two steppers vertically and sizes them
   to fit a 42px input: `…​.tpa-bid_price_wrapper .tpa-control { height:100%;
   display:flex; flex-direction:column }` (0,5,0) with
   `… .tpa-increment-button { width:32px; height:22px }` (0,6,0) — 22+22 = the
   44px row. Our mount-wide touch-target rule at :1958,
   `.auction-panel__tipo :is(button,input,select) { min-height:44px }`, then
   forces EACH stepper to 44px. min-height clamps over height in the box
   algorithm irrespective of specificity, so the column becomes 88px — and the
   row does NOT stay 46px and clip it: `.tpa-bid_price_wrapper` is
   `display:flex; align-items:center` with no height set, so it GROWS to fit.
   Measured (before-375.json): control h 88, wrapper h 90. The result is a 90px
   bordered box with the + and the - pushed to its far ends and the 42px input
   floating in the middle. (Corrected after GATE 5 MINOR 5 — the first version
   of this comment said "88px inside a 46px row", a geometry that cannot occur
   and that the team's own measurement file already contradicted.)
   That is the reported breakage,
   and it is present on `main` as well as on the polish branch (T-5 added
   min-width:44px, which widened it but did not touch the height).
   Fix: lay the pair out along the row instead of across it. Both steppers keep
   the full 44px min-height, so the 44x44 target §5.1 asks for is met without
   the row growing at all. */
.auction-panel__tipo .tpa-product-widget__action-group .tpa-action .tpa-your_bid
  .tpa-bid_price_wrapper .tpa-control {
  flex-direction: row;
  height: auto;
  align-items: stretch;
  border-left-color: rgb(var(--border-color));
}

/* The divider between them was drawn for the stacked layout
   (`… .tpa-increment-button:first-child { border-bottom:1px solid #eaeaea }`,
   0,7,0). Turn it through 90 degrees, on the theme token, at 0,8,0. */
.auction-panel__tipo .tpa-product-widget__action-group .tpa-action .tpa-your_bid
  .tpa-bid_price_wrapper .tpa-control .tpa-increment-button:first-child {
  border-bottom: 0;
  border-right: 1px solid rgb(var(--border-color));
}

/* F-3 follow-up, caught by measurement: laying the steppers along the row
   widens the control 45px -> 89px, and Tipo splits the action row 55/45
   (`…​.tpa-action .tpa-your_bid { width:55% }` 0,3,0 /
   `.tpa-auction-bid,.tpa-auction-automatic-bid { width:45%;
   border-left:10px solid transparent }` 0,2,0). At 375 that took the bid
   input from 85.4px to 58.5px — too narrow for a five-figure amount plus the
   currency prefix. Below the theme's 700px breakpoint the input row and the
   CTA each take the full width instead, which is the layout Tipo itself uses
   for its collection widget. Overrides at 0,4,0 and 0,3,0 respectively.

   F-3c — CONTAINER QUERY, not a viewport query. The first version keyed this to
   `@media (max-width: 699px)`, which measured well at 375 and 1280 and left a
   hole exactly where it mattered: theme.css takes the PDP two-column at
   `min-width: 1000px`, so the product-info column — and the bid input — is at
   its NARROWEST just above 1000px, which a 699px query never reaches.
   Measured across the cliff: input 386.8px at 999vw -> **55.0px at 1000vw**, a
   one-pixel drop; then 60.3 at 1024, 77.0 at 1100, 111.3 at 1280. At 55.0px
   border-box (43.0px content) `10500` measures 42.0px at the input's own
   `400 14px Muli` — it clears by 1.0px, which fits arithmetically and fails in
   use: no caret room, no comma grouping, six figures overflow. With the lot at
   CA$7,500 and a 500 increment, five figures is the normal case.
   The constraint was never the viewport, it was the WIDGET's width, so the
   query now asks the widget. `.auction-panel__tipo` already carries
   `container-type: inline-size` (§2.7 mount geometry), so this costs nothing
   new. 500px chosen against the measured widget widths: catches mobile (317px
   at 375vw) and the whole narrow-column band (311.6 at 1000vw, 321.2 at 1024,
   351.6 at 1100, 414 at 1280), while leaving the genuinely wide case alone
   (616px at 700vw, where the input already measures 222.4px). One rule now
   covers both cases the two-query version needed, and it cannot grow a new hole
   when a viewport breakpoint moves. */
/* R2-2 (Emily, 2026-08-17): "Place bid on the same row as its inputs on
   desktop; mobile keeps its own row." The 500px threshold above was catching
   the DESKTOP mounts too — measured 478px at 1440 and 414px at 1280 — so the
   CTA stacked everywhere and Tipo's own 55/45 same-row layout never applied.
   Threshold drops to 400px, chosen against the same measured mount series the
   500 was: the narrow-column band just above the 1000px two-column cliff
   (311.6px at 1000vw, 321.2 at 1024, 351.6 at 1100) stays stacked — at 55%
   of 351.6px the input would be ~78px, marginal for five figures — while 414
   and 478 go same-row with the input at ~108px/~146px, clearing the 42px
   "10500" measurement from F-3c with caret room. The F-3c reasoning is
   untouched: the container stays the instrument for WIDTH-driven stacking.
   The @media guard below exists because Emily's ask keys MOBILE to the
   viewport, not the widget: between ~450 and 699vw the single-column mount
   grows past any width threshold (616px measured at 700vw), so a container
   query alone would put the CTA back on the row on phones held sideways.
   Same declarations in both blocks; whichever matches, the row stacks. */
@container (max-width: 400px) {
  .auction-panel__tipo .tpa-product-widget__action-group .tpa-action .tpa-your_bid,
  /* GATE 5 MAJOR 4: the phone-capture row splits 55/45 the same way
     (`__notification-list .tpa-action .tpa-your_phone { width:55% }`, 0,3,0)
     and was left behind by the first version of this media query. 0,4,0. */
  .auction-panel__tipo .tpa-product-widget__notification-list .tpa-action .tpa-your_phone {
    width: 100%;
    border-right: 0;
  }

  .auction-panel__tipo .tpa-product-widget__action-group .tpa-auction-bid,
  .auction-panel__tipo .tpa-product-widget__action-group .tpa-auction-automatic-bid {
    width: 100%;
    border-left: 0;
  }
}

/* R2-2 mobile guard — see the block comment above the container query. */
@media screen and (max-width: 699px) {
  .auction-panel__tipo .tpa-product-widget__action-group .tpa-action .tpa-your_bid,
  .auction-panel__tipo .tpa-product-widget__notification-list .tpa-action .tpa-your_phone {
    width: 100%;
    border-right: 0;
  }

  .auction-panel__tipo .tpa-product-widget__action-group .tpa-auction-bid,
  .auction-panel__tipo .tpa-product-widget__action-group .tpa-auction-automatic-bid {
    width: 100%;
    border-left: 0;
  }
}

/* --- F-6: the duplicate "next minimum bid", found by measurement ---------- */
/* FIX-7 (T-7) added our own increment note on the premise that "Tipo only
   reveals the next minimum after a bid exists … never un-hidden (P-10)".
   Measured on Route C at 1280 with a live lot and one bid, both render:
     ours  [data-auction-increment-note]        "… CA$8,000.00"
     Tipo  .tpa-auction-amount-note             "Next minimum bid $8,000.00"
   Same defect shape as F-1. Ours is kept, because Tipo suppresses its own note
   entirely at zero bids (`.tpa-product-widget:has(.tpa-no-bids)
   .tpa-auction-amount-note { display:none !important }`) — exactly the case
   FIX-7 exists to cover — and ours carries brand type.
   Only Tipo's PRICE line is hidden, not its container: that container doubles
   as the validation slot ("Please insert a valid bid amount", the .is-close
   error span), and dropping the whole note would take the error message with
   it. The price line is the container's element child, the error is a span
   sibling, so the child-combinator + type selector separates them cleanly. */
.auction-panel__tipo .tpa-auction-amount-note > div.tpa-text {
  display: none;
}

/* --- F-7: "Bidding closes in" carries more weight -------------------------- */
/* Every weight token on this theme measures 400 (--text-font-weight,
   --heading-font-weight and --badge-font-weight all resolve to 400), so there
   is no bold token to consume — the emphasis has to be a literal weight.
   700 rather than 500/600 because the label renders in Muli, and Muli 400 and
   700 are the only two faces the theme actually loads (document.fonts on the
   rendered page: `Muli 400 loaded, Muli 700 loaded`; GTStandard-M's 500/600
   are `unloaded` and are a different family). A 500 or 600 here would be
   synthesised by the browser, which on a loaded-face family reads as a
   smeared 400 rather than as emphasis. */
/* Scope note (GATE 5 MINOR 8): this element wraps FOUR label variants (:1435)
   — "Bidding opens in", "Opening now", "Bidding closes in", "Final bids" — and
   the weight applies to all of them. Emily named only "Bidding closes in", but
   they are one UI slot in four states and exactly one is ever visible, so a
   per-variant weight would read as an inconsistency rather than as emphasis.
   Recorded as a deliberate widening, not an accident. Measured on the live
   state ("Bidding closes in"): computed font-weight 400 -> 700, via
   getComputedStyle on .auction-panel__countdown-label. The other three
   variants are unmeasured — they inherit the same declaration. */
.auction-panel__countdown-label {
  font-weight: 700;
}

/* --- F-8: "Place bid" matches the sitewide CTA ---------------------------- */
/* Measured, not assumed. The theme's own primary CTA (Add to cart, a print
   PDP) computes: Halant serif / 14px / weight 400 / letter-spacing 0.28px
   (0.02em) / text-transform none / padding 10.4px 28px / 1px solid border in
   the button colour / radius 0. Tipo's button computed: Muli / 15px /
   weight 700 (its .tpa-text--bold) / no letter-spacing / padding 10px 15px /
   border none. That difference — the type and the box, not the colour — is
   what read as foreign.
   The COLOUR is deliberately left as FIX-5 set it: rgb(var(--button-background))
   already resolves to 101 122 112, which a survey of sitewide CTAs (home
   "Full Story", print-PDP "Add to cart", collection-card "Place bid") shows is
   the dominant primary green, and FIX-5's pairing is the one measured at
   4.59:1. Re-deriving it here would put a measured contrast result at risk for
   no visual gain.
   Prestige paints its button with a two-layer gradient (theme.css:417
   --initial-gradient / --hover-gradient over background-size 101% 101%, 0 101%)
   purely to animate the hover sweep. That is NOT replicated: the sweep is wired
   to markup Tipo's button does not carry, and FIX-5 already supplies the
   convention both parties share — darken on hover. A solid fill is the honest
   equivalent, and it keeps the contrast reading FIX-5 measured.
   All tokens, so the button follows the theme's button settings rather than
   freezing values. 0,3,0 clears Tipo's 0,2,0 base rule and its 0,2,0
   .tpa-text--bold / .tpa-text--white, which sit on the same element. */
.auction-panel__tipo .tpa-product-widget .tpa-button {
  font: var(--button-font);
  letter-spacing: var(--button-letter-spacing);
  text-transform: var(--button-text-transform);
  padding: 0.65rem 1.75rem;
  border: 1px solid rgb(var(--button-outline-color, var(--button-background)));
}

/* ==========================================================================
 * D2 WIDGET FEEDBACK ROUND 2 — Emily, 2026-08-17. Items R2-1/-4/-6 here;
 * R2-2 lives at the F-3c container query, R2-5 in the padding-inline group
 * above. Every rule scoped under the panel (P-11).
 * ========================================================================== */

/* R2-1: space below "How bidding works". V-18 (:2016) grows the link's tap
   box with padding-block 13px and takes it back with margin-block -13px, so
   the link's text sat 21px off the panel's bottom edge at 1440 (17px at 375;
   method: link rect bottom minus computed padding-bottom, against the
   .auction-panel rect bottom). Handing back 12px of the compensation opens
   that to ~33px/29px without touching the 44px hit box V-18 measured. The
   how-link is the only note-link this applies to — the fallback/contact
   links sit mid-panel where V-18's zero-sum rhythm is still right. */
.auction-panel .auction-panel__note-link[data-note='how-link'] {
  margin-bottom: -1px;
}

/* R2-4: "Bid directly" / "Place an automatic bid" carry weight. Same
   reasoning as F-7 (:2509): every theme weight token resolves to 400 and
   Muli 400/700 are the only faces loaded, so emphasis is a literal 700 — a
   500/600 would be browser-synthesised smear. Reference point on record:
   Emily asked for "the How bidding works heading's weight", which measures
   400 (both the panel link and the accordion h6) — the ask's intent is BOLD,
   so this matches the widget's own bold (the F-7 countdown label, 700), not
   the literal 400 reading. Labels are Tipo's
   label.tpa-live_bid_label/.tpa-automatic_bid_label (.tpa-text, 0,2,0);
   ours 0,4,0. */
.auction-panel__tipo .tpa-product-widget :is(.tpa-live_bid_label, .tpa-automatic_bid_label) {
  font-weight: 700;
}

/* R2-6: the automatic-bid amount input has no horizontal padding — Tipo's
   `.tpa-product-widget [data-amount-input] { padding: 0 6px }` names only
   the direct-bid input (measured: direct 6px inline, automatic 0px). One
   rule covers both attributes so the pair can never diverge again; for the
   direct input the declaration is value-identical to Tipo's. 0,3,0 over
   Tipo's 0,2,0. */
.auction-panel__tipo .tpa-product-widget :is([data-amount-input], [data-automatic-amount-input]) {
  padding: 0 6px;
}

/* ==========================================================================
 * D2 AUCTION COLLECTION CARDS — Emily, 2026-08-17 ("make the auction cards
 * presentable"). Five items, C-1..C-5, measured on /collections/all-originals
 * (three live Tipo auctions; first three cards). Every rule scoped under
 * .product-card + .tipo-auction-collection — the collection-card analogue of
 * the PDP's .auction-panel__tipo scope (P-11) — so nothing leaks to the PDP
 * widget or to any other Tipo surface. Tipo's collection widget classes are
 * tpa-collection-widget / tpa-collection-widget__* (NOT tpa-product-widget:
 * that is the PDP shape), plus the shared .tpa-remaining flip clock.
 * C-5 (title over bidding UI) is the Liquid move in product-card.liquid; the
 * mount-visibility rules that make that move regression-free live here.
 * ========================================================================== */

/* C-5 support — mount placement. The mount is now the last grid item inside
   .product-card__info (a grid, gap .75rem, justify-items:center,
   theme.css:2343-2352). Two consequences handled here:
   1. justify-items:center would shrink-wrap the widget; stretch it and give
      it the full column.
   2. The info block inherits text-align:center and the card's
      text-transform token (theme.css:2345-2346); the widget is third-party
      UI, not card copy, so both are reset to keep Tipo's own layout.
   C-2 rides on the same rule: the widget area sat flush to the card edges
   (widget rect x=0, width = card width, measured 1440 and 375). The one
   inset the card's own content declares is 1.25rem
   (.product-card__info padding-block-start, theme.css:2350) — that measure
   is applied as the inline inset so the bidding UI follows the card's
   existing rhythm rather than a new number. */
.product-card .product-card__info > .tipo-auction-collection {
  justify-self: stretch;
  width: 100%;
  box-sizing: border-box;
  padding-inline: 1.25rem;
  text-align: start;
  text-transform: none;
}

/* C-5 support — non-auction cards must not move. Tipo injects only on
   collection pages, and only for products carrying a live auction; everywhere
   else the mount is an empty div. Left visible, that empty div would be a
   zero-height grid item and its 0.75rem row gap would open below every
   normal card's price line. :has() keyed on Tipo's injected root keeps the
   mount display:none until the widget actually arrives (async-safe, no JS). */
.product-card .product-card__info > .tipo-auction-collection:not(:has(.tpa-collection-widget)) {
  display: none;
}

/* C-1: no "Sold out" badge on a card with a live auction. Auction originals
   are deliberately unavailable in Shopify, so snippets/product-badges.liquid
   renders sold-out-badge.badge--sold-out (settings.show_sold_out_badge +
   variant.available == false) next to a live countdown. Keyed on Tipo's
   injected widget so the badge returns by itself the moment the auction ends
   and the widget stops rendering. 0,3,0; the theme's badge rules are 0,1,0 /
   0,2,0 and none sets display with importance. The theme ships no
   "Unavailable" badge — sold-out is the only availability badge on cards. */
.product-card:has(.tpa-collection-widget) .badge--sold-out {
  display: none;
}

/* C-3: card countdown matches the PDP auction countdown's palette — ink
   digits/labels, sage chip tint — at the card's existing small size (the PDP
   reference: .auction-panel__countdown-label at :1492, ink unit labels
   uppercase at :1578; sage 12% tint #eff1ef with ink #303030 = 11.63:1,
   measured at :1384). Tipo paints the card clock from its injected
   #tpa-custom-css (runtime <style>, loads after every sheet, so ties lose):
     .tpa-remaining .tpa-remaining__flipClock .flip-clock .flip-clock-group,
       [... .flip-clock-list .flip-clock-list-item-inner > .top / > .bottom,
       .flip-clock-divider::before/::after]   background #3d4246 !important
       (0,4,0 .. 0,7,0)
     .tpa-remaining .tpa-remaining__flipClock .flip-clock .flip-clock-group
       .flip-clock-list                        color #fff !important (0,5,0)
     ...​ .flip-clock-label                     color #3d4246 (0,5,0)
   The two background/colour overrides below carry !important ONLY because
   Tipo's do — an unflagged declaration can never beat a flagged one — and
   clear Tipo's specificity by the +0,2,0 scope prefix, so they win in every
   load order. */

/* "Bidding ends in" heading: Tipo's .tpa-text--small/--grey (13px,
   rgb(50,51,52), no transform; 0,1,0 each). PDP treatment: ink, small caps
   (uppercase + the theme's heading letter-spacing, as .smallcaps
   theme.css:8628), --text-xs. 0,3,0, no importance needed. */
.product-card .tipo-auction-collection .tpa-remaining__text__label {
  color: rgb(var(--text-color));
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: var(--heading-letter-spacing);
}

/* Chips: sage tint in place of Tipo's near-black #3d4246. The stacked
   flip-animation layers (.active/.before) rely on opaque chip faces, so the
   chips stay opaque — tinted, not removed. Selector list mirrors Tipo's own
   (quoted above) with the card scope prefixed: 0,6,0-0,9,0 !important over
   Tipo's 0,4,0-0,7,0 !important. The divider dots become ink so the colon
   still reads against white. */
.product-card .tipo-auction-collection .tpa-remaining .tpa-remaining__flipClock .flip-clock .flip-clock-group,
.product-card .tipo-auction-collection .tpa-remaining .tpa-remaining__flipClock .flip-clock .flip-clock-group .flip-clock-list .flip-clock-list-item-inner > .top,
.product-card .tipo-auction-collection .tpa-remaining .tpa-remaining__flipClock .flip-clock .flip-clock-group .flip-clock-list .flip-clock-list-item-inner > .bottom {
  background: #eff1ef !important;
}

.product-card .tipo-auction-collection .tpa-remaining .tpa-remaining__flipClock .flip-clock .flip-clock-divider::before,
.product-card .tipo-auction-collection .tpa-remaining .tpa-remaining__flipClock .flip-clock .flip-clock-divider::after {
  background: rgb(var(--text-color)) !important;
}

/* Digits: ink over Tipo's white-on-dark. Tabular figures match the PDP
   digits (:1561). 0,7,0 !important over Tipo's 0,5,0 !important. */
.product-card .tipo-auction-collection .tpa-remaining .tpa-remaining__flipClock .flip-clock .flip-clock-group .flip-clock-list {
  color: rgb(var(--text-color)) !important;
  font-variant-numeric: tabular-nums;
}

/* Unit labels (d/h/m/s): ink, small caps — the PDP unit-label treatment
   (:1578) at Tipo's existing 11px. 0,7,0 over Tipo's unflagged 0,5,0. */
.product-card .tipo-auction-collection .tpa-remaining .tpa-remaining__flipClock .flip-clock .flip-clock-group .flip-clock-label {
  color: rgb(var(--text-color));
  text-transform: uppercase;
  letter-spacing: var(--heading-letter-spacing);
}

/* C-4: the up (+) stepper glyph sat on the left edge of its box (measured:
   left gap 1px, right gap 29px at 1440). Same root cause and same fix as the
   PDP's T-5/FIX-4 (:2150): theme.css:170 makes every svg display:block,
   which turns Tipo's text-align:center on .tpa-increment-button into a no-op
   for the 10x10 glyph. Restore inline flow inside the card mount only. 0,3,1
   over the 0,0,1 element reset. */
.product-card .tipo-auction-collection .tpa-increment-button svg {
  display: inline-block;
  margin-inline: auto;
}

/* ==========================================================================
 * F0923-08 (EMI-1185) — info links row + side drawer.
 * Rendered by snippets/product-info.liquid when the main-product section
 * setting `info_links_style` is `links_drawer` (default `accordion` renders the
 * stock <details> accordions and touches none of this). New classes; no
 * Prestige control owns them. Geometry is HoS as measured in
 * _research/B-layout-findings.md §F0923-08: desktop = one centred flex row,
 * 40px gap, 12/24 text, underline offset 4px, 28px block padding (80px row),
 * no borders; below 1000px = a column of 48px rows, text left, chevron right,
 * no borders. The drawer itself is Prestige's <x-drawer> (theme.css .drawer).
 * ========================================================================== */
.product-info-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  column-gap: 2.5rem;
  row-gap: .5rem;
  margin: 0;
  padding: 1.75rem 0;
  border: 0;
}

.product-info-links__item {
  display: flex; /* blockifies the button: no line box around it, so the item is exactly 24px */
  margin: 0;
  padding: 0;
  border: 0;
}

.product-info-links .product-info-links__link {
  display: inline-flex;
  align-items: center;
  column-gap: .5rem;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font-family: inherit;
  font-size: 12px;
  line-height: 24px;
  letter-spacing: .025rem;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  cursor: pointer;
}

.product-info-links__chevron {
  display: none;
  flex-shrink: 0;
}

@media screen and (max-width: 999px) {
  .product-info-links {
    flex-direction: column;
    align-items: stretch;
    row-gap: 0;
    padding: 1.5rem 0 0;
  }

  .product-info-links .product-info-links__link {
    width: 100%;
    justify-content: space-between;
    padding: .75rem 0; /* 12 + 24 + 12 = 48px rows */
    text-decoration: none;
  }

  .product-info-links__chevron {
    display: block;
  }
}

/* ==========================================================================
 * F0923-09 (EMI-1186) — star rating on the title row.
 * Rendered by snippets/product-info.liquid when the title block's
 * `show_rating` is on (main-product context only). Title left, badge right on
 * the same baseline; the badge never wraps and never shrinks, the title may
 * wrap to a second line (B-layout-findings §F0923-09 prototype). The badge's
 * own look and its link to #product-reviews (EMI-886) are the stock
 * .rating-badge / .rating-badge-link rules in theme.css.
 * ========================================================================== */
.product-title-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .75rem;
}

.product-title-row .product-title {
  min-width: 0;
}

.product-title-row .rating-badge-link,
.product-title-row > .rating-badge {
  flex-shrink: 0;
}

.product-title-row .rating-badge {
  flex-wrap: nowrap;
  white-space: nowrap;
}

.product-info--center .product-title-row {
  justify-content: center;
}

/* ==========================================================================
 * F0923-11a (EMI-1188 part a) — feature_with_icon block, `style: line`.
 * HoS's rows under Add to cart (C-trust-geo-findings §F0923-11(a),
 * HOS-REFERENCE-3 :188,190): no pill, 16px icon, 12px subdued text, 30px
 * pitch. The `badge` style (default) is the stock .feature-badge renderer and
 * is untouched. Adjacent line blocks are grouped by product-info.liquid into
 * .feature-line-list; 20px line + 10px gap = 30px pitch.
 * ========================================================================== */
.feature-line {
  display: flex;
  align-items: center;
  column-gap: .5rem;
  margin: 0;
  padding: 0;
  background: none;
  font-size: 12px;
  line-height: 20px;
  min-height: 20px;
  text-decoration: none;
}

.feature-line :is(svg, img) {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
}

.feature-line p {
  margin: 0;
  font-size: inherit;
  line-height: inherit;
}

.feature-line-list {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .625rem;
}

/* F0923-11b (GATE 5 B-1): country-targeted lines are emitted with the hidden attribute and
 * assets/feature-country.js unhides the ones for the visitor's country. A hidden item is
 * display: none (theme.css [hidden]), so it takes no space; a group whose items are all hidden
 * is removed here too, so it leaves no wrapper, margin or gap and the rows below do not move.
 * Holds with no JavaScript, when every country line stays hidden. */
.product-info__block-group:has(> .product-info__block-item[data-country-mode]):not(:has(> .product-info__block-item:not([hidden]))) {
  display: none;
}

/* ==========================================================================
 * F0924 — "Displays well with" (main-product block displays_well_with,
 * snippets/displays-well-with.liquid). HoS shows two prints side by side
 * under the selector. Two across at every width, in the info column. The
 * snippet outputs nothing when there are no picks, so the block item is
 * :empty and theme.css hides it (no gap); these rules only style a filled row.
 * ========================================================================== */
.product-info__block-item[data-block-type="displays-well-with"] {
  --product-info-gap: 2rem;
}

.displays-well-with__heading {
  margin-block-end: 1rem;
}

.displays-well-with__list {
  --product-list-items-per-row: 2;
}

@media screen and (max-width: 699px) {
  .displays-well-with__list.product-list {
    margin-inline: 0;
  }
}

/* ==========================================================================
 * F1006-10 — "Series feature" (main-product block series_feature,
 * snippets/series-feature.liquid). One image card linking an original to
 * its series, in the slot the print template gives "Displays well with".
 * The snippet outputs nothing when no series resolves, so the block item is
 * :empty and theme.css hides it (no gap); these rules only style a filled card.
 * The aspect box comes from the theme's aspect-* utilities on the media span.
 * ========================================================================== */
.product-info__block-item[data-block-type="series-feature"] {
  --product-info-gap: 2rem;
}

.series-feature__heading {
  margin-block-end: 1rem;
}

.series-feature__card {
  color: inherit;
  text-decoration: none;
}

.series-feature__media {
  display: block;
  overflow: hidden;
}

.series-feature__media .series-feature__image {
  width: 100%;
  height: 100%;
}

.series-feature__link {
  justify-self: start;
}

/* F1006-09: the series hero's "View originals" button lands on the originals
 * grid (#pieces, the Collection page section's Anchor ID). Same clearance as
 * #product-reviews in theme.css, so the grid is not hidden under the sticky
 * header on arrival. */
#pieces {
  scroll-margin-block-start: calc(var(--sticky-area-height, 0px) + 20px);
}
