/* ============================================================
   TrendiestStore — the last layer

   WHY THERE IS A LAYER AFTER ts-woo.css. ts-woo.css says nothing may
   load after it, and on most pages nothing does. The product page is
   the exception: inc/assets.php loads Dawn's section-main-product.css
   set AFTER it, because that is where Shopify loaded it from. So a
   product-page correction written in ts-woo.css loses every tie to a
   file that arrives later — which is exactly how the gallery bug in
   §1 survived.

   This file is enqueued last on every page, after that set. It holds
   the corrections that have to win, and the WooCommerce-facing
   components that have no Shopify counterpart to port: colour
   swatches, the filter panel, the cart, the checkout.

   1rem IS 12px. Dawn sets `html { font-size: calc(var(--font-body-scale)
   * 62.5%) }` with body_scale 120. Control heights, icon boxes and
   borders are written in px on purpose; type is in rem so it scales
   with the rest of the theme.
   ------------------------------------------------------------
    0  WooCommerce's own form variables
    1  Product gallery: the stage, the rail and the phone slider
    2  Colour swatches
    3  Catalog: sort bar, filters, pagination
    4  Product cards on the home page (gift packs)
    5  Cart
    6  Checkout
    7  Shared summary rows
    8  Quantity steppers
    9  Buttons, forms and notices
   10  Header: search and account
   11  Footer social icons
   12  Account, login and register
   13  Responsive
   14  Product reviews
   ============================================================ */

/* ---------- 0. WooCommerce's own form variables --------------- */
/* THE FIVE VARIABLES BEHIND EVERY FIELD ON THE SHOP, AND WHY THEY ARE
   RE-POINTED HERE RATHER THAN IN ts-tokens.css.

   WooCommerce 8 stopped hard-coding its field colours and started
   drawing every input through a `:root` block of its own:

     --wc-form-border-color: rgba(32, 7, 7, 0.8);
     --wc-form-border-radius: 4px;
     --wc-form-color-text: #000;

   applied by `.woocommerce form .form-row .input-text`, which is
   (0,3,1). No theme rule written as `.ts-auth input[type='text']` or
   `.woocommerce-checkout input[type='text']` — both (0,1,1) or (0,2,1)
   — reaches it, so every field on the shop was drawn with a near-black
   1px border and a 4px corner on a storefront whose every other box is
   a soft pink line at 10px. Chasing it selector by selector means a
   four-class rule for every field type on every page, forever.

   Beating the variables instead is one block, and it fixes the fields
   nobody has looked at yet. It has to live HERE and not in
   ts-tokens.css: `:root` versus `:root` is a tie, ties go to the later
   stylesheet, and woocommerce.css loads after everything ts-tokens.css
   can reach. This file loads after woocommerce.css. That is the whole
   reason for the address.

   Padding and height are still set per component — those are layout,
   and Woo's `.5em` is not wrong so much as not ours. */
:root {
  --wc-form-border-color: var(--ts-line-2);
  --wc-form-border-width: 1px;
  --wc-form-border-radius: var(--inputs-radius, 10px);
  --wc-form-color-text: var(--ts-text);
  --wc-form-color-background: #fff;
}

/* ---------- 1. Product gallery ------------------------------- */
/* THE DESKTOP BUG. Dawn's section-main-product.css gives a media slide
   its full width only when it is the FIRST child:

     @media (min-width: 990px) {
       .product:not(.product--columns) .product__media-list
         .product__media-item:first-child { width: 100%; max-width: 100% }
     }

   Every other slide falls back to `.grid__item` in base.css, which is
     width: calc(25% - ...); max-width: calc(50% - ...)

   On this theme all slides are stacked in one stage and switched by a
   class, so slide 2 onwards is a HALF-WIDTH stage. That is the
   "clicking next wrecks the image size" report: the first photo filled
   the box and every one after it was drawn in a quarter of it.

   Dawn never hit this because its thumbnail gallery moves `is-active`
   through slides that are all `:first-child` of their own container in
   the layouts it ships. Ours are siblings.

   Fixed by giving every slide the width the first one already had. The
   phone scroller below 750px gets its own width in §1d — there the
   slides really are side by side. */
@media screen and (min-width: 750px) {
  product-info .product__media-list .product__media-item,
  .product__media-list .product__media-item {
    width: 100%;
    max-width: 100%;
    flex: 0 0 100%;
    padding-left: 0;
  }
}

/* BOTH CUSTOM ELEMENTS ON THIS PAGE ARE UNKNOWN TO THE BROWSER.
   media-gallery.js and product-info.js are Dawn's and are deliberately
   not enqueued, so `product-info` and `media-gallery` fall back to the
   unknown-element default, `display: inline` — the exact trap
   `slider-component` fell into in ts-product.css §9. An inline box
   drops its vertical margins and is a useless containing block for the
   arrows below. */
product-info,
media-gallery {
  display: block;
}

/* The stage holds the slide rail, the two arrows and the counter, and
   is what those three are positioned against. The rail is positioned
   as well, deliberately: ts-gallery.js scrolls it with
   `slide.offsetLeft`, and that is only the slide's scroll offset while
   the rail itself is the slide's offset parent. */
.ts-gallery__stage {
  position: relative;
}

product-info .product__media-wrapper .product__media-list {
  position: relative;
}

product-info .product__media-wrapper {
  --ts-thumb: 88px;
}

/* The stage keeps one size no matter which photo is in it. `contain`
   letterboxes a portrait bottle and a landscape flat-lay into the same
   square instead of reflowing the column between clicks.

   The fallback in the var() matters: ts-critical.css carries this rule
   without one, and on the frame before ts-product.css has defined
   --ts-stage an unresolved var() makes the width `auto`. */
product-info .product__media-list .product__media-item .product-media-container {
  width: var(--ts-stage, min(44rem, 100%, 45vh));
  max-width: 100%;
}

/* ---- 1a. the thumbnail rail: ONE row, at every width ----------
   section-main-product.css turns `.thumbnail-list` into
   `display: grid; grid-template-columns: repeat(4, 1fr)` — six columns
   under `.product--large`, which this template always sets — and it is
   enqueued AFTER ts-product.css, so it won every tie against the
   `display: flex` written there. Ten thumbnails therefore came out as
   two ragged grid rows of fixed-width tiles floating at the left edge
   of much wider 1fr cells, on desktop and on tablet both.

   Carried to (0,2,1) so the row wins back, and given real padding: the
   active ring below is a 2px box-shadow drawn OUTSIDE the tile, and
   `overflow-x: auto` computes `overflow-y` to `auto` too, so without
   that padding the ring is clipped away on the one tile it marks. */
product-info .product__media-gallery .thumbnail-list,
.product__media-wrapper .thumbnail-list {
  display: flex;
  position: relative;
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: 8px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: 3px;
  margin: 0;
  padding: 3px;
  scrollbar-width: none;
  /* `justify-content: center` on a scroller whose content OVERFLOWS
     puts the leading items at a negative offset, and scrollLeft cannot
     go below 0 — with 17 media the first six thumbnails sat at x = -528
     and could never be scrolled back. `safe center` centres only while
     everything fits and falls back to flex-start the moment it does
     not; the plain `flex-start` above it is the fallback for engines
     that do not know the `safe` keyword. Order matters. */
  justify-content: flex-start;
  justify-content: safe center;
}

product-info .product__media-gallery .thumbnail-list::-webkit-scrollbar {
  display: none;
}

product-info .product__media-gallery .thumbnail-list__item {
  flex: 0 0 auto;
  width: var(--ts-thumb, 88px);
  max-width: var(--ts-thumb, 88px);
  margin: 0;
  padding: 0;
  scroll-snap-align: center;
}

/* `.global-media-settings` on the button is `overflow: visible
   !important` and hangs a 10x12px drop shadow off an ::after, which on
   a tight rail reads as smudge rather than depth. The tile gets a hard
   edge of its own instead — and the radius has to go on the <img> too,
   because that !important means the button cannot clip it. */
product-info .product__media-gallery .thumbnail-list__item .thumbnail {
  position: relative;
  display: block;
  width: 100%;
  height: var(--ts-thumb, 88px);
  padding: 0;
  border: 0;
  border-radius: var(--ts-r-sm);
  background: var(--ts-plate);
  box-shadow: inset 0 0 0 1px var(--ts-line);
  cursor: pointer;
  opacity: 1;
  transition: box-shadow 0.18s var(--ts-ease), transform 0.18s var(--ts-ease);
}

product-info .product__media-gallery .thumbnail-list__item .thumbnail::after {
  content: none;
}

product-info .product__media-gallery .thumbnail-list__item .thumbnail img {
  width: 100%;
  height: 100%;
  /* `contain`, not Dawn's `cover`. These thumbnails ARE the variant
     picker on a phone — a cropped tile can hide the exact bit of
     colour someone is choosing by. */
  object-fit: contain;
  padding: 4px;
  border-radius: var(--ts-r-sm);
  pointer-events: none;
}

product-info .product__media-gallery .thumbnail:hover {
  opacity: 1;
  box-shadow: inset 0 0 0 1px var(--ts-line-2);
  transform: translateY(-1px);
}

/* The thumbnail that is showing has to be obvious — a 2px brand ring,
   not Dawn's 1px grey outline, which on a pink page read as no state. */
product-info .product__media-gallery .thumbnail[aria-current='true'],
product-info .thumbnail[aria-current='true'],
.thumbnail-list__item .thumbnail[aria-current='true'] {
  box-shadow: 0 0 0 2px var(--ts-brand);
  border-radius: var(--ts-r-sm);
  transform: none;
}

/* The rail hides its scrollbar, so a fade at whichever end still has
   tiles behind it is the only thing telling anyone they are there.
   ts-gallery.js sets the classes — CSS has no way to ask whether a box
   overflows. Masked rather than overlaid with a gradient because the
   rail sits on the page background, not on a known colour, and a fake
   white overlay would show as a smear on any coloured section. */
product-info .product__media-gallery .thumbnail-list.ts-rail--more-end {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 44px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 44px), transparent);
}

product-info .product__media-gallery .thumbnail-list.ts-rail--more-start {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 44px);
  mask-image: linear-gradient(to right, transparent, #000 44px);
}

product-info .product__media-gallery .thumbnail-list.ts-rail--more-start.ts-rail--more-end {
  -webkit-mask-image: linear-gradient(
    to right,
    transparent,
    #000 44px,
    #000 calc(100% - 44px),
    transparent
  );
  mask-image: linear-gradient(to right, transparent, #000 44px, #000 calc(100% - 44px), transparent);
}

/* ---- 1b. arrows and the position counter ---------------------
   woocommerce/single-product.php prints these only when the product
   actually has more than one photo, so there is no single-image state
   to style away here. */
.ts-gallery__nav {
  position: absolute;
  top: 50%;
  z-index: 3;
  display: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: var(--ts-r-pill);
  background: rgba(255, 255, 255, 0.94);
  color: var(--ts-text);
  /* The inset ring is not decoration. Most of this catalogue is shot on
     white, and a white pill with only a soft drop shadow under it
     disappears completely on those photos — the arrows were invisible
     until you happened to hover where one was. */
  box-shadow: inset 0 0 0 1px var(--ts-line-2), var(--ts-sh-2);
  cursor: pointer;
  transform: translateY(-50%);
  transition: background 0.18s var(--ts-ease), box-shadow 0.18s var(--ts-ease),
    opacity 0.18s var(--ts-ease);
}

.ts-gallery__nav svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The icon set has exactly one chevron — `caret`, pointing down.
   Rotating it is one declaration; a left and a right path added to
   ts_icon_paths() would be two more shapes to keep in step with it. */
.ts-gallery__nav--prev {
  left: 10px;
}

.ts-gallery__nav--prev svg {
  transform: rotate(90deg);
}

.ts-gallery__nav--next {
  right: 10px;
}

.ts-gallery__nav--next svg {
  transform: rotate(-90deg);
}

.ts-gallery__nav:hover {
  background: #fff;
  color: var(--ts-brand);
  box-shadow: inset 0 0 0 1px var(--ts-line-2), var(--ts-sh-3);
}

/* At the ends the control stays legible and stops responding, rather
   than fading to nothing — an arrow that vanishes reads as a rendering
   fault, not as "you are at the first photo". */
.ts-gallery__nav[disabled] {
  color: var(--ts-muted-2);
  opacity: 0.6;
  cursor: default;
  box-shadow: inset 0 0 0 1px var(--ts-line), var(--ts-sh-1);
}

.ts-gallery__nav[disabled]:hover {
  background: rgba(255, 255, 255, 0.94);
  color: var(--ts-muted-2);
  box-shadow: inset 0 0 0 1px var(--ts-line), var(--ts-sh-1);
}

/* Arrows are a pointer affordance. A phone swipes the rail instead, and
   two 40px buttons parked over a full-bleed photo would only cover it. */
@media screen and (min-width: 750px) {
  .ts-gallery__nav {
    display: flex;
  }
}

.ts-gallery__count {
  position: absolute;
  right: 12px;
  bottom: 12px;
  z-index: 3;
  margin: 0;
  padding: 4px 10px;
  border-radius: var(--ts-r-pill);
  background: rgba(31, 27, 29, 0.62);
  color: #fff;
  font-size: 1.0833rem;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0.02em;
  pointer-events: none;
}

/* ---- 1c. tablet: the wrapper IS the stage --------------------
   ts-product.css §7 caps the stage at `min(38rem, 100%, 48vh)` below
   990px but leaves the wrapper at `min(76rem, 100%)`, so on an iPad a
   456px square sat centred in a 728px box — with the thumbnail rail and
   both arrows lining up on the WRAPPER's edges, tens of pixels clear of
   the photo they belong to. Sizing the wrapper instead gives the photo,
   the arrows and the rail one shared edge. */
@media screen and (min-width: 750px) and (max-width: 989px) {
  product-info .product--large:not(.product--no-media) .product__media-wrapper,
  product-info .product--medium:not(.product--no-media) .product__media-wrapper,
  product-info .product--small:not(.product--no-media) .product__media-wrapper,
  product-info .product__media-wrapper {
    --ts-stage: 100%;
    --ts-thumb: 76px;
    width: min(52rem, 100%, 58vh);
    max-width: 100%;
    margin-inline: auto;
  }
}

/* ---- 1d. phone: the rail that never was ----------------------
   THE MOBILE BUG. `.product__media-list` carries Dawn's `slider
   slider--mobile` pair, and every rule those two bring assumes the list
   is ALREADY a flex row — Dawn's own markup carries `grid` as well, and
   `.grid { display: flex }` in base.css is where the row actually comes
   from. This template does not carry it, and nothing else in the
   cascade sets a display on that element. So below 750px — the one
   range where section-main-product.css does NOT hide the inactive
   slides — the list stayed a plain block:

     * all ten photos rendered stacked down the page, one under the
       other, which is the "every variant's picture is listed
       separately on my phone" report;
     * each one capped at `max-width: calc(50% - ...)` by `.grid__item`
       in base.css, so they were half the width of the column;
     * and the lot dragged 30px off the left edge by Dawn's
       `margin-left: -2.5rem; width: calc(100% + 4rem)` bleed, which is
       meant for a peeking carousel and on a stack reads as a page that
       has slipped sideways.

   Restoring the row is what makes the phone half of ts-gallery.js —
   swipe the rail and the thumbnail selection follows, tap a thumbnail
   and the rail scrolls — do what its header comment always claimed. */
@media screen and (max-width: 749px) {
  product-info .product__media-wrapper .product__media-list {
    display: flex;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: 0;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    margin: 0;
    padding: 0;
    width: 100%;
    max-width: 100%;
  }

  product-info .product__media-wrapper {
    --ts-thumb: 66px;
  }

  product-info .product__media-wrapper .product__media-list::-webkit-scrollbar {
    display: none;
  }

  /* `.slider--mobile:after` appends a 1.5rem flex child as a trailing
     gutter. Under `scroll-snap-type: x mandatory` that is one more snap
     stop, and it shows nothing. */
  product-info .product__media-wrapper .product__media-list::after {
    content: none;
  }

  product-info .product__media-list .product__media-item,
  .product__media-list .product__media-item {
    flex: 0 0 100%;
    width: 100%;
    max-width: 100%;
    padding-inline: 0;
    /* component-slider.css reserves the media drop-shadow's full reach
       above and below every slide, and ts-mobile.css §4 already trims
       the bottom half of it. Both go: the stage is the full width of
       the phone here and the shadow has nowhere left to fall. */
    padding-block: 0;
    scroll-snap-align: center;
    scroll-snap-stop: always;
  }

  /* THE STAGE IS SQUARE ON A PHONE TOO, and that is a measurement, not
     a default. The stage is one fixed box with `object-fit: contain`,
     so whichever ratio it takes, the other shapes letterbox inside it —
     the only question is which shape is rarest. The media library is
     100 square images to 25 portrait 3:4 and 13 landscape 4:3, so a
     square stage fits the great majority exactly and costs the
     portraits 40px down each side; a 3:4 stage would cost the square
     majority 53px top and bottom AND push the buy block a further
     107px down the phone.

     ts-mobile.css §4 asks for 3/4 here and has never once got it:
     ts-critical.css prints the 1/1 rule at the same (0,2,1) from inside
     <head>, after every <link>, so it takes the tie. Restating 1/1
     rather than deleting that rule, because ts-mobile.css is a verbatim
     port and is not ours to hand-edit — see the theme notes. */
  product-info .product__media-list .product__media-item .product-media-container {
    aspect-ratio: 1 / 1;
    width: 100%;
    max-width: 100%;
  }

  /* the rail is edge to edge here, so the counter comes in off the
     corner to clear the rounded plate */
  .ts-gallery__count {
    right: 10px;
    bottom: 10px;
  }

  product-info .product__media-gallery .thumbnail-slider {
    margin-top: 10px;
  }
}

@media (prefers-reduced-motion: reduce) {
  product-info .product__media-wrapper .product__media-list {
    scroll-behavior: auto;
  }

  .ts-gallery__nav,
  product-info .product__media-gallery .thumbnail-list__item .thumbnail {
    transition: none;
  }
}

/* ---------- 2. Colour swatches ------------------------------- */
/* inc/swatches.php prints these in front of Woo's attribute <select>
   and leaves the select in the form; assets/js/ts-swatch.js copies a
   press into it. The select is hidden rather than removed — see the
   header of either file. */
.ts-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 12px;
}

/* THE THREE SIZING DECLARATIONS ARE ALL LOAD-BEARING.

   A <button> does not shrink-wrap its contents the way a <div> does.
   Chrome gives form controls `field-sizing: fixed`, which sizes them
   from a UA default instead of their content, so these pills came out
   38px wide — the chip plus its padding — and every colour name was
   clipped to its first letter. `Blue` read as `B`.

   `field-sizing: content` is the correct fix and is what newer Chrome
   honours. `width: max-content` is the same instruction for engines
   that do not know the property yet (Firefox, Safari before 26), and
   `max-width: 100%` stops a long name — "Rose Pink Glitter" — from
   pushing the row wider than the column it sits in. `flex: 0 0 auto`
   then keeps the flex line from shrinking what those two just sized. */
.ts-swatch {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: 0 0 auto;
  field-sizing: content;
  width: max-content;
  max-width: 100%;
  min-height: 38px;
  padding: 0 12px 0 6px;
  border: 1.5px solid var(--ts-line);
  border-radius: var(--ts-r-pill);
  background: #fff;
  color: var(--ts-text-2);
  font: inherit;
  font-size: 1.0833rem;
  font-weight: 500;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.18s var(--ts-ease), color 0.18s var(--ts-ease),
    box-shadow 0.18s var(--ts-ease);
}

.ts-swatch__name {
  white-space: nowrap;
}

.ts-swatch__chip {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  /* an inset ring, so a white or a cream swatch still has an edge
     against a white card */
  box-shadow: inset 0 0 0 1px rgba(31, 27, 29, 0.18);
  flex: none;
}

.ts-swatch--text {
  padding-left: 14px;
}

.ts-swatch:hover:not(:disabled) {
  border-color: var(--ts-line-2);
  color: var(--ts-text);
}

.ts-swatch.is-selected {
  border-color: var(--ts-brand);
  color: var(--ts-brand);
  box-shadow: 0 0 0 1px var(--ts-brand);
}

.ts-swatch.is-selected .ts-swatch__chip {
  box-shadow: inset 0 0 0 1px rgba(31, 27, 29, 0.18), 0 0 0 2px #fff,
    0 0 0 3.5px var(--ts-brand);
}

/* A colour no remaining combination can reach. Struck through rather
   than merely faded: faded alone reads as "not chosen yet". */
.ts-swatch.is-unavailable {
  cursor: not-allowed;
  opacity: 0.45;
}

.ts-swatch.is-unavailable .ts-swatch__chip {
  position: relative;
  overflow: hidden;
}

.ts-swatch.is-unavailable .ts-swatch__chip::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top left,
    transparent calc(50% - 1px),
    rgba(31, 27, 29, 0.55) calc(50% - 1px),
    rgba(31, 27, 29, 0.55) calc(50% + 1px),
    transparent calc(50% + 1px)
  );
}

/* The select stays in the form and stays reachable by a screen reader;
   it is only taken off the screen. `display: none` would also do, but
   this keeps it focusable if a browser autofills it. */
.ts-swatches__select {
  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;
}

/* Without JavaScript nothing hides the select and nothing drives the
   buttons, so the select is the control — put it back. */
html:not(.ts-js) .ts-swatches {
  display: none;
}

html:not(.ts-js) .ts-swatches__select {
  position: static;
  width: auto;
  height: auto;
  margin: 0;
  overflow: visible;
  clip: auto;
  clip-path: none;
}

/* The attribute label sits above the row rather than beside it — a
   table cell layout for one <select> was the reason "Color" and its
   dropdown were on the same line at 400px wide. */
.ts-product-page .variations tr {
  display: block;
  margin-bottom: 6px;
}

/* ---------- 2b. A split product's colours -------------------- */
/* template-parts/colour-family.php: one tile per colour of a product
   the 2026-09-27 split broke up, each a link to that colour's page.
   assets/js/ts-family.js makes a press switch the page in place. The
   tiles carry no names — the chosen one is written in the label above,
   so six characters' names do not wrap into a paragraph on a phone.

   Links, not buttons, so none of §2's button sizing applies; the
   selected ring is §2's, so a split product's picker and a variable
   product's read as the same control. Its margins are the buy
   column's own rhythm, `.product__info-container > * + *` in
   section-main-product.css. */
.ts-family__label {
  margin: 0 0 8px;
  font-size: 1.1667rem;
  font-weight: 600;
  color: var(--ts-text-2);
}

.ts-family__label b {
  color: var(--ts-text);
  font-weight: 700;
}

.ts-family__row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.ts-family__opt {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  color: var(--ts-text-2);
  text-decoration: none;
  cursor: pointer;
  transition: border-color 0.18s var(--ts-ease), box-shadow 0.18s var(--ts-ease);
}

/* Photo tiles: a white square with the product on it, the same white
   the gallery and the cards use, so the tile is a small copy of what
   the stage will show. */
.ts-family--image .ts-family__opt {
  width: 56px;
  height: 56px;
  padding: 3px;
  border: 1.5px solid var(--ts-line);
  border-radius: var(--ts-r-sm);
  background: #fff;
}

.ts-family__img,
.ts-family__img img {
  display: block;
  width: 100%;
  height: 100%;
}

.ts-family__img img {
  object-fit: contain;
}

/* Dot tiles, for a family whose colours share one photograph. */
.ts-family--dot .ts-family__opt {
  width: 40px;
  height: 40px;
  border-radius: 50%;
}

.ts-family__dot {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(31, 27, 29, 0.18);
}

/* A colour with no swatch and no photo of its own falls back to its
   name, in §2's text pill. */
.ts-family__text {
  padding: 0 12px;
  font-size: 1.0833rem;
  font-weight: 500;
  white-space: nowrap;
}

.ts-family--dot .ts-family__opt:has(.ts-family__text) {
  width: auto;
  min-height: 38px;
  border: 1.5px solid var(--ts-line);
  border-radius: var(--ts-r-pill);
}

.ts-family__opt:hover {
  border-color: var(--ts-line-2);
}

.ts-family--dot .ts-family__opt:hover {
  box-shadow: 0 0 0 1.5px var(--ts-line-2);
}

.ts-family__opt.is-selected {
  border-color: var(--ts-brand);
  box-shadow: 0 0 0 1px var(--ts-brand);
}

.ts-family--dot .ts-family__opt.is-selected {
  box-shadow: 0 0 0 2px var(--ts-brand);
}

/* Out of stock: struck through and faded, and still a link — its page
   says so and offers WhatsApp for when it is back. §2's diagonal. */
.ts-family__opt.is-out {
  opacity: 0.45;
}

.ts-family__opt.is-out::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(
    to top left,
    transparent calc(50% - 1px),
    rgba(31, 27, 29, 0.55) calc(50% - 1px),
    rgba(31, 27, 29, 0.55) calc(50% + 1px),
    transparent calc(50% + 1px)
  );
}

/* While the sibling's column is on its way the old one is still the
   old colour, so it steps back rather than looking current. The row
   itself stays at full strength — it is already right. */
.product__info-container.is-switching > :not([data-ts-family]) {
  opacity: 0.55;
  transition: opacity 0.15s var(--ts-ease);
}

@media (max-width: 619px) {
  .ts-family__row {
    gap: 6px;
  }

  .ts-family--image .ts-family__opt {
    width: 48px;
    height: 48px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ts-family__opt,
  .product__info-container.is-switching > * {
    transition: none;
  }
}

/* ---------- 3. Catalog: sort bar, filters, pagination -------- */
/* THE GRID NEEDS ITS OWN BREATHING ROOM. `.ts-collhead` is a tinted band
   and `.ts-collection` had no block padding at all, so the filter card and
   the sort bar started against the bottom edge of the tint and read as one
   run-together block with the title. The space below is larger than the
   space above so the grid sits under the header rather than floating
   between it and the features strip. */
.ts-collection {
  padding-block: clamp(22px, 2.8vw, 38px) clamp(30px, 3.6vw, 52px);
}

.ts-collection__layout {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr);
  gap: clamp(20px, 2.4vw, 34px);
  align-items: start;
}

/* The sidebar follows the shopper down a long grid instead of leaving a
   column of empty space beside row eleven. `align-items: start` above is
   what makes the sticky possible; the offset clears the sticky header. */
@media screen and (min-width: 990px) {
  .ts-collection__layout > .ts-filters {
    position: sticky;
    top: 96px;
    max-height: calc(100vh - 116px);
    overflow-y: auto;
    scrollbar-width: thin;
  }
}

/* --- the bar above the grid --- */
.ts-facetbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin: 0 0 20px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--ts-line);
}

.ts-facetbar__spacer {
  flex: 1 1 auto;
}

.ts-facetbar .woocommerce-result-count {
  order: 0;
  margin: 0;
}

.ts-filters__open {
  display: none; /* phones only; turned on in §13 */
  align-items: center;
  gap: 8px;
  min-height: 40px;
  padding: 0 16px;
  border: 1px solid var(--ts-line-2);
  border-radius: var(--ts-r-pill);
  background: #fff;
  color: var(--ts-text);
  font: inherit;
  font-size: 1.0833rem;
  font-weight: 600;
  cursor: pointer;
}

.ts-filters__open svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ts-filters__open-n {
  display: grid;
  place-items: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--ts-brand);
  color: #fff;
  font-size: 0.9167rem;
  line-height: 1;
}

/* --- active filter chips --- */
.ts-activefilters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0 0 18px;
}

.ts-activefilters__chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 8px 0 12px;
  border: 1px solid var(--ts-line-2);
  border-radius: var(--ts-r-pill);
  background: var(--ts-bg-2);
  color: var(--ts-text-2);
  text-decoration: none;
  font-size: 1rem;
}

.ts-activefilters__chip:hover {
  border-color: var(--ts-brand);
  color: var(--ts-brand);
}

.ts-activefilters__chip svg {
  width: 13px;
  height: 13px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
}

.ts-activefilters__clear {
  font-size: 1rem;
  color: var(--ts-muted);
  text-decoration: underline;
}

/* --- the panel --- */
.ts-filters__panel {
  padding: 18px;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r-lg);
  background: var(--ts-surface-2);
}

.ts-filters__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}

.ts-filters__title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 1.3333rem;
  font-weight: 600;
  color: var(--ts-text);
}

.ts-filters__title svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: var(--ts-brand);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ts-filters__count {
  display: grid;
  place-items: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--ts-brand);
  color: #fff;
  font-size: 0.9167rem;
}

.ts-filters__close {
  display: none; /* drawer only */
  place-items: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--ts-line);
  border-radius: 50%;
  background: #fff;
  color: var(--ts-text-2);
  cursor: pointer;
}

.ts-filters__close svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}

.ts-filters__group {
  border-top: 1px solid var(--ts-line);
  padding: 12px 0 4px;
}

.ts-filters__legend {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 4px 0 10px;
  border: 0;
  background: none;
  color: var(--ts-text);
  font: inherit;
  font-size: 1.1667rem;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.ts-filters__legend svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: var(--ts-muted);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 0.2s var(--ts-ease);
}

.ts-filters__group.is-collapsed .ts-filters__legend svg {
  transform: rotate(-90deg);
}

.ts-filters__group.is-collapsed .ts-filters__body {
  display: none;
}

.ts-filters__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 2px;
}

.ts-filters__opt {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 6px 8px;
  border-radius: var(--ts-r-sm);
  cursor: pointer;
  font-size: 1.0833rem;
  color: var(--ts-text-2);
}

.ts-filters__opt:hover {
  background: var(--ts-row-hover);
}

/* The real input is the control and stays focusable; the square beside
   it is what gets drawn. Hiding it with display:none would take it out
   of the tab order, which is how a filter panel becomes keyboard-only
   decoration. */
.ts-filters__opt input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.ts-filters__mark {
  position: relative;
  flex: none;
  width: 17px;
  height: 17px;
  border: 1.5px solid var(--ts-line-2);
  border-radius: 5px;
  background: #fff;
  transition: background-color 0.15s var(--ts-ease), border-color 0.15s var(--ts-ease);
}

.ts-filters__mark--radio {
  border-radius: 50%;
}

.ts-filters__opt input:checked + .ts-filters__mark {
  background: var(--ts-brand);
  border-color: var(--ts-brand);
}

.ts-filters__opt input:checked + .ts-filters__mark::after {
  content: '';
  position: absolute;
  left: 5px;
  top: 1.5px;
  width: 4px;
  height: 9px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

.ts-filters__opt input:checked + .ts-filters__mark--radio::after {
  left: 4px;
  top: 4px;
  width: 5px;
  height: 5px;
  border: 0;
  border-radius: 50%;
  background: #fff;
  transform: none;
}

.ts-filters__opt input:focus-visible + .ts-filters__mark {
  outline: 2px solid var(--ts-brand);
  outline-offset: 2px;
}

.ts-filters__label {
  flex: 1 1 auto;
}

/* --- the category group --- */
/* NINE ROWS AND A SCROLLBAR. Twenty categories drawn in full made the
   sidebar taller than the product grid beside it and pushed Price and
   Availability below the fold, on the page where the point of the
   sidebar is that everything in it is in view at once. The fade at the
   foot is the only thing on screen that says there is more — the
   scrollbar is hidden on macOS until you touch it, and CSS cannot ask
   whether a box overflows. It is painted unconditionally because the
   list is twenty long on every page of this shop; if that ever stops
   being true it wants the `--more-end` treatment the gallery rail uses.

   `mask-image` and not a gradient overlay: the rows scroll UNDER it,
   so an opaque white strip would have to know the panel's background,
   and the panel's background is a token that changes. */
.ts-filters__scroll {
  max-height: 306px;
  margin: 0 -4px;
  padding: 0 4px 10px;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--ts-line-2) transparent;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 26px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 26px), transparent);
}

.ts-filters__scroll::-webkit-scrollbar {
  width: 6px;
}

.ts-filters__scroll::-webkit-scrollbar-thumb {
  border-radius: 3px;
  background: var(--ts-line-2);
}

/* On a category archive the rows are links to the other archives, not
   radios — see the note in template-parts/filters.php. They keep the
   same box so the panel does not change shape between the shop and a
   category. */
.ts-filters__opt--link {
  text-decoration: none;
}

.ts-filters__opt--link[aria-current='page'] {
  background: var(--ts-bg-3);
  color: var(--ts-brand);
  font-weight: 600;
}

/* The category's own thumbnail, the same picture as the mega menu's.
   A fixed square with a tinted ground behind it, so a category whose
   image has not been set yet still lines its name up with the rest. */
.ts-filters__thumb {
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 8px;
  object-fit: cover;
  background: var(--ts-bg-3);
}

.ts-filters__list--cats .ts-filters__opt {
  gap: 10px;
  padding: 5px 8px;
}

.ts-filters__n {
  font-size: 1rem;
  color: var(--ts-muted-2);
}

/* --- colour checkboxes, drawn as the swatches they are --- */
.ts-filters__swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.ts-filters__swatch {
  display: grid;
  place-items: center;
  cursor: pointer;
}

.ts-filters__swatch input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.ts-filters__dot {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--ts-bg-3);
  box-shadow: inset 0 0 0 1px rgba(31, 27, 29, 0.18);
  transition: box-shadow 0.15s var(--ts-ease);
}

.ts-filters__swatch input:checked + .ts-filters__dot {
  box-shadow: inset 0 0 0 1px rgba(31, 27, 29, 0.18), 0 0 0 2px #fff,
    0 0 0 3.5px var(--ts-brand);
}

.ts-filters__swatch input:focus-visible + .ts-filters__dot {
  outline: 2px solid var(--ts-brand);
  outline-offset: 3px;
}

/* The name is for screen readers and for the tooltip; the chip is the
   visible label. Two dozen colour names in a 260px column is a list
   nobody scans. */
.ts-filters__swatch-name {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- price --- */
.ts-filters__price {
  display: flex;
  align-items: center;
  gap: 8px;
}

.ts-filters__price label {
  flex: 1 1 0;
  min-width: 0;
}

.ts-filters__price input {
  width: 100%;
  min-height: 38px;
  padding: 0 10px;
  border: 1px solid var(--ts-line-2);
  border-radius: var(--ts-r-sm);
  background: #fff;
  color: var(--ts-text);
  font: inherit;
  font-size: 1.0833rem;
}

.ts-filters__dash {
  color: var(--ts-muted-2);
}

.ts-filters__hint {
  margin: 8px 0 0;
  font-size: 0.9583rem;
  line-height: 1.5;
  color: var(--ts-muted);
}

.ts-filters__actions {
  display: grid;
  gap: 8px;
  padding-top: 14px;
  border-top: 1px solid var(--ts-line);
  margin-top: 12px;
}

.ts-filters__apply {
  width: 100%;
  justify-content: center;
}

/* With JavaScript the panel applies on change, so the button is a
   fallback rather than the way in. It stays in the DOM (and in the
   drawer, where confirming a sheet is the expected gesture) but leaves
   the desktop sidebar, where it was a button under a panel that had
   already applied itself. */
html.ts-js .ts-filters:not(.is-open) .ts-filters__apply {
  display: none;
}

.ts-filters__reset,
.ts-filters__clear {
  display: inline-block;
  text-align: center;
  font-size: 1.0833rem;
  color: var(--ts-muted);
  text-decoration: underline;
}

.ts-filters__reset:hover,
.ts-filters__clear:hover {
  color: var(--ts-brand);
}

/* --- pagination ---

   WOO'S PAGINATION IS ONE JOINED BOX AND EVERY PART OF IT HAS TO BE
   UNDONE. Its stylesheet draws `ul` as a bordered strip with the right
   border removed, each `li` as a floated cell with a divider, and each
   link as a `display: block` with `padding: .5em` — a grey segmented
   control from 2013. Under this theme's rules it came out as three
   cramped cells with the current page squashed into a pink oval.

   The selectors are long because Woo's are:
   `.woocommerce nav.woocommerce-pagination ul li a` is (0,3,2), and
   anything shorter loses however late this file loads. Both the scoped
   and unscoped forms are listed because the archive template prints the
   nav itself and its `.woocommerce` ancestor depends on the page.

   WordPress's own `the_posts_pagination` (the search results page)
   prints `.pagination .nav-links` instead, so that gets the same
   treatment and the two look identical. */
.woocommerce-pagination,
nav.woocommerce-pagination,
.ts-collection .pagination {
  margin-top: 34px;
  padding-top: 22px;
  border-top: 1px solid var(--ts-line);
  text-align: center;
}

.woocommerce nav.woocommerce-pagination ul,
nav.woocommerce-pagination ul,
.ts-collection .pagination .nav-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  margin: 0;
  padding: 0;
  border: 0;
  white-space: normal;
  list-style: none;
}

.woocommerce nav.woocommerce-pagination ul li,
nav.woocommerce-pagination ul li {
  display: block;
  float: none;
  margin: 0;
  padding: 0;
  border: 0;
  overflow: visible;
}

.woocommerce nav.woocommerce-pagination ul li a,
.woocommerce nav.woocommerce-pagination ul li span,
nav.woocommerce-pagination ul li a,
nav.woocommerce-pagination ul li span,
.ts-collection .pagination .page-numbers {
  display: grid;
  place-items: center;
  min-width: 40px;
  height: 40px;
  padding: 0 12px;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r-sm);
  background: #fff;
  color: var(--ts-text-2);
  font-size: 1.1667rem;
  font-weight: 500;
  line-height: 1;
  text-decoration: none;
  transition: background-color 0.18s var(--ts-ease), border-color 0.18s var(--ts-ease),
    color 0.18s var(--ts-ease);
}

.woocommerce nav.woocommerce-pagination ul li span.current,
nav.woocommerce-pagination ul li span.current,
.ts-collection .pagination .page-numbers.current {
  background: var(--ts-brand);
  border-color: var(--ts-brand);
  color: var(--ts-on-brand);
}

.woocommerce nav.woocommerce-pagination ul li a:hover,
.woocommerce nav.woocommerce-pagination ul li a:focus,
nav.woocommerce-pagination ul li a:hover,
.ts-collection .pagination a.page-numbers:hover {
  background: var(--ts-bg-2);
  border-color: var(--ts-brand);
  color: var(--ts-brand);
}

.woocommerce nav.woocommerce-pagination ul li a.next,
.woocommerce nav.woocommerce-pagination ul li a.prev,
nav.woocommerce-pagination ul li a.next,
nav.woocommerce-pagination ul li a.prev,
.ts-collection .pagination .next,
.ts-collection .pagination .prev {
  padding: 0 18px;
}

.woocommerce nav.woocommerce-pagination ul li span.dots,
nav.woocommerce-pagination ul li span.dots,
.ts-collection .pagination .dots {
  border-color: transparent;
  background: none;
  color: var(--ts-muted-2);
}

/* ---------- 4. Product cards and gift-pack cards ------------- */
/* THE BUTTON ROW HAS TO LINE UP ACROSS A ROW OF CARDS. `.ts-pcard__body`
   is already a flex column that grows, but nothing pushed the button to
   its end — so a card with no tagline put its Add to Cart 20px higher
   than its neighbours, and a row of four read as four different
   components rather than one grid. The price is the last thing that
   varies in height, so the gap goes above the button. */
.ts-pcard__price {
  margin-bottom: 0;
}

.ts-pcard__add {
  margin-top: auto;
}

/* WHAT AN ADD LOOKS LIKE NOW THAT NOTHING POPS UP. There used to be a
   dialog under the header offering View cart / Check out / Continue
   shopping — three decisions about something the shopper had already
   done, covering the next product they were reaching for. The button
   answers instead, in the place the thumb already is, and the header
   pill counts up beside it.

   The width is pinned in JS before the label swaps (`btn.style.minWidth`)
   because "Add to Cart" and "Added" are different lengths and a card
   that resizes under a finger is worse than no feedback at all. */
/* THE HOVER ARM IS NOT OPTIONAL AND IT IS NOT A DUPLICATE. A tap leaves
   the pointer sitting on the button, so `.ts-pcard__add:hover:not([disabled])`
   in ts-glow.css — (0,3,0), the same weight as a two-class rule here —
   is live for the whole second the confirmation is on screen. Written as
   one class this lost the tie about half the time, and the button that
   said "Added" was still brand pink. Both arms, and the second one
   matches Woo's shape exactly. */
.ts-pcard__add.is-added,
.ts-pcard__add.is-added:hover:not([disabled]),
.ts-pcard__add.is-added:focus-visible {
  background: var(--ts-mint);
  color: #fff;
}

/* Titles are clamped to two lines by ts-glow.css, but the tagline under
   them is not, and a three-line tagline pushed one card taller than the
   rest. Two lines, then ellipsis — the full text is on the product
   page, which is where someone reading it is going anyway. */
.ts-pcard__meta {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* --- gift packs --- */
/* THE CARD IS NO LONGER ONE ANCHOR. It was `<a>` wrapping the whole
   tile, which is why it had no Add to cart: a <form> cannot be nested
   inside an <a>, and a <button> inside one is a browser's choice of
   which action fires. The tile is now a div; the image and the title
   are the links, and the button is a real control beside them. */
/* THESE SELECTORS ARE TWO CLASSES DEEP ON PURPOSE. ts-critical.css is
   an inline <style> printed LAST in <head> (see ts_critical_css() in
   inc/assets.php), so it lands after every <link> including this file
   and wins any tie. It carries a verbatim copy of
   `.ts-packs__body { display: block }`, which is exactly the rule the
   button layout below has to replace. One extra class settles it
   without touching the critical slice. */
.ts-packs .ts-packs__card {
  display: flex;
  flex-direction: column;
}

.ts-packs__card .ts-packs__media,
.ts-packs__card .ts-packs__name {
  text-decoration: none;
  color: var(--ts-text);
}

.ts-packs__card .ts-packs__name:hover {
  color: var(--ts-brand);
}

.ts-packs .ts-packs__card .ts-packs__body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  padding: 0 14px 16px;
  text-align: center;
}

/* pushes the button to the bottom edge so six cards with titles of
   different lengths still line their buttons up */
.ts-packs__card .ts-packs__buy {
  margin-top: auto;
  padding-top: 12px;
}

.ts-packs__add {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  width: 100%;
  padding: 10px 12px;
  border: 0;
  border-radius: var(--ts-r-sm);
  background: var(--ts-pack-accent, var(--ts-brand));
  color: #fff;
  font-family: inherit;
  font-size: 1.0667rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: filter 0.2s var(--ts-ease);
}

.ts-packs__add:hover:not([disabled]) {
  filter: brightness(0.92);
}

/* A sold-out pack. The tinted card backgrounds are pale, so a pale grey
   fill made the disabled button read as a line of text rather than a
   control that is switched off — the border is what says "button". */
.ts-packs__add[disabled] {
  background: rgba(255, 255, 255, 0.6);
  border: 1.5px solid var(--ts-line);
  color: var(--ts-muted);
  cursor: not-allowed;
}

.ts-packs__add.is-busy {
  opacity: 0.72;
  pointer-events: none;
}

.ts-packs__add svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ts-packs__card form {
  margin: 0;
}

/* the whole tile still lifts on hover, but only the links and the
   button are clickable */
.ts-packs__card:hover {
  transform: translateY(-4px);
}

/* ---------- 5. Cart ------------------------------------------ */
.ts-cart {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  gap: clamp(20px, 2.6vw, 36px);
  align-items: start;
}

.ts-cart__list {
  display: grid;
  gap: 12px;
}

/* One card per line. The columns are named here rather than left to
   auto-placement so the remove button can sit in the corner on a phone
   without a second grid definition. */
.ts-cart__row {
  display: grid;
  grid-template-columns: 84px minmax(0, 1fr) auto auto 32px;
  align-items: center;
  gap: 14px;
  padding: 14px;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r);
  background: #fff;
}

.ts-cart__media {
  width: 84px;
  height: 84px;
  border-radius: var(--ts-r-sm);
  background: var(--ts-bg-2);
  overflow: hidden;
}

.ts-cart__media a,
.ts-cart__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.ts-cart__name {
  margin: 0;
  font-size: 1.1667rem;
  font-weight: 600;
  line-height: 1.4;
}

.ts-cart__name a {
  color: var(--ts-text);
  text-decoration: none;
}

.ts-cart__name a:hover {
  color: var(--ts-brand);
}

.ts-cart__meta,
.ts-cart__meta p {
  margin: 4px 0 0;
  font-size: 1rem;
  color: var(--ts-muted);
}

.ts-cart__meta dl,
.ts-cart__meta dt,
.ts-cart__meta dd {
  display: inline;
  margin: 0;
  font-size: inherit;
}

.ts-cart__meta dt::after {
  content: ' ';
}

.ts-cart__unit {
  margin: 6px 0 0;
  font-size: 1rem;
  color: var(--ts-muted);
}

.ts-cart__subtotal {
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--ts-text);
  white-space: nowrap;
}

.ts-cart__remove a.remove {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--ts-bg-2);
  color: var(--ts-muted);
  text-decoration: none;
  font-size: 1.5rem;
  line-height: 1;
  transition: background-color 0.18s var(--ts-ease), color 0.18s var(--ts-ease);
}

.ts-cart__remove a.remove:hover {
  background: var(--ts-brand);
  color: #fff;
}

.ts-cart__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--ts-line);
}

.ts-cart__coupon {
  display: flex;
  gap: 8px;
  flex: 1 1 260px;
  max-width: 380px;
}

.ts-cart__coupon input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--ts-line-2);
  border-radius: var(--ts-r-sm);
  background: #fff;
  font: inherit;
  font-size: 1.0833rem;
}

.ts-cart__actions-right {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-left: auto;
}

.ts-cart__keep {
  font-size: 1.0833rem;
  color: var(--ts-muted);
  text-decoration: underline;
}

.ts-cart__keep:hover {
  color: var(--ts-brand);
}

/* THE UPDATE CART BUTTON IS PRINTED AND THEN HIDDEN, AND THE ORDER OF
   THOSE TWO MATTERS. The stepper saves on its own now — see the
   cartAutoSave block in assets/js/ts-shop.js — so for anyone with
   JavaScript the button is a control that has nothing left to do. It is
   still in the markup because `update_cart` is the only way a typed
   quantity reaches WC_Form_Handler with the script blocked or still
   loading, and this form has always been a real form.

   `html.ts-js` is set by an inline script in <head> (ts_head_boot() in
   inc/assets.php) before anything paints, so the button never appears
   for the people who do not need it. Written the other way round —
   hidden by default, revealed by script — it would flash on every cart
   load, which is the one thing worse than an extra button. */
html.ts-js .ts-cart__update {
  display: none;
}

/* A line that is being saved. Deliberately quiet: the numbers are about
   to move on their own, and a spinner over a two-hundred-millisecond
   request reads as trouble rather than as progress. The row is still
   readable and still scrolls; only the controls stop taking input, so a
   second tap cannot race the answer to the first. */
.ts-cart__row.is-saving {
  opacity: 0.62;
}

.ts-cart__row.is-saving .ts-qty,
.ts-cart__row.is-saving .ts-cart__remove {
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .ts-cart__row {
    transition: opacity 0.16s var(--ts-ease);
  }
}

.ts-cart__side {
  position: sticky;
  top: 84px;
}

/* WOO FLOATS THE TOTALS PANEL AT 48% AND THAT IS 48% OF THIS COLUMN.
   `.woocommerce .cart-collaterals .cart_totals { float: right; width: 48% }`
   is (0,3,0), so inside the 360px sidebar the summary rendered 173px
   wide — "Order summary" on two lines, "Proceed to checkout" on two
   lines, and the cash-on-delivery note clipped off the bottom. Four
   classes to beat three, and both the `.woocommerce` and
   `.woocommerce-page` forms of Woo's selector are covered because which
   one matches depends on the page. */
.ts-cart .cart-collaterals,
.woocommerce .ts-cart .cart-collaterals,
.woocommerce-page .ts-cart .cart-collaterals {
  width: auto;
  float: none;
  margin: 0;
}

.woocommerce .ts-cart .cart-collaterals .cart_totals,
.woocommerce-page .ts-cart .cart-collaterals .cart_totals,
.ts-cart .cart-collaterals .cart_totals {
  width: 100%;
  max-width: none;
  float: none;
  margin: 0;
  text-align: left;
}

/* Same story for the form on the left. */
.woocommerce .ts-cart form.woocommerce-cart-form,
.ts-cart form.woocommerce-cart-form {
  width: auto;
  float: none;
  margin: 0;
}

/* ---------- 6. Checkout -------------------------------------- */
/* `form.ts-checkout` rather than `.ts-checkout`, and the same on every
   breakpoint below. The element is a <form> that WooCommerce also
   targets as `form.checkout`, so a single-class selector here is one
   step below anything Woo writes and loses ties it should win. This is
   what the old ts-woo.css grid beat before it was removed. */
form.ts-checkout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: clamp(20px, 2.6vw, 40px);
  align-items: start;
}

/* THE COLUMNS ARE PLACED EXPLICITLY, NOT BY SOURCE ORDER. The summary
   panel comes FIRST in the markup because on a phone it belongs above
   the fields and DOM order is the only ordering that survives with CSS
   off. Left to auto-placement that put it in column 1 on desktop too —
   the order summary in the wide left column and the address form
   squeezed into 380px on the right, which is the layout backwards. */
.ts-checkout__main {
  grid-column: 1;
  grid-row: 1;
  min-width: 0;
}

.ts-checkout__side {
  position: sticky;
  top: 84px;
  min-width: 0;
  display: grid;
  gap: 14px;
  align-content: start;
  grid-column: 2;
  grid-row: 1;
}

.ts-checkout__summary {
  padding: 20px;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r-lg);
  background: var(--ts-surface-2);
}

/* On desktop the payment block is the bottom half of the same panel —
   one border around summary + pay would be ideal, but they are two
   elements so the seam is drawn instead of faked. */
.ts-checkout__pay {
  padding: 20px;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r-lg);
  background: var(--ts-surface-2);
}

/* The payment block draws a divider above itself so it separates from
   the totals when the two sit together. Inside `.ts-checkout__pay` it
   is the only thing in the card, so that divider was a stray rule
   floating at the top of an otherwise empty panel. The `#payment`
   form is needed because Woo sets the same properties through the id. */
.ts-checkout__pay .woocommerce-checkout-payment,
.ts-checkout__pay #payment,
.woocommerce-checkout .ts-checkout__pay #payment {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

/* the collapsed strip is a phone control; §13 turns it on */
.ts-checkout__summary-toggle {
  display: none;
  width: 100%;
  align-items: center;
  gap: 10px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ts-text);
  font: inherit;
  cursor: pointer;
}

.ts-checkout__summary-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 1.1667rem;
  font-weight: 600;
}

.ts-checkout__summary-label svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: var(--ts-brand);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ts-checkout__summary-n {
  font-size: 1rem;
  font-weight: 400;
  color: var(--ts-muted);
}

.ts-checkout__summary-total {
  margin-left: auto;
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--ts-brand);
}

/* THE STRIP STARTS EXPANDED AND THE MODIFIER IS THE CLOSED STATE,
   which is the opposite of how this began. The summary used to be the
   first thing on a phone, so collapsing it by default was what kept the
   form above the fold; it now sits BELOW the form (§13) where a shopper
   reaches it having already typed their address, and the one thing they
   want at that point is to see what they are about to pay for. The
   toggle stays, because a ten-item cart between the form and the Place
   order button is a scroll nobody asked for. */
.ts-checkout__summary-caret {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: var(--ts-muted);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transform: rotate(180deg);
  transition: transform 0.2s var(--ts-ease);
}

.ts-checkout--summary-closed .ts-checkout__summary-caret {
  transform: rotate(0deg);
}

.ts-checkout__h {
  margin: 0 0 14px;
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--ts-text);
}

/* --- the field columns --- */
/* The selector is long because ts-woo.css §7 still sets this box's gap
   through `.woocommerce-checkout .col2-set`, and a bare
   `.ts-checkout__fields` loses that tie however late this file loads —
   the same (0,2,0) problem as the `.form-row-first` widths below. */
.woocommerce-checkout form.ts-checkout .ts-checkout__fields {
  display: grid;
  gap: 16px;
}

/* `display: contents` DISSOLVES WOO'S TWO COLUMNS, IT DOES NOT HIDE
   THEM. `.col-1` and `.col-2` are WooCommerce's own wrappers and other
   code reaches for them, so they stay in the markup — but the boxes
   that matter here are the four panels inside them, and only by
   promoting those to grid items of `.ts-checkout__fields` do they all
   sit in one stack with one gap. Left as real boxes, the gap fell
   between the columns and the panels inside a column touched. Same
   trick, and the same reason, as `.ts-checkout__side` in §13. */
.woocommerce-checkout .col2-set .col-1,
.woocommerce-checkout .col2-set .col-2 {
  width: auto;
  float: none;
  display: contents;
}

/* --- each group of fields is a card --------------------------
   The summary and the payment block on the other side are bordered
   cards on a tint; before this the whole left half was form rows on
   the page ground, and the checkout read as one finished column beside
   one unfinished one. White rather than the summary's `--ts-surface-2`
   because the delivery-rate box inside is that tint and would have
   disappeared into it. */
.ts-checkout__panel {
  padding: clamp(16px, 1.8vw, 22px);
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r-lg);
  background: #fff;
  box-shadow: var(--ts-sh-1);
}

/* The two that hold a single tickbox and whatever it opens. */
.ts-checkout__panel--tight {
  padding: 15px 16px;
}

/* `.ts-checkout__pay .ts-checkout__h2` rather than the class on its own:
   ts-woo.css §7 sets `.woocommerce-checkout h3 { margin: 0 0 14px }` at
   (0,1,1), and body_class() puts `woocommerce-checkout` on this page, so
   a single class here would lose the margin. */
.ts-checkout__pay .ts-checkout__h2,
.woocommerce-billing-fields h3,
.woocommerce-shipping-fields h3,
.woocommerce-additional-fields h3 {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0 0 4px;
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--ts-text);
}

.ts-checkout__h2 .optional {
  font-size: 1.0833rem;
}

/* A heading with nothing but fields under it wants the fields nearer
   than the 16px `.ts-checkout__note` leaves after itself. The 4px above
   is sized for a heading with that note under it; these two have the
   control directly beneath instead. */
.woocommerce-additional-fields .ts-checkout__h2 {
  margin-bottom: 10px;
}

.ts-checkout__pay .ts-checkout__h2 {
  margin-bottom: 14px;
}

/* --- a tickbox that opens a block ----------------------------
   "Ship to a different address?" and "Create an account?" are the same
   control twice: an offer, off by default, that reveals a set of fields.
   WooCommerce renders the first inside an `<h3>` — a 13px browser
   tickbox at heading size — and the second as a bare label with a
   password field hanging underneath it. Both are drawn here as one row:
   a real 20px box, the question, and a line saying what happens if it
   is left alone. */
/* THE SELECTORS CARRY THE FORM. "Create an account?" is a `.form-row`,
   and further down this section `.woocommerce-checkout .form-row` and
   `.woocommerce-checkout .form-row label` set a bottom margin and
   `display: block` — at (0,2,0) and (0,2,1), later in the file, they
   beat a one- or two-class rule here and stacked the tickbox on its own
   line above its own question. "Ship to a different address?" is not a
   form row and looked right, which is what made it look like two
   different controls. */
.woocommerce-checkout form.ts-checkout .ts-checkout__toggle {
  margin: 0;
}

.woocommerce-checkout form.ts-checkout .ts-checkout__toggle label.checkbox {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  margin: 0;
  font-weight: 400;
  cursor: pointer;
}

.ts-checkout__toggle input[type='checkbox'] {
  flex: none;
  width: 20px;
  height: 20px;
  margin: 1px 0 0;
  accent-color: var(--ts-brand);
  cursor: pointer;
}

.ts-checkout__toggle b {
  display: block;
  font-size: 1.1667rem;
  font-weight: 600;
  line-height: 1.35;
  color: var(--ts-text);
}

.ts-checkout__toggle i {
  display: block;
  margin-top: 2px;
  font-size: 1rem;
  font-style: normal;
  line-height: 1.45;
  color: var(--ts-muted);
}

/* WHAT THE TICKBOX OPENS STARTS CLOSED, IN CSS AS WELL AS IN JS.
   WooCommerce's checkout.js hides `div.shipping_address` and
   `div.create-account` on load and slides them open on change, which
   leaves both blocks visible for the frame before its handler runs —
   a full second address form flashing past on a slow phone. These
   declare the closed state up front; `:has()` reopens them without JS,
   and once jQuery runs its inline `display` wins over both. The
   element qualifier on `div.create-account` is not optional: the
   tickbox's own `<p>` carries the same class, and Woo's script is
   qualified the same way. */
.woocommerce-checkout form.ts-checkout .shipping_address,
.woocommerce-checkout form.ts-checkout div.create-account {
  display: none;
}

.woocommerce-checkout form.ts-checkout #ship-to-different-address:has(input:checked) + .shipping_address,
.woocommerce-checkout form.ts-checkout .create-account:has(input:checked) ~ div.create-account {
  display: block;
}

.woocommerce-checkout form.ts-checkout .shipping_address,
.woocommerce-checkout form.ts-checkout .woocommerce-account-fields div.create-account {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px dashed var(--ts-line);
}

/* The password row is the only thing in that block; Woo's `.clear`
   float-breaker after it added 20-odd px of nothing. */
.woocommerce-account-fields div.create-account .form-row:last-of-type {
  margin-bottom: 0;
}

.woocommerce-account-fields .clear {
  display: none;
}

.ts-checkout__step {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--ts-brand);
  color: #fff;
  font-size: 1.0833rem;
  font-weight: 700;
  flex: none;
}

.ts-checkout__note {
  margin: 0 0 16px;
  font-size: 1.0833rem;
  line-height: 1.55;
  color: var(--ts-muted);
}

.woocommerce-billing-fields__field-wrapper,
.woocommerce-shipping-fields__field-wrapper {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 14px;
}

/* THE GRID DOES THE COLUMNS NOW, SO WOO'S OWN WIDTHS HAVE TO GO.
   WooCommerce marks fields `form-row-first` / `form-row-last` and its
   stylesheet gives those `width: 47%; float: left`. Inside a two-column
   grid that is 47% OF A COLUMN — First name came out a 160px box in a
   357px cell on desktop and a 75px box on a phone, with the label above
   it running the full width. The form read as half-built.

   THE SELECTOR NEEDS THE ELEMENT. Woo's rule is
   `.woocommerce form .form-row-first` — two classes AND an element,
   (0,2,1). A plain `.woocommerce-checkout .form-row-first` is (0,2,0)
   and loses no matter how late this file loads, which is why the first
   attempt at this changed nothing. `form.ts-checkout` puts the element
   back and adds a class. */
.woocommerce-checkout form.ts-checkout .form-row-first,
.woocommerce-checkout form.ts-checkout .form-row-last,
.woocommerce-checkout form.ts-checkout .form-row-wide,
.woocommerce-checkout form.ts-checkout .form-row,
.woocommerce form.ts-checkout .form-row-first,
.woocommerce form.ts-checkout .form-row-last,
.woocommerce form.ts-checkout .form-row-wide,
.woocommerce form.ts-checkout .form-row {
  width: auto;
  float: none;
  clear: none;
}

/* WHICH ROWS TAKE THE WHOLE LINE, AND WHY THE LIST IS SHORT NOW.
   The form is seven fields (ts_checkout_field_order() in
   inc/woo-shop.php) and they pair off as:

     Full name                          whole line
     Phone            | Email           a pair
     Country                            whole line, one option
     Province         | City            a pair — the two that pick the rate
     Full address                       whole line
     Order notes                        whole line

   A full address on half a line is how "House 12, Block 4,
   Gulshan-e-Iqbal" ends up in a 180px box; a province dropdown on a
   whole one is a 700px bar holding the word "Sindh", which is what the
   desktop form looked like when every row was `form-row-wide`.

   `#billing_email_field` USED TO BE IN THIS LIST AND HAD TO COME OUT.
   An id is (1,1,0) and beats the `.form-row-last` that now pairs email
   with the phone, so leaving it here would have kept email full width
   and stranded the phone beside nothing. The two ids that remain are
   belt-and-braces on rows that also carry `form-row-wide`. */
.woocommerce-checkout .form-row-wide,
.woocommerce-checkout #billing_address_1_field,
.woocommerce-checkout #order_comments_field {
  grid-column: 1 / -1;
}

/* ...and the pairs keep their column. */
.woocommerce-checkout .form-row-first,
.woocommerce-checkout .form-row-last {
  grid-column: span 1;
}

.woocommerce-checkout .form-row {
  margin: 0 0 14px;
}

/* THE LABELS ARE THE HALF OF THIS FORM NOBODY STYLED. They inherited
   base.css's 19px/700 body rule, so "Phone number" was set at nearly the
   size of the panel heading above it and in the same weight — ten
   headings down the left column, and the delivery card read as a
   different design from the summary beside it. 13px semibold in the
   secondary text colour is the same label the account card and the
   contact form use. */
.woocommerce-checkout .form-row label,
.woocommerce-checkout form .form-row label {
  display: block;
  margin: 0 0 5px;
  font-size: 1.0833rem;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.01em;
  color: var(--ts-text-2);
}

/* "(optional)", which Woo prints inside the label of every field that
   is not required. It is a note about the field, not part of its name. */
.woocommerce-checkout .form-row label .optional {
  font-weight: 500;
  color: var(--ts-muted-2);
}

/* WooCommerce wraps every field in `<span class="woocommerce-input-wrapper">`,
   and a <span> is inline — so `width: 100%` on the input inside resolved
   against a box that had already shrink-wrapped to nothing in
   particular. First name and Last name came out 160px wide in 357px
   columns, and the form read as a draft. */
.woocommerce-checkout .woocommerce-input-wrapper {
  display: block;
  width: 100%;
}

.woocommerce-checkout .woocommerce-input-wrapper > .select2-container {
  width: 100% !important;
}

/* THE COUNTRY FIELD IS THE ONE SELECT WITHOUT SELECT2 BEHIND IT.
   Pakistan is the only country the shop ships to, so the dropdown has a
   single option — and WooCommerce's country-select.js skips enhancing a
   one-option select. Every other select on the page is a select2 widget
   styled above; this one stayed a bare `<select>`, inherited 19px/700
   from base.css and shrank to its content: an 88px box shouting
   "Pakistan" in the middle of the address form.

   Three classes and an element so it clears Woo's own `form .form-row
   select` rules, and applied to every select in the form so a future
   second country, or any select2 fallback, lands in the same box. */
.woocommerce-checkout form.ts-checkout select,
.woocommerce form.ts-checkout select,
.woocommerce-checkout form.ts-checkout .woocommerce-input-wrapper select {
  width: 100%;
  min-height: 48px;
  padding: 0 40px 0 14px;
  border: 1px solid var(--ts-line-2);
  border-radius: var(--inputs-radius, 10px);
  background-color: #fff;
  background-image: var(--ts-caret);
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 18px;
  color: var(--ts-select-text);
  font-family: inherit;
  font-size: 1.1667rem;
  font-weight: 400;
  line-height: 1.4;
  appearance: none;
}

/* A field WooCommerce prints as plain text rather than a control — it
   does this for a single-option country on some configurations. */
.woocommerce-checkout .form-row strong {
  display: block;
  font-size: 1.1667rem;
  font-weight: 500;
  color: var(--ts-text);
}

/* Woo marks a required field with a bare asterisk, and its own
   stylesheet gives that a dotted underline and a red `border-bottom`
   through `abbr[title]`. A dotted line under a star is a hover
   affordance no touch device can use. */
.woocommerce-checkout .required {
  color: var(--ts-brand);
  text-decoration: none;
  border: 0;
}

.woocommerce-checkout .optional {
  font-weight: 400;
  color: var(--ts-muted-2);
}

/* THE WHOLE FIELD, NOT JUST ITS HEIGHT. This used to set `min-height`
   and a transition and leave the border, padding and radius to
   ts-woo.css §7 — which lists text, email, tel, textarea and select and
   NOT password. So "Create account password", the one field on the
   checkout typed by somebody who has decided to come back, was the one
   field with no border on it: a browser-default box in the middle of a
   themed form. Every type the form can contain is named here now, and
   the values are the same ones the log-in card uses. */
.woocommerce-checkout input[type='text'],
.woocommerce-checkout input[type='email'],
.woocommerce-checkout input[type='tel'],
.woocommerce-checkout input[type='number'],
.woocommerce-checkout input[type='password'],
.woocommerce-checkout textarea,
.woocommerce-checkout select {
  width: 100%;
  min-height: 48px;
  padding: 11px 14px;
  border: 1px solid var(--ts-line-2);
  border-radius: var(--inputs-radius, 10px);
  background-color: var(--ts-select-bg);
  color: var(--ts-select-text);
  font-family: inherit;
  font-size: 1.1667rem;
  line-height: 1.4;
  transition: border-color 0.18s var(--ts-ease), box-shadow 0.18s var(--ts-ease);
  -webkit-appearance: none;
  appearance: none;
}

.woocommerce-checkout input::placeholder,
.woocommerce-checkout textarea::placeholder {
  color: var(--ts-muted-2);
}

/* THE PADDING NEEDS THE LONGER ADDRESS. Colour, border and radius come
   from the variables in §0 now, but `.woocommerce form .form-row
   .input-text` also carries `padding: .5em` at (0,3,1), and 8px in a
   48px box puts the text hard against the left edge while every other
   field on the site sits at 14px. Matching Woo's shape wins on source
   order. `select` is deliberately not in here: it has its own rule
   above with room reserved for the caret. */
.woocommerce-checkout form .form-row .input-text,
.woocommerce-checkout form .form-row textarea {
  padding: 11px 14px;
}

/* WOO'S OWN SHOW/HIDE EYE, WHICH IS NOT IN ANY TEMPLATE. woocommerce.js
   wraps every password field on a Woo form — here that is "Create
   account password" and the one in the log-in panel — in
   `<span class="password-input">` and appends
   `<button class="show-password-input">` to it, after the page has
   loaded. Untouched it inherits `--wc-form-color-text`, so it was a
   near-black glyph pressed against the right edge of the field with no
   tap target around it. See the longer note in
   assets/css/ts-account.css §5. */
.woocommerce-checkout form .form-row .password-input .input-text {
  padding-right: 48px;
}

.woocommerce-checkout form .show-password-input {
  right: 4px;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--ts-r-sm);
  color: var(--ts-muted-2);
}

.woocommerce-checkout form .show-password-input:hover {
  background: var(--ts-brand-wash);
}

/* The glyph is a background image with its colour baked into the data
   URI, so `color` cannot reach it and the picture has to be swapped —
   see --ts-eye in ts-tokens.css. */
.woocommerce-checkout form .show-password-input::before {
  width: 20px;
  height: 20px;
  background-image: var(--ts-eye);
  background-repeat: no-repeat;
  background-position: center;
  background-size: 20px 20px;
}

.woocommerce-checkout form .show-password-input.display-password::before {
  background-image: var(--ts-eye-off);
}

/* The order note is a paragraph, not a line, and the rule above would
   otherwise hand it the same 48px box as the postcode. */
.woocommerce-checkout textarea,
.woocommerce-checkout form .form-row textarea {
  min-height: 96px;
  padding: 12px 14px;
  resize: vertical;
}

.woocommerce-checkout input:focus,
.woocommerce-checkout textarea:focus,
.woocommerce-checkout select:focus {
  outline: none;
  border-color: var(--ts-brand);
  box-shadow: 0 0 0 3px var(--ts-brand-wash);
}

.woocommerce-checkout .woocommerce-invalid input,
.woocommerce-checkout .woocommerce-invalid select,
.woocommerce-checkout .woocommerce-invalid .select2-container .select2-selection--single {
  border-color: #c62828;
}

.woocommerce-checkout .woocommerce-validated input {
  border-color: rgba(47, 138, 78, 0.5);
}

/* select2 draws the province dropdown. Three rules, because it builds
   its own box, its own text row and its own arrow, and any one left
   out is the single control on the page at a different height. */
.woocommerce-checkout .select2-container--default .select2-selection--single {
  height: 48px;
  padding: 10px 14px;
  border: 1px solid var(--ts-line-2);
  border-radius: var(--inputs-radius, 10px);
  background: #fff;
}

.select2-container--default .select2-selection--single .select2-selection__rendered {
  line-height: 26px;
  padding: 0;
  color: var(--ts-select-text);
}

.select2-container--default .select2-selection--single .select2-selection__arrow {
  top: 50%;
  right: 10px;
  transform: translateY(-50%);
  height: auto;
}

.select2-dropdown {
  border-color: var(--ts-line-2);
  border-radius: var(--ts-r-sm);
  overflow: hidden;
}

.select2-container--default .select2-results__option--highlighted[aria-selected] {
  background: var(--ts-brand);
}

/* --- the delivery-charge explainer --- */
/* Rates differ by Rs 150 between Karachi and Gilgit and the shopper
   cannot see the zone table, so the rule is stated rather than
   discovered when the total moves. It now sits inside
   `.ts-checkout__pay`, directly above Cash on delivery — the charge and
   the amount the courier will ask for, together. */
.ts-checkout__rates {
  margin: 0 0 16px;
  padding: 14px 16px;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r);
  background: var(--ts-surface-2);
}

/* WHITE INSIDE THE PAYMENT CARD, BECAUSE THAT CARD IS THE SAME TINT.
   `.ts-checkout__pay` is `--ts-surface-2` and so was this box, so on its
   new home the border was the only thing separating them and the panel
   read as one washed-out rectangle. Same reasoning as the note above
   `.ts-checkout__panel`, in the other direction. */
.ts-checkout__pay .ts-checkout__rates {
  background: #fff;
}

.ts-checkout__rates-h {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 8px;
  font-size: 1.0833rem;
  font-weight: 600;
  color: var(--ts-text);
}

.ts-checkout__rates-h svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: var(--ts-brand);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ts-checkout__rates ul {
  display: grid;
  gap: 3px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 1rem;
}

.ts-checkout__rates li {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  color: var(--ts-muted);
}

.ts-checkout__rates b {
  color: var(--ts-text);
  white-space: nowrap;
}

/* THE "NO ACCOUNT NEEDED" ROW IS GONE, AND SO IS EVERY RULE FOR IT.
   `.ts-checkout__account` was one dashed strip above the form; the Log
   in / Sign up buttons on the end of it and the whole `.ts-cauth` card
   they opened went earlier, for the same reason — the checkout is the
   one page where the shopper has already decided to buy something, and
   furniture above the seven fields is furniture in front of the order.
   Guest checkout is still the only path offered here; the header's
   account menu and /my-account/ carry both, and
   woocommerce/global/form-login.php is untouched because the account
   page uses it. Deleted rather than left as a dead block: this file
   loads last on every page, so a rule kept here wins ties it should
   lose. See inc/woo-shop.php §2. */

/* --- the discount-code row --- */
/* IT SITS IN THE PAYMENT CARD ABOVE PLACE ORDER, so it is a line of
   small print and not a panel — a dashed rule separates it from the
   payment method without drawing a second box inside the one it is
   already in. assets/js/ts-checkout.js §4 is what puts it there;
   WooCommerce prints it at the top of the page, and it has to be sent
   from the server in that position because it is a <form> and every
   hook beside the total is inside `form.checkout`. See the note in
   inc/woo-shop.php §2 for what nesting one form in another does.

   HIDDEN AT THE TOP WHILE THE SCRIPT IS ON. `html.ts-js` is set by an
   inline script in <head> before anything paints, so the row is never
   seen in the place it is about to leave. The child combinator is what
   makes this specific: `.woocommerce > …` is true only in the position
   WooCommerce printed it in, and stops being true the moment the node
   is moved into `.ts-checkout__pay`. With no script it stays at the
   top, visible and working — the same trade as the cart's Update
   button in §5. */
html.ts-js .woocommerce > .ts-checkout__coupon-toggle,
html.ts-js .woocommerce > .ts-checkout__coupon {
  display: none;
}

.ts-checkout__coupon-toggle {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 2px 0 12px;
  padding-top: 12px;
  border-top: 1px dashed var(--ts-line-2);
  font-size: 1rem;
  color: var(--ts-muted);
}

.ts-checkout__coupon-toggle a {
  color: var(--ts-brand);
  font-weight: 600;
  text-decoration: underline;
}

.ts-checkout__coupon {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 14px;
  padding: 12px;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r);
  background: var(--ts-surface-2);
}

/* Woo's own checkout.js does the slideToggle, so the form is inline
   `display:none` until it runs — and `display:flex` from this file
   would beat that on load. It is set only once the toggle has opened
   it, which jQuery does with an inline style of its own. */
.ts-checkout__coupon .button {
  min-height: 44px;
}

.ts-checkout__coupon .form-row {
  margin: 0;
  flex: 0 1 auto;
}

.ts-checkout__coupon .form-row:first-child {
  flex: 1 1 200px;
}

.ts-checkout__coupon input {
  width: 100%;
  min-height: 44px;
}

/* --- payment --- */
/* WOO STYLES THE PAYMENT BOX THROUGH AN ID AND THAT RAISES THE BAR.
   `.woocommerce-checkout #payment` gives the block a grey `#ebe9eb`
   background, and `#payment ul.payment_methods` / `#payment div.form-row`
   each add `padding: 1em`. At (1,2,1) and (1,2,0) no class-only
   selector reaches them — which is why the panel had a grey slab
   behind it and Place order sat 18px inside its own card, 288px wide in
   a 324px box. Matching the same shape wins on source order. */
.woocommerce-checkout #payment,
.woocommerce-checkout-payment {
  margin-top: 16px;
  padding: 0;
  padding-top: 16px;
  border-top: 1px solid var(--ts-line);
  border-radius: 0;
  background: transparent;
}

.woocommerce-checkout #payment ul.payment_methods,
.woocommerce-checkout-payment ul.payment_methods {
  padding: 0;
  border: 0;
  margin: 0 0 10px;
}

.woocommerce-checkout #payment div.form-row,
.woocommerce-checkout #payment .form-row.place-order {
  padding: 0;
  margin: 0;
}

.woocommerce-checkout-payment li.wc_payment_method {
  padding: 0;
  margin-bottom: 8px;
}

.woocommerce-checkout-payment li.wc_payment_method > label {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 13px 15px;
  border: 1.5px solid var(--ts-line);
  border-radius: var(--ts-r-sm);
  background: #fff;
  font-size: 1.1667rem;
  font-weight: 600;
  color: var(--ts-text);
  cursor: pointer;
}

.woocommerce-checkout-payment li.wc_payment_method input[type='radio']:checked + label {
  border-color: var(--ts-brand);
  background: var(--ts-brand-wash);
}

/* Woo's payment box is a grey speech bubble with a grey triangle
   pointing at the method above it — its own palette, on a pink
   storefront, at the last screen before an order. */
.woocommerce-checkout #payment div.payment_box,
.woocommerce-checkout-payment .payment_box,
.woocommerce-checkout-payment li.wc_payment_method .payment_box {
  margin: 8px 0 0;
  padding: 12px 14px;
  background: #fff;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r-sm);
  color: var(--ts-muted);
  font-size: 1.0833rem;
  line-height: 1.55;
}

.woocommerce-checkout-payment .payment_box p:last-child {
  margin-bottom: 0;
}

.woocommerce-checkout #payment div.payment_box::before,
.woocommerce-checkout-payment .payment_box::before,
.woocommerce-checkout-payment li.wc_payment_method .payment_box::before {
  display: none; /* the grey arrow, drawn for a grey bubble */
  content: none;
}

/* The radio itself. Woo hides it behind its own label styling on some
   themes; here it stays visible because with one payment method a
   checked control is the confirmation that a choice was made. */
.woocommerce-checkout-payment li.wc_payment_method > input[type='radio'] {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.woocommerce-checkout-payment li.wc_payment_method > label::before {
  content: '';
  flex: none;
  width: 18px;
  height: 18px;
  border: 2px solid var(--ts-line-2);
  border-radius: 50%;
  background: #fff;
  box-shadow: inset 0 0 0 3px #fff;
}

.woocommerce-checkout-payment li.wc_payment_method > input[type='radio']:checked + label::before {
  border-color: var(--ts-brand);
  background: var(--ts-brand);
}

/* NOTHING BELOW HERE RENDERS ON THIS STORE TODAY. The privacy
   paragraph and the terms tickbox are both inside checkout/terms.php,
   and `woocommerce_checkout_show_terms` returns false — see
   inc/woo-shop.php for why. The rules are kept because turning that
   filter back on is one line, and because they are what the block
   looked like when it was styled rather than a grey slab. */

/* The privacy-policy paragraph WooCommerce prints under the payment
   methods. It was a grey slab the width of the panel. */
.woocommerce-privacy-policy-text,
.woocommerce-privacy-policy-text p {
  margin: 10px 0 0;
  font-size: 0.9583rem;
  line-height: 1.6;
  color: var(--ts-muted-2);
}

.woocommerce-privacy-policy-text a {
  color: var(--ts-brand);
  text-decoration: underline;
}

/* THE TERMS CHECKBOX IS REQUIRED, SO IT HAS TO LOOK LIKE A CONTROL.
   WooCommerce rejects the order without it — "Please read and accept
   the terms and conditions to proceed with your order" — and it shipped
   as a 13px browser checkbox in a line of grey text directly above the
   button that fails. Given a real box and a readable line, the refusal
   stops being a surprise. */
.woocommerce-terms-and-conditions-wrapper {
  margin: 14px 0;
  padding: 12px 14px;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r-sm);
  background: #fff;
}

.woocommerce-terms-and-conditions-wrapper .form-row {
  margin: 0;
}

/* The `.woocommerce-checkout` prefix is not decoration: the label rule
   further up this section is (0,2,2), and without it this one is
   (0,2,1) and loses its `display: flex` to a `display: block` — which
   would stack a checkbox above its own sentence. */
.woocommerce-checkout .woocommerce-terms-and-conditions-wrapper label.checkbox,
.woocommerce-terms-and-conditions-wrapper label.checkbox {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 0;
  font-size: 1.0833rem;
  font-weight: 400;
  line-height: 1.5;
  color: var(--ts-text-2);
  cursor: pointer;
}

.woocommerce-terms-and-conditions-wrapper input[type='checkbox'] {
  flex: none;
  width: 18px;
  height: 18px;
  margin: 1px 0 0;
  accent-color: var(--ts-brand);
  cursor: pointer;
}

.woocommerce-terms-and-conditions-wrapper a {
  color: var(--ts-brand);
  text-decoration: underline;
}

.woocommerce-terms-and-conditions-wrapper .required {
  color: var(--ts-brand);
}

/* Woo marks an unticked box by adding this class on submit. */
.woocommerce-terms-and-conditions-wrapper .woocommerce-invalid label.checkbox {
  color: #c62828;
}

#place_order {
  width: 100%;
  min-height: 54px;
  margin-top: 6px;
  font-size: 1.3333rem;
  letter-spacing: 0.01em;
}

.ts-summary__note,
.ts-checkout__trust {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 12px 0 0;
  font-size: 1rem;
  line-height: 1.55;
  color: var(--ts-muted);
}

.ts-checkout__trust a {
  color: var(--ts-muted);
  text-decoration: underline;
  text-underline-offset: 2px;
  white-space: nowrap;
}

.ts-checkout__trust a:hover {
  color: var(--ts-brand);
}

.ts-summary__note svg,
.ts-checkout__trust svg {
  flex: none;
  width: 15px;
  height: 15px;
  margin-top: 2px;
  fill: none;
  stroke: var(--ts-mint);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Woo blocks the panel with a spinner while the rate is being fetched.
   Fading it is what tells the shopper the number they are looking at is
   about to change, rather than that nothing happened. */
.woocommerce-checkout .blockUI.blockOverlay {
  background: #fff !important;
  opacity: 0.6 !important;
}

/* ---------- 7. Shared summary rows --------------------------- */
/* One definition for the cart's totals, the checkout's review panel and
   the order-received page — they are the same three numbers and used to
   be three different tables. */
.ts-summary {
  padding: 20px;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r-lg);
  background: var(--ts-surface-2);
}

.ts-summary__title {
  margin: 0 0 14px;
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--ts-text);
}

.ts-summary__rows {
  display: grid;
  gap: 2px;
}

.ts-summary__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding: 9px 0;
  border-bottom: 1px dashed var(--ts-line);
  font-size: 1.1667rem;
  color: var(--ts-muted);
}

.ts-summary__row:last-child {
  border-bottom: 0;
}

.ts-summary__row--block {
  display: block;
}

.ts-summary__row--block > span:first-child {
  display: block;
  margin-bottom: 5px;
}

.ts-summary__v {
  font-weight: 600;
  color: var(--ts-text);
  text-align: right;
  white-space: nowrap;
}

.ts-summary__row--total {
  margin-top: 6px;
  padding-top: 13px;
  border-top: 1px solid var(--ts-line-2);
  border-bottom: 0;
  font-size: 1.3333rem;
  font-weight: 600;
  color: var(--ts-text);
}

.ts-summary__row--total .ts-summary__v,
.ts-summary__row--total .amount {
  font-size: 1.6667rem;
  font-weight: 700;
  color: var(--ts-brand);
}

/* shipping choices inside a summary row */
.ts-ship__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 5px;
}

.ts-ship__list li {
  display: flex;
  align-items: center;
  gap: 8px;
}

.ts-ship__list label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  font-size: 1.1667rem;
  color: var(--ts-text);
  cursor: pointer;
}

.ts-ship__list .amount {
  font-weight: 600;
}

.ts-ship__to {
  margin: 8px 0 0;
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ts-muted);
}

.ts-ship__to strong {
  color: var(--ts-text-2);
}

/* "Change address" opens the cart's shipping calculator. Woo appends its
   own arrow icon and the pair wrapped onto two lines inside a 360px
   summary column, which read as a broken link rather than one control. */
.woocommerce-shipping-calculator {
  margin-top: 8px;
  font-size: 1.0833rem;
}

.woocommerce-shipping-calculator > a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--ts-brand);
  text-decoration: underline;
  white-space: nowrap;
}

/* Woo appends a glyph from its own `WooCommerce` icon font after this
   link. That font is not loaded on this theme, so the codepoint fell
   back to a tofu box sitting next to "Change address" and read as a
   broken image. */
.woocommerce-shipping-calculator a.shipping-calculator-button::after,
a.shipping-calculator-button::after {
  content: none;
}

.woocommerce-shipping-calculator .shipping-calculator-form {
  display: grid;
  gap: 10px;
  margin-top: 10px;
}

.woocommerce-shipping-calculator .form-row {
  margin: 0;
}

.woocommerce-shipping-calculator input,
.woocommerce-shipping-calculator select,
.woocommerce-shipping-calculator .select2-container .select2-selection--single {
  width: 100%;
  min-height: 42px;
  padding: 8px 12px;
  border: 1px solid var(--ts-line-2);
  border-radius: var(--ts-r-sm);
  background: #fff;
  font: inherit;
  font-size: 1.0833rem;
}

/* --- the checkout's item list --- */
.ts-review {
  padding: 0;
  border: 0;
  background: none;
}

.ts-review__items {
  display: grid;
  gap: 12px;
  margin: 0 0 16px;
  padding: 0 0 14px;
  border-bottom: 1px solid var(--ts-line);
  list-style: none;
}

.ts-review__item {
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
}

.ts-review__thumb {
  position: relative;
  width: 52px;
  height: 52px;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r-sm);
  background: #fff;
  overflow: visible;
}

.ts-review__thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: inherit;
}

/* the quantity as a badge on the corner, the way a basket app shows it
   — it was a "× 2" lost inside the product name */
.ts-review__qty {
  position: absolute;
  top: -7px;
  right: -7px;
  display: grid;
  place-items: center;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--ts-brand);
  color: #fff;
  font-size: 0.9167rem;
  font-weight: 600;
  line-height: 1;
}

.ts-review__name {
  min-width: 0;
  font-size: 1.0833rem;
  line-height: 1.4;
  color: var(--ts-text-2);
}

.ts-review__name dl,
.ts-review__name dt,
.ts-review__name dd,
.ts-review__name p {
  display: inline;
  margin: 0;
  font-size: 1rem;
  color: var(--ts-muted);
}

.ts-review__total {
  font-size: 1.1667rem;
  font-weight: 600;
  color: var(--ts-text);
  white-space: nowrap;
}

/* --- the order floor, in the checkout button's place ---

   inc/min-order.php unhooks Proceed to checkout under Rs 500 and prints
   this instead. It sits where the button did, inside .ts-summary, so it
   is the last thing read in the column rather than a bar at the top of
   the page that the total has already scrolled past. */
.ts-min-order {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  border: 1px solid var(--ts-line-2);
  border-radius: var(--ts-r-sm);
  background: var(--ts-bg-3);
}

.ts-min-order__icon {
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 1px;
  fill: none;
  stroke: var(--ts-brand);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ts-min-order__copy {
  margin: 0;
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ts-muted);
}

.ts-min-order__copy b {
  display: block;
  font-size: 1.1667rem;
  font-weight: 600;
  color: var(--ts-text);
}

/* Full width and 52px to match the Proceed to checkout button it stands
   in for — see .wc-proceed-to-checkout .checkout-button in §9. Swapping one
   for the other must not move the column. */
.wc-proceed-to-checkout .ts-min-order__cta {
  width: 100%;
  min-height: 52px;
  margin-top: 10px;
  font-size: 1.25rem;
}

.wc-proceed-to-checkout .ts-min-order__cta svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ---------- 8. Quantity steppers ----------------------------- */
.ts-qty {
  display: inline-flex;
  align-items: stretch;
  width: auto;
  border: 1px solid var(--ts-line-2);
  border-radius: var(--ts-r-sm);
  background: #fff;
  overflow: hidden;
}

.ts-qty__btn {
  display: grid;
  place-items: center;
  width: 40px;
  border: 0;
  background: none;
  color: var(--ts-text-2);
  cursor: pointer;
  transition: background-color 0.15s var(--ts-ease), color 0.15s var(--ts-ease);
}

.ts-qty__btn:hover {
  background: var(--ts-row-hover);
  color: var(--ts-brand);
}

.ts-qty__btn svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}

/* The field between the buttons has no border of its own and no
   spinner: the buttons ARE the spinner, and the browser's arrows beside
   them is two controls for one number. */
.ts-product-page .ts-qty input[type='number'],
.ts-qty input[type='number'],
.ts-qty input.qty {
  width: 46px;
  min-height: 42px;
  padding: 0;
  border: 0;
  border-left: 1px solid var(--ts-line);
  border-right: 1px solid var(--ts-line);
  border-radius: 0;
  background: none;
  text-align: center;
  font-size: 1.1667rem;
  font-weight: 600;
  -moz-appearance: textfield;
  appearance: textfield;
}

.ts-qty input::-webkit-outer-spin-button,
.ts-qty input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
  opacity: 0;
}

.ts-qty input:focus {
  outline: none;
  box-shadow: none;
}

/* on the product page the stepper sits beside Add to cart */
.ts-product-page .quantity.ts-qty {
  width: auto;
  flex: none;
}

.ts-product-page .ts-qty input[type='number'] {
  min-height: 48px;
}

.ts-product-page .ts-qty__btn {
  width: 42px;
}

/* ---------- 9. Buttons, forms and notices -------------------- */
/* DAWN'S BUTTON RING, TURNED OFF EVERYWHERE IT REACHES A WOO BUTTON.
   base.css hangs an `::after` on `.button` and draws two rings on it
   from the theme's `--buttons-*` tokens:

     box-shadow: 0 0 0 1.3px rgba(255,255,255,.45), 0 0 0 1px <accent>;

   On Dawn's own pale buttons the white ring is invisible. On this
   store's solid pink it is a white hairline INSIDE the edge, so "Create
   account", "Log in" and "Place order" all read as double-bordered — a
   1px mistake that makes a primary button look unfinished, and the most
   noticeable thing on the sign-up card. The ring's only job was a focus
   affordance; `:focus-visible` below does that properly and visibly,
   which the ring never did because it was painted at all times.

   The element is in the selector because base.css writes
   `.button::after` and `.shopify-challenge__button::after` at (0,1,0) —
   a bare class here would tie and win on order, but naming the button
   makes it obvious this is aimed at controls and not at every `.button`
   -ish box in the theme. */
.woocommerce a.button::after,
.woocommerce button.button::after,
.woocommerce input.button::after,
.ts-auth .button::after,
.ts-auth button::after,
.ts-account__content .button::after,
#place_order::after,
.checkout-button::after {
  box-shadow: none;
}

.ts-auth .button:focus-visible,
.ts-account__content .button:focus-visible,
#place_order:focus-visible,
.checkout-button:focus-visible {
  outline: 2px solid var(--ts-brand);
  outline-offset: 2px;
}

/* EVERY AUTO-WIDTH <button> NEEDS THIS. Chrome gives form controls
   `field-sizing: fixed`, so a <button> is sized from a UA default
   rather than from its own text — which is why the colour swatches in
   §2 came out 38px wide with their labels clipped to one letter. Any
   button here whose width is meant to follow its label needs telling
   otherwise. Buttons with an explicit `width` (the quantity steppers,
   Place order, Apply filters) are unaffected either way, so this is
   scoped to the ones that are not. */
.ts-btn,
.ts-filters__open,
.ts-swatch,
.ts-packs__add,
.woocommerce button.button,
.woocommerce input.button,
.woocommerce-Button {
  field-sizing: content;
}

/* THE THEME HAS ONE BUTTON. `.ts-btn` is the ported one; every button
   WooCommerce prints is mapped onto it here rather than styled
   separately, which is why the cart, the checkout, the account pages
   and the 404 all matched each other and nothing else before. */
/* `.alt` IS NOT OPTIONAL IN THESE SELECTORS. WooCommerce's own
   stylesheet paints its primary buttons — Add to cart, Proceed to
   checkout, Place order — with `.woocommerce a.button.alt` and
   friends, which is three classes. `body_class()` puts `woocommerce`
   on the body of every shop page, so those rules match here even
   though no wrapper in this theme carries the class. Written without
   `.alt`, the mapping below is one class short and loses every tie:
   the product page's Add to cart came out WooCommerce purple on a pink
   storefront, which is exactly what it looked like. */
.ts-btn,
.woocommerce a.button,
.woocommerce button.button,
.woocommerce input.button,
.woocommerce a.button.alt,
.woocommerce button.button.alt,
.woocommerce input.button.alt,
.woocommerce #respond input#submit.alt,
.ts-product-page button.single_add_to_cart_button,
.woocommerce-Button,
.wc-proceed-to-checkout .checkout-button,
.woocommerce-form-login__submit,
.woocommerce-form-register__submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 46px;
  padding: 0 22px;
  border: 1.5px solid var(--ts-brand);
  border-radius: var(--buttons-radius, 10px);
  background: var(--ts-brand);
  color: var(--ts-on-brand);
  font-family: inherit;
  font-size: 1.1667rem;
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.18s var(--ts-ease), border-color 0.18s var(--ts-ease),
    color 0.18s var(--ts-ease), transform 0.18s var(--ts-ease);
}

.ts-btn:hover,
.woocommerce a.button:hover,
.woocommerce button.button:hover,
.woocommerce input.button:hover,
.woocommerce a.button.alt:hover,
.woocommerce button.button.alt:hover,
.woocommerce input.button.alt:hover,
.ts-product-page button.single_add_to_cart_button:hover,
.wc-proceed-to-checkout .checkout-button:hover,
.woocommerce-Button:hover {
  background: var(--ts-brand-700);
  border-color: var(--ts-brand-700);
  color: #fff;
  transform: translateY(-1px);
}

/* Add to cart on a variable product before a colour is chosen. Woo puts
   `.disabled` on it and its own stylesheet paints that purple too. Grey
   says "not yet"; a coloured button that does nothing says "broken". */
.woocommerce button.button.alt.disabled,
.woocommerce button.button.alt:disabled,
.woocommerce button.button.alt:disabled[disabled],
.ts-product-page button.single_add_to_cart_button.disabled,
.ts-product-page button.single_add_to_cart_button:disabled {
  background: var(--ts-bg-3);
  border-color: var(--ts-line);
  color: var(--ts-muted);
  transform: none;
  cursor: not-allowed;
}

.ts-btn svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The secondary. Woo's "Update cart", "Apply coupon" and
   "Return to shop" are all this — actions beside the main one, not
   competing with it. */
.ts-btn--ghost,
.woocommerce button.button[name='update_cart'],
.woocommerce button.button[name='apply_coupon'],
.woocommerce a.button.wc-backward {
  background: #fff;
  border-color: var(--ts-line-2);
  color: var(--ts-text);
}

.ts-btn--ghost:hover,
.woocommerce button.button[name='update_cart']:hover,
.woocommerce button.button[name='apply_coupon']:hover,
.woocommerce a.button.wc-backward:hover {
  background: var(--ts-bg-2);
  border-color: var(--ts-brand);
  color: var(--ts-brand);
}

.woocommerce button.button:disabled,
.woocommerce button.button:disabled[disabled] {
  background: var(--ts-bg-3);
  border-color: transparent;
  color: var(--ts-muted);
  transform: none;
  cursor: not-allowed;
}

.wc-proceed-to-checkout {
  margin-top: 16px;
}

.wc-proceed-to-checkout .checkout-button {
  width: 100%;
  min-height: 52px;
  font-size: 1.25rem;
}

/* --- notices ---

   ts-woo.css STYLES THESE AND LOSES, WHICH IS WHY THEY ARE RESTATED
   HERE. WordPress enqueues woocommerce.css AFTER ts-woo.css — the
   handle order is ts-woo, wc-blocks, woocommerce-layout, woocommerce,
   then ts-fixes — so ts-woo's `border: 1px solid var(--ts-line)` was
   overwritten by Woo's `border-top: 3px solid #b81c23` and the error
   notice came out with a red top edge in Woo's palette. Anything that
   has to beat woocommerce.css belongs in this file, not that one.

   The icon goes too: Woo positions a glyph from its own `WooCommerce`
   icon font absolutely at `left: 1.5em` and pads the container to
   clear it. This layout has its own padding, so the icon landed on top
   of the first word — "Please" read as "Pl?ase" — and that font is the
   same one that rendered a tofu box next to "Change address". The
   coloured left edge says which kind of notice it is. */
.woocommerce-message,
.woocommerce-info,
.woocommerce-error,
.woocommerce-notice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  border: 1px solid var(--ts-line);
  border-left-width: 3px;
  border-radius: 12px;
  background: var(--ts-surface-2);
  color: var(--ts-text-2);
}

.woocommerce-message::before,
.woocommerce-info::before,
.woocommerce-error::before,
.woocommerce-notice::before {
  content: none;
  display: none;
}

.woocommerce-message {
  border-left-color: var(--ts-mint);
}

.woocommerce-info {
  border-left-color: var(--ts-brand);
}

.woocommerce-error {
  border-left-color: #c62828;
}

/* THE NOTICE BUTTONS NEED THEIR OWN RULE, NOT THE `.woocommerce`
   MAPPING ABOVE. page.php calls wc_print_notices() itself, outside the
   `<div class="woocommerce">` the cart shortcode renders — and the cart
   page's body carries `woocommerce-cart woocommerce-page` but not a
   plain `woocommerce` class. So `.woocommerce a.button` matches none of
   these, and the only rule that reached them was ts-woo.css's
   `.woocommerce-message .button { color: var(--ts-brand) }`: brand pink
   text on a brand pink background. "View cart" was a solid pink
   rectangle with nothing readable in it. */
.woocommerce-message .button,
.woocommerce-info .button,
.woocommerce-error .button,
.woocommerce-message a.wc-forward,
.woocommerce-info a.wc-forward {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  float: none;
  margin-left: auto;
  min-height: 38px;
  padding: 0 16px;
  border: 1.5px solid var(--ts-brand);
  border-radius: var(--buttons-radius, 10px);
  background: var(--ts-brand);
  color: var(--ts-on-brand);
  font-size: 1.0833rem;
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
}

.woocommerce-message .button:hover,
.woocommerce-info .button:hover,
.woocommerce-error .button:hover {
  background: var(--ts-brand-700);
  border-color: var(--ts-brand-700);
  color: #fff;
}

/* The message itself gets the room the button does not need. */
.woocommerce-message,
.woocommerce-info {
  padding-right: 14px;
}

.woocommerce-message > .button,
.woocommerce-info > .button {
  order: 2;
}

.woocommerce-error {
  display: block;
}

/* WooCommerce gives each notice `tabindex="-1"` and focuses it after an
   add-to-cart so a screen reader announces it. base.css then draws
   Dawn's `*:focus-visible` treatment on it — a 2px dark outline offset
   by 3px plus a white halo and a soft shadow — so every confirmation
   arrived wearing a second border in a third colour, on top of its own
   coloured left edge.
   `:focus:not(:focus-visible)` was the first attempt and it does
   nothing here: Chrome DOES match `:focus-visible` on a programmatic
   focus of a tabindex="-1" element, so the negation is false and the
   ring stays. The ring is removed outright instead, which costs
   nothing — `tabindex="-1"` means no keyboard user can ever Tab here,
   so there is no focus state for the ring to communicate. */
.woocommerce-message:focus,
.woocommerce-info:focus,
.woocommerce-error:focus,
.woocommerce-message:focus-visible,
.woocommerce-info:focus-visible,
.woocommerce-error:focus-visible,
.woocommerce-notices-wrapper [tabindex='-1']:focus,
.woocommerce-notices-wrapper [tabindex='-1']:focus-visible {
  outline: none;
  box-shadow: none;
}

/* Links inside a notice were the browser default blue with a default
   underline — the only blue on the site. */
.woocommerce-message a:not(.button),
.woocommerce-info a:not(.button),
.woocommerce-error a:not(.button) {
  color: var(--ts-brand);
  font-weight: 600;
  text-decoration: underline;
}

.woocommerce-message a:not(.button):hover,
.woocommerce-info a:not(.button):hover,
.woocommerce-error a:not(.button):hover {
  color: var(--ts-brand-700);
}

.woocommerce-error li {
  margin: 0 0 4px;
}

.woocommerce-error li:last-child {
  margin-bottom: 0;
}

/* --- the empty state, shared by the cart and a filtered-to-nothing
       catalog --- */
.ts-empty {
  display: grid;
  justify-items: center;
  gap: 6px;
  padding: 46px 20px 52px;
  text-align: center;
}

.ts-empty__icon {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  margin-bottom: 6px;
  border-radius: 50%;
  background: var(--ts-bg-3);
}

.ts-empty__icon svg {
  width: 28px;
  height: 28px;
  fill: none;
  stroke: var(--ts-brand);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ts-empty__title {
  margin: 0;
  font-size: 1.8333rem;
  font-weight: 600;
  color: var(--ts-text);
}

.ts-empty__text {
  max-width: 44rem;
  margin: 0;
  font-size: 1.1667rem;
  line-height: 1.6;
  color: var(--ts-muted);
}

.ts-empty__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin-top: 14px;
}

/* ---------- 10. Header: search and account ------------------- */
/* TWO SEARCH CONTROLS, ONE JOB. The header carries a search pill AND a
   magnifier button. ts-mobile.css hides the pill under 1200px and the
   button is what is left — but above 1200px BOTH were on screen, so the
   field had a redundant magnifier sitting next to its own submit
   button. The button is now the pill's replacement rather than its
   companion: shown only where the pill is not. */
@media screen and (min-width: 1201px) {
  .ts-nav__tools [data-ts-search-link] {
    display: none;
  }
}

/* --- the search bar the magnifier opens ---
   THE MAGNIFIER USED TO BE A DEAD END. Above it is the search pill,
   hidden under 1200px by ts-mobile.css §2 because the link row needs
   the room — so on every phone the icon WAS the search, and it was a
   link to `/?s=&post_type=product`: the results template with an empty
   query, which printed "Results for “”", found nothing, and offered no
   field to type into anywhere on the page. Tapping search gave you a
   page with no search on it.

   The panel is a child of `.ts-nav` and ABSOLUTE, not fixed, so `top:
   100%` is the bottom of the bar and stays that way — `.ts-nav` is
   `position: sticky`, and a non-static position is the containing block
   for absolute descendants no matter what the element is doing about
   backdrop-filter. The drawer beside it is fixed and needs
   `--ts-bar-bottom` measured in JS for exactly the reason this does
   not; see the note in ts-mobile.css §2.

   CLOSED IS THE DEFAULT AND IT IS WRITTEN IN ts-critical.css TOO. The
   bar is above the fold, so left to this file the panel would be a form
   sitting open under the header for the moment before the stylesheet
   lands.

   `visibility` rather than `display`, so it can slide and so it is out
   of the tab order while closed — the same trade the drawer makes. */
.ts-searchbar {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 40;
  visibility: hidden;
  padding: 10px 0 14px;
  background: var(--ts-surface);
  border-bottom: 1px solid var(--ts-line);
  box-shadow: 0 18px 36px -20px rgba(31, 27, 29, 0.3);
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity 0.2s var(--ts-ease), transform 0.24s var(--ts-out),
    visibility 0s linear 0.24s;
}

.ts-nav[data-search='true'] .ts-searchbar {
  visibility: visible;
  opacity: 1;
  transform: none;
  transition: opacity 0.2s var(--ts-ease), transform 0.24s var(--ts-out),
    visibility 0s;
}

/* `position: relative` is load-bearing: `.ts-predictive` is absolute and
   hangs off its nearest positioned ancestor, the same way it does off
   `.ts-search` in ts-woo.css. */
.ts-searchbar__form {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 4px 4px 14px;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r-pill);
  background: #fff;
}

.ts-searchbar__form:focus-within {
  border-color: var(--ts-brand);
  box-shadow: 0 0 0 3px var(--ts-brand-wash);
}

/* 16px is the floor, not a preference: iOS zooms the whole page in on a
   focused field below it and never zooms back out. Root is 12px here,
   so 1.35rem is 16.2px. */
.ts-searchbar__form input[type='search'] {
  flex: 1 1 auto;
  min-width: 0;
  height: 38px;
  border: 0;
  outline: 0;
  background: transparent;
  font-family: inherit;
  font-size: 1.35rem;
  color: var(--ts-text);
}

.ts-searchbar__form input[type='search']::placeholder {
  color: var(--ts-muted-2);
}

/* THE FOCUS RING BELONGS TO THE PILL, NOT TO THE FIELD INSIDE IT.
   base.css §733 gives everything on the page a `*:focus-visible` ring —
   a white 3px band plus a soft dark halo — and a bare `outline: 0` only
   takes off half of it, so the field lit up as a hard white RECTANGLE
   floating inside the round pill. The wrapper already shows focus with
   `:focus-within` above, which is the same information drawn on the
   shape the shopper is actually looking at. */
.ts-searchbar__form input[type='search']:focus,
.ts-searchbar__form input[type='search']:focus-visible,
.ts-searchbar__form input[type='search'].focused {
  outline: 0;
  box-shadow: none;
}

/* TWO × IN A ROW, AND ONE OF THEM DOES SOMETHING ELSE. WebKit draws a
   clear button inside every `type="search"` the moment it has text, and
   it landed a few pixels from the panel's own close button — one wipes
   the query, one dismisses the search, and nothing on screen said
   which. The close button is the one that has to stay, so the native
   glyph goes; clearing is select-all and type. */
.ts-searchbar__form input[type='search']::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

.ts-searchbar__go,
.ts-searchbar__close {
  flex: none;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
}

.ts-searchbar__go {
  background: var(--ts-brand);
  color: #fff;
}

.ts-searchbar__close {
  background: var(--ts-bg-2);
  color: var(--ts-muted);
}

.ts-searchbar__close:hover {
  background: var(--ts-bg-3);
  color: var(--ts-text);
}

/* ---------- 10b. Shop by Category, as a rail ------------------ */
/* THE SECTION SHOWED FIVE OF FIFTEEN CATEGORIES AND POINTED THE REST AT
   /shop/, which is a page of products and not a page of categories. It
   is a snap-scrolling row now, carrying every category in the menu, with
   the same arrows the gift-pack rail uses — see the note at the top of
   template-parts/sections/categories.php.

   `.ts-cats--rail` is (0,2,0) against `.ts-cats` at (0,1,0), so it beats
   the grid rule AND ts-mobile.css §1's `--cols` overrides, which is what
   lets one class swap the shape at every width. `grid-template-columns:
   none` is not decoration: without it the auto-flow columns are laid
   after five explicit tracks and the row wraps into a grid again. */
.ts-cats--rail {
  --rail-gap: clamp(12px, 1.4vw, 20px);
  --rail-cols: 5;
  display: grid;
  grid-auto-flow: column;
  grid-template-columns: none;
  /* the tiles keep the width they had in the grid, so the rail reads as
     the same row it replaced rather than as a different component */
  grid-auto-columns: calc(
    (100% - (var(--rail-cols) - 1) * var(--rail-gap)) / var(--rail-cols)
  );
  gap: var(--rail-gap);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  /* the tiles lift on hover and an overflow container would clip the
     shadow — give it room rather than turning the lift off, the same
     trade .ts-packs__rail makes */
  margin-inline: -6px;
  padding-inline: 6px;
  padding-bottom: 4px;
}

.ts-cats--rail::-webkit-scrollbar {
  display: none;
}

.ts-cats--rail > .ts-cat {
  scroll-snap-align: start;
}

/* The arrows sit beside "View All Categories" rather than replacing it:
   the link goes to the shop, which is a different question. */
.ts-head__tools {
  flex: none;
  display: flex;
  align-items: center;
  gap: 14px;
}

/* A TILE AND A HALF ON A PHONE, NOT TWO. Two whole tiles fill the screen
   edge to edge and the row reads as a grid that happens to be cut off —
   there is nothing to say it moves. The half tile at the right edge is
   the entire affordance on a touch screen, where the arrows are the
   least likely thing to be used. */
@media screen and (max-width: 1200px) {
  .ts-cats--rail {
    --rail-cols: 4;
  }
}

@media screen and (max-width: 989px) {
  .ts-cats--rail {
    --rail-cols: 3;
  }
}

@media screen and (max-width: 749px) {
  .ts-cats--rail {
    --rail-cols: 2.4;
  }
}

@media screen and (max-width: 620px) {
  .ts-cats--rail {
    --rail-cols: 2.2;
  }

  /* the heading, the link and the arrows in one strip is three things
     on a 360px line — the arrows go and the flick stays */
  .ts-head__tools .ts-packs__nav {
    display: none;
  }
}

/* the same field on the results page, where it is a block of its own
   rather than a panel — see the note at the top of search.php */
.ts-collhead__search {
  max-width: 46rem;
  margin-top: 16px;
}

.ts-searchbar__go svg,
.ts-searchbar__close svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The account button. It is rendered on every viewport — the tools
   cluster is the one part of the header that does not become a drawer —
   so a shopper can reach their orders from a phone without opening the
   menu first. */
.ts-nav__tools .ts-icon-btn[data-ts-account] svg {
  stroke-width: 1.8;
}

/* --- the menu that hangs off it ---
   NO JAVASCRIPT, AND THE TRIGGER IS STILL A LINK. `:hover` opens it for
   a mouse and `:focus-within` for a keyboard, which between them covers
   every desktop visitor including one with scripting blocked. A phone
   has neither, so §13 hides the panel there outright and the drawer
   carries its own Create account row instead — the icon's link still
   goes to /my-account/, where the same two choices are the tabs on the
   card, so a thumb never reaches a dead end.

   `visibility` rather than `display`, so the panel can fade and so it is
   out of the tab order while closed. */
.ts-acct {
  position: relative;
  display: flex;
}

.ts-acct__panel {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  z-index: 70;
  width: 270px;
  padding: 16px;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r-lg);
  background: var(--ts-surface);
  box-shadow: var(--ts-sh-3, 0 18px 40px rgba(20, 8, 14, 0.14));
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 0.18s var(--ts-ease), transform 0.18s var(--ts-ease),
    visibility 0.18s;
}

/* THE BRIDGE IS LOAD-BEARING, exactly as it is on the mega menu. The
   panel hangs 10px below the icon, and without something filling that
   gap a pointer moving down from the icon leaves `.ts-acct` for a frame
   and the menu closes before it can be reached. */
.ts-acct::after {
  content: '';
  position: absolute;
  top: 100%;
  right: 0;
  width: 270px;
  height: 12px;
}

.ts-acct:hover .ts-acct__panel,
.ts-acct:focus-within .ts-acct__panel {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.ts-acct__title {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1.3;
  color: var(--ts-text);
}

.ts-acct__sub {
  margin: 4px 0 12px;
  font-size: 1.0833rem;
  line-height: 1.5;
  color: var(--ts-muted);
}

.ts-acct__cta {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 42px;
  padding: 0 14px;
  border-radius: var(--ts-r-sm);
  background: var(--ts-brand);
  color: #fff;
  font-size: 1.1667rem;
  font-weight: 600;
  text-decoration: none;
  transition: background-color 0.18s var(--ts-ease);
}

.ts-acct__cta:hover {
  background: var(--ts-brand-700);
  color: #fff;
}

.ts-acct__alt {
  display: block;
  margin-top: 10px;
  font-size: 1.0833rem;
  font-weight: 500;
  color: var(--ts-muted);
  text-align: center;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.ts-acct__alt:hover {
  color: var(--ts-brand);
}

.ts-acct__fine {
  margin: 12px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--ts-line);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ts-muted-2);
}

/* signed in: the same box, four shortcuts in it */
.ts-acct__links {
  display: grid;
  gap: 2px;
  margin-top: 10px;
}

.ts-acct__links a {
  padding: 9px 10px;
  border-radius: var(--ts-r-sm);
  color: var(--ts-text-2);
  font-size: 1.1667rem;
  font-weight: 500;
  text-decoration: none;
  transition: background-color 0.16s var(--ts-ease), color 0.16s var(--ts-ease);
}

.ts-acct__links a:hover {
  background: var(--ts-bg-2);
  color: var(--ts-brand);
}

.ts-acct__links a:last-child {
  margin-top: 4px;
  border-top: 1px solid var(--ts-line);
  border-radius: 0 0 var(--ts-r-sm) var(--ts-r-sm);
  padding-top: 12px;
  color: var(--ts-muted);
}

/* the drawer's own account row, under the links */
.ts-nav__accountrow {
  display: none; /* §13 turns it on inside the drawer */
}

.ts-nav__accountrow a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 4px;
  color: var(--ts-text);
  text-decoration: none;
  font-size: 1.3333rem;
  font-weight: 500;
}

.ts-nav__accountrow svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: var(--ts-brand);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ts-nav__accountrow small {
  display: block;
  font-size: 1rem;
  font-weight: 400;
  color: var(--ts-muted);
}

/* The drawer's Create account row. It is a filled button rather than a
   third quiet link because it is the offer, not the navigation — and on
   a phone it is the ONLY place the offer appears, since the menu on the
   header icon needs a hover the phone does not have. */
/* THE ELEMENT IS IN THE SELECTOR. `.ts-nav__accountrow a` above is
   (0,1,1) and sets the dark text colour; a bare `.ts-nav__accountrow-cta`
   is (0,1,0) and loses, which left the heading black over a pink row
   whose second line had already gone brand. */
.ts-nav__accountrow a.ts-nav__accountrow-cta {
  margin-top: 4px;
  padding: 12px 14px;
  border-radius: var(--ts-r);
  background: var(--ts-brand-wash);
  color: var(--ts-brand);
}

.ts-nav__accountrow-cta small {
  color: var(--ts-brand);
  opacity: 0.78;
}

/* ---------- 11. Footer social icons -------------------------- */
/* WAS: four bare 24px strokes in a row, at whatever colour the footer
   text happened to be, with the TikTok and WhatsApp marks drawn as
   outlines that read as scribbles at that size.
   NOW: brand glyphs (filled, from inc/template-tags.php) in circular
   buttons that pick up each brand's own colour on hover — the same
   treatment the contact panel's buttons already had. */
.ts-foot__social {
  display: flex;
  align-items: center;
  gap: 10px;
}

.ts-foot__social a {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border: 1px solid var(--ts-line);
  border-radius: 50%;
  background: #fff;
  color: var(--ts-text-2);
  text-decoration: none;
  transition: background-color 0.2s var(--ts-ease), color 0.2s var(--ts-ease),
    border-color 0.2s var(--ts-ease), transform 0.2s var(--ts-ease);
}

.ts-foot__social a svg {
  width: 18px;
  height: 18px;
  fill: currentColor;
  stroke: none;
}

.ts-foot__social a:hover {
  transform: translateY(-2px);
  border-color: transparent;
  color: #fff;
}

/* Each brand's own colour, because a row of four identical pink circles
   is a row of four identical pink circles. */
.ts-foot__social a[data-brand='instagram']:hover {
  background: linear-gradient(45deg, #f09433, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888);
}

.ts-foot__social a[data-brand='tiktok']:hover {
  background: #010101;
}

.ts-foot__social a[data-brand='whatsapp']:hover {
  background: #25d366;
}

.ts-foot__social a[data-brand='mail']:hover {
  background: var(--ts-brand);
}

.ts-foot__social a:focus-visible {
  outline: 2px solid var(--ts-brand);
  outline-offset: 2px;
}

/* the same glyphs in the contact panel and the contact list */
.ts-social__btn svg:first-child,
.ts-contact__ico svg {
  fill: currentColor;
  stroke: none;
}

/* `go` is an arrow, not a brand mark: it stays a stroke. */
.ts-social__btn svg.ts-go {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ---------- 11b. The trust strip above the footer ------------ */
/* TWO PINK SHAPES WITH NOTHING BETWEEN THEM READ AS ONE PINK SHAPE.
   The plain strip's row is `var(--ts-bg-2)`, and so is `.ts-band--tint`
   — the ground under the related-products row that sits directly above
   it on a product page, and under the last row on the home page. The
   section carried no margin, so the band ended and the card began on
   the same pixel: same colour, no seam, the card's rounded corners
   biting two notches out of the band above it.

   Air fixes it everywhere the strip is used; the hairline gives the
   card an edge of its own for the pages where it lands on white
   instead. Only `--plain` — `.ts-trust--overlap` on the home page has a
   NEGATIVE top margin on purpose, because that one is meant to ride up
   over the bottom of the hero. */
.ts-trust--plain {
  margin-top: clamp(26px, 3.2vw, 46px);
}

.ts-section-trust .ts-trust--plain .ts-trust__row {
  border: 1px solid var(--ts-line);
}

/* ---------- 12. Account, login and register ------------------ */
/* MOVED OUT, NOT DELETED. Everything this section held —
   `.ts-auth`, its cards and fields, and the `.woocommerce-MyAccount-*`
   navigation — now lives in assets/css/ts-account.css, which is queued
   from ts_enqueue_last() AFTER this file on account pages.

   It had to move rather than be extended. This file loads last on every
   page, so any rule left here would win every tie against the new one
   for reasons that have nothing to do with which is correct: the old
   `.ts-auth input[type='text']` and the old two-column `.ts-auth` grid
   would both have quietly survived the rewrite. A component with one
   stylesheet is the only version of this that stays fixed. */

/* ---------- 13. Responsive ----------------------------------- */
@media screen and (max-width: 1199px) {
  .ts-cart,
  form.ts-checkout {
    grid-template-columns: minmax(0, 1fr) 330px;
  }
}

/* --- the checkout becomes one column, and the order changes ---

   AT 1100px, NOT 990px. Between the two the summary column was 330px
   wide with a product thumbnail, a wrapping product name and a price in
   it, beside a form that had lost a third of its width to hold it — the
   worst of both layouts on exactly the screen an iPad in landscape
   reports. One column is the better answer for everything under a real
   desktop.

   `display: contents` on the aside dissolves it as a box and promotes
   its two children — the summary and the payment block — into grid items
   of `.ts-checkout` itself. That is what lets all three be ordered
   independently here while staying two columns above this width:

     1  the form      what the shopper came to fill in
     2  the summary   what they are buying, read once it is filled in
     3  payment + Place order, where the thumb finishes

   THE FORM IS FIRST AND THAT IS A DELIBERATE REVERSAL. The summary used
   to be pinned to the top as a collapsed "Order summary — Rs 1,234"
   strip, on the theory that the total should be visible before any
   typing. In practice it put a control the shopper had already seen the
   total in — the cart page they came from — above the only thing on the
   screen they were there to do, and pushed the first field below the
   fold on a small phone. The total is still one line away: it is on the
   strip, which now sits between the form and the payment block.

   Rendering the summary twice instead of moving it would give the form
   two `#place_order` buttons and two sets of payment radios, and the
   browser would submit whichever one the shopper never looked at. */
@media screen and (max-width: 1099px) {
  form.ts-checkout {
    grid-template-columns: minmax(0, 1fr);
  }

  .ts-checkout__side {
    display: contents;
  }

  /* The desktop placement pins these to a named row and column. Both
     have to be released here, or `order` below is overruled by a
     `grid-row: 1` that still applies and the summary lands back beside
     nothing. */
  .ts-checkout__main,
  .ts-checkout__side {
    grid-column: auto;
    grid-row: auto;
  }

  .ts-checkout__main {
    order: 1;
  }

  .ts-checkout__summary {
    order: 2;
    position: static;
    padding: 14px 16px;
  }

  .ts-checkout__pay {
    order: 3;
    padding: 18px 16px;
    border: 1px solid var(--ts-line);
    border-radius: var(--ts-r-lg);
    background: var(--ts-surface-2);
  }

  .ts-checkout__summary-toggle {
    display: flex;
  }

  /* The heading is redundant once the strip carries the label, so it
     goes rather than being printed twice. */
  .ts-checkout__summary #order_review_heading {
    display: none;
  }

  /* Open by default — see the note on the caret in §6 — and closed only
     when the shopper says so. */
  .ts-checkout__summary #order_review {
    display: block;
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--ts-line);
  }

  .ts-checkout--summary-closed .ts-checkout__summary #order_review {
    display: none;
  }

  /* The payment block is a sibling of the summary, not inside it (see
     woocommerce/checkout/form-checkout.php), so the collapse above
     cannot reach it — which is the point. */
  .ts-checkout__pay .woocommerce-checkout-payment,
  .ts-checkout__pay #payment,
  .woocommerce-checkout .ts-checkout__pay #payment {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
  }
}

@media screen and (max-width: 989px) {
  /* --- filters become a sheet --- */
  .ts-collection__layout {
    grid-template-columns: minmax(0, 1fr);
  }

  .ts-filters__open {
    display: inline-flex;
  }

  .ts-filters {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: none;
    padding: 0;
    background: var(--ts-backdrop);
  }

  .ts-filters.is-open {
    display: block;
  }

  .ts-filters__panel {
    position: absolute;
    inset: 0 0 0 auto;
    width: min(360px, 88vw);
    border: 0;
    border-radius: 0;
    background: var(--ts-bg);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    animation: ts-filters-in 0.24s var(--ts-out);
  }

  @keyframes ts-filters-in {
    from {
      transform: translateX(100%);
    }
  }

  .ts-filters__close {
    display: grid;
  }

  /* The sheet's Apply button is the way out, so it is pinned to the
     bottom. The panel's own bottom padding is what stops the last
     filter group scrolling underneath it — without that the price
     inputs sat behind the button and could not be reached. */
  .ts-filters__panel {
    padding-bottom: 84px;
  }

  .ts-filters__actions {
    position: sticky;
    bottom: 0;
    z-index: 2;
    margin: 12px -18px -84px;
    padding: 14px 18px calc(14px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--ts-line);
    background: var(--ts-bg);
    box-shadow: 0 -8px 20px -12px rgba(90, 20, 55, 0.25);
  }

  html.ts-filters-open,
  html.ts-filters-open body {
    overflow: hidden;
  }

  /* --- cart becomes one column --- */
  .ts-cart {
    grid-template-columns: minmax(0, 1fr);
  }

  .ts-cart__side {
    position: static;
  }

  /* The drawer IS the navigation below 990px (see ts-mobile.css §2), so
     the account row inside it is the only way to reach an order history
     from a phone. */
  .ts-nav__accountrow {
    display: block;
    margin-top: 6px;
    padding-top: 10px;
    border-top: 1px solid var(--ts-line);
  }

  /* A HOVER MENU ON A TOUCHSCREEN IS A TRAP, so the panel is gone here
     rather than merely hard to open: on the browsers that do emulate
     hover it would appear on the tap that is meant to follow the link,
     and swallow it. The icon is a plain link to /my-account/ again, and
     the Create account row two lines up is where the offer lives. */
  .ts-acct__panel,
  .ts-acct::after {
    display: none;
  }
}

@media screen and (max-width: 749px) {
  /* --- the cart card restacks --- */
  /* Third column is `auto`, not 30px. It carries the remove button on
     the first row and the line subtotal on the second, and 30px is wide
     enough for one of those — "Rs 494" was clipped at the card edge. */
  .ts-cart__row {
    grid-template-columns: 72px minmax(0, 1fr) auto;
    grid-template-areas:
      'media info remove'
      'media qty  subtotal';
    row-gap: 10px;
    padding: 12px;
  }

  .ts-cart__media {
    grid-area: media;
    align-self: start;
    width: 72px;
    height: 72px;
  }

  .ts-cart__info {
    grid-area: info;
  }

  .ts-cart__qty {
    grid-area: qty;
  }

  .ts-cart__subtotal {
    grid-area: subtotal;
    text-align: right;
  }

  .ts-cart__remove {
    grid-area: remove;
    align-self: start;
    justify-self: end;
  }

  /* `.ts-cart__actions` becomes a COLUMN here, and that changes what
     `flex` means for its children: the desktop `flex: 1 1 260px` on the
     coupon row is a 260px flex-basis along the main axis, which is now
     vertical. The discount field and its Apply button stretched to
     260px TALL. Basis has to go back to auto whenever the axis turns. */
  .ts-cart__actions {
    flex-direction: column;
    align-items: stretch;
    gap: 14px;
  }

  .ts-cart__coupon {
    flex: 0 0 auto;
    max-width: none;
  }

  .ts-cart__actions-right {
    flex-wrap: wrap;
    margin-left: 0;
    justify-content: space-between;
  }

  .ts-cart__update {
    flex: 0 0 auto;
  }

  /* --- checkout fields go single column, all of them ---
     There used to be an exception here keeping First name and Last name
     side by side. There is no last name any more — the checkout asks for
     one "Full name" (ts_checkout_field_order() in inc/woo-shop.php) —
     and the exception was addressed BY ID, which is (1,1,0) and beats
     the rule below it: left in place it would have drawn the full name
     field at half the width of a phone with nothing beside it.

     Phone/Email and Province/City stay paired from 750px up, where the
     checkout is already one column across the whole page and there is
     room for two boxes. Below that every row takes the line. */
  .woocommerce-checkout .form-row,
  .woocommerce-checkout .form-row-first,
  .woocommerce-checkout .form-row-last {
    grid-column: 1 / -1;
  }

  /* A 16px minimum on every field: iOS zooms the whole page in on a
     focused input below that, and the shopper then finishes the form
     scrolled sideways. 1.3333rem is 16px at this root size. */
  .woocommerce-checkout input[type='text'],
  .woocommerce-checkout input[type='email'],
  .woocommerce-checkout input[type='tel'],
  .woocommerce-checkout input[type='number'],
  .woocommerce-checkout input[type='password'],
  .woocommerce-checkout textarea,
  .woocommerce-checkout select {
    font-size: 1.3333rem;
  }

  /* --- the sticky place-order bar --- */
  #place_order {
    min-height: 52px;
  }

  /* --- related / cross-sell rows --- */
  .ts-section-row--related .ts-products,
  .ts-section-row--siblings .ts-products,
  .ts-section-row--crosssell .ts-products,
  .ts-section-row--upsells .ts-products {
    --ts-cols: 2;
  }

  /* --- footer socials centre under the copyright --- */
  .ts-foot__social {
    justify-content: center;
  }
}

@media screen and (max-width: 619px) {

  .ts-review__item {
    grid-template-columns: 46px minmax(0, 1fr) auto;
  }

  .ts-review__thumb {
    width: 46px;
    height: 46px;
  }

  .ts-filters__panel {
    width: 100%;
  }

  .ts-empty {
    padding: 34px 12px 40px;
  }

  .ts-empty__title {
    font-size: 1.6667rem;
  }
}

/* ---------- 14. Product reviews ------------------------------ */
/* THE PRODUCT PAGE NEVER RENDERED REVIEWS AT ALL until the
   comments_template() call at the foot of
   woocommerce/single-product.php, so there was nothing here to style and
   this section did not exist. What lands in that slot is WooCommerce's
   own single-product-reviews.php, extended by Customer Reviews for
   WooCommerce — neither of which knows anything about this theme, so all
   of it arrived as browser defaults: a blue-outline star picker, a
   square grey Submit, and headings at whatever size base.css last said.

   Everything below dresses THEIR markup. Nothing here is a template
   override, on purpose: a WooCommerce release that revises the review
   list, or a plugin update that adds a field to the form, should not
   need a merge in this repo. The same reasoning as email-styles.php. */

/* THE BLOCK IS THE LAST SECTION ON THE PAGE NOW, not the third — see
   the note above the comments_template() call in
   woocommerce/single-product.php. It follows a full-bleed trust strip
   and is followed by the footer, so the spacing is the section's own
   rather than the divider .ts-product__full hands every block that sits
   inside the product wrapper: no rule between it and a band that has
   just ended, and real air under it, which is what was missing when the
   card ended flush against the footer. */
.ts-product-reviews {
  padding: clamp(30px, 4vw, 52px) 0 clamp(44px, 6vw, 80px);
}

/* The rating under the product title links to #reviews and .ts-nav is
   sticky, so an unadjusted jump parks the heading under the header. */
.ts-product-reviews,
.ts-product__reviews #reviews {
  scroll-margin-top: clamp(90px, 12vw, 118px);
}

.ts-product__reviews {
  margin-top: clamp(28px, 3vw, 44px);
}

/* Inside the section it is the only child, so .ts-product__full's
   top rule and margin would draw a second divider under the trust
   strip's own edge. */
.ts-product-reviews .ts-product__full {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.ts-product__reviews #reviews {
  padding: clamp(20px, 2.4vw, 30px);
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r-lg);
  background: var(--ts-surface);
}

/* --- the heading --- */
.ts-product__reviews .woocommerce-Reviews-title {
  margin: 0 0 18px;
  font-family: var(--font-heading-family);
  font-size: 1.8333rem;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--ts-text);
}

/* The heading is rebuilt in PHP — ts_reviews_title() in inc/woo-shop.php
   §3b — because WooCommerce's own is "3 reviews for <the whole product
   name>", and hiding the name in CSS leaves the word "for" hanging. */

/* --- the list --- */
.ts-product__reviews .commentlist {
  display: grid;
  gap: 12px;
  margin: 0 0 26px;
  padding: 0;
  list-style: none;
}

.ts-product__reviews .commentlist li.review {
  margin: 0;
  padding: 16px 18px;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r);
  background: var(--ts-bg-2);
}

.ts-product__reviews .comment_container {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}

.ts-product__reviews .comment_container img.avatar {
  flex: none;
  width: 42px;
  height: 42px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 50%;
}

.ts-product__reviews .comment-text {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

.ts-product__reviews .comment-text .meta {
  margin: 0 0 6px;
  font-size: 1.0833rem;
  line-height: 1.4;
  color: var(--ts-muted);
}

.ts-product__reviews .woocommerce-review__author {
  font-size: 1.1667rem;
  font-weight: 700;
  color: var(--ts-text);
}

.ts-product__reviews .woocommerce-review__verified {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 8px;
  border-radius: var(--ts-r-pill);
  background: var(--ts-brand-wash);
  font-size: 0.9167rem;
  font-weight: 600;
  font-style: normal;
  color: var(--ts-brand);
}

.ts-product__reviews .comment-text .description {
  margin: 0;
  font-size: 1.1667rem;
  line-height: 1.6;
  color: var(--ts-text-2);
}

.ts-product__reviews .comment-text .description p:last-child {
  margin-bottom: 0;
}

/* --- the stars, read-only ---
   WooCommerce draws these with its own `star` webfont: the element holds
   five outline glyphs and an inner span is width-clipped to the rating.
   Only the colour and the size are ours. */
.ts-product__reviews .star-rating,
.ts-product__rating .ts-product__stars {
  color: var(--ts-star, #f5a524);
}

.ts-product__reviews .comment-text .star-rating {
  float: right;
  margin: 0 0 0 12px;
  font-size: 1.0833rem;
}

/* --- the star line under the product title --- */
/* NOT WooCommerce's markup: five ★ characters with a gradient clipped to
   the rating, because the `star` webfont is queued only where Woo prints
   a rating and this line sits in the theme's own details column. The
   percentage arrives as `--ts-stars` from the template. */
.ts-product__rating {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 2px 0 12px;
  font-size: 1.0833rem;
  color: var(--ts-muted);
  text-decoration: none;
}

.ts-product__rating:hover {
  color: var(--ts-brand);
}

.ts-product__stars {
  font-size: 1.25rem;
  letter-spacing: 1px;
  background: linear-gradient(
    90deg,
    var(--ts-star, #f5a524) var(--ts-stars, 100%),
    var(--ts-line-2) var(--ts-stars, 100%)
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.ts-product__rating b {
  font-weight: 700;
  color: var(--ts-text);
}

.ts-product__rating span:last-child {
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* --- the form --- */
.ts-product__reviews #review_form_wrapper {
  padding-top: 22px;
  border-top: 1px solid var(--ts-line);
}

.ts-product__reviews .comment-reply-title {
  display: block;
  margin: 0 0 4px;
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--ts-text);
}

.ts-product__reviews .comment-notes,
.ts-product__reviews .comment-form-cookies-consent label {
  font-size: 1.0833rem;
  line-height: 1.55;
  color: var(--ts-muted);
}

.ts-product__reviews .comment-form label {
  display: block;
  margin: 0 0 6px;
  font-size: 1.0833rem;
  font-weight: 600;
  color: var(--ts-text-2);
}

/* ...except the cookie consent, which is a sentence beside a tickbox
   and not the name of a field. */
.ts-product__reviews .comment-form-cookies-consent {
  display: flex;
  align-items: flex-start;
  gap: 9px;
}

.ts-product__reviews .comment-form-cookies-consent input {
  flex: none;
  width: 17px;
  height: 17px;
  margin: 2px 0 0;
  accent-color: var(--ts-brand);
}

.ts-product__reviews .comment-form-cookies-consent label {
  margin: 0;
  font-weight: 400;
  color: var(--ts-muted);
}

.ts-product__reviews .comment-form p {
  margin: 0 0 14px;
}

/* ONE COLUMN, AND THE TWO-COLUMN VERSION IS NOT COMING BACK. Name and
   email were paired here the way the phone and email are on the
   checkout, and it worked until Customer Reviews for WooCommerce added
   its own rows to the same form — an upload field that is not a
   `.comment-form-*` row auto-placed itself into the free cell, and the
   result was Upload beside Name and Email underneath, in that order.
   The form is short; a stack is correct however many fields a plugin
   adds to it next. */

.ts-product__reviews .comment-form input[type='text'],
.ts-product__reviews .comment-form input[type='email'],
.ts-product__reviews .comment-form textarea {
  width: 100%;
  min-height: 46px;
  padding: 11px 14px;
  border: 1px solid var(--ts-line-2);
  border-radius: var(--inputs-radius, 10px);
  background: var(--ts-surface);
  font-family: inherit;
  /* 16px, so iOS does not zoom the page in on focus. */
  font-size: 1.3333rem;
  line-height: 1.4;
  color: var(--ts-text);
  -webkit-appearance: none;
  appearance: none;
}

.ts-product__reviews .comment-form textarea {
  min-height: 110px;
  resize: vertical;
}

.ts-product__reviews .comment-form input:focus,
.ts-product__reviews .comment-form textarea:focus {
  outline: none;
  border-color: var(--ts-brand);
  box-shadow: 0 0 0 3px var(--ts-brand-wash);
}

/* --- the star picker ---
   `p.stars a` is five stacked anchors that WooCommerce's own script
   swaps classes on. They come out as blue links at the browser's default
   size otherwise, which is the single most obviously unstyled thing on
   the page. */
.ts-product__reviews .comment-form-rating {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.ts-product__reviews .comment-form-rating label {
  margin: 0;
}

.ts-product__reviews p.stars {
  margin: 0;
}

.ts-product__reviews p.stars {
  font-size: 1.3333rem;
}

.ts-product__reviews p.stars a {
  color: var(--ts-muted-2);
  text-decoration: none;
  transition: color 0.14s var(--ts-ease);
}

.ts-product__reviews p.stars:hover a,
.ts-product__reviews p.stars.selected a:not(.active) {
  color: var(--ts-star, #f5a524);
}

.ts-product__reviews p.stars a:hover ~ a,
.ts-product__reviews p.stars.selected a.active ~ a {
  color: var(--ts-muted-2);
}

/* --- the submit --- */
.ts-product__reviews .form-submit {
  margin: 6px 0 0;
}

/* TWO IDS IN WOO'S SELECTOR, SO TWO IDS HERE. WooCommerce styles this
   button through `.woocommerce #respond input#submit` — (2,1,1) — and
   the first version of this rule was (0,3,1), which is why the review
   form kept the browser's grey Submit while every other button on the
   site was pink. */
.ts-product__reviews #respond input#submit,
.woocommerce .ts-product__reviews #respond input#submit,
.ts-product__reviews .form-submit input[type='submit'],
.ts-product__reviews .form-submit .submit {
  min-height: 48px;
  padding: 0 26px;
  border: 0;
  border-radius: var(--ts-r-sm);
  background: var(--ts-brand);
  color: #fff;
  font-family: inherit;
  font-size: 1.25rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.18s var(--ts-ease);
}

.ts-product__reviews #respond input#submit:hover,
.woocommerce .ts-product__reviews #respond input#submit:hover,
.ts-product__reviews .form-submit input[type='submit']:hover,
.ts-product__reviews .form-submit .submit:hover {
  background: var(--ts-brand-700);
  color: #fff;
}

/* --- what Customer Reviews for WooCommerce adds ---
   The photo/video upload row on the form, and the gallery it renders
   under a review that has one. Its own stylesheet sizes these; these
   rules only bring the colours and the corner radius into line. */
.ts-product__reviews .cr-review-form-media,
.ts-product__reviews .cr-upload-images {
  margin: 0 0 14px;
}

/* THE UPLOAD ROW, AND THE BROWSE BUTTON INSIDE IT.
   CusRev prints a plain `<input type="file">` under a `<label>`, and a
   bare file input is drawn by the browser: a grey system button reading
   Browse… or Choose File, in the browser's own font, sitting in a form
   where every other control is the theme's. On a phone it was the only
   piece of chrome on the page that looked like a file dialog.

   The row is now a dashed drop-panel like the rest of the shop's
   secondary surfaces, and the button is dressed through
   `::file-selector-button`. THE TWO PSEUDO-ELEMENTS GET TWO RULES, NOT
   ONE SELECTOR LIST: a browser drops the WHOLE rule when any selector
   in the list is one it does not know, so pairing the standard name
   with `::-webkit-file-upload-button` would leave the button unstyled
   in exactly the engines that only understand one of them. */
.ts-product__reviews .cr-upload-local-images {
  margin: 0 0 14px;
  padding: 14px;
  border: 1px dashed var(--ts-line-2);
  border-radius: var(--ts-r);
  background: var(--ts-bg-2);
}

.ts-product__reviews .cr-upload-images-status {
  display: block;
  margin: 0 0 10px;
  font-size: 1.0833rem;
  font-weight: 600;
  line-height: 1.4;
  color: var(--ts-text-2);
}

.ts-product__reviews input[type='file'] {
  display: block;
  width: 100%;
  max-width: 100%;
  font-family: inherit;
  font-size: 1.0833rem;
  color: var(--ts-muted);
  cursor: pointer;
}

.ts-product__reviews input[type='file']::file-selector-button {
  margin: 0 12px 0 0;
  min-height: 38px;
  padding: 0 18px;
  border: 1px solid var(--ts-brand);
  border-radius: var(--ts-r-sm);
  background: var(--ts-brand-wash);
  font-family: inherit;
  font-size: 1.0833rem;
  font-weight: 600;
  color: var(--ts-brand);
  cursor: pointer;
  transition: background-color 0.18s var(--ts-ease), color 0.18s var(--ts-ease);
}

.ts-product__reviews input[type='file']::-webkit-file-upload-button {
  margin: 0 12px 0 0;
  min-height: 38px;
  padding: 0 18px;
  border: 1px solid var(--ts-brand);
  border-radius: var(--ts-r-sm);
  background: var(--ts-brand-wash);
  font-family: inherit;
  font-size: 1.0833rem;
  font-weight: 600;
  color: var(--ts-brand);
  cursor: pointer;
  transition: background-color 0.18s var(--ts-ease), color 0.18s var(--ts-ease);
}

.ts-product__reviews input[type='file']:hover::file-selector-button {
  background: var(--ts-brand);
  color: #fff;
}

.ts-product__reviews input[type='file']:hover::-webkit-file-upload-button {
  background: var(--ts-brand);
  color: #fff;
}

/* Under about 430px the button and the "no file chosen" text share a
   line the row is not wide enough for, and the filename is clipped
   mid-word. Stacking gives the button the full width, which is also
   the easier tap target. */
@media screen and (max-width: 430px) {
  .ts-product__reviews input[type='file']::file-selector-button {
    display: block;
    width: 100%;
    margin: 0 0 8px;
  }

  .ts-product__reviews input[type='file']::-webkit-file-upload-button {
    display: block;
    width: 100%;
    margin: 0 0 8px;
  }
}

.ts-product__reviews .cr-review-image img,
.ts-product__reviews .crstar-rating {
  border-radius: var(--ts-r-sm);
}

.ts-product__reviews .cr-ajax-search-btn,
.ts-product__reviews .cr-btn {
  border-radius: var(--ts-r-sm);
  background: var(--ts-brand);
  border-color: var(--ts-brand);
  color: #fff;
}

/* Nothing in a review — a long word in a name, a wide uploaded photo —
   may push the card past the page. */
.ts-product__reviews img,
.ts-product__reviews .comment-text {
  max-width: 100%;
}

.ts-product__reviews .description,
.ts-product__reviews .woocommerce-review__author {
  overflow-wrap: anywhere;
}


/* ============================================================
   §15 — THE 2026-09-21 SHOPPING PASS

   Everything added in the pass that put the answers a shopper needs
   where the decision is made: the type-ahead's own results list, the
   filter counts, the card's second photograph and its colourways, the
   product page's delivery and stock block, the phone's sticky Add to
   cart bar, the floating WhatsApp button and the recently-viewed row.

   It is in ts-fixes.css because these are new class names with no
   existing rules to outrank, and because ts-skin.css is documented as
   the re-skin and nothing else. Nothing here re-styles an existing
   component; a rule that did would belong in the file that draws it.
   ============================================================ */

/* ---------- 15a. Type-ahead results -------------------------- */
/*
   ts-woo.css §2 draws the panel and one result. These are the parts
   that result grew: a category and a stock word beside the price, a
   keyboard-highlighted row, and the "View all N results" foot.

   `.ts-predictive__item span` in ts-woo.css is a DESCENDANT selector,
   so it lays out every span inside the row as a flex column —
   including the two inside <small>, which came out stacked. This puts
   the meta line back on one line without touching the older rule.
*/
.ts-predictive__list {
  display: flex;
  flex-direction: column;
}

.ts-predictive__body {
  flex: 1 1 auto;
}

.ts-predictive__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 8px;
}

.ts-predictive__meta span {
  display: inline;
}

.ts-predictive__price {
  color: var(--ts-brand);
  font-weight: 600;
}

.ts-predictive__was {
  color: var(--ts-muted);
  font-weight: 400;
}

.ts-predictive__cat {
  color: var(--ts-muted);
  font-weight: 400;
}

.ts-predictive__cat::before {
  content: '·';
  margin-inline-end: 6px;
}

.ts-predictive__out {
  color: var(--ts-out);
  font-weight: 600;
}

/* The keyboard's own highlight. It matches the pointer's rather than
   inventing a second one, so arrowing down and hovering look the same
   and nobody has to learn which is which. */
.ts-predictive__item.is-active {
  background: var(--ts-row-hover);
  outline: 2px solid var(--ts-brand);
  outline-offset: -2px;
}

.ts-predictive__all {
  display: block;
  margin-top: 4px;
  padding: 10px 8px;
  border-top: 1px solid var(--ts-line);
  border-radius: 10px;
  color: var(--ts-brand);
  font-weight: 600;
  font-size: 1.1667rem;
  text-align: center;
  text-decoration: none;
}

.ts-predictive__all:hover,
.ts-predictive__all:focus-visible,
.ts-predictive__all.is-active {
  background: var(--ts-row-hover);
}

/*
   THE DRAWER'S PANEL IS IN THE FLOW, NOT OVER IT. `.ts-predictive` is
   absolute, which is right inside the header pill and inside the bar
   the magnifier opens — both of those sit over the page. The drawer is
   a scrolling column of links, and a panel floating over it would
   cover the menu it is part of and be clipped by the drawer's own
   overflow. Here it pushes the links down instead, which is what a
   drawer does.
*/
/*
   AND THE FORM HAS TO WRAP, OR THE PANEL EATS THE FIELD. The drawer's
   search form is `display: flex` with two items — the input and the go
   button. A static panel is a THIRD flex item on the same line, so it
   took its share of the width and squeezed the input to 63px. Wrapping
   costs nothing while the panel is hidden (two items still fit on one
   line) and gives it a line of its own once it opens.
*/
.ts-drawer-search {
  flex-wrap: wrap;
}

.ts-predictive--drawer {
  position: static;
  flex: 1 0 100%;
  max-height: 46vh;
  margin-top: 8px;
  box-shadow: none;
}

/* ---------- 15b. A search that found nothing ----------------- */
/*
   The three-way empty state in woocommerce/archive-product.php. A
   search with no results used to be told it was an empty category.
*/
.ts-empty__chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin: 0 0 18px;
  padding: 0;
  list-style: none;
}

.ts-empty__chip {
  display: inline-block;
  padding: 7px 14px;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r-pill);
  background: var(--ts-surface);
  color: var(--ts-text);
  font-size: 1.1667rem;
  text-decoration: none;
}

.ts-empty__chip:hover,
.ts-empty__chip:focus-visible {
  border-color: var(--ts-brand);
  color: var(--ts-brand);
}

.ts-empty__alt {
  display: inline-block;
  margin-top: 10px;
  color: var(--ts-muted);
  font-size: 1.1667rem;
  text-decoration: underline;
}

/* ---------- 15c. Filter counts ------------------------------- */
/*
   An option the current filters empty out is still worth SEEING — it
   is what tells the shopper the price range is too narrow — but it is
   not worth pressing, so it reads as unavailable rather than
   disappearing. A list that changes length as you filter is a list
   nobody can learn.
*/
.ts-filters__list li.is-empty,
.ts-filters__swatches li.is-empty {
  opacity: 0.4;
}

.ts-filters__list li.is-empty .ts-filters__opt,
.ts-filters__swatches li.is-empty .ts-filters__swatch {
  cursor: not-allowed;
}

.ts-filters__swatch .ts-filters__n {
  margin-inline-start: auto;
  color: var(--ts-muted);
  font-size: 1.0833rem;
  font-variant-numeric: tabular-nums;
}

/*
   THE PENDING COUNT ON THE DRAWER'S APPLY BUTTON. On a phone the
   filter choices are collected rather than applied one page load at a
   time (see assets/js/ts-filters.js), so this is what says how many
   are waiting — and turns Apply from a button you might not need into
   the one that finishes the job.
*/
.ts-filters__apply.is-pending {
  animation: ts-apply-pulse 0.5s var(--ts-ease, ease);
}

@keyframes ts-apply-pulse {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.04); }
  100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .ts-filters__apply.is-pending {
    animation: none;
  }
}

/* ---------- 15d. Product cards ------------------------------- */
/*
   THE SECOND PHOTOGRAPH. Every shot in this shop is the same square on
   the same white, which is what makes the grid legible and also what
   makes two colourways of one clip look like one product until you
   open it.

   IT IS OPACITY, NOT `display`. The second image is stacked over the
   first and faded in, so there is no reflow and no moment where the
   card is empty; and because both are laid out, a browser that never
   paints the second one still sizes the card from the first.
*/
.ts-pcard__media {
  position: relative;
}

.ts-pcard__img--alt {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity 0.35s var(--ts-ease, ease);
  pointer-events: none;
}

@media (hover: hover) and (pointer: fine) {
  .ts-pcard:hover .ts-pcard__img--alt,
  .ts-pcard:focus-within .ts-pcard__img--alt {
    opacity: 1;
  }
}

/*
   A TOUCH SCREEN HAS NO HOVER, so the swap can never happen there and
   the image would be weight for nothing. It is `lazy` in the markup,
   so a phone never downloads it — this keeps it out of the layout too.
*/
@media (hover: none) {
  .ts-pcard__img--alt {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ts-pcard__img--alt {
    transition: none;
  }
}

/* The colourways, as colour. A preview, not a picker — see the note in
   template-parts/pcard.php. */
.ts-pcard__swatches {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  margin: 0 0 6px;
  min-height: 14px;
}

.ts-pcard__dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1px solid var(--ts-line-2);
  flex: none;
}

.ts-pcard__dot-more {
  color: var(--ts-muted);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1;
}

/* ---------- 15e. The product page's assurances ---------------- */
/*
   When it arrives, what it costs to get here, whether it is in stock,
   and how to ask. All four answers were on the page already and none
   of them was visible — see template-parts/product-assurance.php.
*/
.ts-assure {
  margin: 16px 0 4px;
}

.ts-assure__list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
  padding: 14px;
  list-style: none;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r);
  background: var(--ts-surface-2, var(--ts-surface));
}

/* Keep the three purchase answers as separate, scannable rows. In
   particular, the delivery price and its rates link must not run into the
   heading or each other on a narrow product column. */
.ts-assure__list {
  gap: 0;
  padding: 0;
  overflow: hidden;
}

.ts-assure__row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
}

.ts-assure__row + .ts-assure__row {
  border-top: 1px solid var(--ts-line);
}

.ts-assure__icon {
  width: 18px;
  height: 18px;
  margin-top: 2px;
  color: var(--ts-brand);
  flex: none;
}

.ts-assure__row--stock.is-out .ts-assure__icon {
  color: var(--ts-out);
}

.ts-assure__text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.ts-assure__text b {
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.3;
}

.ts-assure__text small {
  display: block;
  color: var(--ts-muted);
  font-size: 1.0833rem;
  line-height: 1.45;
  overflow-wrap: anywhere;
}

/* "Only 3 left" is the one line here that changes a decision, so it is
   the one line allowed a colour. */
.ts-assure__low {
  color: var(--ts-brand);
}

.ts-assure__row--stock.is-out .ts-assure__text b {
  color: var(--ts-out);
}

.ts-assure__link {
  display: inline-block;
  margin-inline-start: 8px;
  color: var(--ts-brand);
  text-decoration: underline;
  white-space: nowrap;
}

@media screen and (max-width: 480px) {
  .ts-assure__link {
    display: block;
    width: fit-content;
    margin: 3px 0 0;
  }
}

.ts-assure__wa {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
  padding: 11px 14px;
  border: 1px solid rgb(37 211 102 / 22%);
  border-radius: var(--ts-r);
  background: rgb(37 211 102 / 6%);
  color: var(--ts-text);
  text-decoration: none;
}

.ts-assure__wa:hover,
.ts-assure__wa:focus-visible {
  border-color: rgb(37 211 102 / 55%);
  background: rgb(37 211 102 / 11%);
}

.ts-assure__wa svg {
  width: 22px;
  height: 22px;
  color: #25d366;
  flex: none;
}

.ts-assure__wa span {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.ts-assure__wa b {
  font-size: 1.25rem;
  font-weight: 600;
}

.ts-assure__wa small {
  color: var(--ts-muted);
  font-size: 1.0833rem;
}

/* ---------- 15f. The phone's sticky Add to cart bar ----------- */
/*
   DESKTOP DOES NOT GET ONE. Above 990px the buy column sits beside the
   gallery and the button is in view for most of the page; a bar pinned
   across the bottom of a wide screen is a toolbar covering content to
   solve a problem that screen does not have.
*/
.ts-buybar {
  display: none;
}

@media (max-width: 989px) {
  .ts-buybar {
    position: fixed;
    z-index: 60;
    inset: auto 0 0 0;
    display: block;
    padding: 8px 12px calc(8px + env(safe-area-inset-bottom, 0px));
    background: var(--ts-surface);
    border-top: 1px solid var(--ts-line);
    box-shadow: 0 -6px 20px rgb(0 0 0 / 8%);
    animation: ts-buybar-in 0.22s var(--ts-ease, ease);
  }

  /* `[hidden]` is a UA rule at (0,0,0) and the block above is an author
     rule, so it loses without this — the same trade
     `.ts-feed__foot [hidden]` makes for the feed's own button. */
  .ts-buybar[hidden] {
    display: none !important;
  }

  .ts-buybar__inner {
    display: flex;
    align-items: center;
    gap: 10px;
    max-width: 620px;
    margin: 0 auto;
  }

  .ts-buybar__media {
    flex: none;
  }

  .ts-buybar__media img {
    display: block;
    width: 44px;
    height: 44px;
    object-fit: contain;
    border-radius: 10px;
    background: var(--ts-plate);
  }

  .ts-buybar__body {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
    flex: 1 1 auto;
  }

  /* One line. The title is context, not the thing being read — the
     page above it already says what this is. */
  .ts-buybar__title {
    font-size: 1.0833rem;
    line-height: 1.25;
    color: var(--ts-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .ts-buybar__price {
    font-size: 1.25rem;
    font-weight: 700;
    color: var(--ts-text);
    line-height: 1.2;
  }

  .ts-buybar__price del {
    margin-inline-start: 5px;
    color: var(--ts-muted);
    font-weight: 400;
  }

  .ts-buybar__price .price,
  .ts-buybar__price ins {
    text-decoration: none;
  }

  /* 44px is the floor for a thumb, and this is the one control on the
     page that exists to be hit with one. */
  .ts-buybar__add {
    flex: none;
    padding-inline: 18px;
    min-height: 44px;
    white-space: nowrap;
  }

  .ts-buybar__add svg {
    width: 17px;
    height: 17px;
  }
}

@keyframes ts-buybar-in {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .ts-buybar {
    animation: none;
  }
}

/* ---------- 15g. The floating WhatsApp button ----------------- */
/*
   BOTTOM LEFT. `.ts-totop` owns the bottom-right corner and the buy
   bar owns the bottom edge; a third round control in the right corner
   would put two of them under one thumb.
*/
.ts-wafloat {
  position: fixed;
  z-index: 55;
  left: 16px;
  bottom: 16px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 11px;
  border-radius: var(--ts-r-pill);
  background: #25d366;
  color: #fff;
  text-decoration: none;
  box-shadow: 0 6px 18px rgb(37 211 102 / 35%);
  transition: transform 0.25s var(--ts-ease, ease), bottom 0.25s var(--ts-ease, ease);
}

.ts-wafloat:hover,
.ts-wafloat:focus-visible {
  color: #fff;
  transform: translateY(-2px);
}

.ts-wafloat svg {
  width: 24px;
  height: 24px;
  flex: none;
}

/*
   AT REST IT IS THE MARK ALONE, AND THE WORDS ARRIVE ON HOVER.
   A permanent "Chat with us" pill is about 170px wide, and on the
   catalogue that is exactly the width of the filter column's bottom
   left corner — it sat over the colour swatches and their counts. The
   circle alone is 46px, which clears the page gutter at every width,
   so the label is what expands into the empty margin when somebody
   reaches for it. A phone has no hover and needs no label: the green
   circle is the most recognised button in Pakistan.

   `max-width` rather than `display`, so the words slide out instead of
   appearing — and `overflow: hidden` on the link is what clips them
   while they are folded away.
*/
.ts-wafloat {
  overflow: hidden;
}

.ts-wafloat__label {
  max-width: 0;
  padding-inline-end: 0;
  font-family: var(--ts-font-display, inherit);
  font-weight: 700;
  font-size: 1.1667rem;
  letter-spacing: var(--ts-ui-track, 0.02em);
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  transition: max-width 0.25s var(--ts-ease, ease), padding 0.25s var(--ts-ease, ease);
}

@media (hover: hover) and (pointer: fine) {
  .ts-wafloat:hover .ts-wafloat__label,
  .ts-wafloat:focus-visible .ts-wafloat__label {
    max-width: 12rem;
    padding-inline-end: 6px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ts-wafloat__label {
    transition: none;
  }
}

/* Out of the buy bar's way rather than hidden behind it: the point of
   the button is to be reachable at the moment somebody hesitates, and
   that moment is on the product page. */
.ts-wafloat.is-lifted {
  bottom: 76px;
}

@media (prefers-reduced-motion: reduce) {
  .ts-wafloat {
    transition: none;
  }
}

/* ---------- 15h. Recently viewed ----------------------------- */
/*
   The section ships `hidden` and empty — see inc/recently-viewed.php
   for why it cannot be rendered server-side — so these rules only ever
   apply to a row the script has already filled.
*/
.ts-recent[hidden] {
  display: none;
}

.ts-recent__clear {
  padding: 4px 10px;
  border: 1px solid var(--ts-line);
  border-radius: var(--ts-r-pill);
  background: none;
  color: var(--ts-muted);
  font: inherit;
  font-size: 1.0833rem;
  cursor: pointer;
}

.ts-recent__clear:hover,
.ts-recent__clear:focus-visible {
  border-color: var(--ts-brand);
  color: var(--ts-brand);
}
