﻿/* ==========================================================================
   EXTENSIONS LIST
   ========================================================================== */
/* ======================================
   EXTENSIONS / BREADCRUMBS / BREADCRUMBS
   ====================================== */
.x-breadcrumbs {
  margin-bottom: 1em;
}

@media (min-width: 60em) {
  .x-breadcrumbs {
    margin: 0 auto calc(var(--default-spacing-unit) * 2);
    max-width: var(--default-content-width);
    width: 100%;
  }
}
.x-breadcrumbs__list {
  border-bottom: 0 none;
  display: flex;
  flex-wrap: wrap;
  gap: 1em 0;
  line-height: inherit;
  list-style: none;
  margin-bottom: 0;
  margin-left: auto;
  margin-right: auto;
  max-width: var(--default-wrapper-width);
  padding-left: var(--default-spacing-unit);
  padding-right: var(--default-spacing-unit);
  padding-top: 1rem;
}

.x-breadcrumbs__item:not(:last-child) {
  margin-right: 1em;
}

.x-breadcrumbs__item:not(:last-of-type) a::after {
  border-right: 0.2ch solid currentColor;
  content: "";
  display: inline-block;
  height: 1.75ch;
  position: relative;
  right: -0.5em;
  top: 0.25ch;
  transform: rotate(15deg);
}

.x-breadcrumbs a {
  display: inline-block;
}

/* ==========================================================================
   EXTENSIONS / CATEGORY TREE / CATEGORY TREE
   ========================================================================== */
.x-category-tree {
  margin-bottom: var(--default-spacing-unit);
  position: relative;
}

.x-category-tree__trigger {
  display: none;
}

.x-category-tree__label.x-collapsible-content__toggle {
  border-bottom: var(--default-input-border);
  margin: 0 0 1em;
  min-height: 2em;
  padding: 1em 0;
}

.x-category-tree .x-collapsible-content__item[aria-hidden=false],
.x-category-tree .x-collapsible-content__item[aria-hidden=false] .x-category-tree__row {
  max-height: 100%;
}

.x-collapsible-content .x-category-tree__title {
  display: none;
}

.x-category-tree__heading {
  margin-bottom: 1em;
}

.x-category-tree__heading--facets {
  align-items: center;
  display: flex;
  justify-content: space-between;
}

.x-category-tree__heading--facets a > * {
  display: inline-block;
  margin-right: 0.5em;
  text-decoration: none;
}

.x-category-tree__row {
  list-style: none;
  margin: 0;
  padding: 0;
  width: 100%;
}

.x-collapsible-content .x-category-tree__row {
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  transition: 0.2s ease-in-out;
  visibility: hidden;
}

.x-category-tree__link,
.x-category-tree__link-set,
.x-category-tree__text {
  display: block;
  line-height: 2;
}

.x-category-tree__link,
.x-category-tree__text {
  margin: 1em 0;
}

.x-category-tree__link,
.x-category-tree__link-set {
  transition: color 0.2s ease-in-out;
}

.x-category-tree__link {
  text-decoration: none;
}

.x-category-tree__link.u-text-underline {
  text-decoration: underline;
}

.x-category-tree__link:is(:focus, :focus-within, :hover),
.x-category-tree__link--current {
  text-decoration: underline;
  text-decoration-thickness: 0.1em;
  text-underline-offset: 0.2em;
}

.x-category-tree__section-title:not(:first-of-type) {
  margin-top: 2.5rem;
}

.x-category-tree__list--level-2 .x-category-tree__link,
.x-category-tree__list--level-3 .x-category-tree__link {
  font-size: smaller;
}

.x-category-tree__list--level-2 .x-category-tree__link {
  padding-left: 1em;
}

.x-category-tree__list--level-3 .x-category-tree__link {
  padding-left: 2em;
}

@media (min-width: 60em) {
  .x-category-tree__label.x-collapsible-content__toggle {
    display: none;
  }
  .x-collapsible-content .x-category-tree__title {
    display: block;
  }
  .x-collapsible-content .x-category-tree__row,
  .x-category-tree__row.x-collapsible-content__item {
    max-height: none;
    opacity: 1;
    overflow: visible;
    visibility: visible;
  }
}
/**
 * This section contains the necessary styles when using the built-in search
 * faceting ability of Miva. Since the functional code is generated at run-time,
 * these styles do not conform to the tenants used in the rest of the framework.
 */
.mm_facet_rangeslider {
  display: block;
  height: 2em;
  margin: 0.25em;
  position: relative;
}

.mm_facet_rangeslider_input_container_low,
.mm_facet_rangeslider_input_container_high {
  border-bottom: var(--default-border-width) solid var(--gray-300);
  height: 2em;
  position: absolute;
  text-align: center;
  top: 0;
  width: 4em;
}

.mm_facet_rangeslider_input_container_low {
  border-radius: 0.25em 0 0 0.25em;
  left: 0;
}

.mm_facet_rangeslider_input_container_high {
  border-radius: 0 0.25em 0.25em 0;
  right: 0;
}

.mm_facet_rangeslider_input_container_low.mm_facet_rangeslider_input_active,
.mm_facet_rangeslider_input_container_high.mm_facet_rangeslider_input_active {
  border-color: var(--gray-300);
}

.mm_facet_rangeslider_track_container {
  border-bottom: var(--default-border-width) solid var(--gray-300);
  border-top: var(--default-border-width) solid var(--gray-300);
  box-sizing: border-box;
  height: 2em;
  left: 4em;
  position: absolute;
  right: 4em;
  top: 0;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
          user-select: none;
}

.mm_facet_rangeslider_track {
  height: 0.25em;
  left: 0.25em;
  position: absolute;
  right: 0.25em;
  top: 50%;
  transform: translateY(-50%);
}

.mm_facet_rangeslider_track_background,
.mm_facet_rangeslider_track_selection {
  background: var(--gray-300);
  bottom: 0;
  left: 0.5em;
  position: absolute;
  right: 0.5em;
  top: 0;
}

.mm_facet_rangeslider_track_selection {
  background: var(--gray-300);
}

.mm_facet_rangeslider_track_handle_low,
.mm_facet_rangeslider_track_handle_high {
  background: var(--white);
  border: var(--default-border-width) solid var(--gray-300);
  border-radius: 50%;
  cursor: pointer;
  height: 2em;
  left: -0.5em;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 2em;
}

.mm_facet_rangeslider.mm_facet_rangeslider_mini {
  height: 6em;
  margin: 0;
}

.mm_facet_rangeslider.mm_facet_rangeslider_mini .mm_facet_rangeslider_input_container_low {
  border-radius: 0.25em 0 0 0;
  width: 50%;
}

.mm_facet_rangeslider.mm_facet_rangeslider_mini .mm_facet_rangeslider_input_container_high {
  border-left: none;
  border-radius: 0 0.25em 0 0;
  width: 50%;
}

.mm_facet_rangeslider.mm_facet_rangeslider_mini .mm_facet_rangeslider_track_container {
  border: var(--default-border-width) solid var(--gray-300);
  border-radius: 0 0 0.25em 0.25em;
  border-top: none;
  height: 3em;
  left: 0;
  right: 0;
  top: 2em;
}

.mm_facet_rangeslider.mm_facet_rangeslider_mini .mm_facet_rangeslider_input_container_low.mm_facet_rangeslider_input_active,
.mm_facet_rangeslider.mm_facet_rangeslider_mini .mm_facet_rangeslider_input_container_high.mm_facet_rangeslider_input_active {
  border-color: var(--gray-200);
  box-shadow: inset 2px 2px 5px rgba(0, 0, 0, 0.15);
}

/* ==========================================================================
   EXTENSIONS / DISPLAY LIST FILTERING / DISPLAY LIST FILTERING
   ========================================================================== */
/**
 * Display list filtering is for use in product list filtering as well as any
 * place a batch list or sortable data is used.
 */
.x-display-list-filtering {
  display: flex;
  flex-wrap: nowrap;
  margin-bottom: var(--default-spacing-unit);
}

.x-display-list-filtering :is(.x-display-list-filtering__per-page, .x-display-list-filtering__sort-by, .c-form-list__item, .c-form-label, .c-form-select) {
  margin-bottom: 0;
}

.x-display-list-filtering__per-page {
  margin-right: calc(var(--default-spacing-unit) * 4);
}

/**
 * Product List Page Modifications
 */
:is(.t-page-ctgy, .t-page-plst, .t-page-srch) :is(.x-display-list-filtering .c-form-select__dropdown) {
  border: 0 none;
  padding: 0;
}

:is(.t-page-ctgy, .t-page-plst, .t-page-srch) :is(.x-display-list-filtering .c-form-select)::after {
  right: 0;
}

/* ==========================================================================
   EXTENSIONS / HERO
   ========================================================================== */
/**
 * The Hero component provides a dominant piece of media to a page
 *
 * 1. Allows for absolute positioning of borders and content.
 * 2. Hero images set via background-image should cover and center.
 */
.x-hero {
  background-position: center; /* [2] */
  background-size: cover; /* [2] */
  display: block;
  position: relative; /* [1] */
}

/**
 * The caption overlays at the bottom of a Hero, providing space for additional
 * content such as titles or logos.
 */
.x-hero__caption {
  bottom: 0;
  padding: var(--default-spacing-unit) 0;
  position: absolute;
  text-shadow: 0 0 25px rgba(0, 0, 0, 0.25), 1px 1px 1px rgba(0, 0, 0, 0.5);
  width: 100%;
}

/**
 * Hero video needs to be set to `display: block` to avoid spacing issues.
 */
.x-hero__video {
  display: block;
  height: 100%;
  -o-object-fit: fill;
     object-fit: fill;
  width: 100%;
}

/**
 * Hero link inherits the Hero `color` to override anchor element styles.
 */
.x-hero__link {
  color: inherit;
}

/**
 * As we cannot set a border property over video, borders are implemented
 * using pseudo-elements.
 */
.x-hero--borders::before {
  border: calc(var(--default-spacing-unit) / 4) solid rgba(255, 255, 255, 0.3);
  bottom: 0;
  content: "";
  display: block;
  left: 0;
  position: absolute;
  right: 0;
  top: 0;
}

/**
 * Make any other content below the Hero overlap upwards.
 */
.x-hero--overlap {
  margin-bottom: -10vh;
}

/**
* If hero has overlap modifier, shift caption upwards to compensate.
*/
.x-hero--overlap .x-hero__caption {
  bottom: 10vh;
}

/* ==========================================================================
   EXTENSIONS / MESSAGES
   ========================================================================== */
/**
 * Messages are block elements used to display information to the visitor about
 * their current session. Typically these are informational or error messages
 * such as you would see on a checkout page if there was an error with the
 * credit card used.
 */
.x-messages {
  background-color: var(--gray-200);
  border: var(--default-border-width) solid var(--gray-500);
  color: var(--black);
  font-size: 0.875rem;
  margin-bottom: var(--default-spacing-unit);
  padding: 0.875em 1.25em;
}

.x-messages *:last-child {
  margin-bottom: 0;
}

.x-messages--borderless {
  border: 0 none;
}

.x-messages--error {
  background-color: var(--red-100);
  border-color: var(--red-200);
  color: var(--red-400);
}

.x-messages--info {
  background-color: var(--blue-100);
  border-color: var(--blue-200);
  color: var(--blue-400);
}

.x-messages--success {
  background-color: var(--green-100);
  border-color: var(--green-200);
  color: var(--green-400);
}

.x-messages--warning {
  background-color: var(--yellow-100);
  border-color: var(--yellow-200);
  color: var(--yellow-400);
}

/**
 * These classes will help format the message if you are adding an icon.
 */
.x-messages--with-icon {
  align-items: center;
  display: flex;
  gap: 1.25em;
}

.x-messages__icon {
  font-size: 2em;
}

.x-messages__close {
  line-height: 1;
  margin-top: -1rem;
  position: absolute;
  right: 0.25rem;
  text-decoration: none;
  top: 50%;
}

/* ==========================================================================
   EXTENSIONS / PAGINATION
   ========================================================================== */
/**
 * Pagination is for use in product list as well as any place a pageable batch
 * list or data is used.
 */
.x-pagination {
  margin-bottom: auto var(--default-spacing-unit);
  max-width: 18rem;
}

.x-pagination--centered {
  justify-content: center;
  margin-left: auto;
  margin-right: auto;
}

.x-pagination--right {
  margin-left: auto;
}

.x-pagination__list {
  align-items: center;
  display: flex;
  gap: var(--default-spacing-unit);
  justify-content: space-between;
  list-style: none;
  margin: 0;
  width: 100%;
}

.x-pagination__link {
  border-color: var(--transparent);
  border-radius: 50%;
  color: var(--gray-600);
  height: 3rem;
  padding: 0;
  width: 3rem;
}

.x-pagination .c-form-select {
  margin-bottom: 0;
}

/* ==========================================================================
   EXTENSIONS / PRODUCT LAYOUT / PRODUCT LAYOUT
   ========================================================================== */
/**
 * This is the default product layout for the product page.
 */
.x-product-layout {
  margin-bottom: calc(var(--default-spacing-unit) * 2);
}

@media (min-width: 60em) {
  .x-product-layout {
    padding-top: calc(var(--default-spacing-unit) * 2);
  }
}
.x-product-layout-images {
  display: flex;
  flex-flow: column nowrap;
  gap: 1.25rem;
  margin-left: 0;
  min-height: var(--prod-img-y);
}

@media (min-width: 60em) {
  .x-product-layout-images {
    flex-direction: row-reverse;
    max-height: var(--prod-img-y);
    min-height: 100%;
    overflow: hidden;
    place-content: flex-start flex-end;
  }
}
.x-product-layout-images__figure,
.x-product-layout-images__picture,
.x-product-layout-images__image {
  display: block;
  margin-bottom: 0;
}

.x-product-layout-images__figure {
  flex-basis: 100%;
}

.x-product-layout-images__image {
  aspect-ratio: var(--prod-img-x)/var(--prod-img-y);
  max-height: calc(var(--prod-img-y) * 1px);
  -o-object-fit: contain;
     object-fit: contain;
  -o-object-position: center;
     object-position: center;
  width: 100%;
}

.x-product-layout-images__thumbnail-image {
  margin-bottom: var(--default-spacing-unit);
}

.x-product-layout-purchase {
  margin-bottom: 0;
}

.x-product-layout-purchase .c-form-list__item {
  margin-bottom: 0;
}

.x-product-layout-purchase__sku {
  display: block;
  font-size: var(--message-one);
  font-weight: var(--font-medium);
}

.x-product-layout-purchase__name {
  margin-bottom: calc(var(--default-spacing-unit) * 1.5);
}

.x-product-layout-purchase__pricing {
  margin-bottom: var(--message-one);
}

.x-product-layout-purchase__pricing-original,
.x-product-layout-purchase__pricing-current {
  font-size: var(--subheading-small);
}

.x-product-layout-purchase__pricing-current {
  font-weight: var(--font-medium);
  padding-right: var(--default-spacing-unit);
}

.x-product-layout-purchase__product-discounts {
  margin-bottom: 2rem;
}

.x-product-layout-purchase__product-discounts *,
.x-product-layout-purchase__product-weight,
.x-product-layout-purchase__product-quantity {
  font-size: var(--message-one);
  font-weight: var(--font-medium);
  line-height: var(--title-four);
}

.x-product-layout-purchase__product-discounts * {
  color: var(--red-400);
}

.x-product-layout-purchase__inventory-message:empty {
  display: none;
}

.x-product-layout-purchase__options,
.x-product-layout-purchase__cta {
  max-width: none;
  width: auto;
}

@media (min-width: 75em) {
  .x-product-layout-purchase__pricing,
  .x-product-layout-purchase__inventory-message,
  .x-product-layout-purchase__options,
  .x-product-layout-purchase__cta {
    max-width: 60%;
    width: 60%;
  }
}
.x-product-layout-purchase__options-attribute {
  margin-bottom: calc(var(--default-spacing-unit) / 2);
  width: 100%;
}

.x-product-layout-purchase__swatches {
  min-height: 5em;
  padding-top: 3px;
}

.x-product-layout-purchase__swatches button {
  background-color: transparent;
  border: 0 none;
  padding: 0;
}

.x-product-layout-purchase__swatches img {
  border: var(--default-input-border);
  border-radius: 50%;
  height: 48px;
  width: 48px;
}

.x-product-layout-purchase__swatches--active {
  border-radius: 50%;
  outline: var(--default-input-border--focus);
  outline-offset: 0.25rem;
}

.x-product-layout-purchase__cta .x-quantify {
  height: calc(3.75em - var(--default-border-width));
}

.x-product-description {
  margin-bottom: calc(var(--default-spacing-unit) * 6);
  padding-top: calc(var(--default-spacing-unit) * 4);
}

/* ==========================================================================
   EXTENSIONS / PRODUCT LISTS / PRODUCT LISTS
   ========================================================================== */
/**
 * Product lists are used in many spots throughout the store. They can be found 
 * on the storefront as featured product, the related products section of a 
 * product page, as an auto-suggest feature during checkout, or wherever.
 */
.x-product-list,
.x-subcategory-list {
  display: grid;
  gap: calc(var(--default-spacing-unit) * 2);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-bottom: var(--default-spacing-unit);
}

.x-subcategory-list {
  grid-template-columns: repeat(auto-fit, minmax(0, 6.5rem));
  justify-content: center;
}

@media (min-width: 60em) {
  .x-product-list {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
  .x-subcategory-list {
    grid-template-columns: repeat(auto-fit, minmax(0, 9.5rem));
  }
}
.x-product-list__heading {
  margin-bottom: 2em;
}

.x-product-list__item,
.x-subcategory-list__item {
  position: relative;
}

.x-product-list__item {
  display: flex;
}

.x-product-list__figure,
.x-product-list__figure-caption,
.x-subcategory-list__figure,
.x-subcategory-list__figure-caption {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.x-product-list__figure-caption {
  flex-grow: 1;
  gap: 1em;
  justify-content: space-between;
}

.x-subcategory-list__figure-caption {
  font-weight: var(--font-medium);
  text-align: center;
}

.x-product-list__picture,
.x-subcategory-list__picture {
  aspect-ratio: 1;
  align-items: center;
  display: flex;
  justify-content: center;
  overflow: hidden;
  width: 100%;
}

.x-product-list__link,
.x-subcategory-list__link {
  text-decoration: none;
}

.x-product-list__link:hover,
.x-subcategory-list__link:hover {
  text-decoration: underline;
}

.x-product-list__link::before,
.x-subcategory-list__link::before {
  bottom: 0;
  content: "";
  cursor: pointer;
  left: 0;
  position: absolute;
  right: 0;
  top: 0;
  transition: box-shadow 250ms;
  z-index: 1;
}

.x-product-list__name {
  -webkit-box-orient: vertical;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.x-product-list__price {
  font-size: var(--default-input-font-size);
}

.x-product-list__price_set {
  display: inline-flex;
  gap: 1em;
}

/**
* Modifications for related products.
*/
.x-product-list--related {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (min-width: 48em) {
  .x-product-list--related {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
@media (min-width: 75em) {
  .x-product-list--related {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
@media (min-width: 90em) {
  .x-product-list--related {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}
/* ==========================================================================
   EXTENSIONS / SUBSCRIPTIONS / SUBSCRIPTIONS
   ========================================================================== */
/**
 * When using the subscription function of Miva, this is the default styling
 * used for the date picker. You can update the styles in your theme file if
 * you are looking to make changes.
 */
.miva_datepicker {
  background-color: var(--white);
  border: var(--default-input-border);
  display: none;
  padding: var(--default-spacing-unit);
  position: absolute;
  z-index: 1000;
}

.miva_datepicker_calendar_container {
  display: inline-block;
  position: relative;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
          user-select: none;
  vertical-align: top;
  width: 214px;
}

.miva_datepicker_calendar_month_selector {
  align-items: center;
  background-color: var(--gray-400);
  display: flex;
  position: relative;
}

.miva_datepicker_calendar_month_selector_next,
.miva_datepicker_calendar_month_selector_prev {
  background: 0 -35px no-repeat;
  border: 0 none;
  cursor: pointer;
  height: 35px;
  padding: 0;
  width: 35px;
}

.miva_datepicker_calendar_month_selector_next:hover,
.miva_datepicker_calendar_month_selector_prev:hover {
  background-color: var(--gray-600);
}

.miva_datepicker_calendar_month_selector_title {
  color: var(--white);
  flex-grow: 1;
  text-align: center;
}

.miva_datepicker_calendar_month_selector_title_month {
  display: inline-block;
}

.miva_datepicker_calendar_month_selector_title_year {
  display: inline-block;
  margin-left: 0.25em;
}

.miva_datepicker_cancel {
  display: inline-block;
  margin-top: 0.75em;
}

.miva_datepicker_calendar_weekday_container {
  height: 34px;
  margin: 2px;
  position: relative;
}

.miva_datepicker_calendar_weekday_title {
  display: inline-block;
  font-weight: 700;
  padding: 3px 0;
  position: absolute;
  text-align: center;
  width: 28px;
}

#miva_datepicker_calendar_week_container {
  margin: 2px;
  position: relative;
}

.miva_datepicker_calendar_week {
  display: block;
  height: 30px;
  position: relative;
}

.miva_datepicker_calendar_day {
  align-items: center;
  border: var(--default-input-border);
  bottom: 1px;
  cursor: pointer;
  display: inline-flex;
  font-size: 0.75em;
  justify-content: center;
  position: absolute;
  top: 1px;
  width: 26px;
}

.miva_datepicker_calendar_day:hover {
  background-color: var(--gray-400);
  border-color: var(--gray-600);
  color: var(--white);
}

.miva_datepicker_calendar_day_noselect,
.miva_datepicker_calendar_day_noselect:hover {
  background-color: var(--gray-300);
  border-color: var(--gray-600);
  color: var(--gray-600);
}

.miva_datepicker_calendar_day_current {
  border-color: var(--gray-600);
  color: var(--gray-600);
  font-weight: 700;
}

.miva_datepicker_calendar_day_current:hover {
  background-color: var(--gray-600);
  color: var(--white);
}

.miva_datepicker_calendar_day_active {
  background-color: var(--gray-600);
  border-color: var(--gray-400);
  color: var(--white);
  font-weight: 700;
}

.miva_datepicker_calendar_day_0 {
  left: 1px;
}

.miva_datepicker_calendar_day_1 {
  left: 31px;
}

.miva_datepicker_calendar_day_2 {
  left: 61px;
}

.miva_datepicker_calendar_day_3 {
  left: 91px;
}

.miva_datepicker_calendar_day_4 {
  left: 121px;
}

.miva_datepicker_calendar_day_5 {
  left: 151px;
}

.miva_datepicker_calendar_day_6 {
  left: 181px;
}

/* ==========================================================================
   EXTENSIONS / SEARCH-PREVIEW / SEARCH-PREVIEW
   ========================================================================== */
.x-search-preview {
  background-color: var(--white);
  border: var(--default-input-border--focus);
  border-top: 0;
  border-radius: 0 0 var(--default-border-radius) var(--default-border-radius);
  display: none;
  flex-direction: column;
  font-size: var(--default-input-font-size);
  list-style: none;
  margin: 0;
  outline: none;
  padding: 0.5rem 0;
  position: absolute;
  right: calc(-1 * var(--default-border-width));
  width: calc(100% + 2 * var(--default-border-width));
  z-index: 500;
}

.x-search-preview--open {
  display: flex;
}

.x-search-preview__item {
  align-items: center;
  display: flex;
  gap: 1rem;
}

/**
 * This section contains the necessary styles when using the built-in search
 * preview ability of Miva. Since the functional code is generated at run-time,
 * these styles do not conform to the tenants used in the rest of the framework.
 */
.mm_searchfield_menuitem_selected {
  background-color: var(--gray-100);
}

.x-search-preview__image {
  flex-shrink: 0;
  height: var(--search-preview-image-y);
  margin-bottom: 0;
  overflow: hidden;
  width: var(--search-preview-image-x);
}

.x-search-preview__item-name {
  overflow-wrap: break-word;
  word-break: break-word;
}

.x-search-preview__entry,
.x-search-preview__search-all {
  cursor: pointer;
  padding: 0.8rem 1rem;
}

.x-search-preview__search-all {
  font-style: italic;
}

/**
 * Category + did-you-mean suggestion rows (2026-08-17), injected by
 * cssui-javascript-searchfield.mvt via ODSD_SUGGEST (extensions.js).
 * Category rows use the Home Depot treatment: distinct badge, name, and an
 * italic soft-ink scope ("in Speakers") so they read as navigation, not as
 * products. Rows carry .x-search-preview__entry, so padding, cursor and the
 * .mm_searchfield_menuitem_selected keyboard/hover state come from the
 * existing rules above.
 */
.x-search-preview__cat,
.x-search-preview__dym {
  align-items: baseline;
  display: flex;
  gap: 0.5rem;
}

.x-search-preview__cat-badge,
.x-search-preview__dym-badge {
  border: 1px solid var(--odsd-line);
  border-radius: 999px;
  color: var(--odsd-ink-soft);
  flex-shrink: 0;
  font-size: 0.625rem;
  letter-spacing: 0.08em;
  line-height: 1;
  padding: 0.25rem 0.5rem;
  text-transform: uppercase;
  white-space: nowrap;
}

.x-search-preview__cat-name {
  color: var(--odsd-ink);
  font-weight: 600;
}

.x-search-preview__cat-scope {
  color: var(--odsd-ink-soft);
  font-size: 0.8125rem;
  font-style: italic;
}

.x-search-preview__dym-name {
  color: var(--odsd-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The SRCH empty state's "Did you mean:" block (extensions.js). */
.odsd-cat__empty-dym-lead {
  font-weight: 600;
  margin: 1rem 0 0.25rem;
}

.odsd-cat__empty-dym-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.odsd-cat__empty-dym-list li {
  padding: 0.25rem 0;
}

.odsd-cat__empty-dym-kind {
  border: 1px solid var(--odsd-line);
  border-radius: 999px;
  color: var(--odsd-ink-soft);
  display: inline-block;
  font-size: 0.625rem;
  letter-spacing: 0.08em;
  line-height: 1;
  margin-right: 0.5rem;
  padding: 0.25rem 0.5rem;
  text-transform: uppercase;
}

.odsd-cat__empty-dym-list em {
  color: var(--odsd-ink-soft);
  font-size: 0.8125rem;
}

/* Zero-result auto-correct, checking state (owner feedback 2026-08-17): the
   server renders the empty state as .is-checking - children swapped for the
   one-line "Looking for close matches..." - so "No products matched" cannot
   paint and then flash away when the correction redirects. extensions.js
   removes .is-checking once staying is the final answer; the noscript style
   and the 3.5s inline timer in srch-search_results.mvt are the fallbacks. */
.odsd-cat__empty.is-checking > * {
  display: none;
}

.odsd-cat__empty-checking {
  color: var(--odsd-ink-soft);
  display: none;
}

.odsd-cat__empty.is-checking > .odsd-cat__empty-checking {
  display: block;
}

/* The corrected-search banner ("Showing results for X. Search instead for Y")
   injected above the SRCH toolbar by the auto-correct flow in extensions.js. */
.odsd-cat__corrected {
  color: var(--odsd-ink-soft);
  margin: 0 0 1rem;
}

.odsd-cat__corrected b {
  color: var(--odsd-ink);
}

.odsd-cat__corrected a {
  color: inherit;
  font-style: italic;
  text-decoration: underline;
}

/* ==========================================================================
   EXTENSIONS / MINI-BASKET / MINI-BASKET
   ========================================================================== */
/**
 * This is an extension to use the mini-basket functionality of Miva in an
 * off-canvas method.
 */
.x-mini-basket {
  background-color: rgba(34, 34, 34, 0.55);
  bottom: 0;
  display: flex;
  justify-content: flex-end;
  left: 0;
  opacity: 0;
  position: fixed;
  right: 0;
  top: 0;
  transition: all ease-in-out 0.2s;
  z-index: 500;
}

.x-mini-basket--open {
  opacity: 1;
}

/* [!] THE "PAGE ZOOMS WHEN I OPEN THE CART" BUG - fixed 2026-08-03.
   -------------------------------------------------------------------------
   Reported as the page jumping and "getting bigger then smaller", with a faint
   white outline round the screen and everything shifting a pixel or two. It was
   not a zoom and it was not the drawer animation at all.

   `extensions.js` toggles `u-overflow-hidden` on <html> to stop the page
   scrolling behind the open basket, and `core.css` defines that as
   `overflow: hidden`. On a desktop browser with a classic scrollbar, removing
   the scroll REMOVES THE SCROLLBAR - so the viewport gets roughly 15px wider,
   every centred container re-centres, and the whole page nudges sideways. Close
   it and the scrollbar returns and it all nudges back. The "white outline" is
   the scrollbar track disappearing.

   `scrollbar-gutter: stable` reserves that strip permanently, so locking and
   unlocking the scroll no longer changes the layout width. One line, and it
   fixes the same jump for the mobile nav drawer and the search overlay, which
   use the identical lock. Anything else that hides overflow on <html> inherits
   the fix for free.

   The cost is a reserved gutter on pages too short to scroll. That is the
   standard trade and it is far less noticeable than the shift it removes. */
html { scrollbar-gutter: stable; }

/* [!] KILL THE THEME'S OVERLAY TRANSFORM. Corrected 2026-08-03 after the first
   attempt failed in the owner's browser.

   The combined stylesheet carries FOUR competing rules on this element:

     .x-mini-basket        transform: scaleX(0); transform-origin: right
     .x-mini-basket--open  transform: scaleX(1)
     .x-mini-basket        transform: scaleY(0); transform-origin: top   <- wins
     .x-mini-basket--open  transform: scaleY(1)                          <- wins

   The scaleY pair is written later, so the OVERLAY unrolls downward from the
   top like a curtain, and because a scaled parent scales its children the panel
   appears to grow out of the top-right corner. That is what the store actually
   did, and animating the child alone could not overcome it.

   Neutralised here rather than edited at source: these live in the combined
   stylesheet, and extensions.css is served after it at equal specificity, so a
   plain override wins without `!important`. The transition is narrowed to
   `opacity` as well - the stock `transition: all` would animate any transform
   that came back. */
.x-mini-basket { transform: none; transition: opacity 0.22s ease-in-out; }
.x-mini-basket--open { transform: none; }

/* [!] COVER THE SCROLLBAR GUTTER TOO. Follow-on from the fix above, reported by
   the owner as "a really thin white border, like the dimmer didn't stretch all
   the way".

   `scrollbar-gutter: stable` reserves the scrollbar strip permanently, and the
   containing block for a `position: fixed` element EXCLUDES that strip. So
   `inset: 0` stops at the edge of the content box and leaves the reserved gutter
   undimmed - a pale line down the side of an otherwise darkened page, obvious
   precisely because everything around it just went dark.

   `100vw` / `100vh` are measured against the full viewport INCLUDING that strip,
   so the scrim reaches the real edge. Safe here because this element is only ever
   displayed while the page scroll is locked; it can never add a scrollbar of its
   own. `dvh` second so mobile browsers discount their toolbars. */
.x-mini-basket {
  left: 0; top: 0; right: auto; bottom: auto;
  width: 100vw;
  height: 100vh;
  height: 100dvh;
}
/* Safety net for removing that transform. `scaleY(0)` used to collapse the
   closed overlay to nothing; without it the element is a full-viewport layer
   held back only by the wrapper's `hidden` attribute. If anything ever shows it
   without adding `--open`, it would sit invisibly over the page and swallow
   every click. Costs nothing and removes that failure mode. */
.x-mini-basket { pointer-events: none; }
.x-mini-basket--open { pointer-events: auto; }

/* ---- SLIDE IN FROM THE RIGHT (2026-08-03) --------------------------------
   Owner request: the cart should come in from the right rather than appear in
   place. The panel was already anchored right (`justify-content: flex-end`) but
   nothing moved - the stock theme animated the whole overlay with
   `transform: scaleX(0)` from `transform-origin: right`, which SQUASHES the
   contents rather than sliding them, and our earlier override had dropped even
   that in favour of a plain opacity fade.

   [!] WHY THIS IS AN ANIMATION AND NOT A TRANSITION. The wrapper
   `#mini_basket` carries the `hidden` attribute, so the element goes from
   `display: none` straight to displayed. A transition cannot run across that -
   there is no frame in which the start value is rendered, so the panel would
   simply appear at its end state. A CSS animation DOES play when an element
   first becomes displayed, so it fires reliably every open. Do not "simplify"
   this back to a transition; it will silently stop animating.

   The panel is animated, not the overlay, so the scrim fades while the drawer
   travels - and the contents keep their proportions. */
.x-mini-basket--open .x-mini-basket__content {
  animation: odsd-minibasket-in 0.32s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}
@keyframes odsd-minibasket-in {
  from { transform: translateX(100%); }
  to   { transform: translateX(0); }
}

/* The SCRIM has to be animated for the same reason the panel does. The wrapper
   goes display:none -> displayed in one step, so its opacity transition never
   gets a starting frame and the dark layer lands at full strength instantly.
   A hard full-screen flash arriving at the same moment the panel moves is what
   makes the whole thing feel like it lurches. Fading it properly is most of
   what "smoother" means here. */
.x-mini-basket--open {
  animation: odsd-minibasket-scrim 0.28s ease-out both;
}
@keyframes odsd-minibasket-scrim {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* NOTE: `will-change: transform` was removed on 2026-08-03. It promotes the
   panel to its own compositor layer, and on a page that already runs a
   backdrop-filter header that re-raster can show up as a brief blur or scale
   wobble. The animation is a single short transform and does not need the hint. */

/* Respect a reduced-motion preference: still appears, just does not travel. */
@media (prefers-reduced-motion: reduce) {
  .x-mini-basket--open .x-mini-basket__content,
  .x-mini-basket--open { animation: none; }
}

/* ---- ODSD styling for the mini-basket (2026-08-01) -----------------------
   It was left as stock Shadows: the close control rendered as a bare ">"
   because it relies on the theme's icon font (u-icon-cross), and Checkout was
   a plain underlined link while every other CTA on the site is the black pill.
   Structure untouched - this is presentation only, per the presentation/
   structure rule. */
.x-mini-basket__header {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--odsd-line);
}
.x-mini-basket__summary {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: 1.05rem; color: var(--odsd-ink);
}
/* Draw the cross as a data-URI SVG. A CSS unicode escape was tried first and
   backfired: the escape was read as OCTAL and wrote a real NUL byte into this
   file. An SVG has no encoding to get wrong. */
.x-mini-basket [data-hook~="close-mini-basket"] {
  font-size: 0; line-height: 0; border: 0; background: transparent;
  cursor: pointer; padding: 0.35rem; border-radius: 50%;
  width: 34px; height: 34px; display: grid; place-items: center; flex: none;
  color: var(--odsd-ink);
}
.x-mini-basket [data-hook~="close-mini-basket"]::before {
  content: ""; width: 15px; height: 15px; background: currentColor;
  -webkit-mask: var(--odsd-x-icon) center / contain no-repeat;
          mask: var(--odsd-x-icon) center / contain no-repeat;
}
.x-mini-basket [data-hook~="close-mini-basket"]:hover { background: var(--odsd-mist, #ecebe6); }

.x-mini-basket { --odsd-x-icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round'><path d='M6 6l12 12M18 6L6 18'/></svg>"); }
/* The theme makes .x-mini-basket__cta a grid whose column is content-sized, so
   width:100% on the anchor resolved against 122px rather than the panel. Pin
   the track instead of fighting the anchor. */
.x-mini-basket .x-mini-basket__cta { margin-top: 0.4rem; display: grid; grid-template-columns: 1fr; }
/* .mm-theme-button-primary.mm-theme-button-primary__small is 0,2,0 and beat a
   single-class override - the Two-Class Core Rule again. */
.x-mini-basket .x-mini-basket__cta > a,
.x-mini-basket .x-mini-basket__cta > a.mm-theme-button-primary {
  display: block; width: 100%; text-align: center; text-decoration: none;
  font-family: var(--odsd-font-display); font-size: 0.95rem; font-weight: 600;
  color: #fff; background: var(--odsd-ink);
  border: 1px solid var(--odsd-ink); border-radius: 999px;
  padding: 0.85rem 1.4rem;
}
.x-mini-basket__cta > a:hover { background: #000; border-color: #000; }
/* "View cart" (2026-10-17): the outline twin of Checkout, under it. */
.x-mini-basket .x-mini-basket__cta > a.odsd-mb-view { background: #fff; color: var(--odsd-ink); }
.x-mini-basket .x-mini-basket__cta > a.odsd-mb-view:hover { background: var(--odsd-sand); border-color: var(--odsd-ink); color: var(--odsd-ink); }
/* Estimate Shipping: "Getting shipping rates..." while theme.js waits on the
   rates (extensions.js "ESTIMATE SHIPPING SHOWS IT IS WORKING"). */
.odsd-estimate-status { margin: 0.75rem 0 0; font-size: 0.9rem; color: var(--odsd-ink-soft); }
.odsd-estimate-status--error { color: #8a1f17; }
[data-hook="calculate-shipping-estimate"][disabled] { opacity: 0.6; cursor: progress; }

/* ---- CTGY: the subcategory component repeats the h1 verbatim -------------
   #mmx-category-list__subcategories renders an <h2> with the category name,
   directly above the tiles and directly above our <h1> saying the same thing.
   "Outdoor Speakers" appeared five times on the page. The TILES are internal
   links and stay; only the duplicate heading is hidden. */
body.t-page-ctgy #mmx-category-list__subcategories mmx-text[slot="title"] { display: none; }

.x-mini-basket__empty-icon {
  align-items: center;
  border-radius: 50%;
  display: inline-flex;
  height: 4.5rem;
  justify-content: center;
  margin-top: 1.875rem;
  width: 4.5rem;
}

.x-mini-basket__content {
  display: flex;
  flex: 1 1 100%;
  flex-direction: column;
  max-width: 25rem;
  padding: 1rem 0;
  width: 100vw;
}

.x-mini-basket__content :is(header, footer),
.x-mini-basket__list {
  padding: 0 1rem;
}

.x-mini-basket__header,
.x-mini-basket__subtotal {
  display: flex;
  justify-content: space-between;
  padding-bottom: 1.875rem;
}

.x-mini-basket__subtotal {
  padding-bottom: 1.75rem;
}

.x-mini-basket__header button {
  background-color: transparent;
  border: 0 none;
  cursor: pointer;
  font-size: 0.625rem;
}

.x-mini-basket__list {
  flex-grow: 1;
  margin-bottom: 1rem;
  overflow-y: auto;
}

.x-mini-basket__line {
  border-top: var(--default-input-border);
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
  font-size: 0.875rem;
  padding: 1.5rem 0;
}

.x-mini-basket__line:last-of-type {
  padding: 1rem 0;
}

.x-mini-basket__image {
  height: 2.5rem;
  overflow: hidden;
  text-align: center;
  width: 2.5rem;
}

.x-mini-basket__item {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  justify-content: space-between;
  text-decoration: none;
}

.x-mini-basket__item:hover {
  text-decoration: none;
}

.x-mini-basket__item-name,
.x-mini-basket__item-attributes {
  display: block;
  margin-bottom: 1rem;
}

.x-mini-basket__item-attributes {
  line-height: 1.75;
}

.x-mini-basket__subscription {
  flex-basis: 100%;
}

.x-mini-basket__item-price {
  width: 100%;
  max-width: 300px;
}

.x-mini-basket__item-total {
  align-items: center;
  display: flex;
  justify-content: space-between;
  gap: 1rem;
}

/* Checkout Order Summary (OCST, OSEL, OPAY) reuses these classes. The old theme's
   max-height: 50vh on the list, with theme.css turning its scrolling off, ran the items
   over the Subtotal, Tax and Total rows. The summary shows every item; the page scrolls.
   The price box's width: 100% squeezed "Qty: 1" onto two lines. The small cart keeps its
   own rules. */
.t-basket__order-summary .x-mini-basket__list {
  max-height: none;
}

.t-basket__order-summary .x-mini-basket__item-total > span {
  white-space: nowrap;
}

.x-mini-basket__charges,
.x-mini-basket__total {
  display: flex;
  flex: 1 0 100%;
  justify-content: space-between;
}

.x-mini-basket__charges {
  flex-direction: column;
  font-size: 0.875rem;
  margin-bottom: 1em;
}

.x-mini-basket__charge-item {
  display: flex;
  justify-content: space-between;
}

.x-mini-basket__cta {
  display: grid;
  gap: 0.5rem 0;
}

/* ==========================================================================
   EXTENSIONS / QUANTIFY / QUANTIFY
   ========================================================================== */
/**
 * This extension allows for the use of buttons to increase/decrease item
 * quantities on the product and basket pages. When used on the basket page,
 * the decrease button becomes a remove button if the quantity is 1.
 */
.x-quantify {
  border: var(--default-input-border);
  border-radius: var(--default-border-radius);
  display: grid;
  grid-template-columns: 2.5rem 1fr 2.5rem;
  grid-template-rows: 1fr;
  height: 2.5rem;
  width: 7.5rem;
}
.x-quantify:focus-within {
  border: var(--default-input-border--focus);
}
.x-quantify > * {
  align-self: stretch;
  border: 0 none;
  line-height: normal;
  margin-bottom: 0;
  outline-offset: calc(-1 * var(--default-border-width) * 2);
  padding: 0;
}
.x-quantify button {
  background: transparent;
  border-radius: var(--default-border-radius);
  color: inherit;
  cursor: pointer;
  font-size: 0.625rem;
  -webkit-user-select: none;
          user-select: none;
}
.x-quantify button[aria-disabled=true] {
  opacity: 0.25;
}
.x-quantify button:focus-visible {
  outline: var(--default-button-outline--focus);
}
.x-quantify button > * {
  pointer-events: none;
}
.x-quantify input:focus-visible {
  border: none;
}

/* =====
   EXTENSIONS / TABS / A11Y-TABS
   ===== */
/**
 * This is an accessible tab solution extension based on guidelines documented
 * by Heydon Pickering on the Inclusive Components Pattern Library.
 * https://inclusive-components.design/tabbed-interfaces/
 */
[data-tab-component] *:focus:not(:focus-visible) {
  box-shadow: inset 0 0 0 4px var(--transparent);
  outline: none;
  text-decoration: none;
}

[data-tab-component] *:focus-visible {
  box-shadow: inset 0 0 0 4px var(--focus);
  outline: none;
  text-decoration: none;
}

[data-tab-component] [role=tablist] {
  display: flex;
  gap: 1.75rem;
  justify-content: center;
  list-style: none;
  margin: 0;
  padding: 0;
}

[data-tab-component] [role=presentation],
[data-tab-component] [role=tab] {
  display: inline-block;
}

[data-tab-component] [role=tab] {
  border-bottom: calc(var(--default-border-width) * 2) solid var(--transparent);
  font-size: var(--paragraph-large);
  font-weight: var(--font-medium);
  padding: 0.5rem;
  text-decoration: none;
}

[data-tab-component] [role=tab][aria-selected] {
  border-color: currentColor;
}

[data-tab-component] [role=tabpanel] {
  font-size: var(--paragraph-large);
  margin: auto;
  max-width: 50rem;
  padding-top: 2rem;
}

[data-tab-component] [role=tabpanel] * + * {
  margin-top: 0.75rem;
}

@media (max-width: 40em) {
  [data-tab-component] [role=tablist],
  [data-tab-component] [role=presentation] {
    display: block;
  }
  [data-tab-component] [role=tab] {
    border-bottom: var(--default-border-width) solid var(--gray-300);
    color: var(--black);
    display: flex;
    justify-content: space-between;
  }
  [data-tab-component] [role=tab][aria-selected] {
    border-color: var(--gray-300);
  }
  [data-tab-component] [role=tablist] li + li a {
    border-top: 0 !important;
  }
  [data-tab-component] [role=tab]::after {
    content: "+";
    font-size: 1.25em;
    line-height: 1;
  }
  [data-tab-component] [role=tab][aria-selected]::after {
    content: "-";
  }
  [data-tab-component] [role=tabpanel] {
    border-bottom: var(--default-border-width) solid var(--transparent);
    padding: 1rem 0.5rem;
  }
  [data-tab-component] [role=tabpanel]:not([hidden]) {
    border-color: var(--gray-300);
  }
}
/* ==========================================================================
   EXTENSIONS / NAVIGATION / TRANSFIGURE NAVIGATION
   ========================================================================== */
@media screen and (max-width: 59.999em) {
  .has-open-main-menu,
  .has-open-main-menu body {
    overflow: hidden;
  }
  .x-transfigure-navigation {
    backface-visibility: hidden;
    background-color: var(--transparent);
    bottom: 0;
    left: -100vw;
    opacity: 0;
    overflow: hidden;
    padding: 0;
    position: fixed;
    right: 0;
    top: 0;
    transform: scaleX(0);
    transform-origin: left;
    transition: all ease-out 0.2s;
    z-index: -1;
  }
  .x-transfigure-navigation.is-open {
    background: rgba(17, 17, 17, 0.65);
    left: 0;
    opacity: 1;
    transform: scaleX(1);
    transition: all ease-in 0.2s;
    z-index: 2;
  }
  .x-transfigure-navigation__wrap {
    background-color: var(--white);
    max-width: 22.5rem;
    min-height: 100%;
    -webkit-overflow-scrolling: touch;
    overflow-x: hidden;
    overflow-y: auto;
  }
  .x-transfigure-navigation__header {
    padding: 0.5rem;
  }
  .x-transfigure-navigation__header > * {
    align-self: auto;
    padding: calc(1em - var(--default-border-width));
    text-decoration: none;
  }
  .x-transfigure-navigation__content {
    position: relative;
    padding-bottom: 1em;
    padding-top: 1em;
  }
  .x-transfigure-navigation__row,
  .x-transfigure-navigation__row .c-navigation__row,
  .x-transfigure-navigation__row .c-navigation__row:nth-child(2) .c-navigation__row:last-of-type {
    background-color: var(--white);
    display: block;
    left: 0;
    list-style: none;
    min-height: 100%;
    position: absolute;
    top: 0;
    transition: transform 0.3s;
    width: 100%;
  }
  .x-transfigure-navigation .c-navigation__list {
    position: static;
  }
  .x-transfigure-navigation .c-navigation__list:hover > .c-navigation__row {
    box-shadow: none;
    display: block;
  }
  @supports (will-change: auto) {
    .x-transfigure-navigation .c-navigation__list:focus-within > .c-navigation__row {
      display: block;
      box-shadow: none;
    }
  }
  .x-transfigure-navigation .c-navigation__link, .x-transfigure-navigation .c-navigation__text {
    align-items: center;
    backface-visibility: hidden;
    display: flex;
    min-height: 3rem;
    justify-content: space-between;
    overflow: hidden;
    padding: 0.5rem 1rem;
    text-overflow: ellipsis;
    transform: translateZ(0);
    transition: all 0.3s;
    white-space: normal;
  }
  .x-transfigure-navigation .c-navigation__row:nth-child(2) .c-navigation__link,
  .x-transfigure-navigation .c-navigation__row:nth-child(2) .c-navigation__row:last-of-type .c-navigation__link,
  .x-transfigure-navigation .c-navigation__row:nth-child(2) .c-navigation__text,
  .x-transfigure-navigation .c-navigation__row:nth-child(2) .c-navigation__row:last-of-type .c-navigation__text {
    padding-left: 1rem;
  }
  .x-transfigure-navigation__row .c-navigation__list:last-of-type > .c-navigation__link,
  .x-transfigure-navigation__row .c-navigation__row .c-navigation__list:last-of-type > .c-navigation__link,
  .x-transfigure-navigation__row .c-navigation__list:last-of-type > .c-navigation__text,
  .x-transfigure-navigation__row .c-navigation__row .c-navigation__list:last-of-type > .c-navigation__text {
    border-bottom-width: var(--default-border-width);
  }
  .x-transfigure-navigation__row.is-hidden,
  .x-transfigure-navigation__row .c-navigation__row.is-hidden {
    transform: translateX(100%);
  }
  .x-transfigure-navigation__row.is-hidden > .cmp-primary-navigation__title,
  .x-transfigure-navigation__row.show-next > .cmp-primary-navigation__title,
  .x-transfigure-navigation__row.is-hidden > .c-navigation__list > .c-navigation__link,
  .x-transfigure-navigation__row.show-next > .c-navigation__list > .c-navigation__link,
  .x-transfigure-navigation__row .c-navigation__row.is-hidden > .c-navigation__list > .c-navigation__link,
  .x-transfigure-navigation__row .c-navigation__row.show-next > .c-navigation__list > .c-navigation__link,
  .x-transfigure-navigation__row.is-hidden > .c-navigation__list > .c-navigation__text,
  .x-transfigure-navigation__row.show-next > .c-navigation__list > .c-navigation__text,
  .x-transfigure-navigation__row .c-navigation__row.is-hidden > .c-navigation__list > .c-navigation__text,
  .x-transfigure-navigation__row .c-navigation__row.show-next > .c-navigation__list > .c-navigation__text {
    opacity: 0;
  }
  .x-transfigure-navigation__row.show-next > .c-navigation__list > .c-navigation__link,
  .x-transfigure-navigation__row .c-navigation__row.show-next > .c-navigation__list > .c-navigation__link,
  .x-transfigure-navigation__row.show-next > .c-navigation__list > .c-navigation__text,
  .x-transfigure-navigation__row .c-navigation__row.show-next > .c-navigation__list > .c-navigation__text {
    transform: translateX(-100%);
  }
  .x-transfigure-navigation .c-navigation__link-carat {
    display: none;
  }
  .x-transfigure-navigation .has-child-menu .c-navigation__link-carat {
    align-self: stretch;
    display: inline-flex;
  }
  .x-transfigure-navigation .c-navigation__link-carat > * {
    align-items: center;
    display: flex;
    font-size: 0.75rem;
    justify-content: center;
    width: 4rem;
  }
  .x-transfigure-navigation__footer {
    border-top: var(--default-border-width) solid var(--gray-300);
    font-size: var(--default-input-font-size);
    font-weight: var(--font-regular);
    margin: 1rem 0 0.5rem 1rem;
    padding: 1rem 0;
    width: -moz-fit-content;
    width: fit-content;
  }
  .x-transfigure-navigation__footer-link, .x-transfigure-navigation__footer-text {
    align-items: center;
    display: flex;
    height: 2.5rem;
    text-decoration: none;
  }
}
@media screen and (min-width: 60em) {
  .x-transfigure-navigation__row {
    gap: 0.25rem;
  }
  .x-transfigure-navigation__row .c-navigation__row {
    background-color: var(--white);
    display: none;
    padding: 0.5rem;
    position: absolute;
    white-space: nowrap;
    width: auto;
    z-index: 100;
  }
  .x-transfigure-navigation > .c-navigation__row {
    display: flex;
  }
  .x-transfigure-navigation .c-navigation__link, .x-transfigure-navigation .c-navigation__text {
    padding: 1.5em 1em;
  }
  .x-transfigure-navigation .c-navigation__link span {
    position: relative;
  }
  .x-transfigure-navigation .c-navigation__link span::after {
    background-color: currentColor;
    bottom: -0.4em;
    content: "";
    height: 0.2em;
    left: 0;
    position: absolute;
    transform: scaleX(0);
    transform-origin: bottom left;
    transition: transform 0.25s ease-out;
    width: 100%;
  }
  .x-transfigure-navigation .c-navigation__link:is(:focus, :focus-within, :hover) span::after {
    transform: scaleX(1);
  }
  .x-transfigure-navigation .c-navigation__link:is(:target) {
    text-decoration: underline;
    text-decoration-thickness: 0.2em;
    text-underline-offset: 0.4em;
  }
  .x-transfigure-navigation__row .c-navigation__row:nth-child(2) .c-navigation__list {
    text-align: left;
    flex-basis: 100%;
  }
  .x-transfigure-navigation__row .c-navigation__row:nth-child(2) .c-navigation__row:last-of-type {
    left: 100%;
    top: -0.5rem;
    z-index: 200;
  }
  .x-transfigure-navigation .c-navigation__row:nth-child(2) .c-navigation__link,
  .x-transfigure-navigation .c-navigation__row:nth-child(2) .c-navigation__row:last-of-type .c-navigation__link,
  .x-transfigure-navigation .c-navigation__row:nth-child(2) .c-navigation__text,
  .x-transfigure-navigation .c-navigation__row:nth-child(2) .c-navigation__row:last-of-type .c-navigation__text {
    justify-content: space-between;
    padding: 0.5rem 1rem;
  }
  .x-transfigure-navigation .c-navigation__list:hover > .c-navigation__row,
  .x-transfigure-navigation .c-navigation__list:focus-within > .c-navigation__row {
    box-shadow: 0 3px 0.5em 0 rgba(0, 0, 0, 0.08);
    display: flex;
    gap: 0.25rem;
    min-width: -moz-fit-content;
    min-width: fit-content;
  }
  .x-transfigure-navigation .c-navigation__list:hover > .c-navigation__row::before,
  .x-transfigure-navigation .c-navigation__list:focus-within > .c-navigation__row::before {
    content: "";
    display: block;
    height: 1em;
    position: absolute;
    top: -1em;
    width: 100%;
  }
  .x-transfigure-navigation__row .is-off-screen .c-navigation__row:nth-child(2) .c-navigation__row {
    box-shadow: -1px 1px 0.25em 0 rgba(17, 17, 17, 0.5);
    left: auto;
    right: 100%;
  }
  .x-transfigure-navigation__row .c-navigation__row:nth-child(2) .c-navigation__row .c-navigation__link,
  .x-transfigure-navigation__row .c-navigation__row:nth-child(2) .c-navigation__row:last-of-type .c-navigation__link .x-transfigure-navigation__row .c-navigation__row:nth-child(2) .c-navigation__row .c-navigation__text,
  .x-transfigure-navigation__row .c-navigation__row:nth-child(2) .c-navigation__row:last-of-type .c-navigation__text {
    padding-left: 1em;
  }
  .x-transfigure-navigation .c-navigation__link-carat {
    font-size: 0.5em;
    transform: rotate(90deg);
  }
  .x-transfigure-navigation .c-navigation__row:nth-child(2) .c-navigation__link-carat {
    transform: rotate(0deg);
  }
}
.x-transfigure-navigation .c-navigation__link {
  text-decoration: none;
}

.x-transfigure-navigation .c-navigation__link:hover {
  text-decoration: none;
  box-shadow: none;
}

.x-transfigure-navigation .c-navigation__row:nth-child(2) .c-navigation__link:hover {
  background-color: var(--gray-100);
  border-radius: var(--default-border-radius);
  text-decoration: none;
  transition: background-color ease-out 0.1s;
}

/* ==========================================================================
   EXTENSIONS / FASTEN HEADER / FASTEN HEADER
   ========================================================================== */
.x-fasten-header {
  display: none;
}

@media screen and (min-width: 60em) {
  .x-fasten-header {
    background-color: var(--white);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    display: flex;
    left: 0;
    max-width: none;
    opacity: 0;
    padding: 0.5rem 2rem;
    position: fixed;
    right: 0;
    top: 0;
    transform: translateY(-100%);
    transform-origin: top;
    transition: all ease-in-out 0.15s;
    z-index: 300;
  }
  .x-fasten-header--is-active .x-fasten-header {
    opacity: 1;
    transform: translateY(0);
  }
  .x-fasten-header__navigation {
    position: relative;
  }
  .x-fasten-header__navigation::before {
    content: " ";
    display: block;
    height: var(--default-input-font-size);
    width: 100%;
  }
  .x-fasten-header__navigation-element {
    cursor: pointer;
    position: absolute;
    top: -1em;
  }
  .x-fasten-header__navigation-element summary {
    border-radius: var(--default-border-radius);
    line-height: 1;
    margin-bottom: 0;
    padding: 1em 3.5em 1em 1.5em;
    -webkit-user-select: none;
            user-select: none;
    white-space: nowrap;
  }
  .x-fasten-header__navigation-element summary::marker {
    content: "V";
    font-family: "ReadyTheme Icons", sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }
  .x-fasten-header__navigation-element summary span {
    padding-left: 1em;
    position: relative;
    top: calc(-1 * (var(--default-spacing-unit) - var(--default-input-font-size)) / 2);
  }
  .x-fasten-header__navigation-element[open] summary {
    background-color: var(--gray-100);
    margin-bottom: calc(0.25rem + var(--default-border-width));
  }
  .x-fasten-header__navigation-list {
    background-color: var(--white);
    border: var(--default-border-width) solid var(--gray-200);
    box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.08);
    list-style: none;
    margin: 0;
    padding: 0.5em 1.5em;
  }
  .x-fasten-header__navigation-item {
    padding: 0.5em 0;
  }
  .x-fasten-header__navigation-link {
    display: block;
    padding: 0.5em 0;
    text-decoration: none;
  }
  .x-fasten-header__navigation-link:hover {
    text-decoration: underline;
    text-decoration-thickness: 0.1em;
    text-underline-offset: 0.15em;
  }
}
/* ==========================================================================
   EXTENSIONS / SHOW PASSWORD / SHOW PASSWORD
   ========================================================================== */
/**
 * This extension allows a user to reveal the password they have typed.
 */
input[type=password]:invalid + .x-toggle-password {
  color: var(--gray-400);
}

.x-toggle-password {
  border: 0 none;
  border-radius: 0;
  color: var(--gray-600);
  font-size: 1.5em;
  margin-top: -0.125em;
  padding: 0.25em;
  position: absolute;
  right: 0.5em;
  top: calc(50% - 1px);
}

.x-toggle-password--no-label {
  margin-top: -0.25em;
  top: 25%;
}

.x-collapsible-content__toggle {
  align-items: center;
  background-color: transparent;
  border: 0 none;
  display: flex;
  font-weight: 700;
  justify-content: space-between;
  line-height: normal;
  padding: 0;
  width: 100%;
}

.x-collapsible-content__toggle[aria-expanded=true] [class^=u-icon-]::before {
  content: attr(data-toggle);
}

.x-collapsible-content__item[aria-hidden=false] {
  max-height: -moz-fit-content;
  max-height: fit-content;
  opacity: 1;
  overflow: visible;
  transition: visibility 0s ease, max-height 0.3s ease, opacity 0.3s ease;
  visibility: visible;
}

.x-collapsible-content__item {
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  visibility: hidden;
}

/**
	Thumbnail Filmstrip
 */
[data-filmstrip-wrapper] {
  position: relative;
}

[data-filmstrip] {
  overflow: hidden;
  position: relative;
  scroll-behavior: smooth;
  scroll-snap-type: x mandatory;
  -webkit-user-select: none;
          user-select: none;
}

[data-vertical] [data-filmstrip] {
  height: 100%;
  scroll-snap-type: y mandatory;
}

[data-filmstrip]::-webkit-scrollbar {
  display: none;
}

[data-filmstrip] ul {
  align-items: center;
  display: flex;
  flex-flow: row nowrap;
  gap: 1rem;
  margin: 0;
  width: max-content;
}

[data-filmstrip] ul.has-controls {
  padding: 0 calc(var(--default-spacing-unit) / 2);
}

[data-vertical] [data-filmstrip] ul {
  flex-direction: column;
  height: max-content;
}

[data-vertical] [data-filmstrip] ul.has-controls {
  padding: calc(var(--default-spacing-unit) / 2) 0;
}

[data-filmstrip] li {
  height: var(--prod-thumb-y);
  list-style: none;
  scroll-snap-align: none;
  scroll-snap-stop: always;
  width: var(--prod-thumb-x);
}

[data-filmstrip] picture {
  align-items: center;
  display: flex;
  flex-direction: column;
  height: 100%;
  justify-content: center;
  width: 100%;
}

[data-filmstrip] img {
  width: auto;
  height: auto;
}

[data-filmstrip-controls] {
  align-items: center;
  display: flex;
  height: 100%;
  justify-content: space-between;
  left: 0;
  list-style: none;
  margin: 0;
  position: absolute;
  top: 0;
  width: 100%;
}

[data-filmstrip-controls] button {
  border-color: var(--gray-400);
  border-radius: 50%;
  height: 3rem;
  padding: 0;
  width: 3rem;
}

[data-filmstrip-controls] button:focus {
  outline: 3px solid var(--focus);
  outline-offset: -3px;
}

[data-filmstrip-controls] button span {
  pointer-events: none;
}

[data-vertical] [data-filmstrip-controls] {
  flex-direction: column;
}

[data-vertical] [data-filmstrip-controls] button span {
  transform: rotate(90deg);
}

/**
	Modal Photo Viewer
 */
.is-inert {
  pointer-events: none;
  -webkit-user-select: none;
          user-select: none;
}

.has-photo-viewer {
  overflow: hidden;
}

.x-photo-viewer {
  background-color: rgba(0, 0, 0, 0.75);
  border: 0;
  clip: rect(0 0 0 0);
  display: flex;
  flex-direction: column;
  height: auto;
  left: 0;
  margin: 0;
  opacity: 0;
  overflow: hidden;
  padding: 0;
  position: absolute;
  top: 0;
  transform: scale(0);
  transition: opacity 0.3s ease, transform 0.2s ease;
  white-space: nowrap;
  width: 1px;
  z-index: 9999;
}

.x-photo-viewer__visible {
  clip: auto;
  height: 100%;
  opacity: 1;
  position: fixed;
  transform: scale(1);
  width: 100%;
}

.x-photo-viewer__header,
.x-photo-viewer__controls {
  align-items: center;
  background-color: var(--black);
  color: var(--gray-100);
  display: flex;
  width: 100%;
}

.x-photo-viewer__title {
  flex: 1;
  font-size: var(--title-four);
  margin: 0;
  padding-left: 2em;
  text-align: center;
  white-space: normal;
}

.x-photo-viewer__close {
  height: 2.25em;
  margin: 0.5em;
  width: 2.25em;
}

.x-photo-viewer__close button {
  height: 100%;
  padding: 0;
  width: 100%;
}

.x-photo-viewer__container {
  align-items: center;
  display: flex;
  height: 100%;
  justify-content: center;
  padding: 0 1rem;
  touch-action: manipulation;
  white-space: nowrap;
  width: 100%;
}

.x-photo-viewer__current-image {
  align-items: center;
  display: flex;
  height: 100%;
  justify-content: center;
  opacity: 0;
  transition: opacity ease-in 0.3s 0.3s;
  width: 100%;
}

.x-photo-viewer__current-image.is-loaded {
  opacity: 1;
  transition: opacity 0.3s ease-out;
}

.x-photo-viewer__current-image img {
  max-height: 80vh;
}

.x-photo-viewer__controls {
  justify-content: space-between;
  padding: 1em 2em;
}

.x-photo-viewer__count {
  font-weight: var(--font-medium);
  text-align: center;
}

/* ==========================================================================
   EXTENSIONS / PAYMENT / PAYMENT CARD
   ========================================================================== */
/**
 * This is the default card layout for the saved payment methods page.
 */
.x-payment-card {
  align-items: baseline;
  display: grid;
  font-size: var(--default-input-font-size);
  gap: var(--default-spacing-unit);
  grid-template-columns: repeat(2, 1fr);
  padding: var(--default-spacing-unit);
}

.x-payment-card__image {
  display: none;
}

@media (min-width: 48em) {
  .x-payment-card {
    align-items: center;
    grid-template-columns: 38px repeat(4, 1fr);
  }
  .x-payment-card__image {
    display: block;
    grid-area: 1/1/2/2;
  }
  .x-payment-card__number {
    grid-area: 1/2/2/3;
  }
  .x-payment-card__cardholder {
    grid-area: 1/3/2/4;
  }
  .x-payment-card__expiry {
    grid-area: 1/4/2/5;
  }
  .x-payment-card__actions {
    grid-area: 1/5/2/6;
    place-self: center end;
  }
}
.x-payment-card__actions > * {
  align-items: baseline;
  gap: 2rem;
}

/* ============================================================================
   ODSD homepage (SFNT Page Builder) - translated from the static design spec
   (outdoorspeakerdepot/ on main). Scoped by component instance ids.
   ========================================================================== */

/* Ken Burns: the hero images slowly zoom on load so the page feels alive
   (spec: .hero-slide keyframes). Scale stays >= 1 so cover-fit never shows
   gaps; the two heroes alternate direction. */
@keyframes odsd-kenburns-in {
  from { transform: scale(1); }
  to { transform: scale(1.08); }
}
@keyframes odsd-kenburns-out {
  from { transform: scale(1.08); }
  to { transform: scale(1); }
}
/* hero-2 (single mmx-hero, "Premium outdoor speakers") keeps a subtle Ken Burns zoom;
   its <picture> is light DOM. */
#mmx-hero__hero-2 picture[slot="image"] {
  display: block;
  overflow: hidden;
}
#mmx-hero__hero-2 picture[slot="image"] img {
  animation: odsd-kenburns-out 7s ease both;
}
@media (prefers-reduced-motion: reduce) {
  #mmx-hero__hero-2 picture[slot="image"] img { animation: none; }
}

/* ---- Custom hero rotator (.odsd-hero) -------------------------------------
   Exact port of the repo homepage hero: crossfade + Ken Burns + hover-only arrows +
   bar dots. Built and injected by js/extensions.js (the "ODSD HERO" block), which
   removes the mmx-hero-slider mount and inserts .odsd-hero in its place. The slider is
   hidden here so it never flashes before the script runs. If the hero disappears,
   check: this CSS block, the extensions.js ODSD HERO block, and that the SFNT page still
   has a mmx-hero-slider component (id mmx-hero-slider__hero) acting as the mount. */
#mmx-hero-slider__hero { display: none !important; }

.odsd-hero {
  position: relative;
  width: 100%;
  aspect-ratio: 1861 / 550;
  max-height: 80vh;
  overflow: hidden;
  background: #0d0e10;
}
.odsd-hero__slides,
.odsd-hero__slide { position: absolute; inset: 0; }
.odsd-hero__slide {
  opacity: 0;
  pointer-events: none;
  transition: opacity 1s ease;
}
.odsd-hero__slide.is-active { opacity: 1; pointer-events: auto; }
.odsd-hero__link,
.odsd-hero__slide picture,
.odsd-hero__slide img { display: block; width: 100%; height: 100%; }
.odsd-hero__slide img { object-fit: cover; object-position: center; }
.odsd-hero__slide.is-active img { animation: odsd-kenburns-in 7s ease both; }
.odsd-hero__slide:nth-child(even).is-active img { animation-name: odsd-kenburns-out; }
@media (prefers-reduced-motion: reduce) {
  .odsd-hero__slide.is-active img,
  .odsd-hero__slide:nth-child(even).is-active img { animation: none; }
}
.odsd-hero__dots {
  position: absolute; bottom: 1.4rem; left: 50%; transform: translateX(-50%);
  z-index: 3; display: flex; gap: 0.55rem;
}
.odsd-hero__dots button {
  width: 30px; height: 3px; border-radius: 2px; border: none; padding: 0;
  cursor: pointer; background: rgba(255, 255, 255, 0.45);
  transition: background 0.3s ease;
}
.odsd-hero__dots button.is-active { background: #fff; }
.odsd-hero__arrow {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  width: 46px; height: 46px; border-radius: 50%; border: none;
  display: grid; place-items: center;
  background: rgba(20, 20, 26, 0.32);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  color: #fff; cursor: pointer; opacity: 0;
  transition: opacity 0.3s ease, background 0.2s ease;
}
.odsd-hero:hover .odsd-hero__arrow { opacity: 1; }
.odsd-hero__arrow:hover { background: rgba(20, 20, 26, 0.55); }
.odsd-hero__arrow svg { width: 22px; height: 22px; stroke: #fff; }
.odsd-hero__arrow--prev { left: 1.2rem; }
.odsd-hero__arrow--next { right: 1.2rem; }

/* ---- ODSD design tokens (repo spec) - shared by the custom homepage sections ------- */
:root {
  --odsd-ink: #16161a;
  --odsd-ink-soft: #6b6b70;
  --odsd-line: #e4e1da;
  --odsd-sand: #f5f3ef;
  --odsd-night: #121316;
  --odsd-night-soft: #1c1d21;   /* repo --night-soft: the storyband ground */
  --odsd-accent: #fcae46;
  --odsd-accent-deep: #966310;   /* was #a76f12 (4.26:1 on white) - WCAG AA 5.1:1 white / 4.6:1 sand, audit 2026-09-22 */
  --odsd-star: #c69a4e;
  --odsd-font-display: "Poppins", "Segoe UI", system-ui, -apple-system, sans-serif;
  /* --odsd-font-body was referenced by three rules but NEVER defined - no commit
     in this repo's history has ever contained the declaration. That is not a
     harmless no-op: a `font:` SHORTHAND containing an undefined var() is invalid
     at computed-value time, and the whole declaration then falls back to `unset`,
     which for inherited properties means INHERIT. So
     `.odsd-cat__tree ... .x-category-tree__link { font: 400 0.84rem/1.35 var(--odsd-font-body) }`
     silently threw away its own font-size AND line-height and took the body's
     instead - 17px/28.05px in a 240px rail. The font switch did not break this;
     it made it visible, by raising the inherited base the broken rule fell back to.
     Watch for this shape: an undefined var inside `font:` fails LOUDLY in layout
     and SILENTLY in the stylesheet. */
  --odsd-font-body: "Poppins", "Segoe UI", system-ui, -apple-system, sans-serif;
  --odsd-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --odsd-container: 1280px;

}

/* ---- base text SIZE, to the repo's body -----------------------------------
   STILL NEEDED after the Theme Editor recompile (2026-08-05), and the reason is
   worth keeping. The recompiled `mm-theme-styles` does set paragraph-small to
   17px - but it sets it on the <html> element, where core.css's
   `html { font-size: 16px !important }` outranks it. `body` never reads that
   class; it reads `font-size: var(--default-font-size)`, which chains to
   `--paragraph-small: 0.875rem`. So the inherited base would fall back to 14px
   without this.

   `--default-font-size` is the narrow lever - core.css uses it in exactly two
   places, `body` and the bare h2-h6 reset - so raising it moves the inherited
   base without touching every component that reads `--paragraph-small`
   directly. Deliberately NOT the `html` font-size: that 16px pin is what keeps
   1rem = 16px, and every `--odsd-*` value in this file is a rem tuned against
   it. Changing the root would rescale the whole port by 6% in one go.

   The font-family override that used to sit above this block is GONE: the
   Poppins font property was `active: 0`, which is why the theme compiled to
   Inter. Activated, recompiled, and the resource now serves Poppins natively. */
:root {
  --default-font-size: 1.0625rem;
}
body {
  line-height: 1.65;
}

/* ---- Rating bar + "Commercial-grade audio" CTA band (repo .ratingbar + .herocta) ----
   Injected by js/extensions.js right under the hero. Replaces the Miva "free shipping"
   promo strip (hidden below), which isn't in the repo layout at this position. */
.odsd-ratingbar {
  text-align: center; padding: 1rem; background: var(--odsd-ink); color: #fff; font-size: 0.9rem;
}
.odsd-ratingbar .odsd-stars { color: var(--odsd-star); letter-spacing: 0.05em; }
.odsd-ratingbar b { font-family: var(--odsd-font-display); }
/* The hero strip is a link since 2026-10-19 (C2): same look, underline on hover/focus. */
a.odsd-ratingbar { display: block; color: #fff; text-decoration: none; }
a.odsd-ratingbar:hover .odsd-ratingbar__text,
a.odsd-ratingbar:focus-visible .odsd-ratingbar__text { text-decoration: underline; text-underline-offset: 3px; }
a.odsd-ratingbar:focus-visible { outline: 2px solid var(--odsd-accent); outline-offset: -4px; }
.odsd-ratingbar__count { white-space: nowrap; }   /* phones wrap before it, not inside it */
/* The .odsd-cta hero CTA band was REMOVED with repo PR #13 - see the note in
   js/extensions.js. Its CSS is gone with it. .odsd-btn* below STAYS: it is shared by
   the BLACK band, the outdoor band and the upload-plans "Get started" button. */
.odsd-btn {
  display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.7rem 1.4rem;
  border-radius: 999px; border: 1px solid transparent; font-family: var(--odsd-font-display);
  font-weight: 600; font-size: 0.9rem; text-decoration: none; cursor: pointer; white-space: nowrap;
  transition: background 0.2s var(--odsd-ease), border-color 0.2s var(--odsd-ease);
}
.odsd-btn--primary { background: var(--odsd-ink); color: #fff; }
.odsd-btn--primary:hover { background: #000; }
.odsd-btn--light { background: #fff; color: var(--odsd-ink); border-color: var(--odsd-line); }
.odsd-btn--light:hover { border-color: var(--odsd-ink); }
#mmx-text-banner__text-banner { display: none !important; }

/* ============================================================================
   CATEGORY PAGE (CTGY) - repo category.html parity
   ----------------------------------------------------------------------------
   ARCHITECTURE. This page is SERVER-RENDERED, in the light DOM, by Miva's own
   `category_listing` + `facets` items - the same path production has always
   used. The row markup is ours (`inc-plst-product` readytheme content section),
   the wrapper is `ctgy-category_listing.mvt`, and Miva still owns faceting,
   sorting and pagination.

   This REPLACES an earlier <mmx-product-list> approach, and corrects the note
   that used to live here. That note claimed the component "carries server-side
   faceting, pagination, sorting and crawlable markup" and so should be styled
   rather than replaced. That was wrong on every count: the component is
   configured `origin: "Runtime API"` and fetches client-side, rendering name,
   price, stock and Add-to-Cart into a SHADOW ROOT. Verified 2026-07-30 - the V2
   category page emitted one empty <mmx-product-list> element and zero product
   text, while production emitted six full product rows. Keeping the component
   was a live SEO regression against production, not a protection.
   ========================================================================= */

.odsd-catpage {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: clamp(1.4rem, 3vw, 2.2rem) clamp(1rem, 4vw, 2rem) clamp(2.5rem, 5vw, 4rem);
}

/* repo .catpage-crumb-row - display is Home / <category>. The category TREE is
   untouched; only the crumb is shortened. */
.odsd-cat__crumb {
  font-size: 0.85rem; color: var(--odsd-ink-soft); margin-bottom: 1rem;
}
.odsd-cat__crumb a { color: var(--odsd-ink-soft); text-decoration: none; }
.odsd-cat__crumb a:hover { color: var(--odsd-accent-deep); }
.odsd-cat__crumb-sep { margin: 0 0.15rem; }

/* repo .catpage-toolbar h1
   WEIGHT 700, not 800 (corrected 2026-08-06). The repo sets `h1,h2,h3,h4 {
   font-weight: 700 }` and its #catpage-title measures 700; ours was rendering
   800, which is why the category heading read heavier than the design. The repo
   does use 800 - but only for .lp-name, .lp-price and the small badges, which
   our list cards already match. Headings are 700 throughout. */
.odsd-cat__title {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: clamp(1.6rem, 2.6vw, 2.25rem);
  line-height: 1.08; letter-spacing: -0.02em;
  color: var(--odsd-ink); margin: 0 0 1.4rem;
}

/* repo .catpage-layout - 240px filter rail, sticky */
.odsd-cat__layout {
  display: grid; grid-template-columns: 240px 1fr;
  gap: 2.2rem; align-items: start;
}
.odsd-cat__side { position: sticky; top: 84px; }
.odsd-cat__main { min-width: 0; }

/* repo .catpage-toolbar - count left, controls right */
.odsd-cat__toolbar {
  /* flex-start + nowrap (2026-08-22, once the facet pills moved into the left
     slot): the pills wrap INSIDE their slot while Sort + the view toggle stay
     on the first row, right-aligned, level with the first row of pills. With
     `wrap` a long facet set (search: nine pills) pushed the controls onto a
     second row of their own; with `center` the facet block's trailing space
     sat the controls 8px low. */
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; flex-wrap: nowrap; margin-bottom: 1.1rem;
}
.odsd-cat__toolbar .odsd-cat__controls { flex: 0 0 auto; margin-left: auto; }
/* repo .catpage-toolbar .count uses --fs-small, which measures 13px. */
.odsd-cat__count { color: var(--odsd-ink-soft); font-size: 0.8125rem; }
.odsd-cat__controls { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }

/* repo .catpage-sort */
.odsd-cat__sortform { display: flex; align-items: center; gap: 0.5rem; margin: 0; }
.odsd-cat__sortlabel { font-size: 0.8rem; color: var(--odsd-ink-soft); }
/* Sized to the VIEW TOGGLE next to it (owner, 2026-08-22): the toggle is 34px
   tall outside (32px buttons + 1px borders), and the repo's body-size select
   (17px type, .6rem padding) stood ~48px beside it. Explicit 34px box-sizing
   height with the type at 0.86rem; a deliberate deviation from the repo's
   .catpage-sort, which has no size override. (First pushed c4449, lost to a
   concurrent session's c4450 overwrite of this file, re-pushed.) */
.odsd-cat__sort {
  font: inherit; font-size: 0.86rem; line-height: 1;
  box-sizing: border-box; height: 34px; padding: 0 2rem 0 0.9rem;
  border: 1px solid var(--odsd-line); border-radius: 999px;
  background: #fff; color: var(--odsd-ink); cursor: pointer;
}
.odsd-cat__sort:hover { border-color: var(--odsd-ink); }

/* repo .view-toggle */
.view-toggle {
  display: inline-flex; border: 1px solid var(--odsd-line);
  border-radius: 8px; overflow: hidden;
}
.view-toggle button {
  width: 36px; height: 32px; display: grid; place-items: center;
  border: 0; background: #fff; color: var(--odsd-ink-soft); cursor: pointer;
  transition: background 0.15s var(--odsd-ease), color 0.15s var(--odsd-ease);
}
.view-toggle button + button { border-left: 1px solid var(--odsd-line); }
.view-toggle button:hover { color: var(--odsd-ink); background: var(--odsd-sand); }
.view-toggle button[aria-pressed="true"] { background: var(--odsd-ink); color: #fff; }
.view-toggle svg { width: 18px; height: 18px; }

.u-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---- visually-hidden <legend>: keep it from widening the DOCUMENT ---------
   Branch 16 applies core.css TWICE - once inside the combined `stylesheet`
   resource (branchless URL, so it is production's build) and once as the
   branch-scoped individual file the 07-31 fix added. The two copies disagree
   about `legend`, and the individual file loads LAST:

     combined       legend { display: table; white-space: normal }
     core.css       legend { width: 1px; position: absolute; white-space: nowrap }

   `display: table` + `width: 1px` + `nowrap` is not a 1px box - CSS table
   sizing treats `width` as a MINIMUM, so the legend grows to its full unwrapped
   text. On a PDP that text is the product name, which measured 837px inside a
   430px viewport and set document scrollWidth on all 398 product pages.
   `html{overflow-x:hidden}` hid it, so nothing looked wrong.

   extensions.css is emitted after core.css, so restoring production's computed
   value here settles it at equal specificity - no !important needed. Fixing the
   double-load itself means touching the combined-resource config, which is the
   07-31 workaround; this stays branch-scoped and reversible. */
legend { white-space: normal; }

/* Same double-load, second casualty (2026-09-18): BLACK-ON-BLACK BUTTONS.
   Templates mark primary buttons `c-button u-bg-primary u-color-black` - gold
   fill, dark text. The individual core.css loads last and says
   `.c-button { background-color: var(--black) }`, which beats the gold
   `.u-bg-primary` (equal specificity, later file) while `.u-color-black` still
   makes the text black. Found on the PDP Q&A "Submit"; the same class trio is on
   the Amazon Pay checkout buttons (Continue to Shipping / Payment, Complete
   Order) and content sections 101/107. This restores production's look for
   exactly that trio (0,3,0 outranks `.c-button`) and touches no other button. */
.c-button.u-bg-primary.u-color-black { background-color: var(--odsd-accent); color: var(--odsd-ink); }
.c-button.u-bg-primary.u-color-black:hover { background-color: #f39c26; color: var(--odsd-ink); }

/* ---- facet rail (repo .catpage-filters) ---------------------------------
   Miva's own facet markup, server-rendered into the light DOM. We restyle it
   rather than rebuild it, so faceting stays Miva's job. */
/* The theme ships the refinery as a HORIZONTAL DROPDOWN BAR: .x-refinery is
   display:none until a breakpoint, .c-form-list is flex, and each set's list is
   position:absolute + opacity:0 until its hidden trigger checkbox is checked.
   The repo wants a vertical, always-open rail, so each of those has to be undone
   explicitly - not just `display: block`, which was the first attempt and left
   the groups sitting side by side with their options invisible. */
.odsd-cat__filters .x-refinery {
  display: block; border: 0; padding: 0; margin: 0; opacity: 1; transition: none;
}
.odsd-cat__filters fieldset { border: 0; padding: 0; margin: 0; }
.odsd-cat__filters legend { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.odsd-cat__filters .x-refinery .c-form-list,
.odsd-cat__filters .x-refinery__list {
  display: block; position: static;
  list-style: none; margin: 0; padding: 0;
}
.odsd-cat__filters .x-refinery-set {
  border-bottom: 1px solid var(--odsd-line); padding: 0.9rem 0; position: static;
}
.odsd-cat__filters .x-refinery-set:first-child { padding-top: 0; }
/* Kill the dropdown chrome: the divider bar and the twisty chevron. */
.odsd-cat__filters .x-refinery-set__label::before,
.odsd-cat__filters .x-refinery-set__label::after { display: none; content: none; }
/* The "Filters" title tile and "View All Filters" belong to the mobile annex
   drawer, which is theme-JS driven. Nothing mounts them here. */
.odsd-cat__filters .x-refinery__title-container,
.odsd-cat__filters [data-hook="refinery__view-target"],
.odsd-cat__filters [data-hook="refinery__overflow"] { display: none; }
.odsd-cat__filters .x-refinery-set__trigger { display: none; }
/* STRICT REPO PARITY (2026-08-06, owner call). Was 0.82rem/700, uppercase, with
   0.04em tracking - an ODSD flourish that had drifted from the design. The repo's
   equivalent is `.facet summary { font-family: var(--font-display); font-weight:
   600 }` at the inherited body size, sentence case, no tracking. Matched exactly:
   600, 1rem (17px like the repo's), text-transform none, letter-spacing normal. */
.odsd-cat__filters .x-refinery .x-refinery-set__label,
.odsd-cat__filters .x-refinery-set__label {
  display: block; cursor: default; height: auto;
  justify-content: flex-start; white-space: normal; position: static;
  font-family: var(--odsd-font-display); font-weight: 600;
  /* The repo's `.facet summary` takes the inherited body size - 17px - so match
     the token body itself reads rather than a bare `1rem` (which is 16px here,
     core.css pinning `html { font-size: 16px !important }`).
     `!important` DELIBERATELY, and it is the documented exception rather than
     laziness: `CSS.getMatchedStylesForNode` shows the winning declaration is
     `.x-refinery-set__label { font-size: 14.5px !important }` emitted by the
     GENERATED `mm-theme-styles` resource. No specificity beats an !important, so
     the only alternatives are this or changing the size in the Theme Editor -
     which would move it for every page that reads that theme style, not just the
     category rail. Revisit if the theme styles are ever retuned. */
  font-size: var(--default-font-size) !important;
  letter-spacing: normal; text-transform: none; color: var(--odsd-ink);
  margin-bottom: 0.55rem;
}
/* Undo the absolutely-positioned dropdown panel, in full. */
.odsd-cat__filters .x-refinery-set__list,
.odsd-cat__filters .x-refinery-set__trigger:checked ~ .x-refinery-set__list {
  display: block; position: static; left: auto;
  min-width: 0; max-width: none; max-height: none;
  overflow: visible; background: none; box-shadow: none;
  opacity: 1; pointer-events: auto; transform: none; transition: none;
  font-weight: 400;
}
.odsd-cat__filters .c-form-checkbox {
  display: flex; align-items: center; gap: 0.5rem;
  font-size: 0.86rem; color: var(--odsd-ink); cursor: pointer;
  padding: 0.16rem 0; margin: 0; white-space: normal; background: none;
}
.odsd-cat__filters .x-refinery-set__list label:hover { background: none; }
.odsd-cat__filters .x-refinery-set__list label.is-selected { background: none; font-weight: 600; }
.odsd-cat__filters .c-form-checkbox__input { margin: 0; accent-color: var(--odsd-ink); }
/* Facet option labels, to the repo's `.facet-opt`: 0.9rem, and the body's 1.65
   line-height, which the repo inherits rather than declaring. Ours were 13.76px
   at 1.35 - and that size was INHERITED, not declared (getMatchedStylesForNode
   shows no rule setting font-size on this element), so a plain declaration wins
   and no !important is needed here, unlike the group heading above. */
.odsd-cat__filters .c-form-checkbox__caption {
  font-size: 0.9rem; line-height: 1.65;
}
.odsd-cat__filters .c-form-checkbox .u-font-small { color: var(--odsd-ink-soft); font-size: 0.76rem; }
/* Facets configured as type `select` (e.g. "Type") come wrapped in the theme's
   .c-form-select. That wrapper is `visibility: hidden` by design - the theme's
   custom-select extension is supposed to build a styled replacement and reveal
   it. Branch 16 never emits themes/colossus/core/js/scripts.min.js, so nothing
   ever runs and the select stayed invisible while still occupying its box.
   Measured, not guessed: computed visibility on LABEL.c-form-select was hidden
   while every ancestor was visible.

   SUPERSEDED 2026-09-28: the hide is now lifted site-wide by the "INVISIBLE
   SELECT, FIXED GLOBALLY" block below. This rule and the three per-page ones
   after it stay because they also carry each page's own LOOK (rounded, native
   arrow), which the owner has reviewed - they outrank the global base. */
.odsd-cat__filters .c-form-select {
  display: block; position: relative; width: 100%; margin: 0; padding: 0;
  visibility: visible;
}
.odsd-cat__filters .c-form-select::before,
.odsd-cat__filters .c-form-select::after { display: none; content: none; }
.odsd-cat__filters .c-form-select__dropdown {
  display: block; width: 100%; height: auto; opacity: 1;
  -webkit-appearance: auto; appearance: auto;
  font: inherit; font-size: 0.84rem; padding: 0.45rem 0.6rem;
  color: var(--odsd-ink); border: 1px solid var(--odsd-line);
  border-radius: 8px; background: #fff; box-shadow: none;
}
/* ---- account pages: reveal the native selects (2026-08-14) --------------
   Same Invisible Select fix as the facet rail above, scoped to the customer
   account family via the body page classes. Measured logged in on branch 16:
   ACED "Customer Edit" had 8 of 9 selects hidden - the "Saved Address" pickers
   (ShipID/BillID) and both Country dropdowns painted NOTHING - and CADA
   "Address Add" hides its Country the same way. CADE (address edit) renders
   the same addressfields partial, so it is scoped in with them. ACAD (create
   account) joined 2026-09-30: the same customer form, and with the global
   reveal its Country no longer matched ACED's reviewed look (code review). */
.t-page-aced .c-form-select,
.t-page-acad .c-form-select,
.t-page-cada .c-form-select,
.t-page-cade .c-form-select {
  display: block; position: relative; width: 100%; margin: 0 0 0.5rem; padding: 0;
  visibility: visible;
}
.t-page-aced .c-form-select::before,
.t-page-aced .c-form-select::after,
.t-page-acad .c-form-select::before,
.t-page-acad .c-form-select::after,
.t-page-cada .c-form-select::before,
.t-page-cada .c-form-select::after,
.t-page-cade .c-form-select::before,
.t-page-cade .c-form-select::after { display: none; content: none; }
.t-page-aced .c-form-select__dropdown,
.t-page-acad .c-form-select__dropdown,
.t-page-cada .c-form-select__dropdown,
.t-page-cade .c-form-select__dropdown {
  display: block; width: 100%; height: auto; opacity: 1;
  -webkit-appearance: auto; appearance: auto;
  font: inherit; font-size: 0.95rem; padding: 0.55rem 0.6rem;
  color: var(--odsd-ink); border: 1px solid var(--odsd-line);
  border-radius: 8px; background: #fff; box-shadow: none;
}

/* ---- INVISIBLE SELECT, FIXED GLOBALLY (2026-09-28, Miva QA ticket #1389066) --
   Miva's QA found the dropdowns missing on the wish list, in the basket's
   shipping estimate and on checkout; a scan found create-account and affiliate
   signup the same. Fixing page by page had left all of those uncovered, so the
   hide is lifted everywhere, as Miva recommended. The custom-select script
   that was meant to replace these is not loaded on this branch
   (`typeof init_custom_selects` is undefined).
   [!] IF IT IS EVER LOADED AGAIN, the doubled controls come from the PER-PAGE
   select rules above (facet rail, search filters, ACED/CADA/CADE/ACAD) and
   the PDP/PATR attribute rule: they set `display: block` on the native select
   at (0,2,0) or more, which beats the `u-hidden` the script adds. This global
   block only sets visibility and height, so it cannot double anything; retire
   those per-page `display` lines first. (Corrected 2026-09-30 - this note
   first said to remove THIS block.)

   The rules, and the height one is not optional:
   1. the wrapper stops hiding. `.c-form-select:not(.x-stock-select-element)`
      weighs (0,2,0) - `:not()` counts its argument - the same selector the
      theme hides with, so it wins on order alone. Do not "lighten" it to
      `.c-form-select`: at (0,1,0) it would lose and re-hide every select.
   2. the select gets room for its text. The box is held at
      `calc(2.5em - 2px)` by the OLD theme's c-forms.css (linked in the
      branch's own combined stylesheet), and Shadows' core.css adds 1.23em of
      padding top and bottom - leaving ZERO height for the text, so a revealed
      select painted as an empty box with an arrow. Both files ship with the
      branch, so this is needed PERMANENTLY, after go-live too (corrected
      2026-09-30: this note first blamed production's copy of core.css).
      2.46em is the height of the text inputs beside it.
   3. `--large` selects keep the large inputs' height (they sit beside
      43px inputs in the basket edit dialog and subscription edit). `:where()`
      keeps it at (0,1,0) so the per-page rules above still win.
   4. a DISABLED select (checkout's one-option Country - the store ships to one
      country) loses the wrapper's arrow, white fade and pointer: it cannot
      open, so it should not look as if it can. */
.c-form-select:not(.x-stock-select-element) { visibility: visible; }
.c-form-select__dropdown { height: 2.46em; padding-top: 0; padding-bottom: 0; }
.c-form-select__dropdown:where(.c-form-input--large) { height: calc(3.5em - 2px); }
.c-form-select:has(> select:disabled)::before,
.c-form-select:has(> select:disabled)::after { display: none; }
.c-form-select > select:disabled { cursor: default; }

/* Icon spans inside buttons carry aria-hidden="true", so the theme's global
   [aria-hidden="true"]{display:none} swallows them and the button paints as an
   empty pill - the WLST "Find A Wish List" submit was a blank black oval
   (found 2026-08-14; the aria-hidden Vanish trap). Scoped to buttons, where an
   invisible icon always means an empty control; (0,1,1) outranks the theme's
   (0,1,0) attribute rule. */
button [class^="u-icon-"],
button [class*=" u-icon-"],
.c-button [class^="u-icon-"],
.c-button [class*=" u-icon-"] { display: inline-block; }
/* ...and once visible, the WLST magnifier painted in #333 on the near-black
   primary button - present but unreadable (Miva QA ticket #1389066,
   2026-09-28). It takes the button's own text colour, white. Keyed on the
   PAGE, not `.t-wish-list-search`: that class is only on the signed-out form,
   and the signed-in form (wlst.mvt, Customer_Session_Verified branch) renders
   the same button without it (code review, 2026-09-30). */
.t-page-wlst .mm-theme-button-primary [class*="u-icon-"] { color: currentColor; }

/* Active-filter chips (repo .chip-filter) */
.odsd-cat__filters .x-refinery__selected-filters {
  list-style: none; display: flex; flex-wrap: wrap; gap: 0.4rem;
  margin: 0.9rem 0 0; padding: 0;
}
.odsd-cat__filters .x-refinery__selected-filters .c-button {
  display: inline-flex; align-items: center; gap: 0.4em;
  font: 600 0.74rem var(--odsd-font-display); color: var(--odsd-ink);
  background: var(--odsd-sand); border: 1px solid var(--odsd-line);
  border-radius: 999px; padding: 0.34em 0.7em; cursor: pointer;
  text-decoration: none;
}
.odsd-cat__filters .x-refinery__selected-filters .c-button:hover {
  background: #fff; border-color: var(--odsd-ink-soft);
}

/* The searched term inside the SRCH h1 - soft ink so the frame and the query
   read separately; same trick the repo uses for muted emphasis. */
.odsd-cat__title-term { color: var(--odsd-ink-soft); font-weight: 600; }

/* ---- PDP: neutralize pasted white backgrounds in catalog HTML (2026-08-17)
   62 of 399 active products carry rich-text-paste artifacts in their specs/
   description custom fields - spans and lists with an inline
   `background-color: rgb(255, 255, 255)` (the ONLY form in the wild;
   enumerated across the affected pages, 139 occurrences sampled). On the
   PDP's cream card band they paint white stripes behind headings and lists
   (owner report, KIT-NEROWRA-2XRS670-PAR). The data fix is a per-product
   admin cleanup of catalog HTML shared with production, so the THEME
   neutralizes it: scoped to the PDP content bands, matching that exact
   declaration - swatch dots and any deliberate colors use `background:`
   shorthand and are untouched. !important because inline styles outrank
   any selector. */
.odsd-pdp-band [style*="background-color: rgb(255, 255, 255)"] {
  background-color: transparent !important;
}

/* ---- SRCH horizontal filter bar (Rapha/Boozt pattern, 2026-08-17) -------
   The SAME shared facet markup as the category rail above, in a different
   scope: search has no left rail, so the facet groups render as a row of
   pill DROPDOWNS above the results. Unlike the rail (which had to undo the
   theme's dropdown mechanics), this keeps the markup's own open/close - the
   hidden checkbox `.x-refinery-set__trigger` + label[for] + `:checked ~`
   panel - and only restyles it to the repo vocabulary. A small helper in
   extensions.js closes open panels on outside click; without JS they still
   open and close by their own labels. */
.odsd-srch__filters { margin: 0.4rem 0 1.1rem; }
.odsd-srch__filters .x-refinery {
  display: block; border: 0; padding: 0; margin: 0; opacity: 1; transition: none;
}
.odsd-srch__filters fieldset { border: 0; padding: 0; margin: 0; }
/* core.css gives every <form> a 16px bottom margin; here it was 16px of dead
   space under the pills, which sat the toolbar loose above the grid. */
.odsd-srch__filters form { margin: 0; }
.odsd-srch__filters legend { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.odsd-srch__filters .x-refinery .c-form-list,
.odsd-srch__filters .x-refinery__list {
  display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;
  list-style: none; margin: 0; padding: 0; position: static;
}
/* Theme-JS furniture (mobile annex drawer, overflow measurement) - nothing
   mounts it here, same as the category rail. */
.odsd-srch__filters .x-refinery__title-container,
.odsd-srch__filters [data-hook="refinery__view-target"],
.odsd-srch__filters [data-hook="refinery__overflow"] { display: none; }
/* core.css sizes .c-form-list__item in fractions of the row (the pills were
   spreading into three loose columns); pin each set to its content width. */
.odsd-srch__filters .x-refinery .c-form-list__item,
.odsd-srch__filters .x-refinery-set {
  position: relative; border: 0; padding: 0; margin: 0;
  width: auto; max-width: none; flex: 0 0 auto;
}
.odsd-srch__filters .x-refinery-set__trigger { display: none; }
/* Group label as the pill button. Same double selector + !important as the
   category rail: the generated mm-theme-styles resource pins the label size
   with an !important of its own. */
.odsd-srch__filters .x-refinery .x-refinery-set__label,
.odsd-srch__filters .x-refinery-set__label {
  display: inline-flex; align-items: center; gap: 0.45em; position: static;
  height: auto; margin: 0; cursor: pointer; white-space: nowrap;
  font-family: var(--odsd-font-display); font-weight: 600;
  font-size: 0.85rem !important;
  letter-spacing: normal; text-transform: none; color: var(--odsd-ink);
  background: #fff; border: 1px solid var(--odsd-line); border-radius: 999px;
  /* 34px outside, same as the sort select and the view toggle it now shares
     a row with (owner, 2026-08-22). Explicit height + line-height:1 because
     the inherited 1.65 line-height was making the pill ~46px. */
  box-sizing: border-box; height: 34px; line-height: 1; padding: 0 1em;
}
.odsd-srch__filters .x-refinery-set__label::before { display: none; content: none; }
/* Chevron, drawn - the theme's own ::after twisty is icon-font dependent. */
.odsd-srch__filters .x-refinery .x-refinery-set__label::after,
.odsd-srch__filters .x-refinery-set__label::after {
  content: ""; display: inline-block; width: 0.42em; height: 0.42em;
  border: 0; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translateY(-0.12em);
  position: static; background: none; margin: 0 0 0 0.15em;
}
.odsd-srch__filters .x-refinery-set__label:hover { border-color: var(--odsd-ink); }
.odsd-srch__filters .x-refinery-set__trigger:checked + .x-refinery-set__label {
  background: var(--odsd-ink); border-color: var(--odsd-ink); color: #fff;
}
/* The dropdown card. */
.odsd-srch__filters .x-refinery-set__list {
  position: absolute; top: calc(100% + 8px); left: 0; z-index: var(--odsd-z-page-float);
  min-width: 250px; max-width: min(330px, 86vw); max-height: 340px; overflow: auto;
  background: #fff; border: 1px solid var(--odsd-line); border-radius: 12px;
  box-shadow: var(--odsd-shadow-md); padding: 0.7rem 0.85rem;
  opacity: 0; pointer-events: none; transform: translateY(4px);
  transition: opacity 0.15s ease, transform 0.15s ease;
  font-weight: 400;
}
.odsd-srch__filters .x-refinery-set__trigger:checked ~ .x-refinery-set__list {
  opacity: 1; pointer-events: auto; transform: none;
}
/* Option rows - same vocabulary as the category rail. */
.odsd-srch__filters .c-form-checkbox {
  display: flex; align-items: center; gap: 0.5rem;
  font-size: 0.86rem; color: var(--odsd-ink); cursor: pointer;
  padding: 0.16rem 0; margin: 0; white-space: normal; background: none;
  height: auto;
}
.odsd-srch__filters .x-refinery-set__list label:hover { background: none; }
.odsd-srch__filters .x-refinery-set__list label.is-selected { background: none; font-weight: 600; }
.odsd-srch__filters .c-form-checkbox__input { margin: 0; accent-color: var(--odsd-ink); }
.odsd-srch__filters .c-form-checkbox__caption { font-size: 0.9rem; line-height: 1.65; }
.odsd-srch__filters .c-form-checkbox .u-font-small { color: var(--odsd-ink-soft); font-size: 0.76rem; }
/* Nested (Category) facet inside the card. Shadows' x-category-tree.css hides every
   .x-category-tree__row and shows them only from 60em, a collapsed sidebar tree with its
   own trigger - which this pill markup does not carry - so below 960px the Category pill
   opened an empty card (L4-O2, 2026-10-20). The pill IS the trigger: show the rows at
   every width, which is what 60em and up already does. */
.odsd-srch__filters .x-category-tree__row { display: block; }
/* select-type facets inside the card: reveal (Invisible Select, scoped). */
.odsd-srch__filters .c-form-select {
  display: block; position: relative; width: 100%; margin: 0; padding: 0;
  visibility: visible;
}
.odsd-srch__filters .c-form-select::before,
.odsd-srch__filters .c-form-select::after { display: none; content: none; }
.odsd-srch__filters .c-form-select__dropdown {
  display: block; width: 100%; height: auto; opacity: 1;
  -webkit-appearance: auto; appearance: auto;
  font: inherit; font-size: 0.84rem; padding: 0.45rem 0.6rem;
  color: var(--odsd-ink); border: 1px solid var(--odsd-line);
  border-radius: 8px; background: #fff; box-shadow: none;
}
/* Active-filter chips, inline after the pill row. */
.odsd-srch__filters .x-refinery__selected-filters {
  list-style: none; display: flex; flex-wrap: wrap; gap: 0.4rem;
  margin: 0.7rem 0 0; padding: 0;
}
.odsd-srch__filters .x-refinery__selected-filters .c-button {
  display: inline-flex; align-items: center; gap: 0.4em;
  font: 600 0.74rem var(--odsd-font-display); color: var(--odsd-ink);
  background: var(--odsd-sand); border: 1px solid var(--odsd-line);
  border-radius: 999px; padding: 0.34em 0.7em; cursor: pointer;
  text-decoration: none;
}
.odsd-srch__filters .x-refinery__selected-filters .c-button:hover {
  background: #fff; border-color: var(--odsd-ink-soft);
}

/* ---- category tree (restored 2026-08-03) --------------------------------
   Miva's own category_tree item, restyled - not rebuilt. Same reasoning as
   the facet rail above: faceting and the category tree stay Miva's job, we
   only change how they look. The item ships each row with Shadows' button
   classes (mm-theme-button-secondary-navigation), which paint a pill button,
   so the link rules below have to undo that explicitly.

   SPECIFICITY: core.css writes its rules two classes deep, so a single-class
   override loses regardless of source order (the "Two-Class Core Rule" in
   HANDOFF.md). Link rules here are three classes deep on purpose. */
.odsd-cat__tree {
  border-bottom: 1px solid var(--odsd-line);
  padding-bottom: 0.9rem; margin-bottom: 0.9rem;
}
.odsd-cat__tree .x-category-tree { border: 0; padding: 0; margin: 0; }
.odsd-cat__tree .x-category-tree__title { margin: 0 0 0.55rem; padding: 0; border: 0; }
/* Rail heading to the repo's `.filters-title`: display font, 1.1rem, sentence
   case. Was 0.82rem uppercase with 0.04em tracking - the same ODSD flourish the
   facet headings carried, and the last thing on this page reading a different
   type scale from the design. */
.odsd-cat__tree .x-category-tree__heading {
  font-family: var(--odsd-font-display); font-weight: 700; font-size: 1.1rem;
  line-height: 1.08;
  letter-spacing: normal; text-transform: none; color: var(--odsd-ink);
  margin: 0; padding: 0;
}
.odsd-cat__tree .x-category-tree__row { list-style: none; margin: 0; padding: 0; }
.odsd-cat__tree .x-category-tree__list { margin: 0; padding: 0; }
.odsd-cat__tree .x-category-tree__row .x-category-tree__link {
  display: block; width: auto; min-height: 0; height: auto;
  font: 400 0.84rem/1.35 var(--odsd-font-body);
  letter-spacing: normal; text-transform: none; text-align: left;
  color: var(--odsd-ink-soft); text-decoration: none;
  background: none; border: 0; border-radius: 0; box-shadow: none;
  padding: 0.3rem 0; margin: 0;
  transition: color 0.15s var(--odsd-ease);
}
.odsd-cat__tree .x-category-tree__row .x-category-tree__link:hover {
  color: var(--odsd-accent-deep); background: none; text-decoration: none;
}
.odsd-cat__tree .x-category-tree__row .x-category-tree__link--current {
  color: var(--odsd-ink); font-weight: 600;
}
/* The item emits a FLAT list with a --level-N class per row, so depth is
   expressed by indent here rather than by nesting in the markup. */
/* Level 1 is a PARENT row, not a peer: the department you are inside (subtree
   view) or each department heading (fallback view). Weighted so the rail reads
   as a hierarchy rather than a flat list. Four classes deep to outrank the base
   link rule above, which is three - not with !important. */
.odsd-cat__tree .x-category-tree__row .x-category-tree__list--level-1 > .x-category-tree__link {
  font-weight: 600; color: var(--odsd-ink);
}
.odsd-cat__tree .x-category-tree__list--level-2 .x-category-tree__link { padding-left: 0.85rem; }
.odsd-cat__tree .x-category-tree__list--level-3 .x-category-tree__link { padding-left: 1.7rem; }
.odsd-cat__tree .x-category-tree__list--level-4 .x-category-tree__link { padding-left: 2.55rem; }

/* ---- collapsible branches (2026-08-06) ----------------------------------
   Native <details>, no JS - see the comment in cattree.mvt for why we did not
   port the theme's accordion. The nested <ul> is reset to nothing so the
   level-3 padding above still measures from the rail edge, not from the branch. */
/* EVEN RHYTHM. Every row is the link's own 0.3rem/0.3rem padding and nothing
   else, so a branch with a dropdown sits on exactly the same beat as one
   without. Measured before this: rows were a consistent 27.7px but the first
   child of an open branch carried a 16px gap above it - a stray vertical margin
   on the nested <ul>/<details>, not on the links. Zeroed block-direction
   spacing only; the left padding is what draws the indent levels, so it stays. */
.odsd-cat__tree details,
.odsd-cat__tree summary,
.odsd-cat__tree ul,
.odsd-cat__tree li {
  margin-top: 0; margin-bottom: 0;
  padding-top: 0; padding-bottom: 0;
}
.odsd-cat__branch-d { margin: 0; }
.odsd-cat__branch-sub { list-style: none; margin: 0; padding-left: 0; padding-right: 0; }
.odsd-cat__branch-sum {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 0.3rem;
}
.odsd-cat__branch-sum::-webkit-details-marker { display: none; }
.odsd-cat__branch-sum .x-category-tree__link { flex: 1 1 auto; }
/* Chevron drawn from borders - no icon font, and it inherits colour. */
.odsd-cat__branch-sum::after {
  content: ""; flex: none;
  width: 0.4rem; height: 0.4rem; margin-right: 0.15rem;
  border-right: 1.5px solid var(--odsd-ink-soft);
  border-bottom: 1.5px solid var(--odsd-ink-soft);
  transform: rotate(45deg); opacity: 0.55;
  transition: transform 0.15s var(--odsd-ease), opacity 0.15s var(--odsd-ease);
}
.odsd-cat__branch-sum:hover::after { opacity: 1; }
.odsd-cat__branch-d[open] > .odsd-cat__branch-sum::after {
  transform: rotate(-135deg); margin-top: 0.2rem;
}

/* ---- category copy, below the listing (2026-08-06) -----------------------
   The category's Header field, which we had been suppressing entirely. See the
   comment in ctgy.mvt for why it moved here rather than staying hidden. Styled
   as body copy, not as a hero: it is there to be read and indexed, and it must
   not compete with the listing above it. Measured worth ~100+ words per page. */
.odsd-cat__copy {
  max-width: var(--odsd-container);
  margin: 0 auto;
  padding: clamp(1.2rem, 3vw, 2rem) clamp(1.1rem, 4vw, 3rem) clamp(2rem, 4vw, 3rem);
}
.odsd-cat__copy p {
  font-size: 0.95rem; line-height: 1.7; color: var(--odsd-ink-soft);
  margin: 0 0 0.85rem; max-width: 78ch;
}
.odsd-cat__copy p:last-child { margin-bottom: 0; }
.odsd-cat__copy strong { font-family: var(--odsd-font-display); font-weight: 700; color: var(--odsd-ink); }
.odsd-cat__copy :is(h1, h2, h3, h4) {
  font-family: var(--odsd-font-display); font-weight: 700; color: var(--odsd-ink);
  font-size: 1.05rem; margin: 0 0 0.6rem;
}
.odsd-cat__copy a { color: var(--odsd-accent-deep); }
/* The theme wraps this copy in a show-more shell whose JS we do not ship, so
   pin it open - otherwise it renders collapsed to nothing, the Template Trap. */
.odsd-cat__copy .t-category-description__article,
.odsd-cat__copy [data-hook="show-more"] {
  max-height: none; overflow: visible; opacity: 1; visibility: visible;
}

/* Checkbox-filter rail (owner request 2026-08-18): the current category's
   children (or, on a leaf, its siblings with itself checked) drawn as
   checkbox rows - but each row is a real crawlable LINK. Checking = navigate
   into the subcategory; clicking the checked row = back up to the parent.
   Styled to sit naturally above the real facet groups (Size / Type). */
.odsd-cat__catfilter-list { list-style: none; margin: 0 0 1.1rem; padding: 0; }
.odsd-cat__catfilter-item { padding: 0; }
.odsd-cat__catfilter {
  display: flex; align-items: center; gap: 0.55rem;
  padding: 0.3rem 0;
  color: var(--odsd-ink); text-decoration: none;
  font-size: 0.9rem; line-height: 1.3;
}
.odsd-cat__catfilter-box {
  /* display declared HERE on purpose: the box span carries aria-hidden and
     Shadows ships a bare [aria-hidden="true"]{display:none} - the aria-hidden
     Vanish bug in FINDINGS. Equal specificity, our file loads later, we win. */
  display: inline-block;
  width: 1rem; height: 1rem; flex: 0 0 auto;
  background: #fff; border: 1.5px solid #cfcabf; border-radius: 4px;
  position: relative;
}
.odsd-cat__catfilter:hover { text-decoration: none; }
.odsd-cat__catfilter:hover .odsd-cat__catfilter-box { border-color: var(--odsd-ink); }
.odsd-cat__catfilter:hover .odsd-cat__catfilter-name { color: var(--odsd-accent-deep); }
.odsd-cat__catfilter.is-checked .odsd-cat__catfilter-box {
  background: var(--odsd-ink); border-color: var(--odsd-ink);
}
.odsd-cat__catfilter.is-checked .odsd-cat__catfilter-box::after {
  content: ""; position: absolute;
  left: 4.5px; top: 1.5px; width: 4px; height: 8px;
  border: solid #fff; border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.odsd-cat__catfilter.is-checked .odsd-cat__catfilter-name { font-weight: 600; }

/* Subcategory chips above the grid (2026-08-18, checkbox-rail v3): the
   current category's children as crawlable pill links - the "more specific"
   layer now that the rail lists top-level categories only. */
.odsd-cat__chips {
  display: flex; flex-wrap: wrap; gap: 0.5rem;
  margin: 0 0 1.1rem;
}
.odsd-cat__chip {
  display: inline-block;
  font-family: var(--odsd-font-display); font-size: 0.8rem; font-weight: 600;
  color: var(--odsd-ink); text-decoration: none;
  border: 1px solid var(--odsd-line); border-radius: 999px;
  background: #fff;
  padding: 0.4rem 0.85rem;
  line-height: 1.2;
}
.odsd-cat__chip:hover { border-color: var(--odsd-accent-deep); color: var(--odsd-accent-deep); text-decoration: none; }
/* Rail placement (v4): the pills live in the rail's Categories slot now. */
.odsd-cat__chips--rail { margin: 0 0 1.25rem; gap: 0.4rem; }
.odsd-cat__chips--rail .odsd-cat__chip { font-size: 0.78rem; padding: 0.35rem 0.7rem; }

/* Applied-filter chips (2026-08-18 rebuild - stock block was unstyled theme
   buttons + a Permalink nobody wanted; removal handled in extensions.js). */
.odsd-applied {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem;
  margin: 0.9rem 0 0; padding-top: 0.9rem;
  border-top: 1px solid var(--odsd-line);
}
.odsd-applied__chip {
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-family: var(--odsd-font-display); font-size: 0.76rem; font-weight: 600;
  color: var(--odsd-ink);
  background: var(--odsd-sand); border: 1px solid var(--odsd-line);
  border-radius: 999px; padding: 0.32rem 0.75rem 0.32rem 0.55rem;
  cursor: pointer; line-height: 1.2;
}
.odsd-applied__chip:hover { border-color: var(--odsd-accent-deep); color: var(--odsd-accent-deep); }
.odsd-applied__chip:focus-visible { outline: 2px solid var(--odsd-accent); outline-offset: 1px; }
/* A label long enough to wrap squeezed the circle into an oval - measured
   9x16px at 320px (Miva QA ticket #1389066, 2026-09-28; no live facet value is
   that long today, the longest is "Outdoor Patio Speaker"). `flex: none` holds
   the circle; the chip's max-width lets the label wrap inside the row. */
.odsd-applied__chip { max-width: 100%; text-align: left; }
.odsd-applied__x {
  flex: none;
  display: inline-grid; place-items: center;
  width: 1rem; height: 1rem; border-radius: 50%;
  background: var(--odsd-ink); color: #fff;
  font-size: 0.72rem; line-height: 1;
}
.odsd-applied__chip:hover .odsd-applied__x { background: var(--odsd-accent-deep); }
.odsd-applied__clear {
  font-size: 0.76rem; font-weight: 600; color: var(--odsd-ink-soft);
  text-decoration: underline; margin-left: 0.2rem;
}
.odsd-applied__clear:hover { color: var(--odsd-accent-deep); }

/* ---- the listing itself ------------------------------------------------- */
.odsd-cat__list.is-list { display: grid; grid-template-columns: 1fr; gap: 14px; }
.odsd-cat__list:not(.is-list) {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--odsd-gap, 20px);
}

/* repo .lp-row */
.lp-row {
  position: relative; background: #fff; border: 1px solid var(--odsd-line);
  border-radius: 22px; padding: 20px 22px;
  display: grid; grid-template-columns: 1fr 210px; gap: 20px;
}
.lp-flags {
  position: absolute; top: 14px; left: 14px; z-index: 2;
  display: grid; gap: 5px; justify-items: start;
}
.lp-flag {
  font-family: var(--odsd-font-display); font-weight: 700; font-size: 0.62rem;
  line-height: 1; letter-spacing: 0.05em; text-transform: uppercase;
  padding: 0.42em 0.6em; border-radius: 5px;
}
.lp-flag--sale { background: var(--odsd-accent); color: var(--odsd-ink); }
.lp-flag--top { background: #16515a; color: #fff; }

/* The image FLOATS inside the text block rather than taking its own grid column.
   In a column, anything taller than the square left dead space beneath it and
   squeezed the text into a ribbon. Floated, the text sits beside the image and
   resumes full width underneath. (repo note, kept.) */
/* .lp-shot carries the float (the image used to). It exists so the grid view's
   overlay button has a positioned box the size of the image to sit in - see
   .lp-quick below. */
.lp-shot {
  float: left; width: 178px; margin: 0 20px 8px 0; position: relative;
}
.lp-media {
  width: 100%; margin: 0; aspect-ratio: 1 / 1;
  display: grid; place-items: center; overflow: hidden;
}
.lp-media img { max-width: 100%; max-height: 100%; object-fit: contain; }

/* Bare display SKU - grid headline only, hidden in the list where the full
   catalog name is the headline (the "Part# ..." line is hidden there too since
   2026-08-22). Built in inc-plst-product.mvt; see the comment there. */
.lp-sku { display: none; }

/* Spec chips (repo .card-badges / .cbadge) - up to 3 short gray feature pills
   ("Patio", "2-Way", "Weatherproof") from the card_badges custom field, built in
   inc-plst-product.mvt. Grid-only like .lp-line below: the repo's list row
   carries BULLET lines instead (.lp-specs - key_features, built as a <ul> under the
   stock line since 2026-08-22), so the list stays chipless. Values are repo-exact: .62rem/700/.04em uppercase on the
   mist gray, 5px radius. No slot-holding placeholder when the field is empty
   (the repo's own card drops the row too); full data coverage is the fix for
   ragged rows, same as product_line's 397/398. */
.lp-badges { display: none; }
.lp-badge {
  font-size: 0.62rem; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; background: var(--odsd-mist);
  color: #68686d; padding: 0.25em 0.5em; border-radius: 5px;   /* 4.6:1 on mist (ink-soft was 4.44), audit 2026-09-22 */
}

/* The PDP shows the same chips under the price (repo pdp: `.card-badges` right
   after `.pdp-price`, before the Finish swatches). Same class, same field,
   same values; only the grid-only hide is lifted here (owner, 2026-08-21). */
body.t-page-prod .odsd-pdp__buy-main .lp-badges {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; order: 2; margin: 0.3rem 0 0.9rem; /* measured: price box already carries 14px below, so 5+14 above = 14 below */
}

/* Gold eyebrow (repo .product-line). Present in the markup on every card, shown
   only where the headline is the bare SKU - i.e. the grid. In the list the long
   catalog name states the type one line down, so it would just repeat itself. */
.lp-line {
  display: none;
  font-size: 0.8125rem; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--odsd-accent-deep);
  margin: 0 0 0.2rem;
}

/* letter-spacing -0.02em comes from the repo's global `h1,h2,h3,h4` rule, which
   Shadows has no equivalent of - without it the list title measures the repo's
   size and weight but still reads looser than the design. The grid override
   below resets it: there the name is demoted to body-copy blurb (.card-blurb),
   which the repo does not track-tighten. */
.lp-name {
  font-family: var(--odsd-font-display); font-weight: 800; font-size: 1.16rem;
  line-height: 1.3; letter-spacing: -0.02em; margin: 0 0 0.2rem;
}
.lp-name a { color: var(--odsd-ink); text-decoration: none; }
.lp-name a:hover { color: var(--odsd-accent-deep); }
/* Split-blurb spans (owner, 2026-08-26). When the product has a
   product_custom_name descriptor, the h3's anchor holds TWO texts -
   .lp-name__full (the catalog name) and .lp-name__desc (the PDP-subtitle
   descriptor) - and the view picks one, same technique as the SAVE/SALE flag.
   Default/list: the full name is the headline, descriptor hidden. The grid
   swap lives with the other grid rules further down. Cards WITHOUT a
   descriptor render a plain unsplit h3, so these rules never touch them. */
.lp-name__desc { display: none; }
.lp-part { font-size: 0.74rem; color: var(--odsd-ink-soft); margin: 0 0 0.35rem; }
/* "Part# ..." line dropped from the LIST row too (owner, 2026-08-22) - the grid
   already hid it. The code is still in the markup (and the PDP); nothing else
   on the card needed it once the headline is the full catalog name. */
.odsd-cat__list .lp-part { display: none; }
/* Repo .lp-specs slot - three lines under the stock line, list view only.
   Repo sizing (.9rem/1.5, .3rem gap) but NO marker of any kind: no bullet, no
   tick, no indent - plain text lines (owner, 2026-08-22, after trying both).
   Source is key_features (the PDP's benefit lines), built in inc-plst-product.mvt.
   Must stay a BLOCK, not grid/flex: a flex list makes its own formatting
   context and sits BESIDE the floated image instead of flowing under it. */
.lp-specs { display: block; margin: 0; padding: 0; list-style: none; }
.lp-specs li { font-size: 0.9rem; line-height: 1.5; margin-bottom: 0.3rem; color: var(--odsd-ink); }

/* THE WHOLE CARD IS A LINK TO THE PRODUCT (owner, 2026-08-07).
   The "stretched link" pattern: the product name's EXISTING anchor grows a
   pseudo-element covering the card, so a click anywhere lands on the PDP.

   NOT a JS click handler and NOT a wrapping <a>, both deliberately:
     - a wrapper would put the add-to-cart <form> inside an anchor, which is
       invalid HTML and lets the anchor swallow the button's click - the same
       reason .lp-shot keeps the image link and .lp-quick as SIBLINGS.
     - a wrapper or a duplicate anchor would add a SECOND crawlable link and a
       second tab stop to all twelve cards. This adds no markup at all: same one
       link, same tab order, same accessible name (the product name).
     - a JS handler would not work on the pages where extensions.js is absent,
       and would not give the browser a real URL to preview, middle-click or
       open in a new tab.

   The containing block is .lp-row, which declares `position: relative` in the
   base rule above AND again in the grid rule further down, so both views work.

   Free side effect worth knowing: hovering ANYWHERE on the card now matches
   `.lp-name a:hover`, so the title takes its hover colour and the grid's image
   zoom and quick-add reveal all fire together.

   TRADE-OFF, and it is the accepted one for this pattern: text inside the card
   can no longer be drag-selected. Everything raised in the next rule stays
   fully clickable. */
.lp-name a::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  border-radius: inherit;
}

/* Anything that is its OWN control has to sit above that overlay, or the card
   link eats it:
     .lp-shot  - the image anchor plus the grid's over-image quick-add
     .lp-add / .lp-cart - the rail's Add to cart, and the out-of-stock
                 "View details" anchor, which carries .lp-cart too
     .lp-rating - insurance, not observed: it is EMPTY on branch 16, but Shopper
                 Approved fills it client-side on production and its stars can
                 carry a link.
   .lp-flags is the opposite case - the SALE / badge labels are decorative, so
   they get `pointer-events: none` and let the click through to the card link
   rather than sitting on top of it dead. */
.lp-shot,
.lp-rating,
.lp-add,
.lp-cart { position: relative; z-index: 2; }
/* z-index 3, NOT the 2 the base rule declares, and this is a fix for a
   regression this block caused rather than a precaution. `.lp-flags` sits
   BEFORE `.lp-main` in the DOM, so once `.lp-shot` was also raised to 2 the two
   tied and source order handed the stack to the image - the teal "Open Box"
   badge disappeared behind every product photo on the Outlet grid, with only a
   sliver showing past the image's left edge. Ties on z-index are decided by DOM
   order; raise the earlier element to break it. */
.lp-flags { z-index: 3; pointer-events: none; }

/* Gold line on the LEGACY card - search, product list, related products and
   recently-viewed (2026-08-07). Its own class because that card is centred and
   because `.lp-line` is display:none by default for the CTGY list view. Same
   type scale as the repo's .product-line. */
.odsd-plist-line {
  display: block; font-size: 0.8125rem; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--odsd-accent-deep); margin: 0 0 0.15rem;
}

/* The over-image control is grid-only; the list view uses the rail button. One
   of the two is always display:none, so only one is focusable at a time.
   TWO classes on purpose: the out-of-stock copy also carries .lp-cart, which
   declares `display: inline-block` further down this file. Single-class rules
   tie on specificity and source order decides, so a bare `.lp-quick` here loses
   and the list view grows a second "View details" under every sold-out image. */
.lp-shot .lp-quick { display: none; }
.lp-stock {
  display: flex; align-items: center; gap: 0.35rem; margin: 0 0 0.7rem;
  font-size: 0.88rem; font-weight: 600; color: #1c7a3e;
}
.lp-stock--low { color: var(--odsd-accent-deep); }
.lp-stock--out { color: var(--odsd-ink-soft); }
.lp-tick {
  display: grid; place-items: center; width: 15px; height: 15px;
  border-radius: 50%; background: #1c7a3e; color: #fff; font-size: 0.6rem;
}
.lp-tick--low { background: var(--odsd-accent-deep); }
.lp-tick--out { background: var(--odsd-ink-soft); }

.lp-rail {
  display: grid; gap: 0.4rem; align-content: start;
  justify-items: end; text-align: right;
}
/* LIST view: the rail is a flex column and the Add to cart / View details
   control is pushed to the BOTTOM-RIGHT corner of the row (owner, 2026-08-22),
   flush with the bottom of the text block, instead of hanging under the price
   wherever that lands. Rating/was/price stay stacked at the top. Grid has its
   own .lp-rail override further down, so this is list-scoped on purpose. */
.odsd-cat__list.is-list .lp-rail {
  display: flex; flex-direction: column; align-items: flex-end;
}
.odsd-cat__list.is-list .lp-rail .lp-add,
.odsd-cat__list.is-list .lp-rail .lp-cart--out { margin-top: auto; }
/* Shopper Approved group2.0 widget fills the BARE inner .star_container.<code>
   div client-side (wired 2026-08-14; the loader is in ctgy-category_listing.mvt
   and the markup note in inc-plst-product.mvt explains why the inner div must
   carry no other class). Unreviewed products keep the server-rendered
   "Not yet rated" - the repo's own no-reviews copy. */
.lp-rating { font-size: 0.82rem; color: var(--odsd-ink-soft); }
/* SA injects its own <style> with `.star_container { min-height: 25px }` - a
   reserved gap we do not want; (0,2,0) outranks it without !important. */
.lp-rating .star_container { min-height: 0; }
.lp-rating .group-stars { display: inline-flex; align-items: center; }
/* Their stars are 20x19px SVG backgrounds; scale to sit with 0.82rem text like
   the repo's 0.9rem glyph stars. background-size is needed once the box shrinks. */
.lp-rating .group-stars .on,
.lp-rating .group-stars .half,
.lp-rating .group-stars .off {
  width: 15px; height: 14px; background-size: contain; background-repeat: no-repeat;
}
/* Repo shows "(84)"; SA renders "84 reviews" at 14px !important. Match the
   repo: their own rules carry !important, so these must too. */
.lp-rating .ind_cnt {
  font-size: 0.82rem !important; padding-left: 6px !important;
  vertical-align: 0 !important; color: var(--odsd-ink-soft);
}
.lp-rating .ind_cnt_desc { display: none; }
.lp-rating .ind_cnt_num::before { content: "("; }
.lp-rating .ind_cnt_num::after { content: ")"; }
/* Compact grid rating badge (repo .card-rating / .cbadge-rating) - built by
   extensions.js from the SA fill, hidden in the list view where the five-star
   strip above does the job. Shown by the grid rules further down. */
.lp-cardrating { display: none; }
.lp-ship { font-size: 0.82rem; color: var(--odsd-ink-soft); }
.lp-was { font-size: 0.84rem; color: var(--odsd-ink-soft); text-decoration: line-through; }
.lp-price {
  font-family: var(--odsd-font-display); font-weight: 800;
  font-size: 1.6rem; line-height: 1; color: var(--odsd-ink);
}
/* PRICE + SALE PILL ON ONE LINE (owner, 2026-08-07). A deliberate departure
   from the repo, which stacks the pill in the card's top-left corner - see the
   note in inc-plst-product.mvt. LIST view: .lp-rail is a grid with
   `justify-items: end`, so a wrapper is the only way two spans share a row.
   `justify-content: flex-end` keeps the pair hard against the right edge like
   everything else in the rail. */
.lp-priceline {
  display: flex; align-items: center; justify-content: flex-end;
  flex-wrap: wrap; gap: 0.5rem;
}
/* Pill sizing beside the price. `.lp-flag` already carries the pill shape and
   type from the corner-badge rules above; only the line-height needs relaxing,
   because 1 crops the descender when it sits next to 1.6rem type instead of on
   its own. */
.lp-priceline .lp-flag--sale { line-height: 1.2; }
.lp-add { width: 100%; margin-top: 0.35rem; }
.lp-cart {
  width: 100%; display: inline-block; text-align: center; cursor: pointer;
  padding: 0.72rem 1.2rem; border-radius: 999px; border: 1px solid transparent;
  font-family: var(--odsd-font-display); font-weight: 600; font-size: 0.86rem;
  background: var(--odsd-ink); color: #fff; text-decoration: none;
  transition: background 0.2s var(--odsd-ease), border-color 0.2s var(--odsd-ease);
}
.lp-cart:hover { background: #000; color: #fff; }
/* repo .btn--primary, type AND shape: the list rail's Add to cart is UPPERCASE
   with .06em tracking on a 14px radius - the repo's own comment says the 999px
   pill "distorts" at full row width and squarer corners match the cards. Scoped
   under .lp-add because THREE controls share .lp-cart and the other two keep
   the repo's pill and case: the grid's over-image quick-add (repo .quick-add)
   and the out-of-stock "View details" (repo .btn--light). */
.lp-add .lp-cart {
  text-transform: uppercase; letter-spacing: 0.06em;
  border-radius: 14px; padding: 0.9rem 1.6rem;
}
/* SIX classes, and none of them decorative. Shadows core.css ships
   `.c-button:hover:not(:disabled):not([aria-disabled=true]):not(.c-button--as-link)`
   - specificity (0,5,0) - which repaints ANY hovered .c-button white with a black
   border. It was quietly beating the rule above, so every Add-to-cart on this page
   inverted on hover in both views (found 2026-08-07 while porting the grid card;
   pre-existing, not caused by that work). This is the Two-Class Core Rule with a
   longer :not() chain: match the weight, never reach for !important. */
.odsd-cat__list .lp-row .lp-rail .c-button.lp-cart:hover {
  background: #000; color: #fff; border-color: transparent;
}
.odsd-cat__list .lp-row .lp-rail .c-button.lp-cart--out:hover {
  background: #fff; color: var(--odsd-ink); border-color: var(--odsd-ink);
}
.lp-cart--out {
  background: #fff; color: var(--odsd-ink); border-color: var(--odsd-line);
  margin-top: 0.35rem;
}
.lp-cart--out:hover { background: #fff; color: var(--odsd-ink); border-color: var(--odsd-ink); }

/* Grid mode restacks the SAME rows via CSS only - no re-render, so the
   crawlable markup is identical in both views.
   Ported to the repo grid card 2026-07-31: LEFT aligned (it was centred, which
   is what made it read as a different font - four centred lines of catalog name
   against the repo's short left-aligned model code), price and compare-at on
   one line with the price first, and the add-to-cart sitting over the image
   rather than taking a row of its own. */
.odsd-cat__list:not(.is-list) .lp-row { grid-template-columns: 1fr; text-align: left; position: relative; }
.odsd-cat__list:not(.is-list) .lp-shot {
  float: none; width: 100%; margin: 0 0 12px;
}
/* DOM order is list order (name, then part number). The grid inverts the two, so
   .lp-main becomes a flex column and reorders rather than duplicating markup. */
.odsd-cat__list:not(.is-list) .lp-main { display: flex; flex-direction: column; }
.odsd-cat__list:not(.is-list) .lp-shot  { order: 1; }
/* Three order-2 rows, stacked by DOM order (cardrating is injected as .lp-main's
   first child by extensions.js; .lp-badges sits before .lp-line in the markup):
   rating, chips, gold line - the repo card's exact sequence. */
.odsd-cat__list:not(.is-list) .lp-badges {
  order: 2; display: flex; flex-wrap: wrap; align-items: center;
  gap: 0.35rem; margin: 0 0 0.3rem;
}
.odsd-cat__list:not(.is-list) .lp-line  { order: 2; display: block; }
.odsd-cat__list:not(.is-list) .lp-sku   { order: 3; display: block; }
.odsd-cat__list:not(.is-list) .lp-name  { order: 4; }
.odsd-cat__list:not(.is-list) .lp-stock { order: 5; }

/* HEADLINE SWAP (owner, 2026-08-07). The repo card is a gold type line over a
   bare model - "CDX8" - with the descriptive sentence underneath. Ours crammed
   all of it into one four-line name. The stripped SKU becomes the headline and
   the catalog name drops to the blurb. Nothing left the markup: the name is
   still the <h3> and still the link, which is why this does not repeat the
   product_custom_name SEO problem. (Since 2026-08-26 the blurb TEXT prefers
   the product_custom_name descriptor over the full name - see the split-span
   rules below - but the structure here is unchanged.) */
.odsd-cat__list:not(.is-list) .lp-sku {
  font-family: var(--odsd-font-display); font-weight: 700; font-size: 1.15rem;
  line-height: 1.25; color: var(--odsd-ink); margin: 0 0 0.3rem;
}
/* The SKU headline already IS the part number, so repeating "Part# OSD-ACE600"
   under "ACE600" is exactly the bloat this removes. Still in the markup. */
.odsd-cat__list:not(.is-list) .lp-part { display: none; }

/* Repo: .product-card:hover .product-art { transform: scale(1.04) }. Grid only -
   the repo's list row has no zoom. .lp-media is overflow:hidden so it crops. */
.odsd-cat__list:not(.is-list) .lp-media img {
  transition: transform 0.5s var(--odsd-ease);
}
.odsd-cat__list:not(.is-list) .lp-row:hover .lp-media img { transform: scale(1.04); }
/* repo .card-blurb */
.odsd-cat__list:not(.is-list) .lp-name {
  font-family: var(--odsd-font-body); font-weight: 400; font-size: 0.8125rem;
  line-height: 1.5; letter-spacing: normal; color: var(--odsd-ink-soft); margin: 0 0 0.5rem;
}
.odsd-cat__list:not(.is-list) .lp-name a {
  color: var(--odsd-ink-soft);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden;
}
.odsd-cat__list:not(.is-list) .lp-name a:hover { color: var(--odsd-ink); }
/* Grid blurb prefers the PDP subtitle (owner, 2026-08-26): under a bare-model
   headline, the full catalog name restated the model on 20 of the 22
   best-sellers ("...Black/White BTP525" under "BTP-525"); the descriptor half
   of product_custom_name was written for exactly this slot. Spans swap INSIDE
   the same anchor, so the clamp above, the stretched-link overlay and the tab
   order are untouched. Unsplit cards (no descriptor) keep showing the full
   name here - the fallback is doing nothing at all. */
.odsd-cat__list:not(.is-list) .lp-name--split .lp-name__full { display: none; }
.odsd-cat__list:not(.is-list) .lp-name--split .lp-name__desc { display: inline; }
.odsd-cat__list:not(.is-list) .lp-stock { justify-content: flex-start; }
.odsd-cat__list:not(.is-list) .lp-specs { display: none; }
.odsd-cat__list:not(.is-list) .lp-rail {
  justify-items: start; text-align: left;
  border-top: 1px solid var(--odsd-line); padding-top: 0.8rem;
  /* `center`, NOT `baseline` (owner, 2026-08-07) - the same fault the PDP price
     row had, and the same fix. Baseline lines up the TEXT baselines of the
     1.2rem price, the 0.95rem struck price and the SALE pill, so the pill's
     padded box hangs below both and the trio reads as sitting on a bottom rule.
     The repo's own `.pdp-price` is `align-items: center`. */
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.1rem 0.5rem;
}
/* Price first, compare-at after it, both on one line (repo: "$379  $449"). The
   DOM order is was-then-price for the list view, so grid swaps it with order
   rather than duplicating markup. */
/* The five-star SA strip is LIST-view furniture. The repo's grid card carries
   the compact top badge instead (its main.js renders a different template per
   view - productCard vs listRow); ours swaps visibility on shared markup. */
.odsd-cat__list:not(.is-list) .lp-rating { display: none; }
/* Repo .card-rating/.cbadge-rating: Poppins 700 .75rem star-gold, count in
   soft ink, LEFT-aligned at the top of the info block (.card-rating resets
   the badge's margin-left:auto), one slot on every card so rows stay even.
   `display` declared here outranks the theme's [aria-hidden="true"]
   {display:none}, which the blank placeholder would otherwise trip (the
   aria-hidden Vanish trap - extensions.js explains the placeholder). */
.odsd-cat__list:not(.is-list) .lp-cardrating {
  display: block; order: 2; margin: 0 0 0.1rem;
  font-family: var(--odsd-font-display); font-weight: 700; font-size: 0.75rem;
  color: var(--odsd-ink); white-space: nowrap;
}
/* The star is DECORATIVE (the number beside it carries the rating), so it is drawn here, not as
   text: star-gold text was 2.58:1 on white and failed WCAG contrast (audit 2026-09-22). */
.odsd-cat__list:not(.is-list) .lp-cardrating[data-filled]::before { content: "\2605"; color: var(--odsd-star); margin-right: 0.25em; }
.odsd-cat__list:not(.is-list) .lp-cardrating .lp-cardrating__count {
  color: var(--odsd-ink-soft); font-weight: 600;
}
/* "Free shipping over $50" dropped from the CARD in BOTH views (owner: grid
   2026-08-07, list 2026-08-22): the utility bar already says it on every page, so
   repeating it on every card is noise. The repo's own list row keeps a .lp-ship
   line - this is a deliberate owner deviation, do not restore to "match the repo".
   Still in the markup, so restoring it is a one-line change. */
.odsd-cat__list .lp-ship { display: none; }
/* Price scale is LIST-view sizing (1.6rem / 800), which matches the repo's own
   list row exactly. The repo's CARD is smaller - .product-price is 1.2rem/700
   and its <del> .95rem/500 - so the grid, and only the grid, scales down. */
.odsd-cat__list:not(.is-list) .lp-price { order: 2; font-size: 1.2rem; font-weight: 700; }
.odsd-cat__list:not(.is-list) .lp-was { order: 3; font-size: 0.95rem; font-weight: 500; }
/* `display: contents` DISSOLVES the .lp-priceline wrapper here, so .lp-price and
   the pill rejoin the rail's own flex row and the `order:` values above keep
   working. Without it the wrapper becomes the flex item, `order: 2` on a child
   inside it does nothing, and the grid card's price / was / pill sequence
   scrambles. The list view needs the wrapper; the grid view needs it gone; this
   is how one piece of markup serves both. */
.odsd-cat__list:not(.is-list) .lp-priceline { display: contents; }
.odsd-cat__list:not(.is-list) .lp-flag--sale { order: 4; }

/* ADD-TO-CART OVER THE IMAGE (owner, 2026-08-07). Previously logged here as NOT
   PORTED because .lp-add lives in .lp-rail with no image to anchor to. Solved in
   the markup instead: a second control now sits beside the image inside
   .lp-shot, and each view displays exactly one of the two. The rail copy is the
   list view's; this one is the grid's. */
.odsd-cat__list:not(.is-list) .lp-add,
.odsd-cat__list:not(.is-list) .lp-cart--out { display: none; }
.odsd-cat__list:not(.is-list) .lp-quick {
/* Full width of .lp-shot, not inset. The repo's .quick-add insets .9rem inside a
   .product-media that spans the WHOLE card - ours already sits inside the card's
   22px padding, so insetting again made it read ~58px narrower than the repo. */
  display: block; position: absolute; left: 0; right: 0; bottom: 0.9rem;
  width: auto; margin: 0;
  opacity: 0; transform: translateY(8px); pointer-events: none;
  transition: opacity 0.3s var(--odsd-ease), transform 0.3s var(--odsd-ease);
}
.odsd-cat__list:not(.is-list) .lp-quick__btn {
  width: 100%; margin: 0; padding: 0.75rem; font-size: 0.9rem;
  background: var(--odsd-ink); color: #fff; border-color: transparent;
}
/* Same core.css hover override as .lp-cart above - needs (0,6,0) to survive it.
   Gold on hover, matching the repo's .quick-add. */
.odsd-cat__list .lp-row .lp-shot .c-button.lp-quick__btn:hover {
  background: var(--odsd-accent-deep); color: #fff; border-color: transparent;
}
/* :focus-within as well as :hover - the button is keyboard reachable in this
   view (the rail copy is display:none), so tabbing to it has to reveal it. */
.odsd-cat__list:not(.is-list) .lp-row:hover .lp-quick,
.odsd-cat__list:not(.is-list) .lp-row:focus-within .lp-quick {
  opacity: 1; transform: none; pointer-events: auto;
}
/* TOUCH: the button leaves the image entirely (owner, 2026-09-08). Touch has no
   hover, so the reveal never fires and the button is unreachable - that much was
   right. The old fix here pinned the OVERLAY on (`opacity: 1`), which parked a
   black pill permanently across the bottom fifth of every product photo; on the
   Forza landscape speakers it cut the mounting stake in half. An overlay that
   never hides is not an overlay, it is a bar over the picture.
   The rail copy is the same control and it is already in the markup - the grid
   view just hides it (see the display:none pair above). On touch we swap which
   of the two shows: the quick-add goes, the rail's Add to cart comes back, on its
   own full-width line under the price, and the photo is never covered.
   `flex: 1 0 100%` because .lp-rail is a wrapping flex row - the button has to
   claim a line of its own rather than sit beside the price.
   Desktop is untouched: with a pointer, the hover reveal is still the design. */
@media (hover: none) {
  .odsd-cat__list:not(.is-list) .lp-quick { display: none; }
  /* `order: 5` is load-bearing. The grid card reorders this rail (price 2, was 3,
     sale pill 4) and an unordered flex item defaults to 0, so the button came back
     ABOVE the price. 5 puts it last, which is the order the card reads in. */
  .odsd-cat__list:not(.is-list) .lp-add,
  .odsd-cat__list:not(.is-list) .lp-cart--out {
    display: block; order: 5; flex: 1 0 100%; width: 100%; margin-top: 0.7rem;
  }
  /* .lp-add is only the FORM - the button inside it is .lp-cart, which the 760px
     block narrows to `width: auto; min-width: 190px` for the list rail. Full width
     here or the card gets a 190px button floating in a 318px row. */
  .odsd-cat__list:not(.is-list) .lp-add .lp-cart { width: 100%; min-width: 0; }
}

/* Repo badge wording differs per view: "SAVE $70" in the list, "SALE" in the
   grid. Both are in the markup; each view shows one. */
.lp-flag__sale { display: none; }
.odsd-cat__list:not(.is-list) .lp-flag__save { display: none; }
.odsd-cat__list:not(.is-list) .lp-flag__sale { display: inline; }

/* Empty state - a search that matched nothing. Shares the listing's column so it
   lines up with where results would have been. */
.odsd-cat__empty { padding: 1.5rem 0 3rem; max-width: 46rem; }
.odsd-cat__empty-lead {
  font-family: var(--odsd-font-display); font-weight: 700; font-size: 1.15rem;
  color: var(--odsd-ink); margin: 0 0 0.5rem;
}
.odsd-cat__empty-help { font-size: 0.95rem; color: var(--odsd-ink-soft); margin: 0 0 1rem; line-height: 1.6; }
.odsd-cat__empty-links { font-size: 0.95rem; display: flex; gap: 0.6rem; flex-wrap: wrap; }
.odsd-cat__empty-links a { color: var(--odsd-accent-deep); font-weight: 600; }
.odsd-cat__empty-links a:hover { color: var(--odsd-ink); }
.odsd-cat__empty-links span { color: var(--odsd-ink-soft); }
/* Phones: the two links do not fit one line, and once they wrapped the dot separator was
   left dangling after the first (L4-O6, 2026-10-20). Stack them and drop the dot. */
@media (max-width: 640px) {
  .odsd-cat__empty-links { flex-direction: column; align-items: flex-start; }
  .odsd-cat__empty-links span[role="presentation"] { display: none; }
}

/* ---- pagination (repo has none; this is Miva's, styled to match) --------- */
.odsd-cat__pager {
  display: flex; align-items: center; justify-content: center;
  gap: 0.5rem; flex-wrap: wrap; margin-top: 2.2rem;
}
.odsd-cat__pager-pages {
  display: flex; align-items: center; gap: 0.3rem;
  list-style: none; margin: 0; padding: 0;
}
.odsd-cat__pager-btn, .odsd-cat__pager-page {
  font-family: var(--odsd-font-display); font-size: 0.84rem; font-weight: 600;
  color: var(--odsd-ink); text-decoration: none;
  border: 1px solid var(--odsd-line); border-radius: 999px;
  padding: 0.5rem 0.95rem; background: #fff; display: inline-block;
}
.odsd-cat__pager-page { min-width: 2.4rem; text-align: center; padding: 0.5rem 0.6rem; }
.odsd-cat__pager-btn:hover, .odsd-cat__pager-page:hover { border-color: var(--odsd-ink); }
.odsd-cat__pager-page.is-current { background: var(--odsd-ink); color: #fff; border-color: var(--odsd-ink); }
.odsd-cat__pager-btn.is-disabled { color: var(--odsd-ink-soft); opacity: 0.55; }
/* The "..." between the first/last page and the current-page window (windowed
   pager, 2026-08-18). Plain text on purpose - it must not read as clickable. */
.odsd-cat__pager-gap {
  color: var(--odsd-ink-soft);
  display: inline-block;
  padding: 0.5rem 0.15rem;
  font-size: 0.84rem;
}

@media (max-width: 1024px) {
  .odsd-cat__list:not(.is-list) { grid-template-columns: repeat(2, 1fr); }
}
/* Below 860 the rail stacks ABOVE the listing, so leaving it expanded pushed the
   first product off the bottom of a phone screen - on Outdoor Speakers that is
   three facet groups and thirteen checkboxes before a single product. Collapsed
   behind a Filters button instead. Desktop is unchanged: the button is hidden
   and the rail is always open. */
.odsd-cat__filters-toggle { display: none; }
@media (max-width: 860px) {
  .odsd-cat__layout { grid-template-columns: 1fr; }
  .odsd-cat__side { position: static; }
  /* The tree stays in the DOM on phones - it is the page's link graph, and
     hiding it behind the Filters button would bury this category's own
     subcategories. Capped and scrollable so it does not push the first
     product off the screen, which is the same problem the facet rail had. */
  .odsd-cat__tree { max-height: 300px; overflow-y: auto; }
  .odsd-cat__filters-toggle {
    display: inline-flex; align-items: center; gap: 0.5rem;
    font-family: var(--odsd-font-display); font-size: 0.9rem; font-weight: 600;
    color: var(--odsd-ink); background: #fff; cursor: pointer;
    border: 1px solid var(--odsd-line); border-radius: 999px;
    padding: 0.6rem 1.15rem; margin-bottom: 1rem;
  }
  .odsd-cat__filters-toggle svg { width: 16px; height: 16px; }
  .odsd-cat__filters-toggle[aria-expanded="true"] { border-color: var(--odsd-ink); }
  .odsd-cat__filters { display: none; margin-bottom: 1.4rem; }
  .odsd-cat__filters.is-open { display: block; }
}
@media (max-width: 760px) {
  .lp-row { grid-template-columns: 1fr; }
  .lp-shot { width: 116px; margin-right: 14px; }
  /* LIST ROW HEAD on phones: thumbnail and title as two real columns (owner,
     2026-08-27). `.lp-shot` floats, which is right on desktop where the catalog
     name is one or two lines - the text sits beside the image and resumes full
     width underneath. At 430px those names run to five lines, so the last line
     cleared the float and jumped back to the row's left edge, under the picture.
     It read as broken indentation rather than as text wrapping.
     Only the HEAD is columnar: the title stays in column 2 beside the thumb, and
     everything below it (stock, key features, price rail) still spans the full
     width, which is where those lines already read well. Scoped to `.is-list`
     because the grid card puts the image on top and must keep its own layout. */
  .odsd-cat__list.is-list .lp-main {
    display: grid; grid-template-columns: 116px minmax(0, 1fr);
    column-gap: 14px; align-items: start;
  }
  .odsd-cat__list.is-list .lp-shot {
    float: none; width: auto; margin: 0; grid-column: 1; grid-row: 1;
  }
  .odsd-cat__list.is-list .lp-name { grid-column: 2; grid-row: 1; margin-top: 0; }
  /* Hidden children take no grid slot, so this only ever catches what is shown. */
  .odsd-cat__list.is-list .lp-main > *:not(.lp-shot):not(.lp-name) { grid-column: 1 / -1; }
  .lp-rail {
    justify-items: start; text-align: left;
    border-top: 1px solid var(--odsd-line); padding-top: 0.8rem;
  }
  .lp-add { width: auto; min-width: 190px; }
  .lp-cart { width: auto; min-width: 190px; }
}
/* Narrow phones (360px and down) leave the title only ~120px beside a 116px thumb,
   which is more wrap than the row can carry. Shrink the thumb rather than the type. */
@media (max-width: 400px) {
  .odsd-cat__list.is-list .lp-main { grid-template-columns: 96px minmax(0, 1fr); column-gap: 12px; }
}
@media (max-width: 480px) {
  .odsd-cat__list:not(.is-list) { grid-template-columns: 1fr; }
}

/* ============================================================================
   PRODUCT PAGE (PROD) - repo product.html parity
   ----------------------------------------------------------------------------
   ARCHITECTURE, same as CTGY. The PDP is SERVER-RENDERED in the light DOM by
   Miva's own `product_display` + `related_products` items - the path production
   uses. It replaced <mmx-product-details>, which rendered the whole page into a
   shadow root and cost every heading and all the microdata production emits.

   We restyle Miva's markup rather than rewrite `prod-product_display.mvt`
   (30KB of pricing, attribute, wishlist and shipping-estimate logic). The class
   names below are the store's real ones, read off the rendered page - not
   guessed. Repo equivalents are named in comments.

   All content is live Miva catalog data: name and subtitle from
   `product_custom_name`, specs from the `specifications` custom field, gallery
   from the product's image records, related products from real merchandising.
   ========================================================================= */

body.t-page-prod .odsd-pdp { background: #fff; }

/* repo .pdp-crumb */
body.t-page-prod .t-breadcrumbs,
body.t-page-prod .x-breadcrumbs {
  font-size: 0.85rem; color: var(--odsd-ink-soft);
}

/* The buy section sits inside the theme's `.o-wrapper`, which adds 16px of its
   own horizontal padding; the bands below do not. At desktop the container
   max-width binds and both centre to the same edge, so it never showed - at
   430px nothing binds and the buy column sat 16px right of every band (measured
   37 vs 21 by tools\pdp_audit.py). The section carries its own max-width and
   padding, so the wrapper's is redundant here. */
body.t-page-prod .odsd-pdp .o-wrapper {
  padding-left: 0; padding-right: 0;
}

/* ---- repo .pdp : two-column media / buy box ---------------------------- */
body.t-page-prod .x-product-layout {
  max-width: var(--odsd-container); margin: 0 auto;
  /* Horizontal padding is the REPO'S `.container` value, and it has to be, because
     every band below uses it too. It was `clamp(1rem, 4vw, 2rem)`, which put the
     buy column's content edge at x=135 while all six bands sat at x=151 - a 16px
     step down the whole page, on every product. Found by the geometry audit
     (tools\pdp_audit.py), not by eye; 16px is small enough to look intentional. */
  padding: clamp(1.2rem, 3vw, 2rem) clamp(1.1rem, 4vw, 3rem);
  /* Bottom padding zeroed 2026-08-05. It only ever showed below the buy column
     at narrow widths, so the space above the section bar was 19px on a phone
     and 0px on a desktop. The gap now belongs to `.odsd-pdp-info` alone, which
     makes it one number at every width instead of two accidents. */
  padding-bottom: 0;
  align-items: start;
  /* NO `gap` here. The theme's o-layout sizes its items with percentage widths
     that already total 100%; adding a gap made 608 + 608 + 56 exceed the 1216
     content box and the flex row WRAPPED, dropping the buy box underneath the
     gallery. Column separation comes from the items' own padding. */
}

/* The theme's `sidebar2` makes <figure> a FLEX row, so the <figcaption> holding
   the h1 sits BESIDE the gallery and takes 344px of the 608px column - leaving
   the gallery wrapper 34px wide (measured). Stack them instead: caption first,
   then a full-width gallery. */
body.t-page-prod figure.sidebar2 { display: block; width: 100%; margin: 0; }
body.t-page-prod figure.sidebar2 > figcaption { width: auto; margin-bottom: 1rem; }

/* repo .pdp-media - white card, hairline, generous radius */
body.t-page-prod .sidebar2__inner {
  width: 100%; background: #fff; border: 1px solid var(--odsd-line);
  border-radius: 22px; overflow: hidden;
}
body.t-page-prod #js-main-image-zoom { display: block; padding: 4%; }
/* aspect-ratio locks the image BOX to a constant square, so switching between
   photos of different shapes cannot resize the card (owner 2026-08-07);
   object-fit letterboxes each bitmap inside it. height:auto is what made the
   card track each image's own ratio. */
body.t-page-prod #js-main-image { width: 100%; aspect-ratio: 1 / 1; height: auto; object-fit: contain; }

/* repo .pdp-thumbs / .pdp-thumb */
/* The wrapper is a grid item: its parent pulls left 1rem and an item's own padding-left
   puts that back, so this shorthand has to carry the 1rem or the row lands off-centre
   (Aaron, 2026-11-03: 21px left, 37px right inside the card). #js-thumbnails is an
   .o-layout too, and its own -1rem pull clipped the first thumbnail on the card edge;
   the overflow-x from the one-row rule further down would clip anything that wraps. */
body.t-page-prod .thumbnail-wrapper { padding: 0 0.9rem 0.9rem calc(0.9rem + 1rem); }
body.t-page-prod #js-thumbnails {
  display: flex; flex-wrap: wrap; gap: 0.6rem; justify-content: center;
  margin-left: 0; overflow: visible;
}
body.t-page-prod .thumbnail-img-wrap {
  width: 68px; height: 68px; border-radius: 10px;
  border: 1px solid var(--odsd-line); background: #fff;
  overflow: hidden; cursor: pointer; padding: 0;
  transition: border-color 0.2s var(--odsd-ease), box-shadow 0.2s var(--odsd-ease);
}
body.t-page-prod .thumbnail-img-wrap:hover { border-color: var(--odsd-ink-soft); }
body.t-page-prod .thumbnail-img-wrap.active,
body.t-page-prod .thumbnail-img-wrap.selected {
  border-color: var(--odsd-ink); box-shadow: 0 0 0 1px var(--odsd-ink);
}
/* contain, not cover (Aaron, 2026-11-03): product photos are not all square, and a
   thumbnail that crops the product is no use for choosing a view. */
body.t-page-prod .thumbnail-img-wrap img {
  width: 100%; height: 100%; object-fit: contain; display: block;
  padding: 3px; box-sizing: border-box;
}

/* ---- repo .pdp-name / .pdp-subtitle ------------------------------------
   product_custom_name carries the model in <b> and the descriptor after it,
   which is exactly the repo's name + subtitle split. */
body.t-page-prod h1.x-product-layout-purchase__name {
  font-family: var(--odsd-font-display); margin: 0 0 0.5rem;
  /* The BASE size here is the repo's .pdp-subtitle, not .pdp-name.
     product_custom_name is "<b>OSD Nero XMAX11</b>11-Channel Home Theater Power
     Amplifier" - model inside <b>, descriptor as bare text after it. Sizing the
     h1 itself at display scale rendered the whole descriptor in 2.6rem and it
     ran down the page. The <b> gets the display treatment instead. */
  font-size: 1.02rem; font-weight: 500; line-height: 1.45;
  color: var(--odsd-ink-soft); letter-spacing: 0;
}
body.t-page-prod h1.x-product-layout-purchase__name b,
body.t-page-prod h1.x-product-layout-purchase__name strong {
  /* 1.8rem FLAT to match .odsd-pdp__price-now exactly (owner 2026-08-24:
     "make the product name the same size as the price"). Deliberate deviation
     from the repo's .pdp-name clamp(1.8rem, 4vw, 2.6rem) - do not restore. */
  display: block; font-weight: 700;
  font-size: 1.8rem; line-height: 1.1;
  letter-spacing: -0.02em; color: var(--odsd-ink); margin-bottom: 0.25rem;
}
body.t-page-prod .x-product-layout-purchase__sku {
  font-size: 0.78rem; color: var(--odsd-ink-soft); letter-spacing: 0.02em;
}

/* ---- repo .pdp-price ---------------------------------------------------- */
body.t-page-prod .x-product-layout-purchase__pricing,
body.t-page-prod .x-product-layout-purchase__pricing-current {
  font-family: var(--odsd-font-display); font-weight: 800;
  font-size: 1.8rem; line-height: 1.1; color: var(--odsd-ink);
}
body.t-page-prod .x-product-layout-purchase__pricing s,
body.t-page-prod .x-product-layout-purchase__pricing .strike,
body.t-page-prod .x-product-layout-purchase__product-discounts .strike {
  color: var(--odsd-ink-soft); font-weight: 500; font-size: 1.05rem;
}
body.t-page-prod .x-product-layout-purchase__product-discounts {
  font-family: var(--odsd-font-display); font-size: 0.92rem;
  color: var(--odsd-ink-soft); font-weight: 500;
}

/* repo .pdp-stock - the green in-stock line */
body.t-page-prod .x-product-layout-purchase__inventory-message {
  font-size: 0.92rem; font-weight: 600; color: #1c7a3e;
}

/* ---- repo .qty + .pdp-buy ---------------------------------------------- */
body.t-page-prod .t-product-layout-purchase__quantity .c-form-input {
  height: 46px; width: 62px; text-align: center;
  font: inherit; border: 1px solid var(--odsd-line);
  border-radius: 999px; background: #fff; color: var(--odsd-ink);
}
body.t-page-prod .t-product-layout-purchase__add-to-cart input,
body.t-page-prod .t-product-layout-purchase__add-to-cart button,
body.t-page-prod .x-product-layout-purchase__cta .mm-theme-button-primary {
  width: 100%; min-height: 46px; cursor: pointer;
  padding: 0.72rem 1.4rem; border: 1px solid transparent; border-radius: 999px;
  font-family: var(--odsd-font-display); font-weight: 600; font-size: 0.92rem;
  letter-spacing: 0; text-transform: none;
  background: var(--odsd-ink); color: #fff;
  transition: background 0.2s var(--odsd-ease);
}
body.t-page-prod .t-product-layout-purchase__add-to-cart input:hover,
body.t-page-prod .x-product-layout-purchase__cta .mm-theme-button-primary:hover {
  background: #000; color: #fff;
}
/* repo treats "Add to Wish List" as a quiet text link, not a second button */
body.t-page-prod .t-product-layout-purchase__add-to-wish a,
body.t-page-prod .mm-theme-button-secondary-link {
  font-size: 0.84rem; color: var(--odsd-ink-soft);
  text-decoration: underline; text-underline-offset: 3px;
}
body.t-page-prod .t-product-layout-purchase__add-to-wish a:hover { color: var(--odsd-ink); }

/* ---- repo .pdp-tabs: RETIRED 2026-08-05 ---------------------------------
   Three generations of rules for the `.x-flex-tabs` radio tab group used to
   live in this file (here, and again around the CTA fix, and again in the
   sand-bar fix). The markup they styled is gone - the PDP now renders
   continuous `.odsd-pdp-band` sections under a pinned `.odsd-pdp-tabs` bar.
   Deleted rather than left to rot: a rule that outlives its markup is the
   Zombie Rule in the repo CLAUDE.md, and three copies of one is how the sand
   bar took two attempts. The replacement is one block, near the end of this
   file, searchable as "PDP product information". */

/* repo .pdp-trust-chips - the three service promises under the buy box */
/* No rule above the promises: the repo's .pdp-trust-chips draws none, and the
   two we had (this + .odsd-pdp__trust) stacked into a double line under Add
   to Wish List (owner, 2026-08-21). */
body.t-page-prod .x-icons__container {
  display: flex; flex-wrap: wrap; gap: 0.6rem 1.6rem; margin-top: 0.9rem;
  margin-left: 0; /* .o-layout's -16px gutter pulled the ticks out of the column */
}

/* ---- related products - repo card treatment ----------------------------- */
body.t-page-prod .x-product-list__item {
  background: #fff; border: 1px solid var(--odsd-line);
  border-radius: 22px; padding: 14px;
}
body.t-page-prod .x-product-list__name {
  font-family: var(--odsd-font-display); font-weight: 600;
  font-size: 0.94rem; line-height: 1.35; color: var(--odsd-ink);
}
body.t-page-prod .x-product-list__price {
  font-family: var(--odsd-font-display); font-weight: 700; color: var(--odsd-ink);
}

@media (max-width: 860px) {
  body.t-page-prod .x-product-layout { gap: 1.5rem; }
}

/* ---- "Complete systems, matched to work together." (repo #systems) ----
   Sand band between the storyband and the Advisor. Cards are live bundle products. */
.odsd-systems { background: var(--odsd-sand); padding: clamp(2.5rem, 5vw, 4rem) 0; }
/* SCROLLER, matching Best Sellers (owner, 2026-08-06 - replaced a paged fade
   with dots). The controls reuse Best Sellers' own classes so the two bands
   cannot drift apart; only the track behaviour below is restated, because
   `.odsd-pgrid--scroll` is bound to `.product-card` widths and these are
   `.odsd-system-card`.
   Every card stays in the DOM and is reachable by scroll or swipe, so the
   control never costs a link. Without JS the grid is a plain scrolling row -
   it degrades to a swipeable list, not an empty box. */
/* TWO classes deep on purpose: `.odsd-systems__grid { display: grid }` is
   declared LATER in this file, so at equal specificity it would win and the
   row would never overflow - measured as scrollWidth == clientWidth with both
   arrows dead. Match the weight rather than moving the block. */
.odsd-systems__grid.odsd-systems__grid--scroll {
  display: flex;
  grid-template-columns: none;
  gap: var(--odsd-gap, 1.5rem);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 0.25rem;
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* Hiding a scrollbar does not stop the scrolling: overflow-x:auto makes
     overflow-y compute to `auto`, which let a wheel gesture nudge the cards
     vertically inside their box on Best Sellers (owner report, 2026-08-03).
     An explicit non-visible value is what actually disables it. */
  overflow-y: hidden;
  /* the mobile rule further down narrows .odsd-systems__grid to 440px and
     centres it; that is right for a stacked grid and wrong for a scroller. */
  max-width: none; margin-inline: 0;
}
.odsd-systems__grid.odsd-systems__grid--scroll::-webkit-scrollbar { display: none; }
/* Exactly three per view, so no fourth card peeks in. Steps down to 2, then 1. */
.odsd-systems__grid.odsd-systems__grid--scroll .odsd-system-card {
  flex: 0 0 calc((100% - 2 * var(--odsd-gap, 1.5rem)) / 3);
  scroll-snap-align: start;
}
@media (max-width: 900px) {
  .odsd-systems__grid.odsd-systems__grid--scroll .odsd-system-card { flex-basis: calc((100% - var(--odsd-gap, 1.5rem)) / 2); }
}
@media (max-width: 600px) {
  .odsd-systems__grid.odsd-systems__grid--scroll .odsd-system-card { flex-basis: 100%; }
}
.odsd-systems__inner {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 0 clamp(1rem, 4vw, 2rem);
}
.odsd-systems__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-bottom: clamp(1.4rem, 3vw, 2rem);
}
.odsd-systems__head h2 {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: clamp(1.6rem, 2.6vw, 2.25rem);
  line-height: 1.08; letter-spacing: -0.02em; margin: 0;
  color: var(--odsd-ink);
}
.odsd-systems__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--odsd-gap); }
@media (max-width: 900px) {
  .odsd-systems__grid { grid-template-columns: 1fr; max-width: 440px; margin-inline: auto; }
}
.odsd-system-card {
  background: #fff; border: 1px solid var(--odsd-line); border-radius: 22px;
  overflow: hidden; display: flex; flex-direction: column;
  color: var(--odsd-ink); text-decoration: none;
  transition: transform 0.35s var(--odsd-ease), box-shadow 0.35s var(--odsd-ease),
              border-color 0.35s var(--odsd-ease);
}
.odsd-system-card:hover {
  transform: translateY(-5px); box-shadow: var(--odsd-shadow-md); border-color: transparent;
}
.odsd-sys-media {
  position: relative; aspect-ratio: 4 / 3; display: grid; place-items: center; padding: 0.6rem;
}
.odsd-sys-media img { width: 100%; height: 100%; object-fit: contain; }
.odsd-sys-info { display: flex; flex-direction: column; gap: 0.3rem; padding: 0 1.1rem 1.2rem; }
.odsd-sys-type {
  font-family: var(--odsd-font-display); font-size: 0.68rem; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--odsd-accent-deep);
}
.odsd-sys-name {
  font-family: var(--odsd-font-display); font-weight: 600; font-size: 1rem;
  line-height: 1.35; color: var(--odsd-ink);
}
.odsd-sys-price { font-family: var(--odsd-font-display); font-weight: 700; margin-top: 0.2rem; }

/* ---- Design-service split: copy left, floor-plan illustration right ----
   Repo .uploadplans-inner / .plan-art. The section used to be centred copy on flat
   black, which is what "missing the background" meant - the illustration IS the
   background treatment. */
.odsd-uploadplans__inner {
  display: grid; grid-template-columns: minmax(280px, 460px) minmax(300px, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem); align-items: center;
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 0 clamp(1rem, 4vw, 2rem); text-align: left;
}
.odsd-uploadplans__copy { text-align: left; }
.odsd-uploadplans__copy h2 { margin-top: 0; max-width: 16ch; }
.odsd-uploadplans__copy p { margin-left: 0; margin-right: 0; max-width: 46ch; }
/* display is REQUIRED: .odsd-plan-art carries aria-hidden="true" (it is decorative)
   and the Shadows scaffold ships a bare [aria-hidden="true"]{display:none}. */
.odsd-plan-art { display: block; }
.odsd-plan-art svg { width: 100%; height: auto; display: block; overflow: visible; }
@media (max-width: 820px) {
  .odsd-uploadplans__inner { grid-template-columns: 1fr; text-align: center; }
  .odsd-plan-art { max-width: 460px; margin: 0 auto; }
}

/* ---- Trust stats inside the single "Why customers trust us" block ----
   Repo PR #13 .trust-stats / .tstat. Four stats on one rule-separated row, sitting
   between the heading and the review quotes. */
.odsd-tstats {
  display: grid; grid-template-columns: repeat(4, 1fr);
  max-width: 1080px; margin: 0 auto clamp(2rem, 4vw, 3rem);
}
.odsd-tstat {
  text-align: center; padding: 0.2rem clamp(1rem, 2vw, 1.75rem);
  display: flex; flex-direction: column; gap: 0.35rem;
}
.odsd-tstat + .odsd-tstat { border-left: 1px solid var(--odsd-line); }
.odsd-tstat-num {
  font-family: var(--odsd-font-display);
  font-size: clamp(1.2rem, 1.9vw, 1.4rem); font-weight: 600;
  color: var(--odsd-accent-deep); line-height: 1;
}
/* NO underline - a deliberate departure from the repo's .tstat-label (owner-approved
   2026-10-18, Miva experience audit: underlined text reads as a link, and none of
   these four labels is one). Do not restore it to "match the repo". */
.odsd-tstat-label {
  font-size: 0.9rem; font-weight: 500; color: var(--odsd-ink);
}
.odsd-tstat-sub { font-size: 0.78rem; color: var(--odsd-ink-soft); line-height: 1.4; }
@media (max-width: 760px) {
  .odsd-tstats { grid-template-columns: 1fr 1fr; gap: 1.6rem 0; }
  .odsd-tstat:nth-child(odd) { border-left: none; }
}
/* the collapsed block centres its heading (repo centres the section head) */
.odsd-reviews__head { text-align: center; }

/* ---- BLACK by OSD rotator stage (repo PR #13 .feature-stage) ----
   The slides stack in one grid cell and cross-fade; the stage carries the radius and
   clipping so the progress-track controls sit OUTSIDE it, below the panel. */
.odsd-black-stage {
  position: relative; display: grid;
  border-radius: 22px; overflow: hidden;
}
.odsd-black-stage .odsd-black {
  grid-area: 1 / 1; border-radius: 0;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 0.5s var(--odsd-ease), visibility 0.5s var(--odsd-ease);
}
.odsd-black-stage .odsd-black.is-active { opacity: 1; visibility: visible; pointer-events: auto; }
@media (prefers-reduced-motion: reduce) {
  .odsd-black-stage .odsd-black { transition: none; }
}
/* the controls sit inside the band's padding, aligned with the panel */
.odsd-black-controls { max-width: var(--odsd-container); margin: 0 auto; }

/* ---- "Our latest news" post scroller (repo #news .post-grid) ----
   Three per view with the same progress-track controls as Best Sellers. */
.odsd-news { padding: clamp(2.5rem, 5vw, 4rem) 0; }
.odsd-news__inner {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 0 clamp(1rem, 4vw, 2rem);
}
.odsd-news__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-bottom: clamp(1.4rem, 3vw, 2rem);
}
.odsd-news__head h2 {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: clamp(1.6rem, 2.6vw, 2.25rem);
  line-height: 1.08; letter-spacing: -0.02em; margin: 0; color: var(--odsd-ink);
}
.odsd-postgrid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--odsd-gap); }
.odsd-postgrid--scroll {
  display: flex; grid-template-columns: none;
  gap: var(--odsd-gap);
  overflow-x: auto; scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch; padding-bottom: 0.25rem;
  scrollbar-width: none; -ms-overflow-style: none;      /* track + arrows are the indicator */
}
.odsd-postgrid--scroll::-webkit-scrollbar { display: none; }
.odsd-postgrid--scroll .odsd-post-card {
  flex: 0 0 calc((100% - 2 * var(--odsd-gap)) / 3);
  scroll-snap-align: start;
}
@media (max-width: 900px) {
  .odsd-postgrid--scroll .odsd-post-card { flex-basis: calc((100% - var(--odsd-gap)) / 2); }
}
@media (max-width: 600px) {
  .odsd-postgrid--scroll .odsd-post-card { flex-basis: 100%; }
}
.odsd-post-card { display: flex; flex-direction: column; color: var(--odsd-ink); text-decoration: none; }
.odsd-post-media {
  display: block; aspect-ratio: 16 / 9; box-sizing: content-box; border-radius: 14px;
  overflow: hidden; background: #fff; border: 1px solid var(--odsd-line);
}
.odsd-post-media img {
  width: 100%; height: 100%; object-fit: contain;
  transition: transform 0.45s var(--odsd-ease);
}
.odsd-post-card:hover .odsd-post-media img { transform: scale(1.04); }
.odsd-post-body { display: flex; flex-direction: column; gap: 0.45rem; padding-top: 0.9rem; }
.odsd-post-tag {
  font-family: var(--odsd-font-display);
  font-size: 0.64rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--odsd-accent-deep);
}
/* h3 to match the repo's <h3 class="post-title">. It was a <span>, which cost
   four real headings on the homepage - SEO signal we do not give up for free.
   Reset the browser h3 defaults so the card looks identical. */
.odsd-post-title {
  font-family: var(--odsd-font-display); font-weight: 600;
  font-size: 0.98rem; line-height: 1.4; color: var(--odsd-ink);
  margin: 0;
}
.odsd-post-card:hover .odsd-post-title { text-decoration: underline; text-underline-offset: 2px; }

/* ---- Brand-story band (repo #story .storyband) ----
   Injected by js/extensions.js directly after the BLACK band. Four pillars on a dark
   slate ground, giving the mid-page a dark beat in the sand/white rhythm. Ports
   styles.css .storyband / .story-grid / .story-col at the repo's own values. */
.odsd-storyband {
  background: var(--odsd-night-soft); color: #fff;
  padding: clamp(2.5rem, 5vw, 4rem) 0;
}
.odsd-storyband__inner {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 0 clamp(1rem, 4vw, 2rem);
}
.odsd-storyband .odsd-eyebrow { color: var(--odsd-accent); }
.odsd-storyband h2 {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: clamp(1.6rem, 2.6vw, 2.25rem);          /* repo --fs-h2 */
  line-height: 1.08; letter-spacing: -0.02em;
  max-width: 24ch; margin: 0 0 clamp(1.6rem, 3.5vw, 2.6rem); color: #fff;
}
.odsd-story-grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: clamp(1.5rem, 3vw, 2.75rem);
}
.odsd-story-col h3 {
  font-family: var(--odsd-font-display);
  font-size: 0.82rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase;
  margin: 0 0 0.7rem; color: #fff;
}
.odsd-story-col p {
  color: rgba(255, 255, 255, 0.72); font-size: 0.92rem; line-height: 1.6; margin: 0;
}
@media (max-width: 980px) { .odsd-story-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .odsd-story-grid { grid-template-columns: 1fr; } }

/* ---- BLACK by OSD reference band (repo #black-osd .feature) ----
   Injected by js/extensions.js right after the Best Sellers carousel. Dark split
   panel: copy + stats + button on the left; the live TreVoce 12 PRO product shot
   (store graphics) on the white right panel. Image + button are live store data. */
.odsd-black-band {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 24px clamp(1rem, 4vw, 2rem);
}
.odsd-black {
  background: var(--odsd-night); color: #fff;
  border-radius: 22px; overflow: hidden;
  display: grid; grid-template-columns: 1.05fr 1fr;
  align-items: center; min-height: 460px;
}
.odsd-black__body { padding: clamp(2rem, 5vw, 4.5rem); }
.odsd-black__eyebrow {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--odsd-accent); margin: 0 0 0.9rem;
}
.odsd-black__body h2 {
  font-family: var(--odsd-font-display); font-weight: 700;   /* repo: h1-h4 are 700 */
  font-size: clamp(1.9rem, 3.5vw, 3rem);
  line-height: 1.08; letter-spacing: -0.02em;                /* repo heading tracking */
  margin: 0 0 1.1rem; color: #fff;
}
.odsd-black__body p {
  color: rgba(255, 255, 255, 0.78); max-width: 44ch; margin: 0 0 1.8rem; line-height: 1.5;
}
.odsd-black__stats { display: flex; gap: 2.5rem; margin-bottom: 2rem; }
.odsd-black__stats div b {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: 1.9rem; display: block; color: #fff;
}
.odsd-black__stats div span { font-size: 0.8125rem; color: rgba(255, 255, 255, 0.6); }
.odsd-black__btn {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.9rem 1.6rem; border-radius: 999px;
  background: #fff; color: var(--odsd-ink);
  font-family: var(--odsd-font-display); font-weight: 600; font-size: 0.95rem;
  text-decoration: none; white-space: nowrap;
  transition: transform 0.25s var(--odsd-ease);
}
.odsd-black__btn:hover { transform: translateY(-2px); }
.odsd-black__media { position: relative; align-self: stretch; min-height: 320px; background: #fff; }
.odsd-black__media img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; padding: 7%;
}
/* Full-bleed photo slides fill the panel (owner, 2026-08-03: "the image needs to
   take up the whole white area or it looks weird").
   Only slides flagged `fill: true` in the SLIDES config get this. The rest are
   products silhouetted on white and are meant to float on the panel's white
   background - every slide image is square and the panel is landscape, so
   covering crops ~12% off the top and bottom, which is invisible on a photo and
   can clip a product. Judged per slide rather than globally for that reason. */
.odsd-black__media--fill img { object-fit: cover; padding: 0; }
@media (max-width: 900px) {
  .odsd-black { grid-template-columns: 1fr; }
  /* SQUARE the media panel on a phone (owner, 2026-08-27: "too much white space").
     Stacked, the panel is no longer HALF of a split layout - it is a full-width slab
     above the copy, and every slide image is square (1000x1000). A square image
     contained in a 364x280 landscape slab left ~40% of it dead white, which read as
     a broken/empty band rather than a design. Squaring the panel removes the dead
     axis: `contain` now fills it edge to edge, and `cover` on the `--fill` photo
     slides has nothing left to crop, so both treatments land in the same place.
     Do NOT put `min-height` back - it is what forced the landscape slab. */
  /* max-height caps the square: this query runs to 900px, where a 1/1 panel would be
     an ~835px-tall block. Under ~420px wide (every phone) the cap never binds and the
     panel stays square; above it the panel goes landscape again, which is the desktop
     treatment arriving early rather than a second layout. */
  /* width:100% is load-bearing. A grid item with an aspect-ratio and a definite
     height (which max-height makes it) stops accepting the default stretch, so the
     panel derived its WIDTH from the ratio too and sat as a 420px square inside an
     803px row at tablet widths. Pinning the width keeps the ratio driving height only. */
  .odsd-black__media { width: 100%; aspect-ratio: 1 / 1; max-height: 420px; min-height: 0;
    align-self: start; order: -1; }
  /* :not(--fill) - a bare `.odsd-black__media img` here TIES on specificity with the
     `--fill` rule above (both 0,1,1) and wins on source order, which put a 4% white
     frame around the full-bleed photo slides. */
  .odsd-black__media:not(.odsd-black__media--fill) img { padding: 4%; }
  .odsd-black__stats { gap: 1.5rem; }
}
/* The three stats are a fixed 3-up row; at 430px "3x210W" + "8x85W" overflowed the
   card and the third label was clipped mid-word. Shrink the row rather than wrap it -
   wrapping puts one stat alone on a second line and reads as a mistake. */
@media (max-width: 560px) {
  .odsd-black__stats { gap: 1rem; }
  .odsd-black__stats div b { font-size: 1.45rem; }
  .odsd-black__stats div span { font-size: 0.75rem; line-height: 1.3; display: block; }
}

/* ---- Depot Sound Advisor (repo #advisor .advisor-shell) ----
   Injected by js/extensions.js after the BLACK band. A 3-question client-side
   quiz (no backend) whose results map the answers to real store categories.
   The free-text Claude mode is deferred (see PROJECT-LOG / ROADMAP). */
.odsd-advisor-band {
  background: linear-gradient(180deg, var(--odsd-sand) 0%, #fff 100%);
  padding: 24px 0;
}
.odsd-advisor-inner {
  max-width: var(--odsd-container); margin: 0 auto; padding: 0 clamp(1rem, 4vw, 2rem);
}
.odsd-adv-shell {
  background: #fff; border: 1px solid var(--odsd-line); border-radius: 22px;
  box-shadow: 0 18px 40px rgba(20, 19, 22, 0.08);
  overflow: hidden; display: grid; grid-template-columns: 1fr 1.15fr;
  /* 378px = the old 540 minus 30% (owner 2026-08-24). MIN-height only: the
     answer view still grows to fit the reply + cards, so results are unaffected. */
  min-height: 378px;
}
.odsd-adv-aside {
  background: var(--odsd-night); color: #fff;
  padding: clamp(2rem, 4vw, 3.2rem);
  display: flex; flex-direction: column; justify-content: center;
}
/* Repo .eyebrow verbatim (13px / 600 / .18em) - this one had been 0.72rem/700/.14em. */
.odsd-adv-eyebrow {
  font-family: var(--odsd-font-display); font-weight: 600; font-size: 0.8125rem;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--odsd-accent); margin: 0 0 0.9rem;
}
.odsd-adv-aside h2 {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: clamp(1.9rem, 3.5vw, 3rem); line-height: 1.08; letter-spacing: -0.02em; margin: 0 0 1rem;
}
.odsd-adv-aside p { color: rgba(255, 255, 255, 0.78); margin: 0 0 1.6rem; line-height: 1.5; }
.odsd-adv-badge {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-size: 0.8125rem; color: rgba(255, 255, 255, 0.6);
}
.odsd-adv-badge svg { width: 16px; height: 16px; stroke: var(--odsd-accent); flex-shrink: 0; }
.odsd-adv-main { padding: clamp(1.6rem, 3vw, 2.4rem); display: flex; flex-direction: column; }
.odsd-adv-progress { display: flex; gap: 0.4rem; margin-bottom: 1.6rem; }
.odsd-adv-progress span {
  height: 4px; flex: 1; border-radius: 2px; background: var(--odsd-line);
  transition: background 0.4s var(--odsd-ease);
}
.odsd-adv-progress span.done { background: var(--odsd-accent); }
.odsd-adv-step { display: flex; flex-direction: column; flex: 1; animation: odsd-adv-fade 0.4s var(--odsd-ease); }
@keyframes odsd-adv-fade { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.odsd-adv-step h3 {
  font-family: var(--odsd-font-display); font-weight: 700; font-size: 1.5rem;
  margin: 0 0 0.4rem; letter-spacing: -0.01em; color: var(--odsd-ink);
}
.odsd-adv-sub { color: var(--odsd-ink-soft); margin: 0 0 1.4rem; font-size: 0.95rem; }
.odsd-adv-options { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
.odsd-adv-option {
  text-align: left; border: 1.5px solid var(--odsd-line); border-radius: 14px;
  padding: 1rem 1.1rem; background: #fff; cursor: pointer;
  transition: border-color 0.2s var(--odsd-ease), background 0.2s var(--odsd-ease), transform 0.2s var(--odsd-ease);
  display: flex; gap: 0.8rem; align-items: center;
}
.odsd-adv-option:hover { border-color: var(--odsd-ink); transform: translateY(-2px); }
.odsd-adv-option.selected { border-color: var(--odsd-accent); background: #fff8ec; }
.odsd-adv-ico { font-size: 1.4rem; line-height: 1; }
.odsd-adv-option b { font-family: var(--odsd-font-display); display: block; font-size: 0.98rem; color: var(--odsd-ink); }
.odsd-adv-od { display: block; font-size: 0.8125rem; color: var(--odsd-ink-soft); }
.odsd-adv-nav { margin-top: auto; padding-top: 1.5rem; display: flex; justify-content: space-between; align-items: center; gap: 1rem; }
.odsd-adv-back { color: var(--odsd-ink-soft); font-size: 0.9rem; background: none; border: 0; cursor: pointer; font-family: inherit; }
.odsd-adv-back:hover { color: var(--odsd-ink); }
.odsd-adv-reclist { display: flex; flex-direction: column; gap: 0.8rem; }
.odsd-adv-rec {
  display: flex; gap: 1rem; align-items: center; text-decoration: none;
  border: 1px solid var(--odsd-line); border-radius: 14px; padding: 0.9rem 1rem;
  transition: border-color 0.2s var(--odsd-ease), transform 0.2s var(--odsd-ease);
}
.odsd-adv-rec:hover { border-color: var(--odsd-ink); transform: translateY(-2px); }
.odsd-adv-recico { font-size: 1.5rem; line-height: 1; }
.odsd-adv-rec b { font-family: var(--odsd-font-display); display: block; font-size: 1rem; color: var(--odsd-ink); }
.odsd-adv-rec span { font-size: 0.8125rem; color: var(--odsd-ink-soft); }
.odsd-adv-go { margin-left: auto; color: var(--odsd-accent-deep); font-weight: 700; white-space: nowrap; }
@media (max-width: 900px) {
  .odsd-adv-shell { grid-template-columns: 1fr; min-height: 0; }
  .odsd-adv-options { grid-template-columns: 1fr; }
}

/* ---- Closing bands: Upload-your-plans (repo .uploadplans) + Newsletter (repo .newsletter) ----
   Injected by js/extensions.js at the end of the homepage content, before the footer.
   Upload "Get started" -> /send-us-your-plans.html (real OSD design-service page);
   Newsletter form -> the store's live Mailchimp audience (same action as production). */
.odsd-uploadplans {
  background: var(--odsd-ink); color: #fff;
  padding: clamp(2.5rem, 5vw, 4rem) clamp(1rem, 4vw, 2rem);
}
/* Superseded 2026-07-29: this section is a split layout now (copy left, floor-plan
   illustration right), so the old centred 620px column would fight the grid above.
   Only the padding-side defaults are kept. */
.odsd-uploadplans__inner { margin: 0 auto; }
.odsd-newsletter {
  background: var(--odsd-night); color: #fff;
  padding: clamp(2.5rem, 5vw, 4rem) clamp(1rem, 4vw, 2rem);
}
.odsd-newsletter__inner { text-align: center; max-width: 560px; margin: 0 auto; }
.odsd-uploadplans__eyebrow {
  font-family: var(--odsd-font-display); font-weight: 700; font-size: 0.72rem;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--odsd-accent); margin: 0 0 0.9rem;
}
.odsd-uploadplans h2, .odsd-newsletter h2 {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: clamp(1.9rem, 3.5vw, 3rem); line-height: 1.08; letter-spacing: -0.02em;
  margin: 0 0 1rem; color: #fff;
}
.odsd-uploadplans p { color: rgba(255, 255, 255, 0.8); margin: 0 0 1.8rem; line-height: 1.5; }
.odsd-newsletter p { color: rgba(255, 255, 255, 0.75); margin: 0 0 2rem; line-height: 1.5; }
.odsd-newsletter__form { display: flex; gap: 0.7rem; max-width: 440px; margin: 0 auto; }
.odsd-newsletter__form input[type="email"] {
  flex: 1; padding: 0.85rem 1.1rem; border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.25); background: rgba(255, 255, 255, 0.06);
  color: #fff; font-size: 0.95rem;
}
.odsd-newsletter__form input::placeholder { color: rgba(255, 255, 255, 0.5); }
.odsd-newsletter__form input:focus { outline: none; border-color: var(--odsd-accent); }
@media (max-width: 640px) {
  .odsd-newsletter__form { flex-direction: column; }
  .odsd-newsletter__form .odsd-black__btn { justify-content: center; }
}

/* ---- Parity polish (owner screenshot feedback 2026-07-09) ----
   Repo-style section heads (eyebrow + h2 + sub + side button) injected above the
   product carousels + bundles; native centered headings are disabled in sfnt.json.
   Plus: Poppins on all remaining native flex headings, explicit sand on the sand
   carousels, the .odsd-outdoor feature-split, and bundle-card cleanup. */

/* Repo heading spec on every native flex component's light-DOM headings */
[id^="mmx-"] h1, [id^="mmx-"] h2, [id^="mmx-"] h3, [id^="mmx-"] h4 {
  font-family: var(--odsd-font-display);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.02em;
}

/* ---- CONTENT PAGES (WP -> Miva migration, ABUS first; owner 2026-08-07) ----
   The mm-theme typography classes are Poppins but NOT the repo scale: title-four
   is 20px with letter-spacing normal where every sitewide heading is 700 / 1.08 /
   -0.02em, and the paragraph classes force 16px under a 17px body. This block
   gives content pages the exact metrics the rest of the converted site uses -
   h1 matches .odsd-cat__title verbatim. Reuse on all seven migrated pages. */
.odsd-page h1, .odsd-page h2, .odsd-page h3 {
  font-family: var(--odsd-font-display); font-weight: 700;
  line-height: 1.08; letter-spacing: -0.02em; color: var(--odsd-ink);
}
.odsd-page h1 { font-size: clamp(1.6rem, 2.6vw, 2.25rem); margin: 0 0 1rem; }
.odsd-page h2 { font-size: clamp(1.2rem, 1.8vw, 1.5rem); margin: 2.4rem 0 0.9rem; }
.odsd-page__copy { max-width: 72ch; }
.odsd-page__copy p { margin: 0 0 1.15rem; line-height: 1.65; }
.odsd-page__copy a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.odsd-page__copy a:hover { color: var(--odsd-accent-deep); }
/* Content-page CTA: gold with black text (owner 2026-08-07) - the same pairing
   as the hero CTA, the Support pill and the consent accept button, and the
   higher-contrast direction (gold is light; dark text on it passes WCAG).
   Weight (0,2,1) beats the copy-link color rule that once made it black-on-black. */
.odsd-page__copy a.odsd-btn { text-decoration: none; }
.odsd-page__copy a.odsd-btn--primary { background: var(--odsd-accent); color: var(--odsd-ink); }
.odsd-page__copy a.odsd-btn--primary:hover { background: var(--odsd-accent-deep); color: var(--odsd-ink); }
.odsd-foot-choices { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.odsd-foot-choices:hover { color: var(--odsd-accent); }
/* Quantity stepper: full pill (owner 2026-08-07). core.css control-group corner
   logic (.c-control-group .c-control-group__field:first-child, 0,3,0) squared
   the LEFT corners to 4px while the right stayed 999px. Outweigh it. */
body.t-page-prod .c-control-group .odsd-qty:first-child,
body.t-page-prod .c-control-group .odsd-qty:last-child { border-radius: 999px; }

/* Option rows: ONE line whenever they fit, wrap only when they cannot
   (owner 2026-08-07 made it one line after JA-14-4's 500FT wrapped below the
   rest; owner 2026-09-30 approved wrapping as the last resort).
   The 2026-08-07 `white-space: nowrap` is gone because it could not tell the
   two cases apart. JA-14-4 only wrapped because the options block was 60% wide
   above 1200px; with the block at full column width (rule above) its row fits
   at every desktop width, so it still reads as one line. KIT-XSP's four
   speaker choices need 1,221px, so under nowrap the last ones ran off the
   screen at EVERY width - clipped by html{overflow-x:hidden}, unreachable - and
   JA-14-4 itself was 74px past a 320px phone. Now a row that cannot fit wraps,
   and a pill wider than the row wraps its own text. */
body.t-page-prod .x-product-layout-purchase__options .x-product-layout-purchase__options-attribute { white-space: normal; }
body.t-page-prod .x-product-layout-purchase__options .c-form-label { white-space: normal; }

/* Waitlist modal (vanilla open/close in prod-waitlist_content.mvt). The
   container keeps u-hide-visually until .is-open lifts it into a fixed
   overlay; every hide-visually property must be overridden here. */
#waitlist-modal.is-open {
  position: fixed; inset: 0; z-index: var(--odsd-z-modal);
  width: auto; height: auto; clip: auto; clip-path: none; overflow: auto;
  white-space: normal; margin: 0; padding: 1rem;
  display: flex; align-items: center; justify-content: center;
  background: rgba(20, 20, 26, 0.55);
}
#waitlist-modal.is-open .modal-bg {
  position: relative; background: #fff; border-radius: 22px;
  padding: 2.25rem 2rem 2rem; max-width: 560px; width: 100%;
  box-shadow: var(--odsd-shadow-md);
}
#waitlist-modal.is-open h2 { font-family: var(--odsd-font-display); font-size: 1.15rem; line-height: 1.3; margin: 0 0 1rem; }
#waitlist-modal.is-open .waitlist_add_form .o-layout { display: flex; gap: 0.6rem; }
.odsd-wl-close {
  position: absolute; top: 0.65rem; right: 0.9rem;
  border: 0; background: none; cursor: pointer;
  font-size: 1.6rem; line-height: 1; color: var(--odsd-ink-soft);
}
.odsd-wl-close:hover { color: var(--odsd-ink); }

/* Bulk Deals (volume pricing) below the gallery (owner 2026-08-07). The parent
   image column is u-text-center; the table reads better left-aligned. */
body.t-page-prod .odsd-bulk { margin-top: 1.5rem; text-align: left; }
body.t-page-prod .odsd-bulk__title {
  font-family: var(--odsd-font-display); font-weight: 700; font-size: 1.05rem;
  letter-spacing: -0.02em; color: var(--odsd-ink); margin: 0 0 0.5rem;
}

/* PDP finish dots (owner 2026-08-07): Color attributes render repo-style round
   swatches (repo .swatch: round, hairline border, inner white ring). The label
   pill chrome is stripped when a dot renders; the checked ring marks selection.
   Radio semantics untouched. */
/* Three chrome layers must die at matching weight (owner 2026-08-07 round 2 -
   "only the circle with color inside"): the pill border/radius from
   `body.t-page-prod .x-radio-to-button` (0,2,1 - outweighed the first 0,2,0
   attempt), the caption's own `outline: 1px currentColor` from
   x-radio-to-button.css, and the :has(:checked) box-shadow ring. Selection
   lives on the DOT (ink ring) and nowhere else. */
body.t-page-prod .x-radio-to-button.odsd-finish,
body.t-page-prod .x-radio-to-button.odsd-finish:hover {
  border: 0; background: none; box-shadow: none; padding: 4px; min-width: 0;
  border-radius: 50%; overflow: visible;
}
/* :has() in its own rule: in one comma list it would drop the WHOLE rule in browsers without :has() (FINDINGS 2026-09-22) */
body.t-page-prod .x-radio-to-button.odsd-finish:has(.c-form-checkbox__input:checked) {
  border: 0; background: none; box-shadow: none; padding: 4px; min-width: 0;
  border-radius: 50%; overflow: visible;
}
/* [!] Selector weight is load-bearing (owner glitch report 2026-08-07): the
   theme's TEXT-PILL rule `.x-radio-to-button:not(:has(img)) .c-form-checkbox__caption`
   (0,3,2 with body) matches dot labels too (no <img> inside) and was winning at
   REST with its 0.5rem/1.15rem padding, while the hover state fell to our
   padding: 0 - so the label snapped 79px -> 42px on hover. All caption states
   must sit ABOVE (0,3,2). */
body.t-page-prod .x-radio-to-button.odsd-finish .c-form-checkbox__caption,
body.t-page-prod .x-radio-to-button.odsd-finish .c-form-checkbox__input:hover + .c-form-checkbox__caption,
body.t-page-prod .x-radio-to-button.odsd-finish .c-form-checkbox__input:checked + .c-form-checkbox__caption {
  outline: none; padding: 0; margin: 0; opacity: 1;
  display: inline-flex; flex-direction: column; align-items: center; gap: 6px;
  line-height: 1;
}
/* No max-width (2026-09-28, Miva QA ticket #1389066). The 76px cap sat inside a
   `white-space: nowrap` row, so a long name did not wrap, it spilled: on RS670
   "Sandstone Canyon Brown" is 152px of text in a 76px box and ran straight
   through "Slate Dark Grey" beside it. */
.odsd-finish-name {
  font-family: var(--odsd-font-display); font-size: 0.72rem; font-weight: 600;
  color: var(--odsd-ink-soft); text-align: center;
}
/* NARROW BOXES keep the cap and WRAP the name instead (2026-09-30, replacing
   a 35em viewport query). RS670's three names need ~403px on one line. The
   first version of this fix switched on the SCREEN width, which protected
   phones but not the narrow boxes on wide screens: the missing-option page
   (PATR) put the third finish 184-273px outside its card from 768px up. The
   cap now follows the options block itself (`odsd-options` container, set on
   `.x-product-layout-purchase__options`): under 420px - PATR's card, the
   960px two-column PDP (402px), every phone - names wrap under their dot;
   wider, they stay on one line as Miva's QA asked. 420 rather than 403 so the
   row is never left 8px short at 960px. Browsers without container queries
   keep one-line names and fall back to the row wrapping (rule above). */
@container odsd-options (max-width: 419px) {
  .odsd-finish-name { max-width: 76px; white-space: normal; line-height: 1.25; }
}
body.t-page-prod .odsd-finish:has(.c-form-checkbox__input:checked) .odsd-finish-name { color: var(--odsd-ink); }
.odsd-finish-dot {
  display: inline-block; width: 22px; height: 22px; border-radius: 50%;
  border: 1px solid var(--odsd-line); box-shadow: inset 0 0 0 2px #fff;
  vertical-align: middle; cursor: pointer;
}
body.t-page-prod .odsd-finish:hover .odsd-finish-dot {
  outline: 2px solid var(--odsd-line); outline-offset: 2px;
}
body.t-page-prod .odsd-finish .c-form-checkbox__input:checked ~ .c-form-checkbox__caption .odsd-finish-dot {
  outline: 2px solid var(--odsd-ink); outline-offset: 2px;
}
/* :has() in its own rule: in one comma list it would drop the WHOLE rule in browsers without :has() (FINDINGS 2026-09-22) */
body.t-page-prod .odsd-finish:has(.c-form-checkbox__input:checked) .odsd-finish-dot {
  outline: 2px solid var(--odsd-ink); outline-offset: 2px;
}
/* TEXT PILLS get the same caption-outline cure as the dots (owner 2026-08-07):
   the theme's x-radio-to-button.css draws `outline: 1px currentColor` on every
   caption (2px on hover/checked) INSIDE our label border - a rectangle in a
   pill that thickens on hover. Selection/hover live on the label border alone. */
body.t-page-prod .x-radio-to-button .c-form-checkbox__caption,
body.t-page-prod .x-radio-to-button .c-form-checkbox__input:hover + .c-form-checkbox__caption,
body.t-page-prod .x-radio-to-button .c-form-checkbox__input:checked + .c-form-checkbox__caption {
  outline: none; margin: 0; opacity: 1;
}
/* FINISH + OPTIONS on one row (owner 2026-08-07): the options <li> holds one
   .group-N per attribute; the template's forced <br> is gone, the groups drop
   their u-width-12 full-width and flex side by side, wrapping on small screens. */
body.t-page-prod .x-product-layout-purchase__options { display: flex; flex-wrap: wrap; align-items: flex-start; column-gap: 2.5rem; row-gap: 0.75rem; }
body.t-page-prod .x-product-layout-purchase__options > div[class*="group-"] { width: auto; flex: 0 0 auto; }
/* The blue ellipse on click was core.css `.c-form-checkbox:focus-within
   { outline: var(--default-button-outline--focus) }` landing on the circular
   label. Mouse focus gets nothing; KEYBOARD focus (:focus-visible) keeps an
   accessible ring, drawn on the dot like the other states. */
body.t-page-prod .x-radio-to-button.odsd-finish:focus-within { outline: none; }
body.t-page-prod .odsd-finish:has(.c-form-checkbox__input:focus-visible) .odsd-finish-dot {
  outline: 2px solid var(--odsd-ink); outline-offset: 2px;
}
.odsd-visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

/* PDP: Shopper Approved stars row above the price (owner 2026-08-07). The SA
   widget brings its own star imagery; this just spaces the row and keeps it
   from collapsing before the script paints. */
.odsd-pdp__stars { display: flex; align-items: center; gap: 0.75rem; min-height: 22px; margin: 0 0 0.5rem; }
.odsd-pdp__stars a { color: var(--odsd-ink-soft); font-size: var(--odsd-fs-small); }
/* No SA data for this product (merchantfallback) leaves the stars div empty -
   collapse the row instead of showing a blank gap (SLK-300 report, 2026-08-07;
   production behaves identically, the DATA is absent, not the feature). */
.odsd-pdp__stars:has(#product_just_stars:empty) { display: none; }
/* Copy left, imagery right (owner 2026-08-07: fill the empty right side; the
   lede treatment is GONE - every paragraph reads identically now). The measure
   stays at 72ch on purpose: full-width text lines are unreadable at desktop
   widths. Images collapse below the copy under 900px. */
.odsd-page__grid { display: grid; grid-template-columns: minmax(0, 72ch) minmax(260px, 400px); gap: clamp(2rem, 5vw, 4.5rem); align-items: start; }
/* International distributor directory (INTC) - card grid over the WP page's
   run-together paragraphs. Same words, structured. */
.odsd-dist { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--odsd-gap); }
.odsd-dist__card {
  background: #fff; border: 1px solid var(--odsd-line); border-radius: 22px;
  padding: 1.5rem 1.6rem; display: flex; flex-direction: column; gap: 0.5rem;
}
.odsd-dist__card h2 { font-size: 1.15rem; margin: 0; }
.odsd-dist__region {
  font-size: var(--odsd-fs-small); color: var(--odsd-accent-deep);
  font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
}
.odsd-dist__card p { margin: 0; font-size: 0.9375rem; line-height: 1.55; color: var(--odsd-ink-soft); }
.odsd-dist__card a { color: var(--odsd-ink); text-decoration: underline; text-underline-offset: 2px; }
.odsd-dist__card a:hover { color: var(--odsd-accent-deep); }
.odsd-dist__note { font-style: italic; }
@media (max-width: 1024px) { .odsd-dist { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .odsd-dist { grid-template-columns: 1fr; } }
.odsd-page__aside { display: grid; gap: var(--odsd-gap); }
.odsd-page__aside img { width: 100%; border-radius: 22px; aspect-ratio: 4 / 3; object-fit: cover; display: block; }
@media (max-width: 900px) { .odsd-page__grid { grid-template-columns: 1fr; } }

/* Section head (repo .section-head, metrics verbatim from styles.css) */
.odsd-sechead-band { padding: var(--odsd-section-y) 0 clamp(1.8rem, 4vw, 3rem); }
/* Repo `.ratingbar + .section` tightens the FIRST section's top pad after the
   rating strip to clamp(1.5rem, 3vw, 2.25rem). Our equivalent seam is the
   categories sechead, which sits directly under the hero rating bar - without
   this it takes the full --odsd-section-y (~69-80px vs the repo's ~36px). */
.odsd-sechead-band[data-odsd-sechead="mmx-image-across__six-across"] { padding-top: clamp(1.5rem, 3vw, 2.25rem); }
.odsd-sechead-band--sand { background: var(--odsd-sand); }
.odsd-sechead {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 0 clamp(1.1rem, 4vw, 3rem);   /* repo .container padding-inline */
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 1.5rem; flex-wrap: wrap;
}
.odsd-eyebrow {
  font-size: 0.8125rem;                  /* repo .eyebrow verbatim */
  font-weight: 600;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--odsd-accent-deep); margin: 0 0 0.9rem;
}
.odsd-sechead h2 {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: clamp(1.6rem, 2.6vw, 2.25rem);   /* repo --fs-h2 exact (was 1.9/3.5vw/3 - too big, owner 2026-07-09) */
  line-height: 1.08; letter-spacing: -0.02em;
  margin: 0; color: var(--odsd-ink);
}
.odsd-sechead p { color: var(--odsd-ink-soft); margin: 0.55rem 0 0; max-width: 46ch; line-height: 1.65; }
.odsd-btn--ghost {
  background: none; border: none; color: var(--odsd-ink); padding-inline: 0;
}
.odsd-btn--ghost .odsd-arrow { display: inline-block; transition: transform 0.25s var(--odsd-ease); }
.odsd-btn--ghost:hover .odsd-arrow { transform: translateX(4px); }

/* Popular-categories band (owner request 2026-08-17): light-DOM replacement
   for the shadow-DOM mmx-image-across "six-across". Metrics are the repo's
   .cat-grid/.cat-card verbatim (compact square thumb, 14px radius, hover
   lift + 1.06 image zoom, name + quiet tagline), and the grid shares the
   Best Sellers container (--odsd-container + the repo .container padding),
   which is what aligns the two bands' edges. Literal hexes are repo tokens
   extensions.css does not carry: #ecebe6 = --mist, the shadow = --shadow-md. */
.odsd-cats { padding-bottom: var(--odsd-section-y); }
.odsd-cats__grid {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 0 clamp(1.1rem, 4vw, 3rem);
  display: grid; grid-template-columns: repeat(6, 1fr);
  gap: 1.1rem 0.9rem;
}
.odsd-cat-card {
  display: flex; flex-direction: column; align-items: center;
  text-align: center; gap: 0.6rem; text-decoration: none;
}
.odsd-cat-card:hover { text-decoration: none; }
.odsd-cat-card__thumb {
  width: 100%; aspect-ratio: 1 / 1;
  border-radius: 14px; overflow: hidden;
  background: #ecebe6;
  border: 1px solid var(--odsd-line);
  transition: transform 0.3s var(--odsd-ease), box-shadow 0.3s var(--odsd-ease), border-color 0.3s var(--odsd-ease);
}
.odsd-cat-card__thumb img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform 0.5s var(--odsd-ease);
}
.odsd-cat-card:hover .odsd-cat-card__thumb {
  transform: translateY(-3px);
  box-shadow: 0 10px 40px rgba(20, 20, 26, 0.1);
  border-color: transparent;
}
.odsd-cat-card:hover .odsd-cat-card__thumb img { transform: scale(1.06); }
.odsd-cat-card__name {
  font-family: var(--odsd-font-display); font-weight: 600;
  font-size: 0.95rem; line-height: 1.25; color: var(--odsd-ink);
}
.odsd-cat-card:hover .odsd-cat-card__name { color: var(--odsd-accent-deep); }
.odsd-cat-card__tag {
  font-size: 0.74rem; line-height: 1.35; color: var(--odsd-ink-soft);
  margin-top: 0.12rem; text-wrap: balance;
}
/* Repo responsive: 6 -> 3 columns below 1024px (kept at 3 all the way down,
   same as the repo's 480px rule). AFTER the base rule on purpose - a media
   query buys no specificity (FINDINGS: Media Query Above Its Base Rule). */
@media (max-width: 1024px) {
  .odsd-cats__grid { grid-template-columns: repeat(3, 1fr); }
}

/* The "Build a System" tile anchors to the systems band. Without this the
   sticky header covers the band heading (FINDINGS: Behind-The-Header Anchor;
   --odsd-header-h is published only on PDP, so the fallback applies here). */
.odsd-systems { scroll-margin-top: calc(var(--odsd-header-h, 84px) + 12px); }

/* Sand bands hug their section heads (kill the white sliver between them).
   Deployed-repo zebra: Deals + Bundles = one sand block; Customer favorites = WHITE. */
#mmx-product-carousel__deals {
  background: var(--odsd-sand);
  margin-top: 0 !important;
}
#mmx-product-carousel__product-carousel { background: #fff; margin-top: 0 !important; }
#mmx-product-carousel__packages { background: #fff; margin-top: 0 !important; }
#mmx-image-across__two-across { margin-top: 0 !important; }

/* Kill the carousels' 50vh initial-height reserve on the components we replace
   with .odsd-pgrid. The reserve prevents layout shift while the component loads
   its OWN data, but we hide it after our grid builds - so an un-built (slow/failed
   fetch) carousel would otherwise leave a tall empty placeholder (50vh; huge at
   large viewports). Collapse it so a pending/failed grid never leaves a gap. */
#mmx-product-carousel__deals,
#mmx-product-carousel__product-carousel,
#mmx-product-carousel__packages {
  --mmx-initial-height: 0px !important;
  min-height: 0 !important;
}

/* Bundle (two-across) cards: text sits under the image now; tidy the type */
#mmx-image-across__two-across h1, #mmx-image-across__two-across h2,
#mmx-image-across__two-across h3, #mmx-image-across__two-across h4 {
  font-size: 1.35rem;
}
#mmx-image-across__two-across img { border-radius: 22px; }

/* Outdoor Audio feature-split (repo .feature-split): image left, copy right, sand */
.odsd-outdoor { background: var(--odsd-sand); padding: clamp(2rem, 5vw, 4rem) clamp(1rem, 4vw, 2rem); }
.odsd-outdoor__inner {
  max-width: var(--odsd-container); margin: 0 auto;
  display: grid; grid-template-columns: 1fr 1fr;
  gap: clamp(1.5rem, 4vw, 3.5rem); align-items: center;
}
.odsd-outdoor__inner img {
  width: 100%; height: auto; border-radius: 22px; display: block;
}
.odsd-outdoor__body h2 {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: clamp(1.9rem, 3.5vw, 3rem); line-height: 1.08; letter-spacing: -0.02em;
  margin: 0 0 1rem; color: var(--odsd-ink);
}
.odsd-outdoor__body p { color: var(--odsd-ink-soft); margin: 0 0 1.8rem; max-width: 48ch; line-height: 1.55; }
@media (max-width: 860px) {
  .odsd-outdoor__inner { grid-template-columns: 1fr; }
}

/* WHY band: sand background (repo #why = .section--sand) + the trust-badge band
   injected as a sibling right after it (grid styles live in .odsd-trust above) */
#mmx-text-banner__text-banner-3 {
  background: var(--odsd-sand);
  margin-top: 0 !important;
  margin-bottom: 0 !important;
}
.odsd-trustband { background: var(--odsd-sand); padding: 0 0 var(--odsd-section-y); }
.odsd-trustband__inner {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 0 clamp(1.1rem, 4vw, 3rem);
}
.odsd-trustband .odsd-trust { margin-top: 0; }

/* Bundle Savings: repo collection cards (styles.css .collection-* verbatim) */
.odsd-collections__inner {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 0 clamp(1.1rem, 4vw, 3rem) var(--odsd-section-y);
}
.odsd-collections .collection-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--odsd-gap); }
.odsd-collections .collection-card {
  display: flex; flex-direction: column;
  background: #fff; border: 1px solid var(--odsd-line); border-radius: 22px;
  overflow: hidden; text-decoration: none; color: var(--odsd-ink);
  transition: transform 0.35s var(--odsd-ease), box-shadow 0.35s var(--odsd-ease), border-color 0.35s var(--odsd-ease);
}
.odsd-collections .collection-card:hover { transform: translateY(-5px); box-shadow: var(--odsd-shadow-md); border-color: transparent; }
.odsd-collections .collection-media { display: block; aspect-ratio: 5 / 4; background: #fff; overflow: hidden; }
.odsd-collections .collection-media img { width: 100%; height: 100%; object-fit: contain; padding: 10%; transition: transform 0.5s var(--odsd-ease); }
.odsd-collections .collection-card:hover .collection-media img { transform: scale(1.05); }
.odsd-collections .collection-body { display: flex; flex-direction: column; gap: 0.45rem; padding: 1.25rem 1.35rem 1.5rem; flex: 1; }
.odsd-collections .collection-tag { font-size: var(--odsd-fs-small); font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--odsd-accent-deep); }
.odsd-collections .collection-title { font-family: var(--odsd-font-display); font-size: 1.25rem; font-weight: 700; }
.odsd-collections .collection-desc { font-size: 0.95rem; color: var(--odsd-ink-soft); line-height: 1.55; }
.odsd-collections .collection-go {
  margin-top: auto; padding-top: 0.7rem;
  font-family: var(--odsd-font-display); font-weight: 600; font-size: 0.92rem;
  display: inline-flex; align-items: center; gap: 0.4rem;
}
.odsd-collections .collection-card:hover .collection-go { color: var(--odsd-accent-deep); }
.odsd-collections .collection-go .arrow { transition: transform 0.25s var(--odsd-ease); }
.odsd-collections .collection-card:hover .collection-go .arrow { transform: translateX(4px); }
@media (max-width: 1024px) { .odsd-collections .collection-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 480px) { .odsd-collections .collection-grid { grid-template-columns: 1fr; } }
.odsd-collections { background: var(--odsd-sand); }   /* deployed repo: bundles share the deals sand block */

/* ---- Deployed-repo exactness batch (headless-diff findings 2026-07-09) ----
   1) The Shadows page wrapper (<main class="o-wrapper">) centers all content and
      the page around it paints gray (#f6f6f6): force the repo's white paper and
      FULL-BLEED every colored band out of the wrapper (100vw breakout).
   2) Eyebrow colors: the section-body "p" rules (e.g. .odsd-black__body p) have
      higher specificity than the eyebrow class alone, painting every eyebrow
      gray. Explicit p.<eyebrow> overrides below restore the repo colors
      (accent gold on dark bands, accent-deep amber on light bands). */
html, body { background: #fff !important; }

/* The 100vw-breakout guard, MOVED FROM body TO html (2026-08-05). Read this
   before moving it back.

   It was `body { overflow-x: hidden }`. That single declaration had silently
   killed `position: sticky` on EVERY page of the branch - the site header
   included. Mechanism: when a box's overflow-x is not `visible`, its overflow-y
   computes to `auto`, so body became a scroll CONTAINER. body's overflow only
   propagates to the viewport when html's own overflow is `visible`, and it is
   not - core.css and page.css both set `html { overflow-y: scroll }`. So body
   stayed a scroller whose scrollport is its own full-height box, which never
   scrolls, and a sticky child of a container that never scrolls never sticks.

   Measured on branch 16, PDP, before and after (CDP, real frames):
     before   scrollY 1768   header bottom -1649   bar top  -728   (both gone)
     after    scrollY 1768   header bottom    79   bar top    79   (both pinned)

   html is already the viewport's scroller, so clipping there costs nothing and
   leaves body `visible`. This also revives the category page's sticky filter
   rail, which had never worked either. */
html { overflow-x: hidden; }

.odsd-hero,
.odsd-ratingbar,
.odsd-sechead-band--sand,
.odsd-gridband--sand,
.odsd-collections,
.odsd-trustband,
#mmx-text-banner__text-banner-3,
.odsd-outdoor,
.odsd-advisor-band,
.odsd-uploadplans,
.odsd-newsletter,
/* Added 2026-08-06. Both are JS-inserted bands that arrived after this list was
   written, so they sat inside `main.o-wrapper` (max-width 1440 + 16px padding)
   and painted their background 1408px wide in a 1474px viewport - a 26px white
   gutter down each side. Nothing wrong with the bands; they were just never
   added here. Any new full-width band needs to join this list. */
.odsd-systems,
.odsd-storyband,
.odsd-qpv {
  width: 100vw;
  position: relative;
  left: 50%;
  margin-left: -50vw !important;
  margin-right: 0 !important;
}

/* Eyebrow color restoration (repo: .eyebrow amber on light, accent on dark) */
.odsd-sechead p.odsd-eyebrow,
.odsd-outdoor__body p.odsd-eyebrow { color: var(--odsd-accent-deep); }
.odsd-black__body p.odsd-black__eyebrow,
.odsd-adv-aside p.odsd-adv-eyebrow,
.odsd-uploadplans__inner p.odsd-uploadplans__eyebrow { color: var(--odsd-accent); }

/* Reviews (repo styles.css section 12 verbatim) - REAL Shopper Approved reviews,
   snapshotted server-side via tools\sa_reviews.py (the SA token never ships to the
   browser). White band between the WHY trust badges and the Outdoor split. */
.odsd-reviews { background: #fff; padding: var(--odsd-section-y) 0; }

/* Anchor landing for the two in-page jumps on the homepage: the utility bar's
   "Reviews" link (/#odsd-reviews) and the footer's "Sound Advisor" (#odsd-advisor).
   Measured before this rule: the jump put the section top at y=0 while the sticky
   header still occupied 0-79px, so the "Why customers trust us" heading landed
   BEHIND the header and the section looked like it had stopped short. The PDP
   bands already carry this; the homepage sections never got it.
   `--odsd-header-h` is only published on PDP, hence the fallback. */
#odsd-reviews,
#odsd-advisor {
  scroll-margin-top: calc(var(--odsd-header-h, 84px) + 12px);
}
.odsd-reviews__inner {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 0 clamp(1.1rem, 4vw, 3rem);
}
.odsd-reviews__head { margin-bottom: clamp(1.8rem, 4vw, 3rem); }
.odsd-reviews__head h2 {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: clamp(1.9rem, 3.5vw, 3rem); line-height: 1.08; letter-spacing: -0.02em;
  margin: 0; color: var(--odsd-ink);
}
.odsd-reviews__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--odsd-gap); }

/* ---- Reviews marquee (2026-08-07) --------------------------------------------
   All 14 quotes drift right-to-left continuously instead of 3 being drawn per
   load. Deliberately a pure CSS transform animation: it runs on the compositor,
   so there is no per-frame main-thread work and no layout or paint - the cost is
   one extra layer, which is why this does not behave like a JS ticker.
   Pauses on hover, on keyboard focus, when scrolled off screen (observer in
   extensions.js), and entirely under prefers-reduced-motion.
   --odsd-rev-speed is the time for ONE full pass of the 14 cards. */
.odsd-reviews__marquee {
  --odsd-rev-speed: 150s;
  overflow: hidden;                       /* must clip: the track is ~2x page wide */
  /* Fade the two edges so cards enter and leave instead of being sliced. Kept
     narrow (2.5%): at 5% the ramp reached into the first card's text and made a
     fully-visible quote look half-faded. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 2.5%, #000 97.5%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 2.5%, #000 97.5%, transparent);
}
.odsd-reviews__track {
  display: flex;
  width: max-content;
  animation: odsd-rev-drift var(--odsd-rev-speed, 150s) linear infinite;
}
/* Gap on the CARD, never on the track. A flex `gap` puts the -50% loop point in
   the middle of the joining gap and the wrap visibly jumps. */
.odsd-reviews__track .review {
  flex: 0 0 clamp(260px, 26vw, 360px);
  margin-right: var(--odsd-gap);
}
.odsd-reviews__marquee:hover .odsd-reviews__track,
.odsd-reviews__marquee:focus-within .odsd-reviews__track,
.odsd-reviews.is-paused .odsd-reviews__track,
.odsd-reviews.is-focus-paused .odsd-reviews__track,   /* keyboard focus on the pause button, extensions.js */
.odsd-reviews.is-offscreen .odsd-reviews__track { animation-play-state: paused; }
/* Pause / play button under the marquee (2026-10-17). Same round control as the
   scroller arrows; stays on phones, where there is no hover to pause with. */
.odsd-reviews__controls { display: flex; justify-content: center; margin-top: 1.25rem; }
.odsd-reviews__pause .odsd-i-play,
.odsd-reviews.is-paused .odsd-reviews__pause .odsd-i-pause { display: none; }
.odsd-reviews.is-paused .odsd-reviews__pause .odsd-i-play { display: block; }
@keyframes odsd-rev-drift {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  /* No drift at all - fall back to a static, centred row of the real cards and
     drop the duplicate run. (0,3,0) beats the .odsd-reviews .review display. */
  .odsd-reviews__track { animation: none; flex-wrap: wrap; justify-content: center; width: auto; }
  .odsd-reviews__marquee { -webkit-mask-image: none; mask-image: none; }
  .odsd-reviews__track > .review[aria-hidden="true"] { display: none; }
  .odsd-reviews__controls { display: none; }   /* nothing moves, nothing to pause */
}
/* Card type is deliberately a step SMALLER than the repo's (owner, 2026-08-26).
   The marquee is a flex row, so every card is as tall as the tallest quote -
   a 5-line quote set at 1.05rem/1.6 made the whole band ~410px tall and it ate
   too much of the fold. Quote drops to 0.95rem/1.55 and the card's chrome
   (padding, gaps, avatar, foot) comes down with it so the card stays in
   proportion rather than looking like small text in a big box. Card WIDTH is
   unchanged on purpose: narrowing it would re-wrap the quotes and give the
   height straight back. Scoped to .odsd-reviews .review - the trust stats above
   and every other band keep their sizes. */
.odsd-reviews .review {
  background: #fff; border: 1px solid var(--odsd-line);
  border-radius: 18px; padding: 1.35rem 1.4rem;
  display: flex; flex-direction: column; gap: 0.75rem;
}
.odsd-reviews .review .stars { color: var(--odsd-star); letter-spacing: 0.05em; font-size: 0.82rem; }
.odsd-reviews .review p { font-size: 0.95rem; line-height: 1.55; margin: 0; color: var(--odsd-ink); }
.odsd-reviews .review-foot { margin-top: auto; display: flex; align-items: center; gap: 0.65rem; }
.odsd-reviews .review-avatar {
  width: 34px; height: 34px; border-radius: 50%; font-size: 0.85rem;
  background: var(--odsd-mist); display: grid; place-items: center;
  font-family: var(--odsd-font-display); font-weight: 700; color: var(--odsd-ink-soft);
}
.odsd-reviews .review-foot b { font-family: var(--odsd-font-display); font-size: 0.875rem; display: block; color: var(--odsd-ink); }
.odsd-reviews .review-foot span { font-size: 0.75rem; color: var(--odsd-ink-soft); }
@media (max-width: 720px) { .odsd-reviews__grid { grid-template-columns: 1fr; } }
@media (max-width: 720px) {
  /* narrower cards and a proportionally shorter pass, so the drift reads at the
     same speed on a phone as it does on a desktop */
  .odsd-reviews__marquee { --odsd-rev-speed: 110s; }
  .odsd-reviews__track .review { flex-basis: min(78vw, 300px); }
}

/* QPV band (repo .qpv, injected with the ratingbar just before the footer) */
.odsd-qpv { background: var(--odsd-sand); padding: clamp(2.5rem, 5vw, 4rem) 0; }
.odsd-qpv__inner {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 0 clamp(1.1rem, 4vw, 3rem);
}
.odsd-qpv__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem; }
.odsd-qpv__col h3 {
  font-family: var(--odsd-font-display); font-weight: 700; font-size: 1.1rem;
  letter-spacing: 0.04em; text-transform: uppercase; margin: 0 0 0.6rem; color: var(--odsd-ink);
}
.odsd-qpv__col p { color: var(--odsd-ink-soft); font-size: 0.95rem; line-height: 1.6; margin: 0; }
@media (max-width: 720px) { .odsd-qpv__grid { grid-template-columns: 1fr; gap: 1.4rem; } }

/* ---- Repo-exact product grids (styles.css section 9 verbatim, --odsd tokens) ----
   Rendered by the ODSD PRODUCT GRIDS engine in extensions.js, which fetches LIVE
   products via the same public Runtime_*ProductList_Load_Query the native
   mmx-product-carousel uses (its shadow DOM can't be restyled, so we mirror the
   repo's productCard() markup 1:1 instead and hide the carousel). */
:root {
  --odsd-paper: #ffffff;
  --odsd-mist: #ecebe6;
  --odsd-gap: clamp(1rem, 2vw, 1.75rem);
  --odsd-section-y: clamp(2.5rem, 5vw, 5rem);
  --odsd-shadow-sm: 0 1px 2px rgba(20, 20, 26, 0.05), 0 6px 18px rgba(20, 20, 26, 0.06);
  --odsd-shadow-md: 0 10px 40px rgba(20, 20, 26, 0.10);
  --odsd-fs-small: 0.8125rem;
}
body { background: var(--odsd-paper); }   /* repo body: background var(--paper) */

.odsd-gridband--sand { background: var(--odsd-sand); }
.odsd-gridband__inner {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 0 clamp(1.1rem, 4vw, 3rem) var(--odsd-section-y);
}

.odsd-pgrid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--odsd-gap);
}
.odsd-pgrid .product-card {
  position: relative; /* anchor for the full-card click overlay below */
  background: #fff;
  border: 1px solid var(--odsd-line);
  border-radius: 22px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: transform 0.35s var(--odsd-ease), box-shadow 0.35s var(--odsd-ease), border-color 0.35s var(--odsd-ease);
}
.odsd-pgrid .product-card:hover { transform: translateY(-5px); box-shadow: var(--odsd-shadow-md); border-color: transparent; }
.odsd-pgrid .product-media {
  position: relative;
  aspect-ratio: 1 / 1;
  background: #fff;
  display: grid; place-items: center;
  overflow: hidden;
}
.odsd-pgrid .media-link { display: contents; }
.odsd-pgrid .product-media .product-art { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s var(--odsd-ease); }
.odsd-pgrid .product-card:hover .product-media .product-art { transform: scale(1.04); }
.odsd-pgrid .product-media .product-photo { object-fit: contain; padding: 8%; }
.odsd-pgrid .sale-flag {
  position: absolute; top: 0.8rem; left: 0.8rem; z-index: 2;
  background: var(--odsd-accent); color: var(--odsd-ink); font-family: var(--odsd-font-display);
  font-weight: 800; font-size: 0.68rem; letter-spacing: 0.08em; padding: 0.3em 0.6em; border-radius: 5px;
}
.odsd-pgrid .quick-add {
  position: absolute; left: 0.9rem; right: 0.9rem; bottom: 0.9rem;
  z-index: 2; /* above the full-card click overlay so Add to cart still wins */
  background: var(--odsd-ink); color: #fff;
  border-radius: 999px; padding: 0.75rem;
  font-family: var(--odsd-font-display); font-weight: 600; font-size: 0.9rem;
  text-align: center; text-decoration: none;
  opacity: 0; transform: translateY(8px);
  transition: opacity 0.3s var(--odsd-ease), transform 0.3s var(--odsd-ease), background 0.2s;
}
.odsd-pgrid .product-card:hover .quick-add { opacity: 1; transform: translateY(0); }
.odsd-pgrid .quick-add:hover { background: var(--odsd-accent-deep); }
.odsd-pgrid .product-info { padding: 1.2rem 1.25rem 1.4rem; display: flex; flex-direction: column; gap: 0.4rem; flex: 1; }
.odsd-pgrid .card-badges { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; }
.odsd-pgrid .cbadge {
  font-size: 0.62rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  background: var(--odsd-mist); color: var(--odsd-ink-soft); padding: 0.25em 0.5em; border-radius: 5px;
}
.odsd-pgrid .cbadge-rating { margin-left: auto; font-family: var(--odsd-font-display); font-weight: 700; font-size: 0.75rem; color: var(--odsd-star); white-space: nowrap; }
.odsd-pgrid .product-line { font-size: var(--odsd-fs-small); color: var(--odsd-accent-deep); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; }
.odsd-pgrid .product-name { font-family: var(--odsd-font-display); font-size: 1.15rem; font-weight: 700; line-height: 1.08; letter-spacing: -0.02em; margin: 0; color: var(--odsd-ink); }
.odsd-pgrid .card-blurb { font-size: var(--odsd-fs-small); color: var(--odsd-ink-soft); line-height: 1.5; margin: 0; }
.odsd-pgrid .product-foot { margin-top: auto; padding-top: 0.7rem; display: flex; align-items: center; justify-content: space-between; }
.odsd-pgrid .product-price { font-family: var(--odsd-font-display); font-weight: 700; font-size: 1.2rem; color: var(--odsd-ink); }
.odsd-pgrid .product-price del { color: var(--odsd-ink-soft); font-weight: 500; font-size: 0.95rem; margin-left: 0.4rem; }
.odsd-pgrid .product-price .unit { font-weight: 600; font-size: 0.8rem; color: var(--odsd-ink-soft); font-family: inherit; }
.odsd-pgrid a { text-decoration: none; color: inherit; }

/* FULL-CARD CLICK (owner request 2026-08-07): the product-name anchor stretches
   over the whole card via ::after, so a click anywhere on the card opens the PDP
   with real anchor semantics (new tab, middle-click, status bar) and NO extra
   link in the markup. It must be THIS anchor, not .media-link - .media-link is
   display:contents inside .product-media (position:relative), so its ::after
   would be confined to the image box. .quick-add and .sale-flag sit at z-index 2,
   above the overlay. Covers every .odsd-pgrid band: SSR Best Sellers + JS grids. */
.odsd-pgrid .product-info > a::after { content: ""; position: absolute; inset: 0; z-index: 1; }

/* repo reveal-on-scroll, scoped */
.odsd-pgrid .reveal { opacity: 0; transform: translateY(22px); transition: opacity 0.7s var(--odsd-ease), transform 0.7s var(--odsd-ease); }
.odsd-pgrid .reveal.in { opacity: 1; transform: none; }

@media (max-width: 1024px) { .odsd-pgrid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 480px) { .odsd-pgrid { grid-template-columns: 1fr; } }
@media (prefers-reduced-motion: reduce) {
  .odsd-pgrid .reveal { opacity: 1; transform: none; }
}

/* Refresh (repo PR #11): rating on its own line above the spec badges. */
.odsd-pgrid .card-rating { display: flex; margin-bottom: 0.3rem; }
.odsd-pgrid .card-rating .cbadge-rating { margin-left: 0; }

/* Refresh: Best Sellers horizontal scroller (Deals + Bundles + Favorites merged
   into one section). Fixed-width cards that snap-scroll sideways, with prev/next
   arrow controls. Ports styles.css .product-grid--scroll / .deal-scroller / .scroll-arrow. */
.odsd-scroller { position: relative; }
.odsd-pgrid--scroll {
  display: flex;
  grid-template-columns: none;
  gap: var(--odsd-gap);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 0.25rem; /* was room for the now-hidden scrollbar */
  /* Hide the native scrollbar (repo PR #13 parity) - the progress track and the
     prev/next arrows are the indicator, and showing both reads as two competing
     lines. This also kills the VERTICAL scrollbar: overflow-x:auto makes
     overflow-y compute to auto, and the old 8px horizontal bar ate enough height
     to tip the tallest card into vertical overflow. */
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* [!] HIDING A SCROLLBAR DOES NOT STOP THE SCROLLING. The rules above only make
     the vertical bar invisible - the row stayed vertically scrollable, so a wheel
     or trackpad gesture over Best Sellers nudged the cards up and down inside
     their box. Owner reported it 2026-08-03 as "a scroll up and down trigger
     within it". This is the line that actually disables it: an explicit
     non-visible value stops overflow-y computing to `auto` off the back of
     overflow-x. */
  overflow-y: hidden;
}
/* Exactly three cards per view (repo PR #13 parity) - width is a third of the row
   minus the gaps, so no fourth card peeks in. Steps down to 2, then 1. */
.odsd-pgrid--scroll .product-card {
  flex: 0 0 calc((100% - 2 * var(--odsd-gap)) / 3);
  scroll-snap-align: start;
}
@media (max-width: 900px) {
  .odsd-pgrid--scroll .product-card { flex-basis: calc((100% - var(--odsd-gap)) / 2); }
}
@media (max-width: 600px) {
  .odsd-pgrid--scroll .product-card { flex-basis: 100%; }
}
.odsd-pgrid--scroll .card-blurb {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
}
.odsd-pgrid--scroll::-webkit-scrollbar { display: none; }
/* Controls sit BELOW the cards (repo PR #13 parity) so no arrow overlaps a product
   photo, with a progress track whose thumb width = the share of the row visible and
   whose position = how far along you have scrolled. Ports styles.css
   .scroller-controls / .scroller-track / .scroller-thumb / .scroll-arrow. */
.odsd-scroller-controls {
  display: flex; align-items: center; gap: clamp(1rem, 3vw, 2rem);
  margin-top: 1.1rem;
}
.odsd-scroller-track {
  /* display:block is REQUIRED, not cosmetic: the track carries aria-hidden="true"
     (it is decorative) and the Shadows scaffold ships a bare global
     [aria-hidden="true"]{display:none}. Same specificity (0,1,0), and this block is
     emitted after the scaffold, so declaring display here outranks it. Without this
     the track collapses and the arrows slide to the left of the row. */
  display: block;
  flex: 1; height: 2px; background: var(--odsd-line); border-radius: 2px;
  position: relative; overflow: hidden;
}
.odsd-scroller-thumb {
  position: absolute; left: 0; top: 0; height: 100%;
  width: 30%; background: var(--odsd-ink); border-radius: 2px;
  transition: transform 0.25s var(--odsd-ease), width 0.25s var(--odsd-ease);
}
.odsd-scroller-arrows { display: flex; gap: 0.5rem; flex: none; }
.odsd-scroll-arrow {
  width: 42px; height: 42px; border-radius: 50%;
  display: grid; place-items: center;
  background: #fff; border: 1px solid var(--odsd-line);
  color: var(--odsd-ink); cursor: pointer;
  transition: background 0.2s var(--odsd-ease), color 0.2s var(--odsd-ease),
              border-color 0.2s var(--odsd-ease), opacity 0.2s var(--odsd-ease);
}
.odsd-scroll-arrow:hover { background: var(--odsd-ink); border-color: var(--odsd-ink); color: #fff; }
.odsd-scroll-arrow:focus-visible { outline: 2px solid var(--odsd-accent-deep); outline-offset: 2px; }
.odsd-scroll-arrow svg { width: 18px; height: 18px; stroke: currentColor; fill: none; }
.odsd-scroll-arrow[disabled] { opacity: 0.3; cursor: default; pointer-events: none; }
@media (max-width: 720px) {
  .odsd-scroller-arrows { display: none; }   /* touch users swipe */
  .odsd-scroller-controls { margin-top: 0.8rem; }
}

/* Category tiles: fixed 16px gap (spec .cat-grid). The grid sits in the
   component's shadow DOM; the custom property inherits through. */
#mmx-image-across__six-across {
  --mmx-image-across__gap: 16px;
}

/* Trust badges inside the "Premium audio, honest prices, real people."
   banner (spec .trust-grid / .trust-item, relocated there on main). The
   markup lives in the banner body; icons are CSS data-URI SVGs. */
.odsd-trust {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
  margin-top: 2.5rem;
  text-align: left;
}
.odsd-trust__item {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.85rem;
}
.odsd-trust__item::before {
  content: "";
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  background-repeat: no-repeat;
  background-size: contain;
  background-position: center;
}
.odsd-trust__item--ship::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a76f12' stroke-width='1.8'%3E%3Cpath d='M3 7h11v8H3zM14 10h4l3 3v2h-7z'/%3E%3Ccircle cx='7' cy='17' r='1.6'/%3E%3Ccircle cx='17' cy='17' r='1.6'/%3E%3C/svg%3E");
}
.odsd-trust__item--support::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a76f12' stroke-width='1.8'%3E%3Cpath d='M3 12a9 9 0 1118 0v5a2 2 0 01-2 2h-2v-6h4M3 12v5a2 2 0 002 2h2v-6H3'/%3E%3C/svg%3E");
}
.odsd-trust__item--secure::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a76f12' stroke-width='1.8'%3E%3Crect x='4' y='10' width='16' height='11' rx='2'/%3E%3Cpath d='M8 10V7a4 4 0 018 0v3'/%3E%3C/svg%3E");
}
.odsd-trust__item--returns::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a76f12' stroke-width='1.8'%3E%3Cpath d='M3 8a9 9 0 0115-3l3 3M21 16a9 9 0 01-15 3l-3-3'/%3E%3Cpath d='M21 5v4h-4M3 19v-4h4'/%3E%3C/svg%3E");
}
.odsd-trust__item b {
  display: block;
  font-size: 0.95rem;
}
.odsd-trust__item span {
  display: block;
  font-size: 0.8rem;
  color: #6b6b70;
}
@media (max-width: 760px) {
  .odsd-trust {
    grid-template-columns: 1fr 1fr;
    gap: 1.4rem 1rem;
  }
}

/* ============================================================================
   ODSD design language - chrome skin (spec: outdoorspeakerdepot/css/styles.css
   tokens). Typography/button tokens live in theme.json (mm-theme-styles);
   this covers what tokens can't reach: header hairline, dark footer, imagery
   radius, accent hovers.
   ========================================================================== */
:root {
  --odsd-ink: #16161a;
  --odsd-ink-soft: #6b6b70;
  --odsd-line: #e4e1da;
  --odsd-sand: #f5f3ef;
  --odsd-night: #121316;   /* repo --night (dark SECTIONS); the footer is darker, see below */
  --odsd-accent: #fcae46;
  --odsd-accent-deep: #966310;   /* was #a76f12 (4.26:1 on white) - WCAG AA 5.1:1 white / 4.6:1 sand, audit 2026-09-22 */
}

/* ---- z-index SCALE (Miva review CSS-01 part 1, 2026-10-04) -----------------
   Every stacking layer WE own, named, in order. Values are unchanged from the
   literals they replace - this names the stack, it does not move anything.
   Give a new floating element one of these (or a new named step between two
   of them), never a fresh number: the two bugs Miva's QA found (Q2 phone
   drawer under the header, Q6 delivery panel under the tab strip) were both
   magic numbers that nobody had placed against the others. Rule of thumb from
   FINDINGS "z-index stack": join the stack, do not out-bid it - anything
   under the chat scrim is covered by the Ask OSD drawer for free.
   Shadows' own stock layers are left as the theme ships them (in its
   extension blocks above): fasten header 300, search preview / mini-basket
   500, datepicker 1000, photo viewer 9999. Vendors sit above everything:
   the Google merchant badge at 2147483647, hidden by hand while the chat
   drawer or the phone menu is open. Small 1-3 values inside components are
   local stacking, not layers, and stay literal. */
:root {
  --odsd-z-page-float: 80;      /* in-page sticky + dropdowns: PDP tab strip, facet dropdown card */
  --odsd-z-page-popover: 85;    /* over the tab strip, under the menus: ASM delivery panel (Q6) */
  --odsd-z-overlay: 90;         /* mega menu dropdown, search dim */
  --odsd-z-header: 100;         /* sticky site header */
  --odsd-z-header-popover: 110; /* header search preview */
  --odsd-z-drawer: 200;         /* phone menu drawer, over the header (Q2) */
  --odsd-z-chat-scrim: 220;     /* Ask OSD scrim */
  --odsd-z-chat: 221;           /* Ask OSD drawer */
  --odsd-z-modal: 10000;        /* full-page modal (waitlist): over the theme's photo viewer at 9999 */
}

/* Header: crisp white with a hairline, per spec .site-header */
.t-site-header {
  border-bottom: 1px solid var(--odsd-line);
}

/* Accent hover on navigation, per spec link treatment */
.c-navigation__link:hover,
.x-fasten-header__navigation-link:hover {
  color: var(--odsd-accent-deep);
}

/* Footer per repo .site-footer: a warm gold-tinted radial glow in the top-left fading
   into near-black. The tint is what stops the page ending on a flat black slab - it was
   missing here, which read as "the bottom of the site lost its colour". */
footer.t-site-footer.u-bg-white {
  background:
    radial-gradient(120% 90% at 15% 0%, rgba(252, 174, 70, 0.16) 0%, rgba(252, 174, 70, 0) 55%),
    linear-gradient(180deg, #17150f 0%, #0d0e10 60%);
  color: rgba(255, 255, 255, 0.7);
}
.t-site-footer .c-menu__title,
.t-site-footer .x-collapsible-content__toggle,
.t-site-footer .mm-theme-typography-subheading-small {
  color: #ffffff;
}
.t-site-footer .c-menu__link,
.t-site-footer a,
.t-site-footer .mm-theme-button-secondary-navigation {
  color: rgba(255, 255, 255, 0.72);
}
.t-site-footer .c-menu__link:hover,
.t-site-footer a:hover {
  color: var(--odsd-accent);
}
.t-site-footer__copyright,
.t-site-footer__copyright a {
  color: rgba(255, 255, 255, 0.5);
}
/* White logo on the dark footer: RETIRED 2026-08-17. The invert moved onto
   .odsd-foot-logo-img (class-based) because the ReadyTheme entity made the
   logo filename admin-changeable, and a src-matched filter would silently
   stop inverting on a rename. Do not re-add a filename-matched rule. */

/* Footer brand block (repo .footer-brand), injected as the first column of the
   footer content grid: wordmark + tagline + address with the phone on its own
   line (repo delta 5). Keeps the store's real nav columns + newsletter alongside. */
.odsd-foot-brand { max-width: 34ch; }
.odsd-foot-logo {
  font-family: var(--odsd-font-display); font-weight: 800;
  font-size: 1.35rem; letter-spacing: 0.01em; line-height: 1.05;
  color: #fff; text-transform: uppercase; margin-bottom: 1.1rem;
}
.odsd-foot-tag { font-size: 0.95rem; color: rgba(255, 255, 255, 0.7); margin-bottom: 1.2rem; line-height: 1.6; }
.odsd-foot-addr { font-size: 0.9rem; color: rgba(255, 255, 255, 0.7); line-height: 2; margin: 0; }
.odsd-foot-addr a { color: #fff; }
.odsd-foot-addr a:hover { color: var(--odsd-accent); }

/* Payment badges in the copyright bar (repo .footer-pay) */
.t-site-footer__copyright { flex-wrap: wrap; align-items: center; gap: 0.8rem 1.2rem; }
.odsd-foot-pay { display: inline-flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; margin-left: auto; }
.odsd-foot-pay span {
  font-size: 0.62rem; font-weight: 700; letter-spacing: 0.04em;
  padding: 0.35rem 0.55rem; border-radius: 5px;
  background: rgba(255, 255, 255, 0.1); color: #fff;
}

/* PayPal "Pay in 4" messaging bar on the homepage - hidden per owner request
   (not part of the 2026 design). Product-page PayPal messaging unaffected. */
[data-pp-message][data-pp-placement="home"] {
  display: none;
}

/* Rounded imagery on the homepage tiles, per spec --radius-lg: 22px
   (.cat-card / .product-card use the large radius) */
#mmx-image-across__six-across picture[slot="image"] img,
#mmx-image-across__two-across picture[slot="image"] img,
#mmx-image-across__three-across picture[slot="image"] img {
  border-radius: 22px;
}
#mmx-hero__hero picture[slot="image"],
#mmx-hero__hero-2 picture[slot="image"] {
  border-radius: 0; /* full-bleed heroes stay square, per spec rotator */
}

/* ============================================================================
   ODSD HEADER (repo .promo/.utility-bar + .site-header port, 2026-07-09)
   Markup: templates/cssui-navbar.mvt + cssui-global-header.mvt.
   Desktop nav (.odsd-nav) is built by js/extensions.js ODSD HEADER NAV from
   the native transfigure nav, which stays as the sub-960px drawer.
   ========================================================================= */

/* ---- Utility bar (repo .promo -> .utility-bar) ---- */
.odsd-utility {
  background: var(--odsd-ink); color: #fff;
  font-size: 0.8125rem; letter-spacing: 0.04em;
}
.odsd-utility__inner {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 0.55rem clamp(1.1rem, 4vw, 3rem);
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
}
/* ARRANGEMENT 2b (repo main @97ecad6, ported 2026-08-07). Policies left, things
   you act on right - split by meaning rather than by leftovers. The previous
   arrangement put four company links on the left and three claims on the right;
   About/Story/Reviews went because the footer already carries all three, and
   "Expert tech support" went because the Support pill beside the phone says it
   better by naming the channel.
   Icons are inline SVG rather than emoji - it sidesteps the mojibake problem
   entirely, which is why the repo uses them. No aria-hidden on any of them:
   Shadows ships a bare [aria-hidden="true"]{display:none} that would erase them. */
.odsd-utility__left, .odsd-utility__right { display: flex; align-items: center; gap: 0; }
.odsd-utility__left a, .odsd-utility__right a { color: #fff; opacity: 0.9; text-decoration: none; }
.odsd-utility__left a:hover, .odsd-utility__right a:hover { opacity: 1; color: var(--odsd-accent); }
/* "Free shipping over $50" -> shipping policy (C1, 2026-10-19): same hover cue as Outlet & Deals */
.odsd-utility__left a:hover { text-decoration: underline; text-underline-offset: 3px; }
.odsd-utility__left > *, .odsd-utility__right > * { display: inline-flex; align-items: center; gap: 0.45rem; padding-inline: 0.75rem; opacity: 0.92; }
.odsd-utility__left > *:first-child { padding-left: 0; }
.odsd-utility__right > *:last-child { padding-right: 0; }
.odsd-utility svg { width: 15px; height: 15px; fill: none; stroke: var(--odsd-accent); flex: none; display: block; }
/* The separator is a real character rather than a border, matching the repo -
   it reads lighter at this type size than a 1px rule does. */
.odsd-util-sep { opacity: 0.35; padding-inline: 0.25rem; }
.odsd-util-phone { color: #fff; font-weight: 600; letter-spacing: 0.01em; opacity: 1; }
/* Outlet is a link and has to read as one, but the gold is spent on the Support
   pill (below), so this is marked by weight and its tag icon instead. */
.odsd-util-outlet { font-weight: 700; letter-spacing: 0.01em; opacity: 1; }
.odsd-utility__right a.odsd-util-outlet:hover { color: var(--odsd-accent); text-decoration: underline; text-underline-offset: 3px; }
/* Support is the channel we want people to take, so it is the ONE accented item
   on the row and the only one shaped like a button. `.odsd-utility__right a`
   sets #fff, so this has to be at least as specific to win. */
.odsd-utility__right a.odsd-util-chat {
  color: var(--odsd-accent); font-weight: 600; opacity: 1;
  border: 1px solid rgba(252, 174, 70, 0.45); border-radius: 999px;
  padding: 0.22rem 0.85rem; margin-left: 0.9rem;
}
.odsd-utility__right a.odsd-util-chat:hover {
  background: var(--odsd-accent); color: #2a1d05; border-color: var(--odsd-accent);
}
.odsd-utility__right a.odsd-util-chat:hover svg { stroke: #2a1d05; }
@media (max-width: 860px) {
  /* Keep the two things a shopper acts on - the phone and Support. The shipping
     and returns notes are the first to go; they are reassurance, not navigation. */
  .odsd-utility__left { display: none; }
  .odsd-utility__inner { justify-content: flex-end; }
  .odsd-util-outlet { padding-left: 0; }
}
@media (max-width: 560px) {
  /* Three items still fit here, but only once the row stops paying for the
     separator and the wide gutters. Nothing is hidden - every link survives. */
  .odsd-utility { font-size: 0.72rem; letter-spacing: 0.02em; }
  .odsd-utility__right .odsd-util-sep { display: none; }
  .odsd-utility__right > * { padding-inline: 0.4rem; }
  .odsd-utility__right a.odsd-util-chat { margin-left: 0.4rem; padding-inline: 0.6rem; }
  .odsd-utility svg { width: 13px; height: 13px; }
}

/* ---- Sticky header row (repo .site-header + .nav) ----
   The repo's `.site-header` is sticky at EVERY width - it has no mobile
   override - so ours has to be too. theme.css line 165 sets
   `.t-site-header { position: relative }` inside `@media (max-width: 59.999em)`,
   and `.odsd-header` alone is the same weight (0,1,0), so below 960px the theme
   was winning and the header stopped following. Found via
   CSS.getMatchedStylesForNode, not by reading the file - the declaration below
   looked correct and the served page disagreed.

   `.odsd-header.t-site-header` is (0,2,0), so it wins in any media query and
   source order stops mattering. Both classes are always on the element (see
   cssui-global-header.mvt). */
/* [!] NO BACKDROP BLUR - OWNER DECISION 2026-08-17, do not restore it to match
   the repo. The repo ships `backdrop-filter: saturate(150%) blur(14px)` here
   and the store carried it until today; the owner A/B'd it in their own
   browser (console toggle) and scrolling is visibly smoother without it - a
   backdrop filter forces a backdrop snapshot + filter pass on every scrolled
   frame under a sticky header. Background alpha raised 0.85 -> 0.97: at 0.92
   the homepage hero's display-size headline still ghosted READABLY through
   the bar (screenshotted); at 0.97 the ghost is ~3% contrast, effectively
   invisible, while the bar keeps a whisper of translucency. A future session
   diffing store vs repo will see the missing blur as drift: it is not, it is
   this decision.
   Removing the filter also un-makes the header a containing block for
   position:fixed descendants - the only one (the search dim) already lives on
   body for that exact reason (see the comment at the search-band rules). */
.odsd-header,
.odsd-header.t-site-header {
  position: sticky; top: 0; z-index: var(--odsd-z-header);
  background: rgba(255,255,255,0.97);
  border-bottom: 1px solid var(--odsd-line);
}
/* The phone menu drawer opens ABOVE the header (2026-09-28, Miva QA ticket
   #1389066). The theme opens it at z-index 2, under this header's 100, so the
   header row covered the drawer's first link ("Speakers"). 200 is the drawer's
   documented slot in the stack (FINDINGS "z-index stack"): over the header and
   the search preview, under the Ask OSD scrim at 220. Same media query and
   selector weight as the theme's rule; this one is later in the file. */
@media screen and (max-width: 59.999em) {
  .x-transfigure-navigation.is-open { z-index: var(--odsd-z-drawer); }
}
.odsd-header__row {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 1.05rem clamp(1.1rem, 4vw, 3rem);
  display: flex; align-items: center; gap: 2rem;
}
/* ODSD MINIMAL HEADER (checkout) - Miva review item, 2026-08-17. On page_style
   minimal (OCST/OSEL/OPAY + AmazonPay screens) cssui-global-header.mvt renders
   only the brand lockup in the row; this centers it. (0,2,0) beats the
   single-class row rule above regardless of source order. */
.odsd-header--minimal .odsd-header__row { justify-content: center; }
/* The three checkout pages carry page_style "With Background" in admin, but
   cssui-html-profile.mvt forces them to minimal for the logo-only chrome - so
   the gray that theme.css keys on .t-page-style--with-background is replicated
   here on the body page classes (same var, same breakpoint tweaks). Remove
   these if the pages ever leave the forced-minimal list.
   !important is load-bearing: the "Deployed-repo exactness" rule below paints
   `html, body { background: #fff !important }` site-wide (the repo's white
   paper), and only another important declaration can out-cascade it - among
   importants the class here (0,1,0) beats that rule's (0,0,1). Measured with
   CSS.getMatchedStylesForNode on OCST, 2026-08-17. */
.t-page-ocst, .t-page-osel, .t-page-opay { background-color: var(--gray-100) !important; }
@media (max-width: 47.999em) {
  .t-page-ocst .t-main-content-element,
  .t-page-osel .t-main-content-element,
  .t-page-opay .t-main-content-element { padding-left: 0; padding-right: 0; }
}
@media (min-width: 90em) {
  .t-page-ocst .t-main-content-element,
  .t-page-osel .t-main-content-element,
  .t-page-opay .t-main-content-element { padding-top: 1rem; }
}
/* Checkout notices carried over from production (2026-09-30): the shipping
   step's signature-required note and freight-fee list (osel.mvt) and the
   billing-address warning (ocst-customer.mvt). Production drew them in inline
   red; here they take the repo's palette - bold ink for the note, the fees on
   a sand card like the rest of the theme's info panels. */
.odsd-ship-notes {
  margin: 1.25rem 0 0.25rem; padding-top: 1rem; border-top: 1px solid var(--odsd-line);
  font-size: 0.88rem; line-height: 1.5; color: var(--odsd-ink);
}
.odsd-ship-notes__alert { margin: 0 0 0.85rem; }
.odsd-ship-notes__freight { background: var(--odsd-sand); border-radius: 10px; padding: 0.9rem 1.1rem; }
.odsd-ship-notes__freight p { margin: 0 0 0.45rem; }
.odsd-ship-notes__freight ul { margin: 0; padding-left: 1.1rem; }
.odsd-ship-notes__freight li { margin: 0.3rem 0; }
.odsd-checkout-note { margin: 0.25rem 0 0; font-size: 0.85rem; color: var(--odsd-ink-soft); }
/* Repo .site-header .brand + .brand-est, ported 2026-07-31: wordmark with the
   "Since 2003" heritage lockup beneath it, small caps and wide tracking in the
   brand gold, hairline rules fading in from either side. */
.odsd-brand { display: flex; flex-direction: column; align-items: stretch; gap: 0.3rem; line-height: 0; flex: none; }
/* No underline under "Since 2003" (owner, 2026-08-03). The lockup is an <a>, and
   theme-styles.css underlines every anchor by default (`a { text-decoration:
   underline }`), which drew a gold rule under the text - easily mistaken for part
   of the lockup, since the est line already has hairlines either side of it.
   Those hairlines are the ::before/::after below and are meant to stay; only the
   text-decoration goes. A single class beats the bare element selector, so no
   !important is needed. (core.css does carry an `a { text-decoration: underline
   !important }`, but it is inside a @media print block and never applies here.) */
.odsd-brand, .odsd-brand:hover, .odsd-brand__est { text-decoration: none; }
.odsd-brand__est {
  display: flex; align-items: center; gap: 0.55em;
  font-family: var(--odsd-font-display);
  font-size: 0.58rem; font-weight: 600; line-height: 1;
  letter-spacing: 0.42em; text-indent: 0.42em;
  text-transform: uppercase; color: var(--odsd-accent-deep);
  white-space: nowrap;
}
/* GOLD HAIRLINES - owner request 2026-08-06, and a DELIBERATE departure from the
   repo, which fades these to `--line` (#e4e1da, the neutral hairline) on both
   sides. Owner wants them to read as part of the gold lockup rather than as
   neutral rules, so they now fade to `--odsd-accent-deep` (#a76f12), the same
   colour as the "Since 2003" text they flank. The fade from transparent is kept,
   which is what stops a 1px gold rule looking heavy next to 0.58rem type.
   Do not "correct" this back to --line without asking; it is intentional. */
.odsd-brand__est::before, .odsd-brand__est::after {
  content: ""; flex: 1; height: 1px; display: block;
  background: linear-gradient(to right, transparent, var(--odsd-accent-deep));
}
.odsd-brand__est::after { background: linear-gradient(to left, transparent, var(--odsd-accent-deep)); }
.odsd-brand__logo { height: 30px; width: auto; display: block; }

/* icon buttons (repo .icon-btn) - search toggle / account / orders / cart.
   RESTORED 2026-08-07 with the two-row header. The labelled Account/Orders/Cart
   stack that briefly replaced these belonged to the three-row build, where the
   actions had a row to themselves; back in a row shared with seven nav items and
   the brand, the labels cost width the nav needs. `margin-left: auto` pins the
   group right. */
.odsd-header__actions { display: flex; align-items: center; gap: 1.1rem; margin-left: auto; }
/* Labelled glyphs (owner 2026-08-24): the repo's .act design - icon stacked
   over a one-word .66rem label, both deep-gold on hover, labels hidden under
   1100px (repo breakpoint). Only the DESIGN is copied; the store keeps its
   own four actions (AI / Search / Account / Cart - no Orders, see below).
   Replaces the 42px mist-circle treatment for this row. */
.odsd-icon-btn {
  position: relative;
  display: grid; justify-items: center; gap: 0.18rem;
  background: none; border: 0; padding: 0; cursor: pointer;
  font: 500 0.66rem/1 var(--odsd-font-display); letter-spacing: 0.02em;
  color: var(--odsd-ink-soft); text-decoration: none;
}
.odsd-icon-btn:hover { color: var(--odsd-accent-deep); }
.odsd-icon-btn:hover svg { stroke: var(--odsd-accent-deep); }
/* display:block also outranks the Shadows scaffold's [aria-hidden="true"]{display:none} */
.odsd-icon-btn svg { width: 21px; height: 21px; stroke: var(--odsd-ink); display: block; }
@media (max-width: 1100px) {
	.odsd-icon-btn__label { display: none; }
}

/* Back inside the 42px circle (2026-08-07). The repo's corner-hung pill was
   right for a glyph-with-label-under-it; the action is a circular icon button
   again, so the count sits in it. */
/* Anchored to the ICON, not the button's corner (2026-09-30; Miva QA ticket
   #1389066 Q1, reworked after the code review). The button is 38px on small
   phones, 44px to 959px, 36px at 960-1149, 39px at 1150-1339 and 24px wide
   above that, and the first fix only covered the last tier - the count still
   sat ON the basket glyph at 960-1100px. What never changes: the icon is
   centred horizontally (justify-items:center), so `left: 50% - 1.5px` puts the
   badge's centre 7px right of the icon's centre at every width. Vertically the
   icon is centred in the phone buttons and top-aligned on desktop, so `top`
   is set per tier to land the badge centre 7px above the icon's centre:
   19px icon at 960-1149 -> -6px, 21px icon from 1150 -> -5px. */
.odsd-cart-count {
  position: absolute; top: 4px; left: calc(50% - 1.5px);
  background: var(--odsd-accent-deep); color: #fff;
  font-size: 0.62rem; font-weight: 700;
  min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 999px; display: grid; place-items: center;
}
.odsd-cart-count.is-empty { display: none; }
@media (min-width: 430px) and (max-width: 959px) { .odsd-cart-count { top: 7px; } }
@media (min-width: 960px) { .odsd-cart-count { top: -5px; } }
@media (min-width: 960px) and (max-width: 1149px) { .odsd-cart-count { top: -6px; } }

/* Burger only below the nav breakpoint.
   960 IS NOT AN ARBITRARY NUMBER - do not move it. The burger is Shadows'
   `open-main-menu` hook, and the transfigure nav only has a drawer below its own
   60em breakpoint; above that it is a bar and the hook does nothing. Raising this
   to 1080 was tried on 2026-08-03 and produced a dead burger plus the native nav
   bar leaking in underneath. The six-item overflow is solved by scaling the bar
   down instead - see the nav rules below. */
.odsd-header__menu-btn { display: none; }
@media (max-width: 959px) {
  .odsd-header__menu-btn { display: grid; }
  .odsd-header__row { gap: 1rem; }
}

/* hide the Shadows fasten (scroll-clone) header - .odsd-header is sticky itself */
.x-fasten-header { display: none !important; }

/* ---- Desktop nav (repo .nav-links / .nav-item / .mega) ---- */
.odsd-nav { display: none; }
@media (min-width: 960px) {
  .odsd-nav { display: flex; align-items: center; gap: 1.7rem; margin: 0 auto; }
  /* Native transfigure nav is the drawer below 960 and hidden on desktop.
     display:none keeps it in the DOM, which matters: the third-level items
     (Commercial 70V's children, and Outlet's five since it was promoted) live
     ONLY there - buildNav in extensions.js reads two levels - so this is what
     keeps them crawlable. Do not remove it. */
  .odsd-native-nav { display: none; }
}

.odsd-nav-item > a {
  font-size: 0.95rem; font-weight: 500; color: var(--odsd-ink);
  position: relative; padding: 0.4rem 0; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 0.25rem; text-decoration: none;
}
.odsd-nav-item > a::after {
  content: ""; position: absolute; left: 0; bottom: 0;
  width: 100%; height: 1.5px; background: var(--odsd-ink);
  transform: scaleX(0); transform-origin: left;
  transition: transform 0.3s ease;
}
.odsd-nav-item > a:hover::after { transform: scaleX(1); }
.odsd-caret { font-size: 0.7em; color: var(--odsd-ink-soft); transition: transform 0.25s ease; }
.odsd-nav-item.is-open > a .odsd-caret { transform: rotate(180deg); }

/* ORDER MATTERS HERE, and getting it wrong is why the old six-item fit never
   actually worked. These two blocks set `.odsd-nav-item > a { font-size }`,
   which ties at (0,1,0)+element with the base rule above - so SOURCE ORDER
   decides and they MUST come after it. They used to sit before it, and the
   nav type measured 15.2px at 960px while the rule asked for 0.76rem. The
   row had been overflowing at the narrow end ever since, invisibly, because
   html{overflow-x:hidden} clips it. Do not move these back up.
   (FINDINGS.md #4, "Source-Order Tie".) */
/* SIX-ITEM FIT. The bar shares its row with the brand and three icon buttons,
   and it overflows at the narrow end of the desktop range, so the whole row
   scales down in two steps rather than moving to a drawer - the drawer is not
   available above 960 (see the burger note above).

   RELAXED 2026-08-07 when Outlet left the bar. It had been tightened to 0.72rem
   to fit seven items; six need far less, and 0.72rem (11.5px) was small enough
   to hurt. Measured at 960px with six items: the nav needs 495px of the ~665px
   it can have, so the type went back up to 0.82rem with room still spare.
   If an item is ever ADDED here, re-measure - do not assume this still fits. */
@media (min-width: 960px) and (max-width: 1149px) {
  .odsd-nav { gap: 0.6rem; }
  .odsd-nav-item > a { font-size: 0.82rem; }
  .odsd-header__row { gap: 0.8rem; padding-left: 1rem; padding-right: 1rem; }
  .odsd-header__actions { gap: 0.5rem; }
  .odsd-icon-btn { width: 36px; height: 36px; }
  .odsd-icon-btn svg { width: 19px; height: 19px; }
  .odsd-brand__logo { height: 26px; }
  .odsd-brand__est { font-size: 0.5rem; letter-spacing: 0.3em; text-indent: 0.3em; }
}
@media (min-width: 1150px) and (max-width: 1339px) {
  .odsd-nav { gap: 1.1rem; }
  .odsd-nav-item > a { font-size: 0.9rem; }
  .odsd-header__row { gap: 1.4rem; }
  .odsd-header__actions { gap: 0.8rem; }
  .odsd-icon-btn { width: 39px; height: 39px; }
}

/* full-width mega panel under the sticky header */
.odsd-nav-item { position: static; }
.odsd-mega {
  position: absolute; left: 0; right: 0; top: 100%;
  background: #fff; border-top: 1px solid var(--odsd-line);
  box-shadow: 0 18px 40px rgba(22, 22, 26, 0.12);
  opacity: 0; visibility: hidden; transform: translateY(-8px);
  transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
  z-index: var(--odsd-z-overlay);
}
.odsd-nav-item.is-open > .odsd-mega { opacity: 1; visibility: visible; transform: none; }
.odsd-mega-grid {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 1.6rem clamp(1.1rem, 4vw, 3rem) 2rem;
  display: flex; flex-wrap: wrap; gap: 1.4rem 1.6rem;
}
.odsd-mega-card {
  width: 132px; display: flex; flex-direction: column; gap: 0.55rem;
  text-decoration: none; color: var(--odsd-ink);
}
.odsd-mega-thumb {
  aspect-ratio: 1 / 1; background: var(--odsd-sand);
  border: 1px solid var(--odsd-line); border-radius: 12px; overflow: hidden;
  transition: border-color 0.2s ease, transform 0.25s ease;
}
.odsd-mega-thumb img { width: 100%; height: 100%; object-fit: cover; }
.odsd-mega-card:hover .odsd-mega-thumb { border-color: var(--odsd-ink); transform: translateY(-3px); }
.odsd-mega-label {
  font-family: var(--odsd-font-display); font-weight: 600; font-size: 0.85rem;
  text-align: center; color: var(--odsd-ink);
}
.odsd-mega-card:hover .odsd-mega-label { color: var(--odsd-accent-deep); }

/* ---- Search band (Shadows form restyled; theme.js toggles .search-is-active) ---- */
/* RESTORED 2026-08-07, after the owner reverted the three-row header. This is the
   toggled overlay again: an icon button in the actions opens an absolutely-
   positioned band over a dimmed page. The inline field that briefly replaced it
   is gone along with the row it lived in.
   If it is ever made inline again, the three things that cost time the first
   time round: core.css ships `.c-button { align-self: start }`, so the submit
   will not fill the box; `.c-control-group` children need core's own
   `width: 1%`, not 100%, or the submit wraps onto a second line; and below 860px
   the row has no room for a field once the burger is in it. FINDINGS.md #4.
*/
/* Animated open/close. `display` cannot be transitioned, so the band stays in
   the layout permanently and its height is driven by max-height.
   Three things this has to respect:
   1. theme.js calls headerSearchInput.focus() the instant it adds the class, so
      `visibility` must flip to visible with NO delay on open (it is delayed only
      on close, so the field stays untabbable while collapsed).
   2. `.x-search-preview`, the live-search dropdown, is absolutely positioned
      INSIDE this container. overflow must be hidden during the slide or the form
      spills out of the collapsing box, but it has to return to visible once open
      or the preview gets clipped as soon as anyone types. Hence the 280ms delay
      on overflow when opening and none when closing.
   3. Padding moves onto the form, otherwise it stays visible at max-height 0. */
.odsd-header__search {
  position: absolute;
  top: 100%; left: 0; right: 0;
  padding: 0.9rem 0 1.15rem;
  background: none;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-10px);
  transition: opacity 180ms ease,
              transform 260ms cubic-bezier(0.4, 0, 0.2, 1),
              visibility 0s 260ms;
}
/* theme.css draws the border on the FORM, not the field, and it escalates:
     .t-site-header__search-form                                     0,1,0
     .t-site-header__search-form:focus-within                        0,2,0
     .t-site-header__search-form:focus-within:has(.x-search-preview--open)  0,3,0
   theme.js focuses the input the moment the band opens, so the focus-within
   rectangle is what you actually see in use. Our design is the pill on the field
   alone, so all three have to be answered, each one weight above its counterpart. */
.odsd-header__search > form,
.odsd-header__search > form:focus-within {
  max-width: var(--odsd-container);
  margin: 0 auto;
  padding: 0 clamp(1.1rem, 4vw, 3rem);
  border: 0;
  border-radius: 0;
  background: none;
}
/* :has() in its own rule: in one comma list it would drop the WHOLE rule in browsers without :has() (FINDINGS 2026-09-22) */
.odsd-header__search > form:focus-within:has(.x-search-preview--open) {
  max-width: var(--odsd-container);
  margin: 0 auto;
  padding: 0 clamp(1.1rem, 4vw, 3rem);
  border: 0;
  border-radius: 0;
  background: none;
}
.odsd-header.search-is-active .odsd-header__search {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity 200ms ease,
              transform 260ms cubic-bezier(0.4, 0, 0.2, 1),
              visibility 0s;
}

/* Page dim. This CANNOT live inside .odsd-header: the header sets
   backdrop-filter, which makes it a containing block for position:fixed
   descendants, so a scrim in there would only ever cover the header. Hung off
   body instead and driven by :has(), which the theme already relies on.
   z-index 90 sits under the header's 100, so the header and the band stay
   above the dim. pointer-events lets a click on the dim blur the field, which
   is what theme.js watches to close. */
body::after {
  content: "";
  position: fixed; inset: 0;
  background: rgba(14, 14, 16, 0.45);
  opacity: 0; visibility: hidden; pointer-events: none;
  z-index: var(--odsd-z-overlay);
  transition: opacity 240ms ease, visibility 0s 240ms;
}
body:has(.odsd-header.search-is-active)::after {
  opacity: 1; visibility: visible; pointer-events: auto;
  transition: opacity 240ms ease, visibility 0s;
}
@media (prefers-reduced-motion: reduce) {
  .odsd-header__search,
.odsd-header.search-is-active .odsd-header__search,
body::after { transition: none; }
/* :has() in its own rule: in one comma list it would drop the WHOLE rule in browsers without :has() (FINDINGS 2026-09-22) */
body:has(.odsd-header.search-is-active)::after { transition: none; }
}
.odsd-header__search fieldset { border: 0; margin: 0; padding: 0; }
.odsd-header__search legend { position: absolute; left: -9999px; }
.odsd-header__search .c-control-group { position: relative; }
/* Frosted rather than solid white: the band now floats over a dimmed page, so a
   plain #fff pill reads as a hard slab. 55% white over the dim composites to a
   light frosted panel that still carries dark text legibly. */
.odsd-header__search-field {
  width: 100%; padding: 0.8rem 3rem 0.8rem 1.2rem;
  border: 1px solid var(--odsd-line); border-radius: 999px;
  background: rgba(255, 255, 255, 0.94);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  backdrop-filter: blur(18px) saturate(150%);
  font-size: 0.95rem; color: var(--odsd-ink);
}
/* The band floats over whatever is behind it, which on the homepage is a dark
   hero. Legibility of the typed query wins over the glass effect here, so the
   field is nearly opaque at 94% - the backdrop blur still reads as frosted
   against the dimmed page, but text sits on an effectively solid surface.
   (55% and 78% were both tried and both made the placeholder hard to read.)
   Placeholder pinned rather than left to the theme's light grey. */
.odsd-header__search-field::placeholder { color: #55555f; opacity: 1; }
.odsd-header__search-field:focus { outline: none; border-color: var(--odsd-accent); }
.odsd-header__search-submit {
  width: 38px; height: 38px; display: grid; place-items: center;
  border: 0; border-radius: 50%; background: transparent; cursor: pointer;
}
/* core.css ships `.c-control-group .c-control-group__button, .c-control-group
   .c-control-group__field { position: relative; flex: 1 1 auto; width: 1%;
   border-radius: 0 }` at 0,2,0 - it outranks a single class no matter what order
   the files load in. Without matching that weight the submit leaves the overlay
   and flexes out to half the row, and the field loses its pill. Two classes here,
   deliberately, rather than !important. */
.odsd-header__search .c-control-group .odsd-header__search-field {
  flex: 1 1 auto; width: 100%;
  border-radius: 999px;
  padding: 0.8rem 3.4rem 0.8rem 1.2rem;
}
/* `.c-button` also contributes 26.95px of horizontal padding, which on its own
   exceeds the 38px width, so the box measured 53.9 x 38 - an oval sitting left
   of where the icon belongs. Zero the padding and pin both axes here. */
.odsd-header__search .c-control-group .odsd-header__search-submit {
  position: absolute; right: 0.4rem; top: 50%; transform: translateY(-50%);
  flex: 0 0 auto;
  width: 38px; min-width: 0; height: 38px; padding: 0;
  border-radius: 50%;
}
.odsd-header__search-submit svg { width: 19px; height: 19px; stroke: var(--odsd-ink); display: block; }
.odsd-header__search .x-search-preview { z-index: var(--odsd-z-header-popover); }

/* ---- Under-hero rating band variant (repo static .ratingbar after the hero) ---- */
.odsd-ratingbar--hero { margin: 0; }

/* ---- Categories tile labels (repo .cat-name: Poppins 600) ---- */
#mmx-image-across__six-across mmx-text[slot="heading"] {
  font-family: var(--odsd-font-display); font-weight: 600; font-size: 0.95rem;
  color: var(--odsd-ink);
}

/* ============================================================================
   ODSD FOOTER POLISH (repo .footer-col / .footer-bottom, 2026-07-09)
   Complements the earlier dark-footer skin above: quiet uppercase column
   headings, muted block links, dark newsletter input, and the repo bottom bar
   (top hairline, copyright left / payment badges right).
   ========================================================================= */
.t-site-footer .c-menu__title,
.t-site-footer .x-collapsible-content__toggle,
.t-site-footer h5.c-menu__title {
  font-family: var(--odsd-font-display); font-size: 0.85rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; color: #fff;
  margin-bottom: 1.1rem;
}
.t-site-footer .c-menu a,
.t-site-footer .x-collapsible-content__item a {
  display: block; font-size: 0.93rem; color: rgba(255,255,255,0.7);
  padding: 0.35rem 0; text-decoration: none; transition: color 0.2s;
}
.t-site-footer .c-menu a:hover,
.t-site-footer .x-collapsible-content__item a:hover { color: #fff; }

/* newsletter form, repo dark-input treatment */
.t-site-footer input[type="email"],
.t-site-footer input[type="text"] {
  background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.14);
  border-radius: 999px; color: #fff; padding: 0.7rem 1.1rem;
}
.t-site-footer input[type="email"]:focus,
.t-site-footer input[type="text"]:focus { outline: none; border-color: var(--odsd-accent); }
.t-site-footer form button[type="submit"],
.t-site-footer form .c-button {
  border-radius: 999px; background: #fff; color: var(--odsd-ink);
  border: 0; font-family: var(--odsd-font-display); font-weight: 600;
}

/* copyright bar -> repo .footer-bottom (border-top, copyright left, badges right) */
section.t-site-footer__copyright {
  max-width: var(--odsd-container);
  margin: 3rem auto 0; padding: 1.8rem clamp(1.1rem, 4vw, 3rem) 0.8rem;
  border-top: 1px solid rgba(255,255,255,0.1);
  display: flex; justify-content: space-between; align-items: center;
  gap: 0.8rem 1.2rem; flex-wrap: wrap;
}
section.t-site-footer__copyright p {
  margin: 0; font-size: 0.8125rem; color: rgba(255,255,255,0.7); flex: none;
}

/* ---- Repo PR #13 footer (cssui-global-footer.mvt rewrite 2026-07-29) ----
   Contact bar on top, then link columns + brand mark on the left, a vertical rule,
   and the newsletter signup on the right. Ports styles.css .footer-contact /
   .footer-top / .footer-links / .footer-mark / .footer-signup / .footer-meta. ---- */
footer.t-site-footer { padding: clamp(3rem, 6vw, 5rem) 0 2rem; }
.odsd-foot {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 0 clamp(1.1rem, 4vw, 3rem);
}
.odsd-foot-contact {
  display: flex; flex-wrap: wrap; gap: 0.6rem clamp(1.5rem, 4vw, 3rem);
  padding-bottom: 1.6rem; margin-bottom: 2.4rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  font-size: 0.93rem; color: rgba(255, 255, 255, 0.7);
}
.odsd-foot-contact b { color: #fff; font-weight: 600; margin-right: 0.35rem; }
.odsd-foot-contact a { color: rgba(255, 255, 255, 0.7); text-decoration: none; }
.odsd-foot-contact a:hover { color: #fff; }

.odsd-foot__top {
  display: grid; grid-template-columns: 1.55fr 1px 1fr;
  gap: clamp(1.8rem, 4vw, 3.5rem);
}
.odsd-foot__top::before {
  content: ""; grid-column: 2; background: rgba(255, 255, 255, 0.1);
}
/* FOUR columns since 2026-08-06 - the Shop column was added for the three
   departments the old flat category rail was the only link to, and at three
   columns it pushed Support onto a row of its own with a dead gap beside it. */
.odsd-foot-links {
  grid-column: 1;
  /* was repeat(4, 1fr) - the Shop column was removed 2026-08-17 (owner
     instruction; see the comment in cssui-global-footer.mvt). Restore the
     count if that column ever comes back. */
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem 1.2rem;
}
/* the mark sits under the columns, bottom-aligned, sized to the wordmark */
/* Brand mark matched to the repo's .footer-mark / .footer-est (owner request
   2026-08-17): flex column with the repo's gap and top clearance, and the
   "Since 2003" line carries the fading hairline rules either side - the same
   lockup treatment the header uses, in the footer's white-alpha palette
   (repo styles.css .footer-est / .brand--footer). */
.odsd-foot-mark {
  grid-column: 1 / -1; align-self: end; width: fit-content;
  margin-top: clamp(1.5rem, 4vw, 2.75rem);
  display: flex; flex-direction: column; gap: 0.32rem;
}
.odsd-foot-est {
  display: flex; align-items: center; gap: 0.55em;
  font-family: var(--odsd-font-display); font-size: 0.58rem; font-weight: 600;
  line-height: 1; letter-spacing: 0.42em; text-indent: 0.42em;
  text-transform: uppercase; color: rgba(255, 255, 255, 0.55);
  white-space: nowrap;
}
.odsd-foot-est::before, .odsd-foot-est::after {
  content: ""; flex: 1; height: 1px; display: block;
  background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.22));
}
.odsd-foot-est::after {
  background: linear-gradient(to left, transparent, rgba(255, 255, 255, 0.22));
}

.odsd-foot-signup { grid-column: 3; }
.odsd-foot-signup h4 {
  font-family: var(--odsd-font-display); font-size: 1rem; font-weight: 600;
  color: #fff; margin: 0 0 0.5rem; line-height: 1.35;
}
.odsd-foot-signup > p {
  font-size: 0.88rem; color: rgba(255, 255, 255, 0.7);
  margin: 0 0 1.1rem; line-height: 1.55;
}
.odsd-foot-form { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.odsd-foot-form input {
  flex: 1 1 130px; min-width: 0;
  background: rgba(255, 255, 255, 0.06); color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 999px;
  padding: 0.7rem 1.1rem; font: inherit; font-size: 0.9rem;
}
.odsd-foot-form input::placeholder { color: rgba(255, 255, 255, 0.5); }
.odsd-foot-form input:focus { outline: none; border-color: var(--odsd-accent); }
/* white, not gold - owner: no orange on the footer signup */
.odsd-foot-form button {
  flex: none; background: #fff; color: var(--odsd-ink);
  border: 0; border-radius: 999px; padding: 0.7rem 1.5rem;
  font-family: var(--odsd-font-display); font-weight: 600; font-size: 0.9rem;
  cursor: pointer; transition: opacity 0.2s var(--odsd-ease);
}
.odsd-foot-form button:hover { opacity: 0.85; }
.odsd-foot-bot { position: absolute; left: -5000px; }

.odsd-foot-meta { display: flex; flex-wrap: wrap; gap: 1.6rem; margin-top: 1.6rem; }
.odsd-foot-meta h5 {
  font-family: var(--odsd-font-display); font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; color: #fff; margin: 0 0 0.35rem;
}
.odsd-foot-meta p { margin: 0; font-size: 0.86rem; color: rgba(255, 255, 255, 0.7); }
.odsd-sa-badge {
  display: inline-flex; align-items: center; gap: 0.35rem;
  font-size: 0.86rem; color: rgba(255, 255, 255, 0.7); text-decoration: none;
}
.odsd-sa-badge:hover { color: #fff; }
.odsd-sa-badge b { color: #fff; }
.odsd-sa-badge .odsd-stars { color: var(--odsd-star); letter-spacing: 0.05em; }
/* Off-site marker (C4, 2026-10-19): this link leaves the store for Shopper Approved in a new
   tab. Escape, not a literal arrow - no non-ASCII in store files. The link's aria-label says
   it in words, so the glyph is decoration only. */
.odsd-sa-badge::after { content: "\2197"; margin-left: 0.3em; font-size: 0.85em; opacity: 0.8; }

@media (max-width: 980px) {
  .odsd-foot__top { grid-template-columns: 1fr; }
  .odsd-foot__top::before { display: none; }
  .odsd-foot-links, .odsd-foot-signup { grid-column: 1; }
}
/* Four columns need more room than three did, so drop to two a step earlier
   rather than letting every label wrap to three lines in a ~120px column. */
@media (max-width: 1080px) {
  .odsd-foot-links { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 620px) {
  .odsd-foot-links { grid-template-columns: 1fr 1fr; }
}

.odsd-foot-logo-link { display: inline-block; line-height: 0; }
/* 37px + white inversion = the repo's .brand--footer .brand-logo. The filter
   lives on the CLASS (not an img[src*=...] match) so it follows whatever logo
   the admin sets - the ReadyTheme entity made the filename changeable. */
.odsd-foot-logo-img {
  height: 37px; width: auto; display: block;
  filter: brightness(0) invert(1);
}
.odsd-foot-social { display: flex; gap: 0.6rem; margin-top: 1.3rem; }
.odsd-foot-social a {
  width: 38px; height: 38px; display: grid; place-items: center;
  border: 1px solid rgba(255,255,255,0.18); border-radius: 50%;
  color: #fff; font-size: 1rem; text-decoration: none;
  transition: background 0.2s, border-color 0.2s;
}
.odsd-foot-social a:hover { background: rgba(255,255,255,0.1); border-color: rgba(255,255,255,0.3); color: #fff; }
.odsd-foot-col h4 {
  font-family: var(--odsd-font-display); font-size: 0.85rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; color: #fff;
  margin: 0 0 1.1rem;
}
.odsd-foot-col a {
  display: block; font-size: 0.93rem; color: rgba(255,255,255,0.7);
  padding: 0.35rem 0; text-decoration: none; transition: color 0.2s;
}
.odsd-foot-col a:hover { color: #fff; }
.odsd-foot__bottom {
  margin-top: 3rem; padding-top: 1.8rem;
  border-top: 1px solid rgba(255,255,255,0.1);
  display: flex; justify-content: space-between; align-items: center;
  gap: 0.8rem 1.2rem; flex-wrap: wrap;
  font-size: 0.8125rem; color: rgba(255,255,255,0.7);
}
.odsd-foot__bottom a { color: rgba(255,255,255,0.7); }
.odsd-foot__bottom a:hover { color: #fff; }
.odsd-foot__bottom .odsd-foot-pay { margin-left: auto; }
@media (max-width: 980px) {
  .odsd-foot__grid { grid-template-columns: 1fr 1fr; }
  .odsd-foot-brand { grid-column: 1 / -1; }
}

/* ============================================================================
   ODSD SOUND ADVISOR - PRODUCT RESULTS (owner 2026-07-09: recommend real gear,
   not aisles). Cards reuse the .odsd-pgrid suite; this compacts them to fit
   the advisor panel and styles the per-pick "why" line + answer chips.
   ========================================================================= */
.odsd-adv-products { grid-template-columns: repeat(3, 1fr); gap: 1rem; margin: 1.1rem 0 0.3rem; }
.odsd-adv-products .product-info { padding: 0.9rem 1rem 1.1rem; }
.odsd-adv-products .product-name { font-size: 1rem; }
.odsd-adv-products .card-blurb { display: none; }   /* keep results scannable */
.odsd-adv-products .product-price { font-size: 1.05rem; }
.odsd-adv-prec { display: flex; flex-direction: column; }
.odsd-adv-prec .product-card { flex: 1; }
.odsd-adv-why {
  font-size: 0.78rem; font-weight: 600; line-height: 1.45;
  color: var(--odsd-accent-deep); margin: 0.55rem 0.2rem 0;
}
.odsd-adv-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.5rem; }
.odsd-adv-chip {
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.02em;
  padding: 0.28rem 0.7rem; border: 1px solid var(--odsd-line);
  border-radius: 999px; background: #fff; color: var(--odsd-ink-soft);
}
.odsd-adv-loading { animation: odsd-adv-pulse 1.2s ease infinite; }
@keyframes odsd-adv-pulse { 50% { opacity: 0.45; } }
.odsd-adv-catlink {
  font-size: 0.85rem; font-weight: 600; color: var(--odsd-ink);
  text-decoration: none;
}
.odsd-adv-catlink:hover { color: var(--odsd-accent-deep); }
@media (max-width: 860px) {
  .odsd-adv-products { grid-template-columns: 1fr; }
}

/* ============================================================================
 * ODSD CATEGORY HERO (Phase C - category pages; JS: extensions.js CAT HERO)
 * Port of the repo design site's .cat-hero: full-bleed lifestyle banner with
 * category name overlay; --light variant (dark title, no shade); transparent
 * product overlay PNGs positioned by CSS so they never crop off.
 * ========================================================================== */
.odsd-cat-hero {
	position: relative;
	height: clamp(240px, 32vw, 420px);
	overflow: hidden;
	/* full-bleed like the repo design (escape the content gutters) */
	width: 100vw;
	margin-left: calc(50% - 50vw);
}
/* The 100vw overhang guard used to be repeated here as
   `body.t-page-ctgy { overflow-x: hidden }`. Retired 2026-08-05: the global
   guard now lives on `html` (see the long note beside it), and putting it back
   on body would re-break this page's own sticky filter rail. */
.odsd-cat-hero__img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}
.odsd-cat-hero__shade {
	position: absolute;
	inset: 0;
	background: rgba(16, 16, 20, .28);
	pointer-events: none;
}
.odsd-cat-hero__title {
	position: absolute;
	inset: 0;
	display: grid;
	place-items: center;
	margin: 0;
	color: #fff;
	text-transform: uppercase;
	z-index: 1;
	font-family: "Poppins", sans-serif;
	font-weight: 700;
	line-height: 1.08;
	font-size: clamp(2.2rem, 6vw, 4.2rem);
	letter-spacing: .06em;
	text-shadow: 0 2px 26px rgba(0, 0, 0, .5);
}
.odsd-cat-hero--light .odsd-cat-hero__shade { background: none; }
.odsd-cat-hero--light .odsd-cat-hero__title {
	color: #16161a;
	text-shadow: none;
	place-items: center start;
	padding-left: clamp(1.5rem, 8vw, 7rem);
	/* wrap before reaching the product overlay on the right */
	padding-right: 42%;
}
.odsd-cat-hero__overlay {
	position: absolute;
	right: clamp(.75rem, 7vw, 7rem);
	top: 50%;
	transform: translateY(-50%);
	height: 68%;
	width: auto;
	z-index: 1;
	filter: drop-shadow(0 18px 30px rgba(0, 0, 0, .35));
}
.odsd-cat-hero__overlay--top {
	top: 0;
	transform: none;
	height: 88%;
}
@media (max-width: 720px) {
	.odsd-cat-hero__overlay { height: 48%; right: .75rem; top: 38%; }
	.odsd-cat-hero__overlay--top { height: 60%; top: 0; }
	/* keep the title clear of the product overlay: bottom-left on phones */
	.odsd-cat-hero__title,
	.odsd-cat-hero--light .odsd-cat-hero__title {
		font-size: clamp(1.4rem, 7vw, 2.2rem);
		place-items: end start;
		padding: 0 1rem .9rem 1rem;
	}
}


/* ---- named hero + intro band (owner, 2026-08-22) ---------------------------
   Final phase-1 shape after the owner's iterations: banner shows the big
   category name (the page h1) and a small folded trail top-left (below). The
   trail is the page's only breadcrumb and must stay in the markup with every
   ancestor link - the standing SEO rule; restyle it, never delete it. The
   copy renders in the white band below;
   non-bannered pages keep the visible crumb above the listing. Hero is
   shorter than the repo's because the art is production's 1640x300 strips. */
.odsd-cat-hero--named {
	height: clamp(190px, 19vw, 300px);
}
.odsd-cat-hero--named .odsd-cat-hero__shade {
	background: linear-gradient(90deg, rgba(12, 12, 16, .62) 0%, rgba(12, 12, 16, .34) 45%, rgba(12, 12, 16, .08) 100%);
}
.odsd-cat-hero--named .odsd-cat-hero__title {
	place-items: center start;
	max-width: var(--odsd-container);
	margin: 0 auto;
	padding: 0 clamp(1rem, 4vw, 2rem);
	box-sizing: border-box;
	font-weight: 300;
	letter-spacing: .16em;
	line-height: 1.1;
	font-size: clamp(1.5rem, 3.4vw, 2.7rem);
	text-wrap: balance;
	text-shadow: 0 1px 18px rgba(0, 0, 0, .45);
	/* keep the name clear of the product on the right of the strips */
	padding-right: 30%;
}
/* Banner trail: top-left of the hero, small light text - reads "... / Speakers /",
   current page in the h1, Home in the logo. Since L3-01 (2026-10-19) this is the
   page's full breadcrumb nav (.odsd-cat__crumb, ctgy.mvt BREADCRUMB) folded:
   every crumb is a link in the markup, the far ones hidden until "..." is
   pressed. z-index 2, not 1: the h1 is also z-index 1, covers the whole hero
   (inset: 0) and comes later in the DOM, so at 1 it sat over the trail and
   swallowed clicks on its links. */
.odsd-cat-hero__crumb {
	position: absolute; top: .95rem; left: 0; right: 0; z-index: 2;
	margin-bottom: 0;
	max-width: var(--odsd-container); margin: 0 auto;
	padding: 0 clamp(1rem, 4vw, 2rem);
	font-size: .82rem; line-height: 1.5; font-weight: 600;
	color: #fff;
	text-shadow: 0 1px 3px rgba(0, 0, 0, .55), 0 1px 14px rgba(0, 0, 0, .5);
}
.odsd-cat-hero__crumb a { color: #fff; text-decoration: none; }
.odsd-cat-hero__crumb a:hover { color: var(--odsd-accent); }
.odsd-cat-hero__crumb .odsd-cat__crumb-sep { color: rgba(255, 255, 255, .75); margin: 0 .3rem; }
/* the h1 directly below names the current page; its crumb stays a link in the
   markup but is not shown twice. Visually hidden, NOT display:none, so screen
   readers still hear the full trail (Codex P1 review); it reappears if a
   keyboard user tabs onto it, so focus never lands on something invisible. */
.odsd-cat-hero__crumb [aria-current="page"]:not(:focus) {
	position: absolute; width: 1px; height: 1px; overflow: hidden;
	clip-path: inset(50%); white-space: nowrap;
}
/* Fold (bannered nested categories): "..." button, then parent / current.
   Hidden crumbs are display:none, never omitted - they are still crawlable. */
.odsd-cat__crumb-dots { display: none; }
.odsd-cat__crumb--fold .odsd-cat__crumb-dots {
	display: inline;
	appearance: none; -webkit-appearance: none;
	background: none; border: 0; border-radius: 0; box-shadow: none;
	/* ~24px tap target (the glyph alone is ~10x20); the negative margin keeps
	   the trail where it was */
	margin: -2px -7px; padding: 2px 7px; min-height: 0;
	font: inherit; line-height: inherit; letter-spacing: .05em;
	color: inherit; cursor: pointer;
}
.odsd-cat__crumb--fold .odsd-cat__crumb-dots:hover { color: var(--odsd-accent); }
.odsd-cat__crumb--fold:not(.is-full) .odsd-cat__crumb-far { display: none; }
.odsd-cat__crumb--fold.is-full .odsd-cat__crumb-dsep { display: none; }
/* Top-level bannered category (owner 2026-08-22: "nothing on a top-level
   category"): Home / Current stay in the markup, visually hidden until a
   keyboard user tabs into them. */
.odsd-cat-hero__crumb--top:not(:focus-within) {
	width: 1px; height: 1px; padding: 0; overflow: hidden;
	clip-path: inset(50%); white-space: nowrap;
}
/* bring the listing up under the copy */
/* Intro band: white, same surface as the header and the rest of the page
   (owner 2026-08-22, after a charcoal trial: "go for white"). No rule, no
   Read more; the copy takes the full container width, centred. */
.odsd-cat-intro {
	background: #fff;
	color: var(--odsd-ink);
	width: 100vw;
	margin-left: calc(50% - 50vw);
	text-align: center;
	padding: .95rem 0 1rem;
}
.odsd-cat-intro + .odsd-catpage { padding-top: .4rem; }
.odsd-cat-intro .odsd-cat__copy {
	max-width: var(--odsd-container); margin: 0 auto;
	padding: 0 clamp(1rem, 4vw, 2rem);
}
.odsd-cat-intro .odsd-cat__copy p,
.odsd-cat-intro .odsd-cat__copy li {
	color: var(--odsd-ink-soft);
	font-size: .95rem; line-height: 1.65;
	max-width: none;
	margin: 0 0 .6rem;
}
.odsd-cat-intro .odsd-cat__copy p:last-child { margin-bottom: 0; }
.odsd-cat-intro .odsd-cat__copy strong,
.odsd-cat-intro .odsd-cat__copy :is(h1, h2, h3, h4) { color: var(--odsd-ink); }
.odsd-cat-intro .odsd-cat__copy :is(h1, h2, h3, h4) {
	font-family: var(--odsd-font-display);
	font-size: .82rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
	margin: 0 0 .6rem;
}
.odsd-cat-intro .odsd-cat__copy a { color: var(--odsd-accent-deep); }
@media (max-width: 720px) {
	.odsd-cat-hero--named .odsd-cat-hero__title {
		place-items: end start;
		padding: 0 1rem .9rem 1rem;
		font-size: clamp(1.3rem, 6.5vw, 2rem);
		letter-spacing: .12em;
	}
	.odsd-cat-intro { text-align: left; }
}

/* ---- PHONE FIRST SCREEN (2026-10-19; JS: extensions.js "CATEGORY FIRST
   SCREEN ON PHONES") -----------------------------------------------------
   Measured at 390px on c4690: the intro band (442px on Outdoor Speakers,
   1217px on Home Theater) plus the browse accordion (636px, current group
   pre-expanded) put the first product at 1587 / 1733 - no product on an
   844px phone screen. Phones only (<=760px); desktop renders unchanged.
   (a) The intro copy CLAMPS to three lines of body text with a real
       Read more / Read less button. Clipped by max-height, NOT removed: every
       word stays in the served HTML and in the accessibility tree (it is
       indexable catalog copy - restyle, never delete). Headings - and a
       paragraph that is only a bold title, like "Take It Outside!" - are not
       counted, so every intro shows its title plus three lines. The clamp
       height is measured by the JS (--odsd-intro-clamp); 6.3rem (a title
       line plus three .95rem x 1.65 lines) is only the first-paint estimate, gated on html.js
       so a no-JS visitor gets the full copy and no dead clip.
   (b) The browse accordion starts COLLAPSED behind one "Browse categories"
       button the JS inserts above it. Its links stay in the markup (the
       collapsed groups already use display:none the same way - crawlable),
       the current group keeps its pre-expanded subcategories for when the
       shopper opens it, and nothing is stored. No animation, so nothing for
       prefers-reduced-motion to switch off. */
.odsd-cat-intro__more,
.odsd-cat__browse-btn { display: none; }
@media (max-width: 760px) {
	html.js .odsd-cat-intro .odsd-cat__copy > .o-layout__item {
		max-height: var(--odsd-intro-clamp, 6.3rem);
		overflow: hidden;
	}
	html.js .odsd-cat-intro.is-fits .odsd-cat__copy > .o-layout__item,
	html.js .odsd-cat-intro.is-expanded .odsd-cat__copy > .o-layout__item { max-height: none; overflow: visible; }
	/* fade the last clamped line so the cut reads as "more below" */
	.odsd-cat-intro.is-clamped:not(.is-expanded) .odsd-cat__copy > .o-layout__item {
		-webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 1.4rem), transparent);
		mask-image: linear-gradient(to bottom, #000 calc(100% - 1.4rem), transparent);
	}
	.odsd-cat-intro.is-clamped .odsd-cat-intro__more {
		display: inline-flex; align-items: center;
		min-height: 44px; padding: 0;
		/* the copy sits inside an .o-layout__item gutter; the button is its
		   sibling, so it takes the same inset to line up with the text */
		margin: 0 0 0 var(--default-spacing-unit, 1rem);
		background: none; border: 0; cursor: pointer;
		font-family: var(--odsd-font-display); font-size: .9rem; font-weight: 600;
		color: var(--odsd-accent-deep);
		text-decoration: underline; text-underline-offset: 3px;
	}
	.odsd-cat-intro__more:focus-visible { outline: 2px solid var(--odsd-accent-deep); outline-offset: 2px; }
	.odsd-cat-intro.is-clamped { padding-bottom: .1rem; }

	.odsd-cat__browse-btn {
		display: flex; align-items: center; justify-content: space-between; gap: .75rem;
		width: 100%; min-height: 48px; margin: 0; padding: .75rem 0;
		background: none; border: 0;
		border-top: 1px solid var(--odsd-line); border-bottom: 1px solid var(--odsd-line);
		cursor: pointer; text-align: left;
		font-family: var(--odsd-font-display); font-size: 1rem; font-weight: 600;
		color: var(--odsd-ink);
	}
	.odsd-cat__browse-btn:focus-visible { outline: 2px solid var(--odsd-accent-deep); outline-offset: 2px; }
	/* same CSS-bar +/- as the group rows (the theme font tofu-boxes U+2212);
	   the icon span is empty, so it needs no aria-hidden (and aria-hidden would
	   trip the theme's [aria-hidden="true"]{display:none}) */
	.odsd-cat__browse-btn-icon { position: relative; flex: 0 0 auto; width: 14px; height: 14px; }
	.odsd-cat__browse-btn-icon::before,
	.odsd-cat__browse-btn-icon::after { content: ""; position: absolute; background: currentColor; }
	.odsd-cat__browse-btn-icon::before { left: 0; right: 0; top: 6px; height: 2px; }
	.odsd-cat__browse-btn-icon::after { top: 0; bottom: 0; left: 6px; width: 2px; }
	.odsd-cat__browse-btn[aria-expanded="true"] .odsd-cat__browse-btn-icon::after { display: none; }
	.odsd-cat__browse.is-collapsed { display: none; }
	.odsd-cat__browse-btn + .odsd-cat__browse { border-top: 0; }
}

/* ============================================================================
 * ODSD CTGY LIST PARITY (Phase C - category pages; JS: extensions.js LIST POLISH)
 * Repo design tokens: ink #16161a, ink-soft #6b6b70, accent #fcae46 (gold),
 * accent-deep #a76f12, line #e8e8ec, Poppins display font.
 * mmx-product-list / mmx-product-card expose CSS shadow parts; inner mmx-text
 * fonts are set by the JS injector (theme classes live inside nested shadows).
 * ========================================================================== */
mmx-product-list {
	--mmx-product-price-color: #16161a;
	--mmx-product-additional-price-color: #6b6b70;
}

/* --- product cards (repo .product-card) --- */
mmx-product-card::part(wrapper) {
	background: #fff;
	border: 1px solid #e8e8ec;
	border-radius: 16px;
	overflow: hidden;
	transition: transform .35s cubic-bezier(.2, .7, .2, 1),
		box-shadow .35s cubic-bezier(.2, .7, .2, 1), border-color .35s;
}
mmx-product-card:hover::part(wrapper) {
	transform: translateY(-5px);
	box-shadow: 0 18px 40px rgba(22, 22, 26, .12);
	border-color: transparent;
}
mmx-product-card::part(image-container) {
	background: #fff;
	padding: 8%;
}
mmx-product-card::part(name) {
	padding: 0 1.1rem;
}
mmx-product-card::part(prices) {
	padding: .2rem 1.1rem 1.2rem;
}
mmx-product-card::part(wishlist-button) {
	filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .25));
}

/* --- header row (count + view/sort selects as repo pills) --- */
mmx-product-list::part(header) {
	border-bottom: 1px solid #e8e8ec;
	padding-bottom: .9rem;
}
mmx-product-list::part(detail-total-products) {
	font-family: "Poppins", sans-serif;
	color: #6b6b70;
	font-size: .8125rem;
}
mmx-product-list::part(per-page-label),
mmx-product-list::part(sort-by-label) {
	font-family: "Poppins", sans-serif;
	font-weight: 600;
	color: #16161a;
	font-size: .85rem;
}
mmx-product-list::part(per-page-select-dropdown),
mmx-product-list::part(sort-by-select-dropdown) {
	font-family: "Poppins", sans-serif;
	border: 1px solid #e8e8ec;
	border-radius: 999px;
	background: #fff;
	padding: .55rem 2.1rem .55rem 1rem;
	cursor: pointer;
}

/* --- facets sidebar (repo .catpage-filters / .facet) --- */
mmx-product-list::part(facets) {
	accent-color: #fcae46;
}
mmx-product-list::part(facets-title) {
	font-family: "Poppins", sans-serif;
	font-weight: 700;
	color: #16161a;
}
mmx-product-list::part(facet) {
	border-top: 1px solid #e8e8ec;
	padding: .7rem 0;
}
mmx-product-list::part(facet-title) {
	font-family: "Poppins", sans-serif;
	font-weight: 600;
	color: #16161a;
}
mmx-product-list::part(facet-value) {
	font-family: "Poppins", sans-serif;
	color: #6b6b70;
	font-size: .9rem;
}
mmx-product-list::part(facet-value-count) {
	color: #9a9aa4;
}

/* --- pagination (repo pill buttons) --- */
mmx-product-list::part(pagination-page) {
	font-family: "Poppins", sans-serif;
	font-weight: 600;
}
mmx-product-list::part(pagination-page-current) {
	color: #a76f12;
}


/* build stamp: 2026-07-31 - bumped to rotate the combined-resource T= value
   and re-prime the CDN cache entry for this branch. See miva/HANDOFF.md
   'STORE INCIDENT' - the stylesheet URL carries no branch id and the response
   sends no Vary: Cookie, so one branch's build can be cached under another's URL. */

/* =========================================================================
   PDP - REPO PASS 2  (2026-08-03)
   -------------------------------------------------------------------------
   The first pass styled the individual controls correctly (full-width CTA,
   pill qty field, wish list as a quiet link). What it did not do was fix the
   ROW that wraps them, so the theme's own grid still laid qty / Add To Cart /
   Add to Wish List out side by side in one narrow column and the CTA never got
   to be full width. This pass fixes the containers, plus four elements the
   first pass never touched at all.

   Owner decisions this encodes (2026-08-03):
     - Store furniture STAYS and is restyled: wish list, delivery estimator with
       the zip changer, Manuals tab, trust badges. The page is fuller than the
       repo mockup and that is intended.
     - Per-product savings promos (`specs_short`) are rare - 0 of 14 products
       sampled carry one - so the layout is NOT designed around them. Where one
       exists it is toned down rather than left as a green billboard.
   ========================================================================= */

/* ---- buy-box column rhythm --------------------------------------------- */
/* The theme caps these at 60% width, which leaves the price and stock line
   stranded in a narrow gutter beside empty space. */
body.t-page-prod .x-product-layout-purchase__pricing,
body.t-page-prod .x-product-layout-purchase__inventory-message {
  max-width: none; width: auto;
}
/* The OPTIONS block takes the whole column too (2026-09-30, code review of the
   Miva QA fixes). Above 1200px the theme held it at 60% - 287-336px - while the
   buy column is 522-592px, so rows that fit the column did not fit their box:
   JA-14-4's four length pills (352px) are why the one-line rule below was ever
   needed, and RS670's three finishes (403px) spilled 74-116px past it.
   It is also the width the finish names are sized against (`odsd-options`
   container below): the rows themselves shrink to their content, so only this
   block has a width that means "the room available". Explicit 100% because the
   theme's 60% width would otherwise become the container's size. */
body.t-page-prod .x-product-layout-purchase__options {
  max-width: none; width: 100%;
  container: odsd-options / inline-size;
}

/* ---- brand mark + SKU (repo .pdp-eyebrow) ------------------------------ */
/* The store puts a full-size brand logo top-right of the buy box. The repo has
   a small gold category eyebrow there instead. Keeping the logo (it is real
   brand furniture) but bringing it down to eyebrow scale so it stops competing
   with the product name. */
body.t-page-prod .x-product-layout-purchase img[src*="brand"],
body.t-page-prod .x-product-layout-purchase img[src*="logo"] {
  max-height: 34px; width: auto; opacity: 0.9;
}
body.t-page-prod .x-product-layout-purchase__sku {
  font-family: var(--odsd-font-display); font-size: 0.72rem; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--odsd-ink-soft);
}

/* ---- the CTA row (repo .pdp-buy) --------------------------------------- */
/* This is the fix the first pass missed: make the wrapper a flex row so the
   quantity field stays its natural width and Add To Cart takes the rest. */
body.t-page-prod .x-product-layout-purchase__options-quantity {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 0.7rem; width: 100%; max-width: none;
}
body.t-page-prod .t-product-layout-purchase__quantity {
  flex: 0 0 auto; display: flex; align-items: center; gap: 0.5rem; margin: 0;
}
body.t-page-prod .t-product-layout-purchase__quantity .c-form-label {
  margin: 0; text-transform: uppercase; letter-spacing: 0.06em;
  font-size: 0.7rem; color: var(--odsd-ink-soft);
}
body.t-page-prod .t-product-layout-purchase__add-to-cart {
  flex: 1 1 220px; min-width: 200px;
}
body.t-page-prod .t-product-layout-purchase__add-to-cart input {
  min-height: 52px; font-size: 0.95rem; letter-spacing: 0.04em;
  text-transform: uppercase;
}
/* Wish list drops to its own line under the CTA rather than sitting beside it
   and stealing width from the button. */
body.t-page-prod .t-product-layout-purchase__add-to-wish {
  flex: 1 0 100%; text-align: left; margin-top: 0.1rem;
}
body.t-page-prod .t-product-layout-purchase__add-to-wish input {
  width: auto; min-height: 0; padding: 0; border: 0; background: none;
  font-family: var(--odsd-font-body); font-size: 0.84rem;
  color: var(--odsd-ink-soft); text-decoration: underline;
  text-underline-offset: 3px; cursor: pointer;
}
body.t-page-prod .t-product-layout-purchase__add-to-wish input:hover { color: var(--odsd-ink); }

/* ---- attribute swatches: COLOR / OPTIONS (repo .pdp-swatches) ---------- */
body.t-page-prod .x-product-layout-purchase__options .c-form-label {
  font-family: var(--odsd-font-display); font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--odsd-ink);
  margin-bottom: 0.5rem;
}
/* [!] The first version of this block guessed at `.x-product-attribute-swatch`
   and `[data-hook="attribute-swatch"]`. Neither exists. Both Color swatches and
   text options (8ohm / 70V) are the SAME control: a `.x-radio-to-button` label
   wrapping a radio input plus a `.c-form-checkbox__caption`, which holds an
   <img> for a swatch and plain text for an option. Read off the page, not
   guessed - the guessed selectors shipped once and did nothing. */
body.t-page-prod .x-radio-to-button {
  display: inline-flex; align-items: center; justify-content: center;
  margin: 0 0.5rem 0.5rem 0; padding: 0; cursor: pointer;
  border: 1px solid var(--odsd-line); border-radius: 10px; overflow: hidden;
  transition: border-color 0.15s var(--odsd-ease), box-shadow 0.15s var(--odsd-ease);
}
body.t-page-prod .x-radio-to-button:hover { border-color: var(--odsd-ink-soft); }
/* Selected state comes from the radio inside, so :has() is what reaches it. */
body.t-page-prod .x-radio-to-button:has(.c-form-checkbox__input:checked) {
  border-color: var(--odsd-ink); box-shadow: 0 0 0 1px var(--odsd-ink);
}
body.t-page-prod .x-radio-to-button .c-form-checkbox__input {
  position: absolute; opacity: 0; pointer-events: none;
}
/* Image swatch: compact tile instead of a large bordered panel. */
body.t-page-prod .x-radio-to-button .c-form-checkbox__caption img {
  display: block; width: 58px; height: 58px; object-fit: contain; padding: 4px;
}
/* Text option: repo pill. Only when there is no image inside. */
body.t-page-prod .x-radio-to-button:not(:has(img)) {
  border-radius: 999px;
}
body.t-page-prod .x-radio-to-button:not(:has(img)) .c-form-checkbox__caption {
  padding: 0.5rem 1.15rem;
  font-family: var(--odsd-font-display); font-size: 0.85rem; font-weight: 600;
  color: var(--odsd-ink);
}
/* Phones (2026-09-30): tighter pills and gaps so a row of short options -
   JA-14-4's four lengths - still fits one line at 360-430px now that rows may
   wrap (the one-line rule near the finish dots). Measured at 1rem padding: one
   line at 390px, but a 5px wrap at 375 and 360 once the spaces between the
   inline pills are counted. 0.85rem + a 0.4rem gap fit 360px. Must stay BELOW
   the base rules: same weight, so source order decides. */
@media (max-width: 560px) {
  body.t-page-prod .x-radio-to-button { margin-right: 0.4rem; }
  body.t-page-prod .x-radio-to-button:not(:has(img)) .c-form-checkbox__caption { padding-left: 0.85rem; padding-right: 0.85rem; }
}

/* ---- delivery estimate (repo "Get it Thu, Aug 6 - Tue, Aug 11") -------- */
body.t-page-prod .x-icons__container {
  font-size: 0.88rem; color: var(--odsd-ink-soft); line-height: 1.5;
}
body.t-page-prod .x-icons__container b,
body.t-page-prod .x-icons__container strong { color: var(--odsd-ink); font-weight: 700; }
/* The delivery truck art is oversized next to repo-scale type. */
body.t-page-prod .x-icons__container img,
body.t-page-prod .x-icons__container svg { max-height: 30px; width: auto; }

/* ---- trust badge images (repo .pdp-trust-chips) ------------------------ */
/* Kept per the owner decision, but scaled down and greyed so they read as a
   quiet reassurance strip rather than three pieces of clip art. */
body.t-page-prod .x-icons__container .readytheme-contentsection img {
  max-height: 34px; width: auto; opacity: 0.62;
  filter: grayscale(1);
}

/* ---- gallery thumbnails (repo .pdp-thumbs - one row, not a 3x2 grid) --- */
body.t-page-prod .thumbnails.o-layout.u-grids-4 {
  display: flex; flex-wrap: nowrap; overflow-x: auto; gap: 0.55rem;
  scrollbar-width: none;
}
body.t-page-prod .thumbnails.o-layout.u-grids-4::-webkit-scrollbar { display: none; }
/* 68 to match the square .pdp-thumb rule above: 74 wide on a 68 tall box was oblong.
   `!important` because every thumbnail also carries the theme's utility class `.tp`
   (padding-top: 1rem !important), which left the picture 50px tall inside a 68px box
   and cropped its top and bottom (Aaron, 2026-11-03). Matching that weight is the only
   way past it. */
body.t-page-prod .thumbnails.o-layout.u-grids-4 > * {
  flex: 0 0 68px; width: 68px; max-width: 68px; padding: 0 !important;
}

/* ---- per-product savings promo (`specs_short`) ------------------------- */
/* Rare - 0 of 14 sampled products have one - and hand-authored with inline
   styles, so it cannot be restyled by class alone. These overrides tone the
   green billboard down to something that sits inside the buy box instead of
   dominating it. `!important` is unavoidable against inline styles. */
body.t-page-prod .x-produtct-short-layout > div {
  max-width: none !important;
  background: var(--odsd-sand) !important;
  border: 1px solid var(--odsd-line) !important;
  box-shadow: none !important;
  border-radius: 12px !important;
  padding: 0.85rem 1rem !important;
  font-family: var(--odsd-font-body) !important;
}
body.t-page-prod .x-produtct-short-layout > div div { color: var(--odsd-ink) !important; }
body.t-page-prod .x-produtct-short-layout > div div:first-child {
  color: var(--odsd-accent-deep) !important; font-size: 0.7rem !important;
}
/* The headline savings figure: repo scale, not 30px. */
body.t-page-prod .x-produtct-short-layout > div div:nth-child(2) {
  font-family: var(--odsd-font-display) !important;
  font-size: 1.15rem !important; font-weight: 700 !important;
}
body.t-page-prod .x-produtct-short-layout > div div:nth-child(2) span {
  color: var(--odsd-accent-deep) !important;
}
body.t-page-prod .x-produtct-short-layout > div div:last-child {
  color: var(--odsd-ink-soft) !important; font-size: 0.8rem !important;
}

/* ---- PDP rebuild: repo-shaped buy column (2026-08-03) -------------------
   These classes are OURS, emitted by prod-product_display.mvt, rather than
   Miva's that we were painting over. Every Miva item inside the form is
   untouched - attributes, quantity, AddToBasket, AddToWishList, shipestimate,
   applepay - so the purchase path is still entirely Miva's. Only the wrappers
   and their order changed. */

/* repo .pdp-eyebrow */
body.t-page-prod .odsd-pdp__eyebrow-item { margin-bottom: 0.15rem; }
body.t-page-prod .odsd-pdp__eyebrow {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--odsd-accent-deep); margin: 0;
  display: flex; align-items: center; flex-wrap: wrap; gap: 0.6rem;
}
/* The derived NEW / Open Box pill beside the eyebrow (owner, 2026-08-22: the
   theme's faint grey .x-product-flag over the gallery was too easy to miss).
   Same .lp-flag--top teal pill as the category card; .lp-flag already sets
   its own size/letter-spacing so it does not inherit the eyebrow's 0.14em.
   Nudged up to the eyebrow's cap height and given the card's radius. */
body.t-page-prod .odsd-pdp__flag { position: relative; top: -0.05em; }

/* The buy column is one full-width run now, not two half-width stacks. */
body.t-page-prod .odsd-pdp__buy { display: block; width: 100%; }
body.t-page-prod .odsd-pdp__buy-main { width: 100%; max-width: none; }

/* repo .pdp-price - big price, struck original beside it, SAVE pill */
body.t-page-prod .odsd-pdp__price {
  /* `center`, NOT `baseline` (owner, 2026-08-07: "they seem like they are riding
     off a vertical bottom line") - and this is repo parity, not a departure:
     `.pdp-price` in outdoorspeakerdepot/css/styles.css is `align-items: center`.
     Baseline was fine while the row held two runs of plain text, but it lines up
     the TEXT baselines of three very differently sized things - 1.8rem price,
     1.05rem struck price, and a 0.74rem pill whose padded box then hangs below
     everyone else. Centring aligns the boxes instead, so all three sit on a
     common middle. */
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 0.55rem 0.8rem; margin: 0.6rem 0 0.9rem;
}
body.t-page-prod .odsd-pdp__price .odsd-pdp__price-now {
  /* 700, not 800 - the repo's `.pdp-price` is 700 and ours was a step heavier,
     which is part of what read as "bolder than the repo" (2026-08-05). */
  font-family: var(--odsd-font-display); font-weight: 700;
  /* 1.8rem flat, the repo's `.pdp-price`. Ours clamped up to 2.6rem, which is
     41.6px against the repo's 28.8 - half again as large (2026-08-05). */
  font-size: 1.8rem; line-height: 1;
  letter-spacing: -0.02em; color: var(--odsd-ink);
}
body.t-page-prod .odsd-pdp__price .odsd-pdp__price-was {
  font-size: 1.05rem; color: var(--odsd-ink-soft); font-weight: 500;
  /* [!] `margin: 0` is the actual fix for the row looking bottom-anchored, and it
     is not cosmetic. This span also carries Shadows'
     `.x-product-layout-purchase__product-discounts`, which was written for a
     BLOCK paragraph and ships `margin-bottom: 32px`. Measured: the struck price
     is 27.7px tall inside a 59.7px row - 27.7 + 32 - because a flex container
     sizes itself to the item's MARGIN box. Centring then centres that 60px
     margin box, which parks the visible text at the very top and left the three
     parts looking like they hung off a bottom rule. Fix the margin and the row
     collapses to the height of its tallest real content. */
  margin: 0;
}
body.t-page-prod .odsd-pdp__price .odsd-pdp__price-was .strike,
body.t-page-prod .odsd-pdp__price .strike { text-decoration: line-through; }
/* repo's SAVE badge - the small pill, not the old green block */
body.t-page-prod .odsd-pdp__save {
  display: inline-block; padding: 0.28em 0.7em; border-radius: 999px;
  background: var(--odsd-accent); color: var(--odsd-ink);
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: 0.74rem; letter-spacing: 0.04em; text-transform: uppercase;
  white-space: nowrap;
}

/* Stock line sits under the price now (repo order), quiet until it matters. */
body.t-page-prod .x-product-layout-purchase__inventory-message {
  font-size: 0.9rem; color: var(--odsd-ink-soft); margin: 0;
}
body.t-page-prod .x-product-layout-purchase__inventory-message .u-color-red { color: #b4232a; }

/* Star rating slot - Shopper Approved fills #product_just_stars when its script
   is enabled. Reserve nothing when empty so the layout does not gap. */
body.t-page-prod #product_just_stars:empty { display: none; }
body.t-page-prod .qna-total:empty { display: none; }

/* ---- PDP rebuild, part 2 (2026-08-03) -----------------------------------
   Driven by a direct side-by-side of the repo page and the store page. */

/* Repo order inside the buy column is price -> stock. The markup emits stock
   first (its mvt:if block is tangled up with the inventory/ETA logic and is not
   worth reordering by hand), so flex `order` does it without touching that
   logic. */
body.t-page-prod .odsd-pdp__buy-main { display: flex; flex-direction: column; }
body.t-page-prod .odsd-pdp__buy-main .odsd-pdp__price { order: 1; }
/* #inventory-message left this block 2026-08-21 - it now renders in the CTA row,
   under the option selectors and above the delivery estimate (c4353). */
body.t-page-prod .odsd-pdp__buy-main #product_just_stars,
body.t-page-prod .odsd-pdp__buy-main .qna-total { order: 3; }
body.t-page-prod .odsd-pdp__buy-main .x-produtct-short-layout { order: 4; }

/* Stock reads as the repo's green dot line. */
body.t-page-prod #inventory-message {
  font-size: 0.92rem; color: var(--odsd-ink); font-weight: 600; margin: 0 0 0.9rem; /* room below even when the ASM widget is absent (owner 2026-08-22) */
  padding-top: 0; padding-bottom: 0.9rem; /* padding, not margin: the theme's `nbm` class zeroes margin-bottom */
  padding-left: var(--default-spacing-unit); /* the o-layout gutter - core zeroes it */
}
/* In-stock state = the repo's `.pdp-stock`: 8px green dot, green text, and the
   same-day promise (owner 2026-08-21). The store makes that claim already - the
   Returns page says orders before 2 PM PST Mon-Fri ship the same day - so the
   suffix repeats policy, it does not invent one. Everything keys off the
   `.u-icon-check` span Miva emits for an in-stock message (server-rendered AND
   AttributeMachine-injected, both verified identical), so it survives variant
   swaps; the out-of-stock message carries `.u-icon-cross` and is left alone. */
body.t-page-prod #inventory-message:has(.u-icon-check) {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; color: #3f7d54;
}
body.t-page-prod #inventory-message:has(.u-icon-check) br { display: none; }
body.t-page-prod #inventory-message .u-icon-check {
  width: 8px; height: 8px; border-radius: 50%; background: #3f7d54; flex: none;
}
body.t-page-prod #inventory-message .u-icon-check::before { content: none; }
body.t-page-prod #inventory-message:has(.u-icon-check)::after {
  content: "- ships same day on orders before 2 PM PST"; font-weight: 500;
}
/* Variant-level out-of-stock: the delivery promise goes with it. */
body.t-page-prod .x-product-layout-purchase__cta .o-layout:has(#inventory-message .u-icon-cross) #delivery-countdown {
  display: none !important;
}

/* The brand logo duplicates the new brand eyebrow, so it goes. The SKU stays -
   real product data shoppers and staff use - just quiet. */
body.t-page-prod .x-product-layout-purchase .c-form-list__item img[src*="osd"],
body.t-page-prod .x-product-layout-purchase .c-form-list__item img[alt*="OSD"] { display: none; }
body.t-page-prod .x-product-layout-purchase__sku { color: var(--odsd-ink-soft); }

/* repo .pdp-keyfeatures - three gold-tick benefit lines under the option
   selectors, above the stock line (2026-08-21). Numbers are the repo's:
   column, .4rem gap, 1.1rem/1.2rem margins, .93rem/1.5 type, tick in
   accent-deep. The tick reuses the trust row's masked SVG so both rows draw
   the same glyph. Source field: key_features (see the template comment). */
body.t-page-prod .odsd-pdp__features-item { width: 100%; max-width: 100%; }
body.t-page-prod .odsd-pdp__features {
  list-style: none; margin: 1.1rem 0 1.2rem; padding: 0;
  display: flex; flex-direction: column; gap: 0.4rem;
}
body.t-page-prod .odsd-pdp__features li {
  position: relative; padding-left: 1.5rem; margin: 0;
  font-size: 0.93rem; line-height: 1.5; color: var(--odsd-ink);
}
body.t-page-prod .odsd-pdp__features li::before {
  content: ""; position: absolute; left: 0; top: 0.3em; width: 14px; height: 14px;
  background: var(--odsd-accent-deep);
  -webkit-mask: var(--odsd-tick) center / contain no-repeat;
  mask: var(--odsd-tick) center / contain no-repeat;
}
body.t-page-prod .odsd-pdp__features {
  --odsd-tick: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6L9 17l-5-5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/></svg>");
}

/* repo .pdp-trust - ticked reassurance list under the CTA */
body.t-page-prod .odsd-pdp__trust {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 0.5rem 1.6rem; width: 100%;
}
body.t-page-prod .odsd-pdp__trust li {
  display: flex; align-items: center; gap: 0.45rem;
  font-size: 0.88rem; color: var(--odsd-ink-soft); margin: 0;
}
body.t-page-prod .odsd-pdp__trust li::before {
  content: ""; flex: none; width: 14px; height: 14px;
  background: var(--odsd-accent-deep);
  -webkit-mask: var(--odsd-tick) center / contain no-repeat;
  mask: var(--odsd-tick) center / contain no-repeat;
}
body.t-page-prod .odsd-pdp__trust {
  --odsd-tick: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6L9 17l-5-5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/></svg>");
}
/* The ReadyTheme badge images stay in the template but not on screen - the
   ticked list above replaces them. Kept referenced so nobody loses track of
   which ReadyTheme images the PDP owns. */
body.t-page-prod .odsd-pdp__trust-legacy { display: none; }

/* ---- repo `.pdp-advice` help card (icon added 2026-08-05) ---------------
   Numbers taken straight from the repo rather than approximated: gap .9rem,
   padding 1rem 1.2rem, radius 22px (`--radius-lg`, not the 14px this used),
   icon 26px in accent-deep nudged .15rem down so it optically aligns with the
   cap height of the heading rather than its line box. */
body.t-page-prod .odsd-pdp__help {
  display: flex; align-items: flex-start; gap: 0.9rem;
  margin-top: 1.2rem; padding: 1rem 1.2rem;
  background: var(--odsd-sand); border-radius: 22px;
}
body.t-page-prod .odsd-pdp__help-icon {
  width: 26px; height: 26px; flex-shrink: 0; margin-top: 0.15rem;
  display: block; fill: none;
  stroke: var(--odsd-accent-deep); stroke-width: 1.8;
}
body.t-page-prod .odsd-pdp__help-text { min-width: 0; }
body.t-page-prod .odsd-pdp__help-title {
  font-family: var(--odsd-font-display); font-weight: 700; font-size: 0.95rem;
  color: var(--odsd-ink); margin: 0 0 0.15rem;
}
body.t-page-prod .odsd-pdp__help-body {
  font-size: 0.9rem; color: var(--odsd-ink-soft); line-height: 1.55; margin: 0;
}
body.t-page-prod .odsd-pdp__help-body a {
  color: var(--odsd-ink); font-weight: 600;
  text-decoration: underline; text-underline-offset: 3px;
}
body.t-page-prod .odsd-pdp__help-body a:hover { color: var(--odsd-accent-deep); }

/* (tab-strip rules retired 2026-08-05 - see "PDP product information" below) */

/* ---- PDP: CTA row overlap fix (2026-08-03) ------------------------------
   The Add To Cart button was painting over the quantity input. Cause is the
   Two-Class Core Rule again: core.css sizes control-group children with
   `.c-control-group .c-control-group__field { flex: 1 1 auto; width: 1% }`,
   so the quantity field collapsed to ~1% and the button, sized
   `flex: 1 1 220px`, expanded across it. Matching the weight fixes it -
   the field needs its own basis, not a percentage of nothing. */
body.t-page-prod .x-product-layout-purchase__options-quantity .c-control-group__field,
body.t-page-prod .c-control-group .t-product-layout-purchase__quantity {
  flex: 0 0 auto; width: auto; min-width: 0;
}

/* ---- repo qty stepper: [- 1 +] as one rounded control (2026-08-05) -------
   The earlier version of this block styled a pill around a bare text input -
   the store had no plus/minus at all, and the repo's `.qty` is a three-part
   control. The buttons now exist in the markup; this is the rest of it.

   Sizing follows the repo (`.qty button` 42x46, `.qty input` 46) scaled to the
   52px Add To Cart pill so the two line up. `overflow: hidden` on the wrapper
   is what lets the square buttons sit inside a 999px radius and still fill it
   corner to corner - the same trick the repo uses. */
/* `flex-direction: row` is stated, not assumed (2026-09-28, Miva QA ticket
   #1389066): theme-styles.css sets `.t-product-layout-purchase__quantity` to
   COLUMN below 480px, so on a phone the three parts stacked inside this 52px
   pill and `overflow: hidden` showed only the minus button. */
body.t-page-prod .odsd-qty {
  display: inline-flex; flex-direction: row; align-items: center;
  border: 1px solid var(--odsd-line); border-radius: 999px;
  overflow: hidden; background: #fff;
  height: 52px; padding: 0; gap: 0;
  transition: border-color 0.18s var(--odsd-ease), box-shadow 0.18s var(--odsd-ease);
}
/* Focus lives on the WRAPPER, not the input: the input has no border of its
   own, so a focus ring on it would draw a rectangle inside a pill. */
body.t-page-prod .odsd-qty:focus-within {
  border-color: var(--odsd-ink);
  box-shadow: 0 0 0 1px var(--odsd-ink);
}

body.t-page-prod .odsd-qty__step {
  flex: none;
  width: 44px; height: 100%;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 0; padding: 0; margin: 0;
  background: #fff; color: var(--odsd-ink);
  font-family: var(--odsd-font-display); font-weight: 600; font-size: 1.25rem;
  line-height: 1; cursor: pointer;
  -webkit-user-select: none; user-select: none;
  transition: background 0.15s var(--odsd-ease), color 0.15s var(--odsd-ease);
}
body.t-page-prod .odsd-qty__step:hover { background: var(--odsd-sand); }
/* A press that only changes colour reads as a dead button on a fast tap, so
   the glyph takes a small dip as well. */
body.t-page-prod .odsd-qty__step:active { background: var(--odsd-line); transform: scale(0.92); }
body.t-page-prod .odsd-qty__step:focus-visible {
  outline: 2px solid var(--odsd-accent); outline-offset: -3px;
}
/* Disabled at the floor. Kept visible rather than hidden so the control does
   not change width the moment you reach 1. */
body.t-page-prod .odsd-qty__step:disabled {
  color: var(--odsd-line); cursor: default; background: #fff; transform: none;
}

body.t-page-prod .odsd-qty .c-form-input,
body.t-page-prod .t-product-layout-purchase__quantity .c-form-input {
  flex: none;
  border: 0; border-radius: 0; box-shadow: none;
  height: 100%; width: 46px; padding: 0; background: none;
  color: var(--odsd-ink); text-align: center;
  font-family: var(--odsd-font-display); font-weight: 700; font-size: 1rem;
  -moz-appearance: textfield;
}
body.t-page-prod .odsd-qty .c-form-input:focus {
  outline: none; box-shadow: none; border: 0;
}

@media (prefers-reduced-motion: reduce) {
  body.t-page-prod .odsd-qty,
  body.t-page-prod .odsd-qty__step { transition: none; }
  body.t-page-prod .odsd-qty__step:active { transform: none; }
}

/* Stepper and CTA share one row, as they do in the repo (`.pdp-buy`).
   The control group holds THREE children - quantity, Add To Cart and Add to
   Wish List - and wraps. Giving the wish link a full-width basis is what puts
   it on its own line; `nowrap` on the group would drag it up beside the button.
   The button's floor came down from 180px because the stepper grew 26px when it
   gained its plus and minus, which was just enough to push the pair apart. */
/* The buy controls take the WHOLE column. The theme marks this group
   `u-width-6--w`, i.e. half the row at wide, with the delivery countdown in the
   other half - and it only ever looked full width because the old CTA refused
   to shrink below 180px, so the row wrapped. Once the CTA could shrink, the
   countdown slid up beside it and squeezed both. Say what we mean instead:
   full width here, countdown on the next line, which is the repo's order
   (price, stock, buy row, delivery). */
/* Above 1200px the theme pins this whole block to `width: 60%` (core.css,
   `.x-product-layout-purchase__pricing, .x-product-layout-purchase__cta`, inside
   `@media (min-width: 75em)`). That left 346px of content box, and the row
   needed 347 - the stepper, an 11px margin and the button's 200px basis. It
   wrapped by ONE PIXEL, which reads as a deliberate stacked layout rather than
   a near miss. The repo's buy row spans the column, so take the column. */
body.t-page-prod .x-product-layout-purchase__cta {
  width: 100%; max-width: 100%;
}
/* The theme's 32px LEFT-only column gutter (Miva QA ticket #1389066 Q7).
   `.o-layout--wide` normally cancels it with a -32px margin; our
   `.x-product-layout { margin: 0 auto }` above removes that, so every column
   kept a gutter on its left. Stacked (under 960px) the title and buy column sat
   59px from the left edge and 27px from the right (fixed 2026-09-28); side by
   side the GALLERY sat 32px inside the edge that the buy column and the bands
   below end on (code review, 2026-09-30). The first column never needs it -
   the gutter only separates columns - so it goes at every width; stacked, both
   lose it. */
body.t-page-prod .x-product-layout > .o-layout__item:first-child { padding-left: 0; }
/* ...and the WRAPPER round the product block goes full width, as the bands
   below already are (same review). It is the theme's `.t-expanded-block`
   (95vw, centred by left:calc(50% - 50vw) + translateX(calc(50vw - 50%))), and
   the bands escape it with `width: 100vw; margin-left: calc(50% - 50vw)`. Both
   use the same max-width + clamp padding inside, so below ~1400px - where 95vw
   rather than the container's max-width is what binds - the product block ran
   2.5vw narrower than every band on each side (24-32px at 960-1280). At 100vw
   the theme's own centring maths lands it exactly where the bands sit: gallery
   edge = band content edge = buy column edge, measured at 1920-390px. */
body.t-page-prod .t-expanded-block[data-hook="product-content"] { width: 100vw; max-width: none; }
@media (max-width: 59.999em) {
  body.t-page-prod .x-product-layout > .o-layout__item { padding-left: 0; }
}
body.t-page-prod .x-product-layout-purchase__options-quantity {
  flex: 0 0 100%; max-width: 100%;
  align-items: center; row-gap: 0.6rem;
}
/* `flex-basis: 0` rather than 200px: wrapping is decided by the hypothetical
   size, so a zero basis lets the button GROW into whatever is left instead of
   claiming 200px up front and pushing itself onto the next line. `min-width`
   still forces a wrap when the column genuinely is too narrow. */
body.t-page-prod .t-product-layout-purchase__add-to-cart {
  flex: 1 1 0%; min-width: 180px; margin-left: 0.7rem;
}
/* Phones (2026-09-28): the stepper is its full 136px again, so a 180px floor no
   longer fits beside it in a 302px column and the button dropped to its own
   row. 130px keeps the pair on one row at 390px; narrower screens still wrap. */
@media (max-width: 29.999em) {
  body.t-page-prod .t-product-layout-purchase__add-to-cart { min-width: 130px; }
}
body.t-page-prod .t-product-layout-purchase__add-to-wish {
  flex: 0 0 100%; margin-left: 0;
}
/* Match the stepper's height and easing so the pair reads as one row rather
   than two controls that happen to be adjacent. */
body.t-page-prod .t-product-layout-purchase__add-to-cart .mm-theme-button-primary {
  height: 52px;
  transition: background 0.18s var(--odsd-ease), color 0.18s var(--odsd-ease),
              box-shadow 0.18s var(--odsd-ease), transform 0.12s var(--odsd-ease);
}
body.t-page-prod .t-product-layout-purchase__add-to-cart .mm-theme-button-primary:active {
  transform: scale(0.985);
}

/* ---- savings promo, un-collapsed (2026-08-07) --------------------------
   [!] REPLACES a 2026-08-03 block that squashed this panel into a one-line
   inline-block pill and set `display: none !important` on its first and last
   children. Its stated reason was that the panel "cannot become a true inline
   SAVE pill without rewriting merchandising copy" - **and that reason expired
   at c4190**, which gave the price block a real `.odsd-pdp__save` pill fed by
   actual discount data. With a genuine pill one line above, a second
   pill-shaped thing below the stock line read as a stray duplicate: indented
   16px past every other element in the buy column, shrink-wrapped to 145px
   against their 560px, and showing one of its three lines.

   So it goes back to being what it is - a content block - and simply gets the
   buy column's alignment and the theme's type. `!important` throughout is
   unavoidable: the field is hand-authored HTML with inline styles.

   NOTE the copy this panel carries is NOT verified by anything. It is free
   text in the `specs_short` custom field, so it can claim a saving the store
   does not apply - measured on `BK-TREVOCE12-PRO`, whose panel promises
   "Save $100 Today" while the basket charges the full $799.99. That is a data
   problem, not a styling one; see ROADMAP. */
/* [!] NOT `.odsd-pdp__buy-main .x-produtct-short-layout`. Confirmed with
   CSS.getMatchedStylesForNode: that selector matches NOTHING - the panel is a
   SIBLING of `.odsd-pdp__buy-main`, not a child, so the two `order:` rules
   written against it (one here, one further up this file) have never applied to
   anything. The panel's position comes from DOM order alone. Left them in place
   rather than deleted, but do not add to them; target the class directly.

   The wrapper is an `o-layout__item`, which carries the grid gutter as
   `padding-left: 16px` (0,1,0). That is what sat the panel 16px right of the
   price, the stock line and the quantity box - measured 741 against their 725. */
body.t-page-prod .x-produtct-short-layout { padding-left: 0; }
body.t-page-prod .x-produtct-short-layout > div {
  display: block !important;
  padding: 0.8rem 1rem !important;
  border-radius: 14px !important;
}
body.t-page-prod .x-produtct-short-layout > div div:first-child {
  display: block !important; margin-bottom: 0.15rem !important;
}
body.t-page-prod .x-produtct-short-layout > div div:nth-child(2) {
  display: block !important; font-size: 1.05rem !important;
}
body.t-page-prod .x-produtct-short-layout > div div:last-child {
  display: block !important; margin-top: 0.15rem !important;
}

/* ---- delivery block: repo's single "Get it ..." line -------------------
   The ASM countdown widget stays (it is real functionality and the owner
   asked to keep store furniture) but it is stacked tight so it reads as one
   compact block rather than six loose lines. */
/* `display: flex !important` is load-bearing. ASM's stylesheet hides the
   widget (`display:none`) and its JS reveals it with an INLINE `display:block`,
   which beat the plain `flex` here - so the truck fell back to ASM's 70x60px
   float and the text dropped ~50px under it (the gap the owner saw 2026-08-21).
   The truck box is also un-floated and collapsed to its 26px image.
   Full width (the theme class is `u-width-6--w`, half the row at wide) now
   that it sits ABOVE the qty/CTA row rather than beside it. */
/* Scoped to ASM's own reveal (`style="display: block"`) - 2026-08-22. A bare
   `!important` here exposed an EMPTY widget ("FREE Delivery by ,") whenever ASM's
   data call fails; production keeps their `display:none` and shows nothing, which
   is the right failure mode. Now we only restyle what they have shown. */
body.t-page-prod #delivery-countdown[style*="block"] {
  display: flex !important; align-items: flex-start; gap: 0.7rem;
  flex: 0 0 100%; max-width: 100%; margin: 0 0 0.9rem;
}
body.t-page-prod #dc-truck { flex: none; float: none; width: auto; height: auto; line-height: 0; }
body.t-page-prod #dc-truck-img { max-height: 26px; width: auto; }
body.t-page-prod #dc-info { float: none; }
body.t-page-prod #delivery-countdown > .clear-both { display: none; }
body.t-page-prod #dc-info { font-size: 0.86rem; line-height: 1.45; }
body.t-page-prod #dc-top { font-family: var(--odsd-font-display); font-weight: 700; color: var(--odsd-ink); font-size: 0.92rem; }
body.t-page-prod #dc-bottom, body.t-page-prod #dc-ship { color: var(--odsd-ink-soft); }
body.t-page-prod #dc-details { color: var(--odsd-ink); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
/* The "More Delivery Options" panel (2026-09-28, Miva QA ticket #1389066). It is
   the vendor's element at z-index 10, and the sticky tab strip (80) painted
   across it. 85 puts it over the strip and keeps it under the mega menu (90)
   and the header (100) - inside the stack, not above it (FINDINGS). */
body.t-page-prod #asm-calc-details { z-index: var(--odsd-z-page-popover); }
/* Phones: the vendor pins it 320px wide to the LEFT of a column that starts
   97px in, so it ended at 432px on a 390px screen - prices cut off and the
   page scrolling sideways. Pin it to the column's right edge instead.
   560px, not 35em, like the rest of the phone tier (an em query moves with the
   browser's default font size; html is pinned at 16px) - code review
   2026-09-30. The inner columns go fluid in the same range: the vendor sizes
   them in pixels for a 250px content box (125+125 title, 200+50 rows), so once
   the panel narrows below 352px its close X sat on "Estimated Cost". Titles
   and prices now stop 27px short of the right edge, 6px clear of the X. */
@media (max-width: 560px) {
  body.t-page-prod #asm-calc-details {
    left: auto; right: 0; margin-left: 0;
    width: min(320px, calc(100vw - 2rem));
  }
  body.t-page-prod #asm-calc-details #asm-calc-title-left { width: 50%; }
  body.t-page-prod #asm-calc-details #asm-calc-title-right { width: calc(50% - 27px); text-align: right; }
  body.t-page-prod #asm-calc-details .asm-calc-method-top-left { width: 60%; }
  body.t-page-prod #asm-calc-details .asm-calc-method-top-right { width: calc(40% - 27px); text-align: right; }
  body.t-page-prod #asm-calc-details .asm-calc-method-bottom { width: 100%; }
}

/* ==========================================================================
   PDP product information: pinned section bar + continuous bands (2026-08-05)
   ==========================================================================
   Owner: "on the repo, scrolling pins the bar with specifications and
   descriptions to the top and cycles through each one so it looks like one
   continuous page."

   That is the repo's `.pdp-tabs` + `.pdp-section` pair, and it is NOT a tab
   widget: every band is on the page at once, the bar sticks under the site
   header, and the scroll-spy in prod-product_display.mvt marks whichever band
   the reader is currently in. Everything the store's radio tabs used to hide
   is now visible - better to read, and hidden panels are discounted by search.

   The three earlier `.x-flex-tabs` blocks in this file are gone; this is the
   only tab/band styling on the PDP now.

   `--odsd-header-h` and `--odsd-pdp-tabs-h` are published by that script from
   the real measured chrome, so `top:` and `scroll-margin-top:` follow the
   header at any breakpoint instead of being hardcoded twice. The fallbacks
   below are what the page uses for the frame or two before the script runs. */

body.t-page-prod .odsd-pdp-info {
  background: #fff;
  /* Breathing room between the buy column and the section bar, taken from the
     repo rather than guessed: its `.pdp-advice` sits inside a `.section`, whose
     `padding-block` is `--section-y: clamp(1.6rem, 3vw, 2.5rem)`. Measured on
     the repo page at 1500px: exactly 40px from the card's bottom edge to the
     top of `.pdp-tabs`. Ours was 0 there and 19px at 430px - the bar butted
     straight onto the card at desktop.

     Declared HERE rather than as padding under the product layout because that
     padding was not reaching the bar anyway (the purchase column outruns the
     image column, so the section's box ended level with the card). One value in
     one place, and it is the same clamp the repo uses, so it tracks at every
     width instead of only matching at one. */
  margin-top: clamp(1.6rem, 3vw, 2.5rem);
}

/* FULL-BLEED (owner 2026-08-24, "stretch like the repo"): the tab bar and the
   alternating band grounds were stopping at the content wrapper's edge - the
   repo runs them viewport-wide. Same 100vw escape the category hero uses;
   html{overflow-x:hidden} guards the overhang. */
body.t-page-prod .odsd-pdp-tabs,
body.t-page-prod .odsd-pdp-band,
body.t-page-prod .odsd-pdp__system,
body.t-page-prod .odsd-pdp__related {
	width: 100vw;
	margin-left: calc(50% - 50vw);
}
body.t-page-prod .odsd-pdp-tabs {
  position: sticky;
  top: var(--odsd-header-h, 84px);
  /* under the site header (z-index 100), over the bands */
  z-index: var(--odsd-z-page-float);
  background: var(--odsd-sand);
  border-bottom: 1px solid var(--odsd-line);
}
body.t-page-prod .odsd-pdp-tabs__row {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 0.65rem clamp(1.1rem, 4vw, 3rem) 0;
  display: flex; gap: 1.5rem;
  overflow-x: auto;
  scrollbar-width: none; -ms-overflow-style: none;
}
body.t-page-prod .odsd-pdp-tabs__row::-webkit-scrollbar { display: none; }
/* The open link's shoulders (the flares below) sit outside its own box and an
   overflow-x container clips them sideways. The row only needs to scroll when
   the links cannot fit, so let them show once there is room. */
@media (min-width: 760px) {
  body.t-page-prod .odsd-pdp-tabs__row { overflow: visible; }
}

body.t-page-prod .odsd-pdp-tabs__link {
  position: relative;
  padding: 0.8rem 0.9rem;
  margin-bottom: -1px;                 /* sit over the row's bottom border */
  border: 0;
  font-family: var(--odsd-font-display); font-weight: 600; font-size: 0.95rem;
  color: var(--odsd-ink-soft);
  white-space: nowrap;
  /* theme-styles.css underlines every anchor; this is a nav row, not prose */
  text-decoration: none;
  transition: color 0.18s var(--odsd-ease);
}
body.t-page-prod .odsd-pdp-tabs__link:hover { color: var(--odsd-ink); }
body.t-page-prod .odsd-pdp-tabs__link:focus-visible {
  outline: 2px solid var(--odsd-accent); outline-offset: -3px; border-radius: 6px;
}
body.t-page-prod .odsd-pdp-tabs__link.is-active {
  color: var(--odsd-ink);
  border-radius: 12px 12px 0 0;
  background-color: #fff;              /* the open folder joins the page */
  /* Gold stays a small accent (house rule): a 3px cap inset from the
     shoulders, painted as a background layer so the link needs no extra
     element - ::before and ::after are both spent on the flares. */
  background-image: linear-gradient(var(--odsd-accent), var(--odsd-accent));
  background-repeat: no-repeat;
  background-position: center top;
  background-size: calc(100% - 1.8rem) 3px;
  box-shadow: 0 -3px 9px rgba(22, 22, 26, 0.05);
}
/* The concave flare where the open link meets the page - what makes the bar
   read as one sheet with the content rather than a card floating above it.
   Drawn with a radial gradient rather than an offset box, because a box gets
   clipped by the scroll container on narrow screens. */
body.t-page-prod .odsd-pdp-tabs__link.is-active::before,
body.t-page-prod .odsd-pdp-tabs__link.is-active::after {
  content: "";
  position: absolute; top: auto; bottom: 0;
  width: 13px; height: 13px;
}
body.t-page-prod .odsd-pdp-tabs__link.is-active::before {
  left: -13px;
  background: radial-gradient(circle at top left, transparent 13px, #fff 13.5px);
}
body.t-page-prod .odsd-pdp-tabs__link.is-active::after {
  right: -13px;
  background: radial-gradient(circle at top right, transparent 13px, #fff 13.5px);
}
@media (prefers-reduced-motion: reduce) {
  body.t-page-prod .odsd-pdp-tabs__link { transition: none; }
}
@media (max-width: 760px) {
  body.t-page-prod .odsd-pdp-tabs__link { padding: 0.62rem 0.85rem; font-size: 0.88rem; }
}

/* ---- the bands ---------------------------------------------------------
   Alternating white/sand, as the repo alternates .section and
   .section--sand. nth-of-type counts only <section> children, so the <nav>
   does not shift the parity and a product missing the Specifications band
   still alternates cleanly. */
body.t-page-prod .odsd-pdp-band {
  background: #fff;
  /* land a jumped-to band below BOTH sticky bars, and above the spy's line
     (which sits 16px lower), so the link you clicked is the one that opens */
  scroll-margin-top: calc(var(--odsd-header-h, 84px) + var(--odsd-pdp-tabs-h, 53px) + 4px);
}
/* EVERY section in the run, not just `.odsd-pdp-band` (2026-08-27). Complete
   Your System and Related became <section> when Reviews and Q&A moved below
   them, so they now take their turn in the same `:nth-of-type` count as the
   bands and the three hand-written rules that used to colour the trailing pair
   are gone. `.odsd-pdp-info > section` is deliberately not class-scoped - a
   band added later alternates correctly whatever class it carries. */
body.t-page-prod .odsd-pdp-info > section { background: #fff; }
body.t-page-prod .odsd-pdp-info > section:nth-of-type(even) { background: var(--odsd-sand); }
/* An empty bundle block is `display: none` but STILL COUNTS in nth-of-type, so
   it eats a parity slot it does not occupy and everything after it comes out
   one step wrong - two sand bands meeting with no seam. Flip the tail back. */
body.t-page-prod .odsd-pdp-info > .odsd-pdp__system:not(:has(*)) ~ section:nth-of-type(even) {
  background: #fff;
}
body.t-page-prod .odsd-pdp-info > .odsd-pdp__system:not(:has(*)) ~ section:nth-of-type(odd) {
  background: var(--odsd-sand);
}
body.t-page-prod .odsd-pdp-band__inner {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: clamp(2rem, 4vw, 3rem) clamp(1.1rem, 4vw, 3rem);
}
body.t-page-prod .odsd-pdp-band__title {
  font-family: var(--odsd-font-display); font-weight: 700;
  /* repo `.pdp-h2` = `--fs-h2` */
  font-size: clamp(1.6rem, 2.6vw, 2.25rem); line-height: 1.2;
  color: var(--odsd-ink); margin: 0 0 1.3rem;
}
/* Specifications is PLAIN on its white band (owner 2026-09-19). It used to carry the
   repo's sand card (.spec-panel), which read as a third tint inside the white/sand
   alternation; the owner asked for a clean white -> sand -> white run instead. The
   band's own colour now does the separating, as it does for every other band. */
body.t-page-prod .odsd-pdp-band__body--card {
  background: none; border-radius: 0; padding: 0;
}

/* `descrip`, `specifications` and `shipping_returns` are author-written HTML
   (h3/p/ul/li). Style the tags; there are no hooks to target. */
body.t-page-prod .odsd-pdp-band__body > *:first-child { margin-top: 0; }
body.t-page-prod .odsd-pdp-band__body > *:last-child { margin-bottom: 0; }
body.t-page-prod .odsd-pdp-band__body h2,
body.t-page-prod .odsd-pdp-band__body h3 {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: 1.12rem; line-height: 1.3; color: var(--odsd-ink);
  margin: 1.6rem 0 0.5rem;
}
/* repo `.pdp-desc`: 1.05rem / 1.7 / 70ch */
body.t-page-prod .odsd-pdp-band__body p {
  font-size: 1.05rem; line-height: 1.7; color: var(--odsd-ink-soft);
  margin: 0 0 0.8rem; max-width: 70ch;
}
/* DESCRIPTION: ONE TEXT COLUMN (Aaron, 2026-11-03). Only <p> carried the 70ch measure, so
   a description's bullets, its Prop 65 warning and any text outside a <p> (62 of 406 active
   products start as bare text, 65 in a <div>) ran the full 1184px beside 738px paragraphs.
   Capped on the band rather than tag by tag, so everything shares one right edge whatever
   HTML the product was written in. Size, leading and colour are the <p>'s: `ch` is measured
   in the element's own font, so the same size gives the same 738px, and bare text then
   reads like the paragraphs around it. */
body.t-page-prod #area_description .odsd-pdp-band__body {
  max-width: 70ch; font-size: 1.05rem; line-height: 1.7; color: var(--odsd-ink-soft);
}
body.t-page-prod .odsd-pdp-band__body ul { margin: 0 0 1rem; padding-left: 1.2rem; }
/* Nested spec lists read as rows, not as deep bullet indents. */
body.t-page-prod .odsd-pdp-band__body ul ul { padding-left: 1rem; margin-top: 0.35rem; }
/* SPECIFICATIONS IN TWO COLUMNS from 760px (owner 2026-09-22: "so it takes up less vertical
   space"). Only the TOP-LEVEL list splits - some items carry their own sub-list (NERO-STREAM-ARC
   "Power Output"), and `break-inside: avoid` keeps an item and its sub-list in one column. The
   list is author-written HTML, sometimes wrapped in a <div>, so it is matched by position rather
   than by a class. Phones keep one column: at 430px a half-width column wraps every line.
   ONLY A LIST OF 6+ ITEMS, and never one inside a table (2026-09-22 audit of all 401 PDPs):
   the HTX/HTA amp pages carry short sub-lists under their own headings ("4 Ohms: 150W /
   8 Ohms: ..."), wall plates have 3-item lists, and NERO-XMAX11 has lists inside table cells -
   all of those split into scraps. Short lists read better as a single column. */
@media (min-width: 760px) {
  body.t-page-prod #area_specifications .odsd-pdp-band__body ul:not(li ul):not(table ul):has(> li:nth-child(6)) {
    column-count: 2; column-gap: clamp(2rem, 5vw, 4rem);
  }
  body.t-page-prod #area_specifications .odsd-pdp-band__body ul:not(li ul):not(table ul):has(> li:nth-child(6)) > li,
  body.t-page-prod #area_specifications .odsd-pdp-band__body ul:not(li ul):not(table ul):has(> li:nth-child(6)) > ul {
    break-inside: avoid;
  }
  /* Word-pasted specs put a sub-list BESIDE its item instead of inside it (<li>Power:</li><ul>..),
     which is invalid but common (the NERO Stream WRA listings): keep it whole and with its item. */
  body.t-page-prod #area_specifications .odsd-pdp-band__body ul:not(li ul):not(table ul):has(> li:nth-child(6)) > li:has(+ ul) {
    break-after: avoid;
  }
}
/* A pasted URL as link text (BK-T63's warranty line) has no break points; let it wrap. */
body.t-page-prod #area_specifications .odsd-pdp-band__body li { overflow-wrap: anywhere; }
/* repo `.spec-table dd` / `.feature-list li` sit at body size, not smaller */
body.t-page-prod .odsd-pdp-band__body li {
  font-size: 1.0625rem; line-height: 1.65; color: var(--odsd-ink-soft);
  margin-bottom: 0.3rem;
}
body.t-page-prod .odsd-pdp-band__body strong,
body.t-page-prod .odsd-pdp-band__body b { color: var(--odsd-ink); }
body.t-page-prod .odsd-pdp-band__body table { width: 100%; border-collapse: collapse; }
body.t-page-prod .odsd-pdp-band__body td,
body.t-page-prod .odsd-pdp-band__body th {
  padding: 0.6rem 0; border-bottom: 1px solid var(--odsd-line);
  font-size: 0.95rem; line-height: 1.5; text-align: left; vertical-align: top;
}
body.t-page-prod .odsd-pdp-band__body th { font-family: var(--odsd-font-display); font-weight: 600; color: var(--odsd-ink); }
body.t-page-prod .odsd-pdp-band__body td { color: var(--odsd-ink-soft); }

body.t-page-prod .odsd-pdp-band__note {
  font-size: 0.95rem; line-height: 1.6; color: var(--odsd-ink-soft); margin: 0;
}
body.t-page-prod .odsd-pdp-band__note a { color: var(--odsd-ink); font-weight: 600; }
/* The note is a fallback for an empty Reviews band. Shopper Approved is wired
   up again as of 2026-08-05, so on any product it has reviews for the band is
   full and the line is redundant - drop it there and keep it where the widget
   returns nothing. `#product_page` is the container SA fills. */
body.t-page-prod #shopper_review_page:has(#product_page:not(:empty)) + .odsd-pdp-band__note {
  display: none;
}
/* "Bought this? ..." (C5, 2026-10-19) always shows - it is not the fallback above. */
body.t-page-prod .odsd-rv-howto { margin-top: 1rem; font-size: 0.9rem; }

/* Shopper Approved prints "58 reviews" with a trailing divider, expecting the
   theme's jQuery to write "16 Q&A" after it. That script does not run on this
   branch, so the divider hung off the end with nothing beside it. Drop the rule
   only while the Q&A link is genuinely empty, so it comes back on its own if
   the count is ever populated. */
body.t-page-prod #product_just_stars:has(~ .qna-total:empty) .ind_cnt {
  border-right: 0 !important; padding-right: 0 !important; margin-right: 0 !important;
}

/* ---- Reviews band: design B, summary strip + card grid (owner, 2026-09-18) --
   Picked from the design canvas "PDP Reviews Designs" (board B). This restyles
   Shopper Approved's OWN markup - the widget still renders the band, we only
   dress it - so it keeps working if SA changes its data, and it keeps SA's
   paging, sort and photo lightbox.

   HEIGHT BUDGET (owner): the band may not grow past what the stock widget took
   - measured 1123px at 1440 wide, 1373px at 430. Three things hold that line:
   SA still pages three reviews at a time (`sa_products_count = 3` in prod.mvt),
   the cards sit in ONE row on desktop and in a sideways swipe row on phones,
   and review text is clamped to a fixed box. "Read more" scrolls inside that
   box instead of growing the card. Do not raise the page size or un-clamp the
   text without re-measuring the band.

   SA's own stylesheet still loads (the stars beside the price need it). Its
   rules are `#review_header ...` / `.product_review ...`; ours out-rank them by
   the `body.t-page-prod #shopper_review_page` prefix. The handful of
   `!important`s below each answer an `!important` in SA's sheet or
   theme-styles.css, or the inline min-height SA writes onto the list.

   extensions.js ("Reviews band: design B") adds what CSS cannot: gold bars in
   place of SA's histogram IMAGE, and the date / name / place split out of SA's
   single "April 11, 2025 by Chelsea (NC, United States)" line. Without the
   script the cards still render, with SA's info line in place of the footer. */
body.t-page-prod #shopper_review_page {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.6rem;
  font-family: var(--odsd-font-body); color: var(--odsd-ink);
}
body.t-page-prod #shopper_review_page #review_header,
body.t-page-prod #shopper_review_page #review_image { grid-row: 1; grid-column: 1; }

/* summary strip */
body.t-page-prod #shopper_review_page #review_header {
  display: grid; grid-template-columns: 220px minmax(0, 1fr) 250px;
  grid-template-areas: "score bars pct" "stars bars pct" "count bars pct";
  align-items: center; gap: 0.35rem 2.2rem;
  background: #fff; border: 1px solid var(--odsd-line); border-radius: 22px;
  padding: 1.6rem 2.2rem; text-align: left; line-height: 1.3;
}
body.t-page-prod #shopper_review_page #review_header div { line-height: 1.3 !important; }
body.t-page-prod #shopper_review_page #review_header .block {
  display: block; height: auto; margin: 0; width: auto; padding: 0; float: none;
}
body.t-page-prod #shopper_review_page #review_header > div[style*="clear"] { display: none; }
body.t-page-prod #shopper_review_page #review_header .overall {
  grid-area: score; align-self: end; color: var(--odsd-ink);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 0.5rem;
}
body.t-page-prod #shopper_review_page #review_header .overall .numbers {
  background: none; padding: 0; color: var(--odsd-ink);
  font-family: var(--odsd-font-display); font-size: 3.2rem; font-weight: 700;
  line-height: 1 !important; letter-spacing: -0.03em;
}
body.t-page-prod #shopper_review_page #review_header .overall .outof {
  background: none; padding: 0; color: var(--odsd-ink-soft); font-size: 0.95rem;
}
body.t-page-prod #shopper_review_page #review_header .odsd-rv-count {
  grid-area: count; align-self: start;
  font-size: var(--odsd-fs-small); color: var(--odsd-ink-soft);
}
body.t-page-prod #shopper_review_page #review_header .stars {
  grid-area: stars; align-self: start; display: flex; gap: 2px;
  font-size: 0; padding: 0.25rem 0 0;
}
body.t-page-prod #shopper_review_page #review_header .stars .desc { display: none; }
/* SA's histogram image is never shown (owner 2026-10-20, F3). Despite its name,
   gold-bars.png draws BLUE bars (rgb 1,102,154). It used to hide only once our script
   had drawn the gold bars (.has-bars), so it showed - in the same grid cell as ours -
   before the script ran or in any state that missed the class: the "blue bars over
   gold" of the store-review fallback strip. The script reads the counts from its src,
   which works while hidden; a URL it cannot read now leaves no histogram rather than
   SA's blue one.
   !important is required (F4, 2026-10-20): SA's injected stylesheet carries
   `@media (max-width: 425px) { #review_header .block { display: block !important } }`,
   and the image is `img.graph.block`, so on phones it beat a plain display:none. The
   249px image then auto-placed into the phone grid's `auto` column, shrank the pct column
   to 46px and pushed .percentage to x 394: the page was 394px wide at 390, and the blue
   bars showed on every phone. Two !importants: the more specific selector wins. */
body.t-page-prod #shopper_review_page #review_header img.graph { display: none !important; }
body.t-page-prod #shopper_review_page #review_header .percentage {
  grid-area: pct; align-self: start; text-align: left;
  border-left: 1px solid var(--odsd-line); padding: 0 0 2.6rem 2.2rem;
  font-size: 0.9rem; color: var(--odsd-ink); margin: 0;
}
body.t-page-prod #shopper_review_page #review_header .percentage br { display: none; }
body.t-page-prod #shopper_review_page #review_header .percentage .numbers {
  font-family: var(--odsd-font-display); font-size: 1.75rem; font-weight: 700; padding: 0 0 0.2rem;
}
body.t-page-prod #shopper_review_page .odsd-rv-bars {
  grid-area: bars; display: grid; gap: 0.45rem;
  border-left: 1px solid var(--odsd-line); padding-left: 2.2rem;
}
body.t-page-prod #shopper_review_page .odsd-rv-bars__row {
  display: grid; grid-template-columns: 1.1rem 13px minmax(0, 1fr) 2rem;
  align-items: center; gap: 0.5rem; font-size: var(--odsd-fs-small);
}
body.t-page-prod #shopper_review_page .odsd-rv-bars__k { font-weight: 500; text-align: right; }
body.t-page-prod #shopper_review_page .odsd-rv-bars__n { color: var(--odsd-ink-soft); text-align: right; }
body.t-page-prod #shopper_review_page .odsd-rv-bars__track {
  display: block; height: 6px; border-radius: 99px; background: var(--odsd-mist); overflow: hidden;
}
body.t-page-prod #shopper_review_page .odsd-rv-bars__track > span {
  display: block; height: 100%; border-radius: 99px; background: var(--odsd-star);
}
/* the Shopper Approved badge sits in the strip's bottom-right corner, under
   the 99% line - it is SA's link to the full review page, so it stays */
body.t-page-prod #shopper_review_page #review_image {
  align-self: end; justify-self: end; height: auto; margin: 0 2.2rem 1.4rem 0; position: relative;
}
body.t-page-prod #shopper_review_page #review_image .sa_logo {
  float: none; margin: 0 !important; width: 150px; height: auto;
}

/* stars: SA's orange SVG backgrounds, recoloured to the site's rating gold */
body.t-page-prod #shopper_review_page .stars span,
body.t-page-prod #shopper_review_page .odsd-rv-bars__star {
  display: inline-block; width: 18px; height: 18px; margin: 0;
  background: var(--odsd-star);
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 2.5l2.9 6.1 6.6.8-4.9 4.6 1.3 6.6L12 17.3l-5.9 3.3 1.3-6.6L2.5 9.4l6.6-.8z'/></svg>") center / contain no-repeat;
          mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 2.5l2.9 6.1 6.6.8-4.9 4.6 1.3 6.6L12 17.3l-5.9 3.3 1.3-6.6L2.5 9.4l6.6-.8z'/></svg>") center / contain no-repeat;
}
body.t-page-prod #shopper_review_page .stars .off { background: var(--odsd-line); }
body.t-page-prod #shopper_review_page .stars .half {
  background: linear-gradient(90deg, var(--odsd-star) 50%, var(--odsd-line) 50%);
}
body.t-page-prod #shopper_review_page .odsd-rv-bars__star { width: 13px; height: 13px; }
body.t-page-prod #shopper_review_page .product_review .stars span { width: 16px; height: 16px; }

/* the cards - one row of three; SA re-renders #product_page on every page or
   sort change, so everything here keys off SA's classes, not ours */
body.t-page-prod #shopper_review_page #product_page { grid-row: 2; }
body.t-page-prod #shopper_review_page #sa_review_section {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--odsd-gap); min-height: 0 !important;
}
body.t-page-prod #shopper_review_page .product_review {
  display: flex; flex-direction: column; gap: 0.75rem; min-width: 0;
  background: #fff; border: 1px solid var(--odsd-line); border-radius: 22px;
  padding: 1.5rem; box-sizing: border-box;
}
body.t-page-prod #shopper_review_page .product_review .main-questions { display: contents; }
body.t-page-prod #shopper_review_page .product_review .rating {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
}
body.t-page-prod #shopper_review_page .product_review .stars { float: none; padding: 0; display: flex; gap: 2px; }
body.t-page-prod #shopper_review_page .odsd-rv__date { font-size: 0.75rem; color: var(--odsd-ink-soft); }
body.t-page-prod #shopper_review_page .product_review .info { font-size: 0.75rem; color: var(--odsd-ink-soft); margin: 0 !important; }
body.t-page-prod #shopper_review_page .product_review.odsd-rv .info { display: none; }
body.t-page-prod #shopper_review_page .product_review .rheading { font-size: 1rem; font-weight: 600; padding: 0; }
body.t-page-prod #shopper_review_page .product_review .comments {
  padding: 0; font-size: 0.92rem; line-height: 1.6 !important; color: #3a3a40;
  /* 5 lines: the fixed box the height budget depends on */
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 5;
  overflow: hidden; max-height: 8em;   /* 5 x line-height 1.6 */
}
body.t-page-prod #shopper_review_page .product_review .comments.is-open {
  display: block; -webkit-line-clamp: unset; overflow-y: auto; overscroll-behavior: contain;
}
body.t-page-prod #shopper_review_page .odsd-rv__more {
  align-self: flex-start; margin-top: -0.4rem; padding: 0; border: 0; background: none;
  font: 600 0.8rem var(--odsd-font-body); color: var(--odsd-ink);
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
}
body.t-page-prod #shopper_review_page .sa_media img {
  width: 64px; height: 64px; max-width: none; object-fit: cover;
  border-radius: 10px; margin: 0; display: block;
}
body.t-page-prod #shopper_review_page .end_placeholder { display: none; }
body.t-page-prod #shopper_review_page .odsd-rv__foot {
  margin-top: auto; display: flex; align-items: center; gap: 0.65rem; padding-top: 0.3rem;
}
body.t-page-prod #shopper_review_page .odsd-rv__avatar {
  flex: 0 0 40px; height: 40px; border-radius: 50%; background: var(--odsd-mist);
  display: grid; place-items: center;
  font-family: var(--odsd-font-display); font-weight: 700; color: var(--odsd-ink-soft);
}
body.t-page-prod #shopper_review_page .odsd-rv__who { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
body.t-page-prod #shopper_review_page .odsd-rv__name { font-family: var(--odsd-font-display); font-weight: 600; font-size: 0.9rem; }
body.t-page-prod #shopper_review_page .odsd-rv__place { font-size: 0.75rem; color: var(--odsd-ink-soft); }
body.t-page-prod #shopper_review_page .product_review .images {
  float: none; margin: 0; display: flex; flex-direction: column; align-items: flex-end; gap: 0.15rem;
  font-size: 0.72rem; text-align: right;
}
body.t-page-prod #shopper_review_page .product_review .images .verified,
body.t-page-prod #shopper_review_page .product_review .images .recommend {
  float: none; background: none; padding: 0; white-space: nowrap;
}
body.t-page-prod #shopper_review_page .product_review .images .verified { color: #3f7d54; font-weight: 500; }
body.t-page-prod #shopper_review_page .product_review .images .verified::before { content: "\2713\00a0"; }
body.t-page-prod #shopper_review_page .product_review .images .recommend { color: var(--odsd-ink-soft); }

/* paging + sort, one row under the cards */
body.t-page-prod #shopper_review_page #sa_review_paging {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem;
  height: auto !important; padding: 1.5rem 0 0; text-align: left;
}
body.t-page-prod #shopper_review_page #sa_review_paging br { display: none !important; }
body.t-page-prod #shopper_review_page #sa_review_paging .sa_page {
  width: 40px !important; height: 40px; margin: 0; padding: 0; border-radius: 50%;
  border: 1px solid var(--odsd-line); background: #fff; color: var(--odsd-ink);
  font: 500 0.875rem var(--odsd-font-body); cursor: pointer;
}
body.t-page-prod #shopper_review_page #sa_review_paging .sa_page.active {
  background: var(--odsd-ink); border-color: var(--odsd-ink); color: #fff;
}
/* SA's "next" button carries the target page in its value (" > 2"), so the
   text cannot change - hide it and draw a chevron */
body.t-page-prod #shopper_review_page #sa_review_paging .sa_page.arrow {
  color: transparent; text-align: center;
  background: #fff url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2316161a' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M9 6l6 6-6 6'/></svg>") center / 16px no-repeat;
}
body.t-page-prod #shopper_review_page #sa_review_paging label {
  margin: 0 0 0 auto; font-size: var(--odsd-fs-small); color: var(--odsd-ink-soft);
}
body.t-page-prod #shopper_review_page #sa_sort {
  height: 36px; border-radius: 99px; border: 1px solid var(--odsd-line); background: #fff;
  padding: 0 0.9rem; font: 500 0.8125rem var(--odsd-font-body); color: var(--odsd-ink);
  margin: 0 !important;
}

@media (max-width: 760px) {
  body.t-page-prod #shopper_review_page #review_header {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas: "score pct" "stars pct" "count pct" "bars bars";
    gap: 0.35rem 1.2rem; padding: 1.2rem 1.2rem 3.4rem; border-radius: 18px;
  }
  /* Shopper Approved's .merchantheader has no grid-area, so it auto-placed into
     the `auto` column and its long line widened that track to 295px at 390,
     collapsing the pct column to 0 and pushing .percentage to x 352-440: the
     page grew to 441px wide (FORZA5 and 23 of 29 PDP reps, 2026-10-19). Span
     it across both columns so neither track has to hold it. */
  body.t-page-prod #shopper_review_page #review_header .merchantheader { grid-column: 1 / -1; }
  body.t-page-prod #shopper_review_page #review_header .percentage { padding: 0 0 0 1.2rem; font-size: 0.8rem; }
  body.t-page-prod #shopper_review_page #review_header .percentage .numbers { font-size: 1.4rem; }
  body.t-page-prod #shopper_review_page #review_header .overall .numbers { font-size: 2.6rem; }
  body.t-page-prod #shopper_review_page .odsd-rv-bars {
    border-left: 0; padding: 1rem 0 0; margin-top: 0.6rem; border-top: 1px solid var(--odsd-line);
  }
  body.t-page-prod #shopper_review_page #review_image { margin: 0 1.2rem 1rem 0; }
  body.t-page-prod #shopper_review_page #review_image .sa_logo { width: 130px; }
  /* one card wide, the next peeking in - three stacked cards would blow the
     height budget on a phone */
  body.t-page-prod #shopper_review_page #sa_review_section {
    grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: 86%;
    overflow-x: auto; scroll-snap-type: x mandatory; overscroll-behavior-x: contain;
    gap: 0.75rem; padding-bottom: 0.25rem;
  }
  body.t-page-prod #shopper_review_page .product_review { scroll-snap-align: start; padding: 1.2rem; }
  /* Sort shares the page-button row (4 x 40px + select fits 398px): its own line
     cost ~65px and, with Customers say open, pushed the band past the 1373px phone
     cap (measured 1393, 2026-09-18). The label stays for screen readers. */
  body.t-page-prod #shopper_review_page #sa_review_paging { flex-wrap: nowrap; }
  body.t-page-prod #shopper_review_page #sa_review_paging label {
    position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  body.t-page-prod #shopper_review_page #sa_sort { margin-left: auto !important; min-width: 0; max-width: 11rem; }
}

/* ---- Customers say (owner 2026-09-18) ---------------------------------------
   Server-rendered from the review_summary field (prod-product_display.mvt,
   #odsd-cs), written by the reviews agent. It continues the summary strip as ONE
   card: the strip gives up its bottom corners and border, this section takes
   them, joined by a hairline. Height budget (CLAUDE.md, cap 1123 / 1373): ~250px
   desktop with quotes open, and on phones the topics scroll sideways in one row
   so they never stack. Grid row 2 of #shopper_review_page; the cards move to 3. */
body.t-page-prod #shopper_review_page #odsd-cs { grid-row: 2; grid-column: 1; }
body.t-page-prod #shopper_review_page:has(#odsd-cs) #product_page { grid-row: 3; }
body.t-page-prod #shopper_review_page:has(#odsd-cs) #review_header {
  border-bottom: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0;
}
body.t-page-prod #shopper_review_page .odsd-cs {
  margin-top: -1.6rem;                      /* cancel the grid gap: one card with the strip */
  background: #fff; border: 1px solid var(--odsd-line); border-top: 0;
  border-radius: 0 0 22px 22px; padding: 0 2.2rem 1.6rem;
  display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 0 2.2rem;
}
body.t-page-prod #shopper_review_page .odsd-cs__head,
body.t-page-prod #shopper_review_page .odsd-cs__body { border-top: 1px solid var(--odsd-line); padding-top: 1.3rem; }
body.t-page-prod #shopper_review_page .odsd-cs__body { display: grid; gap: 0.9rem; min-width: 0; }
body.t-page-prod #shopper_review_page .odsd-cs__title {
  font-family: var(--odsd-font-display); font-size: 1.1rem; font-weight: 600; margin: 0 0 0.35rem;
}
body.t-page-prod #shopper_review_page .odsd-cs__disc {
  display: flex; gap: 0.4rem; align-items: flex-start; margin: 0;
  font-size: 0.75rem; line-height: 1.4; color: var(--odsd-ink-soft);
}
body.t-page-prod #shopper_review_page .odsd-cs__disc svg { flex: 0 0 14px; margin-top: 1px; color: var(--odsd-accent-deep); }
body.t-page-prod #shopper_review_page .odsd-cs__sum {
  margin: 0; max-width: 62ch; font-size: 0.95rem; line-height: 1.65; color: #3a3a40;
}
body.t-page-prod #shopper_review_page .odsd-cs__topics { display: flex; flex-wrap: wrap; gap: 0.5rem; }
body.t-page-prod #shopper_review_page .odsd-cs__chip {
  display: inline-flex; align-items: center; gap: 0.4rem; min-height: 38px; padding: 0 0.9rem;
  border: 1px solid var(--odsd-line); border-radius: 99px; background: #fff; color: var(--odsd-ink);
  font: 500 0.8125rem var(--odsd-font-body); cursor: pointer; white-space: nowrap;
}
body.t-page-prod #shopper_review_page .odsd-cs__chip svg { color: #3f7d54; flex: 0 0 14px; }
body.t-page-prod #shopper_review_page .odsd-cs__chip[aria-expanded="true"] {
  border-color: var(--odsd-ink); background: var(--odsd-sand);
}
body.t-page-prod #shopper_review_page .odsd-cs__chip:focus-visible { outline: 2px solid var(--odsd-accent-deep); outline-offset: 2px; }
body.t-page-prod #shopper_review_page .odsd-cs__n { color: var(--odsd-ink-soft); }
body.t-page-prod #shopper_review_page .odsd-cs__quotes {
  background: var(--odsd-sand); border-radius: 14px; padding: 0.9rem 1.1rem;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.1rem;
}
body.t-page-prod #shopper_review_page .odsd-cs__quotes[hidden] { display: none; }
body.t-page-prod #shopper_review_page .odsd-cs__quote { margin: 0; }
body.t-page-prod #shopper_review_page .odsd-cs__quote blockquote {
  margin: 0; font-size: 0.875rem; line-height: 1.5; color: var(--odsd-ink);
}
body.t-page-prod #shopper_review_page .odsd-cs__quote figcaption { font-size: 0.75rem; color: var(--odsd-ink-soft); }
body.t-page-prod #shopper_review_page .odsd-cs__none { margin: 0; font-size: 0.85rem; color: var(--odsd-ink-soft); }

@media (max-width: 760px) {
  body.t-page-prod #shopper_review_page .odsd-cs {
    grid-template-columns: minmax(0, 1fr); padding: 0 1.2rem 1.2rem; border-radius: 0 0 18px 18px;
  }
  body.t-page-prod #shopper_review_page .odsd-cs__body { border-top: 0; padding-top: 0.6rem; }
  body.t-page-prod #shopper_review_page .odsd-cs__head { padding-top: 1rem; }
  /* one row that scrolls sideways - stacked topics would break the phone height budget */
  body.t-page-prod #shopper_review_page .odsd-cs__topics {
    flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain;
    margin: 0 -1.2rem; padding: 0.1rem 1.2rem 0.3rem;
  }
  body.t-page-prod #shopper_review_page .odsd-cs__chip { min-height: 44px; }
  body.t-page-prod #shopper_review_page .odsd-cs__quotes { grid-template-columns: minmax(0, 1fr); gap: 0.7rem; }
}

/* SKU row at the head of the Specifications card (moved off the buy box,
   owner 2026-08-05). A label/value pair on one line, keyed to the same rhythm
   as the spec bullets under it, with a hairline separating it from them.
   max-content, not a fixed column (Aaron, 2026-11-03): SKU is the only row, and
   170px left a 190px hole between the label and its value. */
body.t-page-prod .odsd-spec-meta {
  display: grid; grid-template-columns: max-content 1fr;
  gap: 0 1.2rem; margin: 0 0 0.9rem; padding: 0 0 0.9rem;
  border-bottom: 1px solid var(--odsd-line);
}
body.t-page-prod .odsd-spec-meta dt {
  font-family: var(--odsd-font-display); font-weight: 600;
  font-size: 0.92rem; color: var(--odsd-ink); margin: 0;
}
body.t-page-prod .odsd-spec-meta dd {
  margin: 0; font-size: 0.95rem; line-height: 1.6; color: var(--odsd-ink-soft);
}
/* The card has no other content on products with an empty `specifications`
   field, so drop the rule that would otherwise hang under nothing. */
body.t-page-prod .odsd-spec-meta:last-child {
  margin-bottom: 0; padding-bottom: 0; border-bottom: 0;
}

/* The legacy id is Shopper Approved's lookup key, not content. It has to stay
   in a RENDERED element - innerText returns nothing from display:none - so it
   keeps the theme's white-on-white trick and simply takes up no room. */
body.t-page-prod .odsd-pdp__legacy-id {
  margin: 0; padding: 0; height: 0; overflow: hidden;
}

/* The Q&A widget is the store's own module and it lays out at ~480px minimum -
   inside a 389px column on a 430px screen that overflowed the band by 73px and
   the viewport by 55px. Nothing in CSS declares that width; it comes from the
   module's own content, so contain it rather than hunt it. Scoped to this band
   so the blanket max-width cannot reach anything else. */
body.t-page-prod #area_qanda #qna-questions,
body.t-page-prod #area_qanda #qna-questions * {
  max-width: 100%;
}
body.t-page-prod #area_qanda {
  overflow-wrap: anywhere;
}

/* "Have a question?" (2026-09-18). A <label> for the #show_qna_form checkbox -
   theme-styles.css `#qna_form input:checked ~ #pq_form` does the opening, no
   script. The checkbox is visually hidden rather than display:none so Tab
   reaches it and Space toggles it; its focus ring is drawn on the button. */
#area_qanda .qna-ask__check {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; opacity: 0;
}
#area_qanda #qna_ask_toggle { cursor: pointer; }
#area_qanda:has(.qna-ask__check:focus-visible) #qna_ask_toggle {
  outline: 2px solid var(--odsd-accent-deep); outline-offset: 3px;
}
/* ---- Q&A question form (2026-10-17, Miva experience audit A2: "a little wonky
   on mobile"). It sat indented in a narrow centred column with a square orange
   theme button and a browser-default checkbox. Now: left-aligned in the band,
   Name + Email side by side where there is room, the site's black pill button.
   Inputs keep the theme's box, the same as checkout and account forms. Markup:
   templates\qna_form_template.mvt. */
#area_qanda #pq_form h4 {
  font-family: var(--odsd-font-display); font-size: 1.1rem; font-weight: 600;
  color: var(--odsd-ink); margin: 0 0 1rem;
}
#area_qanda .odsd-qna-form { max-width: 640px; margin: 0 0 1.75rem; }
#area_qanda .odsd-qna-form__list {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;
  margin: 0; padding: 0; list-style: none;
}
#area_qanda .odsd-qna-form__list > li { grid-column: 1 / -1; margin: 0; padding: 0; }
#area_qanda .odsd-qna-form__list > li.odsd-qna-form__half { grid-column: auto; }
#area_qanda .odsd-qna-form .c-form-label {
  display: block; margin: 0 0 0.35rem;
  font-size: 0.82rem; font-weight: 600; color: var(--odsd-ink);
}
#area_qanda .odsd-qna-form .c-form-input { width: 100%; }
#area_qanda .odsd-qna-form textarea.c-form-input { min-height: 7.5em; resize: vertical; }
#area_qanda .odsd-qna-form__check { display: flex; align-items: center; gap: 0.6rem; }
#area_qanda .odsd-qna-form__check input {
  width: 18px; height: 18px; margin: 0; flex: none; accent-color: var(--odsd-ink);
}
#area_qanda .odsd-qna-form__check label { font-size: 0.92rem; color: var(--odsd-ink); margin: 0; }
#area_qanda .odsd-qna-form__submit {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 48px; padding: 0 2.2rem; border: 0; border-radius: 999px;
  background: var(--odsd-ink); color: #fff; cursor: pointer;
  font-family: var(--odsd-font-display); font-weight: 600; font-size: 0.95rem;
}
#area_qanda .odsd-qna-form__submit:hover { background: #000; }
#area_qanda .odsd-qna-form__submit:focus-visible { outline: 2px solid var(--odsd-accent-deep); outline-offset: 3px; }
/* the thank-you and the error now read as messages, not stray text */
#area_qanda .odsd-qna-msg {
  max-width: 640px; margin: 0 0 1.25rem; padding: 0.85rem 1rem;
  border-radius: 10px; font-size: 0.95rem; line-height: 1.45;
}
#area_qanda .odsd-qna-msg--ok { background: #eef6ef; border: 1px solid #cfe5d3; color: #1f5f2c; }
#area_qanda .odsd-qna-msg--error { background: #fdf0ef; border: 1px solid #f2cfcb; color: #8a1f17; }
@media (max-width: 560px) {
  #area_qanda .odsd-qna-form__list { grid-template-columns: 1fr; }
  #area_qanda .odsd-qna-form__submit { width: 100%; }
}
/* The band head, same audit item. theme-styles.css centres the count
   (.o-layout--align-center), the form (#qna_form flex centre, #pq_form 40em)
   and the sort box (.qna-sort margin auto), while the title and the questions
   sit on the band's left edge - five things started at five different x
   positions. One left edge for all of it; the count and the ask button share
   a row. .odsd-qna-head is set in qna_prod_questions_template.mvt. */
#area_qanda .odsd-qna-head {
  flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: flex-start;
  gap: 0.75rem 1.25rem; margin: 0 0 1.25rem;
}
#area_qanda #qna_ask_toggle {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: 0 1.4rem; margin: 0; border-radius: 999px;
  font-family: var(--odsd-font-display); font-size: 0.9rem; font-weight: 600;
}
#area_qanda #qna_form { justify-content: flex-start; }
#area_qanda #pq_form { width: 100%; max-width: 640px; margin: 0 0 0.5rem; }
#area_qanda .qna-sort { width: auto; max-width: none; margin: 0 0 1.25rem; }
#area_qanda .qna-sort select {
  min-height: 40px; padding: 0 0.8rem; border: 1px solid var(--odsd-line); border-radius: 8px;
  background: #fff; color: var(--odsd-ink); font: inherit; font-size: 0.9rem;
}
#area_qanda #qna_ask_toggle .qna-ask__close { display: none; }
/* :has() in its own rule: in one comma list it would drop the WHOLE rule in browsers without :has() (FINDINGS 2026-09-22) */
#area_qanda:has(.qna-ask__check:checked) #qna_ask_toggle .qna-ask__open { display: none; }
#area_qanda:has(.qna-ask__check:checked) #qna_ask_toggle .qna-ask__close { display: inline; }

/* Manuals: the two PDF buttons, side by side instead of stacked and centred. */
body.t-page-prod .odsd-pdp-band__docs { display: flex; flex-wrap: wrap; gap: 0.9rem; }
body.t-page-prod .odsd-pdp-doc {
  display: inline-flex; align-items: center; gap: 0.6rem;
  padding: 0.75rem 1.2rem; border: 1px solid var(--odsd-line); border-radius: 999px;
  background: #fff; color: var(--odsd-ink); text-decoration: none;
  font-family: var(--odsd-font-display); font-weight: 600; font-size: 0.9rem;
}
body.t-page-prod .odsd-pdp-doc:hover { border-color: var(--odsd-ink); }
body.t-page-prod .odsd-pdp-doc img { height: 26px; width: auto; margin: 0; }

/* ---- "Complete your system" (Frequently Bought Together) ----------------
   Same Template Trap as Related Products, and it had been invisible for the
   whole port: the band renders 340px of real merchandising - heading, product
   row, shopping list - inside `.t-defer-object`, which theme-styles.css leaves
   at `opacity: 0; visibility: hidden` until its JS adds `--loaded`. Branch 16
   ships no jQuery and no scripts.min.js, so `--loaded` never arrives and the
   band was a 388px white hole between Q&A and Related.

   Scoped to the PDP on purpose. Deferred blocks elsewhere may be hidden for
   reasons of their own; this one is not, and a shopper cannot buy what a
   stylesheet is hiding. */
body.t-page-prod .t-defer-object {
  opacity: 1; visibility: visible; overflow: initial;
}

/* Give it the same band treatment as the sections above so it reads as part of
   the same page rather than a stray theme block: sand ground between the white
   Q&A and white Related bands, and the display face on its heading. */
body.t-page-prod .odsd-pdp__system {
  /* no `background` - the run alternates it (see `.odsd-pdp-info > section`) */
  padding: clamp(1.6rem, 3vw, 2.4rem) 0;
}

/* ---- the trailing blocks used to need hand-written colours ---------------
   They no longer do. Three rules lived here that coloured the bundle and
   Related blocks off whichever band happened to precede them, because as
   <div>s they were invisible to `:nth-of-type` and could not take their turn.
   They are <section>s since 2026-08-27 and the single alternation rule up by
   `.odsd-pdp-band` covers them, Reviews and Q&A included.

   What survives is the emptiness test. `prod_freq_bought_together` removes
   itself when a product has no bundle list, leaving `.odsd-pdp__system` as an
   empty 77px sand strip; `:has(*)` is "the bundle actually rendered something",
   since the wrapper is emitted either way. Hiding it is here, and the parity
   flip that a hidden-but-counted section forces is up with the base rule. */
body.t-page-prod .odsd-pdp__system:not(:has(*)) { display: none; }

/* The content inside this band was hard-left and ~97px out of line with every
   other band (measured on a FORZA page: this band's text started at x=54, the
   Related Products heading right below it at x=151). Two causes, both the
   theme's full-bleed hack applied a SECOND time inside a band that had already
   been broken out:

     .t-expanded-block { left: calc(50% - 50vw); width: 95vw;
                         transform: translateX(calc(50vw - 50%)) }

   Nested inside our band that resolves against the band, not the viewport, so
   it shoves the content left and leaves it uncentred. And because the sand box
   is the full band width while the content sat far left, the band read as badly
   shaded too - a big dead sand area down the right. Fixing the alignment fixes
   the shading; there was never a colour problem.

   Neutralise the inner breakout, then give the content the SAME container as
   `.odsd-pdp-band__inner` and `.odsd-related` so all three line up. */
body.t-page-prod .odsd-pdp__system .t-expanded-block {
  position: static;
  left: auto; width: 100%; max-width: none;
  transform: none; margin: 0; padding: 0;
}
body.t-page-prod .odsd-pdp__system .t-related-product-list,
body.t-page-prod .odsd-pdp__system .t-defer-object {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: 0 clamp(1.1rem, 4vw, 3rem);
}
/* The product thumbnails are bare photos, and product photos are shot on white -
   so on the sand ground each one read as a stray white rectangle. That is the
   other half of "the background shading is off"; the colour was never wrong.

   The repo frames the same element rather than hiding it: `.bundle-media` is a
   square tile with a hairline border, a small radius, a white ground and the
   art `object-fit: contain` inside padding. Same treatment here, on the theme's
   own `.__image-link`, so the white becomes deliberate instead of accidental. */
body.t-page-prod .odsd-pdp__system .__image-link {
  flex: 0 0 auto;
  width: 104px; aspect-ratio: 1 / 1;
  align-items: center; justify-content: center;
  border: 1px solid var(--odsd-line); border-radius: 12px;
  background: #fff; overflow: hidden;
}
body.t-page-prod .odsd-pdp__system .__image-link img {
  display: block; width: 100%; height: 100%;
  object-fit: contain; padding: 8%; margin: 0;
}
body.t-page-prod .odsd-pdp__system .__divider {
  color: var(--odsd-ink-soft); flex: none;
}

body.t-page-prod .odsd-pdp__system .c-heading-delta {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: clamp(1.6rem, 2.6vw, 2.25rem); line-height: 1.2;
  color: var(--odsd-ink); margin: 0 0 1.2rem;
}

/* ---- Related products band (repo "Related products" grid) ---------------
   The markup is rendered directly now (see prod-related_products.mvt for why
   the theme's deferred + slick version showed nothing on this branch). What is
   left is a plain list to lay out. It closes the page, so it takes the same
   anchor offset as the bands above. */
body.t-page-prod .odsd-pdp__related {
  scroll-margin-top: calc(var(--odsd-header-h, 84px) + var(--odsd-pdp-tabs-h, 53px) + 4px);
}
body.t-page-prod .odsd-related {
  max-width: var(--odsd-container); margin: 0 auto;
  padding: clamp(2rem, 4vw, 3rem) clamp(1.1rem, 4vw, 3rem);
}
body.t-page-prod .odsd-related .t-related-product-list__heading {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: clamp(1.6rem, 2.6vw, 2.25rem); line-height: 1.2;
  color: var(--odsd-ink); margin: 0 0 1.3rem; text-align: left;
}
/* ONE SCROLLING ROW, not a wrapping grid (Aaron, 2026-11-03: "if more then 5 are chosen
   it creates another row"; 78 of 406 active products have more than 5). Every item stays
   in the served HTML, so this costs no crawlable link. Five fit at full width; 200px is
   the floor the old grid's minmax() used, so narrower windows show fewer and scroll. The
   track and arrows under it are added by extensions.js ("PDP Related Products"). */
body.t-page-prod .odsd-related__grid {
  --odsd-related-gap: clamp(1.6rem, 3vw, 2.6rem);
  display: flex; flex-wrap: nowrap; gap: var(--odsd-related-gap);
  overflow-x: auto; overflow-y: hidden; scroll-snap-type: x proximity;
  scrollbar-width: none;      /* track + arrows are the indicator */
  /* theme-styles.css:1978 pins this list to `height: 690px` - a carousel-era
     number, and the source of both symptoms here: the fixed height made every
     card stretch to 690px (a grid's default `align-content: normal` fills the
     slack), and once the cards were their own size it left ~370px of white
     under them. Released, and the rows pinned to their own height so nothing
     stretches again. Found with CSS.getMatchedStylesForNode - grep for
     "690px" in our own file first returned only my comments about it. */
  height: auto;
  align-items: flex-start;
}
body.t-page-prod .odsd-related__grid::-webkit-scrollbar { display: none; }
/* overflow-y: hidden above would clip a focus ring drawn outside the link. */
body.t-page-prod .odsd-related__grid .x-product-list__link:focus-visible { outline-offset: -2px; }
/* The shared rule hides the arrows under 720px on the assumption that narrow means touch.
   A narrow window with a mouse has no swipe, and this row hides its scrollbar, so nothing
   else reaches the later cards (Codex review, 2026-11-03). */
@media (max-width: 720px) and (hover: hover) and (pointer: fine) {
  body.t-page-prod .odsd-related .odsd-scroller-arrows { display: flex; }
}
/* NO CARD on this band (owner, 2026-08-27, from the Audac reference): each item
   is a bare image tile with the gold eyebrow and the name under it. The border,
   radius, white ground and hover shadow that used to make it a card are gone;
   what separates the items now is the grid gutter and the tinted image tile.
   The price line went with the card - see prod-related_products.mvt. */
body.t-page-prod .odsd-related__grid .x-product-list__item {
  margin: 0; padding: 0;
  border: 0; border-radius: 0; background: none; overflow: visible;
}
/* The theme sizes this list for a slick carousel of fixed-width slides, so the
   card contents come out WIDER than a grid track: measured a 330px link inside
   a 216px cell, clipped by the card's own overflow. The chain is
   `.x-product-list__picture { min-height: 310px !important }` plus figure
   padding - and an `aspect-ratio` on top of a forced 310px height gives a
   310px WIDTH. Every box below is pinned to the track instead.
   `min-height` needs `!important` because the theme's is `!important`; that is
   the one case where matching the weight is not enough. */
/* The theme makes both the item and the <figure> flex containers. Inside a
   grid track that stretches its items, the link became a flex child stretched
   to the row height and the cards came out 690px tall with 350px of white
   under the price. Plain blocks here; the only flex left is the caption. */
body.t-page-prod .odsd-related__grid .x-product-list__item {
  min-width: 0; display: block;
  flex: 0 0 max(200px, calc((100% - 4 * var(--odsd-related-gap)) / 5));
  scroll-snap-align: start;
}
body.t-page-prod .odsd-related__grid .x-product-list__link {
  text-decoration: none; display: block; width: 100%;
}
body.t-page-prod .odsd-related__grid .x-product-list__link:hover {
  box-shadow: none; transform: none; background: none;
}
body.t-page-prod .odsd-related__grid .x-product-list__figure {
  margin: 0; padding: 0; width: 100%; background: none; display: block;
}
body.t-page-prod .odsd-related__grid .x-product-list__picture {
  display: block; width: 100%; padding: 0;
  min-height: 0 !important;
  aspect-ratio: 1 / 1;
  /* WHITE, not --odsd-sand. The card used to sit on white, so a tinted tile
     inside it read as a deliberate frame; with the card gone the tint has
     nothing around it and every product photo - all shot on white - showed as a
     white square inside a sand border. Same trap as the bundle band above, and
     it looks like a mistake rather than a treatment. White here means the photo
     simply floats on the white band, and on the sand alternate (see the
     nth-of-type rules further up) it reads as a clean rounded tile instead. */
  background: #fff; overflow: hidden;
  border-radius: 12px;
}
body.t-page-prod .odsd-related__grid .x-product-list__image {
  width: 100%; height: 100%; max-height: none; object-fit: contain;
  display: block; padding: 0.5rem; margin: 0;
  transition: transform 0.3s var(--odsd-ease);
}
/* With the card gone there is no border to light up on hover, so the tile
   answers instead: the art lifts inside its own overflow and the name darkens.
   Neither moves the layout. */
body.t-page-prod .odsd-related__grid .x-product-list__link:hover .x-product-list__image {
  transform: scale(1.04);
}
body.t-page-prod .odsd-related__grid .x-product-list__link:hover .x-product-list__name {
  color: var(--odsd-accent-deep);
}
/* Centred here and ONLY here (owner, 2026-08-27) - every other product card on
   the store is left-aligned. With no card edge for the type to line up against,
   left-alignment left the short eyebrow floating; the Audac reference centres
   it and that is what was chosen. The theme's own `u-text-center` on the item
   already wants this - we were overriding it. */
body.t-page-prod .odsd-related__grid .x-product-list__figure-caption {
  display: flex; flex-direction: column; gap: 0.2rem;
  width: 100%; padding: 0.85rem 0.25rem 0; text-align: center;
}
/* Keep the two-line clamp from line 778 - do NOT set `overflow: visible` here.
   I did, and the clamped third and fourth lines spilled straight over the price
   underneath: a clamp hides the overflow, it does not shorten the text. Two
   lines plus a full price is the repo card. */
/* The `order:` juggling that used to live here went with the price - the DOM is
   eyebrow then name, which is the order we want. Keep the two-line clamp from
   line 778: a clamp hides overflow, it does not shorten text, so dropping it
   spills line three over whatever sits below. */
body.t-page-prod .odsd-related__grid .x-product-list__name {
  padding: 0; margin: 0;
  font-size: 0.92rem; line-height: 1.4; color: var(--odsd-ink);
  transition: color 0.2s var(--odsd-ease);
}
body.t-page-prod .odsd-related__grid .odsd-plist-line {
  margin: 0 0 0.1rem;
}


/* ============================================================================
 * PATR - missing-attributes page (2026-08-17)
 * The card shell only. Everything inside it (option pills, finish dots, pill
 * quantity stepper, gold Add to Cart) comes from the body.t-page-prod rules
 * above - patr.mvt deliberately carries that class next to t-page-patr.
 * ========================================================================== */
body.t-page-patr .odsd-patr {
  background: #fff; border: 1px solid var(--odsd-line); border-radius: 14px;
  padding: 1.8rem 1.9rem 2rem; margin: 2rem 0 3rem;
}
/* The card's column (2026-09-30). patr.mvt gives it the theme's
   `u-width-8--m u-width-6--l`: two-thirds of the page on tablets, HALF on
   desktop. With the 220px image beside it, the card body was 137px at 960px,
   169px at 1024 and 297px at 1280 - too narrow for three finish choices, which
   ran out of the card (code review of the Miva QA fixes). Up to 860px instead,
   still centred by the section's o-layout--justify-center: the body is then
   ~550px, and the options fit on one line. */
body.t-page-patr .o-layout--justify-center > .o-layout__item.u-width-6--l {
  flex-basis: 100%; max-width: 860px;
}
body.t-page-patr .odsd-patr__title {
  font-family: var(--odsd-font-display); font-weight: 700;
  font-size: clamp(1.5rem, 2.4vw, 2rem); line-height: 1.1;
  letter-spacing: -0.02em; color: var(--odsd-ink); margin: 0 0 0.4rem;
}
body.t-page-patr .odsd-patr__lead {
  color: var(--odsd-ink-soft); font-size: 0.95rem; margin: 0 0 1.4rem;
}
body.t-page-patr .odsd-patr .x-product-layout-purchase__options { margin-bottom: 1.4rem; }
body.t-page-patr .odsd-patr .x-product-layout-purchase__options-quantity {
  display: flex; align-items: center; gap: 1rem;
}
body.t-page-patr .odsd-patr .t-product-layout-purchase__add-to-cart { flex: 1 1 auto; }
body.t-page-patr .odsd-patr .t-product-layout-purchase__add-to-cart input { width: 100%; }

/* PATR: image rail + subtitle (2026-08-17). Image left, content right; stacks
   on small screens. The figure only renders when a main image record exists. */
body.t-page-patr .odsd-patr__grid { display: flex; gap: 1.8rem; align-items: flex-start; }
body.t-page-patr .odsd-patr__media {
  flex: 0 0 220px; margin: 0; border: 1px solid var(--odsd-line);
  border-radius: 10px; background: #fff; padding: 0.6rem;
}
body.t-page-patr .odsd-patr__media img {
  width: 100%; aspect-ratio: 1 / 1; height: auto; object-fit: contain; display: block;
}
body.t-page-patr .odsd-patr__body { flex: 1 1 auto; min-width: 0; }
body.t-page-patr .odsd-patr__sub {
  color: var(--odsd-ink-soft); font-size: 0.95rem; line-height: 1.5; margin: 0 0 0.9rem;
}
body.t-page-patr .odsd-patr__lead { margin-bottom: 1.2rem; }
@media (max-width: 700px) {
  body.t-page-patr .odsd-patr__grid { flex-direction: column; }
  body.t-page-patr .odsd-patr__media { flex-basis: auto; width: 100%; max-width: 260px; }
}

/* Select-type ATTRIBUTE dropdowns on PDP + PATR (2026-08-17). The Invisible
   Select trap: the theme wraps them in `.c-form-select u-invisible` and hides
   every native select site-wide, waiting for custom-select JS branch 16 does
   not ship. Exactly TWO active products have select-type attributes (both
   prompt "Color", both required): OSD-Tomato renders one real option, and
   OSD-Pimiento has ZERO options - that one is unbuyable by DATA, not by CSS
   (owner action in ROADMAP). Same unhide pattern as the t-page-aced block. */
body.t-page-prod .x-product-layout-purchase__options-attribute .c-form-select {
  display: block; position: relative; width: auto; min-width: 220px; margin: 0; padding: 0;
  visibility: visible;
}
body.t-page-prod .x-product-layout-purchase__options-attribute .c-form-select::before,
body.t-page-prod .x-product-layout-purchase__options-attribute .c-form-select::after { display: none; content: none; }
body.t-page-prod .x-product-layout-purchase__options-attribute .c-form-select__dropdown {
  display: block; width: 100%; height: auto; opacity: 1;
  -webkit-appearance: auto; appearance: auto;
  font: inherit; font-size: 0.95rem; padding: 0.55rem 0.6rem;
  color: var(--odsd-ink); border: 1px solid var(--odsd-line);
  border-radius: 8px; background: #fff; box-shadow: none;
}

/* ============================================================================
 * SOUND ADVISOR - curtain + fixed card shape + small card titles (2026-08-17)
 * ========================================================================== */
/* FIXED SHAPE: the shell keeps one size in every state (ask / thinking /
   results). Anything taller scrolls INSIDE the answer pane. Desktop only -
   under 900px the band stacks and flows naturally. */
@media (min-width: 901px) {
  /* 490px = the 2026-08-17 fixed 700 minus 30% (owner 2026-08-24). Answers
     scroll inside the shape as before, just within less height. */
  .odsd-adv-shell { height: 490px; min-height: 0; }
  /* ...except the ANSWER state (owner 2026-08-24): results grow the card
     instead of scrolling inside it; ask/thinking/quiz keep the fixed shape. */
  .odsd-adv-shell.is-answer { height: auto; min-height: 490px; }
  .odsd-adv-shell.is-answer .odsd-adv-main { overflow-y: visible; }
  .odsd-adv-main { overflow-y: auto; }
}

/* CURTAIN: .is-curtain collapses the black aside track right-to-left and the
   answer takes the full card. Sequenced so text never visibly rewraps: the
   aside CONTENT fades first (0.25s), then the track sweeps shut (0.6s);
   reopening runs the reverse order. Browsers without grid-track animation
   (older Safari) snap instantly - still correct, just not animated. */
.odsd-adv-shell { transition: grid-template-columns 0.6s var(--odsd-ease) 0s; }
.odsd-adv-aside { min-width: 0; overflow: hidden; transition: padding 0.6s var(--odsd-ease) 0s; }
.odsd-adv-aside > * { opacity: 1; transition: opacity 0.3s var(--odsd-ease) 0.45s; }
.odsd-adv-shell.is-curtain { grid-template-columns: 0fr 1.15fr; transition-delay: 0.22s; }
.odsd-adv-shell.is-curtain .odsd-adv-aside { padding-left: 0; padding-right: 0; transition-delay: 0.22s; }
.odsd-adv-shell.is-curtain .odsd-adv-aside > * { opacity: 0; transition-delay: 0s; }
/* The collapsed aside must stop CONTRIBUTING HEIGHT too (found 2026-08-25):
   at 0fr the invisible text re-wraps into a character-wide column ~1200px
   tall, and since the grid row is auto-sized that propped the whole answer
   shell open (main stretched to 1243px with ~600px of content). contain:size
   makes the aside's intrinsic contribution zero WITHOUT touching how its
   children lay out (the box itself is still stretched by the grid, so there
   is no visible re-wrap during the fade - the reason nowrap was rejected).
   Browsers without contain:size just keep the old tall shell. */
.odsd-adv-shell.is-curtain .odsd-adv-aside { contain: size; }
@media (max-width: 900px) {
  .odsd-adv-shell.is-curtain .odsd-adv-aside { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .odsd-adv-shell, .odsd-adv-aside, .odsd-adv-aside > * { transition: none; }
}

/* SMALL CARD TITLES (modelTitle cards): short model headline, full descriptive
   name as a muted line under it. The blanket .card-blurb hide above stays for
   ordinary blurbs; the name line opts back in. */
.odsd-adv-products .card-blurb--name {
  display: block; font-size: 0.8125rem; line-height: 1.4;
  color: var(--odsd-ink-soft); margin: 0.15rem 0 0.5rem;
}

/* Advisor grid: the card media is a FIXED 210px window, not the square aspect
   the big grids use (which would eat ~365px per card here) and NOT flex-grow -
   see the natural-height note below for why grow is banned in this grid. */
.odsd-adv-products .product-media { aspect-ratio: auto; height: 210px; flex: 0 0 auto; min-height: 0; }

/* Results-state compaction: with the reply + 3 cards + nav in a fixed shell,
   every vertical rem counts. Scoped to the advisor answer only. */
.odsd-adv-reply { font-size: 0.92rem; line-height: 1.55; }
.odsd-adv-products { margin-top: 0.8rem; }
.odsd-adv-products .product-info { padding: 0.7rem 0.9rem 0.9rem; }
.odsd-adv-shell.is-curtain .odsd-adv-nav { padding-top: 0.9rem; }

/* Advisor answer cards keep a NATURAL height (owner 2026-08-25: "the cards are
   a bit too tall... shorten it to look natural"). This retires the 2026-08-17
   "cards use ALL the shell height" grow chain (step -> grid -> card -> media,
   all flex-grow), which was designed for the FIXED answer shell. Once the
   answer state became height:auto (owner 2026-08-24) the chain had nothing to
   fill and FED BACK on itself: measured over CDP, the shell inflated to
   ~1245px with ~790px of empty media per card, and freezing any single link
   collapsed the card to ~300px. So: the grid and media are content-sized
   (media = the fixed 210px window above), the card keeps flex:1 only to fill
   its stretched grid row so borders align, and the small per-card variance
   (1- vs 2-line names) is absorbed by the info block with the price pinned to
   the card bottom. Do NOT reintroduce flex-grow anywhere in this grid while
   the answer shell is height:auto.
   contain + border-box on the img so it can never crop: an 8% padding on a
   content-box img was able to push it past the media box's overflow:hidden
   edge. */
.odsd-adv-products { flex: 0 0 auto; min-height: 0; }
.odsd-adv-prec { min-height: 0; }
.odsd-adv-products .product-info { flex: 1 1 auto; }
/* (price pinning comes free: .odsd-pgrid .product-foot is margin-top:auto) */
.odsd-adv-products .product-media .product-art {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; box-sizing: border-box; padding: 0.9rem;
}

/* Advisor wait-copy rotation: soft fade on each 3s message swap (reuses the
   step fade keyframes). Static text under prefers-reduced-motion. */
.odsd-adv-loading.is-swap { animation: odsd-adv-fade 0.35s var(--odsd-ease); }
@media (prefers-reduced-motion: reduce) {
  .odsd-adv-loading.is-swap { animation: none; }
}

/* ============================================================
   Browse accordion (rail v7, 2026-08-18) - production's category
   accordion, rebuilt in the rail BELOW the filters: group rows
   with hairline dividers and a +/- toggle, ALL collapsed by
   default (owner). CSS-only checkbox pattern - branch 16 ships
   no theme widget JS. Markup in cattree.mvt (CTGY only). */
.odsd-cat__browse { margin-top: 0; border-top: 1px solid var(--odsd-line); }
.odsd-cat__browse-group { border-bottom: 1px solid var(--odsd-line); padding: 0.9rem 0; }
.odsd-cat__browse-toggle {
  position: absolute; width: 1px; height: 1px;
  opacity: 0; pointer-events: none;
}
.odsd-cat__browse-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.75rem;
}
.odsd-cat__browse-head {
  font-family: var(--odsd-font-display);
  font-size: 1rem; font-weight: 600;
  color: var(--odsd-ink); text-decoration: none;
}
a.odsd-cat__browse-head:hover { color: var(--odsd-accent-deep); text-decoration: none; }
/* +/- drawn as CSS bars, not glyphs - the theme font tofu-boxes U+2212 */
.odsd-cat__browse-plus {
  flex: 0 0 auto;
  position: relative;
  width: 14px; height: 14px;
  cursor: pointer;
  color: var(--odsd-ink);
  user-select: none;
}
.odsd-cat__browse-plus::before,
.odsd-cat__browse-plus::after {
  content: "";
  position: absolute;
  background: currentColor;
}
.odsd-cat__browse-plus::before { left: 0; right: 0; top: 6px; height: 2px; }
.odsd-cat__browse-plus::after { top: 0; bottom: 0; left: 6px; width: 2px; }
.odsd-cat__browse-toggle:checked ~ .odsd-cat__browse-row .odsd-cat__browse-plus::after { display: none; }
.odsd-cat__browse-list {
  display: none;
  list-style: none; margin: 0.75rem 0 0; padding: 0;
  flex-direction: column; gap: 0.55rem;
}
.odsd-cat__browse-toggle:checked ~ .odsd-cat__browse-list { display: flex; }
.odsd-cat__browse-list a {
  font-size: 0.9rem; color: var(--odsd-ink-soft); text-decoration: none;
}
.odsd-cat__browse-list a:hover { color: var(--odsd-ink); }
.odsd-cat__browse .is-current { color: var(--odsd-accent-deep); }

/* ============================================================
   HTML site map (/site-map.html, smap-sitemap.mvt rebuild
   2026-08-19) - full category tree as accordion department
   groups (pre-expanded) + one collapsed all-products group.
   Same checkbox/CSS-bar toggle pattern as the rail accordion. */
.odsd-smap__title {
  font-family: var(--odsd-font-display);
  font-size: 1.15rem; font-weight: 700; color: var(--odsd-ink);
  margin: 2rem 0 0.5rem;
}
.odsd-smap__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 0 2.5rem;
  align-items: start;
}
.odsd-smap__dept { border-top: 1px solid var(--odsd-line); padding: 0.85rem 0; }
.odsd-smap__toggle { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.odsd-smap__row { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; }
.odsd-smap__dept-head {
  font-family: var(--odsd-font-display);
  font-size: 0.95rem; font-weight: 600;
  color: var(--odsd-ink); text-decoration: none;
}
a.odsd-smap__dept-head:hover { color: var(--odsd-accent-deep); text-decoration: none; }
.odsd-smap__dept-head--label { cursor: pointer; }
.odsd-smap__plus {
  flex: 0 0 auto; position: relative;
  width: 14px; height: 14px;
  cursor: pointer; color: var(--odsd-ink); user-select: none;
}
.odsd-smap__plus::before,
.odsd-smap__plus::after { content: ""; position: absolute; background: currentColor; }
.odsd-smap__plus::before { left: 0; right: 0; top: 6px; height: 2px; }
.odsd-smap__plus::after { top: 0; bottom: 0; left: 6px; width: 2px; }
.odsd-smap__toggle:checked ~ .odsd-smap__row .odsd-smap__plus::after { display: none; }
.odsd-smap__body { display: none; }
.odsd-smap__toggle:checked ~ .odsd-smap__body { display: block; }
.odsd-smap__list { list-style: none; margin: 0.6rem 0 0; padding: 0; }
.odsd-smap__list li { margin: 0 0 0.4rem; }
.odsd-smap__list a { font-size: 0.875rem; color: var(--odsd-ink-soft); text-decoration: none; }
.odsd-smap__list a:hover { color: var(--odsd-ink); text-decoration: underline; }
.odsd-smap__list ul { list-style: none; margin: 0.35rem 0 0.35rem; padding: 0 0 0 0.9rem; }
.odsd-smap__list--products { column-width: 260px; column-gap: 2.5rem; }
.odsd-smap__list--products li { break-inside: avoid; }
.odsd-smap__dept--products { border-bottom: 1px solid var(--odsd-line); }

/* ============================================================
   Rail v9 (2026-08-19): filters move to a horizontal pill bar at
   the top of the product column on EVERY category page - the
   same .odsd-srch__filters presentation the search page uses
   (shared class on purpose: pill CSS + the dropdown-discipline
   JS helper both key off it). Leaf pages have no rail at all. */
.odsd-cat__layout--full { grid-template-columns: 1fr; }
/* Inside the listing toolbar since 2026-08-22 (left slot, where the page
   count was): no outer margins, the toolbar's own gap handles spacing; the
   pills wrap within their slot and the controls stay right. */
.odsd-cat__topfilters { margin: 0; flex: 1 1 auto; min-width: 0; }

/* ---- Third-party corner widgets: equal margins (owner, 2026-08-22) ----
   The OnsiteSupport bubble ships with inline `right: 50px` plus a 15px gap
   inside its own box, so it sat 65px from the right edge but 20px from the
   bottom. The Google store badge is 20px / 20px. Matching: 20px all round.
   `!important` is required - the SDK writes `right` as an inline style. */
.UR_chatHandleTab { right: 5px !important; }
/* ---- Advisor ask view (owner-iterated 2026-08-24, FINAL) --------------------
   Bottom chrome in plain flow: heading/chips at the top, open space, composer,
   then the quiz row - fixed gaps, nothing absolute, no JS placement. The card
   keeps its own padding-bottom small for this view so the composer sits low. */
.odsd-adv-main:has(.odsd-adv-askview) { padding-bottom: 14px; }
.odsd-adv-askview .odsd-adv-askform { margin-top: auto; }
.odsd-adv-askview .odsd-adv-quizrow {
	display: flex; justify-content: flex-end;
	margin: 0; padding: 0;
	font-size: 0.85em;
}
.odsd-adv-askview .odsd-adv-quizrow .odsd-adv-back { padding: 0; line-height: 1.2; }
/* ---- Sound Advisor follow-up chat drawer (owner 2026-08-24) -----------------
   Left slide-in over an overlay; transcript bubbles; cards via the shared
   engine (.odsd-adv-prec, already styled); Claude-style composer pinned at
   the bottom. Full-width under 560px. */
.odsd-advchat-ov {
	position: fixed; inset: 0; z-index: var(--odsd-z-chat-scrim);
	background: rgba(14, 14, 16, .45);
	opacity: 0; pointer-events: none;
	transition: opacity .25s var(--odsd-ease);
}
.odsd-advchat-ov.is-open { opacity: 1; pointer-events: auto; }
.odsd-advchat {
	position: fixed; top: 0; left: 0; bottom: 0; z-index: var(--odsd-z-chat);
	width: min(430px, 100vw);
	background: #fff; color: var(--odsd-ink);
	display: flex; flex-direction: column;
	transform: translateX(-102%);
	transition: transform .28s var(--odsd-ease);
	box-shadow: 12px 0 40px rgba(0, 0, 0, .25);
}
.odsd-advchat.is-open { transform: none; }
html.odsd-advchat-lock, html.odsd-advchat-lock body { overflow: hidden; }
/* The Google merchant badge lives bottom-LEFT (cssui-global-footer.mvt) - the
   same corner the drawer slides over. Fade it out while the chat is open
   (owner 2026-08-24: "the google quality store gets in the way"). */
html.odsd-advchat-lock #google-merchantwidget-iframe-wrapper {
	opacity: 0; visibility: hidden;
	transition: opacity .25s var(--odsd-ease), visibility 0s linear .25s;
}
#google-merchantwidget-iframe-wrapper { transition: opacity .25s var(--odsd-ease); }
/* The OnSite support widget (static.onsitesupport.io, loaded in
   cssui-global-footer.mvt) puts a "Claim 5% Off" tab in that SAME bottom-left
   corner, over the drawer - owner report 2026-09-08 with a screenshot. Same
   problem as the Google badge above, same treatment.
   HIDE RATHER THAN OUTRANK: measured in the live DOM the tab is z-index 99999
   and its expanded viewports are 2147483647, against the drawer's 221. There is
   no winning that with a bigger number.
   `.UR_chatElement` is the SDK's own common class, carried by the collapsed tab
   and by both viewport panels, so this covers an already-open OnSite chat too.
   Visibility, not display, so the widget keeps its state and returns when the
   drawer closes. */
html.odsd-advchat-lock .UR_chatElement {
	opacity: 0; visibility: hidden; pointer-events: none;
	transition: opacity .25s var(--odsd-ease), visibility 0s linear .25s;
}
/* Same two widgets, same treatment, while the PHONE MENU is open (2026-09-30,
   code review of the Miva QA fixes). The drawer now sits over the header
   (z-index 200), but the badge (2147483647) and the chat tab (99999) sat over
   the drawer's bottom corners. The theme puts `has-open-main-menu` on <html>
   while the drawer is open. */
html.has-open-main-menu #google-merchantwidget-iframe-wrapper,
html.has-open-main-menu .UR_chatElement {
	opacity: 0; visibility: hidden; pointer-events: none;
	transition: opacity .25s var(--odsd-ease), visibility 0s linear .25s;
}
/* BASKET: the collapsed tab only, and only while the Checkout / PayPal block
   is under it (2026-10-04; extensions.js "KEEP THE SUPPORT BUBBLE OFF THE
   PAYMENT BUTTONS" sets the class). The tab, not every .UR_chatElement: an
   OPEN chat panel must never vanish because the shopper scrolled. */
html.odsd-pay-under-chat .UR_chatHandleTab,
html.odsd-pay-under-chat .UR_chatHandleTab .UR_chatElement {
	opacity: 0; visibility: hidden; pointer-events: none;
	transition: opacity .25s var(--odsd-ease), visibility 0s linear .25s;
}
.UR_chatElement { transition: opacity .25s var(--odsd-ease); }
.odsd-advchat__head {
	display: flex; align-items: center; gap: .75rem;
	padding: .85rem 1rem;
	background: var(--odsd-night-soft); color: #fff;
}
.odsd-advchat__title {
	font-family: var(--odsd-font-display); font-weight: 700; letter-spacing: .02em;
	margin-right: auto;
}
.odsd-advchat__clear {
	background: none; border: 0; cursor: pointer; font: inherit;
	font-size: .78rem; color: rgba(255, 255, 255, .7); padding: .2rem .4rem;
}
.odsd-advchat__clear:hover { color: #fff; }
.odsd-advchat__close {
	background: none; border: 0; cursor: pointer; color: #fff;
	font-size: 1.05rem; line-height: 1; padding: .3rem .45rem;
}
.odsd-advchat__log {
	flex: 1; overflow-y: auto;
	padding: 1rem;
	display: flex; flex-direction: column; gap: .6rem;
	background: var(--odsd-paper, #fff);
}
.odsd-advchat__msg {
	max-width: 88%;
	padding: .6rem .85rem;
	border-radius: 14px;
	font-size: .92rem; line-height: 1.55;
	white-space: pre-line;
	overflow-wrap: anywhere;
}
.odsd-advchat__msg--u {
	align-self: flex-end;
	background: var(--odsd-night-soft); color: #fff;
	border-bottom-right-radius: 4px;
}
/* THE ADVISOR'S TURNS ARE PLAIN TEXT, NOT BUBBLES (owner 2026-08-26, "like how
   Amazon does it"). Only the shopper's own messages keep a bubble, and that
   asymmetry is the point: their side is short and quotable, so a bubble suits
   it, while the answer is prose that should flow at full column width the way
   an answer reads on a product page. A bubble also caps at 88% and pads .85rem
   in from both edges, which on a 400px drawer left the cards visibly wider
   than the text introducing them.
   This overrides the shared .odsd-advchat__msg chrome above. Both are
   single-class rules at (0,1,0), so it wins on SOURCE ORDER only - if either
   rule is ever moved, this one must stay BELOW the base (FINDINGS
   "Source-Order Tie"). Every property the bubble set is unset explicitly here
   rather than left to inherit. */
.odsd-advchat__msg--a {
	align-self: stretch;
	max-width: 100%;
	background: none; color: var(--odsd-ink);
	border-radius: 0;
	padding: .1rem 0 .2rem;
}
.odsd-advchat__cards { display: flex; flex-direction: column; gap: .6rem; }
.odsd-advchat__cards .odsd-adv-prec { max-width: 100%; }
/* interleaved answers (2026-08-25): lead-in, then why + its card per pick.
   The why hugs the card it introduces; the single-card box tucks up under its
   why so the pair reads as one unit.
   Set smaller and softer than the lead-in since the bubbles went (2026-08-26):
   this line is the card's DESCRIPTION, the same job as the grey line under each
   product in Amazon's answers. Without that contrast an answer with three picks
   collapses into one flat wall of identical text - the bubble backgrounds used
   to do this separating, so dropping them means saying it in type instead. */
.odsd-advchat__why {
	font-size: .88rem;
	/* NOT --odsd-ink-soft (#6b6b70, 5.3:1 on white). That variable is tuned for
	   short labels and captions; on a two-line sentence at this size it reads as
	   fine print, which the owner flagged 2026-08-26. #4e4e53 is 8.3:1 - past AAA
	   - and still a clear step down from the near-black lead-in at 18:1, so the
	   hierarchy survives while the sentence stays comfortable to read.
	   Set literally, not as a variable: --odsd-ink-soft is site-wide and this is
	   a decision about THIS line, not about every caption on the store. The
	   original mistake was de-emphasising twice at once, smaller AND lighter; the
	   size step alone now carries most of the separation. */
	color: #4e4e53;
	margin-bottom: -.15rem;
}
.odsd-advchat__cards--one { margin: 0 0 .35rem; }
/* "See more like this" (owner 2026-08-26). A text link, not a button: it is an
   optional side path off the advisor's actual answer, and a filled button here
   would compete with the Add-to-cart inside every card above it.
   --odsd-accent-deep, not --odsd-accent: the raw gold (#fcae46) is 1.9:1 on
   white and unreadable as text - it is a fill colour. #a76f12 is 4.6:1, which
   clears AA for this size. Same lesson as the why line, one rule up. */
.odsd-advchat__more {
	align-self: flex-start;
	margin: -.1rem 0 .2rem;
	padding: .15rem 0;
	background: none; border: 0;
	font: inherit; font-size: .82rem; font-weight: 600;
	color: var(--odsd-accent-deep);
	cursor: pointer;
	text-align: left;
}
.odsd-advchat__more:hover { text-decoration: underline; }
.odsd-advchat__more:disabled { color: var(--odsd-ink-soft); cursor: default; text-decoration: none; }
/* The expansion is INDENTED under the pick it came from. Without this the extra
   cards read as more advisor picks, which they are not - nothing here was
   chosen by the model, it is the rest of that product line in best-seller
   order. The rule is the cheapest way to say "these belong to the card above". */
.odsd-advchat__more-set {
	border-left: 2px solid var(--odsd-line);
	padding-left: .7rem;
	margin: 0 0 .35rem;
}
/* compact card variant for the 400px column: photo left, details right,
   everything the engine card carries but at chat scale */
.odsd-advchat__cards .product-card {
	display: grid; grid-template-columns: 92px 1fr; gap: .25rem .8rem;
	align-items: start;
	padding: .6rem; border: 1px solid var(--odsd-line); border-radius: 10px;
	background: #fff;
}
/* FIXED 92px square (2026-08-25): image loading must never change the box
   height - the scroll-to-answer glide measures the log AFTER hydration, and a
   height:auto img growing later would shift everything under it. */
.odsd-advchat__cards .product-media {
	grid-row: 1 / span 3; width: 92px; aspect-ratio: 1 / 1; margin: 0; position: relative;
}
.odsd-advchat__cards .product-media img { width: 100%; height: 100%; object-fit: contain; display: block; }
.odsd-advchat__cards .product-name { font-size: .92rem; margin: 0; }
/* Star row (owner 2026-08-25). SELF-SUFFICIENT on purpose: the star artwork
   normally comes from the group2.0 widget's injected stylesheet, which most
   pages never load - the drawer loads only the per-product aggregate scripts
   (see chatStars in extensions.js), so the images are declared here. Fixed
   15px slot so a late fill never shifts the layout under the scroll anchor;
   unrated products keep the slot blank. The .ind_cnt overrides carry
   !important at (0,3,0) because SA's own stylesheet, when a listing page DID
   load it, sets them with !important at (0,2,0) - same trick as .lp-rating. */
/* overflow VISIBLE on purpose (owner 2026-08-25, "star ratings are being cut
   off"): the count's parenthesis descenders sat at 14px of the old 15px
   overflow:hidden slot - fine headless, clipped at the owner's zoom/DPI.
   The slot stays fixed-height for scroll-anchor stability; it just never
   clips. A 1-2px overhang lands in the card's own .4rem row gap. */
.odsd-advchat__cards .star_container { height: 17px; min-height: 0; overflow: visible; display: flex; align-items: center; }
.odsd-advchat__cards .group-stars { display: inline-flex; align-items: center; }
.odsd-advchat__cards .group-stars .on,
.odsd-advchat__cards .group-stars .half,
.odsd-advchat__cards .group-stars .off {
	display: inline-block; width: 14px; height: 13px;
	background-size: contain; background-repeat: no-repeat;
	background-image: url('https://www.shopperapproved.com/page/images_svg/star-full.svg');
}
.odsd-advchat__cards .group-stars .half { background-image: url('https://www.shopperapproved.com/page/images_svg/star-half.svg'); }
.odsd-advchat__cards .group-stars .off { display: none; }
/* On pages that DID load the group widget (homepage, category) its stylesheet
   adds `.group-stars .ind_cnt { display:block; width:100%; text-align:center }`
   which wrapped the count onto a clipped second line inside the fixed slot -
   so the layout props are asserted here too, not just the type. */
.odsd-advchat__cards .star_container .ind_cnt {
	display: inline !important; width: auto !important;
	text-align: left !important; margin: 0 !important; line-height: 15px !important;
	font-size: .72rem !important; padding-left: 5px !important;
	vertical-align: 0 !important; color: var(--odsd-ink-soft);
}
.odsd-advchat__cards .star_container .ind_cnt_desc { display: none; }
.odsd-advchat__cards .star_container .ind_cnt_num::before { content: "("; }
.odsd-advchat__cards .star_container .ind_cnt_num::after { content: ")"; }
.odsd-advchat__cards .card-blurb { font-size: .78rem; margin: 0; }
.odsd-advchat__cards .product-price { font-size: .95rem; margin: 0; }
.odsd-advchat__cards .product-foot { margin: 0; padding: 0; }
/* the photo and the model code both link to the PDP; the hover 'See details' link just wraps ugly at 92px, so it is dropped in the drawer */
.odsd-advchat__cards .quick-add { display: none; }
.odsd-advchat__cards .sale-flag { font-size: .68rem; }
.odsd-advchat__note {
	font-size: .75rem; color: var(--odsd-ink-soft);
	text-align: center; margin: 0 0 .2rem;
}
/* Composer geometry is EXPLICIT (owner 2026-08-25: "the text box and send
   button aren't aligned properly and text inside aren't either"). The input is
   a fixed one-line 46px pill (24px line + 10px padding + 1px border each way)
   so its text sits dead-centre; the form's vertical padding is symmetric and
   the send circle anchors at top:50%, so input centre = form centre = button
   centre. The old bottom-anchored button sat 8px low (measured).

   UPDATED 2026-08-27 (owner: the box did not grow past two lines). The input
   now autosizes from 46px up to 142px - five lines at 24px plus the 22px of
   padding and border - after which it scrolls. That breaks the assumption the
   paragraph above rests on, so the send button can no longer be CENTRED: in a
   tall box it would float in the middle of the text. It is pinned to the last
   line instead, `bottom: calc(.75rem + 7px)` = the form's own padding plus the
   (46-32)/2 inset. At exactly one line that reproduces the measured centred
   position to the pixel, so the one-line composer looks identical to before
   and only a grown one differs. */
.odsd-advchat__form {
	position: relative;
	padding: .75rem .8rem;
	border-top: 1px solid var(--odsd-line);
	background: #fff;
}
.odsd-advchat__input {
	display: block; width: 100%; box-sizing: border-box;
	padding: 10px 3rem 10px .85rem;
	border: 1px solid rgba(15, 23, 32, .2); border-radius: 14px;
	font: inherit; resize: none; background: #fff;
	/* AFTER `font` on purpose: the font shorthand resets line-height */
	font-size: .92rem; line-height: 24px;
	/* height is set by autosize JS; these are the floor and ceiling. Keep the
	   floor equal to the old fixed height so a one-line composer is unchanged. */
	height: 46px; min-height: 46px; max-height: 142px;
	overflow-y: auto;
}
.odsd-advchat__send {
	position: absolute; right: 1.15rem; bottom: calc(.75rem + 7px);
	width: 32px; height: 32px; border-radius: 50%; border: 0;
	background: var(--odsd-accent); color: var(--odsd-ink);
	cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0;
}
.odsd-advchat__send:disabled { opacity: .45; cursor: default; }
.odsd-advchat__send svg { display: block; }
/* the follow-up pill on the band's results view */
.odsd-adv-followup {
	border: 0; cursor: pointer; font: inherit; font-weight: 600;
	font-size: .88rem;
	padding: .45rem 1rem; border-radius: 999px;
	background: var(--odsd-accent); color: var(--odsd-ink);
}
.odsd-adv-followup:hover { background: var(--odsd-accent-deep); color: #fff; }
/* Sound Advisor launcher lives in the header action row (owner 2026-08-24 v2;
   the bottom-left fab was reverted same day - screen clutter). The sparkle
   matches its stroke-icon siblings; gold on hover/focus says "assistant". */
/* gold at rest, lighter stroke than its siblings (owner 2026-08-24: "gold and
   not bold") - deeper gold on hover for the state change */
.odsd-icon-btn--advisor svg { stroke: var(--odsd-accent); stroke-width: 1.5; }
.odsd-icon-btn--advisor:hover svg,
.odsd-icon-btn--advisor:focus-visible svg { stroke: var(--odsd-accent-deep); }

/* ============================================================================
   "Complete Your System" - the PDP accessory band (ODSD 2026-08-27)
   ----------------------------------------------------------------------------
   The band is the requisitioned `prod_freq_bought_together` content section,
   renamed. Two rows: the curated product row (unchanged) and a new category row
   driven by the `accessory_links` product custom field.

   The first block below is the section's OLD INLINE <style>, moved here. It was
   re-sent on every product page and could not be cached; in extensions.css it
   gets a branch-scoped URL via css_list. Selectors are unchanged on purpose, so
   this is a move, not a rewrite.
   ========================================================================= */
.x-freq-bought-together { padding: 1.5rem; }
/* The thumbnail row and Shopping List rules that lived here were deleted with the row
   itself (owner, 2026-08-27). Only the section padding below is still in use. */

/* --- the category cards -------------------------------------------------
   These deliberately share the house product-card spec with Related Products
   (line ~7202) rather than inventing a second one: 14px radius, --odsd-line
   border, sand image ground, ink price, 2-line name clamp, 200px grid track.
   Owner asked 2026-08-27 whether Related could take the accessory card's
   treatment; measured the other way round - 14px/--odsd-line is used 8+ times
   across this file and Related already followed it, so the accessory card was
   the outlier. Unified toward the house style.

   The ONE thing kept different is the category link at the top of the card.
   That is a real functional difference - it is navigation, and Related's gold
   eyebrow is a product_line LABEL that does not click anywhere. Same card,
   one extra affordance.
   ------------------------------------------------------------------------ */
/* BUILD SHEET + BUNDLE (owner 2026-09-18, chosen from three mockups on the "CYS Band
   Polish" canvas; replaces the one-day "system path" card row). Each slot is a numbered
   STEP in install order - number, a short "what it does" label, "All <category>" link -
   over one ROW: tick | image | full catalog name + chips | option pickers | price. A
   "Your add-ons" panel beside the list (under it below 960px) lists the ticked rows,
   totals them and adds them in one request. Markup + why the tick box is the quantity:
   prod_freq_bought_together.mvt; behaviour: extensions.js "CYS BUNDLE".
   The row reads the LIST's width, not the window's (container query): beside the panel
   at 1024 the list is ~620px, too narrow for five columns, so under 720px of list the
   pickers and price drop under the name - the same shape phones get. */
body.t-page-prod .odsd-cys-sheet {
	display: grid;
	gap: 1.5rem 2rem;
	align-items: start;
	margin-top: 1.3rem;
}
@media (min-width: 960px) {
	body.t-page-prod .odsd-cys-sheet:has(> .odsd-cys__sum) { grid-template-columns: minmax(0, 1fr) 320px; }
}
body.t-page-prod .odsd-cys {
	container: cys-list / inline-size;
	display: flex;
	flex-direction: column;
	gap: 1.1rem;
	min-width: 0;
}
body.t-page-prod .odsd-cys__step {
	display: flex;
	flex-direction: column;
	gap: .5rem;
	min-width: 0;
}
body.t-page-prod .odsd-cys__head {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: .3rem .55rem;
	padding: 0 .2rem;
}
body.t-page-prod .odsd-cys__num {
	flex: none;
	display: grid;
	place-items: center;
	width: 24px;
	height: 24px;
	border-radius: 50%;
	background: var(--odsd-ink);
	color: #fff;
	font-family: var(--odsd-font-display);
	font-weight: 700;
	font-size: .72rem;
	font-variant-numeric: tabular-nums;
}
body.t-page-prod .odsd-cys__verb {
	font-family: var(--odsd-font-display);
	font-weight: 700;
	font-size: .76rem;
	line-height: 1.3;
	letter-spacing: .08em;
	text-transform: uppercase;
	color: var(--odsd-ink);
}
body.t-page-prod .odsd-cys__cat {
	margin-left: auto;
	font-size: .82rem;
	font-weight: 600;
	line-height: 1.3;
	white-space: nowrap;
	color: var(--odsd-accent-deep, #8a5a05);
	text-decoration: none;
}
body.t-page-prod .odsd-cys__cat::after { content: " \203A"; }
body.t-page-prod .odsd-cys__cat:hover,
body.t-page-prod .odsd-cys__cat:focus-visible { text-decoration: underline; }

/* --- the row ----------------------------------------------------------- */
body.t-page-prod .odsd-cys__slot {
	display: grid;
	grid-template-columns: 28px 88px minmax(0, 1fr) minmax(150px, 190px) 120px;
	grid-template-areas: "tick img info opts price";
	align-items: center;
	gap: 0 1rem;
	min-width: 0;
	padding: .8rem 1.2rem .8rem .9rem;
	background: #fff;
	border: 1.5px solid var(--odsd-line);
	border-radius: 14px;
	transition: border-color .2s var(--odsd-ease);
}
body.t-page-prod .odsd-cys__slot:has([data-cys-tick]:checked) { border-color: var(--odsd-ink); }
/* Unticked: the row fades, the tick box does not - it is how you tick it back. */
body.t-page-prod .odsd-cys__slot:has([data-cys-tick]:not(:checked)) > :not(.odsd-cys__tick) { opacity: .5; }

body.t-page-prod .odsd-cys__tick {
	grid-area: tick;
	justify-self: center;
	display: grid;
	line-height: 0;
	cursor: pointer;
}
body.t-page-prod .odsd-cys__tick input {
	-webkit-appearance: none;
	appearance: none;
	width: 24px;
	height: 24px;
	margin: 0;
	border: 2px solid #cfcac0;
	border-radius: 7px;
	background: #fff center / 15px no-repeat;
	cursor: pointer;
	transition: background-color .15s var(--odsd-ease), border-color .15s var(--odsd-ease);
}
body.t-page-prod .odsd-cys__tick input:hover { border-color: var(--odsd-ink); }
body.t-page-prod .odsd-cys__tick input:checked {
	background-color: var(--odsd-ink);
	border-color: var(--odsd-ink);
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20 6L9 17l-5-5' fill='none' stroke='%23fcae46' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
body.t-page-prod .odsd-cys__sr {
	position: absolute !important;
	width: 1px; height: 1px;
	margin: -1px; padding: 0; border: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
}

body.t-page-prod .odsd-cys__img {
	grid-area: img;
	display: block;
	width: 88px;
	aspect-ratio: 1 / 1;
	border-radius: 10px;
	background: var(--odsd-sand);
	overflow: hidden;
}
body.t-page-prod .odsd-cys__img img {
	width: 100%; height: 100%;
	object-fit: contain;
	display: block;
	padding: .45rem;
	margin: 0;
	mix-blend-mode: multiply;
}
body.t-page-prod .odsd-cys__info {
	grid-area: info;
	display: flex;
	flex-direction: column;
	gap: .4rem;
	min-width: 0;
}
/* The full catalog name, up to three lines (fits beside the 88px image); the whole
   name is on the link's title. */
body.t-page-prod .odsd-cys__nm {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 3;
	overflow: hidden;
	font-family: var(--odsd-font-body);
	font-weight: 600;
	font-size: .92rem;
	line-height: 1.4;
	color: var(--odsd-ink);
	text-decoration: none;
}
body.t-page-prod .odsd-cys__nm:hover,
body.t-page-prod .odsd-cys__nm:focus-visible { color: var(--odsd-accent-deep, #8a5a05); }
body.t-page-prod .odsd-cys__chips { display: flex; flex-wrap: wrap; gap: .3rem; }
body.t-page-prod .odsd-cys__opts {
	grid-area: opts;
	display: flex;
	flex-direction: column;
	gap: .35rem;
	min-width: 0;
}
body.t-page-prod .odsd-cys__sel {
	width: 100%;
	height: 36px;
	padding: 0 2rem 0 .7rem;
	font: 500 .8rem/1.2 var(--odsd-font-body);
	color: var(--odsd-ink);
	-webkit-appearance: none;
	appearance: none;
	background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 9l6 6 6-6' fill='none' stroke='%2316161a' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") right .65rem center / 12px no-repeat;
	border: 1.5px solid #d9d5cc;
	border-radius: 9px;
	cursor: pointer;
}
body.t-page-prod .odsd-cys__sel:hover { border-color: var(--odsd-ink); }
body.t-page-prod .odsd-cys__sel.is-missing { border-color: #b3261e; box-shadow: 0 0 0 1px #b3261e; }
body.t-page-prod .odsd-cys__more {
	font-size: .82rem;
	font-weight: 600;
	color: var(--odsd-accent-deep, #8a5a05);
}
body.t-page-prod .odsd-cys__pr {
	grid-area: price;
	text-align: right;
	font-family: var(--odsd-font-display);
	font-weight: 700;
	font-size: 1.05rem;
	line-height: 1.3;
	color: var(--odsd-ink);
	font-variant-numeric: tabular-nums;
}
body.t-page-prod .odsd-cys__was {
	display: block;
	font-weight: 500;
	font-size: .8rem;
	color: var(--odsd-ink-soft);
	text-decoration: line-through;
}
body.t-page-prod .odsd-cys__choose,
body.t-page-prod .odsd-cys__stock {
	display: block;
	font-family: var(--odsd-font-body);
	font-weight: 500;
	font-size: .76rem;
	line-height: 1.35;
	color: var(--odsd-ink-soft);
}
body.t-page-prod .odsd-cys__stock { color: #b3261e; }

/* A step with nothing buyable keeps its label and a shop link, in a slim dashed row. */
body.t-page-prod .odsd-cys__slot--bare {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: .4rem 1rem;
	padding: .85rem 1.2rem;
	background: transparent;
	border: 1.5px dashed #cfcac0;
	font-size: .85rem;
	color: var(--odsd-ink-soft);
}
body.t-page-prod .odsd-cys__browse {
	font-size: .85rem;
	font-weight: 600;
	color: var(--odsd-accent-deep, #8a5a05);
	text-decoration: none;
	white-space: nowrap;
}
body.t-page-prod .odsd-cys__browse::after { content: " \203A"; }
/* A powered product's amplifier step (the agent's "Built-in Amplifier", 2026-09-18): an
   answer, not a gap - so a solid row with a tick, not the dashed "shop" row. */
body.t-page-prod .odsd-cys__slot--builtin {
	display: flex;
	align-items: center;
	padding: .85rem 1.2rem;
	background: var(--odsd-sand);
	border: 1.5px solid var(--odsd-line);
}
body.t-page-prod .odsd-cys__builtin {
	display: inline-flex;
	align-items: center;
	gap: .55rem;
	font-size: .88rem;
	font-weight: 600;
	color: var(--odsd-ink);
}
body.t-page-prod .odsd-cys__builtin::before {
	content: "";
	flex: none;
	width: 20px;
	height: 20px;
	border-radius: 50%;
	background: var(--odsd-ink) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20 6L9 17l-5-5' fill='none' stroke='%23fcae46' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 12px no-repeat;
}
body.t-page-prod .odsd-cys__browse:hover,
body.t-page-prod .odsd-cys__browse:focus-visible { text-decoration: underline; }

/* A narrow list (beside the panel at ~1024, or any phone): pickers and price go under
   the name, the tick box and image stay on the left. */
@container cys-list (max-width: 720px) {
	body.t-page-prod .odsd-cys__slot {
		grid-template-columns: 28px 76px minmax(0, 1fr);
		grid-template-areas: "tick img info" "tick img opts" "tick img price";
		align-items: start;
		gap: .5rem .8rem;
		padding: .75rem .9rem .75rem .7rem;
	}
	body.t-page-prod .odsd-cys__tick { align-self: center; }
	body.t-page-prod .odsd-cys__img { width: 76px; }
	body.t-page-prod .odsd-cys__opts { flex-direction: row; flex-wrap: wrap; }
	body.t-page-prod .odsd-cys__opts .odsd-cys__sel { width: auto; min-width: 9.5rem; max-width: 100%; }
	body.t-page-prod .odsd-cys__pr { text-align: left; font-size: 1rem; }
	body.t-page-prod .odsd-cys__was { display: inline; margin-right: .4rem; }
}
@container cys-list (max-width: 420px) {
	body.t-page-prod .odsd-cys__slot { grid-template-columns: 26px 64px minmax(0, 1fr); gap: .45rem .65rem; }
	body.t-page-prod .odsd-cys__img { width: 64px; }
	body.t-page-prod .odsd-cys__nm { font-size: .86rem; }
	body.t-page-prod .odsd-cys__opts .odsd-cys__sel { flex: 1 1 9rem; min-width: 0; }
}

/* --- the summary panel -------------------------------------------------- */
body.t-page-prod .odsd-cys__sum {
	display: flex;
	flex-direction: column;
	gap: .85rem;
	min-width: 0;
	padding: 1.3rem;
	background: #fff;
	border: 1.5px solid var(--odsd-line);
	border-radius: 18px;
	box-shadow: 0 10px 40px rgba(20, 20, 26, .07);
}
@media (min-width: 960px) {
	body.t-page-prod .odsd-cys__sum {
		position: sticky;
		top: calc(var(--odsd-header-h, 84px) + var(--odsd-pdp-tabs-h, 53px) + 1rem);
	}
}
body.t-page-prod .odsd-cys__sum-title {
	margin: 0;
	font-family: var(--odsd-font-display);
	font-weight: 700;
	font-size: 1rem;
	color: var(--odsd-ink);
}
body.t-page-prod .odsd-cys__lines {
	display: flex;
	flex-direction: column;
	gap: .6rem;
	margin: 0;
	padding: 0;
	list-style: none;
}
body.t-page-prod .odsd-cys__lines[hidden] { display: none; }
body.t-page-prod .odsd-cys__line {
	display: grid;
	grid-template-columns: 38px minmax(0, 1fr) auto;
	align-items: center;
	gap: .6rem;
	margin: 0;
	font-size: .84rem;
	color: var(--odsd-ink);
}
body.t-page-prod .odsd-cys__line img {
	width: 38px;
	height: 38px;
	margin: 0;
	padding: 3px;
	border-radius: 8px;
	background: var(--odsd-sand);
	object-fit: contain;
	mix-blend-mode: multiply;
}
body.t-page-prod .odsd-cys__line-nm { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
body.t-page-prod .odsd-cys__line-pr { font-weight: 600; white-space: nowrap; font-variant-numeric: tabular-nums; }
body.t-page-prod .odsd-cys__line-pr.is-wait { font-weight: 500; font-size: .76rem; color: var(--odsd-ink-soft); }
body.t-page-prod .odsd-cys__line-pr.is-out { font-weight: 500; font-size: .76rem; color: #b3261e; }
body.t-page-prod .odsd-cys__total {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: .6rem;
	margin: 0;
	padding-top: .85rem;
	border-top: 1px solid var(--odsd-line);
}
body.t-page-prod .odsd-cys__total[hidden] { display: none; }
body.t-page-prod .odsd-cys__total-label { font-size: .85rem; color: var(--odsd-ink-soft); }
body.t-page-prod .odsd-cys__total-amt {
	font-family: var(--odsd-font-display);
	font-weight: 800;
	font-size: 1.5rem;
	letter-spacing: -.02em;
	color: var(--odsd-ink);
	font-variant-numeric: tabular-nums;
}
body.t-page-prod .odsd-cys__total-note { margin: 0; font-size: .78rem; line-height: 1.4; color: var(--odsd-ink-soft); }
body.t-page-prod .odsd-cys__total-note:empty { display: none; }
body.t-page-prod .odsd-cys__add {
	width: 100%;
	min-height: 50px;
	padding: .8rem 1.2rem;
	font: 700 .95rem/1.2 var(--odsd-font-display);
	color: #fff;
	background: var(--odsd-ink);
	border: 0;
	border-radius: 12px;
	cursor: pointer;
	transition: background .2s var(--odsd-ease);
}
body.t-page-prod .odsd-cys__add:hover { background: #2c2c33; }
body.t-page-prod .odsd-cys__add:disabled { opacity: .4; cursor: not-allowed; }
body.t-page-prod .odsd-cys__add:focus-visible,
body.t-page-prod .odsd-cys__tick input:focus-visible,
body.t-page-prod .odsd-cys__sel:focus-visible { outline: 2px solid var(--odsd-accent-deep, #8a5a05); outline-offset: 2px; }
body.t-page-prod .odsd-cys__msg { font-size: .86rem; line-height: 1.4; color: var(--odsd-ink); }
body.t-page-prod .odsd-cys__msg:empty { display: none; }
body.t-page-prod .odsd-cys__msg.is-error { color: #b3261e; }
body.t-page-prod .odsd-cys__msg a { color: inherit; font-weight: 600; }


/* ============================================================================
   MOBILE HEADER + HERO FIXES (owner reported 2026-08-27, from a real phone)
   ========================================================================= */

/* 1. THE HERO WAS A LETTERBOX ON MOBILE.
   `.odsd-hero` carried `aspect-ratio: 1861/550` (3.38:1, the desktop banner
   ratio) at EVERY width, but the slide markup swaps to a PORTRAIT image at
   `max-width: 639px` (extensions.js: <source media="(max-width: 639px)">).
   Measured at 430px: a 533x800 portrait image crushed into a 430x127 box with
   `object-fit: cover`, i.e. ~84% of the picture thrown away, which is why it
   read as a dark clipped strip.
   This is the repo's own rule, ported verbatim - styles.css:1785-1788, same
   639px breakpoint as the <picture> swap, deliberately matched. */
@media (max-width: 639px) {
	.odsd-hero { aspect-ratio: 400 / 600; max-height: none; }
}

/* 2. EVERY HEADER BUTTON WAS A 21x21 TAP TARGET BELOW 960px.
   `.odsd-icon-btn` sets no width/height of its own - it is a grid that shrinks
   to its contents. Explicit sizes exist ONLY in the 960-1149 and 1150-1339
   media queries, and the text label (which is what gives the button its height
   above 1100px) is hidden at <=1100px. So under 960px the button collapsed to
   its 21px SVG: less than half the 44px minimum, on the menu, advisor, search,
   account and cart alike. That is why "Ask OSD" felt unresponsive on a phone -
   the JS was bound and working, the target was just too small to hit.
   Buttons sat 39px apart centre to centre, so simply padding the hit area to
   44px would have made neighbours OVERLAP and steal each other's taps. The
   cluster had 77px of slack beside the logo, so the buttons are made genuinely
   44px and the gap is spent on them instead. */
@media (max-width: 959px) {
	.odsd-header__actions { gap: 0; }
	.odsd-icon-btn {
		min-width: 44px;
		min-height: 44px;
		align-content: center;
	}
}

/* 3. THE UTILITY BANNER WAS 11.52px.
   `@media (max-width: 560px)` dropped it to 0.72rem so three items would fit.
   Measured at 430px the row only used 327px of 430, so it was shrunk further
   than it needed to be. 0.8rem is 12.8px and still leaves room. */
@media (max-width: 560px) {
	.odsd-utility { font-size: 0.8rem; }
}

/* Two-up product cards on phones (owner, 2026-08-27).
   The shared house card uses a 200px minimum track, which at 430px leaves room
   for exactly ONE column - measured 2810px for a 5-card "Complete Your System"
   and 2698px for Related Products, i.e. about 5.5 metres of stacked scrolling at
   the foot of every mobile PDP. Two columns halve it and still give each card
   ~183px, which is wider than the 158px track this band used before it adopted
   the house spec. Applied to BOTH grids so they stay in step; they were made
   consistent deliberately and a split here would undo that. */
@media (max-width: 560px) {
	/* Two and a bit in view since the band became one row (2026-11-03): the arrows are
	   hidden on touch, so the cut-off third card is part of what says "swipe". */
	body.t-page-prod .odsd-related__grid { --odsd-related-gap: .75rem; }
	body.t-page-prod .odsd-related__grid .x-product-list__item { flex-basis: 44%; }
	/* The name clamp is what keeps the two columns the same height; at half
	   width a 2-line clamp truncates almost every catalog name, so allow 3. */
	body.t-page-prod .odsd-related__grid .x-product-list__name {
		-webkit-line-clamp: 3;
		font-size: .85rem;
	}
	/* Card-era padding until the band lost its card (c4564): the .85rem bottom
	   was clearing a border that no longer exists. Match the desktop caption. */
	body.t-page-prod .odsd-related__grid .x-product-list__figure-caption { padding: .7rem .2rem 0; }
}


/* ==== Accessibility pass (audit 2026-09-22) ================================= */

/* 320px wide (small phones, and ANY phone at 400% zoom - WCAG 1.4.10 reflow):
   the header row needed ~360px (six 44px buttons + the logo), so Account and Cart
   were pushed off-screen and the whole page scrolled sideways (measured 414px).
   Below 375px the buttons are 38px - still well above the 24px WCAG 2.5.8
   minimum - the logo a touch smaller, and the black bar keeps the phone number
   on one line. Nothing is removed: Ask OSD and Outlet stay (owner rules).
   Up to 429px, not 374: at 390px (most iPhones) the same squeeze made the page 414px
   wide and clipped the Cart icon - re-measured the same day. */
@media (max-width: 429px) {
  .odsd-header__row { padding-left: 0.6rem; padding-right: 0.6rem; gap: 0.4rem; }
  .odsd-icon-btn { min-width: 38px; min-height: 38px; width: 38px; height: 38px; }
  .odsd-brand__logo { height: 22px; }
  .odsd-utility { font-size: 0.72rem; }
  .odsd-utility__right > * { padding-inline: 0.25rem; }
  .odsd-utility__right a.odsd-util-chat { margin-left: 0.25rem; padding-inline: 0.5rem; }
  .odsd-util-phone, .odsd-util-outlet { white-space: nowrap; }
}

/* Forced colours (Windows High Contrast and similar): the logo is dark artwork
   and vanished on the forced black header; the icons went faint. Give the logo a
   light plate and draw the icons in the system text colour. */
@media (forced-colors: active) {
  .odsd-brand__logo { forced-color-adjust: none; background: #fff; padding: 2px 4px; border-radius: 3px; }
  .odsd-icon-btn, .odsd-icon-btn span { color: CanvasText; }
  .odsd-icon-btn svg { stroke: CanvasText; }
}

/* Reduced motion: the chat widget's pulsing button (a third-party script) was the
   one animation that ignored the setting. */
@media (prefers-reduced-motion: reduce) {
  .UR_chatTabHandler, .UR_chatElement { animation: none !important; }
}

/* Listing toolbar on PHONES (audit 2026-09-22). The owner's layout puts the facet pills in
   the left slot on the SAME row as Sort + the view toggle (2026-08-22) - that holds from
   ~640px up. Below it the row cannot fit: at 390px (most iPhones) the pill slot measured
   0px wide, so the pills stacked in a zero-width column over the Sort select and the page
   overflowed to 414px, on CTGY and SRCH alike. On phones the pills take their own row and
   Sort + toggle sit on the row under them. */
@media (max-width: 640px) {
  .odsd-cat__toolbar { flex-wrap: wrap; }
  .odsd-cat__toolbar > .odsd-cat__count-wrap { flex: 1 1 100%; min-width: 0; }
  /* Sort (223px) + toggle (74px) + gap = 313px against 256px of room: the controls may
     now shrink to the row, and Sort's select gives up the difference. */
  .odsd-cat__toolbar .odsd-cat__controls { margin-left: 0; flex: 1 1 100%; min-width: 0; flex-wrap: nowrap; justify-content: space-between; }
  .odsd-cat__toolbar .odsd-cat__sortform { flex: 1 1 auto; min-width: 0; }
  .odsd-cat__toolbar .odsd-cat__sortform select { min-width: 0; max-width: 100%; }
}
/* The Prop 65 warning's URL is one unbreakable word in a flex row; at 320px it ran
   98px past the edge. Let it wrap. */
.prop-65 .info-wrapper, .prop-65 .info-wrapper p, .prop-65 .odsd-p65-text { min-width: 0; }
.prop-65 a { overflow-wrap: anywhere; }
/* The field brings its own <p> on 346 of 377 products and a <div> on 8, and it sits
   after a "WARNING:" label. Inline, so label and text read as one sentence beside the
   icon whichever form a product uses (Aaron, 2026-11-03; template: prod-product_display). */
.prop-65 .odsd-p65-text p, .prop-65 .odsd-p65-text div { display: inline; margin: 0; }

/* Search filter panels on phones (audit 2026-09-22): a closed panel is only transparent,
   so it still occupies space - anchored to its own pill with a 250px minimum, a pill near
   the right edge pushed its invisible panel past the screen (search page 428px wide at
   320). On phones the panels anchor to the whole filter bar and span its width instead.
   The static rule repeats the (0,3,0) selector that sets position:relative on the pill
   (".x-refinery .c-form-list__item", above): with only ".x-refinery-set" (0,2,0) it lost,
   every panel stayed anchored to its own pill, and left:0/right:0 shrank it to the pill's
   76-167px - labels clipped to "Audio Syster" (L4-O1, 2026-10-20). */
@media (max-width: 560px) {
  .odsd-srch__filters { position: relative; }
  .odsd-srch__filters .x-refinery .c-form-list__item,
  .odsd-srch__filters .x-refinery-set { position: static; }
  .odsd-srch__filters .x-refinery-set__list { left: 0; right: 0; min-width: 0; max-width: none; }
}

/* Homepage hero slot (sfnt.mvt, 2026-09-22): the server-rendered stand-in the rotating hero
   replaces. Same box as .odsd-hero at every width, so the swap moves nothing (it measured
   CLS 0.165 without it). */
.odsd-hero-slot { position: relative; width: 100%; aspect-ratio: 1861 / 550; max-height: 80vh; overflow: hidden; background: #0d0e10; }
.odsd-hero-slot a, .odsd-hero-slot picture,
.odsd-hero-slot img { display: block; width: 100%; height: 100%; }
.odsd-hero-slot img { object-fit: cover; object-position: center; }
@media (max-width: 639px) { .odsd-hero-slot { aspect-ratio: 400 / 600; max-height: none; } }

/* ==========================================================================
   ADD TO CART STAYS ON THE PAGE (2026-10-19, Miva experience audit B4)
   The line extensions.js writes when the store refuses an add (not enough
   stock, out of stock, an option still to choose) or adds it with a warning.
   Same size and red as the CYS band's message, so the two read as one system.
   PDP: inside the theme's own purchase-message slot, above the stock line.
   Card: the last line of the price rail - the one part of the card BOTH views
   show (the grid's hover button sits over the image and vanishes with the
   hover). Empty and hidden until there is something to say.
   ========================================================================== */
.odsd-addmsg { margin: 0.4rem 0 0; font-size: 0.86rem; line-height: 1.4; color: #b3261e; }
.odsd-addmsg[hidden] { display: none; }
.x-product-layout-purchase__message .odsd-addmsg { margin: 0 0 0.7rem; }
/* Grid card: the rail is a wrapping flex row ordered price 2 / was 3 / pill 4 /
   touch button 5, so the message takes a full line after all of them. */
.odsd-cat__list:not(.is-list) .lp-rail .odsd-addmsg { order: 6; flex: 1 0 100%; }
/* List row: the rail is a right-aligned column; keep the line under the button
   and no wider than the rail's own content. */
.odsd-cat__list.is-list .lp-rail .odsd-addmsg { max-width: 16rem; text-align: right; }
