/* ═══════════════════════════════════════════════════════════════════════
   SIGNAL — the Seamless design system.

   REWRITTEN 2026-08-16 against the markup the page ACTUALLY renders.

   The first version styled a hand-rolled configurator (.sig-choice, .sig-tier,
   .sig-cta, .sig-opt, .sig-gallery__*, .sig-specs, .sig-section) and was then
   paired with WooCommerce's NATIVE variations form, which emits none of those
   classes. All seven styled nothing. The result was a Signal shell wrapped
   around Woostify defaults — a third designed, two thirds not, which reads
   worse than undesigned.

   RULE ADOPTED: never write a selector without confirming it exists in the
   rendered DOM. Every class below was read off the live clone.

   Real markup being targeted:
     .sig                                      our wrapper
     .sig-gal.images                           ours; see product-image.php
     form.variations_form.cart[.sig-form]      WooCommerce + our template
       .variations.sig-variations              plain block, no longer a table
         .sig-axis[data-axis]                  ours; hidden until JS is live
           .sig-tiers > button.sig-tier        the ladder    (price moves)
           .sig-chips > button.sig-chip        everything else
         .sig-native > select                  Woo's control, still the model
       .single_variation_wrap
         .woocommerce-variation-price
         .woocommerce-variation-availability
       .woocommerce-variation-add-to-cart
         .quantity
         button.single_add_to_cart_button
     .woocommerce-tabs.wc-tabs-wrapper          description / additional info
     .related.products

   Shipped separately from style.css so SEAMLESS_VERSION cache-busts it.
   Mobile-first; 390px is the design width, desktop is a widening.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Base ───────────────────────────────────────────────────────────── */

.sig {
  background: var(--sig-paper);
  color: var(--sig-ink);
  font-family: var(--sig-body);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

.sig *, .sig *::before, .sig *::after { box-sizing: border-box; }

.sig img { max-width: 100%; height: auto; display: block; }

.sig h1, .sig h2, .sig h3 {
  font-family: var(--sig-display);
  font-weight: 400; line-height: 1.15; margin: 0;
  letter-spacing: -0.01em;
}

.sig a { color: inherit; }

/* ── Suppressed chrome ──────────────────────────────────────────────────
   The sale flash and the wishlist button, both stacked in the same ~40px
   corner of the gallery. Signal states stock in the head, so the chip is a
   duplicate. `.wlfmc-*` belongs to a wishlist plugin still to be removed;
   this rule comes out with it.                                           */

.sig .onsale,
.sig .wlfmc-add-to-wishlist { display: none !important; }

/* ── Layout ─────────────────────────────────────────────────────────────
   REWRITTEN FOR THE STANDALONE THEME.

   What stood here fought Woostify for the two-column grid. It styled
   `.product-page-container > .woostify-container`, because Woostify's gallery
   callback emitted unclosed divs and a wrapper of our own re-nested the buy
   column inside the gallery. It repainted Woostify's `#F3F3F3` slab back to
   transparent. It undid Woostify's percentage widths with `!important`. It
   hid `.product-summary`, a slot Woostify's JS MOVED our buy column into on
   load.

   Every one of those rules described somebody else's markup, and each was
   discovered by something breaking. The grid is now declared once, on our own
   wrapper, and not one of them is needed.                                 */

.sig__wrap {
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 20px;
}

/* THE FOLD IS ITS OWN BOX, AND THAT IS WHAT BOUNDS THE STICKY GALLERY.
 *
 * The grid used to be on `.sig__wrap`, with the long-form sections spanning
 * `1 / -1`. It measured perfectly at rest and broke the moment anyone
 * scrolled: the sticky gallery travelled past its row and came to rest on top
 * of "A closer look", and because both carry `mix-blend-mode: multiply` they
 * ghosted through one another instead of merely overlapping. Reported from a
 * desktop screenshot — no static measurement would ever have caught it.
 *
 * A sticky box is constrained by its containing block, so giving the two
 * columns their own container is the fix, and it deletes seven span rules. */
@media (min-width: 900px) {
  .sig__top {
    display: grid;
    /* The gallery takes the larger share: it is the only thing carrying
       product information until real photography exists. */
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
    gap: var(--sig-7);
    align-items: start;
  }

  .sig .sig-gal { position: sticky; top: 24px; }
}

/* WooCommerce's OWN layout stylesheet floats these at 48%:
 *
 *   .woocommerce div.product div.images  { float:left;  width:48% }
 *   .woocommerce div.product div.summary { float:right; width:48% }
 *
 * Woostify used to suppress that and we never saw it; standalone, it is ours
 * to answer. It left both columns at exactly half their grid cell — 287px and
 * 261px inside a 1180px page — which is the kind of break that looks like a
 * grid bug and is not one. Asked the browser which rule won rather than
 * guessing; the answer is above.
 *
 * `!important` because the selector is (0,3,2) and no sane class stack of ours
 * reaches it, and because woocommerce-layout.css is a FALLBACK for themes with
 * no layout of their own. We have one. Overriding it is the intended
 * relationship, not a fight — and dequeuing the file wholesale would take the
 * cart and checkout layout with it.
 *
 * `.images` cannot simply be dropped from the markup: add-to-cart-variation.js
 * finds the gallery with `$product.find('.images')`, and losing it loses the
 * colour swap. */
.sig .sig-gal,
.sig .sig__buy {
  width: 100% !important;
  max-width: none !important;
  float: none !important;
  min-width: 0;
}

/* ── Gallery ────────────────────────────────────────────────────────────
   Ours — see woocommerce/single-product/product-image.php. No Flickity, no
   photoswipe, no easyzoom, no tiny-slider: a main image, a strip of
   thumbnails, and a click handler.                                       */

.sig .sig-gal { min-width: 0; }

.sig .sig-gal__main {
  background: var(--sig-panel);
  border-radius: var(--sig-r-img);
  overflow: hidden;
}
.sig .sig-gal__img {
  display: block; width: 100%; height: auto;
  /* Supplier artwork sits on a white ground; multiply melts it into the warm
     panel instead of leaving a bright rectangle on cream. Remove once real
     photography lands. */
  mix-blend-mode: multiply;
}

/* A visible strip, not a carousel — homepage-plan.md: carousels hide
   inventory, and on a page whose job is proving the object is real, "there is
   more to see" is information. */
.sig .sig-gal__thumbs {
  display: flex; gap: var(--sig-2); margin: var(--sig-2) 0 0;
  padding: 0; list-style: none;
  overflow-x: auto; scrollbar-width: none;
  scroll-snap-type: x proximity;
}
.sig .sig-gal__thumbs::-webkit-scrollbar { display: none; }
.sig .sig-gal__thumbs li { flex: 0 0 auto; scroll-snap-align: start; }

/* `position: relative` IS LOAD-BEARING, and it is not for layout.
 *
 * Each thumbnail carries a `.screen-reader-text` label. That class is
 * `position: absolute`, and an absolutely positioned box is clipped by an
 * ancestor's `overflow` ONLY when that ancestor is its containing block —
 * i.e. only when the ancestor is positioned. The strip is a scroll container
 * but it is `position: static`, so every one of those labels escaped it,
 * landed at x = 434, 510, 586… and dragged the DOCUMENT 348px wide at 390.
 *
 * The page looked fine. It just scrolled sideways, and every visible element
 * measured correctly — the thumbnails were 68px inside a 350px strip with a
 * 350px parent. Positioning the button gives each label a containing block
 * inside the scroller, and the strip clips it like everything else. */
.sig .sig-gal__thumb {
  position: relative;
  display: block; width: 68px; padding: 0; margin: 0;
  border: 0; background: none; cursor: pointer;
  border-radius: 8px; overflow: hidden;
}
.sig .sig-gal__thumb img {
  display: block; width: 100%; height: auto;
  border-radius: 8px; background: var(--sig-panel);
  mix-blend-mode: multiply;
}
/* `outline` rather than `border`: a border adds 2px to every thumbnail and
   shifts the strip as the selection moves. An outline is painted outside the
   box model and cannot. */
.sig .sig-gal__thumb.is-current img {
  outline: 2px solid var(--sig-ink);
  outline-offset: -2px;
}
.sig .sig-gal__thumb:focus-visible {
  outline: 2px solid var(--sig-ink); outline-offset: 2px;
}

/* ── Inline arrows and the open control ─────────────────────────────────
   The stage is the containing block for both. It is NOT `overflow: hidden`
   — that lives on `.sig-gal__main` one level in, so a focus ring on these
   controls is not clipped by the picture's own rounded corner.            */
.sig .sig-gal__stage { position: relative; }

.sig .sig-gal__nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  z-index: 2;
  display: flex; align-items: center; justify-content: center;
  /* 44px: the target, not the glyph. The glyph is 20px. */
  width: 44px; height: 44px; padding: 0;
  color: var(--sig-ink);
  background: var(--sig-paper);
  border: 1px solid var(--sig-hair);
  border-radius: var(--sig-r-chip);
  cursor: pointer;
}
.sig .sig-gal__nav--prev { left: var(--sig-2); }
.sig .sig-gal__nav--next { right: var(--sig-2); }
.sig .sig-gal__nav:hover { background: var(--sig-panel); }
.sig .sig-gal__nav:focus-visible { outline: 2px solid var(--sig-ink); outline-offset: 2px; }

/* The whole picture opens the viewer; the chip is the affordance, not the
   target. A 32px corner glyph is the one part of an image that is hard to
   hit on a phone, and "tap the photo" is what a buyer tries first. */
.sig .sig-gal__open {
  position: absolute; inset: 0;
  display: flex; align-items: flex-end; justify-content: flex-end;
  padding: var(--sig-3);
  background: none; border: 0; cursor: zoom-in;
}
.sig .sig-gal__open-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 10px;
  font-family: var(--sig-body); font-size: 12px; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--sig-ink);
  background: var(--sig-paper);
  border: 1px solid var(--sig-hair);
  border-radius: var(--sig-r-chip);
}
.sig .sig-gal__open:hover .sig-gal__open-chip { background: var(--sig-panel); }
.sig .sig-gal__open:focus-visible { outline: 2px solid var(--sig-ink); outline-offset: -2px; }

/* ── The viewer ─────────────────────────────────────────────────────────
   NOT scoped under `.sig`: signal.js moves this node to <body> on init,
   because `.sig-gal` is `position: sticky` on desktop and `.sig-gal__main`
   clips its overflow — a fixed box inside either is a clipping bug waiting
   for the first window resize.

   It reads as a drawing sheet rather than a lightbox. The counter is not a
   floating pill; it is the VIEW field of a title block that also names the
   item — the same component the footer and the PDP fold already use. The
   only yellow is the position bar: a FILL, per design/README.md's rule that
   yellow fills shapes and ink writes words.                               */
.sig-view {
  position: fixed; inset: 0; z-index: 10000;
  display: flex; flex-direction: column;
  background: var(--sig-ink);
  opacity: 0;
  transition: opacity 160ms ease;
}
.sig-view.is-open { opacity: 1; }
.sig-view[hidden] { display: none; }

/* The page behind must not scroll under a modal. */
html.sig-view-open { overflow: hidden; }

.sig-view__stage {
  flex: 1 1 auto; min-height: 0;
  display: flex; align-items: center; justify-content: center;
  padding: 56px var(--sig-4) var(--sig-4);
  overflow: hidden;
}
.sig-view__img {
  max-width: 100%; max-height: 100%;
  width: auto; height: auto;
  object-fit: contain;
  cursor: zoom-in;
  /* Hand every gesture to the pointer handlers. Without this the browser
     claims the horizontal drag for its own back-navigation on touch and the
     swipe never arrives. */
  touch-action: none;
  transform-origin: center center;
  will-change: transform;
}
.sig-view__img.is-zoomed { cursor: grab; }
.sig-view__img.is-zoomed:active { cursor: grabbing; }

/* Controls on ink: reversed, and still drawn. Paper glyph, hairline box,
   the same 8px the rest of the system uses. */
.sig-view__x,
.sig-view__nav {
  position: absolute; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0;
  color: var(--sig-paper);
  background: rgba(250, 248, 242, 0.06);
  border: 1px solid rgba(250, 248, 242, 0.28);
  border-radius: var(--sig-r-chip);
  cursor: pointer;
}
.sig-view__x:hover,
.sig-view__nav:hover { background: rgba(250, 248, 242, 0.16); }
.sig-view__x:focus-visible,
.sig-view__nav:focus-visible { outline: 2px solid var(--sig-yellow); outline-offset: 2px; }

.sig-view__x { top: var(--sig-3); right: var(--sig-3); }
.sig-view__nav { top: 50%; transform: translateY(-50%); }
.sig-view__nav--prev { left: var(--sig-3); }
.sig-view__nav--next { right: var(--sig-3); }

/* The title block. */
.sig-view__block {
  flex: 0 0 auto;
  border-top: 1px solid rgba(250, 248, 242, 0.18);
  background: var(--sig-ink);
}
.sig-view__bar {
  height: 3px; width: 100%;
  background: rgba(250, 248, 242, 0.14);
}
/* `scaleX`, not `width`. Animating width lays the element out on every frame;
   a transform is composited. The bar is 3px tall and the difference is
   invisible here — but the detector is right in principle and this is the
   version that stays right if the bar ever grows. */
.sig-view__bar-fill {
  display: block; height: 100%; width: 100%;
  background: var(--sig-yellow);
  transform: scaleX(0); transform-origin: left center;
  transition: transform 160ms ease;
}
.sig-view__fields {
  display: flex; flex-wrap: wrap; gap: var(--sig-2) var(--sig-6);
  margin: 0; padding: var(--sig-3) var(--sig-4);
}
.sig-view__field { display: flex; align-items: baseline; gap: var(--sig-2); min-width: 0; }
.sig-view__field dt {
  font-family: var(--sig-body); font-size: 11px; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: rgba(250, 248, 242, 0.55);
}
.sig-view__field dd {
  margin: 0; min-width: 0;
  font-family: var(--sig-body); font-size: 14px; color: var(--sig-paper);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sig-view__n { font-variant-numeric: tabular-nums; }
.sig-view__field--hint dd { color: rgba(250, 248, 242, 0.55); font-size: 13px; }

/* One hint, matched to the input the reader actually has. */
.sig-view__hint-touch { display: none; }
@media (hover: none) {
  .sig-view__hint-pointer { display: none; }
  .sig-view__hint-touch   { display: inline; }
}

/* At 390px the item name and the hint together push the block to three rows
   and eat the picture. The name is already the <h1> on the page behind. */
@media (max-width: 600px) {
  .sig-view__field--item,
  .sig-view__field--hint { display: none; }
  .sig-view__stage { padding: 56px var(--sig-2) var(--sig-2); }
}

@media (prefers-reduced-motion: reduce) {
  .sig-view,
  .sig-view__bar-fill { transition: none; }
}

/* ── Head ───────────────────────────────────────────────────────────── */

/* ⚠️ RENAMED FROM `.sig-head` 2026-09-02 — that is the SITE HEADER's class.
   Unscoped, this rule put 24px of padding on the masthead of every product
   page (137px tall against 113px elsewhere) while chrome.css's masthead
   background, hairline and z-index landed on the product title in return.
   Two components, one class name, and the symptom appeared in neither
   file. See woocommerce/content-single-product.php. */
.sig-phead { padding-top: var(--sig-5); }

/* The category, where the breadcrumb's one useful part went. Set as the sheet
   class above the part name — same register as `.sig-assure__key`, because it
   is the same kind of thing: a label on the drawing rather than a sentence in
   it. See woocommerce/global/breadcrumb.php for what it replaced and why. */
.sig-eyebrow {
  margin: 0 0 var(--sig-2);
  font-family: var(--sig-body);
  font-size: 11.5px; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  line-height: 1.4;
}
/* `inline-block` with vertical padding, because a 11.5px line box is an 14px
   tap target and this is a real link. The padding is pulled back out of the
   flow with a negative top margin so the eyebrow does not push the title down
   — the tap area grows, the drawing does not move. Measured 14px → 24px. */
.sig-eyebrow a {
  display: inline-block;
  padding: 5px 0;
  margin-top: -5px;
  color: var(--sig-muted); text-decoration: none;
}
.sig-eyebrow a:hover { color: var(--sig-ink); }
.sig-eyebrow a:focus-visible { outline: 2px solid var(--sig-ink); outline-offset: 3px; }

.sig-phead__title { font-size: 27px; margin-bottom: var(--sig-2); }
@media (min-width: 900px) { .sig-phead__title { font-size: 34px; } }

.sig-phead__sub { color: var(--sig-muted); font-size: 15px; margin: 0 0 var(--sig-4); }

/* PRICE AND SAVING ON ONE LINE.

   Baseline-aligned, not centred: the saving is a footnote to the number, and
   aligning their baselines is what makes it read as one statement rather than
   two things that happen to be adjacent. It wraps at narrow widths, where two
   lines is the honest answer.

   `.sig-save` carries `margin-top: var(--sig-3)` from the days it sat BELOW
   the price. Inside this row that margin is what would knock the two off their
   shared baseline. */
.sig-money {
  display: flex; flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sig-2) var(--sig-4);
}
.sig-money .sig-save { margin-top: 0; }

/* Money never sits on a mono or tabular grid — a monospaced comma occupies a
   full digit cell and renders ৳1,500 as "৳1 , 500". Display face, always. */
.sig-price,
.sig .woocommerce-variation-price .price {
  font-family: var(--sig-display);
  font-size: 27px; line-height: 1.2;
  font-variant-numeric: normal; letter-spacing: 0;
}
.sig-price del, .sig .woocommerce-variation-price del {
  color: var(--sig-muted); font-size: .68em; margin-right: 8px; opacity: .8;
}
.sig-price ins, .sig .woocommerce-variation-price ins { text-decoration: none; }

.sig-stock { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 7px; margin: var(--sig-2) 0 0; }
.sig-stock--in  { color: var(--sig-ok); }
.sig-stock--out { color: var(--sig-warn); }
.sig-stock::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }

/* ── Variation axes ─────────────────────────────────────────────────────
   REPLACES the block that used to live here: `table.variations` de-tabling,
   `.woo-variation-raw-select` hiding behind :has(), `ul.variable-items-wrapper`,
   `li.variable-item`, `li.color-variable-item`. Every one of those selectors
   named markup that woo-variation-swatches emitted. The plugins are gone and
   the CSS that fought them went with them — 18 lines out for this block in,
   which is the test an absorption has to pass. Reproducing a plugin's markup
   so your old CSS keeps matching is how you inherit its bloat instead of
   deleting it.

   Woo's own <select>s are still the model — see the template — so `.variations`
   survives as a plain block wrapper rather than a table.                 */

.sig form.cart { margin-top: var(--sig-5); }
.sig .sig-variations > * + * { margin-top: var(--sig-5); }

/* The fallback IS the default. `.sig-axis` appears only once signal.js has
   marked the form live, so a page whose script never arrives shows stock
   WooCommerce selects instead of nothing at all. */
.sig .sig-axis { display: none; }
.sig .sig-form--live .sig-axis { display: block; }

.sig .sig-form--live .sig-native {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
.sig .sig-native + .sig-native { margin-top: var(--sig-4); }

/* No-JS select, styled to match rather than left raw. */
.sig .sig-native select {
  width: 100%; min-height: 44px; padding: 0 40px 0 16px;
  border: 1px solid var(--sig-rule); border-radius: var(--sig-r-chip);
  background-color: var(--sig-card); color: var(--sig-ink);
  font-family: var(--sig-body); font-size: 15px; font-weight: 600;
  appearance: none; cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, var(--sig-ink) 50%),
                    linear-gradient(135deg, var(--sig-ink) 50%, transparent 50%);
  background-position: calc(100% - 20px) 50%, calc(100% - 14px) 50%;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}
.sig .sig-native label {
  display: block; margin-bottom: var(--sig-2);
  font-size: 12px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--sig-muted);
}

/* ── The axis heading ───────────────────────────────────────────────── */

.sig .sig-axis__head {
  display: flex; align-items: baseline; gap: var(--sig-2);
  margin-bottom: var(--sig-3);
  font-size: 12px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--sig-muted);
}
/* The chosen value, echoed beside the label. On a chip row the difference
   between selected and unselected is one hairline and one underline; naming
   the choice in words removes any doubt about what is in the cart. Filled by
   signal.js for chip axes only — a tier row states its own name. */
.sig .sig-axis__pick { color: var(--sig-ink); text-transform: none; letter-spacing: 0; }
.sig .sig-axis__pick:not(:empty)::before { content: "· "; color: var(--sig-muted); }

/* ── Tier rows — the ladder ─────────────────────────────────────────────
   Ported from design/_pdp-v2.src.html, where the pattern was decided:
   NAME, WHAT IS IN IT, PRICE — the row shows what you GET rather than doing
   unit arithmetic at you. Out-of-stock tiers stay visible and struck rather
   than hidden, because an admitted limit is a trust signal in this market.

   Shown only where price actually moves along the axis, which is computed
   in seamless_variation_axes(). Simple products and colour-only products
   never render one.                                                     */

.sig .sig-tiers { display: grid; gap: var(--sig-2); }

.sig .sig-tier {
  display: grid; grid-template-columns: 1fr auto; gap: var(--sig-4);
  align-items: center; position: relative; width: 100%;
  padding: var(--sig-4); margin: 0;
  border: 1px solid var(--sig-rule);
  border-radius: var(--sig-r-row);
  background: var(--sig-card); color: var(--sig-ink);
  font-family: var(--sig-body); font-size: 16px; text-align: left;
  cursor: pointer;
  transition: border-color .12s, box-shadow .12s;
}
.sig .sig-tier:hover { border-color: var(--sig-muted); }

/* Treatment C — ink on paper with a yellow underline, so yellow stays a MARK
   and the page's one reserved yellow FILL (the CTA) keeps its meaning.
   design/README.md locks this [FOUNDER 2026-08-14]. impeccable's detector
   reads the 3px underline as `side-tab`, "the most recognizable tell of
   AI-generated UIs" — OVERRIDDEN on the brand's authority, not on taste:
   the same device is already on the chips and the active tab, so complying
   here would make this the one component that disagrees with the system. */
.sig .sig-tier[aria-checked="true"],
.sig .sig-chip[aria-checked="true"] {
  border-color: var(--sig-ink);
  box-shadow: inset 0 -3px 0 var(--sig-yellow);
}
.sig .sig-tier:focus-visible,
.sig .sig-chip:focus-visible { outline: 2px solid var(--sig-ink); outline-offset: 2px; }

.sig .sig-tier[aria-disabled="true"],
.sig .sig-chip[aria-disabled="true"] {
  opacity: .45; cursor: not-allowed; box-shadow: none;
}
.sig .sig-tier[aria-disabled="true"] .sig-tier__name,
.sig .sig-chip[aria-disabled="true"] { text-decoration: line-through; }

/* Sold out is NOT disabled. The combination is real and can be asked about;
   it just cannot be bought today. Struck and dimmed, but the button still
   takes a click, so nobody gets stranded on a choice they cannot undo. */
.sig .sig-tier[data-soldout="true"],
.sig .sig-chip[data-soldout="true"] { color: var(--sig-muted); }
.sig .sig-tier[data-soldout="true"] .sig-tier__name,
.sig .sig-chip[data-soldout="true"] { text-decoration: line-through; }

.sig .sig-tier__name { font-weight: 600; line-height: 1.25; }
.sig .sig-tier__what {
  display: block; margin-top: 3px;
  font-weight: 400; font-size: 14px; color: var(--sig-muted);
}
.sig .sig-tier__price {
  font-family: var(--sig-display); font-size: 19px;
  white-space: nowrap; text-align: right;
}
.sig .sig-tier__was {
  display: block; font-size: 13px; color: var(--sig-muted);
  text-decoration: line-through;
}

/* Room for the corner tab. An explicit modifier rather than :has(), because
   :has() fails open on older Android Chrome and the collision would survive
   precisely where it is least affordable. */
/* The tab sits on the NAME side, not the price side. pdp-v2 drew it top-right,
   where nothing else lived; against real data the 6-pack is discounted, so the
   struck ৳3,300 lands in exactly that corner and the two overlapped. Rendered
   proof beat the mockup. Left also reads better — the tab now labels the row
   it belongs to instead of floating above a number. */
.sig .sig-tier--badged { padding-top: calc(var(--sig-4) + 14px); }
.sig .sig-tier__badge {
  position: absolute; top: -1px; left: -1px;
  background: var(--sig-ink); color: var(--sig-paper);
  font-size: 11.5px; font-weight: 600; letter-spacing: .06em;
  padding: 4px 10px; border-radius: var(--sig-r-row) 0 var(--sig-r-row) 0;
}

/* ── Chips — everything that changes which one you get ─────────────────
   Colour, width, length. No colour dots: every colour axis in this catalogue
   is black/white, WooCommerce swaps the gallery image on selection so the
   picture carries the colour, and a dot would need a hex map to maintain —
   which is the data woo-variation-swatches had for two terms out of four. */

.sig .sig-chips { display: flex; flex-wrap: wrap; gap: var(--sig-2); }

.sig .sig-chip {
  display: grid; place-items: center;
  min-height: 44px;                /* 44px minimum touch target */
  padding: 0 18px; margin: 0;
  border: 1px solid var(--sig-rule);
  border-radius: var(--sig-r-chip);
  background: var(--sig-card); color: var(--sig-ink);
  font-family: var(--sig-body); font-size: 15px; font-weight: 600; line-height: 1.2;
  cursor: pointer;
  transition: border-color .12s, box-shadow .12s;
}
.sig .sig-chip:hover { border-color: var(--sig-muted); }
.sig .sig-chip .sig-tier__name { font-weight: 600; }

@media (prefers-reduced-motion: reduce) {
  .sig .sig-tier, .sig .sig-chip { transition: none; }
}


/* Woostify renders this as an outlined button with an icon, and inline it
   floated alongside the variation price. It is a way out, not an action —
   a quiet text link, on its own line, hidden until something is chosen. */
.sig .reset_variations {
  display: none; float: none;      /* Woostify floats it left, into the price */
  /* `padding: 0` made this a 20.6px-tall tap target. It is a quiet link by
     design and stays one — the padding buys the 24px floor without changing
     how it reads, and the negative margin keeps the gap above it as drawn. */
  width: fit-content; padding: 6px 0;
  margin-top: calc(var(--sig-3) - 6px);
  border: 0; background: none;
  font-size: 13px; font-weight: 400; color: var(--sig-muted);
  text-decoration: underline; text-underline-offset: 3px;
}
.sig .sig-form--live .reset_variations { display: block; }
.sig .reset_variations:hover { color: var(--sig-ink); }
/* Nothing to hide here any more. Woostify injected a
   <span class="woostify-svg-icon icon-reload"><svg> into this link and the
   rule that hid it came out with the parent theme. Kept as a note because the
   first attempt aimed a ::before at it and styled nothing — rule 7. */

/* ── Chosen-variation block ─────────────────────────────────────────── */

.sig .single_variation_wrap { margin-top: var(--sig-4); }
.sig .woocommerce-variation-availability { font-size: 14px; }
.sig .woocommerce-variation-availability .stock { color: var(--sig-ok); font-weight: 600; }
.sig .woocommerce-variation-availability .out-of-stock { color: var(--sig-warn); }
.sig .woocommerce-variation-description { color: var(--sig-muted); font-size: 15px; }

/* ── Quantity + CTA ─────────────────────────────────────────────────── */

.sig .woocommerce-variation-add-to-cart,
.sig form.cart:not(.variations_form) {
  display: flex; align-items: stretch; gap: var(--sig-2);
  margin-top: var(--sig-4); flex-wrap: wrap;
}

.sig .quantity { flex: 0 0 auto; display: flex; align-items: stretch; }
.sig .quantity input.qty {
  width: 68px; min-height: 56px; text-align: center;
  border: 1px solid var(--sig-rule); border-radius: var(--sig-r-chip);
  background: var(--sig-card); color: var(--sig-ink);
  font-family: var(--sig-body); font-size: 16px; font-weight: 600;
}

/* The one yellow FILL on the page.
 *
 * TWO SELECTORS, AND THE SECOND IS THE ONE THAT WORKS. WooCommerce paints its
 * primary button with
 *
 *   .woocommerce:where(body:not(...)) button.button.alt { background:#7f54b3 }
 *
 * and `:where()` contributes NOTHING to specificity, so that is (0,3,1) —
 * which beat our (0,2,0) and turned Add to cart purple the moment Woostify
 * stopped overriding it for us. `.button.alt` takes ours to (0,4,0), and class
 * count is compared before type count, so it wins outright rather than by
 * load order. The bare selector stays for buttons Woo renders without `.alt`.
 *
 * Asked the browser which rule won instead of guessing; that is the whole
 * reason this is four classes and not an !important. */
.sig .single_add_to_cart_button,
.sig .single_add_to_cart_button.button.alt {
  flex: 1 1 200px;
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 56px; padding: 0 var(--sig-5);
  border: 0; border-radius: var(--sig-r-chip);
  background: var(--sig-yellow); color: var(--sig-ink);
  font-family: var(--sig-body); font-size: 16px; font-weight: 600;
  letter-spacing: .01em; text-transform: none; cursor: pointer;
  transition: filter .12s;
}
.sig .single_add_to_cart_button:hover,
.sig .single_add_to_cart_button.button.alt:hover { background: var(--sig-yellow); filter: brightness(.95); }
.sig .single_add_to_cart_button.disabled,
.sig .single_add_to_cart_button:disabled,
.sig .single_add_to_cart_button.button.alt.disabled,
.sig .single_add_to_cart_button.button.alt:disabled {
  background: var(--sig-rule); color: var(--sig-muted); cursor: not-allowed;
}

/* ── Assurance — the fold's title block ─────────────────────────────────

   REPLACES `.sig-trust`, which was three <li>s of `<strong>label</strong> text`
   in a flex row, and `.sig .product_meta`, which styled the SKU and category
   WooCommerce printed under the button. Both selectors are gone from the DOM
   now, so both rules are gone from here — design/pdp-brief.md rule 7: never
   write a selector without confirming it exists in the rendered output, which
   cuts both ways.

   WHY IT IS RULED AND KEYED RATHER THAN LISTED
   The footer became a title block on 2026-09-02 for a reason that applies
   twice as hard here: a technical drawing's constant element is a ruled band
   of keyed field/value pairs, and that is exactly what these four facts are —
   two dates and two terms of sale. As a bullet list they read as marketing
   reassurance, which is the register the BD reference teardown found everybody
   else in and nobody believing. As a schedule they read as the sheet's own
   data.

   It also absorbs a plugin. WPC Estimated Delivery Date printed its dated
   range as a fourth loose line here; the "Arrives" group is that mechanism,
   ours, derived from the one delivery promise the business has actually made.

   ONE COLUMN WIDTH AT EVERY BREAKPOINT. The label column is fixed rather than
   `max-content` so the two groups' keys align with each other — separate
   grids with content-sized columns would stagger, and a title block whose
   fields do not line up is just a list with rules drawn on it. */

.sig-assure {
  margin: var(--sig-5) 0 0;
  border-top: 1px solid var(--sig-hair);
}

.sig-assure__group {
  padding: var(--sig-4) 0;
  border-bottom: 1px solid var(--sig-hair);
}

.sig-assure__key {
  margin: 0 0 var(--sig-3);
  font-family: var(--sig-body);
  font-size: 11.5px; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--sig-muted);
}

.sig-assure__rows { margin: 0; display: grid; gap: var(--sig-3); }

.sig-assure__row {
  display: grid;
  grid-template-columns: 118px minmax(0, 1fr);
  gap: var(--sig-3);
  align-items: baseline;
}

.sig-assure__row dt {
  color: var(--sig-muted);
  font-size: 14px;
}

.sig-assure__row dd {
  margin: 0;
  color: var(--sig-ink);
  font-size: 14px; line-height: 1.45;
}

/* The dates are the one value here that is a NUMBER being read rather than a
   sentence, so they take the display face, like every other number on this
   page that matters.

   ⚠️ NO `font-variant-numeric: tabular-nums`. It has been caught and removed
   four times in this codebase — a tabular comma occupies a full digit cell and
   renders "৳1,500" as "৳1 , 500". The column is aligned by the grid track
   above, which is what a grid track is for. */
.sig-assure__row dd [data-eta-zone] {
  font-family: var(--sig-display);
  font-size: 15.5px;
  font-variant-numeric: normal;
}

@media (max-width: 560px) {
  .sig-assure__row { grid-template-columns: 104px minmax(0, 1fr); gap: var(--sig-2); }
}

/* ── Description + specifications ───────────────────────────────────────
   REPLACES the tab CSS that used to live here. The tabs are gone by design —
   design/_pdp-v2.src.html: "a tab is a place a picture-first buyer never
   reaches", and "Additional information" duplicated the specs. Standing alone
   they also came back as Woo's grey pills, because
   `.woocommerce div.product .woocommerce-tabs ul.tabs li` is (0,4,2) against
   our (0,3,1) — but the answer was never to out-specify a component that
   should not be on the page.                                              */

/* Shells for both of these are `.sig-sheet` now — see the note there. */

/* Imported marketplace copy arrives centre-aligned with inline `text-align`
   on nearly every paragraph. Centred body copy is unreadable at this length,
   and the alignment is somebody else's pixel decision baked into our content,
   so it is overridden here rather than edited out of 40 products by hand. */
.sig .sig-desc .sig-prose,
.sig .sig-desc .sig-prose * { text-align: left !important; }
.sig .sig-desc .sig-prose img { border-radius: var(--sig-r-img); margin: var(--sig-3) 0; mix-blend-mode: multiply; }

/* 68ch, not the full track. This is the one genuinely long-form prose block
   on the page and it is the supplier's, not ours — at 960px of content track
   it would set the longest measure on the site. */
.sig .sig-desc .sig-prose { max-width: 68ch; }

/* ── Specifications ─────────────────────────────────────────────────────
   THE SUMMARY IS ALWAYS OPEN. THE SHEET IS NOT.

   The reasoning is in seamless_specs()'s own comment — briefly: Baymard's
   case against tabs is that readers look *below* rather than sideways, which
   collapsed sections satisfy and tabs do not; their spec-sheet study finds
   only 3% of sites lift the critical rows into a summary; and this table
   measured 796px on desktop, the largest single section on the page, against
   an 88-ad finding that feature stacks are what lose in this category.

   Presentation only. Not one row is dropped or rewritten — a spec that is
   wrong is a content problem, and hiding it under CSS would be this stylesheet
   covering for it.                                                        */

/* The three that decide, as a definition list rather than a table: three
   pairs are not a table, and marking them up as one makes a screen reader
   announce a grid to describe three facts. */
.sig .sig-specs__key {
  display: grid; gap: var(--sig-3);
  margin: 0 0 var(--sig-5); padding: 0;
  max-width: 760px;
}
@media (min-width: 620px) {
  .sig .sig-specs__key { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sig-4); }
}
.sig .sig-specs__key > div {
  padding: var(--sig-3) var(--sig-4);
  border: 1px solid var(--sig-rule); border-radius: var(--sig-r-row);
  background: var(--sig-card);
}
.sig .sig-specs__key dt {
  font-size: 11px; font-weight: 600; line-height: 1;
  letter-spacing: .1em; text-transform: uppercase; color: var(--sig-muted);
}
/* The display face, because these are numbers being read — the same rule the
   delivery dates and the price follow. And NO tabular-nums: it has been
   removed from this codebase four times, because a tabular comma takes a full
   digit cell and renders "৳1,500" as "৳1 , 500". */
.sig .sig-specs__key dd {
  margin: 6px 0 0;
  font-family: var(--sig-display); font-size: 17px; line-height: 1.25;
  color: var(--sig-ink);
}

/* The disclosure. `<summary>` is a button — it gets a button's target height
   and a real focus ring, not the browser's default triangle. */
.sig .sig-specs__more { max-width: 760px; }

.sig .sig-specs__more > summary {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sig-3);
  min-height: 48px; padding: 0 var(--sig-4);
  border: 1px solid var(--sig-rule); border-radius: var(--sig-r-row);
  background: var(--sig-card);
  font-size: 15px; font-weight: 600; color: var(--sig-ink);
  cursor: pointer;
  list-style: none;                 /* Firefox */
  transition: border-color .12s;
}
.sig .sig-specs__more > summary::-webkit-details-marker { display: none; }
.sig .sig-specs__more > summary:hover { border-color: var(--sig-ink); }
.sig .sig-specs__more > summary:focus-visible { outline: 2px solid var(--sig-ink); outline-offset: 2px; }

.sig .sig-specs__chev { display: flex; flex: none; }
.sig .sig-specs__chev svg {
  width: 18px; height: 18px;
  fill: none; stroke: var(--sig-muted); stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
  transition: transform .18s ease;
}
.sig .sig-specs__more[open] > summary { border-color: var(--sig-ink); border-radius: var(--sig-r-row) var(--sig-r-row) 0 0; }
.sig .sig-specs__more[open] .sig-specs__chev svg { transform: rotate(180deg); }

/* The panel is drawn as one continuous block with the summary above it, so
   opening it extends the control rather than dropping a second box under it. */
.sig .sig-specs__groups {
  padding: var(--sig-5) var(--sig-4) var(--sig-2);
  border: 1px solid var(--sig-ink); border-top: 0;
  border-radius: 0 0 var(--sig-r-row) var(--sig-r-row);
}
.sig .sig-specs__group + .sig-specs__group { margin-top: var(--sig-5); }
.sig .sig-specs__group h3 {
  font-family: var(--sig-body);
  font-size: 11px; font-weight: 600; line-height: 1;
  letter-spacing: .1em; text-transform: uppercase; color: var(--sig-muted);
  margin-bottom: var(--sig-2);
}

.sig .sig-specs table { width: 100%; border-collapse: collapse; font-size: 15px; }
.sig .sig-specs th,
.sig .sig-specs td {
  text-align: left; padding: 10px 0; border-bottom: 1px solid var(--sig-rule);
  vertical-align: top; background: none;
}
.sig .sig-specs tr:last-child th,
.sig .sig-specs tr:last-child td { border-bottom: 0; }
.sig .sig-specs th { font-weight: 400; color: var(--sig-muted); width: 44%; }
.sig .sig-specs td ul { margin: 0; padding-left: 1.1em; }
.sig .sig-specs td li + li { margin-top: 3px; }

@media (prefers-reduced-motion: reduce) {
  .sig .sig-specs__chev svg { transition: none; }
}

/* ── Related / upsells ──────────────────────────────────────────────── */

/* Inside `.sig-more`'s sheet body now (see seamless_more_close). The old
   `margin-top: var(--sig-8)` was this block spacing ITSELF away from the page
   because nothing else would — the sheet's own rule and padding do that job,
   and leaving both stacked a 64px gap on top of a 48px one. Only the second
   of the two blocks needs separating from the first. */
.sig .up-sells + .related { margin-top: var(--sig-7); }
.sig .related > h2, .sig .up-sells > h2 {
  font-size: 25px; margin-bottom: var(--sig-4); text-align: left;
}
.sig .related ul.products, .sig .up-sells ul.products {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sig-4); margin: 0; padding: 0; list-style: none;
}
@media (min-width: 760px) {
  .sig .related ul.products, .sig .up-sells ul.products { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.sig ul.products li.product { width: auto !important; margin: 0 !important; float: none !important; }
.sig ul.products li.product img { border-radius: var(--sig-r-img); background: var(--sig-panel); }
.sig ul.products li.product .woocommerce-loop-product__title {
  font-family: var(--sig-body); font-size: 14px; font-weight: 600; line-height: 1.35;
}
.sig ul.products li.product .price { font-family: var(--sig-display); font-size: 16px; }

/* ── Reduced motion ─────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .sig *, .sig *::before, .sig *::after {
    animation-duration: .01ms !important; transition-duration: .01ms !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   LONG-FORM SECTIONS.  Everything below the buy block.

   Ported from design/pdp-v2.html 2026-08-30. What came across is the
   MECHANISM — a section shell and the picture rail. What deliberately did
   not is the prototype's scale drawing: it was hand-drawn to one product's
   true millimetres, and a generalised scale figure is a guess wearing the
   costume of a measurement.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── The sheet — ONE section shell, and the two-track desktop grid ──────
   WHAT THIS REPLACES, AND WHY IT READ AS "OLD WEB".

   Five shells did this job: `.sig-sec`, `.sig-figure`, `.sig-angles`,
   `.sig-desc` and `.sig-specs`, each with its own margin/padding/border-top
   triple, in four different spacing pairs (7/6, 8/7, 8/7, 7/6). So the long
   form rendered as seven bands of *almost* the same rhythm — near-identical
   in kind, inconsistent in measure, which is the worst of both.

   Measured on the Fireplace at 1440 before this change: the buy block was
   13.4% of a 5,088px page and every one of the seven sections below it was a
   left-aligned text column capped between 38em and 640px. The right ~40% of
   a 1440px screen carried nothing from the drawing downward. That is the
   whole "taken from old web" reading: a phone layout stretched, with rules
   drawn across the gaps.

   THE FIX IS A LEFT TRACK THAT SAYS WHAT THE SECTION IS FOR.
   It borrows the drawing's own field-label vocabulary — `.sig-figure__label`
   was already uppercase, letterspaced and muted inside the SVG — so the page
   reads as one sheet with an index down its edge rather than a scroll of
   unrelated blocks. It is sticky, so the label stays beside its own content:
   that is what turns a long page into a document you are reading a part of.

   It is not decoration. The label names the section's JOB, and the sequence
   of those jobs IS the argument the page makes — which is why the order is
   documented in woocommerce/content-single-product.php rather than here.  */

.sig-sheet {
  margin: var(--sig-7) 0 0;
  padding: var(--sig-6) 0 0;
  border-top: 1px solid var(--sig-rule);
}

.sig-sheet__body { min-width: 0; }

.sig-sheet__label {
  margin: 0 0 var(--sig-4);
  font-family: var(--sig-body);
  font-size: 11px; font-weight: 600; line-height: 1;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--sig-muted);
}

/* 900px, matching `.sig__top`'s own split — the page should not change its
   mind about what "desktop" means halfway down. */
@media (min-width: 900px) {
  .sig-sheet {
    display: grid;
    grid-template-columns: 124px minmax(0, 1fr);
    gap: var(--sig-7);
    align-items: start;
  }
  .sig-sheet__label {
    position: sticky; top: 24px;
    margin: 0;
    /* Optically aligned to the cap-height of the 25px display h2 beside it,
       not to its line box — a 11px label and a 25px serif sharing a grid row
       sit level only if the small one is nudged. */
    padding-top: 7px;
  }
}

.sig-sheet__body > h2,
.sig-sheet__body > .sig-figure__title { font-size: 25px; margin-bottom: var(--sig-2); }

.sig-sheet__lede {
  color: var(--sig-muted); font-size: 15px;
  margin: 0 0 var(--sig-4); max-width: 42em;
}

/* ── The picture rail ───────────────────────────────────────────────────
   BOUNDED IN BOTH AXES, ASPECT NEVER TOUCHED.

   The prototype sized cards by fixed height and auto width. That was correct
   for the product it was drawn on and wrong for the catalogue. Measured on
   the clone 2026-08-30, all seven products:

     4-Port Charging Station   5 imgs   aspect 1.00
     Under-Desk Tray           6 imgs   aspect 2.44
     Sticky Cable Manager      7 imgs   aspect 2.44
     Fireplace                16 imgs   aspect 0.55–1.53
     Lark                      8 imgs   aspect 0.75–1.01
     Lint Roller               5 imgs   aspect 1.00
     Cable Zipper              8 imgs   aspect 1.00

   Square is the house shape, not tall. Fixed height on a 2.44 banner gives a
   card 1025px wide on a 360px rail — three screens across, which is not a
   picture anyone can read. So cap BOTH axes and let the browser keep the
   aspect ratio: tall images meet the height ceiling, wide ones meet the width
   ceiling, and nothing is ever cropped or squashed.

   Not cropping is not a preference. The marketing text is baked into these
   pixels, and a crop can cut a headline in half.

   Because each product's own banners share one aspect (five of the seven are
   uniform), the row reads as a clean filmstrip per product without any fixed
   dimension forcing it to.

   THE PEEK IS THE AFFORDANCE. 78vw leaves the next card partly visible — a
   card as wide as the viewport gives no signal that anything is beside it,
   which is how carousels get ignored.

   Built on scroll-snap rather than a slider library: native momentum
   scrolling, works with a thumb, a trackpad, arrow keys and a screen reader,
   and adds no library to a page where every visitor is paid for.         */

.sig-rail {
  display: grid; grid-auto-flow: column; grid-auto-columns: auto;
  /* Two of the seven products have mixed aspects, so card heights can differ
     within one rail. Centring puts them on a shared axis instead of hanging
     them off the top edge. */
  align-items: center;
  gap: var(--sig-2);
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-left: 20px;
  scrollbar-width: none;
  padding-bottom: var(--sig-2);
  /* Full-bleed against .sig__wrap's 20px gutter, so the rail runs to the
     screen edge and the peek is a real peek rather than a boxed-in one. */
  margin: 0 -20px; padding-left: 20px; padding-right: 20px;
}
.sig-rail::-webkit-scrollbar { display: none; }
.sig-rail:focus-visible { outline: 2px solid var(--sig-ink); outline-offset: 4px; }

.sig-rail img {
  /* `start`, not `center`. With centre alignment scrollLeft can never reach
     its maximum, so an end-of-rail test against that maximum never fires and
     "next" stays enabled at the end. Measured on mobile, where one card fills
     the viewport. */
  scroll-snap-align: start;
  /* Both dimensions auto so the intrinsic aspect survives; the two ceilings
     below decide which axis binds. `.sig img { max-width: 100% }` earlier in
     this file would otherwise pin every card to the rail's width, so this
     max-width is also the override that makes the rail possible at all. */
  width: auto; height: auto;
  max-width: min(78vw, 420px);
  max-height: 420px;
  display: block;
  border-radius: var(--sig-r-img);
  background: var(--sig-panel);
  /* Same arithmetic as the gallery: supplier artwork is shot on white, and
     multiply re-composites it onto the warm ground instead of leaving a
     bright rectangle on cream. */
  mix-blend-mode: multiply;
}
@media (min-width: 760px) {
  /* vw is meaningless once the rail stops being the full screen — it is 1110px
     inside a 1440px window. Fixed ceilings instead, sized so roughly two cards
     and a peek fit whatever their shape. */
  .sig-rail img { max-width: 520px; max-height: 500px; }
}

/* Desktop keeps the RAIL rather than becoming a grid. Sixteen tall portraits
   four-up is four rows of ~500px — the length problem coming back in a wider
   costume. What desktop lacks is the gesture, so it gets buttons instead. */
@media (min-width: 760px) {
  .sig-rail { margin: 0; padding: 0 0 var(--sig-2); scroll-padding-left: 0; }
}

/* ── Rail readout and arrows ────────────────────────────────────────── */

.sig-rail__count {
  display: flex; align-items: center; gap: var(--sig-3);
  font-size: 13px; color: var(--sig-muted);
  margin: var(--sig-4) 0 0;
}
.sig-rail__n { flex: none; font-variant-numeric: tabular-nums; min-width: 46px; }
.sig-rail__bar {
  flex: 1; height: 2px; background: var(--sig-rule);
  border-radius: 2px; overflow: hidden;
}
/* transform only. Transitioning `width` makes the browser re-run layout on
   every frame of the scroll; on a cheap Android that is the difference
   between a rail that glides and one that stutters. */
.sig-rail__bar i {
  display: block; height: 100%; background: var(--sig-ink);
  width: 6%; transition: transform .15s;
}

/* "Swipe" is a lie on a mouse; the arrows say it instead. */
.sig-rail__hint { flex: none; }
@media (min-width: 760px) { .sig-rail__hint { display: none; } }

/* NOT hidden on desktop. This element CONTAINS the arrows, so an earlier
   `display:none` at >=760px hid the very navigation it was meant to show. */
.sig-rail__nav { display: none; gap: var(--sig-2); flex: none; }
@media (min-width: 760px) { .sig-rail__nav { display: flex; } }

.sig-rail__btn {
  width: 44px; height: 44px; border-radius: 50%;
  border: 1px solid var(--sig-rule); background: var(--sig-card);
  color: var(--sig-ink); cursor: pointer;
  display: grid; place-items: center; padding: 0;
}
.sig-rail__btn:hover:not(:disabled) { border-color: var(--sig-ink); }
.sig-rail__btn:focus-visible { outline: 2px solid var(--sig-ink); outline-offset: 2px; }
.sig-rail__btn:disabled { opacity: .32; cursor: default; }
.sig-rail__btn svg {
  width: 17px; height: 17px; fill: none;
  stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ── What arrives, and running in a minute ──────────────────────────────
   THE CLIP NO LONGER DECIDES WHETHER THE SECTION EXISTS.

   `.sig-duo` was a two-column grid whose left cell was a video. Both of
   these sections were video-gated, no video was ever uploaded, and so both
   rendered nothing on live for the whole life of the port — taking the
   drawing's contents list with them, which had been deleted to avoid
   duplicating a section that never shipped. See seamless_kit()'s docblock.

   Now the words stand on their own and the clip is one more column when it
   exists. `auto-fit` rather than a fixed pair: with no clip the list simply
   takes the track, with no empty cell left behind and no modifier class to
   remember.                                                              */

.sig-kit__cols,
.sig-steps__cols { display: grid; gap: var(--sig-5); }

@media (min-width: 860px) {
  .sig-kit__cols,
  .sig-steps__cols {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
    gap: var(--sig-7);
    align-items: start;
  }
}

/* THE PARTS LIST.
   A drawing's call-out convention: the count out in the margin against a
   hairline, the name in ink, the qualifier under it. Not icon cards — the
   argument here is *how many things are in this box*, and four ruled rows
   say that where four bordered tiles just look like features. */
.sig .sig-kit__list { list-style: none; margin: 0; padding: 0; }
.sig .sig-kit__list li {
  display: grid; grid-template-columns: 30px minmax(0, 1fr);
  gap: var(--sig-3);
  padding: var(--sig-3) 0;
  border-top: 1px solid var(--sig-rule);
}
.sig .sig-kit__list li:last-child { border-bottom: 1px solid var(--sig-rule); }
.sig .sig-kit__n {
  font-family: var(--sig-display); font-size: 15px;
  color: var(--sig-muted); line-height: 1.5;
}
.sig .sig-kit__what { display: block; min-width: 0; }
.sig .sig-kit__what b { font-weight: 600; font-size: 15px; }
.sig .sig-kit__what small {
  display: block; margin-top: 2px;
  font-size: 13.5px; color: var(--sig-muted); line-height: 1.45;
}

/* THE STEPS.
   Numbered, and the number is the only place on this page a yellow fill is
   spent below the fold — the rule is "yellow fills shapes, ink writes
   words", and a step marker is a shape. Three of them draw the eye down the
   sequence, which is the one piece of content on the page whose order is
   load-bearing. */
.sig .sig-steps__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sig-4); }
.sig .sig-steps__list li {
  display: grid; grid-template-columns: 26px minmax(0, 1fr);
  gap: var(--sig-3); align-items: start;
}
.sig .sig-steps__n {
  display: grid; place-content: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--sig-yellow); color: var(--sig-ink);
  font-family: var(--sig-display); font-size: 14px; line-height: 1;
}
.sig .sig-steps__list b { grid-column: 2; font-weight: 600; font-size: 15px; align-self: center; }
.sig .sig-steps__list p {
  grid-column: 2; margin: 4px 0 0;
  font-size: 14.5px; color: var(--sig-muted); line-height: 1.5;
}

.sig-clip { margin: 0; }

/* 1:1 is the master shape — square footage reuses Meta ad creative without a
   recut. Desktop crops it to 16:9 with object-fit rather than asking for a
   second export. */
.sig-clip video,
.sig-clip .sig-clip__empty {
  width: 100%;
  aspect-ratio: 1 / 1;
  display: block;
  background: var(--sig-panel);
  border-radius: var(--sig-r-img);
  object-fit: cover;
}

/* 640, NOT 860 — the crop has to start where the clip gets wide, not where
   the columns split. And this block must sit AFTER the base rule: a media
   query adds no specificity, so an override written above the declaration it
   overrides simply loses. That cost a 720x720 clip rendering at 760px once. */
@media (min-width: 640px) {
  .sig-clip video,
  .sig-clip .sig-clip__empty { aspect-ratio: 16 / 9; }
}

.sig-clip figcaption {
  margin-top: var(--sig-2);
  font-size: 14px;
  color: var(--sig-muted);
}

.sig-duo__words > h2 { font-size: 25px; margin-bottom: var(--sig-2); }

/* ── The placeholder ────────────────────────────────────────────────────
   Rendered ONLY for someone who can edit the product. A customer shown a
   placeholder for a video that does not exist reads the page as broken, and
   on cash-on-delivery a page that looks broken is a refused parcel. */

/* `.sig-clip .sig-clip__empty` — the descendant selector is REQUIRED, not
   decoration. The shared frame rule above is `.sig-clip .sig-clip__empty`
   (0,2,0) and sets `display: block`; writing this one as a bare class (0,1,0)
   lost the cascade, so the heading and the note flowed inline as one run-on
   line. Same family of mistake as the media-query one above: the later rule
   only wins if it is at least as specific. */
.sig-clip .sig-clip__empty {
  display: grid; place-content: center; gap: 6px; text-align: center;
  padding: var(--sig-5);
  border: 1px dashed var(--sig-rule);
  background: repeating-linear-gradient(
    -45deg, var(--sig-panel), var(--sig-panel) 10px,
    var(--sig-paper) 10px, var(--sig-paper) 20px );
  color: var(--sig-muted);
}
.sig-clip .sig-clip__empty span { font-family: var(--sig-display); font-size: 17px; color: var(--sig-ink); }
.sig-clip .sig-clip__empty small { font-size: 12px; max-width: 30em; line-height: 1.5; }

/* ── The signature — a measured elevation ───────────────────────────────
   Ported from design/_pdp-v2.src.html 2026-08-31, unchanged apart from being
   scoped to `.sig`.

   Drawn rather than photographed because a photograph cannot show scale, and
   scale is the entire argument on this SKU: a BD competitor sells a flame
   diffuser at ৳680 and theirs is a ~10 cm upright cylinder. The drawing shares
   the homepage's vocabulary — ink hairlines on paper, yellow filling shapes
   and never writing words — so the two pages read as one site.

   The SVG carries no colours of its own; every part is classed and painted
   from Signal tokens here. That is what lets the drawing follow the page into
   the night theme instead of being a picture of the design. */
/* The shell is `.sig-sheet` now — see the note there. What is left here is
   the drawing itself.

   `.sig-figure__grid` is gone with it. Its comment recorded that the
   right-hand "In the box" list was removed from the drawing because "What
   arrives" duplicated it — and "What arrives" was then never ported, so the
   page carried neither for the whole of the port's life. Both are back:
   the list is `seamless_kit()`, in its own section directly below, and the
   drawing keeps the full measure, which is the right shape for a section
   whose only subject is size. */
.sig .sig-figure__lede {
  color: var(--sig-muted); font-size: 15px;
  margin: 0 0 var(--sig-6); max-width: 38em;
}

/* THE DRAWING SITS ON PAPER, INSET, WITH ITS OWN RULE.
   It is the one section that is an artefact rather than text, and at 960px of
   content track an unbounded elevation stops being a drawing and becomes a
   banner. Capped and centred inside the track, with the hairline frame a
   drawing is presented in. */
.sig .sig-figure__plate {
  max-width: 720px;
  margin-top: var(--sig-5);
  padding: var(--sig-6) var(--sig-5);
  border: 1px solid var(--sig-rule);
  border-radius: var(--sig-r-img);
  background: var(--sig-card);
}
.sig .sig-figure svg { width: 100%; height: auto; display: block; }
.sig .sig-figure__ink   { fill: none; stroke: var(--sig-ink); stroke-width: 1.6; stroke-linejoin: round; }
.sig .sig-figure__hair  { fill: none; stroke: var(--sig-ink); stroke-width: .8; }
.sig .sig-figure__glow  { fill: var(--sig-yellow); }
.sig .sig-figure__solid { fill: var(--sig-ink); }
.sig .sig-figure__dim   { fill: none; stroke: var(--sig-muted); stroke-width: .8; }
.sig .sig-figure__label {
  font-family: var(--sig-body); font-size: 7px; font-weight: 600;
  fill: var(--sig-muted); letter-spacing: .04em; text-transform: uppercase;
}
.sig .sig-figure__measure {
  font-family: var(--sig-display); font-size: 11px; fill: var(--sig-ink);
}

/* Editor-only scaffolding, shared by the figure and the angles. Customers
   never see it — seamless_figure() and seamless_angles() return early without
   edit_product. Deliberately unlovely: it is a to-do, not a design. */
.sig .sig-figure__empty {
  display: grid; place-content: center; gap: var(--sig-2);
  min-height: 160px; padding: var(--sig-5);
  border: 1px dashed var(--sig-rule); border-radius: var(--sig-r-row);
  background: var(--sig-panel); text-align: center;
}
.sig .sig-figure__empty span { font-weight: 600; font-size: 15px; }
.sig .sig-figure__empty small { color: var(--sig-muted); font-size: 13px; max-width: 40ch; }

/* ── The three angles ───────────────────────────────────────────────────
   Three propositions the 88-ad sample under-uses, one block each. NOT a
   feature grid — each is a claim with a reason to believe under it, because
   every 60-word feature stack in that sample was short-lived. */
.sig .sig-angles__grid { display: grid; gap: var(--sig-6); }

@media (min-width: 760px) {
  .sig .sig-angles__grid { grid-template-columns: repeat(3, 1fr); gap: var(--sig-6); }

  /* A HAIRLINE BETWEEN THE COLUMNS, NOT AROUND THEM.
     Three arguments read as three arguments; three bordered cards read as a
     feature grid, which is precisely the shape marketing/fireplace-angles.md
     says loses — "every 60-word feature stack in the sample is short-lived".
     A single rule in the gutter separates without boxing. */
  .sig .sig-angle + .sig-angle {
    padding-left: var(--sig-6);
    border-left: 1px solid var(--sig-rule);
  }
}

/* This is the page's ambience beat, so it gets the air. The angles are the
   only long-form copy on the page set at body size rather than muted-small —
   they are the argument, not a caption for one. */
.sig .sig-angle h3 { font-size: 21px; margin-bottom: var(--sig-3); }
.sig .sig-angle p { margin: 0; color: var(--sig-muted); font-size: 15px; }

/* ── Order now ──────────────────────────────────────────────────────────
   The primary CTA, and the yellow fill moves here from Add to bag. The
   research this spends is in pdp-brief.md: buy-it-now dominant, add-to-cart
   quiet, because on a COD store every step between the ad and the address
   form leaks paid traffic. Add to bag stays — a buyer taking two products
   still needs it, and removing a path to force a funnel is exactly the kind
   of trick this brand's trust argument is against. */

.sig .sig-cta {
  order: -1;                 /* above the quantity row, whatever Woo's order */
  flex: 1 1 100%;
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--sig-3);
  min-height: 58px; padding: 0 var(--sig-5);
  border: 0; border-radius: var(--sig-r-chip);
  background: var(--sig-yellow); color: var(--sig-ink);
  font-family: var(--sig-body); font-size: 17px; font-weight: 600;
  cursor: pointer; transition: filter .12s;
}
.sig .sig-cta:hover { filter: brightness(.96); }
.sig .sig-cta:focus-visible { outline: 2px solid var(--sig-ink); outline-offset: 3px; }
.sig .sig-cta__price { font-family: var(--sig-display); font-size: 18px; }
.sig .sig-cta__price:not(:empty)::before { content: "·"; margin-right: var(--sig-3); opacity: .45; }

/* Waiting on a variation. Not hidden — a disabled primary that names what it
   will cost is a reason to finish choosing; a missing one is a dead end. */
.sig .sig-cta[disabled],
.sig .sig-cta--wait {
  background: var(--sig-rule); color: var(--sig-muted); cursor: not-allowed;
}

/* Add to bag steps down to the quiet outline. Overrides the yellow-fill rule
   above it, so it must stay below it in the file — a later rule of equal
   specificity is the whole mechanism. */
.sig .single_add_to_cart_button,
.sig .single_add_to_cart_button.button.alt {
  flex: 1 1 160px;
  background: transparent;
  color: var(--sig-ink);
  border: 1px solid var(--sig-rule);
  font-weight: 500;
}
.sig .single_add_to_cart_button:hover,
.sig .single_add_to_cart_button.button.alt:hover {
  background: transparent; border-color: var(--sig-ink); filter: none;
}
.sig .single_add_to_cart_button.disabled,
.sig .single_add_to_cart_button:disabled,
.sig .single_add_to_cart_button.button.alt.disabled,
.sig .single_add_to_cart_button.button.alt:disabled {
  background: transparent; color: var(--sig-muted); border-color: var(--sig-rule);
}

/* The delivered price, under the number it corrects. */
.sig .sig-price__note {
  margin: var(--sig-2) 0 0;
  color: var(--sig-muted); font-size: 14px; line-height: 1.45;
}

/* ── You save ───────────────────────────────────────────────────────────
   Absorbed from you-save-x-for-woocommerce, which printed
   `style="color:#ff4747;background-color:#fff1f1"` INLINE — unreachable by any
   stylesheet rule, so restyling it meant `!important` on every declaration
   forever. Nine lines of PHP replaced it.

   The design: the saving is not an alarm. Seamless discounts on purpose —
   bundles and ladders are the pricing model — so this is part of the offer,
   stated plainly rather than shouted in warning red.

   YELLOW AS A MARK, NOT A FILL. The rule is "yellow fills shapes, ink writes
   words", and the two reserved fills are the CTA and the best-value tab. So
   the amount is INK, set in the display face, with yellow struck behind it
   like a highlighter — the eye lands on the number, and the page's one yellow
   button keeps its meaning. Same instinct as Treatment C, one component over. */

.sig-save {
  display: inline-flex; align-items: baseline; flex-wrap: wrap; gap: 7px;
  margin: var(--sig-3) 0 0;
  font-family: var(--sig-body); font-size: 14px; color: var(--sig-muted);
}
.sig-save b {
  position: relative;
  font-family: var(--sig-display); font-weight: 400; font-size: 17px;
  color: var(--sig-ink);
  /* Behind the digits, not around them — inset so it reads as a mark on the
     paper rather than a chip stuck to it. */
  box-shadow: inset 0 -0.42em 0 rgba(250, 208, 44, .55);
}
.sig-save span { font-size: 13px; color: var(--sig-muted); }

/* In the product grid it is a footnote to the price, not a second headline. */
.woocommerce ul.products li.product .sig-save { margin-top: var(--sig-1); font-size: 13px; }
.woocommerce ul.products li.product .sig-save b { font-size: 15px; }

/* ══════════════════════════════════════════════════════════════════════
   THE STICKY ORDER BAR — added 2026-09-04, after the Solar Fan launch.

   WHY IT EXISTS, MEASURED. The Solar Fan PDP is 5,767px tall against an
   852px viewport — 6.8 screens. The primary CTA sits at y=985, just under
   the fold, and there was nothing pinned. Clarity put average scroll depth
   at 39.4% and active engagement at 21 seconds: the median visitor scrolls
   PAST the only Order-now button and then has none. Meta bought 1,246
   landing-page views and 51 add-to-carts on that page — 4.1%.

   THE YELLOW BUDGET IS NOT SPENT TWICE. design/README.md reserves exactly
   two yellow FILLS per page and the critique already found it overspent.
   This bar is not a third: it is a PROXY that appears only while the real
   .sig-cta is off screen, so at any instant exactly one yellow CTA is
   visible. An IntersectionObserver on the real button is what guarantees
   that, which is why the reveal is not a scroll-position guess.

   It is also why the proxy forwards its click rather than submitting: the
   hidden `add-to-cart` field, the buy-now redirect and Woo's own error
   handling all hang off the real button. See seamless_order_now_button().
   ══════════════════════════════════════════════════════════════════════ */

.sig-stick {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 90;
  padding: 10px var(--sig-4);
  padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
  background: var(--sig-paper);
  border-top: 1px solid var(--sig-hair);
  transform: translateY(110%);
  transition: transform .18s ease-out;
}
.sig-stick[data-shown] { transform: none; }

/* Matches .sig-cta exactly — same face, same weight, same 58px target. It is
   meant to read as the same button, because it is. */
.sig-stick__btn {
  width: 100%;
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--sig-3);
  min-height: 58px; padding: 0 var(--sig-5);
  border: 0; border-radius: var(--sig-r-chip);
  background: var(--sig-yellow); color: var(--sig-ink);
  font-family: var(--sig-body); font-size: 17px; font-weight: 600;
  cursor: pointer; transition: filter .12s;
}
.sig-stick__btn:hover { filter: brightness(.96); }
.sig-stick__btn:focus-visible { outline: 2px solid var(--sig-ink); outline-offset: 3px; }
.sig-stick__price { font-family: var(--sig-display); font-size: 18px; }
.sig-stick__price:not(:empty)::before { content: "·"; margin-right: var(--sig-3); opacity: .45; }

/* Waiting on a variation, mirroring .sig-cta--wait. Not hidden: a disabled
   primary that names what it will cost is a reason to finish choosing. The
   click still fires — it scrolls to the unanswered option. */
.sig-stick__btn[data-wait] {
  background: var(--sig-rule); color: var(--sig-muted);
}

/* The bar is a phone affordance. Above the fold on a desktop the real CTA is
   never far, and a pinned bar there is just chrome over the content. */
@media (min-width: 900px) { .sig-stick { display: none; } }

@media (prefers-reduced-motion: reduce) {
  .sig-stick { transition: none; }
}
