/* ==========================================================================
   The Sewing Birdie — shop pages
   --------------------------------------------------------------------------
   Home, Shop and category archives, single product, the slide-out cart and
   the Cart page. Owned by the shop-pages agent.

   Everything is expressed with the design tokens from assets/css/tokens.css
   (--sb-*): no colour, radius, spacing or size is hard-coded here, so a change
   to a token flows through every page.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Layout primitives
   -------------------------------------------------------------------------- */

.tsb-main {
  display: block;
  width: 100%;
}

.tsb-wrap {
  width: 100%;
  max-width: var(--sb-wrap);
  margin-inline: auto;
  padding-inline: var(--sb-gutter);
}

/* Kadence's page wrapper is already a 1240px column with a 24px gutter, so the
   shop wrapper must not add the gutter a second time — otherwise the shop
   content sits 24px inside the header's own content edge. */
main.wrap .tsb-wrap {
  padding-inline: 0;
}

.tsb-section {
  margin-top: var(--sb-section);
}

.tsb-section__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 28px;
}

.tsb-h2 {
  margin: 0;
  font-family: var(--sb-font-display);
  font-weight: 400;
  font-size: var(--sb-size-h2);
  line-height: 1.1;
  color: var(--sb-ink);
}

.tsb-page-title {
  margin: 0;
  font-family: var(--sb-font-display);
  font-weight: 400;
  font-size: clamp(2.375rem, 3.2vw + 1.2rem, 3.75rem);
  line-height: 1;
  color: var(--sb-ink);
}

.tsb-eyebrow {
  margin: 0;
  font-size: var(--sb-size-eyebrow);
  letter-spacing: var(--sb-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--sb-muted);
}

/* QA round 3, D4 — these standalone section links measured 25px (this one) and
   23px (`.tsb-step__link`) tall, under the 44px minimum; WCAG 2.5.5's inline
   exception does not apply to a link that is the whole target. The padding grows
   the hit area to 44px while the matching negative margin takes the same amount
   back out of the layout, so the text stays exactly where it was — level with
   the heading beside it in `.tsb-section__head`. */
.tsb-link-arrow {
  display: inline-flex;
  align-items: center;
  min-height: var(--sb-tap);
  padding-block: 9px;
  margin-block: -9px;
  font-weight: 600;
  color: var(--sb-ink);
  text-decoration: none;
}

.tsb-link-arrow:hover {
  color: var(--sb-accent-hover);
  text-decoration: underline;
}

.tsb-cover {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.tsb-photo-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  min-height: 120px;
  background: var(--sb-photo);
  border-radius: var(--sb-r-img);
  color: var(--sb-muted);
  font-size: var(--sb-size-caption);
  text-align: center;
  padding: 12px;
}

.tsb-stars {
  color: var(--sb-ink);
  letter-spacing: 2px;
}

.tsb-grid {
  display: grid;
  gap: 32px 24px;
}

.tsb-grid--products {
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}

.tsb-grid--reviews {
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 20px;
}

/* --------------------------------------------------------------------------
   2. Buttons
   -------------------------------------------------------------------------- */

.tsb-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: var(--sb-tap);
  padding: 0 26px;
  border: 1px solid transparent;
  border-radius: var(--sb-r-btn);
  font-family: var(--sb-font-body);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: var(--sb-tracking-nav);
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--sb-transition), color var(--sb-transition), border-color var(--sb-transition);
}

/* QA round 3, D2 — the fill is the derived #A8653F but the TEXT was the page
   background (cream #FAF8F5) rather than the surface: 4.31:1, just under 4.5.
   Text on an accent fill is white — `--sb-surface`, never `--sb-bg`. */
.tsb-btn--primary {
  background: var(--sb-accent-strong);
  border-color: var(--sb-accent-strong);
  color: var(--sb-surface);
}

.tsb-btn--primary:hover,
.tsb-btn--primary:focus {
  background: var(--sb-accent-hover);
  border-color: var(--sb-accent-hover);
  color: var(--sb-surface);
}

.tsb-btn--outline {
  background: var(--sb-bg);
  border-color: var(--sb-ink);
  color: var(--sb-ink);
}

.tsb-btn--outline:hover,
.tsb-btn--outline:focus {
  background: var(--sb-ink);
  color: var(--sb-bg);
}

.tsb-btn--light {
  background: var(--sb-bg);
  border-color: var(--sb-bg);
  color: var(--sb-ink);
}

.tsb-btn--light:hover,
.tsb-btn--light:focus {
  background: var(--sb-chip);
  border-color: var(--sb-chip);
  color: var(--sb-ink);
}

.tsb-btn--ghost {
  min-height: 40px;
  padding: 0 14px;
  background: var(--sb-bg);
  border-color: var(--sb-ink);
  color: var(--sb-ink);
  font-size: var(--sb-size-small);
}

.tsb-btn--ghost:hover,
.tsb-btn--ghost:focus {
  background: var(--sb-ink);
  color: var(--sb-bg);
}

.tsb-btn--block {
  width: 100%;
}

/* Visible focus everywhere, keyboard first. */
.tsb-btn:focus-visible,
.tsb-chip:focus-visible,
.tsb-card:focus-visible,
.tsb-tile:focus-visible,
.tsb-filter-option:focus-visible,
.tsb-filters__toggle:focus-visible,
.tsb-accordion__summary:focus-visible,
.tsb-minicart__close:focus-visible,
.tsb-minicart__backdrop:focus-visible,
.tsb-sort__select:focus-visible,
.tsb-sort__apply:focus-visible,
.tsb-startbox:focus-visible,
.tsb-link-arrow:focus-visible,
.tsb-line-item__remove:focus-visible,
.tsb-step__link:focus-visible,
.tsb-videoband__play:focus-visible {
  outline: 2px solid var(--sb-ink);
  outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   3. Product card
   -------------------------------------------------------------------------- */

.tsb-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  color: var(--sb-ink);
  text-decoration: none;
}

.tsb-card__media {
  position: relative;
  display: block;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--sb-photo);
  border-radius: var(--sb-r-img);
}

.tsb-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s ease;
}

.tsb-card:hover .tsb-card__media img {
  transform: scale(1.03);
}

@media (prefers-reduced-motion: reduce) {
  .tsb-card__media img,
  .tsb-card:hover .tsb-card__media img {
    transition: none;
    transform: none;
  }
}

.tsb-badge {
  position: absolute;
  top: 10px;
  left: 10px;
  padding: 4px 10px;
  border-radius: var(--sb-r-chip);
  background: var(--sb-bg);
  color: var(--sb-ink);
  font-size: var(--sb-size-eyebrow);
  font-weight: 600;
}

.tsb-card__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.tsb-card__name {
  font-weight: 500;
}

.tsb-card__price {
  font-weight: 600;
  white-space: nowrap;
}

.tsb-card__price .woocommerce-Price-amount,
.tsb-card__price .tsb-free-price {
  font-weight: 600;
}

.tsb-card__meta {
  font-size: var(--sb-size-caption);
  color: var(--sb-muted);
}

/* --------------------------------------------------------------------------
   4. Home
   -------------------------------------------------------------------------- */

.tsb-hero {
  padding-top: 40px;
}

.tsb-hero__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 48px;
}

/* The hero headline is the one string whose WIDTH, not its size, drives this
   layout: "Sew it. Wear it. <em>Live it.</em>" has to set on one line, and at
   the design's 72px that line is 542px wide (the italic "Live it." is 175px of
   it, 5px wider than the roman).

   The mockup only just manages it, by accident of box-sizing: Home.dc.html is
   content-box, so `max-width: 1240px; padding: 0 24px` leaves a 1240px content
   box and a 546px copy column at 1440 — 4px of clearance, and it wraps at 1280
   in the mockup itself. Our `.tsb-wrap` is border-box, so the same 1240px
   leaves a 1192px content box, and `flex: 1 1 420px` splits the 204px of free
   space evenly with the media column: the copy gets 522px on desktop, 20px
   short of the 72px line. Worse, the even split means the copy column is only
   420px at 1051px — the exact width where the hero first fits side by side and
   the free space is still zero — so the line orphans there too.

   455/485 keeps the pair summing to 940px (so the hero still stacks below
   1051px, unchanged) and the copy column becomes 557px on desktop, 455px in the
   narrowest side-by-side case: 43px of clearance at desktop, 26px at worst. */
.tsb-hero__copy {
  flex: 1 1 455px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.tsb-hero__title {
  margin: 0;
  font-family: var(--sb-font-display);
  font-weight: 400;
  /* Same fluid curve as --sb-size-h1, with a 4.375rem (70px) ceiling instead of
     4.5rem. The curve's own max is only reached at 1467px, where the 542px the
     line needs overflows even the widened 557px column by less than nothing —
     at 70px it needs 526px, inside the 31px margin this ceiling guarantees.
     Scoped to the hero: every other h1 keeps the foundation scale. Remove the
     ceiling and the headline orphans again from ~1408px up. */
  font-size: var(--sb-size-h1);
  font-size: min(var(--sb-size-h1), 4.375rem);
  line-height: 1;
  color: var(--sb-ink);
}

.tsb-hero__intro {
  margin: 0;
  max-width: 480px;
  font-size: 1.1875rem;
  color: var(--sb-ink-2);
}

.tsb-hero__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.tsb-hero__media {
  flex: 1 1 485px;
  min-width: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.tsb-hero__photo {
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--sb-photo);
  border-radius: var(--sb-r-img);
}

.tsb-hero__photo--2 {
  margin-top: 48px;
}

.tsb-trust {
  margin-top: 64px;
}

/* Home, hero → trust strip. The trust strip's own 64px top margin is the
   design's hero→trust gap, and the section that follows it keeps
   `.tsb-section`'s own 96px/64px (base.css: `margin-top: var(--sb-section);
   padding-block: 0`), so the rhythm applies exactly once. Two `.tsb-home`
   overrides used to sit here — `margin-top: calc(var(--sb-section-sm) / 2)`
   on `.tsb-trust` and `margin-top: 0; padding-top: var(--sb-section-sm)` on
   `.tsb-trust + .tsb-section`. They compensated for a doubled section margin
   that the foundation has since fixed in the primitive; with that fix in
   place they made hero→trust 32px (design: 64px) and trust→first section
   64px (design: 96px), so they are removed. Do not re-add them. */
.tsb-trust__list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
  margin: 0;
  padding: 20px 0;
  list-style: none;
  border-top: 1px solid var(--sb-line);
  border-bottom: 1px solid var(--sb-line);
  font-size: var(--sb-size-small);
  color: var(--sb-ink-2);
}

.tsb-trust__list li {
  display: flex;
  align-items: center;
  gap: 10px;
}

.tsb-trust__list svg {
  flex: none;
}

.tsb-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 20px;
}

.tsb-tile {
  display: flex;
  flex-direction: column;
  gap: 12px;
  color: var(--sb-ink);
  text-decoration: none;
}

.tsb-tile__media {
  display: block;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--sb-photo);
  border-radius: var(--sb-r-img);
}

.tsb-tile__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.tsb-tile__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.tsb-tile__label {
  font-family: var(--sb-font-display);
  font-size: 1.625rem;
  line-height: 1.1;
}

.tsb-tile:hover .tsb-tile__label {
  color: var(--sb-accent-hover);
}

.tsb-band {
  margin-top: var(--sb-section);
}

.tsb-band--soft {
  background: var(--sb-soft);
  padding: 72px 0;
}

.tsb-band__inner {
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.tsb-band__head {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 640px;
}

.tsb-band__lead {
  margin: 0;
  color: var(--sb-ink-2);
}

.tsb-steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 20px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.tsb-step {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 24px;
  background: var(--sb-bg);
  border-radius: var(--sb-r-band);
}

.tsb-step__num {
  font-family: var(--sb-font-display);
  font-size: 2.5rem;
  line-height: 1;
  color: var(--sb-accent-strong);
}

.tsb-step__title {
  font-weight: 600;
  font-size: 1.125rem;
}

.tsb-step__text {
  color: var(--sb-ink-2);
  font-size: 0.9375rem;
}

.tsb-step__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--sb-tap);
  padding-block: 10.5px;
  margin-block: -10.5px;
  font-weight: 600;
  font-size: 0.9375rem;
  color: var(--sb-ink);
}

.tsb-course {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  overflow: hidden;
  border-radius: var(--sb-r-band);
  background: var(--sb-ink);
  color: var(--sb-bg);
}

.tsb-course__media {
  position: relative;
  display: block;
  flex: 1 1 420px;
  min-height: 360px;
  background: var(--sb-ink-2);
  overflow: hidden;
}

/* The photo must not drive the band's height: a 4:5 image in a wide column
   would make the band twice as tall as the copy beside it. It is absolutely
   positioned and cropped instead, so the copy column sets the height. */
.tsb-course__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.tsb-course__body {
  flex: 1 1 420px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 16px;
  padding: 48px;
}

.tsb-course .tsb-eyebrow {
  color: var(--sb-chip);
}

.tsb-course .tsb-h2 {
  color: var(--sb-bg);
}

.tsb-course__text {
  margin: 0;
  color: var(--sb-chip);
}

.tsb-course__ctas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 20px;
}

.tsb-course__price {
  font-size: 1.25rem;
  font-weight: 500;
}

.tsb-course__price .woocommerce-Price-amount {
  color: var(--sb-bg);
}

.tsb-freeband {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 32px;
  padding: 48px;
  border: 1px solid var(--sb-line);
  border-radius: var(--sb-r-band);
}

.tsb-freeband__copy {
  flex: 1 1 420px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.tsb-freeband__copy p {
  margin: 0;
  color: var(--sb-ink-2);
}

/* --------------------------------------------------------------------------
   5. Breadcrumb
   -------------------------------------------------------------------------- */

.tsb-breadcrumb {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 20px 0;
  font-size: var(--sb-size-caption);
  color: var(--sb-muted);
}

.tsb-breadcrumb a {
  color: var(--sb-muted);
}

.tsb-breadcrumb a:hover {
  color: var(--sb-ink);
}

/* --------------------------------------------------------------------------
   6. Shop and category archives
   -------------------------------------------------------------------------- */

.tsb-archive__head {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 8px 0 28px;
}

.tsb-archive__intro {
  max-width: 620px;
  color: var(--sb-ink-2);
}

.tsb-archive__intro p {
  margin: 0;
}

.tsb-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--sb-line);
}

.tsb-chip {
  display: inline-flex;
  align-items: center;
  min-height: var(--sb-tap);
  padding: 0 18px;
  border: 1px solid var(--sb-line);
  border-radius: var(--sb-r-chip);
  background: var(--sb-bg);
  color: var(--sb-ink);
  font-size: var(--sb-size-small);
  font-weight: 500;
  text-decoration: none;
}

.tsb-chip:hover {
  border-color: var(--sb-ink);
}

.tsb-chip.is-current {
  background: var(--sb-ink);
  border-color: var(--sb-ink);
  color: var(--sb-bg);
}

.tsb-archive__layout {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 40px;
  padding-top: 28px;
}

/* --- Filters -------------------------------------------------------------- */

.tsb-filters {
  flex: 1 1 220px;
  max-width: 260px;
}

.tsb-filters__toggle {
  display: none;
}

.tsb-filters__panel {
  display: flex;
  flex-direction: column;
  gap: 28px;
}

.tsb-filters__clear {
  margin: 0;
  font-size: var(--sb-size-small);
}

.tsb-filters__clear a {
  color: var(--sb-muted);
}

.tsb-filter-group {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 0;
  padding: 0;
  border: 0;
}

.tsb-filter-group legend {
  padding: 0 0 8px;
}

.tsb-filter-option {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--sb-tap);
  color: var(--sb-ink);
  font-size: var(--sb-size-small);
  text-decoration: none;
}

.tsb-filter-option__box {
  flex: none;
  width: 18px;
  height: 18px;
  border: 1px solid var(--sb-muted);
  border-radius: 3px;
  background: var(--sb-bg);
  position: relative;
}

.tsb-filter-option[aria-checked="true"] .tsb-filter-option__box {
  background: var(--sb-ink);
  border-color: var(--sb-ink);
}

.tsb-filter-option[aria-checked="true"] .tsb-filter-option__box::after {
  content: "";
  position: absolute;
  inset: 3px 4px 6px;
  border-left: 2px solid var(--sb-bg);
  border-bottom: 2px solid var(--sb-bg);
  transform: rotate(-45deg);
}

.tsb-filter-option:hover .tsb-filter-option__label {
  text-decoration: underline;
}

.tsb-filter-option__count {
  margin-left: auto;
  color: var(--sb-muted);
  font-size: var(--sb-size-eyebrow);
}

.tsb-startbox {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 20px;
  background: var(--sb-soft);
  border-radius: var(--sb-r-band);
  color: var(--sb-ink);
  text-decoration: none;
}

.tsb-startbox__title {
  font-family: var(--sb-font-display);
  font-size: 1.5rem;
  line-height: 1.1;
}

.tsb-startbox__text {
  font-size: var(--sb-size-small);
  color: var(--sb-ink-2);
}

/* --- Results -------------------------------------------------------------- */

.tsb-archive__results {
  flex: 999 1 640px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.tsb-resultsbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.tsb-resultsbar__count {
  margin: 0;
  font-size: var(--sb-size-small);
  color: var(--sb-muted);
}

.tsb-sort {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--sb-size-small);
  color: var(--sb-muted);
}

.tsb-sort__label {
  white-space: nowrap;
}

.tsb-sort__select {
  min-height: var(--sb-tap);
  padding: 8px 10px;
  border: 1px solid var(--sb-line);
  border-radius: var(--sb-r-btn);
  background: var(--sb-bg);
  color: var(--sb-ink);
  font: inherit;
}

.tsb-sort__apply {
  min-height: var(--sb-tap);
  padding: 0 16px;
  border: 1px solid var(--sb-ink);
  border-radius: var(--sb-r-btn);
  background: var(--sb-bg);
  color: var(--sb-ink);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

/* With JavaScript the select submits on change, so the Apply button is not
   needed — it stays for the no-JS path. */
html.tsb-js .tsb-sort__apply {
  display: none;
}

.tsb-empty {
  padding: 48px 24px;
  border: 1px solid var(--sb-line);
  border-radius: var(--sb-r-band);
  text-align: center;
  color: var(--sb-ink-2);
}

.tsb-pagination,
.woocommerce-pagination {
  margin-top: 40px;
}

.woocommerce-pagination ul.page-numbers {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
  border: 0;
}

.woocommerce-pagination ul.page-numbers li {
  border: 0;
}

.woocommerce-pagination ul.page-numbers a,
.woocommerce-pagination ul.page-numbers span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--sb-tap);
  min-height: var(--sb-tap);
  padding: 0 12px;
  border: 1px solid var(--sb-line);
  border-radius: var(--sb-r-btn);
  color: var(--sb-ink);
  text-decoration: none;
}

/* QA round 3, D3 — the declared fill never applied. Kadence's own
   `.woocommerce nav.woocommerce-pagination ul li span.current` (specificity
   0,3,4) outranks the `.woocommerce-pagination ul.page-numbers span.current`
   below it (0,3,3) and paints the current page with `--global-palette-btn-bg`
   (#C17F59) and `--global-palette-btn` (white) = 3.26:1. Prefixing with
   `.woocommerce` (0,4,2) makes this declaration win, so the current page is the
   ink pill this file already intended — 13.5:1, and consistent with
   `.tsb-chip.is-current`. `a.page-numbers.current` is covered too: WooCommerce
   renders the current page as a `<span>` today but has shipped it as an anchor. */
.woocommerce .woocommerce-pagination ul.page-numbers a.page-numbers.current,
.woocommerce .woocommerce-pagination ul.page-numbers span.current {
  background: var(--sb-ink);
  border-color: var(--sb-ink);
  color: var(--sb-bg);
}

/* --------------------------------------------------------------------------
   7. Single product
   -------------------------------------------------------------------------- */

/* Kadence prints its own breadcrumb inside .product-title.product-above, above
   the template. This design brings its own breadcrumb and its own H1, so the
   duplicate is hidden on the product surface only (the block also carries a
   -16px margin that collapses out of .tsb-main and pulls the page under the
   header — hiding it removes that too). Kadence's breadcrumb and the T-38c
   short-label filter it feeds still apply everywhere else. */
body.tsb-surface-product .product-title.product-above {
  display: none;
}

.tsb-product__top {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 48px;
}

.tsb-product__gallery {
  flex: 1 1 520px;
  min-width: 0;
}

.tsb-product__info {
  flex: 1 1 380px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 22px;
  padding-top: 8px;
}

.tsb-product__title {
  margin: 0;
  font-family: var(--sb-font-display);
  font-weight: 400;
  font-size: clamp(2.25rem, 2.4vw + 1.2rem, 3.25rem);
  line-height: 1.05;
  color: var(--sb-ink);
}

.tsb-product__rating {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: var(--sb-size-small);
  color: var(--sb-muted);
  text-decoration: none;
}

.tsb-product__price {
  margin: 0;
  font-size: 1.625rem;
  font-weight: 500;
  color: var(--sb-ink);
}

.tsb-product__intro {
  margin: 0;
  font-size: var(--sb-size-body);
  color: var(--sb-ink-2);
}

.tsb-chiprow {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--sb-size-caption);
  font-weight: 500;
}

.tsb-chiprow li {
  padding: 7px 14px;
  background: var(--sb-chip);
  border-radius: var(--sb-r-chip);
}

.tsb-buy {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.tsb-buy form.cart {
  margin: 0;
}

/* Hidden add-to-cart proxy.
   Stripe's express-checkout bundle reads the product id from
   `.single_add_to_cart_button` and calls `.classList` on that node before it
   opens a wallet sheet, so the node has to exist even though the fast-purchase
   layout shows no add-to-cart control. Clipped, not `display:none`: the plugin
   needs it in the DOM and laid out, and a display-hidden ancestor has bitten
   plugins that measure their mount point. Nothing in here is reachable by
   pointer or keyboard (tabindex="-1", aria-hidden on the wrapper). */
.tsb-cart-proxy {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Buy block + express-checkout consent
   -------------------------------------------------------------------------- */

/* One column: Stripe's wallet buttons (printed on the
   `woocommerce_after_add_to_cart_form` action) are the primary action, the
   single card link sits under them. A simple product page has no add-to-cart
   button at all. */
.tsb-buy__actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.tsb-buy__actions .tsb-btn {
  width: 100%;
  justify-content: center;
  text-align: center;
}

/* One-line consent sentence, printed by express-consent.js directly under the
   wallet buttons. A statement, not a control: paying is the agreement. */
.tsb-express-consent {
  margin: 0;
  font-size: var(--sb-size-small);
  line-height: 1.45;
  color: var(--sb-ink-2);
  text-wrap: pretty;
}

.tsb-express-consent[hidden] {
  display: none;
}

.tsb-trustlines {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--sb-size-small);
  color: var(--sb-ink-2);
}

.tsb-trustlines li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

.tsb-trustlines svg {
  flex: none;
  margin-top: 2px;
}

.tsb-accordions {
  border-top: 1px solid var(--sb-line);
}

.tsb-accordion {
  border-bottom: 1px solid var(--sb-line);
}

.tsb-accordion__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 56px;
  font-weight: 600;
  color: var(--sb-ink);
  cursor: pointer;
  list-style: none;
}

.tsb-accordion__summary::-webkit-details-marker {
  display: none;
}

.tsb-accordion__sign::after {
  content: "+";
  font-size: 1.375rem;
  font-weight: 400;
  line-height: 1;
}

.tsb-accordion[open] .tsb-accordion__sign::after {
  content: "−";
}

.tsb-accordion__body {
  padding: 0 0 18px;
  color: var(--sb-ink-2);
  font-size: 0.9375rem;
}

.tsb-accordion__body ul {
  margin: 0;
  padding-left: 20px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* --- WooCommerce gallery, restyled --------------------------------------- */

.tsb-product__gallery .woocommerce-product-gallery {
  position: relative;
  width: 100% !important;
  float: none !important;
  margin: 0;
}

.tsb-product__gallery .flex-viewport {
  border-radius: var(--sb-r-img);
  overflow: hidden;
}

.tsb-product__gallery .woocommerce-product-gallery__image {
  background: var(--sb-photo);
}

.tsb-product__gallery .woocommerce-product-gallery__image img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
}

/* QA round 3, D5 — the full-screen trigger measured 18x21px. The 44px box was
   already declared here but the anchor is `display: inline`, where width and
   height are ignored, and WooCommerce's own rule (scoped to `div.images`, which
   this gallery does not carry) is what normally makes it a positioned block.
   Own both here: a real 44px flex control, anchored to the gallery's top-right
   corner, so the hit area is 44x44 and it no longer sits in the flow pushing
   the main image down. */
.tsb-product__gallery .woocommerce-product-gallery__trigger {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--sb-tap);
  height: var(--sb-tap);
  border-radius: var(--sb-r-btn);
  background: var(--sb-bg);
  line-height: var(--sb-tap);
}

.tsb-product__gallery .flex-control-thumbs {
  display: grid !important;
  grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  gap: 10px;
  margin: 12px 0 0 !important;
  padding: 0;
  list-style: none;
}

.tsb-product__gallery .flex-control-thumbs li {
  width: auto !important;
  float: none !important;
  margin: 0 !important;
}

.tsb-product__gallery .flex-control-thumbs img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border: 2px solid transparent;
  border-radius: var(--sb-r-img);
  background: var(--sb-photo);
  opacity: 1;
  cursor: pointer;
}

/* The selected thumbnail carries an ink border (mockup: Main.dc.html). */
.tsb-product__gallery .flex-control-thumbs img.flex-active,
.tsb-product__gallery .flex-control-thumbs img:hover {
  border-color: var(--sb-ink);
}

/* --- Video band ---------------------------------------------------------- */

.tsb-videoband {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 40px;
  margin-top: var(--sb-section);
  padding: 40px;
  background: var(--sb-soft);
  border-radius: var(--sb-r-band);
}

.tsb-videoband__media {
  position: relative;
  flex: 1 1 520px;
  min-width: 0;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: var(--sb-r-btn);
  background: var(--sb-photo);
}

.tsb-videoband__media iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.tsb-videoband__play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.tsb-videoband__thumb {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.tsb-videoband__playicon {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  background: var(--sb-bg);
  color: var(--sb-ink);
  box-shadow: var(--sb-shadow-panel);
}

.tsb-videoband__copy {
  flex: 1 1 320px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.tsb-videoband__copy p {
  margin: 0;
  color: var(--sb-ink-2);
}

/* --- Sizing -------------------------------------------------------------- */

.tsb-sizing__lead {
  margin: 8px 0 0;
  max-width: 560px;
  color: var(--sb-ink-2);
}

.tsb-sizing__scroll {
  margin-top: 24px;
  overflow-x: auto;
  border: 1px solid var(--sb-line);
  border-radius: var(--sb-r-btn);
}

.tsb-sizing__scroll table {
  width: 100%;
  min-width: 760px;
  border-collapse: collapse;
  font-size: var(--sb-size-small);
  text-align: center;
}

.tsb-sizing__scroll caption {
  padding: 12px 16px;
  text-align: left;
  color: var(--sb-muted);
  font-size: var(--sb-size-caption);
}

.tsb-sizing__scroll th,
.tsb-sizing__scroll td {
  padding: 12px 8px;
}

.tsb-sizing__scroll thead th {
  background: var(--sb-soft);
  font-weight: 600;
  white-space: nowrap;
}

.tsb-sizing__scroll tbody tr + tr {
  border-top: 1px solid var(--sb-line);
}

/* --- Reviews ------------------------------------------------------------- */

.tsb-review {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin: 0;
  padding: 24px;
  border: 1px solid var(--sb-line);
  border-radius: var(--sb-r-btn);
}

.tsb-review__photo {
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: var(--sb-r-img);
  background: var(--sb-photo);
}

.tsb-review__text {
  margin: 0;
  color: var(--sb-ink-2);
}

.tsb-review__text p {
  margin: 0;
}

.tsb-review__meta {
  font-size: var(--sb-size-caption);
  color: var(--sb-muted);
}

.tsb-reviewform {
  margin-top: 24px;
}

.tsb-reviewform summary {
  display: inline-flex;
  cursor: pointer;
}

/* Inside the disclosure only the form is wanted: the cards above already show
   the same reviews, so WooCommerce's own list is hidden there. */
.tsb-reviewform .commentlist {
  display: none;
}

.tsb-reviewform .woocommerce-Reviews-title {
  display: none;
}

/* --------------------------------------------------------------------------
   8. Slide-out cart
   -------------------------------------------------------------------------- */

/* Above WooCommerce's sticky mobile "Proceed to Checkout" bar and Kadence's
   own overlays (which sit in the 60-90 range). */
.tsb-minicart {
  position: fixed;
  inset: 0;
  z-index: 1000;
}

.tsb-minicart[hidden] {
  display: none;
}

.tsb-minicart__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(34, 32, 29, 0.45);
  border: 0;
  padding: 0;
  cursor: pointer;
}

.tsb-minicart__panel {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  width: 440px;
  max-width: 100%;
  background: var(--sb-bg);
  box-shadow: -12px 0 40px rgba(34, 32, 29, 0.18);
  animation: tsb-minicart-in 0.2s ease;
}

@keyframes tsb-minicart-in {
  from { transform: translateX(24px); opacity: 0.6; }
  to   { transform: translateX(0); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .tsb-minicart__panel {
    animation: none;
  }
}

.tsb-minicart__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex: none;
  padding: 20px 24px;
  border-bottom: 1px solid var(--sb-line);
}

.tsb-minicart__title {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-weight: 600;
}

.tsb-minicart__tick {
  display: flex;
  color: var(--sb-success);
}

/* The tick is hidden with the `hidden` attribute when the drawer was opened from
   the header icon rather than after an add-to-cart (QA round 3, D9); `display:
   flex` above would otherwise beat the attribute's UA `display: none`. */
.tsb-minicart__tick[hidden] {
  display: none;
}

.tsb-minicart__close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--sb-tap);
  height: var(--sb-tap);
  padding: 0;
  box-sizing: content-box;
  border: 0;
  border-radius: var(--sb-r-btn);
  background: none;
  color: var(--sb-ink);
  cursor: pointer;
}

/* Kadence styles every `button` — including `button:hover/:focus/:active` —
   from the global palette, which would paint this icon button in the accent
   colour and drop its padding on. The states are pinned explicitly. */
body .tsb-minicart__close,
body .tsb-minicart__close:hover,
body .tsb-minicart__close:focus,
body .tsb-minicart__close:active {
  padding: 0;
  border: 0;
  background: none;
  color: var(--sb-ink);
  box-shadow: none;
}

body .tsb-minicart__close:hover,
body .tsb-minicart__close:focus {
  background: var(--sb-chip);
}

.tsb-minicart__inner {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

.tsb-minicart__body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 20px;
  min-height: 0;
  overflow: auto;
  padding: 20px 24px;
}

.tsb-minicart__empty {
  margin: 0;
  color: var(--sb-ink-2);
}

.tsb-minicart__empty-link {
  margin: 0;
}

.tsb-line-item {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}

.tsb-line-item__media {
  flex: none;
  width: 72px;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--sb-photo);
  border-radius: var(--sb-r-img);
}

.tsb-line-item__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.tsb-line-item__info {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.tsb-line-item__name {
  font-weight: 600;
  color: var(--sb-ink);
  text-decoration: none;
}

.tsb-line-item__meta {
  font-size: var(--sb-size-caption);
  color: var(--sb-muted);
}

/* WooCommerce's classic `.woocommerce .remove` paints cart remove links red and
   bold (specificity 0,2,0); the wrapper raises this rule above it. */
body .tsb-minicart .tsb-line-item__remove,
body.tsb-surface-cart .tsb-line-item__remove {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  width: auto;
  height: auto;
  min-height: 32px;
  border-radius: 0;
  font-size: var(--sb-size-caption);
  font-weight: 400;
  line-height: 1.4;
  text-decoration: underline;
  /* WooCommerce styles this link as its round × control — `.woocommerce a.remove
     { color: red !important; width: 24px; ... }`. The `remove_from_cart_button`
     class is required for its remove-from-cart AJAX, so the design's colour has
     to be forced back over the !important. */
  color: var(--sb-muted) !important;
}

body .tsb-minicart .tsb-line-item__remove:hover,
body.tsb-surface-cart .tsb-line-item__remove:hover {
  color: var(--sb-ink) !important;
}

.tsb-line-item__price {
  font-weight: 600;
  white-space: nowrap;
}

.tsb-minicart__bundle {
  padding: 14px 16px;
  background: var(--sb-chip);
  border-radius: var(--sb-r-btn);
  font-size: var(--sb-size-small);
}

.tsb-minicart__upsell {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.tsb-upsell-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.tsb-upsell-row__media {
  flex: none;
  width: 56px;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--sb-photo);
  border-radius: var(--sb-r-img);
}

.tsb-upsell-row__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.tsb-upsell-row__info {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-width: 0;
}

.tsb-upsell-row__name {
  font-weight: 500;
  font-size: 0.9375rem;
  color: var(--sb-ink);
  text-decoration: none;
}

.tsb-upsell-row__price {
  font-size: var(--sb-size-caption);
  color: var(--sb-muted);
}

.tsb-minicart__foot {
  display: flex;
  flex: none;
  flex-direction: column;
  gap: 12px;
  padding: 20px 24px 24px;
  border-top: 1px solid var(--sb-line);
}

.tsb-minicart__subtotal {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: 1.125rem;
  font-weight: 600;
}

.tsb-minicart__note {
  margin: 0;
  font-size: var(--sb-size-caption);
  color: var(--sb-muted);
}

.tsb-minicart__express:empty {
  display: none;
}

.tsb-minicart__fullcart {
  text-align: center;
  font-size: var(--sb-size-small);
  color: var(--sb-ink);
}

/* --------------------------------------------------------------------------
   9. Cart page (block cart)
   -------------------------------------------------------------------------- */

/* DEFECT (QA): the Cart block carries WordPress' `alignwide` class from the
   page content, and Kadence's `.entry-content .alignwide` rule gives it
   `width: var(--global-vw, 100vw)`. WooCommerce zeroes the negative margins but
   not the width, so the block measured 1433px inside a 1144px column and the
   "Proceed to Checkout" button ended at 1537px — clipped with no way to scroll
   to it. Owning the width and the max-width here puts the block back inside its
   column while the negative margins stay at zero. */
.woocommerce-cart .entry-content > .wp-block-woocommerce-cart.alignwide,
body.tsb-surface-cart .wp-block-woocommerce-cart.alignwide {
  margin-inline: 0;
  width: 100%;
  max-width: 100%;
}

/* QA round 3, D10 — the cross-sells' notices container carries WordPress'
   `alignwide` class and measured 1433px wide at x=-293: 293px of it hangs off
   the left edge. It is empty (height 0) on a stable load so it never showed, but
   a populated notice would be clipped out of reach. Same class of overflow as
   the block above, so it gets the same treatment: own the width inside the
   column the block travels in. */
body.tsb-surface-cart .wc-block-components-notices.alignwide,
.woocommerce-cart .entry-content .wc-block-components-notices.alignwide {
  margin-inline: 0;
  width: 100%;
  max-width: 100%;
}

body.tsb-surface-cart .tsb-page-title {
  padding-top: 48px;
  margin-bottom: 32px;
  /* QA round 3, D7 — mockups/Cart.dc.html heads this page "Your cart" at 56px.
     `.tsb-page-title` tops out at 60px, which is the Shop archive's size (that
     mockup says 60px), so the cart gets its own scale rather than shrinking
     every page title. */
  font-size: clamp(2.25rem, 3.2vw + 0.9rem, 3.5rem);
}

/* Kadence reserves space above the page for its transparent header; on the
   cart surface the design starts 48px under the header like every other shop
   page, so the reserved gap is removed here. Kadence's own breadcrumb is also
   dropped: mockups/Cart.dc.html has none (the shop and product pages carry the
   design's own). */
body.tsb-surface-cart .content-area {
  margin-top: 0;
}

body.tsb-surface-cart .entry-content {
  margin-top: 0;
}

body.tsb-surface-cart .kadence-breadcrumbs {
  display: none;
}

body.tsb-surface-cart .wp-block-woocommerce-cart {
  display: block;
}

/* WooCommerce's own grid, on the design's gutters. */
body.tsb-surface-cart .wc-block-components-sidebar-layout {
  gap: 48px;
}

body.tsb-surface-cart .wp-block-woocommerce-cart-items-block,
body.tsb-surface-cart .wp-block-woocommerce-cart-totals-block {
  min-width: 0;
}

.tsb-cart__note {
  margin: 16px 0 0;
  font-size: var(--sb-size-small);
  color: var(--sb-muted);
}

.tsb-cart__continue {
  margin: 12px 0 0;
  text-align: center;
  font-size: var(--sb-size-small);
}

.tsb-cart__continue a {
  color: var(--sb-ink);
}

/* QA round 3, D6 — CLS, part 1 of 2: a deterministic row height.
   WooCommerce renders the item list from the Store API, so before it answers the
   table holds a two-row skeleton and every row's height depends on how each
   product's name and meta happen to wrap (measured on staging: 135px and 161px
   rows side by side at 1280px). A row's height is therefore pinned to the
   design's own row — mockups/Cart.dc.html draws a 96x120 thumbnail with 20px
   padding either side, i.e. 160px — and the table's height becomes a function of
   the item COUNT alone. Part 2 (the reservation) is the `min-height` below,
   fed by inc/shop/cart.php. Measured row heights before pinning: 135/161px at
   >=1024px, a uniform 153px at 1023-601px, 153-182px at <=600px. */
body.tsb-surface-cart {
  --tsb-cart-row: 153px;
  --tsb-cart-items-head: 0px;
}

@media (min-width: 1024px) {
  body.tsb-surface-cart {
    --tsb-cart-row: 161px;
    --tsb-cart-items-head: 40px;
  }
}

@media (max-width: 600px) {
  body.tsb-surface-cart {
    --tsb-cart-row: 182px;
  }
}

body.tsb-surface-cart .wc-block-cart-items__row {
  grid-template-columns: 96px 1fr auto;
  gap: 20px;
  padding: 20px 0;
  /* A table row and a grid item both treat `height` as a minimum, so longer
     content still wins and nothing is ever clipped. */
  height: var(--tsb-cart-row);
}

/* QA round 3, D6 — CLS, part 2 of 2: reserve the finished height up front.
   `--tsb-cart-rows` is printed by inc/shop/cart.php from the number of line
   items the server already knows, so the table occupies its final height from
   the first paint: the skeleton shows inside it and the item list never grows or
   shrinks under the note and the cross-sells. Measured on a four-item cart
   before this: the table grew 203px -> 633px and moved everything below it
   430px down, 0.0754 CLS at 1440px and 0.1122 at 390px. */
body.tsb-surface-cart .wc-block-cart-items {
  min-height: calc(var(--tsb-cart-rows, 0) * var(--tsb-cart-row) + var(--tsb-cart-items-head));
}

/* The skeleton rows themselves are dropped (JS on only — without scripting the
   skeleton is all the customer has) so nothing inside the reserved space moves
   either. */
html.tsb-js body.tsb-surface-cart .wc-block-cart-items__row:has(.wc-block-components-skeleton__element) {
  display: none;
}

/* QA round 3, D6 — CLS. `aspect-ratio` is inert on a table cell, so the cell
   sized itself from its contents: WooCommerce's 80px square loading skeleton,
   then the hydrated 4:5 image. Every row below moved when that swapped. Both
   boxes are pinned to the 96x120 slot mockups/Cart.dc.html draws (a `height` on
   a `td` is the row's minimum height), so the skeleton and the real image occupy
   the same space and hydration cannot change the row. */
body.tsb-surface-cart .wc-block-cart-item__image,
body.tsb-surface-cart td.wc-block-cart-item__image {
  width: 96px;
  height: 120px;
  padding: 0;
  margin: 0;
  border-radius: var(--sb-r-img);
  background: var(--sb-photo);
}

body.tsb-surface-cart .wc-block-cart-item__image .wc-block-components-skeleton,
body.tsb-surface-cart .wc-block-cart-item__image .wc-block-components-skeleton__element {
  width: 96px;
  height: 120px;
  border-radius: var(--sb-r-img);
}

body.tsb-surface-cart .wc-block-cart-item__image img {
  display: block;
  /* 4:5 at the design's 96px slot. `max-width` is `none` on purpose: left at
     100% it resolves against the inline <a> wrapping the image, which is only as
     wide as the 80x80 file WooCommerce's Store API hands over, so the image came
     out 80x120 — a stretched thumbnail. */
  width: 96px;
  height: 120px;
  aspect-ratio: 4 / 5;
  max-width: none;
  max-height: none;
  object-fit: cover;
  border-radius: var(--sb-r-img);
  background: var(--sb-photo);
}

body.tsb-surface-cart .wc-block-cart-item__product {
  font-weight: 600;
}

body.tsb-surface-cart .wc-block-cart-item__total {
  font-weight: 600;
}

body.tsb-surface-cart .wc-block-components-totals-wrapper,
body.tsb-surface-cart .wc-block-components-order-summary-item {
  border-color: var(--sb-line);
}

body.tsb-surface-cart .wc-block-cart__submit-button,
body.tsb-surface-cart .wc-block-components-button:not(.is-link) {
  min-height: var(--sb-tap);
  border-radius: var(--sb-r-btn);
  background: var(--sb-accent-strong);
  border-color: var(--sb-accent-strong);
  /* QA round 3, D2 — white text on an accent fill is `--sb-surface`, not the
     page cream `--sb-bg`: 4.52:1 instead of 4.31:1. */
  color: var(--sb-surface);
  font-weight: 600;
}

body.tsb-surface-cart .wc-block-cart__submit-button:hover,
body.tsb-surface-cart .wc-block-components-button:not(.is-link):hover {
  background: var(--sb-accent-hover);
  border-color: var(--sb-accent-hover);
  color: var(--sb-surface);
}

body.tsb-surface-cart .wc-block-components-totals-item__description,
body.tsb-surface-cart .wc-block-cart-item__prices {
  color: var(--sb-muted);
}

body.tsb-surface-cart .wc-block-cart__empty-cart__title {
  font-family: var(--sb-font-display);
  font-weight: 400;
}

/* QA round 4, D13 — the empty cart's mobile CLS (0.2202 at 390px, 4/4 samples;
   0.0854 at 1440px), the worst layout shift in the build.
   Cause, measured on staging at 390px: the block cart paints WooCommerce's
   *filled-cart* skeleton for as long as its Store API request takes (measured
   3.5-4.4s on a cold cache: `.wp-block-woocommerce-cart.is-loading`, 751px
   tall, and WooCommerce's own `assets/client/blocks/cart.css` sets
   `.is-loading .wp-block-woocommerce-empty-cart-block{display:none}`). When
   the answer arrives the class is dropped and the block renders the filled
   layout *once* with no items in it — 454px — and only on the next pass is
   the empty state rendered: 2509px. The footer therefore travels
   1048px -> 751px -> 2806px, crossing the fold twice, and every shift is
   attributed to `footer.tsb-footer` (0.0388 + 0.1102 in my instrumentation;
   QA's split was 0.05467 + 0.16556).
   Two things are needed, because CSS cannot stop the middle frame:

   1. `min-height: 100vh` on the block while the cart is empty. The server
      already knows the cart is empty (`body.tsb-cart-empty`, printed by
      inc/shop/shop.php), so the reservation is unconditional while that class
      is present. It is `100vh` on purpose: the footer must stay below the
      fold for the *whole* transition, and the fold depends on the viewport,
      not on the content. Reserving only the empty block's own height (830px
      at 1440px, 2477px at 390px) leaves the footer inside the fold on a tall
      desktop viewport and the shift comes straight back. Nothing is added
      below 600px, where the empty state (2509px) is already taller than any
      phone viewport; on a wide-and-short desktop the block gains the
      difference to one viewport, which is the same "keep the footer at the
      bottom" rule Kadence already applies to `#wrapper`.

   2. Render the empty state from the first paint instead of the filled
      skeleton, so the customer never sees a cart skeleton that the page
      itself knows is wrong. This is scoped to `.is-loading`, which exists
      only until hydration: once dropped, the rules stop applying and the
      block's own JavaScript owns the DOM again, so an item added later by
      AJAX (the drawer, or the cross-sells' Add buttons on this page) cannot
      be hidden by a stale `tsb-cart-empty`. Specificity (0,4,1) against
      WooCommerce's (0,3,0) is what wins here — no `!important`.
   Measured with both in place, cold cache, `LayoutShift` observer: CLS
   0.00000 at 390px and 0.00000 at 1440px, page height unchanged (3837px /
   1615px). */
body.tsb-cart-empty .wp-block-woocommerce-cart {
  min-height: 100vh;
}

body.tsb-cart-empty .wp-block-woocommerce-cart.is-loading .wp-block-woocommerce-empty-cart-block {
  display: block;
}

body.tsb-cart-empty .wp-block-woocommerce-cart.is-loading .wp-block-woocommerce-filled-cart-block {
  display: none;
}

/* The cross-sells block on the cart page ("Goes well with") sits in the items
   column, exactly where the mockup puts it. */
body.tsb-surface-cart .wp-block-woocommerce-product-collection .wp-block-heading {
  font-family: var(--sb-font-display);
  font-weight: 400;
  font-size: 2rem;
  line-height: 1.1;
  margin-bottom: 20px;
}

body.tsb-surface-cart .wp-block-woocommerce-product-collection .wc-block-components-product-image,
body.tsb-surface-cart .wp-block-woocommerce-product-collection .wc-block-components-product-image img {
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--sb-r-img);
  background: var(--sb-photo);
  width: 100%;
}

/* --------------------------------------------------------------------------
   11. WooCommerce loose ends
   -------------------------------------------------------------------------- */

/* WooCommerce appends its own "View cart" link after an AJAX add-to-cart; the
   drawer already offers "View full cart", so the stray link is hidden. */
.added_to_cart.wc-forward {
  display: none !important;
}

.single_add_to_cart_button.loading {
  opacity: 0.7;
  pointer-events: none;
}

/* QA round 3, D1 — the product page's "Add to cart" painted the RAW accent
   (#C17F59) behind white text = 3.26:1 on every product page. The button carries
   `.button.alt`, and Kadence's global button palette outranks both
   `.tsb-btn--primary` (0,1,0) and base.css' `.single_add_to_cart_button.button`
   (0,2,0) with `.woocommerce a.button.alt, .woocommerce button.button.alt, …`
   (0,3,1): `background-color: var(--global-palette-btn-bg)` (#C17F59) and
   `color: var(--global-palette-btn)` (#FFFFFF). Restating the pair here at
   (0,4,0) wins the cascade and uses the tokens that actually carry white text —
   accent-strong 4.57:1, accent-hover 5.88:1.
   The systemic fix is tokens.css: mapping `--global-palette-btn-bg` and
   `--global-palette-btn` to `--sb-accent-strong` / `--sb-surface` there would
   cover every `.alt` button on the site at once (checkout included). That file
   is not shop-owned, so it is reported rather than edited. */
.woocommerce .single_add_to_cart_button.button.alt {
  background-color: var(--sb-accent-strong);
  border-color: var(--sb-accent-strong);
  color: var(--sb-surface);
}

.woocommerce .single_add_to_cart_button.button.alt:hover,
.woocommerce .single_add_to_cart_button.button.alt:focus {
  background-color: var(--sb-accent-hover);
  border-color: var(--sb-accent-hover);
  color: var(--sb-surface);
}

/* No page scroll behind the open drawer. */
body.tsb-minicart-open {
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   12. Small screens
   -------------------------------------------------------------------------- */

@media (max-width: 1023px) {
  .tsb-band--soft {
    padding: 56px 0;
  }

  .tsb-course__body {
    padding: 32px 24px;
  }

  .tsb-freeband {
    padding: 32px 24px;
  }

  .tsb-videoband {
    padding: 24px;
  }

  /* The filter sidebar becomes a "Filters" button that opens a sheet. The
     details element is the button and the sheet at once, so it still works
     with JavaScript switched off (it is then a plain disclosure). */
  .tsb-filters {
    flex: 1 1 100%;
    max-width: none;
  }

  .tsb-filters__toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    min-height: var(--sb-tap);
    padding: 0 18px;
    border: 1px solid var(--sb-line);
    border-radius: var(--sb-r-btn);
    background: var(--sb-bg);
    color: var(--sb-ink);
    font-weight: 600;
    list-style: none;
    cursor: pointer;
  }

  .tsb-filters__toggle::-webkit-details-marker {
    display: none;
  }

  .tsb-filters__toggle-dot {
    width: 8px;
    height: 8px;
    margin-left: auto;
    border-radius: 50%;
    background: var(--sb-accent-strong);
  }

  .tsb-filters[open] .tsb-filters__toggle-chevron {
    transform: rotate(180deg);
  }

  .tsb-filters[open] .tsb-filters__panel {
    padding-top: 20px;
  }

  html.tsb-js .tsb-filters[open] {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 80;
    max-width: none;
    margin: 0;
    background: var(--sb-bg);
    border-radius: var(--sb-r-band) var(--sb-r-band) 0 0;
    box-shadow: 0 -12px 40px rgba(34, 32, 29, 0.18);
  }

  html.tsb-js .tsb-filters[open]::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    background: rgba(34, 32, 29, 0.45);
  }

  html.tsb-js .tsb-filters[open] .tsb-filters__panel {
    max-height: 72vh;
    overflow: auto;
    padding: 20px 24px 32px;
  }
}

@media (max-width: 767px) {
  .tsb-grid {
    gap: 24px 16px;
  }

  .tsb-grid--products {
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  }

  .tsb-hero {
    padding-top: 24px;
  }

  .tsb-hero__inner,
  .tsb-product__top {
    gap: 32px;
  }

  .tsb-hero__photo--2 {
    margin-top: 24px;
  }

  .tsb-hero__ctas .tsb-btn,
  .tsb-freeband .tsb-btn {
    width: 100%;
  }

  /* Full width on phones, as the mockup specifies for the drawer. */
  .tsb-minicart__panel {
    width: 100%;
  }

  .tsb-band--soft {
    padding: var(--sb-section-sm) 0;
  }

  .tsb-section {
    margin-top: var(--sb-section-sm);
  }

  .tsb-videoband,
  .tsb-review {
    padding: 20px;
  }

  .tsb-product__gallery .flex-control-thumbs {
    grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
  }
}

/* --------------------------------------------------------------------------
   Campaign band (home page, above the hero)

   A dark, full-bleed promo strip: label, headline, one line of copy, a primary
   button and a secondary link, with the product photo on the right. Rendered
   from template-parts/home/campaign.php; every string and the image come from
   theme mods so a campaign is switched by the Customizer, not by code.
   -------------------------------------------------------------------------- */

.tsb-campaign {
  background: var(--sb-ink);
  border-radius: var(--sb-r-band);
  color: var(--sb-bg);
  padding: 26px 32px;
}

.tsb-campaign__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 32px;
}

.tsb-campaign__copy {
  flex: 1 1 460px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}

.tsb-campaign__eyebrow {
  margin: 0;
  color: var(--sb-accent-tint);
}

.tsb-campaign__title {
  margin: 0;
  font-family: var(--sb-font-display);
  font-size: var(--sb-size-h2);
  font-weight: 400;
  line-height: 1.08;
  color: var(--sb-bg);
}

.tsb-campaign__text {
  margin: 0;
  max-width: 56ch;
  color: rgba(250, 248, 245, 0.82);
}

.tsb-campaign__ctas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 18px;
  margin-top: 6px;
}

.tsb-campaign__link {
  color: var(--sb-bg);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.tsb-campaign__link:hover,
.tsb-campaign__link:focus {
  color: var(--sb-accent-tint);
}

.tsb-campaign__media {
  flex: 0 0 auto;
  width: 280px;
  align-self: center;
}

/* 280x350 is the source photo's own 4:5 aspect, so the campaign image is shown
   whole — head to ankle — instead of being sliced into a letterbox strip. */
.tsb-campaign__media img {
  display: block;
  width: 100%;
  height: 350px;
  object-fit: cover;
  object-position: 50% 50%;
  border-radius: var(--sb-r-band);
}

@media (max-width: 782px) {
  .tsb-campaign {
    padding: 18px 20px;
  }

  .tsb-campaign__inner {
    gap: 14px;
  }

  .tsb-campaign__media {
    order: -1;
    width: 66%;
    margin-inline: auto;
  }

  .tsb-campaign__media img {
    height: auto;
    aspect-ratio: 4 / 5;
  }

  .tsb-campaign__title {
    font-size: 1.75rem;
  }

  .tsb-campaign__ctas {
    gap: 12px;
  }
}
