:root {
  --ecom-ui-text-font-family: Roboto, -apple-system, BlinkMacSystemFont,
    "Segoe UI", "Helvetica Neue", Arial, sans-serif;

  /* The size a page's own h1 is set at, wherever it appears — the post title
     on a single post, the heading of the front page's first section. Defined
     once so the two cannot drift; theme.json's presets cannot express it
     because it is a clamp() rather than a fixed step. */
  --ap-title-h1: clamp(2.25rem, 4vw, 3.25rem);

  /* The theme's `close` glyph as a mask image, for the two Commerce Connect
     pane close buttons, which render the plugin's own filled X and cannot be
     given the block's markup -- see the .ecom-pane-close rules. Mirrors
     inc/icons.php; the stroke colour is arbitrary, a mask reads only alpha. */
  --ap-icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 6 12 12M18 6 6 18'/%3E%3C/svg%3E");

  /* The theme's `check` glyph, for the newsletter's success card. Same reason
     as the close mask above: the element it marks has its text replaced by
     JavaScript, so a real <svg> child would not survive. Mirrors
     inc/icons.php. */
  --ap-icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9.2'/%3E%3Cpath d='m8.2 12.2 2.6 2.6 5-5.4'/%3E%3C/svg%3E");
}

/**
 * AP FSE Theme — component styles.
 *
 * Only what theme.json cannot express: the header's centred search field,
 * circular icon buttons, the category navigation bar, and reconciliation of
 * the Commerce Connect blocks with the theme's layout.
 */

/* -------------------------------------------------------------------------
 * Utilities
 * ---------------------------------------------------------------------- */

.ap-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

/* -------------------------------------------------------------------------
 * Header — logo, search and utility rail
 * ---------------------------------------------------------------------- */

/* Positioning context for the mobile search panel. */
.ap-header {
  position: relative;
}

/* A note on the selectors in this file.
 *
 * Several rules carry an ancestor class they do not strictly need. That is
 * deliberate. WordPress loads global styles BEFORE the theme stylesheet on the
 * front end, and AFTER the theme's editor styles in the block editor, so a
 * theme rule that merely ties with a global-styles rule wins live and silently
 * loses on the canvas. `.has-<slug>-background-color` (which is !important),
 * `.has-global-padding` and the generated layout rules are all single-class, so
 * a single-class theme rule is a coin toss decided by context. Two classes
 * settle it the same way in both. */

.ap-header .ap-header__inner {
  column-gap: clamp(1rem, 3vw, 2.5rem);
}

.ap-header__main .wp-block-site-logo img {
  display: block;
  height: auto;
  max-width: min(360px, 100%);
}

/* The logo image carries the brand wordmark, so the text title is a
   fallback only — hidden as soon as a logo has been uploaded. */
.ap-header__brand:has(.wp-block-site-logo img) .ap-header__site-title {
  display: none;
}

/* Search takes the middle of the row and stays optically centred as the brand
   and utility rail change width.

   The basis is deliberately small. Flex decides wrapping from basis values
   before any shrinking happens, so a large basis made the whole utility rail
   drop to a second row between roughly 1100px and 1160px — the band where the
   contact details are still shown but space is tight. A small basis plus
   flex-grow lets the field absorb the squeeze instead, and min-width:0 lets it
   shrink past the input's intrinsic width. */
.ap-header__inner > .ap-search {
  flex: 1 1 12rem;
  min-width: 0;
  max-width: 34rem;
  margin-inline: auto;
}

.ap-search .wp-block-search__inside-wrapper {
  border: 0;
  border-radius: 999px;
  background-color: var(--wp--preset--color--base-alt);
  overflow: hidden;
}

/* The 16px floor is not cosmetic: iOS Safari zooms the page whenever a focused
   form control is smaller than that, and header.js focuses this field as soon
   as the mobile panel opens, so tapping the toggle jumps the whole layout.
   Nothing zooms it back — the user is left scrolled and scaled.

   The inherited size is the fluid `medium` preset, which resolves to ~14.2px at
   390px and only clears 16px above roughly 1360px, so this is not a phone-only
   problem: an iPad in landscape trips it too. max() rather than a breakpoint
   therefore covers every width, and hands the preset straight back once it is
   the larger of the two, leaving the desktop field exactly as it was. */
.ap-search .wp-block-search__input {
  padding: 0.85rem 1.25rem 0.85rem 0;
  border: 0;
  background: transparent;
  color: var(--wp--preset--color--contrast);
  font-size: max(1rem, var(--wp--preset--font-size--medium));
}

.ap-search .wp-block-search__input::placeholder {
  color: var(--wp--preset--color--contrast-muted);
  opacity: 1;
}

.ap-search .wp-block-search__inside-wrapper:focus-within {
  outline: 2px solid var(--wp--preset--color--primary);
  outline-offset: 2px;
}

.ap-search .wp-block-search__input:focus {
  outline: 0;
}

/* Magnifier sits ahead of the field, inside the pill. The display/alignment
   trio is declared rather than inherited: core's editor stylesheet flexes this
   button with `.wp-block-search :where(.wp-block-search__button)` and the front
   end leaves it a block, which nudges the icon off centre between the two. */
.ap-search .wp-block-search__button {
  order: -1;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 0 0.65rem 0 1.25rem;
  border: 0;
  background: transparent;
  color: var(--wp--preset--color--contrast-muted);
}

.ap-search .wp-block-search__button svg {
  width: 1.25rem;
  height: 1.25rem;
  fill: currentColor;
}

/* Column direction, alignment and gap come from the block's layout attributes
   so the editor renders them too — see patterns/header.php. */
.ap-header__contact {
  text-align: right;
  line-height: 1.3;
}

.ap-header__phone {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1.125rem;
  font-weight: 700;
  color: var(--wp--preset--color--primary);
  text-decoration: none;
  white-space: nowrap;
}

.ap-header__phone svg {
  width: 1em;
  height: 1em;
}

.ap-header__email {
  font-size: var(--wp--preset--font-size--small);
  color: var(--wp--preset--color--contrast-muted);
  text-decoration: none;
  white-space: nowrap;
}

.ap-header__phone:hover,
.ap-header__phone:focus-visible,
.ap-header__email:hover,
.ap-header__email:focus-visible {
  text-decoration: underline;
}

/* Rule separating the contact details from the icon rail. The gap and the
   padding beside it are block attributes, so the editor matches the front end;
   only the border itself has no block-level equivalent. */
.ap-header__actions {
  border-left: 1px solid var(--wp--preset--color--outline);
}

/* -------------------------------------------------------------------------
 * Circular icon buttons
 *
 * .ap-iconbtn covers the account and support links. The cart is the
 * commerce-connect/cart-icon block — an empty div hydrated by JS — so the
 * same treatment is applied to the button its script renders, leaving the
 * plugin's own icon and count markup alone.
 * ---------------------------------------------------------------------- */

.ap-iconbtn,
.ap-cart .ecom-cart-icon,
.ap-header__utility .ecom-cart-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background-color: var(--wp--preset--color--base-alt);
  color: var(--wp--preset--color--primary);
  text-decoration: none;
  transition: background-color 0.15s ease-in-out;
}

.ap-iconbtn:hover,
.ap-iconbtn:focus-visible,
.ap-cart .ecom-cart-icon:hover,
.ap-cart .ecom-cart-icon:focus,
.ap-header__utility .ecom-cart-icon:hover,
.ap-header__utility .ecom-cart-icon:focus {
  background-color: var(--wp--preset--color--outline);
  color: var(--wp--preset--color--primary-dark);
}

.ap-iconbtn svg {
  width: 1.375rem;
  height: 1.375rem;
}

/* The search toggle only exists below the breakpoint; above it the field is
   always visible in the header row. */
.ap-search-toggle {
  display: none;
  cursor: pointer;
}

/* commerce-connect/cart-icon hard-codes an `alignwide` class that would
   otherwise stretch inside this flex row. */
.ap-cart {
  display: flex;
  align-items: center;
}

.ap-cart .cart-icon-blocks,
.ap-cart .alignwide {
  max-width: none;
  margin: 0;
}

/* Anchors the plugin's absolutely-positioned counter to the round button. */
.ap-cart .ecom-cart-icon {
  position: relative;
}

.ap-cart .ecom-icon-cart {
  width: 1.375rem;
  height: 1.375rem;
}

.ap-cart .ecom-icon-cart path,
.ap-cart .ecom-icon-cart g {
  fill: currentColor;
}

/* The plugin derives the count bubble from its own primary button colour;
   the design calls for AP Green. The design's white count sits at 2.1:1 on
   that green, so the text is Slate Charcoal instead — 6.9:1. */
.ap-cart .ecom-cart-counter {
  background: var(--wp--preset--color--green);
  color: var(--wp--preset--color--contrast);
}

/* -------------------------------------------------------------------------
 * Primary category navigation
 * ---------------------------------------------------------------------- */

.ap-header .ap-navbar {
  position: relative;
  z-index: 20;
  border-bottom: 1px solid var(--wp--preset--color--outline);
}

.ap-header .ap-navbar__inner {
  min-height: 4.25rem;
}

/* Gaps for this bar and for the nav items are block attributes, not CSS, so
   the editor spaces them the same way the front end does. Note the style
   engine rejects clamp() in blockGap — presets and plain lengths only. */

.ap-primary-nav .wp-block-navigation-item__label {
  font-family: var(--wp--preset--font-family--ui);
  font-weight: 700;
  white-space: nowrap;
}

.ap-primary-nav
  > .wp-block-navigation__container
  > .wp-block-navigation-item
  > a {
  padding-block: 0.5rem;
  color: var(--wp--preset--color--contrast);
  text-decoration: none;
}

.ap-primary-nav
  > .wp-block-navigation__container
  > .wp-block-navigation-item
  > a:hover,
.ap-primary-nav
  > .wp-block-navigation__container
  > .wp-block-navigation-item
  > a:focus-visible {
  color: var(--wp--preset--color--primary);
}

/* Sale call to action, sharing the bar's baseline with the nav items. */
.ap-navbar__cta .wp-block-button__link {
  padding: 0.7rem 1.6rem;
  border-radius: 6px;
  font-family: var(--wp--preset--font-family--ui);
  font-weight: 700;
  line-height: 1.2;
}

.ap-navbar__cta .wp-block-button__link:hover,
.ap-navbar__cta .wp-block-button__link:focus-visible {
  background-color: var(--wp--preset--color--accent-dark);
}

/* Submenus read as a panel floating over the page, not part of the bar. */
.ap-primary-nav .wp-block-navigation__submenu-container {
  min-width: 260px;
  padding-block: 0.35rem;
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 8px;
  background-color: var(--wp--preset--color--base);
  box-shadow: 0 12px 28px rgba(30, 41, 59, 0.14);
}

.ap-primary-nav .wp-block-navigation__submenu-container a {
  padding: 0.5rem 1rem;
  color: var(--wp--preset--color--contrast);
}

.ap-primary-nav .wp-block-navigation__submenu-container a:hover,
.ap-primary-nav .wp-block-navigation__submenu-container a:focus-visible {
  background-color: var(--wp--preset--color--base-alt);
  color: var(--wp--preset--color--primary-dark);
}

.ap-primary-nav .wp-block-navigation__submenu-icon svg {
  width: 0.7em;
  height: 0.7em;
}

@media (max-width: 1100px) {
  /* Icons carry the same destinations, so the text pair is the first to go.
	   Needs the child combinator: the contact group is a flex layout block, and
	   WordPress's `.is-layout-flex { display: flex }` matches at the same
	   specificity as a lone class. */
  .ap-header__utility > .ap-header__contact {
    display: none;
  }

  .ap-header__actions {
    padding-left: 0 !important;
    border-left: 0;
  }
}

/* -------------------------------------------------------------------------
 * Mobile header
 *
 * Below 900px the design collapses to [logo] [search] [cart] [menu]. Core
 * switches the navigation to its overlay at 600px, so the first two rules
 * move that threshold up to the design's breakpoint, and .ap-header becomes
 * a single flex row with the toggle pulled out of the category bar and onto
 * the far right of the logo row, after the cart.
 * ---------------------------------------------------------------------- */

@media (max-width: 899.98px) {
  .ap-primary-nav
    .wp-block-navigation__responsive-container-open:not(.always-shown) {
    display: flex;
  }

  .ap-primary-nav
    .wp-block-navigation__responsive-container:not(.hidden-by-default):not(
      .is-menu-open
    ) {
    display: none;
  }

  /* The logo row (header__main) takes the remaining space so the brand sits
	   flush left and the search/cart icons sit flush right within it; the menu
	   toggle (ap-navbar) is a fixed-width flex item after it, landing at the
	   true right edge of the header.

	   header__main and navbar are each `layout:constrained`, so core's
	   root-padding-aware alignment gives each of them its own left/right
	   gutter independently (`.has-global-padding`). That was harmless in the
	   old two-row grid, where the two blocks never sat side by side, but here
	   it would stack a gutter on both sides of the seam between them — the
	   "cart" edge of header__main and the "hamburger" edge of navbar — on
	   top of the column-gap below. Both are zeroed out here and a single
	   gutter is applied to .ap-header itself instead. */
  .ap-header {
    display: flex;
    align-items: center;
    column-gap: var(--wp--preset--spacing--20);
    padding-inline: calc(var(--wp--preset--spacing--40) * 0.5625);
    border-bottom: 1px solid var(--wp--preset--color--outline);
  }

  .ap-header .ap-navbar {
    order: 2;
    flex: 0 0 auto;
    margin: 0;
    padding-inline: 0;
    border-bottom: 0;
    /* Preset background classes are emitted with !important. */
    background-color: transparent !important;
  }

  /* Core gives constrained groups `position: relative` via a zero-specificity
	   :where() rule. Clearing it here leaves `.ap-header` (already
	   `position: relative` — see the top of this file) as the containing
	   block for the full-width search panel below. */
  .ap-header .ap-header__main {
    order: 1;
    flex: 1 1 auto;
    position: static;
    margin: 0;
    padding-inline: 0;
    /* Overrides the block's own inline style (spacing--30 top/bottom), which
	   otherwise beats an external rule at equal specificity — hence !important. */
    padding-block: calc(var(--wp--preset--spacing--30) * 0.75) !important;
    min-width: 0;
  }

  .ap-header .ap-navbar__inner {
    min-height: 0;
    max-width: none;
    margin: 0;
  }

  .ap-header .ap-header__inner {
    flex-wrap: nowrap;
  }

  /* Brand stays in normal flow on the left; the search/cart icons are pushed
	   to the right edge of this row by the space-between below. */
  .ap-header__main .ap-header__inner {
    justify-content: space-between;
  }

  /* The cap has to reach the logo block and its link, not just the image.
	   Both size themselves to the image's intrinsic width — 360px here — so
	   capping only the image leaves the surrounding link/box wider than the
	   picture inside it.

	   In the editor that same box is a ResizableBox with a hard inline width and
	   height, which nothing but !important will shift. The class does not exist
	   on the front end, so the resizable-box rule below is editor-only in
	   practice. */
  .ap-header__main .wp-block-site-logo,
  .ap-header__main .wp-block-site-logo .custom-logo-link,
  .ap-header__main .wp-block-site-logo img {
    max-width: min(230px, 100%);
  }

  .ap-header__main .wp-block-site-logo .components-resizable-box__container {
    width: auto !important;
    height: auto !important;
    max-width: min(230px, 100%);
  }

  /* Account and support move into the overlay; the search toggle stays. The
	   Sale selector carries an extra class to outweigh the generated
	   .wp-block-buttons-is-layout-flex rule. */
  .ap-header__actions .ap-iconbtn:not(.ap-search-toggle),
  .ap-navbar .ap-navbar__cta {
    display: none;
  }

  .ap-header__actions .ap-search-toggle {
    display: inline-flex;
  }

  /* Search drops out of the flex row and becomes a panel under the header. */
  .ap-header__inner > .ap-search {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 30;
    display: none;
    max-width: none;
    margin: 0;
    padding: 0.75rem var(--wp--preset--spacing--40);
    background-color: var(--wp--preset--color--base);
    border-bottom: 1px solid var(--wp--preset--color--outline);
    box-shadow: 0 12px 28px rgba(30, 41, 59, 0.12);
  }

  .ap-header.is-search-open > .ap-header__main .ap-search {
    display: block;
  }

  /* The toggle takes the same circular treatment as the cart.
	   The class is repeated deliberately. On the front end core renders a plain
	   <button>, but in the editor it is a <Button> from wp-components, and
	   block-library/editor.css reaches for it with a doubled class of its own:
	   `.components-button.…-open.…-open { padding: 0; height: auto }`. That ties
	   with a two-class selector here and wins on source order, collapsing the
	   circle to 44x24. Repeating the class outweighs it in both contexts. */
  .ap-primary-nav
    .wp-block-navigation__responsive-container-open.wp-block-navigation__responsive-container-open {
    width: 2.75rem;
    height: 2.75rem;
    padding: 0;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background-color: var(--wp--preset--color--base-alt);
    color: var(--wp--preset--color--contrast);
  }
}

/* -------------------------------------------------------------------------
 * Hero (ap-fse-theme/hero pattern)
 *
 * Desktop only for now — the mobile treatment uses the image as a watermarked
 * background rather than a stacked column, and is pending its own design.
 * The markup supports that without duplication: the image stays a single
 * <img> that CSS can reposition behind the copy.
 *
 * Selectors carry .ap-hero so they sit above the single-class global-styles
 * rules in the editor.
 * ---------------------------------------------------------------------- */

/* No margin-block reset on the title or the body copy. They are flow children
   of the content column, so the column's Block spacing arrives as a
   margin-block-start — zeroing it here pinned the gaps to nothing and made the
   editor's Dimensions control look broken. The kicker keeps its reset because
   it sits in the flex eyebrow row, where spacing comes from `gap` instead. */
.ap-hero .ap-hero__kicker {
  margin-block: 0;
  line-height: 1;
}

.ap-hero .ap-hero__title {
  line-height: 0.95;
}

/* The dot between the kicker and the eyebrow. A marker rather than a block, so
   there is no empty paragraph for an editor to delete by accident. */
.ap-hero .ap-hero__eyebrow {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-block: 0;
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-weight: 700;
}

.ap-hero .ap-hero__eyebrow::before {
  content: "";
  flex: none;
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background-color: var(--wp--preset--color--contrast);
}

.ap-hero .ap-hero__text {
  max-width: 34rem;
  line-height: 1.65;
}

/* Radius comes from theme.json's button element, which every button in the
   theme now shares; only the hero's roomier padding and heavier weight are
   particular to it. */
.ap-hero .ap-hero__btn .wp-block-button__link {
  padding: 0.8rem 1.6rem;
  font-family: var(--wp--preset--font-family--ui);
  font-weight: 700;
}

/* Core draws the outline style with a 2px border in currentColor, which the
   text colour already supplies. */
.ap-hero .ap-hero__btn.is-style-outline .wp-block-button__link {
  border-width: 2px;
}

.ap-hero .ap-hero__trust-item,
.ap-producttrust__item {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-weight: 700;
}

.ap-hero .ap-hero__trust-item .ap-iconlink__icon svg,
.ap-producttrust__item .ap-iconlink__icon svg {
  width: 1.3rem;
  height: 1.3rem;
  color: var(--wp--preset--color--green);
}

/* A thick light frame and no drop shadow, as drawn. The border sits on the
   image rather than the figure so it follows the same rounded corners. */
.ap-hero .ap-hero__image img {
  width: 100%;
  height: auto;
  border: 6px solid var(--wp--preset--color--outline);
  border-radius: 18px;
}

/* Below the width at which core stacks the hero's two columns, the image stops
 * being a panel beside the copy and becomes a watermark behind it. No second
 * copy of the image and no markup change: the same <img> the editor picked is
 * lifted out of flow, stretched over the section and faded, so the media column
 * no longer takes part in the layout and the copy runs full width.
 * ---------------------------------------------------------------------- */

@media (max-width: 781.98px) {
  .ap-hero {
    position: relative;
    overflow: hidden;
  }

  /* The copy is raised above the watermark, not the columns wrapper. Making
	   the wrapper positioned would make it the containing block for the media
	   below, clipping the watermark to the content width instead of letting it
	   run the full width of the section. */
  .ap-hero .ap-hero__content {
    position: relative;
    z-index: 1;
  }

  .ap-hero .ap-hero__media {
    position: absolute;
    inset: 0;
    /* The Columns block is vertically centred, which puts align-self:center on
		   this column. Per flexbox, an absolutely positioned child of a flex
		   container aligns instead of stretching, so inset:0 alone left it zero
		   high and the image only covered the section by overflowing it. */
    align-self: stretch;
    z-index: 0;
    margin: 0;
    padding: 0;
    pointer-events: none;
  }

  /* The collection hero's media column holds the trust card, not an image, so
     the watermark treatment above lays a whole card over the copy. The design
     drops the card on mobile rather than stacking it, which resolves both:
     hidden, it takes no part in the layout and the copy runs full width.
     Declared after the rule it overrides, at equal specificity. */
  .ap-hero--collection .ap-hero__media {
    display: none;
  }

  /* The figure fills the media layer, so the image inside it has a definite
	   height to resolve against. Absolutely positioning the image itself instead
	   collapsed the figure to zero height and took the image with it. */
  .ap-hero .ap-hero__image {
    position: absolute;
    inset: 0;
    margin: 0;
  }

  /* The block's 4/3 aspect ratio is a desktop concern: it keeps the panel a
	   consistent shape whatever is uploaded. As a full-bleed watermark the image
	   has to fill the section instead, so the ratio is released here. It arrives
	   as an inline style from the block, which only !important can override. */
  .ap-hero .ap-hero__image img {
    width: 100%;
    height: 100%;
    aspect-ratio: auto !important;
    border: 0;
    border-radius: 0;
    object-fit: cover;
    opacity: 0.22;
  }

  /* Both calls to action go full width and stack, as in the design. Done here
	   rather than with the button block's own width attribute, which would make
	   them full width on desktop too. */
  .ap-hero .ap-hero__buttons {
    width: 100%;
  }

  .ap-hero .ap-hero__buttons .wp-block-button,
  .ap-hero .ap-hero__btn .wp-block-button__link {
    width: 100%;
  }

  .ap-hero .ap-hero__trust {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* -------------------------------------------------------------------------
 * Category archive description toggle
 *
 * Commerce Connect's category-description block hydrates client-side (see
 * assets/js/category-description-toggle.js), so this only styles the two
 * states — clamped and expanded — that the script switches between. The
 * clamp itself is mobile only; on wider screens the description always
 * shows in full and the toggle button is hidden, whether or not the script
 * added one.
 * ---------------------------------------------------------------------- */

@media (max-width: 781.98px) {
  .ecom-category-description {
    display: -webkit-box;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    line-clamp: 3;
  }

  .ecom-category-description.is-expanded {
    display: block;
    overflow: visible;
    -webkit-line-clamp: unset;
    line-clamp: unset;
  }

  .ap-desc-toggle {
    display: inline-block;
    margin-block-start: var(--wp--preset--spacing--30, 0.5rem);
    padding: 0;
    border: 0;
    background: none;
    color: var(--wp--preset--color--accent);
    font-family: var(--wp--preset--font-family--ui);
    font-weight: 700;
    text-decoration: underline;
    cursor: pointer;
  }
}

@media (min-width: 782px) {
  .ap-desc-toggle {
    display: none;
  }
}

/* -------------------------------------------------------------------------
 * Image + text section
 *
 * No margin-block resets on the title or copy: they are flow children of the
 * content column, so the column's Block spacing reaches them as a
 * margin-block-start and zeroing it would pin the gaps to nothing and make the
 * editor's Dimensions control look broken.
 * ---------------------------------------------------------------------- */

.ap-mediatext .ap-mediatext__image img {
  width: 100%;
  height: auto;
  border-radius: 16px;
}

.ap-mediatext .ap-mediatext__title {
  line-height: 1;
}

.ap-mediatext .ap-mediatext__text {
  line-height: 1.75;
}

.ap-mediatext .ap-mediatext__badge {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--wp--preset--color--contrast);
  font-weight: 700;
}

.ap-mediatext .ap-mediatext__badge .ap-iconlink__icon svg {
  width: 1.5rem;
  height: 1.5rem;
  color: var(--wp--preset--color--accent);
}

/* Icon-link labels are nowrap by default, which suits the short header and
   trust captions but would push this sentence out of the column. */
.ap-mediatext .ap-mediatext__badge .ap-iconlink__label {
  white-space: normal;
}

/* -------------------------------------------------------------------------
 * Post carousel
 *
 * How many cards are visible is set here rather than in JavaScript: Embla
 * measures whatever width the slides end up with, so the breakpoints stay
 * alongside the rest of the layout.
 *
 * The track is held to the container at both ends, matching the product
 * carousel on the same page — it used to run off the right of the page so the
 * row read as continuing, which left it a gutter wider than its neighbour. The
 * partial card at the right end still does that job, clipped at the container
 * edge instead of the window's.
 *
 * Slide width is pinned to the container, not to the viewport, so widening the
 * window reveals more cards instead of inflating the ones already there.
 *
 * Both arrows sit in the page gutter, pulled off their end of the container by
 * --ap-carousel-arrow-bleed.
 *
 * The page gutter is the only room an arrow has to move into, and how much
 * there is depends entirely on the window: on a wide screen the container is
 * centred and the gutter is generous, while below roughly 1470px the container
 * is gutter-bound and `main` leaves only its 1rem of global padding. So the
 * bleed is not a fixed number — it takes as much of the gutter as is going, up
 * to --ap-carousel-arrow-reach, and goes negative on narrow screens, tucking
 * the arrows back over the cards. Nudge the reach to reposition the arrows.
 * ---------------------------------------------------------------------- */

.ap-carousel {
  --ap-carousel-slides: 1.2;
  --ap-carousel-gap: 1.25rem;
  --ap-carousel-arrow-size: 2.75rem;
  --ap-carousel-arrow-bleed: -0.25rem;

  /* The container's own width, spelled out rather than taken as a percentage
	   because a percentage inside the track resolves against the track.
	   Built from the same variables the layout itself uses, so there are no
	   numbers to keep in step with theme.json: the content size, less the
	   global padding `main` applies at each edge. Getting it wrong changes card
	   width, never alignment — it was 100vw - 2rem, which understated the 24px
	   padding by 8px a side and pushed the last card past the container. */
  --ap-carousel-content: min(
    var(--wp--style--global--content-size),
    100vw - var(--wp--preset--spacing--40) * 2
  );

  position: relative;
}

.ap-carousel__viewport {
  overflow: hidden;
}

/* Embla's track. Core lays the post list out as a grid or a stack; it has to be
   a single non-wrapping row for the carousel to have anything to scroll.

   The spacing between cards is slide padding, not `gap`, because `gap` only
   applies between siblings. In loop mode Embla wraps the last slide round to
   sit before the first, and a gap cannot reach across that seam — the two cards
   would touch. Carrying the space inside each slide means every card brings its
   own, wherever Embla moves it to. The track is pulled left by the same amount
   so the first card still starts flush with the container. */
.ap-carousel .wp-block-post-template {
  display: flex;
  flex-wrap: nowrap;

  /* Core's layout CSS puts a blockGap on the post template. It has to be
	   zeroed, not just left alone: any residual gap here is spacing Embla
	   cannot carry across the loop seam, so the last and first cards would sit
	   closer together than every other pair. */
  gap: 0;
  margin: 0 0 0 calc(var(--ap-carousel-gap) * -1);
  padding: 0;
  list-style: none;
}

/* Each slide carries the spacing that precedes it, so the basis is one card
   plus one gap. Sizing off --ap-carousel-content keeps the card the same width
   whatever the bleed exposes to the right of it. */
.ap-carousel .wp-block-post-template > li {
  flex: 0 0
    calc(
      (
          var(--ap-carousel-content) - (var(--ap-carousel-slides) - 1) *
            var(--ap-carousel-gap)
        ) / var(--ap-carousel-slides) + var(--ap-carousel-gap)
    );

  /* Without this the basis sizes the content box and the gap is added on top,
	   so --ap-carousel-slides would understate how much room a slide takes and
	   fewer cards than it names would fit across the container. */
  box-sizing: border-box;
  min-width: 0;
  margin: 0;
  padding-left: var(--ap-carousel-gap);
}

@media (min-width: 600px) {
  .ap-carousel {
    --ap-carousel-slides: 2.2;
  }
}

@media (min-width: 900px) {
  .ap-carousel {
    --ap-carousel-slides: 4;
    --ap-carousel-gap: 1.5rem;
    --ap-carousel-arrow-size: 3.25rem;
    --ap-carousel-arrow-reach: 4.5rem;

    /* The gutter is (100vw - 100%) / 2, with 100% measured against
		   .ap-carousel because it is the arrows' containing block and matches
		   the content width. 100vw counts a classic scrollbar that 100% does
		   not, overstating the gutter by half a scrollbar's width; the 1.25rem
		   held back absorbs that and still leaves the arrow clear of the window
		   edge on platforms that have no scrollbar to spare. */
    --ap-carousel-arrow-bleed: min(
      var(--ap-carousel-arrow-reach),
      (100vw - 100%) / 2 - 1.25rem
    );
  }
}

.ap-carousel__arrow {
  position: absolute;
  /* Centred, matching the product carousel's arrows on the same page. */
  top: 50%;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--ap-carousel-arrow-size);
  height: var(--ap-carousel-arrow-size);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background-color: var(--wp--preset--color--red-orange);
  color: var(--wp--preset--color--base);
  cursor: pointer;
  transform: translateY(-50%);
  transition: background-color 0.15s ease;
}

.ap-carousel__arrow:hover,
.ap-carousel__arrow:focus-visible {
  background-color: var(--wp--preset--color--accent-dark);
}

.ap-carousel__arrow:disabled {
  opacity: 0.35;
  cursor: default;
}

.ap-carousel__arrow svg {
  width: 1.5rem;
  height: 1.5rem;
}

.ap-carousel__arrow--prev {
  left: calc(var(--ap-carousel-arrow-bleed) * -1);
}

/* One chevron icon serves both directions. */
.ap-carousel__arrow--prev svg {
  transform: rotate(180deg);
}

/* Mirrors the previous arrow: with the track held to the container there is
   gutter on this side to sit in, so both ends are placed the same way. */
.ap-carousel__arrow--next {
  right: calc(var(--ap-carousel-arrow-bleed) * -1);
}

/* -------------------------------------------------------------------------
 * Post card
 * ---------------------------------------------------------------------- */

/* Cards fill their slide and share a baseline: the excerpt is clamped and the
   call to action is pushed to the foot, so a long post and a short one produce
   the same card rather than a ragged row. */
.ap-postcard {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 0.75rem 0.75rem 1.25rem;
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 14px;
  background-color: var(--wp--preset--color--base);
}

.ap-postcard .ap-postcard__image img {
  width: 100%;
  border-radius: 10px;
}

.ap-postcard .ap-postcard__title {
  padding-inline: 0.5rem;
  line-height: 1.35;
}

.ap-postcard .ap-postcard__title a {
  color: var(--wp--preset--color--contrast);
  text-decoration: none;
}

.ap-postcard .ap-postcard__title a:hover,
.ap-postcard .ap-postcard__title a:focus-visible {
  color: var(--wp--preset--color--primary);
  text-decoration: underline;
}

.ap-postcard .ap-postcard__excerpt {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  padding-inline: 0.5rem;
  color: var(--wp--preset--color--contrast-muted);
  line-height: 1.6;
}

.ap-postcard .ap-postcard__more {
  margin-inline: 0.5rem;
  margin-top: auto;
}

.ap-postcard .ap-postcard__more a,
.ap-postcard a.ap-postcard__more {
  display: inline-block;
  padding: 0.7rem 1.35rem;
  border-radius: 8px;
  background-color: var(--wp--preset--color--red-orange);
  color: var(--wp--preset--color--base);
  font-family: var(--wp--preset--font-family--ui);
  font-weight: 700;
  text-decoration: none;
}

.ap-postcard .ap-postcard__more a:hover,
.ap-postcard a.ap-postcard__more:hover,
.ap-postcard .ap-postcard__more a:focus-visible,
.ap-postcard a.ap-postcard__more:focus-visible {
  background-color: var(--wp--preset--color--accent-dark);
}

/* -------------------------------------------------------------------------
 * Section rhythm
 *
 * Sections sit flush against each other by default; spacing between them is
 * opt-in per section via Dimensions > Margin in the editor. Without this the
 * root layout inserts the global block gap between the header, main and footer,
 * which shows as a white band above and below full-width coloured sections.
 * ---------------------------------------------------------------------- */

.wp-site-blocks > * + * {
  margin-block-start: 0;
}

/* The post carousel deliberately runs off the right of the page. Its width is
   worked out from 100vw, which counts a classic scrollbar that the container's
   own width does not, so on a platform with one the track can overshoot the
   page edge by a few pixels. Clipping here absorbs that rather than surfacing a
   horizontal scrollbar. `clip` and not `hidden`: hidden would force the block
   axis into its own scroll container and break sticky positioning down the
   page. */
.wp-site-blocks {
  overflow-x: clip;
}

/* -------------------------------------------------------------------------
 * Top categories
 *
 * The stepped layout is CSS multi-column, not grid. Grid would need the row
 * span of every card to be known up front to keep the column bottoms level;
 * multi-column balances the columns itself, so an editor can add, remove or
 * resize cards and the section stays tidy without any row arithmetic.
 *
 * Cards therefore carry a height rather than a row span. The three presets are
 * 312 / 396 / 480px, which is the 1 : 1.25 : 1.5 ratio the design uses.
 * ---------------------------------------------------------------------- */

.ap-catgrid {
  --ap-catgrid-gap: 1.5rem;
  --ap-catgrid-radius: 18px;

  /* The 1 : 1.25 : 1.5 ratio from the design. Named here rather than inlined
	   on the size classes so the narrow-screen fallback can reuse the medium
	   value without the two drifting apart. */
  --ap-catgrid-height-short: 19.5rem;
  --ap-catgrid-height-medium: 24.75rem;
  --ap-catgrid-height-tall: 30rem;
}

.ap-catgrid .ap-catgrid__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  margin-block-end: var(--ap-catgrid-gap);
}

.ap-catgrid .ap-catgrid__eyebrow {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  margin: 0;
  color: var(--wp--preset--color--contrast);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* The dot from the design. A pseudo-element so the eyebrow stays plain text. */
.ap-catgrid .ap-catgrid__eyebrow::before {
  content: "";
  flex: 0 0 auto;
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 50%;
  background-color: var(--wp--preset--color--red-orange);
}

.ap-catgrid .ap-catgrid__title {
  margin-block: 0.5rem 0;
  color: var(--wp--preset--color--red-orange);
  font-family: var(--wp--preset--font-family--display);
  font-size: var(--wp--preset--font-size--xx-large);
}

.ap-catgrid .ap-catgrid__explore {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--wp--preset--color--red-orange);
  font-weight: 700;
  text-decoration: none;
}

.ap-catgrid .ap-catgrid__explore:hover,
.ap-catgrid .ap-catgrid__explore:focus-visible {
  text-decoration: underline;
}

.ap-catgrid .ap-catgrid__arrow {
  transition: transform 0.15s ease;
}

.ap-catgrid .ap-catgrid__explore:hover .ap-catgrid__arrow {
  transform: translateX(3px);
}

.ap-catgrid .ap-catgrid__grid {
  /* Set per grid by render.php from the number of tiles; 3 is the homepage. */
  columns: var(--ap-catgrid-columns, 3);
  column-gap: var(--ap-catgrid-gap);

  /* Each card carries the row gap as a bottom margin — that is what separates
     the stacked cards within a column. The last card in every column carries
     it too, so the container closes a full gap below the cards and the grid
     ships with dead space under it. Pulling the container back by the same
     amount removes only that trailing gap; the ones between rows are inside
     the container and are untouched. */
  margin-block-end: calc(var(--ap-catgrid-gap) * -1);
}

/* -------------------------------------------------------------------------
 * Category card
 * ---------------------------------------------------------------------- */

.ap-catgrid .ap-catcard {
  --ap-catcard-height: var(--ap-catgrid-height-medium);
  --ap-catcard-overlay: var(--wp--preset--color--primary);
  --ap-catcard-overlay-opacity: 0.55;

  position: relative;
  display: block;
  height: var(--ap-catcard-height);

  /* Multi-column would otherwise slice a card across the column break. */
  break-inside: avoid;
  margin-block-end: var(--ap-catgrid-gap);
  border-radius: var(--ap-catgrid-radius);
  overflow: hidden;
  color: var(--wp--preset--color--base);
  text-decoration: none;
}

.ap-catgrid .ap-catcard.is-size-short {
  --ap-catcard-height: var(--ap-catgrid-height-short);
}

.ap-catgrid .ap-catcard.is-size-medium {
  --ap-catcard-height: var(--ap-catgrid-height-medium);
}

.ap-catgrid .ap-catcard.is-size-tall {
  --ap-catcard-height: var(--ap-catgrid-height-tall);
}

.ap-catgrid .ap-catcard__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;

  /* The photographs sit under a colour wash; taking some saturation out of
	   them first stops the two fighting each other. */
  filter: saturate(0.45);
}

/* Flat alpha rather than a blend mode: multiply would crush a dark photograph
   to black and the editor's opacity control would stop behaving predictably. */
.ap-catgrid .ap-catcard__wash {
  position: absolute;
  inset: 0;
  background-color: var(--ap-catcard-overlay);
  opacity: var(--ap-catcard-overlay-opacity);
}

/* Insurance for the text: a light photograph under a light wash would not give
   white type enough contrast on its own. */
.ap-catgrid .ap-catcard__scrim {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 55%;
  background-image: linear-gradient(to top, rgb(0 0 0 / 55%), transparent);
}

.ap-catgrid .ap-catcard__body {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  padding: 1.5rem;
}

.ap-catgrid .ap-catcard__title {
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.2;
}

.ap-catgrid .ap-catcard__subtitle {
  font-size: 0.9375rem;
  line-height: 1.4;
  opacity: 0.92;
}

/* A span dressed as a button. It is not focusable and takes no clicks of its
   own — the card's anchor already covers it — so it needs no hover state of its
   own either; it lights up with the card.

   These values mirror theme.json's button element and the accent/base colours
   the media-text pattern sets on its button, so the two read as the same
   control. Font size is deliberately left to inherit, exactly as a core button
   does. Kept in step by hand: a span cannot pick up wp-element-button. */
.ap-catgrid .ap-catcard__cta {
  align-self: flex-start;
  margin-block-start: 0.75rem;
  padding: 0.75rem 1.5rem;
  border-radius: 8px;
  background-color: var(--wp--preset--color--accent);
  color: var(--wp--preset--color--base);
  font-family: var(--wp--preset--font-family--ui);
  font-weight: 500;
  text-decoration: none;
  transition: background-color 0.15s ease;
}

.ap-catgrid .ap-catcard:hover .ap-catcard__cta,
.ap-catgrid .ap-catcard:focus-visible .ap-catcard__cta {
  background-color: var(--wp--preset--color--accent-dark);
}

.ap-catgrid .ap-catcard:hover .ap-catcard__image,
.ap-catgrid .ap-catcard:focus-visible .ap-catcard__image {
  transform: scale(1.03);
}

.ap-catgrid .ap-catcard__image {
  transition: transform 0.4s ease;
}

@media (prefers-reduced-motion: reduce) {
  .ap-catgrid .ap-catcard__image,
  .ap-catgrid .ap-catcard__cta,
  .ap-catgrid .ap-catgrid__arrow {
    transition: none;
  }
}

/* Below three columns the stagger stops earning its keep: with two columns it
   just goes ragged, and with one a tall card is a long scroll past a single
   photograph. Every card takes the medium height instead. */
@media (max-width: 1023.98px) {
  .ap-catgrid .ap-catgrid__grid {
    columns: 2;
  }

  .ap-catgrid .ap-catcard,
  .ap-catgrid .ap-catcard.is-size-short,
  .ap-catgrid .ap-catcard.is-size-medium,
  .ap-catgrid .ap-catcard.is-size-tall {
    --ap-catcard-height: var(--ap-catgrid-height-medium);
  }
}

@media (max-width: 599.98px) {
  .ap-catgrid .ap-catgrid__grid {
    columns: 1;
  }

  .ap-catgrid .ap-catgrid__head {
    align-items: flex-start;
  }
}

/* -------------------------------------------------------------------------
 * Logo gallery
 *
 * For rows of supplier and partner marks rather than photographs. Core's
 * gallery offers only crop-to-fill or natural size: the first slices the edges
 * off a wide logo, and the second leaves a square mark three times the height
 * of the ones beside it. Neither is what a logo row wants, so the cells are
 * given one height and each mark is fitted whole inside its own.
 *
 * Opt in with the class rather than restyling every gallery — a gallery of
 * photographs still wants core's behaviour.
 * ---------------------------------------------------------------------- */

.ap-logos.wp-block-gallery.has-nested-images figure.wp-block-image img {
  width: 100%;
  height: var(--ap-logos-height, 5.5rem);
  object-fit: contain;
  object-position: center;
}

/* Vertically centre each mark in its cell: core stretches the figure to the
   tallest in the row, which would otherwise sit every logo along the top. */
.ap-logos.wp-block-gallery.has-nested-images figure.wp-block-image {
  align-self: center;
}

@media (max-width: 599.98px) {
  .ap-logos.wp-block-gallery.has-nested-images figure.wp-block-image img {
    height: var(--ap-logos-height-mobile, 4rem);
  }
}

/* -------------------------------------------------------------------------
 * Map
 *
 * The height comes through a custom property set per block, so an editor's
 * Height control lands here rather than as an inline style on the iframe —
 * which keeps the narrow-screen cap below able to win.
 * ---------------------------------------------------------------------- */

.ap-map {
  overflow: hidden;

  /* The same radius the category tiles and the success card use. */
  border-radius: 18px;

  /* Shows through while the lazy iframe is still loading, so the space does
     not read as a hole in the page. */
  background-color: var(--wp--preset--color--base-alt);
}

.ap-map .ap-map__frame {
  display: block;
  width: 100%;
  height: var(--ap-map-height, 26rem);
  border: 0;
}

/* A map tall enough to be useful on a desktop is most of a phone screen, and
   the reader still has to get past it to the rest of the page. */
@media (max-width: 599.98px) {
  .ap-map .ap-map__frame {
    height: min(var(--ap-map-height, 26rem), 15rem);
  }
}

/* -------------------------------------------------------------------------
 * Newsletter
 *
 * The gradient is not set here: the section is an ordinary group carrying the
 * theme's ap-orange preset, so an editor can change it from the palette.
 * ---------------------------------------------------------------------- */

.ap-newsletter .ap-newsletter__eyebrow {
	display: inline-block;
	margin: 0;
	padding: 0.4rem 1rem;
	border-radius: 999px;
	background-color: rgb(255 255 255 / 22%);
	color: var(--wp--preset--color--base);
	font-family: var(--wp--preset--font-family--ui);
	font-size: 0.8125rem;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
}

/* No margin-block reset on these. Flow layout delivers the group's blockGap as
   margin-block-start on each following child, so zeroing the margin here would
   silently collapse the spacing between the eyebrow, heading and lede to
   nothing — and leave it unadjustable from the editor. */
.ap-newsletter .ap-newsletter__title {
	color: var(--wp--preset--color--base);
	font-family: var(--wp--preset--font-family--display);
}

.ap-newsletter .ap-newsletter__lede {
	color: var(--wp--preset--color--base);
}

.ap-newsletter__form-wrap {
	width: 100%;
	max-width: 34rem;
}

/* Editor only. The form is still drawn while unconfigured so the placeholder
   and button label remain editable, but it is dimmed and made non-interactive
   so it reads as a preview of something that will not publish yet — rather than
   contradicting the warning sitting directly above it. */
.ap-newsletter__form-wrap.is-inactive .ap-newsletter__form {
	opacity: 0.5;
	pointer-events: none;
}

.ap-newsletter__form-wrap .ap-newsletter__form {
	display: flex;
	flex-wrap: wrap;
	gap: 0.75rem;
	align-items: stretch;
}

/* The field grows, the button keeps its own width. min-width lets it shrink
   below its content before wrapping, which stops it forcing a scrollbar. */
.ap-newsletter__form-wrap .ap-newsletter__field {
	position: relative;
	flex: 1 1 14rem;
	min-width: 0;
	display: flex;
	align-items: center;
}

.ap-newsletter__form-wrap .ap-newsletter__icon {
	position: absolute;
	inset-inline-start: 1rem;
	display: flex;
	color: var(--wp--preset--color--contrast-muted);
	pointer-events: none;
}

.ap-newsletter__form-wrap .ap-newsletter__icon svg {
	width: 1.25rem;
	height: 1.25rem;
}

.ap-newsletter__form-wrap .ap-newsletter__input,
.ap-newsletter__form-wrap .ap-newsletter__placeholder {
	display: block;
	width: 100%;
	padding: 0.9rem 1rem 0.9rem 3rem;
	border: 0;
	border-radius: 8px;
	background-color: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
	font-family: var(--wp--preset--font-family--body);

	/* Held at 1rem rather than inheriting the fluid body size: below 16px iOS
	   Safari zooms the page when the field takes focus. */
	font-size: 1rem;
}

.ap-newsletter__form-wrap .ap-newsletter__input::placeholder,
.ap-newsletter__form-wrap .ap-newsletter__placeholder {
	color: var(--wp--preset--color--contrast-muted);
}

.ap-newsletter__form-wrap .ap-newsletter__input:focus-visible {
	outline: 2px solid var(--wp--preset--color--contrast);
	outline-offset: 2px;
}

/* Dark rather than the theme's accent: the button sits on the orange gradient,
   where an accent-coloured button would all but disappear. The radius is the
   theme's button radius, kept in step by hand. */
.ap-newsletter__form-wrap .ap-newsletter__button {
	flex: 0 0 auto;
	padding: 0.9rem 1.75rem;
	border: 0;
	border-radius: 8px;
	background-color: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
	font-family: var(--wp--preset--font-family--ui);

	/* Form controls do not inherit type from the page, so a button left alone
	   renders at the browser's own size. .ap-catcard__cta sets no size and
	   simply inherits; this is how a <button> says the same thing. */
	font-size: inherit;
	font-weight: 700;
	cursor: pointer;
	transition: background-color 0.15s ease;
}

.ap-newsletter__form-wrap .ap-newsletter__button:hover,
.ap-newsletter__form-wrap .ap-newsletter__button:focus-visible {
	background-color: var(--wp--preset--color--primary-dark);
}

/* Dimmed only while a submission is in flight, which is the one case where
   fading it says something useful.

   Not on :disabled, because the button is also disabled in the preview an
   editor sees before Klaviyo is connected — and washing it out there gets in
   the way of the very thing that preview exists for, checking the design. */
.ap-newsletter__form-wrap .ap-newsletter__form.is-sending .ap-newsletter__button {
	opacity: 0.6;
}

.ap-newsletter__form-wrap .ap-newsletter__button:disabled {
	cursor: default;
}

/* Off-screen rather than display:none, so a browser autofilling every field
   still reaches the honeypot and trips the trap — and so the input's label is
   still read out.

   The theme defines this rather than leaning on core's .screen-reader-text:
   that class is only present when the block library stylesheet happens to be
   enqueued, and a label that fails to hide breaks the row it sits in. */
.ap-visually-hidden,
.ap-newsletter__form-wrap .ap-newsletter__hp {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	border: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

.ap-newsletter__form-wrap .ap-newsletter__status {
	flex: 1 0 100%;
	margin-block: 0;
	color: var(--wp--preset--color--base);
	font-size: 0.9375rem;
}

.ap-newsletter__form-wrap .ap-newsletter__status:empty {
	display: none;
}

.ap-newsletter__form-wrap .ap-newsletter__status[data-state="error"] {
	font-weight: 700;
}

/* Success is the one state worth building out: the fields have gone, so this
   message is all that is left standing in the band, and a line of plain white
   text on the gradient reads as though something is still loading.

   Borrowed wholesale from the collection hero's badge card — white ground, the
   theme's outline hairline, 18px radius, mark above the words, everything
   centred — so the two read as the same component rather than two takes on it.

   The mark is a masked pseudo-element, not an <svg>: view.js sets the message
   with textContent, which replaces every child the element has. A pseudo is
   outside that, so it survives each update.

   box-sizing because the theme has no global reset — without it the padding
   would be added to the 100% flex-basis below and push the card out of the row.

   The error state deliberately keeps the plain treatment. A failure is worth
   re-reading and retrying, and a card that says "we're done here" above a form
   that is still waiting for you would be telling the opposite story. */
.ap-newsletter__form-wrap .ap-newsletter__status[data-state="success"] {
	box-sizing: border-box;
	padding: var(--wp--preset--spacing--50) var(--wp--preset--spacing--40);
	background-color: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--outline);
	border-radius: 18px;
	color: var(--wp--preset--color--contrast);
	font-size: 1rem;
	text-align: center;
}

.ap-newsletter__form-wrap .ap-newsletter__status[data-state="success"]::before {
	content: "";
	display: block;
	width: 2.5rem;
	height: 2.5rem;
	margin: 0 auto var(--wp--preset--spacing--30);

	/* Green rather than the hero card's accent. This is the same `check` glyph
	   the hero and PDP trust rows use, and they set it green — a confirmation
	   reads as confirmation across the site, where accent is the brand's
	   attention colour and is doing a different job. */
	background-color: var(--wp--preset--color--green);
	-webkit-mask: var(--ap-icon-check) center / contain no-repeat;
	mask: var(--ap-icon-check) center / contain no-repeat;
}

/* Once it has worked there is nothing left to fill in, so the fields go and the
   message stands on its own. */
.ap-newsletter__form-wrap .ap-newsletter__form.is-done .ap-newsletter__field,
.ap-newsletter__form-wrap .ap-newsletter__form.is-done .ap-newsletter__button {
	display: none;
}

@media (max-width: 781px) {
	.ap-newsletter__form-wrap {
		max-width: none;
	}

	.ap-newsletter__form-wrap .ap-newsletter__button {
		flex: 1 1 100%;
	}
}

/* -------------------------------------------------------------------------
 * Single post
 *
 * Posts have no template of their own until now, so they fell through to
 * index.html and its query loop rendered the one post as a card. single.html
 * replaces that with a real article; this only rounds the lead image to match
 * the radius the cards use.
 * ---------------------------------------------------------------------- */

.ap-postheader__image img {
  border-radius: 14px;
}

/* -------------------------------------------------------------------------
 * Page shell
 *
 * Every template wraps its content in the same <main>. Its vertical rhythm
 * lives here rather than as an inline style repeated in each template file —
 * where it had already drifted into two different top margins without anyone
 * deciding to.
 *
 * Qualified with the element (main.ap-main, 0-1-1) so it outranks
 * `.wp-site-blocks > * + *` (0-1-0), which zeroes the block-start margin of
 * every top-level section. A bare .ap-main would tie on specificity and lose to
 * whichever rule came later in the file.
 * ---------------------------------------------------------------------- */

main.ap-main {
  margin-block: var(--wp--preset--spacing--50) var(--wp--preset--spacing--60);
}

/* The two product archives sit closer to the header than everything else. Kept
   as it was, but named: a modifier is a decision someone can review, where the
   same number pasted into two files is just drift. */
main.ap-main--tight {
  margin-block-start: var(--wp--preset--spacing--40);
}

/* The front page is built from sections that carry their own spacing. */
main.ap-main--flush {
  margin-block: 0;
}

/* -------------------------------------------------------------------------
 * Footer
 *
 * The footer sits on the dark `contrast` background, so its muted text is white
 * at reduced alpha rather than the palette's contrast-muted, which is a mid
 * slate and all but vanishes against navy.
 *
 * Column counts are block attributes (grid layout), so the editor lays the
 * footer out exactly as the front end does. The media queries below only step
 * those counts down for narrower screens.
 *
 * Note there is no `margin: 0` reset on any child here. Flow layout applies the
 * parent's blockGap as a margin-block-start on each following child, so zeroing
 * it silently deletes the spacing above that element.
 * ---------------------------------------------------------------------- */

.ap-footer {
  --ap-footer-muted: rgba(255, 255, 255, 0.64);
  --ap-footer-rule: rgba(255, 255, 255, 0.14);
  --ap-footer-chip: rgba(255, 255, 255, 0.08);
}

.ap-footer .ap-footer__title,
.ap-footer .ap-footer__heading {
  margin-block: 0;
  color: var(--wp--preset--color--base);
  font-family: var(--wp--preset--font-family--ui);
  font-weight: 700;
}

/* Category headings are the one orange note in the directory. */
.ap-footer .ap-footer__cat {
  margin-block: 0;
  font-family: var(--wp--preset--font-family--ui);
  font-weight: 700;
}

/* The category heading is a link now that each column is a real category, the
   same as the mega menu's column headings. Colour is inherited so the heading
   keeps the accent it gets from its own textColor attribute rather than taking
   link styling — only the underline marks it as clickable, because a hover
   colour would be a no-op here: the list links below hover *to* accent, which
   is what the heading already is. */
.ap-footer .ap-footer__cat a {
  color: inherit;
  text-decoration: none;
}

.ap-footer .ap-footer__cat a:hover,
.ap-footer .ap-footer__cat a:focus-visible {
  text-decoration: underline;
}

.ap-footer .ap-footer__list {
  margin-block-end: 0;
  padding-left: 0;
  list-style: none;
}

/* core/list has no blockGap support, so unlike the groups around it this
   spacing cannot be declared as a block attribute — it has to live here. */
.ap-footer .ap-footer__list li + li {
  margin-top: var(--wp--preset--spacing--30);
}

.ap-footer .ap-footer__list a {
  color: var(--ap-footer-muted);
  text-decoration: none;
}

.ap-footer .ap-footer__list a:hover,
.ap-footer .ap-footer__list a:focus-visible {
  color: var(--wp--preset--color--accent);
  text-decoration: underline;
}

.ap-footer .ap-footer__rule {
  width: 100%;
  border: 0;
  border-top: 1px solid var(--ap-footer-rule);
  background: none;
  opacity: 1;
}

/* The first column carries the logo and blurb, so it gets more room than the
   three link columns beside it. */
.ap-footer .ap-footer__main {
  grid-template-columns: 1.4fr 1fr 1fr 1fr;
}

/* No margin-block reset on these three: they sit in flow groups, where the
   parent's blockGap arrives as a margin-block-start. Zeroing it collapses the
   address onto the phone and email lines. */
.ap-footer .ap-footer__blurb,
.ap-footer .ap-footer__address,
.ap-footer .ap-footer__contact {
  color: var(--ap-footer-muted);
  line-height: 1.6;
}

/* The legal row is flex, so its gap comes from `gap` and the paragraph's own
   default margins would only push it off centre. */
.ap-footer .ap-footer__copyright {
  margin-block: 0;
  color: var(--ap-footer-muted);
  line-height: 1.6;
}

.ap-footer .ap-footer__contact a {
  color: var(--ap-footer-muted);
  text-decoration: underline;
}

.ap-footer .ap-footer__contact a:hover,
.ap-footer .ap-footer__contact a:focus-visible {
  color: var(--wp--preset--color--base);
}

.ap-footer .ap-footer__about .ap-footer__logo img {
  display: block;
  height: auto;
}

/* Core's logos-only style strips the chip behind each icon and tints the glyph
   with the service's brand colour. The design wants a soft neutral chip and a
   white glyph instead, at three classes so it outranks core's two. */
.ap-footer .ap-footer__social .wp-social-link a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border-radius: 50%;
  background-color: var(--ap-footer-chip);
  color: var(--wp--preset--color--base);
}

.ap-footer .ap-footer__social .wp-social-link svg {
  width: 1.0625rem;
  height: 1.0625rem;
  fill: currentColor;
}

.ap-footer .ap-footer__social .wp-social-link a:hover,
.ap-footer .ap-footer__social .wp-social-link a:focus-visible {
  background-color: var(--wp--preset--color--accent);
  color: var(--wp--preset--color--contrast);
}

.ap-footer .ap-footer__tagline {
  margin-block: 0;
  line-height: 1;
}

/* Below the desktop breakpoint the four bottom columns stack and the legal row
   centres, which is the arrangement in the mobile design. */
@media (max-width: 899.98px) {
  .ap-footer .ap-footer__main {
    grid-template-columns: minmax(0, 1fr);
  }

  .ap-footer .ap-footer__legal {
    flex-direction: column;
    justify-content: center;
    text-align: center;
  }
}

/* Six columns need roughly 1200px before the longest headings stop wrapping —
   "Swings & Movement" goes first, at about 150px per column. */
@media (max-width: 1199.98px) {
  .ap-footer .ap-footer__product-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 599.98px) {
  .ap-footer .ap-footer__product-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* -------------------------------------------------------------------------
 * Collection / PDP layouts
 * ---------------------------------------------------------------------- */

@media (max-width: 781px) {
  .ap-collection__sidebar {
    flex-basis: 100% !important;
  }
}

/* -------------------------------------------------------------------------
 * Commerce Connect block layout
 *
 * The plugin's blocks bring their own internal styling (ecom-ui-styles). These
 * rules only reconcile them with the theme's layout and spacing.
 * ---------------------------------------------------------------------- */

.ap-collection__toolbar {
  align-items: center;
  margin-bottom: var(--wp--preset--spacing--30);
}

/* Two-column toolbar only — the category archive's filter-free variant, which is
   just count + sort. Pushes sort to the right without squeezing the four-column
   variant on the product collection template. */
.ap-collection__toolbar:has(> .wp-block-column:nth-child(2):last-child)
  > .wp-block-column {
  flex-grow: 0;
}

.ap-collection__toolbar:has(> .wp-block-column:nth-child(2):last-child)
  > .wp-block-column:last-child {
  margin-left: auto;
}

/* products / product-details-page / account all render an alignwide wrapper.
   Inside an already-constrained group that double-constrains the content. */
.wp-block-commerce-connect-products.alignwide,
.wp-block-commerce-connect-product-details-page.alignwide,
.ecom-account-container.alignwide {
  max-width: 100%;
}

/* category-breadcrumb renders a bare <nav>, not a get_block_wrapper_attributes()
   wrapper, so it carries no wp-block-* class to hook onto. */
.category-breadcrumb-block {
  margin-bottom: var(--wp--preset--spacing--30);
  font-size: var(--wp--preset--font-size--small);
}

.category-breadcrumb-block__list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.category-breadcrumb-block__item:not(:last-child)::after {
  content: "/";
  margin-left: 0.5rem;
  color: var(--wp--preset--color--outline);
}

.category-breadcrumb-block__item--current {
  color: var(--wp--preset--color--contrast-muted);
}

/* -------------------------------------------------------------------------
 * Ollie Menu Designer dropdown menus
 * ---------------------------------------------------------------------- */

/* The plugin shows its own "X" close button on hover/focus of the dropdown
   panel. Our menus already close via the toggle, clicking outside, or
   Escape, so this is a redundant affordance we don't want. The class is
   repeated to outweigh the plugin's own `:hover`/`:focus` rules (each is
   two classes plus one pseudo-class) regardless of which stylesheet the
   browser happens to load last. */
.wp-block-ollie-mega-menu__menu-container
  .menu-container__close-button.menu-container__close-button.menu-container__close-button {
  display: none;
}

/* -------------------------------------------------------------------------
 * Mega menu — shared
 *
 * Used by the mega-menu-columns, mega-menu-featured and mega-menu-mobile
 * template parts, which Ollie Menu Designer renders inside its own
 * .wp-block-ollie-mega-menu__menu-container (desktop) or injects into the
 * navigation overlay (mobile).
 * ---------------------------------------------------------------------- */

/* The panel's own width drives its layout, not the viewport's: Ollie can render
   this part at content, wide, full or a custom width, and the viewport tells us
   nothing about which. */
.ap-mm {
  container-type: inline-size;
  container-name: apmm;
  background-color: var(--wp--preset--color--base);
}

/* The panel is white on a white page, so without a shadow its only edge is the
 * rounded bottom corners and it reads as part of the page rather than over it.
 *
 * Two layers: a wide, soft one that does the lifting, and a tight one that
 * keeps the edge defined where the spread of the first is too diffuse to show.
 * Tinted with the theme's contrast rather than pure black — against the warm
 * creams used elsewhere on the page, black casts grey and looks dirty.
 *
 * Scoped to Ollie's desktop dropdown container: the same part is injected into
 * the navigation overlay on mobile, where it is a full-height panel with
 * nothing behind it to cast onto. */
.wp-block-ollie-mega-menu__menu-container .ap-mm {
  box-shadow:
    0 18px 40px -12px rgb(30 41 59 / 18%),
    0 4px 10px -4px rgb(30 41 59 / 10%);
}

.ap-mm__body {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1.5rem, 3vw, 3rem);
  align-items: flex-start;
  padding: clamp(1.5rem, 3vw, 2.75rem);
}

/* These are laid out here as grid/flex while WordPress still marks them
   is-layout-flow, whose `* + *` rule adds a block-start margin to every child
   except the first — which knocks the first column out of line with the rest. */
.ap-mm .ap-mm__body > *,
.ap-mm .ap-mm__cols > *,
.ap-mm .ap-mm__promos > * {
  margin-block: 0;
}

/* auto-fill, not auto-fit. Every panel is the full header content width, so a
   group with two categories has spare tracks — and auto-fit would collapse
   those empty tracks and let the two remaining columns stretch to fill the
   panel, giving 636px columns holding one short list each. auto-fill keeps the
   empty tracks, so a column is 298px whether its panel holds two of them or
   nine, and the columns line up across every menu.

   16rem is that column width rounded down: in the 1312px content area it
   resolves to four tracks of 298px, which is what the fixed repeat(4) it
   replaced produced. Deriving it rather than naming it is what lets the same
   rule wrap nine columns onto three rows and lay two out on one. */
.ap-mm__cols {
  display: grid;
  flex: 1 1 34rem;
  grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr));
  gap: clamp(1rem, 2.5vw, 2.5rem);
}

/* Coloured rule against each column heading. */
.ap-mm .ap-mm__heading,
.ap-acc__item .ap-acc__heading {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0 0 1rem;
  color: var(--wp--preset--color--contrast);
  line-height: 1.3;
}

.ap-mm__heading::before {
  content: "";
  flex: none;
  width: 4px;
  height: 1.1em;
  border-radius: 2px;
  background-color: var(--ap-mm-accent, var(--wp--preset--color--primary));
}

.ap-mm__heading--blue,
.ap-acc__heading.ap-mm__heading--blue {
  --ap-mm-accent: var(--wp--preset--color--primary);
}

.ap-mm__heading--amber,
.ap-acc__heading.ap-mm__heading--amber {
  --ap-mm-accent: var(--wp--preset--color--accent);
}

.ap-mm__heading--green,
.ap-acc__heading.ap-mm__heading--green {
  --ap-mm-accent: var(--wp--preset--color--green);
}

.ap-mm__heading--red,
.ap-acc__heading.ap-mm__heading--red {
  --ap-mm-accent: var(--wp--preset--color--red-orange);
}

/* Each column heading is a link now that a column is a real category rather
   than a made-up grouping. It should still read as the heading it replaced,
   so colour and weight are inherited and the underline is kept for hover —
   the same treatment the links below it get, so the column behaves as one
   consistent list with a title on top. */
.ap-mm__heading a {
  color: inherit;
  text-decoration: none;
}

.ap-mm__heading a:hover,
.ap-mm__heading a:focus-visible {
  color: var(--wp--preset--color--primary);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.ap-mm__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.ap-mm__list li {
  margin: 0 0 0.7rem;
  line-height: 1.35;
}

.ap-mm__list a {
  color: var(--wp--preset--color--contrast);
  text-decoration: none;
}

.ap-mm__list a:hover,
.ap-mm__list a:focus-visible {
  color: var(--wp--preset--color--primary);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* The design sets white on AP Green, which is 2.1:1 — Slate Charcoal on the
   same green is 6.9:1, matching the treatment used on the cart count. */
.ap-badge {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.1em 0.5em;
  border-radius: 4px;
  background-color: var(--wp--preset--color--green);
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  line-height: 1.6;
  text-transform: uppercase;
  vertical-align: middle;
}

/* -------------------------------------------------------------------------
 * Mega menu — promo cards
 * ---------------------------------------------------------------------- */

.ap-mm__promos {
  display: flex;
  flex: 1 1 22rem;
  max-width: 40rem;
  gap: 1.25rem;
}

.ap-promo {
  flex: 1 1 0;
  border-radius: 12px;
  overflow: hidden;
  color: #ffffff;
}

.ap-promo .wp-block-cover__inner-container {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 1.5rem;
}

/* Scoped to the card, not to a panel. These were tied to .ap-mm, which is the
   desktop panel wrapper only — so the same card rendered in the mobile slide-in
   panel (.ap-mobilemenu) lost its kicker pill, its title sizing and its margin
   resets. Keying off .ap-promo keeps one card looking the same wherever it is
   placed, including on a page if an editor inserts the pattern there.

   The bottom margin is a real length, not `auto`. An auto margin here resolved
   to 0 in every context because the cover's inner container is shrink-to-fit,
   so it never had free space to absorb — the pill's only separation was the
   container's 9.6px flex gap. */
.ap-promo .ap-promo__kicker {
  margin: 0 0 0.5rem;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  background-color: var(--wp--preset--color--base);
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  line-height: 1.4;
  text-transform: uppercase;
}

.ap-promo .ap-promo__title {
  margin: 0;
  color: #ffffff;
  font-size: clamp(1.75rem, 2.5vw, 2.25rem);
  font-weight: 400;
  line-height: 1.05;
}

.ap-promo .ap-promo__text {
  margin: 0;
  color: #ffffff;
  line-height: 1.45;
}

.ap-promo .ap-promo__link {
  margin: 0.3rem 0 0;
}

.ap-promo__link a {
  color: #ffffff;
  font-family: var(--wp--preset--font-family--ui);
  font-weight: 700;
  text-decoration: none;
}

.ap-promo__link a:hover,
.ap-promo__link a:focus-visible {
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.ap-promo__buttons {
  margin-top: 0.3rem;
}

.ap-promo__button .wp-block-button__link {
  padding: 0.65rem 1.35rem;
  border-radius: 6px;
  font-family: var(--wp--preset--font-family--ui);
  font-weight: 700;
}

/* -------------------------------------------------------------------------
 * Mega menu — footer bar
 * ---------------------------------------------------------------------- */

.ap-mm__footer {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: center;
  justify-content: space-between;
  padding: 1rem clamp(1.5rem, 3vw, 2.75rem);
  border-top: 1px solid var(--wp--preset--color--outline);
  background-color: var(--wp--preset--color--base-alt);
}

.ap-mm__utility {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem clamp(1rem, 2.5vw, 2rem);
  align-items: center;
}

.ap-mm__utility-link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  text-decoration: none;
}

.ap-mm__utility-link svg {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
  color: var(--wp--preset--color--contrast-muted);
}

.ap-mm__utility-link:hover,
.ap-mm__utility-link:focus-visible {
  color: var(--wp--preset--color--primary);
}

.ap-mm__utility-link:hover svg,
.ap-mm__utility-link:focus-visible svg {
  color: var(--wp--preset--color--primary);
}

.ap-mm__tagline.ap-mm__tagline {
  margin: 0;
  font-size: 1.5rem;
  line-height: 1;
}

/* -------------------------------------------------------------------------
 * Mega menu — desktop container
 *
 * Ollie renders the part inside .wp-block-ollie-mega-menu__menu-container.
 * The part supplies its own padding and footer bar, so the container only
 * needs the frame.
 * ---------------------------------------------------------------------- */

.wp-block-ollie-mega-menu__menu-container .ap-mm {
  border-radius: 0 0 12px 12px;
  overflow: hidden;
}

/* One promo card gets more room than two. */
.ap-mm--featured .ap-mm__promos {
  flex-basis: 22rem;
}

.ap-mm--featured .ap-mm__promos .ap-promo {
  min-height: 26.25rem;
}

/* Four 250px columns plus two 370px cards need roughly 1800px of panel before
   they sit side by side. Below that the cards drop underneath and run across,
   which gives the columns the full width and turns the cards from tall strips
   into landscape panels. */
@container apmm (max-width: 1800px) {
  .ap-mm__cols,
  .ap-mm__promos {
    flex-basis: 100%;
    max-width: none;
  }
}

/* -------------------------------------------------------------------------
 * Mega menu — panel alignment
 *
 * Ollie positions the dropdown panel against its own menu item, so a panel on
 * the second or third item opens indented by however far along the bar that
 * item sits — and then gets clipped where it runs past the viewport. Ollie's
 * own "justify left" option does not help: its left:0 is still measured from
 * the item.
 *
 * So the containing block is set here explicitly. Taking the item and the nav
 * out of the positioning flow makes .ap-navbar__inner the reference, and that
 * box IS the content container: its left edge is the container's left edge and
 * its bottom edge is the bar's bottom edge. left:0 and top:100% therefore line
 * every panel up with the container and sit it flush under the bar, whichever
 * item opened it and whatever the bar's height.
 *
 * top needs !important only because Ollie writes topSpacing to the element as
 * an inline style. With this rule in place topSpacing no longer does anything,
 * which is deliberate — it was a hand-tuned number that had to be re-entered
 * correctly on every new dropdown, and getting it wrong overlapped the bar.
 * ---------------------------------------------------------------------- */

@media (min-width: 900px) {
  .ap-navbar .ap-navbar__inner {
    position: relative;
  }

  /* Core positions .wp-block-navigation__responsive-dialog, which sits between
	   the nav and the menu items and is only as tall as the items — that was the
	   real reference box, not the nav. Everything between the inner group and
	   the panel has to be taken out of the flow. .is-menu-open is excluded so the
	   mobile overlay keeps its fixed positioning. */
  .ap-navbar .ap-primary-nav,
  .ap-navbar .wp-block-navigation__responsive-container:not(.is-menu-open),
  .ap-navbar .wp-block-navigation__responsive-dialog,
  .ap-navbar .wp-block-ollie-mega-menu {
    position: static;
  }

  /* Four classes to outrank the plugin's own
	   .wp-block-navigation .…__menu-container.menu-justified-* rules, which are
	   three and would otherwise win or tie depending on stylesheet order. */
  .ap-navbar
    .ap-navbar__inner
    .wp-block-navigation
    .wp-block-ollie-mega-menu__menu-container {
    /* +1px clears the bar's own border-bottom, which is part of .ap-navbar's
		   box but not of .ap-navbar__inner's. Without it the panel covers the
		   border across its own width and the rule appears broken at the sides. */
    top: calc(100% + 1px) !important;
    left: 0;
    right: auto;
  }

  /* Ollie gives a content-width panel a fixed width in pixels taken from the
	   global content size. That only equals the container once the viewport is
	   wide enough for the container to reach its cap — below that the container
	   shrinks to the viewport minus its padding while the panel does not, and
	   the panel overhangs on the right. Measuring against the container instead
	   keeps both edges aligned at every width. */
  .ap-navbar
    .ap-navbar__inner
    .wp-block-navigation
    .wp-block-ollie-mega-menu__menu-container.menu-width-content {
    width: 100%;
    max-width: 100%;
  }
}

/* -------------------------------------------------------------------------
 * Mega menu — Shop All (columns variant)
 *
 * This menu carries its promo card as the fourth item of the link grid rather
 * than in a row of its own, so the card reflows with the columns instead of
 * sitting underneath them: 3 links + card, then 2 up, then 1. The card's
 * min-height is only a floor — as a grid item it stretches to the height of
 * the tallest link column.
 *
 * Scoped to .ap-mm--columns throughout: the featured variant still uses a
 * separate .ap-mm__promos row, and .ap-mm--columns is two classes, which also
 * keeps these rules above the single-class global-styles rules in the editor.
 * ---------------------------------------------------------------------- */

/* Targets any promo card in the grid rather than a bespoke class, so a card
   inserted from the Mega Menu Promo pattern is sized without the editor having
   to add anything. Same reason for the two rules further down. */
.ap-mm--columns .ap-mm__cols > .ap-promo {
  align-self: stretch;
  min-height: 13.75rem;
}

/* The column count used to be hand-set here at three container widths. It is
   derived from the track size now, which matters because a panel's width no
   longer implies a narrow viewport: the grouped menus set their own width, and
   the three-column one is 1062px — under the old 1100px breakpoint, so it was
   being folded to two columns and wrapping onto a second row on a full-size
   screen. auto-fit gives 4/3/2/1 columns at the widths these panels use. */

/* -------------------------------------------------------------------------
 * Mobile menu
 *
 * Injected into the navigation overlay by Ollie's render_block filter, which
 * hides everything after it — so this part carries the whole overlay.
 * ---------------------------------------------------------------------- */

.ap-mobilemenu {
  display: flex;
  flex-direction: column;
  margin: 0 auto;
  max-width: 34rem;
}

.ap-mobilemenu__brand {
  min-width: 0;
}

/* The cap has to reach the logo block and its link, not just the image — see
   the identical note on the header's own logo cap in the 899.98px media query
   above. Capping only the image left the wrapper sized off the image's
   unresolved 70%-of-parent width instead of its px ceiling, rendering it
   noticeably smaller than intended.

   200px rather than the header's 230px: the header logo is squeezed fluid by
   three icons' worth of reserved space in its row (~199px at a 390px
   viewport), while this bar only reserves space for the close button, so a
   230px cap here would render visibly larger than the header at most phone
   widths. 200px tracks the header's actual rendered size at common widths
   instead of its cap. */
.ap-mobilemenu__brand .wp-block-site-logo,
.ap-mobilemenu__brand .wp-block-site-logo .custom-logo-link,
.ap-mobilemenu__brand .wp-block-site-logo img {
  max-width: min(200px, 100%);
}

.ap-mobilemenu__brand .wp-block-site-logo img {
  /* The editor blocks the image and the front end leaves it inline; declaring
	   it keeps the two bars the same height. */
  display: block;
  height: auto;
}

/* Same fallback as the site header: the text title stands in until a logo is
   uploaded, then hides itself. */
.ap-mobilemenu__brand:has(.wp-block-site-logo img) .ap-mobilemenu__title {
  display: none;
}

.ap-mobilemenu__title {
  margin: 0;
}

/* -------------------------------------------------------------------------
 * Mobile menu — overlay chrome
 *
 * Ollie injects the mobile-menu part inside core's overlay, alongside the
 * collapsed <ul> rather than instead of it. Core renders that same container
 * at every width — it is the desktop bar above the breakpoint — so all of this
 * is confined to the collapsed state. Hiding the list unconditionally takes
 * the desktop navigation with it.
 *
 * The parts carry their own close button (ap/icon-link, behavior
 * "menu-close"), which clicks core's, so core's own button is hidden too.
 * ---------------------------------------------------------------------- */

@media (min-width: 900px) {
  .ap-mobilenav {
    display: none;
  }
}

@media (max-width: 899.98px) {
  .wp-block-navigation__responsive-container-content:has(.ap-mobilenav)
    > .wp-block-navigation__container,
  .wp-block-navigation__responsive-container:has(.ap-mobilenav)
    .wp-block-navigation__responsive-container-close {
    display: none;
  }

  /* The overlay is a fixed, full-height box; this hands that height down to
	   the menu so a slide-in panel can fill it. */
  .wp-block-navigation__responsive-container.is-menu-open:has(.ap-mobilenav) {
    padding: 0;
  }

  .wp-block-navigation__responsive-container.is-menu-open:has(.ap-mobilenav)
    .wp-block-navigation__responsive-close,
  .wp-block-navigation__responsive-container.is-menu-open:has(.ap-mobilenav)
    .wp-block-navigation__responsive-dialog,
  .wp-block-navigation__responsive-container.is-menu-open:has(.ap-mobilenav)
    .wp-block-navigation__responsive-container-content,
  .wp-block-navigation__responsive-container.is-menu-open:has(.ap-mobilenav)
    .wp-block-navigation__mobile-menu-content {
    height: 100%;
    min-height: 0;
    /* Core reserves 56px at the top for its close button, which is hidden
		   here, so that space is dead. */
    padding: 0;
    margin-top: 0;
    overflow: hidden;
  }
}

/* -------------------------------------------------------------------------
 * Mobile menu — top level
 * ---------------------------------------------------------------------- */

.ap-mobilenav {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  background-color: var(--wp--preset--color--base);
}

.ap-mobilenav .ap-mobilemenu__bar {
  flex: none;
  padding: 0.75rem clamp(1rem, 4vw, 1.75rem);
  border-bottom: 1px solid var(--wp--preset--color--outline);
}

.ap-mobilenav .ap-mobilenav__list {
  flex: 1 1 auto;
  min-height: 0;
  padding: 0 clamp(1rem, 4vw, 1.75rem) 2rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* One row treatment for links and for panel triggers, so a dropdown row is
   indistinguishable from a plain one apart from its chevron. */
.ap-mobilenav .ap-menurow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  padding: 1.05rem 0;
  border: 0;
  border-bottom: 1px solid var(--wp--preset--color--outline);
  background: none;
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.3;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.ap-mobilenav .ap-menurow:hover,
.ap-mobilenav .ap-menurow:focus-visible {
  color: var(--wp--preset--color--primary);
}

.ap-mobilenav .ap-menurow .ap-iconlink__icon {
  color: var(--wp--preset--color--contrast-muted);
}

.ap-mobilenav .ap-menurow .ap-iconlink__icon svg {
  width: 1.25rem;
  height: 1.25rem;
}

.ap-mobilenav .ap-menurow .ap-iconlink__label {
  white-space: normal;
}

/* Account and support sit below the Sale button, behind a rule. */
.ap-mobilenav .ap-menurow--divided {
  border-top: 1px solid var(--wp--preset--color--outline);
}

/* Three classes: the flow-margin reset below matches at two and comes later in
   the file, which would otherwise flatten this to nothing. */
.ap-mobilenav .ap-mobilenav__list > .ap-mobilenav__cta {
  margin-block: 1.5rem;
}

.ap-mobilenav .ap-mobilenav__cta .wp-block-button {
  width: 100%;
}

.ap-mobilenav .ap-mobilenav__cta .wp-block-button__link {
  width: 100%;
  padding: 0.9rem 1rem;
  text-align: center;
}

/* -------------------------------------------------------------------------
 * Mobile menu — slide-in panels
 *
 * A panel is any group with class .ap-mobilepanel and an HTML anchor; the row
 * that opens it names that anchor in data-ap-panel. It covers the top level
 * rather than replacing it, so returning is a transform, not a re-render.
 * ---------------------------------------------------------------------- */

.ap-mobilenav .ap-mobilepanel {
  position: absolute;
  inset: 0;
  z-index: 2;
  max-width: none;
  padding: 0 0 2rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  background-color: var(--wp--preset--color--base);
  transform: translateX(100%);
  visibility: hidden;
  transition:
    transform 0.28s ease,
    visibility 0s linear 0.28s;
}

.ap-mobilenav .ap-mobilepanel.is-open {
  transform: translateX(0);
  visibility: visible;
  transition:
    transform 0.28s ease,
    visibility 0s;
}

@media (prefers-reduced-motion: reduce) {
  .ap-mobilenav .ap-mobilepanel {
    transition: none;
  }
}

/* The panel's bar spans the full width and stays put while the body scrolls. */
.ap-mobilenav .ap-mobilepanel > .ap-mobilemenu__bar {
  position: sticky;
  top: 0;
  z-index: 1;
  background-color: var(--wp--preset--color--base);
}

/* A length, never auto: an auto inline margin on a column flex item stops it
   stretching and shrink-wraps it to its content instead. Margin rather than
   padding so the promo card is inset and keeps its rounded corners. */
.ap-mobilenav .ap-mobilepanel > *:not(.ap-mobilemenu__bar) {
  margin-inline: clamp(1rem, 4vw, 1.75rem);
}

/* Both lists are flow-layout groups, so WordPress gives every child after the
   first a 24px block-start margin. That is what sets the rhythm here, not the
   theme, so it is cleared and the rows own their own padding. */
.ap-mobilenav .ap-mobilepanel > *,
.ap-mobilenav .ap-mobilenav__list > * {
  margin-block: 0;
}

/* -------------------------------------------------------------------------
 * Mobile menu — accordion
 * ---------------------------------------------------------------------- */

.ap-acc__item {
  border-bottom: 1px solid var(--wp--preset--color--outline);
}

/* Two classes to match the shared heading rule in the mega menu section, which
   sets a 1rem bottom margin the accordion does not want: the toggle's own
   padding provides the spacing, and a margin here unbalances a collapsed row. */
.ap-acc__item .ap-acc__heading {
  margin-block: 0;
}

/* Dot marker rather than the desktop rule. */
.ap-acc__heading::before {
  content: "";
  flex: none;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  background-color: var(--ap-mm-accent, var(--wp--preset--color--primary));
}

/* The toggle is injected by header.js; without JS the panels stay open. */
.ap-acc__toggle {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  padding: 1.1rem 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.ap-acc__label {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex: 1;
}

.ap-acc__chevron {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
  color: var(--wp--preset--color--contrast-muted);
  transition: transform 0.15s ease-in-out;
}

.ap-acc__item--open .ap-acc__chevron {
  transform: rotate(180deg);
}

.ap-acc__panel {
  padding: 0 0 1rem 1.35rem;
}

.ap-acc__panel li {
  margin-bottom: 1rem;
}

/* Sized close to the card's own content. At 18.75rem the content — which is
   bottom-aligned — left ~86px of empty gradient stacked above the pill, where
   the desktop card only has ~17px. */
.ap-mobilemenu .ap-promo {
  min-height: 15rem;
  margin: 1.5rem 0;
}

.ap-mobilemenu__footer {
  padding-top: 1rem;
  border-top: 1px solid var(--wp--preset--color--outline);
}

.ap-mm__utility--stacked {
  flex-direction: column;
  align-items: stretch;
  gap: 0;
}

.ap-mm__utility--stacked .ap-mm__utility-link {
  padding: 0.85rem 0;
}

.ap-mobilemenu__footer .ap-mm__tagline {
  margin-top: 1rem;
  text-align: center;
}

/* The ap/icon-link block wraps its SVG so a visible label can sit beside it. */
.ap-iconlink__icon {
  display: inline-flex;
  flex: none;
}

.ap-iconlink__label {
  white-space: nowrap;
}

/* The search toggle is hidden above the mobile breakpoint, which would make it
   unselectable in the editor. .block-editor-block-list__block exists only on
   blocks inside the editor canvas, so this reveals it there and nowhere else. */
.block-editor-block-list__block.ap-search-toggle {
  display: inline-flex;
}

/* -------------------------------------------------------------------------
 * Editor: Custom HTML block previews
 *
 * The block editor previews every core/html block inside a sandboxed <iframe>
 * — core/html's edit() renders <SandBox>, which renders an iframe. An iframe
 * is a replaced element that fills its container, so the icon rail scatters
 * and the header row wraps, even though the front end renders the same blocks
 * as plain inline anchors.
 *
 * These rules give those frames the size their contents actually need. They
 * match nothing on the front end, where there are no iframes. SandBox sets
 * width and height as inline styles, so !important is required to win.
 *
 * If the icon links are ever converted to ordinary blocks, delete this whole
 * section — it exists only to compensate for the sandbox.
 * ---------------------------------------------------------------------- */

/* Mega menu utility row — leave the height to SandBox, which measures it. */
.ap-mm__footer iframe {
  width: min(100%, 40rem) !important;
  border: 0;
}

/* -------------------------------------------------------------------------
 * Commerce Connect Block Styles
 *
 * @NOTE: We might need to prefix with a namespaced wrapper class. E.g.
 *        ap-product-wrap
 * ------------------------------------------------------------------------- */

/* Product image gallery.
 *
 * The plugin's own layout is a vertical thumbnail rail beside a cropped,
 * 450px-wide portrait frame. The design is the opposite: one wide card with
 * the product floated on white, and a row of thumbnails underneath.
 *
 * Most of the sizing is driven by custom properties the plugin declares on
 * :root, so the values are set here rather than fighting its rules. Declaring
 * them on `.product-gallery` — an ancestor of everything that reads them —
 * means they inherit down and win regardless of stylesheet load order, which
 * a competing `:root` block would not.
 * ------------------------------------------------------------------------- */

.ap-gallery {
  --product-gallery-image-width: 100%;
  --product-gallery-image-aspect-ratio: 4 / 3;
  --product-gallery-image-aspect-ratio-x: 4;
  --product-gallery-image-aspect-ratio-y: 3;
  --product-gallery-image-border-radius: 16px;
  --ap-gallery-gap: 0.75rem;
}

/* Frame above, thumbnails below, at every width. The plugin flips between a
   column and a row at its own breakpoint and the two children carry `order`
   values tuned for that; pinning both is less fragile than reversing it. */
.ap-gallery .product-gallery__container {
  flex-direction: column;
  gap: var(--ap-gallery-gap);
}

.ap-gallery .product-gallery__frame {
  order: 1;
  max-width: 100%;
  background-color: var(--wp--preset--color--base);
  border: 1px solid var(--wp--preset--color--outline);
  /* Set here rather than left to the plugin, which only applies the radius
     above its own breakpoint — the frame was square-cornered on mobile. */
  border-radius: var(--product-gallery-image-border-radius);
}

/* `cover` crops the product to fill the frame; the design shows the whole
   product with white around it, so `contain` plus an inset. */
.ap-gallery .product-gallery__image {
  /* The plugin's border-box reset is scoped to `[data-commerce-connect-block]`,
     which this markup sits outside, so padding would otherwise be added to the
     image's size and push it out past the frame. */
  box-sizing: border-box;
  padding: clamp(1.5rem, 5%, 3rem);
  object-fit: contain;
}

/* A grid, not the plugin's scrolling rail: four across, wrapping to a second
   row for products with more images. minmax(0, 1fr) keeps wide thumbnails
   from forcing the track open. */
.ap-gallery .product-gallery__thumbnails {
  order: 2;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--ap-gallery-gap);
  min-width: 0;
  max-width: none;
  height: auto;
  padding-inline: 0;
  overflow: visible;
}

/* A single image still renders one thumbnail. On its own under the frame it
   reads as a stray tile, so the rail is dropped when there is nothing to
   switch between. */
.ap-gallery .product-gallery__thumbnails:has(> :only-child) {
  display: none;
}

.ap-gallery .product-gallery__thumbnail {
  min-width: 0;
  max-width: none;
  min-height: 0;
  max-height: none;
  aspect-ratio: 1 / 1;
  padding: 0.5rem;
  background-color: var(--wp--preset--color--base);
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 12px;
  transition: border-color 0.15s ease;
}

.ap-gallery .product-gallery__thumbnail img {
  min-height: 0;
  max-height: 100%;
  object-fit: contain;
}

/* The plugin points the active border at the buy-button colour. */
.ap-gallery .product-gallery__thumbnail--active {
  border-color: var(--wp--preset--color--primary);
  box-shadow: inset 0 0 0 1px var(--wp--preset--color--primary);
}

.ap-gallery .product-gallery__thumbnail:hover {
  border-color: var(--wp--preset--color--contrast-muted);
}

/* The plugin's image counter chip ("1/12") is anchored top-left, where the
   badge goes. The design has no counter, and when its label is empty the chip
   still paints as a small grey pill, so it is hidden rather than moved. */
.ap-gallery .ecom-magnify-chip {
  display: none;
}

/* Badge over the frame — rendered by the theme's product-images.php override
   only when a product carries one. */
.ap-gallery__badge {
  position: absolute;
  z-index: 2;
  top: 1rem;
  left: 1rem;
  padding: 0.5rem 1rem;
  border-radius: 8px;
  background-color: var(--wp--preset--color--green);
  color: var(--wp--preset--color--base);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.9375rem;
  font-weight: 700;
  line-height: 1.2;
}

@media (max-width: 600px) {
  .ap-gallery__badge {
    top: 0.75rem;
    left: 0.75rem;
    padding: 0.375rem 0.75rem;
    font-size: 0.875rem;
  }
}

.ecom-product-prices .ecom-product-prices__price {
  font-weight: 800;
}

/*
 * Legacy Avada / Fusion Builder markup, carried over in the imported product
 * - effected products have inline styles which need to be overridden with the
 * following workaround
 */

.ecom-product-description .fusion-builder-row {
  margin-inline: 0 !important;
  max-width: none !important;
}

/* Commerce Connect's own stylesheet pins the single product block to the wide
 * size:
 *
 *   .wp-block-commerce-connect-single-product.alignwide {
 *     max-width: var(--wp--style--global--wide-size);
 *   }
 *
 * That is the same specificity as WordPress's generated layout rules but it
 * loads after them, so it wins the cascade and the product sits at 1600px
 * while the header and every other page sit at 1400px. No template-level
 * layout setting can outrank it, and the block's own `align` attribute is not
 * a way out either: block.json declares `"align": { "default": "wide" }`, and
 * Gutenberg normalises an empty alignment away on save, which lets that
 * default reapply.
 *
 * !important is needed for the editor, not the front end. Commerce Connect
 * maps its container width to the theme's *wide* size —
 *
 *   PluginSettings.php:147
 *   '--ecom-ui-content-size' => themeEntry('wideSize', ['layout'], ...)
 *
 * — and in the editor its JS writes that straight onto the block as an inline
 * `max-width: var(--ecom-ui-content-size)`, which no selector can outrank.
 * On the front end the `main.ap-main >` qualifier (0-3-1) would be enough on
 * its own; !important is what keeps the editor preview honest too.
 * ------------------------------------------------------------------------- */

main.ap-main > .wp-block-commerce-connect-single-product.alignwide {
  max-width: var(--wp--style--global--content-size) !important;
}

/* Quantity stepper and Add to Cart, side by side.
 *
 * The plugin only lays these two out inside `@media (max-width: 1024px)`, and
 * there it stacks them in a column. Above 1024px there is no rule at all, so
 * both wrappers fall back to full-width blocks and stack anyway. The row is
 * declared here for every width and handed back to the plugin's column only
 * on small screens, where side by side would squeeze the button too far.
 *
 * The two controls are also natively different heights (45px stepper, 50px
 * button), so both are pinned to one variable to line their edges up.
 * ------------------------------------------------------------------------- */

/* Everything below is scoped to .wp-block-commerce-connect-single-product, the
 * block's own wrapper class, rather than the .single-product body class.
 * body_class() only runs on the front end, and the plugin adds "single-product"
 * to the wrapper in render.php but not in the editor's edit() -- so scoping to
 * it silently drops every rule here inside the Site Editor. The wp-block-*
 * class is generated from the block name and is present in both. Same
 * specificity (0-1-0), so nothing else in the cascade shifts.
 * ------------------------------------------------------------------------- */

/* A rule closes off the buy controls, matching the one under the assurances
   band. It sits on the description block below rather than the buy button
   above, because the description is what follows the controls in the column
   and the border has to travel with whatever comes after them.

   .ap-productintro rather than .ecom-product-description: the block is placed
   twice on the page, and the plugin gives both instances the same class. The
   override adds this one to the intro half only, and only when it has content
   to print -- see commerce-connect/single-product/product-description.php. */
.wp-block-commerce-connect-single-product .ap-productintro {
  margin-block-start: var(--wp--preset--spacing--40);
  padding-block-start: var(--wp--preset--spacing--40);
  border-block-start: 1px solid var(--wp--preset--color--outline);
}

.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__quantity-and-button {
  --ap-buy-control-height: 50px;

  /* Below 1024px the plugin adds its own 16px top margin here, which stacks on
     the buy block's padding and makes the gap under the rule half again as big
     as every other gap in the column. The padding is the single source. */
  margin-top: 0;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--wp--preset--spacing--30);
  width: 100%;
}

.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__quantity-and-button
  .ecom-product-buy-button__quantity-wrapper {
  flex: 0 0 auto;
  width: auto;
}

.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__quantity-and-button
  .ecom-product-buy-button__add-button-wrapper {
  flex: 1 1 auto;
  min-width: 0;
  margin-top: 0;
}

/* The plugin ships a 10px top margin on the add-to-cart wrapper, which pushes
   the button out of line with the stepper once they sit on one row. */
.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__quantity-and-button
  .ecom-product-buy-button__add-to-cart {
  margin-top: 0;
}

/* The stepper carries a 20px bottom margin outside the plugin's breakpoint,
   which makes its wrapper taller than the button and breaks the alignment. */
.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__quantity-and-button
  .ecom-quantity {
  margin-bottom: 0;
}

/* The button is an inline-flex sized to its label. The plugin's full-width
   rule targets `.ecom-btn-add-to-cart`, a class this button does not carry
   (it renders as `ecom-button ecom-button--primary ecom-button--lg`), and
   lives inside the max-width:1024px query regardless. */
.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__quantity-and-button
  .ecom-product-buy-button__add-to-cart,
.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__quantity-and-button
  .ecom-product-buy-button__add-to-cart-inner,
.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__quantity-and-button
  .ecom-product-buy-button__add-to-cart-inner
  > .ecom-button {
  width: 100%;
}

/* Both controls to a single height. The stepper's height sits on its wrapper
   (below) so its border is included rather than added on top. */
.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__quantity-and-button
  .ecom-product-buy-button__add-to-cart-inner
  > .ecom-button {
  height: var(--ap-buy-control-height);
  max-height: var(--ap-buy-control-height);
}

/* The stepper ships as three separately bordered cells, which reads as a grid.
   The design is one rounded control: the border and fill move to the wrapper
   and the children go transparent, so the internal dividers disappear.
   `overflow: hidden` clips the square children to the rounded corners. */
.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__quantity-and-button
  .ecom-quantity {
  box-sizing: border-box;
  height: var(--ap-buy-control-height);
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 8px;
  background-color: var(--wp--preset--color--base-alt);
  overflow: hidden;
}

/* max-height too: the plugin pins the input to 45px with both properties. */
.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__quantity-and-button
  .ecom-quantity
  > * {
  height: 100%;
  max-height: 100%;
  border: 0;
  background-color: transparent;
}

/* The plugin signals focus by recolouring a border that no longer exists. */
.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__quantity-and-button
  .ecom-quantity__input:focus-visible {
  outline: 2px solid var(--wp--preset--color--primary);
  outline-offset: -2px;
}

/* The +/- glyphs render at 13px against the 14px value, which looks undersized
   next to the Add to Cart label. */
.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__quantity-and-button
  .ecom-quantity
  > button {
  font-size: 1.125rem;
}

/* Variant option buttons ("Size: Regular / Large").
 *
 * The plugin lays these out as a two-column grid of full-width cells with
 * square corners. The design is a wrapping row of pills sized to their label,
 * with the selected one outlined and coloured in the primary blue.
 * ------------------------------------------------------------------------- */

.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__option-label {
  margin-bottom: 0.75rem;
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1rem;
  font-weight: 700;
}

/* Option names arrive lower-cased from the variation terms ("size"), and the
   design shows them capitalised and followed by a colon. The name is wrapped
   in its own span by the theme's product-buy-button.php override so the colon
   lands before the selected-value span rather than after it. */
.wp-block-commerce-connect-single-product .ap-option-label__name {
  text-transform: capitalize;
}

.wp-block-commerce-connect-single-product .ap-option-label__name::after {
  content: ":";
}

.wp-block-commerce-connect-single-product .ecom-product-buy-button__variants {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.wp-block-commerce-connect-single-product .ecom-product-buy-button__variant {
  /* Variant values arrive lower-cased from the variation terms ("black",
     "leather like"), same as the option names above them. */
  text-transform: capitalize;
  width: auto;
  min-height: 0;
  padding: 0.75rem 1.5rem;
  border: 2px solid var(--wp--preset--color--outline);
  border-radius: 8px;
  background-color: var(--wp--preset--color--base);
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1rem;
  font-weight: 500;
  transition:
    border-color 0.15s ease,
    color 0.15s ease;
}

.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__variant:hover:not(:disabled) {
  border-color: var(--wp--preset--color--contrast-muted);
}

/* The border is 2px in both states so selecting an option does not change the
   button's size and shuffle the row. `:not(:disabled)` is here to match the
   hover rule's specificity — without it hover wins and greys out the border of
   the option you just clicked, while the pointer is still over it. */
.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__variant[aria-pressed="true"]:not(:disabled),
.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__variant--is-selected:not(:disabled) {
  border-color: var(--wp--preset--color--primary);
  color: var(--wp--preset--color--primary);
  font-weight: 700;
}

/* Unavailable variants render as a solid grey block. Kept legible and struck
   through, which is what signals "out of stock", but without the heavy fill. */
.wp-block-commerce-connect-single-product
  .ecom-product-buy-button__variant:disabled {
  border-color: var(--wp--preset--color--outline);
  background-color: var(--wp--preset--color--base-alt);
  color: var(--wp--preset--color--contrast-muted);
  text-decoration: line-through;
  cursor: not-allowed;
}

/* Product assurances row — the hairline-separated band of trust badges under
 * the buy button. The badges themselves are ap/icon-link blocks reusing the
 * hero's trust-item styling, so the two stay in step; only the band around
 * them is new.
 * ------------------------------------------------------------------------- */

.wp-block-commerce-connect-single-product .ap-producttrust {
  margin-block-start: var(--wp--preset--spacing--40);
  padding-block: var(--wp--preset--spacing--40);
  border-block: 1px solid var(--wp--preset--color--outline);
}

/* Share row — ap/share-links.
 *
 * Circular brand buttons after a label. The brand colours are declared per
 * network as custom properties so a new network is one variable plus a row in
 * render.php, and email borrows the theme's accent rather than inventing a
 * colour of its own.
 * ------------------------------------------------------------------------- */

.ap-share {
  --ap-share-size: 3rem;
  --ap-share-facebook: #3b5998;
  --ap-share-x: #14171a;
  --ap-share-mail: var(--wp--preset--color--accent);

  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1rem;
}

.ap-share__label {
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--ui);
}

.ap-share__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.ap-share__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--ap-share-size);
  height: var(--ap-share-size);
  border-radius: 50%;
  color: var(--wp--preset--color--base);
  text-decoration: none;
  transition:
    transform 0.15s ease,
    opacity 0.15s ease;
}

.ap-share__link:hover,
.ap-share__link:focus-visible {
  color: var(--wp--preset--color--base);
  opacity: 0.85;
  transform: translateY(-1px);
}

.ap-share__icon {
  display: inline-flex;
}

/* The brand marks are solid fills; mail is the one stroke icon in the row, so
   it needs a heavier stroke to hold its own against them at this size. */
.ap-share__icon svg {
  width: 1.375rem;
  height: 1.375rem;
}

.ap-share__link--mail .ap-share__icon svg {
  stroke-width: 2;
}

.ap-share__link--facebook {
  background-color: var(--ap-share-facebook);
}

.ap-share__link--x {
  background-color: var(--ap-share-x);
}

.ap-share__link--mail {
  background-color: var(--ap-share-mail);
}

/* On the product page the row sits under the assurances band, which already
   draws the rule above it. */
.wp-block-commerce-connect-single-product .ap-share {
  margin-block-start: var(--wp--preset--spacing--40);
}

/* Centred only once the product columns stack — WordPress collapses
   core/columns at 781px, so that is where the single-column layout begins and
   where a centred row reads as deliberate rather than adrift. */
@media (max-width: 781px) {
  .wp-block-commerce-connect-single-product .ap-producttrust,
  .wp-block-commerce-connect-single-product .ap-share {
    justify-content: center;
  }
}

/* Product meta list — SKU / Brand / Categories under the description.
 *
 * A definition list: these are label/value pairs, and `dl > div > dt + dd` is
 * the valid way to keep each pair on its own row.
 * ------------------------------------------------------------------------- */

.ap-productmeta {
  display: grid;
  gap: 0.375rem;
  /* The description around it is prose and runs in the body font; these are
     interface labels, so they take the ui preset. A rule here beats the
     inherited family regardless of specificity. */
  font-family: var(--wp--preset--font-family--ui);
  margin-top: 0;
}

/* nowrap keeps the label beside its value: a long category list should wrap
   within the value, not push the whole value onto the next line. */
.ap-productmeta__row {
  display: flex;
  flex-wrap: nowrap;
  align-items: baseline;
  gap: 0.5rem;
}

.ap-productmeta__label {
  flex: none;
  color: var(--wp--preset--color--contrast-muted);
}

.ap-productmeta__value {
  /* dd carries a browser default indent that would break the row. */
  min-width: 0;
  margin: 0;
  color: var(--wp--preset--color--contrast);
  font-weight: 700;
}

/* Category links inherit the value's weight and colour so the row reads as one
   line rather than a string of blue links, and reveal themselves on hover. */
.ap-productmeta__value a {
  color: inherit;
  text-decoration: none;
}

.ap-productmeta__value a:hover,
.ap-productmeta__value a:focus-visible {
  text-decoration: underline;
}

/* The plugin draws its text from --ecom-ui-text-font-family, which this theme
 * points at the ui preset (Roboto) for interface chrome — buttons, labels,
 * cart. The product description is prose, not chrome, so it follows the body
 * preset (Lato) like the rest of the site's copy. Scoped rather than changing
 * the variable, which would move every Commerce Connect block to Lato.
 * ------------------------------------------------------------------------- */

.ecom-product-description,
.ecom-product-description__short,
.ecom-product-description__full {
  font-family: var(--wp--preset--font-family--body);
}

/* PDP accordion — the below-the-fold sections.
 *
 * core/accordion ships almost unstyled: a bare button, no card, and a literal
 * "+" as its icon. The card treatment and the chevron are the theme's.
 * ------------------------------------------------------------------------- */

.ap-productaccordion {
  display: grid;
  gap: 0.75rem;
  margin-block-start: var(--wp--preset--spacing--50);

  /* Full-bleed grey band. The accordion sits inside the single-product block,
     which this theme caps at contentSize, so it cannot use alignfull to escape
     — the negative inline margin does it instead. Both percentages resolve
     against the *parent's* width (contentSize), not the element's, so the
     margin widens it to the viewport and the padding pulls the cards back to
     contentSize. */
  margin-inline: calc(50% - 50vw);
  padding-block: var(--wp--preset--spacing--50);
  padding-inline: max(var(--wp--preset--spacing--30), calc(50vw - 50%));
  background-color: var(--wp--preset--color--base-alt);
}

/* The accordion is a flow-layout container, so WordPress adds a 24px blockGap
   above every item after the first, on top of the grid gap — 36px in total.
   The grid gap is the one that should decide the spacing. */
.ap-productaccordion .wp-block-accordion-item {
  margin-block-start: 8px;
}

.ap-productaccordion .wp-block-accordion-item {
  background-color: var(--wp--preset--color--base);
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 12px;
}

/* The heading is an h2, so it arrives with the display face (Amatic SC) and a
   heading size. These are interface labels, so they take the ui preset. */
.ap-productaccordion .wp-block-accordion-heading {
  margin: 0;
}

.ap-productaccordion .wp-block-accordion-heading__toggle {
  gap: 1rem;
  padding: 1.25rem 1.5rem;
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1.125rem;
  font-weight: 700;
  /* Core clips the toggle, which cuts the top off the rotated chevron. */
  overflow: visible;
  transition: color 0.15s ease;
}

/* Review count appended by ap_fse_theme_accordion_review_count(). Sits inside
   the toggle title so it is part of the button's accessible name, but is
   de-emphasised so the section name still reads as the heading. Inherits the
   open-state colour from the toggle along with the title. */
.ap-productaccordion .ap-accordion__count {
  font-weight: 400;
  color: currentcolor;
  opacity: 0.6;
}

/* Core underlines the title on hover; the design does not. */
.ap-productaccordion
  .wp-block-accordion-heading__toggle:hover
  .wp-block-accordion-heading__toggle-title {
  text-decoration: none;
}

.ap-productaccordion
  .wp-block-accordion-item.is-open
  .wp-block-accordion-heading__toggle {
  color: var(--wp--preset--color--primary);
}

.ap-productaccordion .wp-block-accordion-panel {
  /* The item is a flow-layout container, so WordPress gives every child after
     the first a blockGap margin (`:where(.is-layout-flow) > *`, 24px). Between
     the heading and the panel that reads as a gap above the content; the
     panel's own padding is what should space it. */
  margin-block-start: 0;
  padding: 0 1.5rem 1.5rem;
}

/* A collapsed panel is hidden with `hidden="until-found"`, which is
   content-visibility, not display:none — the element still generates a box, so
   its padding keeps reserving space under the heading. Core zeroes the panel's
   margin for exactly this reason (`.wp-block-accordion-panel[hidden]`); the
   padding added here needs the same treatment. */
.ap-productaccordion .wp-block-accordion-panel[hidden] {
  padding-block: 0;
}

/* Core's icon is the text "+". Zeroing the font size hides the glyph without
   removing the element the layout depends on, and the chevron is drawn from
   two borders so it needs no asset and can rotate on open. */
/* The rotation sits on the span, not on ::before. The pseudo-element is a flex
   item of this span and would not rotate reliably; the span is a flex box in
   its own right and does. */
.ap-productaccordion .wp-block-accordion-heading__toggle-icon {
  width: 1rem;
  height: 1rem;
  color: var(--wp--preset--color--contrast-muted);
  font-size: 0;
  transform: translateY(-0.125rem) rotate(45deg);
  transition: transform 0.2s ease;
}

.ap-productaccordion .wp-block-accordion-heading__toggle-icon::before {
  content: "";
  box-sizing: border-box;
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
}

/* Core rotates its "+" by 45deg when open, to make an "x":
 *
 *   .wp-block-accordion-item.is-open > .wp-block-accordion-heading
 *     .wp-block-accordion-heading__toggle-icon { transform: rotate(45deg) }
 *
 * That is 0-4-0, so a plainer selector here loses and the chevron never flips.
 * This mirrors core's shape to outrank it rather than reaching for !important. */
.ap-productaccordion
  .wp-block-accordion-item.is-open
  > .wp-block-accordion-heading
  .wp-block-accordion-heading__toggle-icon {
  transform: translateY(0.125rem) rotate(-135deg);
}

.ap-productaccordion .is-open .wp-block-accordion-heading__toggle-icon {
  color: var(--wp--preset--color--primary);
}

/* PDP insets below the width where the columns stack.
 *
 * The product content sits 40px in from each edge -- 24px of root padding on
 * <main>, plus the 16px the plugin puts on its own block -- and the grey band
 * matches that 40px so its cards line up with the product above it. On a 390px
 * screen that hands a fifth of the width to gutters, so both are halved.
 *
 * Only the content inset is set here. The band's inline padding is
 * `max(spacing|30, calc(50vw - 50%))`, which derives the gutter from its
 * parent's width rather than naming it, so it follows this change on its own
 * and the two stay aligned. Its padding-block does have to be halved by hand.
 *
 * Scoped with :has() rather than a class on the template, because the template
 * that actually renders is the copy in the database -- a class added to the
 * theme's single-product.html would not reach the front end until that copy
 * was updated to match.
 *
 * 1.25rem is off the preset scale, which steps 1rem straight to 1.5rem with
 * nothing between. Half of 40px was the ask, so the literal is deliberate. */
@media (max-width: 781px) {
  /* The root padding custom property is redefined rather than the padding
     itself. `.has-global-padding` reads the variable, so the inset comes out
     the same either way — but core also sizes any alignfull child's negative
     margins from it, and setting only the padding leaves the two disagreeing:
     a full-width child pulls itself out by the untouched 24px while its parent
     is inset 20px, overhangs by 4px a side, and puts a horizontal scrollbar on
     the page. Nothing here is alignfull today; this keeps it safe if anything
     is added that is. */
  main.ap-main:has(.wp-block-commerce-connect-single-product) {
    --wp--style--root--padding-left: 1.25rem;
    --wp--style--root--padding-right: 1.25rem;
  }

  main.ap-main:has(.wp-block-commerce-connect-single-product)
    > .wp-block-commerce-connect-single-product {
    padding-inline: 0;
  }

  .ap-productaccordion {
    padding-block: 1.25rem;
  }
}

/* Category breadcrumbs — commerce-connect/category-breadcrumb
 *
 * The block's render.php writes its own <nav class="category-breadcrumb-block">
 * and never calls get_block_wrapper_attributes(), so align, className and block
 * supports are all discarded: there is no way to put a class on the block from
 * the template. Hence the .ap-breadcrumbs wrapper group, which also lifts these
 * rules above the plugin's own (its block stylesheet is enqueued while the
 * template renders, so it can land after theme.css and win any tie).
 * ------------------------------------------------------------------------- */

.ap-breadcrumbs {
  margin-block-end: var(--wp--preset--spacing--30);
}

.ap-breadcrumbs .category-breadcrumb-block__list {
  /* Plugin default is a 1rem bottom margin; the wrapper owns spacing now. */
  margin-block-end: 0;
  row-gap: 0.25rem;
}

.ap-breadcrumbs .category-breadcrumb-block__item {
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.875rem;
}

/* The design separates crumbs with a chevron, not the plugin's slash. */
.ap-breadcrumbs .category-breadcrumb-block__item:not(:last-child)::after {
  content: ">";
  margin-inline: 0.5rem;
  opacity: 1;
}

/* Links read as plain text until hovered, as everywhere else in the theme. */
.ap-breadcrumbs .category-breadcrumb-block__link {
  color: inherit;
  text-decoration: none;
  transition: color 0.15s ease;
}

.ap-breadcrumbs .category-breadcrumb-block__link:hover,
.ap-breadcrumbs .category-breadcrumb-block__link:focus-visible {
  color: var(--wp--preset--color--primary);
  opacity: 1;
  text-decoration: underline;
}

/* Current page is the one crumb that is not a link, so it carries the weight. */
.ap-breadcrumbs .category-breadcrumb-block__item--current {
  color: var(--wp--preset--color--contrast);
  font-weight: 700;
  opacity: 1;
}

/* ap/breadcrumbs — the product page's trail.
 *
 * Matches the archive's crumbs above, but the rules are its own rather than
 * shared. The plugin's block carries its layout in build/category-breadcrumb/
 * style-index.css, which WordPress enqueues only when *that* block renders —
 * so a theme block borrowing its class names would come out as a bare vertical
 * <ol> on the product page, where the plugin's block is not present to pull the
 * stylesheet in. Hence the base layout is restated here.
 *
 * The two sets of values have to be kept in step by hand. The way out is to
 * use this block on the archive too, which would also retire the markup
 * rewrite in inc/category-breadcrumb.php and the plugin's duplicate
 * BreadcrumbList — a job for when someone is next in here, not a side effect
 * of adding the product trail. */
/* Spacing matches the category archive: 24px clear above the trail and 24px
   below it. Above comes from <main>, which the archive gets from its
   .ap-main--tight modifier and the product template gets here — the product
   page has no reason to sit further from the header than the archive does now
   that both open with a trail. Below is theme.json's blockGap between <main>'s
   children, which is the same 24px, so it needs no rule of its own. */
main.ap-main:has(.ap-crumbs) {
  margin-block-start: var(--wp--preset--spacing--40);
}

.ap-crumbs__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: 0.5rem;
  row-gap: 0.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.ap-crumbs__item {
  display: flex;
  align-items: center;
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.875rem;
}

/* The design separates crumbs with a chevron, not the plugin's slash. Margin on
   both sides and an 8px column-gap on the list, which is what the archive
   computes to — the chevron sits nearer the crumb it follows than the one it
   precedes, and matching that is the point of this pass. */
.ap-crumbs__item:not(:last-child)::after {
  content: ">";
  margin-inline: 0.5rem;
  color: var(--wp--preset--color--outline);
}

.ap-crumbs__link {
  color: inherit;
  text-decoration: none;
  transition: color 0.15s ease;
}

.ap-crumbs__link:hover,
.ap-crumbs__link:focus-visible {
  color: var(--wp--preset--color--primary);
  text-decoration: underline;
}

/* Current page is the one crumb that is not a link, so it carries the weight. */
.ap-crumbs__item--current {
  color: var(--wp--preset--color--contrast);
  font-weight: 700;
}

/* Four crumbs ending in a product name run to three lines on a phone, turning
   a wayfinding strip into a 118px block sitting on top of the product — and
   product titles here are unbroken strings ("JohnnyOnTheSpot") that a phone
   cannot even break mid-word. So below the tablet breakpoint the trail scrolls
   sideways instead of wrapping: the band keeps the single-row height the design
   shows, and no crumb has to be dropped to get it.

   flex: none on the items because a nowrap flex line shrinks its children
   before it overflows, which would squeeze the crumbs rather than scroll them. */
@media (max-width: 781px) {
  .ap-crumbs__list {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
  }

  .ap-crumbs__list::-webkit-scrollbar {
    display: none;
  }

  .ap-crumbs__item {
    flex: none;
  }
}

/* Collection toolbar — the filter/sort band on the category archive.
 *
 * The design puts this in a full-width tinted band with a hairline above and
 * below, Filter on the left and the sort control on the right, with its inner
 * content lining up with the page container rather than with the products
 * block (which is alignwide, so on wide viewports it is the wider of the two).
 * Hence the bleed-then-pad pattern: the negative margin takes the band to
 * 100vw whatever the parent measures, and the padding brings the controls back
 * to a centred content-size column so they sit under the heading.
 *
 * Two pieces of the design have no block behind them and are not built: the
 * 24/48/72 page-size control and the grid/list view toggle. Commerce Connect
 * ships neither a block nor any template markup for either.
 * ------------------------------------------------------------------------- */

.ap-collection__count {
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.875rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* The row is commerce-connect's own core/columns (.ecom-filters-toolbar); this
   class is added alongside it purely as the styling hook, since the block's
   markup is generated by the plugin and should stay as it inserts it. */
.ap-collection__toolbar {
  margin-inline: calc(50% - 50vw);
  padding-block: 0.75rem;
  /* Floor is the root padding the rest of the page is laid out against, so the
     controls keep the heading's gutter once the viewport drops below
     content-size. theme.json sets useRootPaddingAwareAlignments, which is what
     publishes this variable. */
  padding-inline: max(
    var(--wp--style--root--padding-left, var(--wp--preset--spacing--40)),
    calc((100vw - var(--wp--style--global--content-size)) / 2)
  );
  gap: 1rem;
  background-color: var(--wp--preset--color--base-alt);
  border-block: 1px solid var(--wp--preset--color--outline);
}

/* The plugin right-aligns the toggle inside its own wrapper; the design has it
   hard left, and the flex row now owns the placement. */
.ap-collection__toolbar .ecom-filters-toggle-wrapper {
  justify-content: flex-start;
}

/* Both controls are .ecom-button--ghost, which is transparent with a
   transparent border and dims its own text on hover. The design wants two
   white pills with a visible outline that darken on hover. */
.ap-collection__toolbar .ecom-button {
  gap: 0.5rem;
  min-height: 2.75rem;
  padding: 0.5rem 1rem;
  background-color: var(--wp--preset--color--base);
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 8px;
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.9375rem;
  font-weight: 700;
  transition:
    border-color 0.15s ease,
    background-color 0.15s ease;
}

.ap-collection__toolbar .ecom-button:hover,
.ap-collection__toolbar .ecom-button:focus-visible {
  background-color: var(--wp--preset--color--base);
  border-color: var(--wp--preset--color--primary);
  color: var(--wp--preset--color--contrast);
}

.ap-collection__toolbar .ecom-button svg {
  width: 1rem;
  height: 1rem;
  fill: currentcolor;
}

/* "Sort by:" is a label, not part of the value — the design greys it back and
   leaves the chosen option as the emphasised text. The trigger renders the
   label as a bare text node, so the weight is reset on the button and
   reapplied to the value span. */
.ap-collection__toolbar .ecom-dropdown__trigger {
  color: var(--wp--preset--color--contrast-muted);
  font-weight: 400;
}

.ap-collection__toolbar .ecom-dropdown__trigger > span:not(.ecom-icon-animate) {
  color: var(--wp--preset--color--contrast);
  font-weight: 700;
}

.ap-collection__toolbar .ecom-dropdown__trigger .ecom-icon-animate svg {
  width: 0.75rem;
  height: 0.75rem;
}

/* Keep the open menu tied to the trigger's right edge, since the trigger now
   sits at the end of the row. */
.ap-collection__toolbar .ecom-dropdown__menu {
  left: auto;
  right: 0;
}

/* The plugin right-aligns the whole row (justify-content: flex-end) and relies
   on the selections column's flex:1 to hold the filter toggle over on the left
   — but it hides that column on narrow screens, at which point both controls
   bunch to the right. Stating the intent directly survives either case. */
.ap-collection__toolbar.wp-block-columns {
  justify-content: space-between;
}

.ap-collection__toolbar .wp-block-column {
  display: flex;
  align-items: center;
  margin-block: 0;
}

/* Filter sits at the head of the row, sort at the tail, with the active-filter
   chips taking up the slack between them. */
.ap-collection__toolbar .ecom-filters-sort {
  justify-content: flex-end;
}

/* Sort on its own (the brand archive, which has no filters) has no neighbour
   to push it across, so it would sit at the start of the row. Keep it where
   the category archive puts it. The mobile rules below override this. */
.ap-collection__toolbar > .ecom-filters-sort:only-child {
  margin-left: auto;
}

@media (max-width: 600px) {
  .ap-collection__toolbar {
    gap: 0.75rem;
  }

  .ap-collection__toolbar .wp-block-column {
    flex: 1 1 100%;
  }

  .ap-collection__toolbar .ecom-filters-sort {
    justify-content: flex-start;
  }

  .ap-collection__toolbar .ecom-button {
    width: 100%;
    justify-content: center;
  }
}

/* Collection hero — the Hero pattern reused for the category archive.
 *
 * It lives *inside* commerce-connect/products rather than above it, because
 * the design puts the product count in the hero and products-count declares
 * `ancestor: ["commerce-connect/products"]`, so it cannot be placed outside
 * that block. The products wrapper has no layout support and therefore no
 * is-layout-* class, which means alignfull does not resolve inside it — hence
 * the same bleed-then-pad pattern the toolbar uses rather than the alignfull
 * the front page's hero relies on.
 * ------------------------------------------------------------------------- */

.ap-hero--collection {
  margin-inline: calc(50% - 50vw);
  padding-block: var(--wp--preset--spacing--60);
  padding-inline: max(
    var(--wp--style--root--padding-left, var(--wp--preset--spacing--40)),
    calc((100vw - var(--wp--style--global--content-size)) / 2)
  );
  background-color: var(--wp--preset--color--base-warm);
}

/* The 64px block padding reads as too much air once the media column is
   dropped (see the .ap-hero--collection .ap-hero__media rule above) and the
   section is just the copy column at full width. */
@media (max-width: 781.98px) {
  .ap-hero--collection {
    padding-block: var(--wp--preset--spacing--40);
  }
}

/* core/query-title is a heading like any other; the pattern's h1 treatment is
   reapplied here because .ap-hero__title only sets line-height.
 *
 * The space above it has to be stated rather than left to the column's Block
 * spacing: blockGap reaches flow children as a margin-block-start, so zeroing
 * the margin — which is what stops core's h1 rule adding its own — took the
 * gap with it and sat the title hard against the eyebrow row, while the
 * description below kept its 1rem. The unit is em so the gap tracks the
 * title's own clamp() rather than needing a second breakpoint: ~24px at the
 * 4.25rem ceiling, ~15px at the 2.5rem floor. */
.ap-hero--collection .ap-hero__title {
  margin-block: 0.375em 0;
}

/* The kicker and the eyebrow share a flex row, and the block's Block spacing
   sets both axes of its gap. Below roughly 500px the row wraps, and that 1rem
   becomes vertical space between two lines of what is one title cluster,
   reading as a paragraph break. The design spaces the pair horizontally, so
   only the row gap is tightened. */
.ap-hero--collection .ap-hero__eyebrow-row {
  row-gap: 0.25rem;
}

/* The description block writes its own class list and never calls
   get_block_wrapper_attributes(), so className from the template is discarded
   and it has to be reached by the plugin's class. It also hardcodes alignwide,
   which would pull it wider than the column. */
/* The brand archive has no plugin description block, so it uses core's
   term-description, which does take className — styled the same either way. */
.ap-hero--collection .category-description-block,
.ap-hero--collection .ap-hero__description {
  /* Wide enough to fill the copy column at content-size, capped so the measure
     does not run away on very wide viewports. */
  max-width: 48rem;
  color: var(--wp--preset--color--contrast);
  font-size: var(--wp--preset--font-size--medium);
  line-height: 1.65;
}

.ap-hero--collection .category-description-block p,
.ap-hero--collection .ap-hero__description p {
  margin-block: 0;
}

/* Copy column takes roughly two thirds, as drawn. Columns with no explicit
   width split evenly, so the card column is sized here rather than in the
   template — no flex-basis to keep in step with a block attribute. */
@media (min-width: 782px) {
  /* Core sizes columns with
     `.wp-block-columns:not(.is-not-stacked-on-mobile) > .wp-block-column`
     (0-3-0) at this same breakpoint, which outranks a two-class selector and
     forces an even split. Matching its shape and adding the modifier wins. */
  .ap-hero--collection .wp-block-columns > .wp-block-column.ap-hero__media {
    flex-grow: 0;
    flex-basis: 34%;
  }
}

/* The trust card that replaces the front page hero's image. */
.ap-collection__badge {
  padding: var(--wp--preset--spacing--50) var(--wp--preset--spacing--40);
  background-color: var(--wp--preset--color--base);
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 18px;
  text-align: center;
}

.ap-collection__badge > * {
  margin-block: 0;
}

.ap-collection__badge-icon {
  display: block;
  margin-block-end: var(--wp--preset--spacing--30);
}

.ap-collection__badge-icon .ap-iconlink__icon svg {
  width: 2.5rem;
  height: 2.5rem;
  color: var(--wp--preset--color--accent);
}

.ap-collection__badge-title {
  margin-block-end: var(--wp--preset--spacing--20);
  font-size: 1.75rem;
  line-height: 1.1;
}

.ap-collection__badge-text {
  color: var(--wp--preset--color--contrast-muted);
  line-height: 1.5;
}

/* Product cards — the archive grid.
 *
 * Markup comes from commerce-connect/products/products.php in this theme, which
 * renders image / title / short description / price / call to action and adds
 * the .ap-card hooks used below. The plugin's own .products__list grid and
 * .product-card__content flex column are kept and only retuned here.
 * ------------------------------------------------------------------------- */

.wp-block-commerce-connect-products .products__list {
  /* Plugin default is 11px; the design gives the cards much more air. */
  --ecom-ui-products-list-gap: var(--wp--preset--spacing--40);
}

/* The plugin's own reset is scoped to [data-commerce-connect-block], an
   attribute the server-rendered products grid does not carry — a leftover from
   when these blocks mounted React. Without it the cards inherit content-box,
   so padding is added on top of any width and both the card and its full-width
   button overflow their grid track. */
.ap-card,
.ap-card * {
  box-sizing: border-box;
}

.ap-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 0.75rem;
  background-color: var(--wp--preset--color--base);
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 12px;
  transition:
    border-color 0.15s ease,
    box-shadow 0.15s ease;
}

.ap-card:hover,
.ap-card:focus-within {
  border-color: var(--wp--preset--color--primary);
  box-shadow: 0 6px 18px rgb(30 41 59 / 8%);
}

.ap-card__media {
  display: block;
}

/* No tinted panel behind the photo. Most product shots are already on white,
   so any tint here draws a visible frame around them — which is what the
   design does not have; the grey in some cards is the photograph's own
   background. The plugin tints the inner slot (#fafbfc), so that is cleared
   too. `contain` rather than `cover` because product shots vary in aspect and
   cropping them cuts the product off. */
.ap-card .product-card__images {
  aspect-ratio: 1;
  background-color: transparent;
  border-radius: 8px;
}

.ap-card .product-card__image {
  width: 100%;
  height: 100%;
  background-color: transparent;
}

.ap-card .product-card__image img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* The body grows so the call to action sits on the bottom edge of every card
   in a row, whatever the title and description do above it. */
/* Zero inline padding so the copy lines up with the image's edges — the card's
   own padding is the only gutter. Block padding is the gap under the image;
   the card's bottom padding already spaces the button. */
.ap-card__body {
  flex: 1 1 auto;
  gap: 0.5rem;
  padding-block: 0.75rem 0;
  padding-inline: 0;
}

.ap-card__title {
  margin-block: 0;
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.35;
}

.ap-card__title-link {
  color: var(--wp--preset--color--contrast);
  text-decoration: none;
}

.ap-card__title-link:hover,
.ap-card__title-link:focus-visible {
  color: var(--wp--preset--color--primary);
  text-decoration: underline;
}

/* Held to two lines so a long description cannot push the price and button out
   of step with the neighbouring cards. */
.ap-card__excerpt {
  display: -webkit-box;
  margin-block: 0;
  overflow: hidden;
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.9375rem;
  line-height: 1.5;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.ap-card .ecom-product-prices__price {
  color: var(--wp--preset--color--primary);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1.25rem;
  font-weight: 700;
}

.ap-card__cta {
  display: block;
  margin-block-start: auto;
  padding: 0.75rem 1rem;
  background-color: var(--wp--preset--color--primary);
  border-radius: 8px;
  color: var(--wp--preset--color--base);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.9375rem;
  font-weight: 700;
  text-align: center;
  text-decoration: none;
  transition: background-color 0.15s ease;
}

/* The whole row of cards shares a top edge for the button, so give it the full
   body width rather than the content-box inset the flex column would allow. */
.ap-card__body > .ap-card__cta {
  width: 100%;
  margin-block-start: auto;
}

.ap-card__cta:hover,
.ap-card__cta:focus-visible {
  background-color: var(--wp--preset--color--primary-dark);
  color: var(--wp--preset--color--base);
}

/* Filter sidebar — commerce-connect/products/products-filters.php and
 * products-filter-group.php in this theme.
 *
 * The .ap-filter* rules are deliberately not scoped to .ap-filters. The same
 * group template is used by the plugin's mobile slide-in pane, which
 * renderFilters() serves instead of the sidebar when wp_is_mobile() is true —
 * so anything scoped to the sidebar would leave the pane with our markup and
 * none of our styling. Only the card itself (frame, header, chips) stays
 * scoped, since the pane has its own chrome.
 *
 * The plugin ships toggle buttons in a grid; the design lists a checkbox per
 * row under a "Filters" heading with a Clear All link. Markup for both comes
 * from the theme overrides, so everything below hangs off .ap-filter* classes
 * and leaves the plugin's own classes to it.
 * ------------------------------------------------------------------------- */

.ap-filters,
.ap-filters *,
.ap-filter,
.ap-filter * {
  box-sizing: border-box;
}

/* The plugin pins the sidebar's inner wrapper (`.ecom-storefront-options`,
   position: sticky, top: 80px). Unpinned here: the card is usually taller than
   the viewport, so it would pin and then scroll away again, and the 80px offset
   does not correspond to anything in this theme's header. */
.ap-filters .ecom-storefront-options {
  position: static;
}

/* The plugin hangs a hardcoded 200px bottom margin off .ecom-storefront-sidebar
   (presumably to clear its own sticky positioning). Inside a bordered card that
   is just dead space below the last group. */
.ap-filters .ecom-storefront-sidebar {
  margin-block-end: 0;
}

/* The sidebar is a card in its own right, sharing the product cards' frame. */
.ap-filters {
  padding: var(--wp--preset--spacing--40);
  background-color: var(--wp--preset--color--base);
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 12px;
}

.ap-filters__header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-block-end: 0.75rem;
  padding-block-end: 0.75rem;
  border-block-end: 1px solid var(--wp--preset--color--outline);
}

.ap-filters__title {
  margin-block: 0;
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1.0625rem;
  font-weight: 700;
}

/* A text link, not a button: the design underlines it and gives it no chrome. */
.ap-filters__clear {
  padding: 0;
  background: none;
  border: 0;
  color: var(--wp--preset--color--primary);
  cursor: pointer;
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.875rem;
  font-weight: 700;
  text-decoration: underline;
}

.ap-filters__clear:hover,
.ap-filters__clear:focus-visible {
  color: var(--wp--preset--color--primary-dark);
}

/* One hairline between groups, matching the rule under the heading. */
/* Vertical rhythm through a group, measured from the text rather than the
   boxes: 12px below the divider to the heading, 10px from the heading to the
   first option, 10px between options, 12px from the last option to the next
   divider. The heading keeps its own padding so the click target stays around
   42px rather than collapsing to the 26px line box. */
.ap-filter {
  padding-block: 0.25rem;
  border-block-end: 1px solid var(--wp--preset--color--outline);
}

.ap-filter:last-child {
  border-block-end: 0;
}

/* Specificity, not preference: the pane styles its headings with
   `.ecom-storefront-options-mobile .ecom-storefront-options-wrapper
   .ecom-storefront-filter .ecom-storefront-filter-heading` (four classes), so a
   single-class rule here loses inside the pane and the heading comes out at
   1.1rem. Stacking the classes the element already carries beats it in both
   contexts without repeating the values in a second rule. */
.ap-filter
  .ap-filter__heading.ecom-storefront-filter-heading.ecom-label.ecom-drawer-trigger {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-block: 0;
  padding-block: 0.5rem;
  padding-left: 0;
  padding-right: 0;
  color: var(--wp--preset--color--contrast);
  cursor: pointer;
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.9rem;
  font-weight: 500;
}

/* The selection counter and the chevron travel together at the far end of the
   heading row, wrapped in .ap-filter__meta by the group template.
 *
 * The wrapper is what does the pushing, not the chevron itself: the plugin
 * gives the counter `margin-right: auto`, which floats it into the middle of
 * the row while it is a direct child of the heading. Inside the wrapper that
 * auto margin has nothing to push against, and the pair moves as a unit. */
.ap-filter__meta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-inline-start: auto;
}

/* Hidden until something is selected (the plugin's own .is-showing toggle), so
   it contributes no gap when empty. */
.ap-filter__meta .ecom-storefront-filter-counter {
  margin-inline: 0;
}

/* The plugin's grid is replaced by a single column of rows, as drawn. It kept
   a fixed max-height with its own scrollbar; long groups scroll instead of
   pushing the page, which is worth keeping for lists like Color. */
.ap-filter__options {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  max-height: 17rem;
  /* No inline padding: the group heading's horizontal padding is zeroed above,
     so options sitting flush to the group edge line up with the accordion
     title. The block padding is the gap under the heading. */
  padding-block: 0.125rem 0.5rem;
  overflow-y: auto;
}

.ap-filter__row {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}

/* The design draws a rounded box with a hairline outline that fills with brand
   blue and a white tick when checked, which accent-color alone cannot produce
   — the platform control keeps its own radius and border. appearance: none
   hands the painting over without replacing the input, so the checkbox keeps
   its keyboard behaviour, its label association and its :checked state.
   The tick is a background image rather than a pseudo-element because Safari
   still refuses to generate content on a form control. */
.ap-filter__checkbox {
  appearance: none;
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
  background-color: var(--wp--preset--color--base);
  background-repeat: no-repeat;
  background-position: center;
  background-size: 0.75rem;
  border: 1.5px solid var(--wp--preset--color--outline);
  border-radius: 5px;
  cursor: pointer;
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.ap-filter__checkbox:hover {
  border-color: var(--wp--preset--color--primary);
}

.ap-filter__checkbox:checked {
  background-color: var(--wp--preset--color--primary);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M13 4.5 6.5 11.5 3 8' stroke='%23ffffff' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  border-color: var(--wp--preset--color--primary);
}

.ap-filter__checkbox:focus-visible {
  outline: 2px solid var(--wp--preset--color--primary);
  outline-offset: 2px;
}

.ap-filter__label {
  color: var(--wp--preset--color--contrast);
  cursor: pointer;
  font-family: var(--wp--preset--font-family--body);
  font-size: 0.9375rem;
  line-height: 1.4;
  text-transform: capitalize;
}

.ap-filter__row:hover .ap-filter__label {
  color: var(--wp--preset--color--primary);
}

/* Selected-filter chips, rendered inside the sidebar card by the
 * products-filters.php override. The plugin styles the chips themselves; only
 * their placement under the heading is set here.
 *
 * The margin is applied with :has(button) because the chips are drawn from a
 * <template> the Interactivity API expands — the container is never empty in
 * the :empty sense, so it would otherwise keep its spacing with nothing in it.
 * ------------------------------------------------------------------------- */

/* The heading and the chips are hidden together until something is selected.
   :has() rather than :empty: the chips are expanded from a <template> by the
   Interactivity API, so the container always has a child node. */
.ap-filters__selected {
  display: none;
}

.ap-filters__selected:has(button) {
  display: block;
  margin-block-end: 0.75rem;
  padding-block-end: 0.75rem;
  border-block-end: 1px solid var(--wp--preset--color--outline);
}

.ap-filters__selected-title {
  margin-block: 0 0.625rem;
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.9rem;
  font-weight: 700;
}

.ap-filters .ecom-storefront-selections {
  gap: 0.5rem;
}

.ap-filters .ecom-selections-group {
  gap: 0.5rem;
}

/* The plugin renders each selection as a full-width tertiary button — square,
   58px tall, one per row. The design has compact pills that sit two or three
   to a row, so the button is reset to shrink-wrap its label. */
.ap-filters .ecom-storefront-selections .ecom-button {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  width: auto;
  min-width: 0;
  min-height: 0;
  padding: 0.375rem 0.5rem 0.375rem 0.75rem;
  background-color: var(--wp--preset--color--base-alt);
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 999px;
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1.3;
  text-align: left;
  white-space: normal;
}

.ap-filters .ecom-storefront-selections .ecom-button:hover,
.ap-filters .ecom-storefront-selections .ecom-button:focus-visible {
  background-color: var(--wp--preset--color--base);
  border-color: var(--wp--preset--color--primary);
  color: var(--wp--preset--color--contrast);
}

/* The dismiss glyph ships at 20x24 and is nudged 5px right to clear the
   plugin's own padding; at pill size it needs neither. */
.ap-filters .ecom-storefront-selections .ecom-button svg {
  left: 0;
  flex: none;
  width: 0.875rem;
  height: 0.875rem;
}

.ap-filters .ecom-storefront-selections .ecom-button svg path {
  fill: currentcolor;
}

/* -------------------------------------------------------------------------
 * Mobile filters pane
 *
 * The plugin swaps the sidebar for a slide-in sheet when wp_is_mobile() is
 * true (Products/ProductsRenderer.php), built from the store-agnostic chrome
 * in templates/pane/pane.php — the same chrome the cart drawer uses. Every
 * rule here is therefore scoped to .ecom-storefront-options-mobile, the class
 * products-filters-pane.php passes in, so none of it reaches the cart.
 *
 * The groups themselves are already ours: the .ap-filter* rules above are
 * deliberately unscoped for this reason, and the pane picks them up. What is
 * left is the sheet's own chrome — header, sort row, footer — plus the wider
 * spacing the design uses at this size.
 *
 * Two things in the design are not reachable from here and are left out: the
 * "Clear All" link beside the title (the header is generated inside the shared
 * pane chrome, and reproducing it would mean forking the cart's pane markup
 * into the theme), and the result count on the apply button (see the note in
 * commerce-connect/products/mobile/products-filters-pane-footer.php).
 *
 * The selectors carry `.ecom-pane` alongside the pane's own class, and repeat
 * the band (`.ecom-pane-header`, `.ecom-pane-footer`) where the plugin does:
 * ecom-ui-styles.css states these at 0-2-0 and 0-3-0, so the shorter form
 * ties and loses on order. Matching its shape and adding a level wins without
 * reaching for !important.
 * ---------------------------------------------------------------------- */

/* The container's blanket 12px is dropped so each band can set its own inset:
   the design runs the header and footer rules edge to edge, which a padded
   container cannot do without negative margins in three places. */
.ecom-pane.ecom-storefront-options-mobile .ecom-pane-container {
  padding: 0;
}

/* The plugin pulls the header out to the container padding it no longer has,
   and leaves the row's items stretched rather than centred, which is what puts
   the close glyph high against the title. */
/* max-height is the plugin's, and it caps the bar at 60px — a foot shorter
   than the close button plus this padding needs once the button is back in
   flow (below). */
.ecom-pane.ecom-storefront-options-mobile .ecom-pane-header {
  width: 100%;
  max-height: none;
  margin: 0;
  padding: 0.875rem 1.25rem;
  border-block-end: 1px solid var(--wp--preset--color--outline);
}

.ecom-pane.ecom-storefront-options-mobile .ecom-cart-header-inner {
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

/* The plugin's title is an inline-block with a -4px top margin — an optical
   nudge for its own header, which here just lifts the text off centre and
   leaves the inline box's descender slack underneath it. Squaring both up is
   what evens the space above and below the heading. */
.ecom-pane.ecom-storefront-options-mobile .ecom-pane-header .ecom-pane-title {
  display: block;
  margin: 0;
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1.375rem;
  font-weight: 700;
  line-height: 1.2;
}

/* A circular tinted target, as drawn, rather than the bare 40x40 glyph the
   pane ships. */
/* The button is taken out of flow by the plugin (position: absolute, top:
   -7px, right: -15px against .ecom-cart-header-inner), which is what sits it
   high in the bar and hard against the pane's edge — and why the row's
   space-between had nothing to place. Back in flow, the row's own centring
   does it. */
/* Both panes take the same treatment as the mobile menu's close button, which
   is an .ap-iconbtn: a 2.75rem circle in base-alt with a primary-coloured mark.
   The values are repeated rather than the selector being folded into that rule
   because the plugin's .ecom-button.ecom-button--md weighs 0-2-0 and lands
   after theme.css -- a single .ap-iconbtn would tie and lose. These selectors
   are 0-3-0, so they carry without !important.

   The two panes are addressed separately because only the filters pane is
   built from the plugin's shared pane.php; the cart is still React-rendered
   and has no pane class of its own to hang off, hence the :has() on its body.

   Both are absolutely positioned by the plugin -- the cart's at top:-7px,
   right:-15px, the filters pane's similarly — with offsets tuned to its own
   container padding. Rather than re-tune those against the padding we use,
   which leaves the button anchored to the inner row and double-counting the
   gutter, position:static returns it to flow and the header row's
   space-between and align-items:center place it. */
.ecom-pane.ecom-storefront-options-mobile .ecom-pane-close,
.ecom-pane-inner:has(.ecom-cart-contents) .ecom-pane-close {
  position: static;
  transform: none;
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 2.75rem;
  height: 2.75rem;

  /* The plugin caps every .ecom-button at 40px below 800px, which is exactly
     the width this runs at. A max- constraint clamps the used height whatever
     the specificity of the height beside it, so releasing it is not optional
     -- without this the buttons come out 44 wide by 40 tall. */
  max-height: none;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background-color: var(--wp--preset--color--base-alt);
  color: var(--wp--preset--color--primary);
  transition: background-color 0.15s ease-in-out;
}

/* The plugin fades the button to 50% on hover; with a tinted target behind it
   the state reads better as a darker fill -- the same swap .ap-iconbtn makes. */
.ecom-pane.ecom-storefront-options-mobile .ecom-pane-close:hover,
.ecom-pane.ecom-storefront-options-mobile .ecom-pane-close:focus-visible,
.ecom-pane-inner:has(.ecom-cart-contents) .ecom-pane-close:hover,
.ecom-pane-inner:has(.ecom-cart-contents) .ecom-pane-close:focus-visible {
  background-color: var(--wp--preset--color--outline);
  color: var(--wp--preset--color--primary-dark);
  opacity: 1;
}

/* The plugin ships a solid, filled X; the menu's is drawn with a 1.7px stroke
   and round caps. Scaling the plugin's up would match the button but not the
   mark, so its icon is dropped and the theme's own glyph painted through a
   mask -- which, being alpha-only, takes its colour from the button and so
   follows the hover swap above like a real icon would.

   The path is inc/icons.php's `close`, and the only copy of it outside that
   file. It is two crossed lines and has no reason to change, but if that one
   is ever redrawn this needs the same edit. */
.ecom-pane.ecom-storefront-options-mobile .ecom-pane-close .ecom-icon,
.ecom-pane-inner:has(.ecom-cart-contents) .ecom-pane-close .ecom-icon {
  display: none;
}

.ecom-pane.ecom-storefront-options-mobile .ecom-pane-close::before,
.ecom-pane-inner:has(.ecom-cart-contents) .ecom-pane-close::before {
  content: "";
  width: 1.375rem;
  height: 1.375rem;
  background-color: currentcolor;
  -webkit-mask: var(--ap-icon-close) center / contain no-repeat;
  mask: var(--ap-icon-close) center / contain no-repeat;
}

.ecom-pane.ecom-storefront-options-mobile .ecom-pane-body {
  padding: 0.25rem 1.25rem 1.25rem;
}

/* Sort has no counterpart in the design — the design's sheet is filters only —
   but the pane is where the plugin puts it on mobile, so it stays and is drawn
   as a control instead of the unbounded ghost button it ships as: a full-width
   outlined row matching the toolbar's pills on the desktop archive. */
.ecom-pane.ecom-storefront-options-mobile
  .ecom-pane-body
  .ecom-mobile-sorting-section {
  margin-block: 0 0.25rem;
  padding-block: 0.75rem;
  border-block-end: 1px solid var(--wp--preset--color--outline);
}

.ecom-pane.ecom-storefront-options-mobile .ecom-dropdown__container {
  width: 100%;
}

.ecom-pane.ecom-storefront-options-mobile .ecom-dropdown__trigger {
  justify-content: flex-start;
  gap: 0.375rem;
  width: 100%;
  min-height: 2.75rem;
  padding: 0.5rem 0.875rem;
  background-color: var(--wp--preset--color--base);
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 8px;
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.9375rem;
  font-weight: 400;
}

/* "Sort by:" is the label and the chosen option the value, as on the archive
   toolbar; the trigger renders the label as a bare text node, so the emphasis
   is reapplied to the value span. */
.ecom-pane.ecom-storefront-options-mobile
  .ecom-dropdown__trigger
  > span:not(.ecom-icon-animate) {
  color: var(--wp--preset--color--contrast);
  font-weight: 700;
}

/* The chevron to the end of the row, away from the value. */
.ecom-pane.ecom-storefront-options-mobile
  .ecom-dropdown__trigger
  .ecom-icon-animate {
  margin-inline-start: auto;
}

.ecom-pane.ecom-storefront-options-mobile
  .ecom-dropdown__trigger
  .ecom-icon-animate
  svg {
  width: 0.75rem;
  height: 0.75rem;
}

.ecom-pane.ecom-storefront-options-mobile .ecom-dropdown__menu {
  left: 0;
  right: 0;
  width: auto;
}

/* The 200px the plugin hangs off the sidebar to clear its own sticky
   positioning is dead space at the foot of a sheet that has a fixed footer. */
.ecom-pane.ecom-storefront-options-mobile .ecom-storefront-sidebar {
  margin-block-end: 0;
}

/* Group rhythm opens up at this size: the design gives each row a ~48px target
   and sets the headings a step larger than the desktop sidebar, where they sit
   inside a narrow card. The heading rule stacks the classes the element
   carries for the reason given above it — the pane's own heading rule is
   0-4-0. */
.ecom-pane.ecom-storefront-options-mobile .ap-filter {
  padding-block: 0.375rem;
}

.ecom-pane.ecom-storefront-options-mobile
  .ap-filter
  .ap-filter__heading.ecom-storefront-filter-heading.ecom-label.ecom-drawer-trigger {
  padding-block: 0.75rem;
  font-size: 1rem;
  font-weight: 700;
}

/* The checkbox itself is the shared .ap-filter__checkbox rule above, so the
   control looks the same in both places. The sizing around it does not: a
   touch target wants more room than a 360px sidebar carrying fourteen groups
   can spare, so the sheet sets its own label size and spacing here and leaves
   the sidebar's alone. */
.ecom-pane.ecom-storefront-options-mobile .ap-filter__options {
  padding-block: 0.25rem 0.875rem;
}

.ecom-pane.ecom-storefront-options-mobile .ap-filter__row {
  gap: 0.75rem;
}

.ecom-pane.ecom-storefront-options-mobile .ap-filter__label {
  font-size: 1rem;
}

/* The rhythm has to come from a margin here, not the `gap` the sidebar uses:
   ecom-ui-styles.css forces `display: block !important` on the options list
   inside the pane (`.ecom-storefront-options-mobile
   .ecom-storefront-options-wrapper .ecom-storefront-filter
   .ecom-storefront-filter-content > div`), so the list is never a flex
   container there and the gap is inert. The rows themselves are grandchildren
   of that element and keep their own flex layout. */
.ecom-pane.ecom-storefront-options-mobile .ap-filter__row + .ap-filter__row {
  margin-block-start: 0.75rem;
}

/* A fixed action bar, separated from the scrolling list by a rule and holding
   the buttons clear of the home indicator on notched devices. */
.ecom-pane.ecom-storefront-options-mobile .ecom-pane-footer {
  width: 100%;
  margin: 0;
  padding: 0.875rem 1.25rem;
  padding-block-end: calc(0.875rem + env(safe-area-inset-bottom, 0px));
  background-color: var(--wp--preset--color--base);
  border-block-start: 1px solid var(--wp--preset--color--outline);
}

.ecom-pane.ecom-storefront-options-mobile
  .ecom-pane-footer
  .ecom-filter-footer-buttons {
  gap: 0.75rem;
}

.ecom-pane.ecom-storefront-options-mobile
  .ecom-pane-footer
  .ecom-filter-footer-buttons
  .ecom-button {
  justify-content: center;
  min-height: 3rem;
  padding: 0.75rem 1rem;
  border-radius: 8px;
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.9375rem;
  font-weight: 700;
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

/* Outlined in the text colour, not the hairline grey the plugin uses — the
   design gives the two buttons equal visual weight. */
.ecom-pane.ecom-storefront-options-mobile
  .ecom-pane-footer
  .ecom-filter-clear-button {
  background-color: var(--wp--preset--color--base);
  border: 1px solid var(--wp--preset--color--contrast);
  color: var(--wp--preset--color--contrast);
}

.ecom-pane.ecom-storefront-options-mobile
  .ecom-pane-footer
  .ecom-filter-clear-button:hover,
.ecom-pane.ecom-storefront-options-mobile
  .ecom-pane-footer
  .ecom-filter-clear-button:focus-visible {
  background-color: var(--wp--preset--color--base-alt);
  color: var(--wp--preset--color--contrast);
}

/* .ecom-button--primary is the plugin's amber; the design's apply button is
   the brand blue used for every other primary action on the site. */
.ecom-pane.ecom-storefront-options-mobile
  .ecom-pane-footer
  .ecom-filter-show-results-button {
  background-color: var(--wp--preset--color--primary);
  border: 1px solid var(--wp--preset--color--primary);
  color: var(--wp--preset--color--base);
}

.ecom-pane.ecom-storefront-options-mobile
  .ecom-pane-footer
  .ecom-filter-show-results-button:hover,
.ecom-pane.ecom-storefront-options-mobile
  .ecom-pane-footer
  .ecom-filter-show-results-button:focus-visible {
  background-color: var(--wp--preset--color--primary-dark);
  border-color: var(--wp--preset--color--primary-dark);
  color: var(--wp--preset--color--base);
}

/* Search results title.
 *
 * The text is rewritten client-side by Commerce Connect (from "Search Results"
 * to "Showing search results for …"), so only the styling belongs here — the
 * copy comes from the plugin.
 * ------------------------------------------------------------------------- */

/* Qualified with main.ap-main (0-2-1) because the title is the first child of
   the flow container, and core zeroes that: `:root :where(.is-layout-flow) >
   :first-child { margin-block-start: 0 }` is 0-2-0 and beats a bare class. */
main.ap-main .ap-searchtitle {
  margin-block: var(--wp--preset--spacing--50) var(--wp--preset--spacing--30);
}

/* Search results: hold the products block to the container.
 *
 * It cannot be wrapped in a constrained group to achieve this. The search-tabs
 * block toggles the two result sets with general sibling combinators —
 * `.commerce-connect-search-tabs ~ .commerce-connect-block.products-blocks` —
 * so the products block and the core/query block have to stay siblings of the
 * tabs. A wrapper puts the products out of reach and the Content tab then
 * shows both sets at once.
 *
 * The child combinator keeps this to the search template: on the category
 * archive the same block sits inside a group and is already content width.
 * ------------------------------------------------------------------------- */

main.ap-main > .commerce-connect-block.products-blocks.alignwide {
  max-width: var(--wp--style--global--content-size);
  margin-inline: auto;
}

/* -------------------------------------------------------------------------
 * Cart drawer
 *
 * The drawer's contents are the plugin's and cannot be changed, so everything
 * here is presentation only, hung off the class names its React components
 * emit (ecom-cart-lineitem-*, ecom-cart-footer-totals, ecom-cart-checkout).
 *
 * Scope: .ecom-pane-* is shared with the mobile filters pane, so any rule
 * touching those classes is qualified with :has(.ecom-cart-contents) to keep
 * it to the cart. Rules on .ecom-cart-* classes need no such guard.
 * ---------------------------------------------------------------------- */

.ecom-pane-inner:has(.ecom-cart-contents) .ecom-pane-title {
  /* The heading class pulls the text up 4px as an optical adjustment, which
     leaves it above the close button's centre line once both are in a centred
     flex row. Zeroed, and the wrapper centres it instead. */
  margin-block: 0;
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1.5rem;
  font-weight: 700;
}

.ecom-pane-inner:has(.ecom-cart-contents) .ecom-cart-title-wrapper {
  display: flex;
  align-items: center;
}

/* Title and price are the wrong way round against the design: the plugin puts
   the link colour on the title and leaves the price in body text. The design
   has a dark bold title with the price as the blue accent. */
/* The link wraps the title rather than sitting inside it:
   a.ecom-cart-lineitem-title-link > div > span.ecom-cart-lineitem-title.
   The underline is drawn by the anchor, in the anchor's own colour, so the
   anchor has to carry the title colour too — otherwise the rule renders in the
   link blue while the text it underlines stays dark. */
.ecom-cart-lineitem-title,
.ecom-cart-lineitem-title-link,
.ecom-cart-lineitem-title-link:hover,
.ecom-cart-lineitem-title-link:focus-visible {
  color: var(--wp--preset--color--contrast);
}

.ecom-cart-lineitem-title {
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.35;
}

.ecom-cart-lineitem-title-link {
  text-decoration: none;
}

.ecom-cart-lineitem-title-link:hover,
.ecom-cart-lineitem-title-link:focus-visible {
  text-decoration: underline;
  text-decoration-color: currentcolor;
}

.ecom-cart-lineitem-variants,
.ecom-cart-lineitem-variant-name,
.ecom-cart-lineitem-variant-value {
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.875rem;
}

/* The wrapper is only a flex row; the amount itself is .ecom-cart-lineitem-price
   inside it, and carries its own colour, weight and a 0.8x font size. Colouring
   the wrapper alone leaves the visible text unchanged. */
.ecom-cart-lineitem-price-total-wrapper,
.ecom-cart-lineitem-price {
  color: var(--wp--preset--color--primary);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1.0625rem;
  font-weight: 700;
}

/* Remove is destructive, so it carries the theme's red rather than sitting in
   body text. red-orange is the only red in the palette; the design's darker
   maroon has no token. */
.ecom-cart-lineitem-remove {
  color: var(--wp--preset--color--red-orange);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.875rem;
  text-decoration: underline;
}

.ecom-cart-lineitem-remove:hover,
.ecom-cart-lineitem-remove:focus-visible {
  color: var(--wp--preset--color--red-orange);
  text-decoration: none;
}

/* The stepper is the same markup as the PDP's, so it gets the same treatment:
   three separately bordered cells become one rounded control by moving the
   border and fill to the wrapper and clearing the children. */
.ecom-cart-lineitem .ecom-quantity {
  box-sizing: border-box;
  height: 2.5rem;
  margin-bottom: 0;
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 8px;
  background-color: var(--wp--preset--color--base-alt);
  overflow: hidden;
}

/* The cart renders one level deeper than the PDP: the controls sit inside an
   .ecom-l-row-no-wrap instead of being direct children of .ecom-quantity, so
   they are reached by class rather than with a child combinator. */
.ecom-cart-lineitem .ecom-quantity > .ecom-l-row-no-wrap {
  height: 100%;
}

.ecom-cart-lineitem .ecom-quantity__button,
.ecom-cart-lineitem .ecom-quantity__input {
  height: 100%;
  max-height: 100%;
  border: 0;
  background-color: transparent;
  font-family: var(--wp--preset--font-family--ui);
}

.ecom-cart-lineitem .ecom-quantity__input {
  color: var(--wp--preset--color--contrast);
  font-weight: 700;
}

.ecom-cart-lineitem .ecom-quantity__input:focus-visible {
  outline: 2px solid var(--wp--preset--color--primary);
  outline-offset: -2px;
}

/* The price breakdown and the checkout button share one tinted panel, as
   drawn. The footer is the element that wraps both. */
/* The pane container pads all four sides, which insets the totals panel from
   the drawer edges. The padding moves to the sections instead, so the panel
   runs edge to edge as drawn. The header offsets that container padding with
   negative margins, which have to go with it. */
.ecom-pane-inner:has(.ecom-cart-contents) .ecom-pane-container {
  padding: 0;
}

/* Empty cart: centre the message and its button in the drawer.
 *
 * .ecom-empty-cart-wrapper already asks for this — height: 100% with
 * justify-content: center — but the height never resolves. .ecom-cart-contents
 * is flex: 1, which needs a flex parent, and .ecom-pane-body is display: block
 * in the cart, so the contents collapse to their own height and 100% of that
 * is just the message. Making the body a flex column lets the existing rule
 * work.
 *
 * Gated on the empty wrapper being present so the populated cart keeps the
 * scroll behaviour it has today — .ecom-cart-contents carries overflow-y:
 * auto, and which element owns the scrolling would otherwise change. The 50px
 * bottom padding it uses to clear the last line item goes with it, or the
 * message would sit 25px above centre. */
.ecom-pane-inner:has(.ecom-cart-contents)
  .ecom-pane-body:has(.ecom-empty-cart-wrapper) {
  display: flex;
  flex-direction: column;
}

.ecom-cart-contents:has(.ecom-empty-cart-wrapper) {
  padding-block-end: 0;
}

.ecom-pane-inner:has(.ecom-cart-contents) .ecom-pane-header {
  /* The header breaks out of the container padding with negative margins *and*
     a matching `width: calc(100% + …)`. Both have to be undone, or it stays
     40px wider than the drawer. */
  width: 100%;
  margin-inline: 0;

  /* The plugin caps the bar at 60px, which is its own 40px close button plus
     this padding above it and nothing below. At 44px the button reaches the
     cap exactly and sits hard on the header's rule, with the padding below it
     squeezed to zero. Releasing the cap lets the padding apply on both sides
     and the button centre in the bar. The filters pane needs the same and does
     it for the same reason -- see the .ecom-storefront-options-mobile header
     rule above. */
  max-height: none;
  padding: var(--wp--preset--spacing--30);
}

.ecom-pane-inner:has(.ecom-cart-contents) .ecom-cart-header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.ecom-pane-inner:has(.ecom-cart-contents) .ecom-cart-contents {
  padding-inline: var(--wp--preset--spacing--30);
}

.ecom-pane-inner:has(.ecom-cart-contents) .ecom-pane-footer {
  padding: var(--wp--preset--spacing--40);
  background-color: var(--wp--preset--color--base-alt);
  border-block-start: 1px solid var(--wp--preset--color--outline);
}

.ecom-cart-footer-totals,
.ecom-cart-footer-totals .ecom-text {
  font-family: var(--wp--preset--font-family--ui);
}

.ecom-cart-footer-totals .ecom-total-prefix {
  color: var(--wp--preset--color--contrast);
  font-weight: 700;
}

.ecom-cart-footer-totals .ecom-subtotal-price,
.ecom-cart-footer-totals .ecom-total-amount {
  color: var(--wp--preset--color--primary);
  font-weight: 700;
}

/* `-description-text` is the explanatory sentence under the total, not a
   label — body copy, so Lato and muted rather than the bold UI face. */
.ecom-cart-footer-totals .ecom-subtotal-description,
.ecom-cart-footer-totals .ecom-subtotal-description-text {
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--body);
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.5;
}

/* The plugin ships checkout in the accent orange; the design uses the primary
   blue that every other principal action on the site uses. */
.ecom-cart-checkout button,
.ecom-cart-checkout .ecom-button {
  width: 100%;
  background-color: var(--wp--preset--color--primary);
  border-color: var(--wp--preset--color--primary);
  color: var(--wp--preset--color--base);
  font-family: var(--wp--preset--font-family--ui);
  font-weight: 700;
}

.ecom-cart-checkout button:hover,
.ecom-cart-checkout .ecom-button:hover {
  background-color: var(--wp--preset--color--primary-dark);
  border-color: var(--wp--preset--color--primary-dark);
  color: var(--wp--preset--color--base);
}

/* -------------------------------------------------------------------------
 * Related products — commerce-connect/related-products on the PDP.
 *
 * The block is a React island: PHP emits an empty div and the component draws
 * everything, so there is no template to override and CSS is the only lever.
 * Its own heading is hidden in favour of a core/heading and core/paragraph in
 * the template, which the client can edit and which carry the theme's display
 * face — the block offers a heading string but no subheading at all.
 * ---------------------------------------------------------------------- */

/* spacing|50 is the section rhythm the front page settled on, and the grey
   accordion band above already uses it for its own top margin -- so the two
   gaps either side of that band now match instead of stepping 40px then 64px. */
.ap-related {
  margin-block-start: var(--wp--preset--spacing--50);
}

.ap-related__title {
  margin-block: 0 0.25rem;
  font-size: clamp(2rem, 3vw, 2.75rem);
  line-height: 1.1;
}

.ap-related__intro {
  margin-block: 0 var(--wp--preset--spacing--40);
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--body);
}

/* The component centres itself in a hardcoded
   `max-width: calc(columns * 300px + gaps)` with its own 40px margin and 16px
   gutter, which lands it out of step with every other row on the page. */
.ap-related .ecom-related-products {
  max-width: none;
  margin: 0;
  padding-inline: 0;
}

.ap-related .ecom-scroll-carousel__header {
  display: none;
}

/* Cards here are the component's, not the .ap-card markup our PHP override
   renders for the archive, so the archive's rules do not reach them. Matched
   by hand to keep the two consistent. */
.ap-related .product-card {
  box-sizing: border-box;
  padding: 0.75rem;
  background-color: var(--wp--preset--color--base);
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 12px;
  transition:
    border-color 0.15s ease,
    box-shadow 0.15s ease;
}

.ap-related .product-card:hover,
.ap-related .product-card:focus-within {
  border-color: var(--wp--preset--color--primary);
  box-shadow: 0 6px 18px rgb(30 41 59 / 8%);
}

.ap-related .product-card__images {
  aspect-ratio: 1;
  background-color: transparent;
  border-radius: 8px;
}

.ap-related .product-card__image img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Titles run to one or two lines, so without this the prices sit at different
   heights across a row. The card becomes a column and the price is pushed to
   the bottom, the same arrangement the archive cards use. */
.ap-related .product-card {
  display: flex;
  flex-direction: column;
}

.ap-related .product-card__content {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 0.5rem;
  height: auto;
  padding-block: 0.75rem 0;
  padding-inline: 0.5rem;
}

.ap-related .product-card__content .ecom-product-pricing {
  margin-block-start: auto;
}

.ap-related .ecom-product-title {
  margin-block: 0;
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.35;
}

.ap-related .ecom-product-prices__price {
  color: var(--wp--preset--color--primary);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1.25rem;
  font-weight: 700;
}

/* -------------------------------------------------------------------------
 * Account forms — sign in, registration and the rest of the account block.
 *
 * These blocks are React, hydrated client-side from
 * commerce-connect-commons.js, so there are no templates to override the way
 * the products block allows: everything here is presentation hung off the
 * class names the Form and Input components emit (.ecom-form__*,
 * .ecom-input__*). Nothing is scoped to the login form — registration, the
 * password reset and the account panels are built from the same components,
 * so they inherit the same treatment.
 *
 * Copy stays the plugin's. The form's title is a hardcoded "Sign In" literal
 * in the bundle (formTitleText in PluginSettings.php is never read), the login
 * form passes an explicit <h2> child rather than the Form.Header title/subtitle
 * props so its subtitle slot never renders, and the inputs are created without
 * a placeholder — so the design's "Welcome Back", its supporting line and its
 * placeholder text cannot be set. Faking them with pseudo-element content
 * would hide them from screen readers and translation, so the design's *type*
 * treatment is applied to the plugin's words instead.
 *
 * One deliberate departure: the design puts white text on the amber button.
 * That is about 2:1 against #f3a11d and fails WCAG AA, so the dark text the
 * rest of the theme's amber buttons use is kept — 8.6:1 on the same fill.
 * ---------------------------------------------------------------------- */

/* The design's card is wider than the plugin's fixed 400px and set in from its
   edges rather than hugging them. */
.ecom-form--card {
  width: 100%;
  max-width: 29rem;
  padding: 2.5rem 2.75rem;
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 12px;
  /* The design floats the card on a tinted page; this theme's account page is
     white, so the card is given a soft lift instead of relying on the ground
     behind it to describe its edges. */
  box-shadow: 0 2px 16px rgb(30 41 59 / 6%);
}

.ecom-form__layout {
  max-width: 29rem;
}

/* Title and the fields under it are centred as a block in the design, with the
   labels still reading from the left. */
.ecom-form__header {
  margin-block-end: 2rem;
  text-align: center;
}

/* Forms that pass a title prop rather than their own <h2> — registration, the
   password reset — render it inside this wrapper, which left-aligns and
   shrink-wraps the heading. Centred to match the sign-in card. */
.ecom-form__title-wrapper {
  align-items: center;
  text-align: center;
}

.ecom-form__title {
  justify-content: center;
  color: var(--wp--preset--color--primary);
  font-family: var(--wp--preset--font-family--display);
  font-size: 2.25rem;
  font-weight: 700;
  line-height: 1.1;
}

.ecom-input__field-group {
  gap: 1.5rem;
  margin-block-end: 0;
}

/* Registration splits its fields across several groups; with the plugin's
   bottom margin dropped they would butt together. Spacing them from the top
   keeps the last group clear of the footer, which sets its own margin. */
.ecom-input__field-group + .ecom-input__field-group {
  margin-block-start: 1.5rem;
}

.ecom-input__label {
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.875rem;
  font-weight: 700;
}

/* Amber rather than the plugin's crimson, which reads as an error state before
   the field has been touched. */
.ecom-input__asterisk {
  color: var(--wp--preset--color--red-orange);
}

/* A filled field with no outline, as drawn. The border is kept at the same
   width in the resting state so the focus ring does not change the field's
   size when it appears. */
.ecom-input {
  height: 3.25rem;
  padding: 0.75rem 1rem;
  background-color: var(--wp--preset--color--base-alt);
  border: 1px solid var(--wp--preset--color--base-alt);
  border-radius: 8px;
  font-family: var(--wp--preset--font-family--body);
  font-size: 0.9375rem;
}

.ecom-input:focus {
  background-color: var(--wp--preset--color--base);
  border-color: var(--wp--preset--color--primary);
  box-shadow: 0 0 0 3px rgb(69 123 210 / 15%);
}

/* "Forgot password?" moves out of the label row to sit under the field, right
 * aligned, as the design has it.
 *
 * Absolute positioning rather than reordering the flex column: the button is a
 * child of the <label>, so moving it in flow would mean `display: contents` on
 * the label, which some browsers still drop out of the accessibility tree. The
 * field reserves the space with padding instead, so nothing overlaps.
 */
.ecom-input__field:has(.ecom-input__label--with-action) {
  position: relative;
  padding-block-end: 1.75rem;
}

.ecom-input__label-action {
  position: absolute;
  right: 0;
  bottom: 0;
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--wp--preset--color--primary);
}

/* The plugin underlines the link with a full-width ::after bar; the design
   leaves it plain until hover. */
.ecom-input__label-action::after {
  content: none;
}

.ecom-input__label-action:hover,
.ecom-input__label-action:focus-visible {
  color: var(--wp--preset--color--primary-dark);
  text-decoration: underline;
}

.ecom-input__password-toggle {
  color: var(--wp--preset--color--contrast-muted);
}

/* Password reset.
 *
 * The reCAPTCHA is dropped in as a bare, unclassed <div> between the field
 * group and the footer, so it inherits no spacing and sits flush against the
 * email input. :not([class]) is what distinguishes it from .ecom-form__footer,
 * which is also a div in that position and sets its own margin.
 */
.ecom-form .ecom-input__field-group + div:not([class]) {
  display: flex;
  justify-content: center;
  margin-block-start: 1.5rem;
}

/* The back arrow is taken out of flow so the heading centres on the card
   rather than on the space left beside the button. The plugin pulls it 20px
   into the card's padding with a negative margin, which is what that offset
   replaces. */
.ecom-form__header--with-back {
  position: relative;
}

/* Bigger than the plugin's 35px and given a resting tint, so it reads as a
   control rather than a stray glyph — it is the only way back to sign in. */
.ecom-form__header--with-back > button:first-child {
  position: absolute;
  left: -0.75rem;
  top: -0.25rem;
  width: 2.75rem;
  height: 2.75rem;
  margin-left: 0;
  padding: 0;
  background: var(--wp--preset--color--base-alt);
  border: 0;
  border-radius: 999px;
  color: var(--wp--preset--color--contrast);
}

/* .ecom-button--is-icon pins every icon to 16px through min-width/min-height
   as well as width/height, so all four are restated. */
.ecom-form__header--with-back > button:first-child svg {
  width: 1.375rem;
  min-width: 1.375rem;
  height: 1.375rem;
  min-height: 1.375rem;
}

.ecom-form__header--with-back > button:first-child:hover,
.ecom-form__header--with-back > button:first-child:focus-visible {
  background: var(--wp--preset--color--outline);
  color: var(--wp--preset--color--contrast);
}

/* The plugin right-aligns an auto-width submit; the design runs it the full
   width of the card. */
.ecom-form__footer {
  display: block;
  width: 100%;
  margin-block-start: 1.75rem;
}

.ecom-form__footer .ecom-button {
  width: 100%;
  min-height: 3.25rem;
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1rem;
  font-weight: 700;
}

.ecom-form__footer-link {
  margin-block-start: 1.5rem;
}

.ecom-form__link {
  font-family: var(--wp--preset--font-family--ui);
  font-weight: 700;
  color: var(--wp--preset--color--primary);
}

.ecom-form__link:hover,
.ecom-form__link:focus-visible {
  color: var(--wp--preset--color--primary-dark);
  text-decoration: underline;
}

/* Account tabs bar — the signed-in row that sits opposite Details/Addresses/
 * Orders.
 *
 * Logout ships as a ghost button (variant: "ghost" in the AccountUserStatus
 * component), which renders as bare text. It is the one action in that row, so
 * it takes the blue the rest of the site uses for a primary call to action —
 * the same values as the product card CTA and the filters' apply button.
 *
 * The interactive states carry .ecom-button--ghost as well as the container:
 * the plugin greys the label on hover, focus and active, and its
 * `.ecom-button--ghost:focus-visible:not(.ecom-button--is-loading)` rule is
 * 0-3-0 — level with the shorter form here, and later in the cascade. Only
 * background, border and colour are restated, so the focus ring the plugin
 * draws in the same rule still applies.
 * ---------------------------------------------------------------------- */

/* The bar is a fixed 48px with its items centred, which left the 40px button
   4px clear of the rule underneath it. Raising the bar rather than hanging a
   margin off the button: the row centres its items, so a bottom margin would
   push the button off-centre and out of line with the name beside it. The tabs
   are height: 100%, so their active underline follows the bar down and still
   meets the rule. */
.ecom-account {
  height: 3.75rem;
}

.ecom-account__user-status {
  align-items: center;
  gap: 1rem;
}

/* max-height is the plugin's blanket 40px cap on every button; the padding
   below needs the box to size itself. */
.ecom-account__user-status .ecom-button {
  max-height: none;
  min-height: 2.5rem;
  padding: 0.5rem 1.25rem;
  background: var(--wp--preset--color--primary);
  border: 1px solid var(--wp--preset--color--primary);
  border-radius: 8px;
  color: var(--wp--preset--color--base);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.9375rem;
  font-weight: 700;
  transition: background-color 0.15s ease;
}

.ecom-account__user-status .ecom-button.ecom-button--ghost:hover,
.ecom-account__user-status .ecom-button.ecom-button--ghost:focus-visible,
.ecom-account__user-status .ecom-button.ecom-button--ghost:active {
  background: var(--wp--preset--color--primary-dark);
  border-color: var(--wp--preset--color--primary-dark);
  color: var(--wp--preset--color--base);
}

/* The page title on sign in, register and the account pages takes the brand
 * blue, matching the card titles and the page titles on search and the
 * category archives. The headings inside the account panels stay black — they
 * label data rather than head a page, and the plugin's own colour is right for
 * them.
 *
 * Keyed off the account block being present rather than a page id, which would
 * not survive the pages being recreated. Covers the reset-password view too,
 * since that renders on /my-account/.
 * ---------------------------------------------------------------------- */
main.ap-main:has(.ecom-account-container) .wp-block-post-title {
  color: var(--wp--preset--color--primary);
}

@media (max-width: 600px) {
  .ecom-form--card {
    padding: 1.75rem 1.25rem;
  }
}

/* -------------------------------------------------------------------------
 * Products carousel — the "Latest Arrivals" section on the front page.
 *
 * The section chrome (heading, intro, category link) is core blocks from
 * patterns/products-carousel.php. The track is
 * commerce-connect/products-carousel, which is server rendered and hands its
 * `.swiper` to Swiper in the block's view.js — so the slide classes are the
 * plugin's and only the card inside a slide is ours, via the
 * commerce-connect/products/products-carousel.php override.
 *
 * The cards themselves need no rules here: the override emits the same
 * .ap-card markup as the archive grid, so the card treatment above applies
 * unchanged and the two stay in step.
 * ---------------------------------------------------------------------- */

/* Heading and intro on the left, the category link on the right, as drawn.
   Wraps to a stack once there is no room for both. */
.ap-productcarousel__intro {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem var(--wp--preset--spacing--40);
}

.ap-productcarousel__heads {
  flex: 1 1 22rem;
}

/* Only the heading is reset. The lede's margin-block-start is where the
   group's Block spacing lands, so zeroing it here would close the gap under
   the title — and leave the editor's Dimensions control doing nothing. */
.ap-productcarousel__title {
  margin-block: 0;
}

/* Keyed on the tag, not the class: the heading is an h1 on the front page and
   so takes the page-title size, but an editor who drops the level back to h2
   gets the block's own preset size again rather than a title-sized subheading.
   Beats the has-xx-large-font-size preset class on specificity (0-1-1 to
   0-1-0) without needing !important. */
h1.ap-productcarousel__title {
  font-size: var(--ap-title-h1);
}

.ap-productcarousel__lede {
  margin-block-end: 0;
}

.ap-productcarousel__lede {
  max-width: 44rem;
}

.ap-productcarousel__actions {
  flex: none;
}

/* core's outline style draws in the contrast colour; the design outlines in
   the brand blue and fills on hover. */
.ap-productcarousel__cta .wp-block-button__link {
  padding: 0.75rem 1.25rem;
  background-color: transparent;
  border: 2px solid var(--wp--preset--color--primary);
  border-radius: 8px;
  color: var(--wp--preset--color--primary);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.9375rem;
  font-weight: 700;
  transition:
    background-color 0.15s ease,
    color 0.15s ease;
}

.ap-productcarousel__cta .wp-block-button__link:hover,
.ap-productcarousel__cta .wp-block-button__link:focus-visible {
  background-color: var(--wp--preset--color--primary);
  color: var(--wp--preset--color--base);
}

/* Swiper gives a slide `height: 100%`, which against an auto-height wrapper
   resolves to the slide's own content — so a card with a two-line title comes
   out taller than its neighbours and the buttons stop sharing a baseline.
   Stretching them instead lets .ap-card's own height: 100% fill the row. */
.ap-productcarousel .swiper-slide {
  height: auto;
  align-self: stretch;
}

/* Arrows.
 *
 * Sized through --swiper-navigation-size rather than width/height, because
 * Swiper derives the button's own `margin-top: calc(0px - (size / 2))` from it
 * — set the size any other way and the arrows sit half a button low.
 *
 * Getting them out into the page gutter takes some care. Swiper's arrows have
 * to be descendants of `.swiper` — view.js finds them with
 * `swiper.querySelector('.swiper-button-prev')` — and `.swiper` clips its
 * slides with overflow: hidden, which would clip an arrow with them. But
 * clipping skips an absolutely positioned descendant whose containing block
 * sits outside the overflow element: so `.swiper` is made static and the block
 * root becomes the arrows' containing block. The slides still clip at the
 * container edge; the arrows no longer do.
 *
 * That also makes the block root what `100%` resolves against below, which is
 * what lets the bleed be measured from the container — the same arrangement
 * .ap-carousel uses for the post carousel, so both sets of arrows sit in the
 * same place. See that section for why the bleed is a min() of the gutter
 * rather than a fixed number.
 *
 * Vertically centred on the card — Swiper's own 50% default, with the
 * half-height pull-up it derives from the size below. */
.ap-productcarousel .products-carousel-block {
  --ap-carousel-arrow-bleed: -0.25rem;

  position: relative;
}

.ap-productcarousel .swiper {
  --swiper-navigation-size: 2.75rem;
  --swiper-navigation-sides-offset: calc(var(--ap-carousel-arrow-bleed) * -1);
  --swiper-navigation-top-offset: 50%;

  position: static;
}

/* Same size, breakpoint and reach as .ap-carousel__arrow, so the two
   carousels' controls read as one control. */
@media (min-width: 900px) {
  .ap-productcarousel .products-carousel-block {
    --ap-carousel-arrow-bleed: min(
      var(--ap-carousel-arrow-reach, 4.5rem),
      (100vw - 100%) / 2 - 1.25rem
    );
  }

  .ap-productcarousel .swiper {
    --swiper-navigation-size: 3.25rem;
  }
}

.ap-productcarousel .swiper-button-prev,
.ap-productcarousel .swiper-button-next {
  background-color: var(--wp--preset--color--primary);
  border-radius: 999px;
  box-shadow: 0 4px 12px rgb(30 41 59 / 20%);
  color: var(--wp--preset--color--base);
  transition: background-color 0.15s ease;
}

.ap-productcarousel .swiper-button-prev:hover,
.ap-productcarousel .swiper-button-next:hover {
  background-color: var(--wp--preset--color--primary-dark);
}

/* Sized to match the post carousel's chevron, which is not the same as
   matching its SVG box. That one is a stroked path inset in a 0 0 24 24
   viewBox, so its ink is 14 of those 24 units — about 15.7px tall once the
   1.7px stroke is counted. Swiper's is a filled path that fills its 0 0 11 20
   viewBox edge to edge, so the box has to come down to the size of the other
   one's ink rather than to the size of its box. */
.ap-productcarousel .swiper-button-prev svg,
.ap-productcarousel .swiper-button-next svg {
  width: 1rem;
  height: 1rem;
}

@media (max-width: 781px) {
  .ap-productcarousel__intro {
    align-items: flex-start;
  }
}

/* -------------------------------------------------------------------------
 * Single post
 *
 * The template holds only what core gives it — featured image, date, title,
 * content — so everything below is typography and treatment for blocks the
 * editor produces, not new furniture. The design's category pill, byline,
 * callouts, related cards and prev/next are deliberately not built here.
 *
 * The content is what came out of the Fusion/classic migration: paragraphs,
 * lists, images, tables and a lot of h4/h5 headings, since old imported posts
 * rarely used h2. Two heading treatments cover that, per the design — display
 * type in accent for the section headings an author reaches for first, and a
 * quieter bold sans for the deeper levels the archive is full of.
 * ---------------------------------------------------------------------- */

.ap-post > * {
  /* The featured image and the header sit tighter together than the rest of
     the flow, which the group's own Block spacing cannot express on its own. */
  margin-block-start: var(--wp--preset--spacing--40);
}

.ap-post .ap-post__image {
  margin-block-start: 0;
}

.ap-post .ap-post__image img {
  width: 100%;
  border-radius: 12px;
  object-fit: cover;
}

/* Meta above the title, as drawn — small, muted, and set in the UI face so it
   reads as a label rather than as the start of the prose. */
.ap-postheader .ap-postheader__meta {
  margin-block: 0 0.5rem;
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.875rem;
}

.ap-postheader .ap-postheader__title {
  margin-block: 0;
  color: var(--wp--preset--color--primary);
  font-size: var(--ap-title-h1);
  line-height: 1.05;
}

/* ---- post body ----------------------------------------------------------
 *
 * Everything between the ap:post-content markers is lifted verbatim by
 * inc/editor-post-styles.php and re-prefixed for the block editor canvas, so
 * the two never drift. Keep .ap-postbody as the only prefix in here.
 * ------------------------------------------------------------------------ */

/* ap:post-content:start */
.ap-postbody {
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--body);
  font-size: 1.0625rem;
  line-height: 1.75;
}

.ap-postbody p {
  margin-block: 0 1.25rem;
}

.ap-postbody > *:last-child {
  margin-block-end: 0;
}

/* Blocks dropped into the flow bring their own typography and are not prose: a
   product card's title is an h3 in the card's own face, and its call to action
   is a button that happens to be an anchor. Left alone, the rules below turn
   those titles into 30px Amatic SC and paint the button's label the same blue
   as the button. Every prose rule that could reach inside one is guarded.

   The guard sits in :where(), so specificity is unchanged from the plain
   element selector it replaces and nothing else in the cascade shifts. It has
   to be repeated per selector rather than factored out — a comma-separated
   list would drop each compound's element back out of the count. */

/* h2 and h3 are the design's feature headings: display face, accent, and set
   off from the paragraph above more than the one below so each reads as
   opening a section rather than floating between two. */
.ap-postbody h2:where(:not(.commerce-connect-block *)),
.ap-postbody h3:where(:not(.commerce-connect-block *)) {
  margin-block: 2.5rem 0.75rem;
  color: var(--wp--preset--color--accent);
  font-family: var(--wp--preset--font-family--display);
  font-weight: 700;
  line-height: 1.15;
}

.ap-postbody h2:where(:not(.commerce-connect-block *)) {
  font-size: clamp(1.75rem, 2.6vw, 2.25rem);
}

.ap-postbody h3:where(:not(.commerce-connect-block *)) {
  font-size: clamp(1.5rem, 2.2vw, 1.875rem);
}

/* h4 and down carry the bulk of the migrated archive, so they take the
   quieter treatment the design gives its sub-headings. */
.ap-postbody h4:where(:not(.commerce-connect-block *)),
.ap-postbody h5:where(:not(.commerce-connect-block *)),
.ap-postbody h6:where(:not(.commerce-connect-block *)) {
  margin-block: 2rem 0.5rem;
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.4;
}

.ap-postbody h2:where(:not(.commerce-connect-block *)):first-child,
.ap-postbody h3:where(:not(.commerce-connect-block *)):first-child,
.ap-postbody h4:where(:not(.commerce-connect-block *)):first-child {
  margin-block-start: 0;
}

/* A category card is an anchor wrapping a whole tile, and it carries its own
   colour and its own hover. The rest state survives on specificity, but the
   hover here adds a pseudo-class and so would outrank it — turning white type
   on a photograph blue the moment the pointer lands. Both states name the
   exclusion, so neither depends on a specificity race to come out right. */
.ap-postbody a:where(:not(.wp-element-button, .commerce-connect-block *, .ap-catgrid *)) {
  color: var(--wp--preset--color--primary);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.ap-postbody a:where(:not(.wp-element-button, .commerce-connect-block *, .ap-catgrid *)):hover,
.ap-postbody a:where(:not(.wp-element-button, .commerce-connect-block *, .ap-catgrid *)):focus-visible {
  color: var(--wp--preset--color--primary-dark);
}

/* The prose measure is a touch larger than the document's, and it inherits —
   which reaches anything that deliberately leaves its own size alone, such as
   a category card's call to action. These are theme.json's own root values, so
   a block dropped into the flow is set exactly as the same block is anywhere
   else on the site. */
.ap-postbody .commerce-connect-block,
.ap-postbody .ap-catgrid {
  font-size: var(--wp--preset--font-size--medium);
  line-height: 1.6;
}

.ap-postbody :where(ul, ol) {
  margin-block: 0 1.25rem;
  padding-inline-start: 1.5rem;
}

.ap-postbody li {
  margin-block-end: 0.5rem;
}

.ap-postbody li::marker {
  color: var(--wp--preset--color--primary);
}

/* Nested lists belong to the item above them, not to the run of items. */
.ap-postbody li > :where(ul, ol) {
  margin-block: 0.5rem 0;
}

/* Images are captioned underneath and centred on the measure, as drawn.
 *
 * Deliberately max-width rather than width: the archive is full of small
 * Blogger graphics, and forcing every one to the full 860px measure upscales
 * a 400px logo to twice its size and drags its own whitespace with it. Large
 * images still fill the measure; small ones sit centred at their own size.
 */
.ap-postbody .wp-block-image {
  margin-block: 2rem;
  text-align: center;
}

.ap-postbody .wp-block-image img {
  max-width: 100%;
  height: auto;
  border-radius: 12px;
}

.ap-postbody figcaption,
.ap-postbody .wp-element-caption {
  margin-block-start: 0.75rem;
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.8125rem;
  line-height: 1.5;
  text-align: center;
}

/* A tinted panel with a brand rule down its leading edge. */
.ap-postbody blockquote {
  margin-block: 2rem;
  padding: 1.25rem 1.5rem;
  background-color: var(--wp--preset--color--base-alt);
  border-inline-start: 4px solid var(--wp--preset--color--primary);
  border-radius: 0 8px 8px 0;
  color: var(--wp--preset--color--contrast);
  font-size: 1.0625rem;
  font-style: italic;
}

.ap-postbody blockquote p:last-of-type {
  margin-block-end: 0;
}

.ap-postbody blockquote cite,
.ap-postbody blockquote .wp-block-quote__citation {
  display: block;
  margin-block-start: 0.75rem;
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.875rem;
  font-style: normal;
  font-weight: 700;
}

.ap-postbody .wp-block-embed {
  margin-block: 2rem;
}

.ap-postbody .wp-block-embed iframe {
  border-radius: 12px;
}

/* The migration produced 26 tables from Blogger's recipe layouts; they arrive
   with no borders of their own. */
.ap-postbody .wp-block-table {
  margin-block: 2rem;
  overflow-x: auto;
}

.ap-postbody .wp-block-table table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
}

.ap-postbody .wp-block-table :where(td, th) {
  padding: 0.625rem 0.875rem;
  border: 1px solid var(--wp--preset--color--outline);
  text-align: left;
  vertical-align: top;
}

.ap-postbody .wp-block-table th {
  background-color: var(--wp--preset--color--base-alt);
  font-family: var(--wp--preset--font-family--ui);
  font-weight: 700;
}

.ap-postbody .wp-block-separator {
  margin-block: 2.5rem;
  border-color: var(--wp--preset--color--outline);
}

@media (max-width: 781px) {
  .ap-postbody .wp-block-image,
  .ap-postbody blockquote,
  .ap-postbody .wp-block-table,
  .ap-postbody .wp-block-embed {
    margin-block: 1.5rem;
  }

  .ap-postbody h2,
  .ap-postbody h3 {
    margin-block-start: 2rem;
  }
}
/* ap:post-content:end */

/* -------------------------------------------------------------------------
 * Blog index and archives
 *
 * Shared by home.html (the posts page) and index.html (term, author and date
 * archives), which differ only in where their title comes from.
 *
 * The card is deliberately its own class rather than a variant of
 * .ap-postcard: that one belongs to the front page's post carousel, where the
 * call to action is amber and the frame is heavier. Same content, different
 * job, so nothing is shared but the idea.
 * ---------------------------------------------------------------------- */

/* White, matching every other template on the site; the cards are told apart
   by their own border rather than by sitting on a tint. `main` spans the full
   width, so colouring it is enough — the full-bleed hero paints its own warm
   band over the top, and the footer has its own.
 *
 * The top margin main.ap-main gives every page is dropped so the hero band
 * meets the navigation directly: there are no breadcrumbs on the listing, so
 * the gap had nothing in it. Qualified with the element to outrank
 * `main.ap-main` (0-1-1), which sets that margin. */
main.ap-main.ap-blog {
  margin-block-start: 0;
  padding-block-end: var(--wp--preset--spacing--60);
  background-color: var(--wp--preset--color--base);
}

/* A tinted band running the width of the page, with the copy held to the
   container.
 *
 * The bleed is core's, not ours: the group is alignfull, which is the one
 * thing constrained layout excludes from the rule that centres its children —
 * and that rule pins margin-left and margin-right with !important, so a
 * negative margin of our own cannot pull against it however specific the
 * selector is. Being alignfull inside a has-global-padding parent also gets
 * the inner padding back for free, so only the band's own colour and vertical
 * rhythm are set here. */
.ap-bloghero {
  padding-block: var(--wp--preset--spacing--50);
  background-color: var(--wp--preset--color--base-warm);
}

.ap-bloghero .ap-bloghero__title {
  margin-block: 0;
  line-height: 1.05;
}

/* No max-width here on purpose. The hero is a constrained group, and core
   centres its children with `margin-left/right: auto !important` — so any
   measure set on the lede leaves it floating in the middle of the band with
   the title hard left, which is not what the design shows. Full width, left
   aligned under the title; the band's own padding does the containing. */
.ap-bloghero .ap-bloghero__lede {
  margin-block: 0.5rem 0;
  color: var(--wp--preset--color--contrast-muted);
  line-height: 1.6;
}

/* Term archives render an empty <div> when the term has no description, which
   would otherwise leave the gap it was going to fill. */
.ap-bloghero .ap-bloghero__lede:empty {
  display: none;
}

.ap-bloglist {
  margin-block-start: var(--wp--preset--spacing--50);
}

.ap-bloggrid {
  gap: var(--wp--preset--spacing--40);
}

/* Three across is right for a desktop, too tight for a tablet. Below 600px
   core takes over on its own — post-template ships a 1fr rule at that width
   whose specificity (0-3-0) beats this one, which is why there is no phone
   rule here. The descendant qualifier is to outrank core's generated
   `.wp-container-…` class, which is a single class. */
@media (max-width: 1023.98px) {
  .ap-bloglist .ap-bloggrid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* ---- card ---------------------------------------------------------------- */

.ap-blogcard {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 0.75rem;
  background-color: var(--wp--preset--color--base);
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 12px;
  transition:
    border-color 0.15s ease,
    box-shadow 0.15s ease;
}

.ap-blogcard:hover {
  border-color: var(--wp--preset--color--primary);
  box-shadow: 0 2px 14px rgb(30 41 59 / 8%);
}

.ap-blogcard .ap-blogcard__image {
  margin-block: 0;
}

.ap-blogcard .ap-blogcard__image img {
  width: 100%;
  border-radius: 8px;
  object-fit: cover;
}

/* The body grows to fill the card so the call to action sits on a shared
   baseline across a row, however uneven the titles and excerpts are. */
.ap-blogcard .ap-blogcard__body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  padding: 0.875rem 0.5rem 0.5rem;
}

.ap-blogcard .ap-blogcard__meta {
  margin-block: 0 0.5rem;
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.8125rem;
}

.ap-blogcard .ap-blogcard__title {
  margin-block: 0 0.5rem;
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.35;
}

.ap-blogcard .ap-blogcard__title a {
  color: var(--wp--preset--color--contrast);
  text-decoration: none;
}

.ap-blogcard .ap-blogcard__title a:hover,
.ap-blogcard .ap-blogcard__title a:focus-visible {
  color: var(--wp--preset--color--primary);
  text-decoration: underline;
}

/* Clamped rather than trimmed by word count alone: no post on this site has a
   hand-written excerpt, so these are auto-generated from the first paragraph
   and their length is unpredictable. */
.ap-blogcard .ap-blogcard__excerpt {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  margin-block: 0 1rem;
  overflow: hidden;
  color: var(--wp--preset--color--contrast-muted);
  font-size: 0.875rem;
  line-height: 1.6;
}

.ap-blogcard .ap-blogcard__excerpt p {
  margin-block: 0;
}

.ap-blogcard .ap-blogcard__cta {
  margin-block: auto 0;
}

.ap-blogcard .ap-blogcard__cta a,
.ap-blogcard a.ap-blogcard__cta {
  display: inline-block;
  padding: 0.5rem 1rem;
  background-color: var(--wp--preset--color--primary);
  border-radius: 6px;
  color: var(--wp--preset--color--base);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.875rem;
  font-weight: 700;
  text-decoration: none;
}

.ap-blogcard .ap-blogcard__cta a:hover,
.ap-blogcard a.ap-blogcard__cta:hover,
.ap-blogcard .ap-blogcard__cta a:focus-visible,
.ap-blogcard a.ap-blogcard__cta:focus-visible {
  background-color: var(--wp--preset--color--primary-dark);
  color: var(--wp--preset--color--base);
}

/* ---- pagination ---------------------------------------------------------- */

.ap-pagination {
  gap: 0.625rem;
  margin-block-start: var(--wp--preset--spacing--50);
}

/* Core marks up the numbers as a bare run of links and a plain span for the
   current page; every one of them gets the same box so the row reads as a set
   of controls rather than a line of text. */
.ap-pagination .wp-block-query-pagination-numbers .page-numbers,
.ap-pagination .wp-block-query-pagination-previous,
.ap-pagination .wp-block-query-pagination-next {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 3rem;
  height: 3rem;
  padding-inline: 0.375rem;
  background-color: var(--wp--preset--color--base);
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 10px;
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.9375rem;
  font-weight: 700;
  text-decoration: none;
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease,
    color 0.15s ease;
}

.ap-pagination .wp-block-query-pagination-numbers {
  display: inline-flex;
  gap: 0.625rem;
}

/* Core's chevron option is the double-guillemet character « », not the single
   chevron the design draws. The glyph is sized away and the real shape painted
   as a mask instead, so it takes currentcolor and matches the chevron the post
   carousel already uses. The character stays in the DOM — it is aria-hidden,
   and core puts the accessible name on the link itself. */
.ap-pagination
  :where(
    .wp-block-query-pagination-previous-arrow,
    .wp-block-query-pagination-next-arrow
  ) {
  display: block;
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
  background-color: currentcolor;
  font-size: 0;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 5 7 7-7 7'/%3E%3C/svg%3E")
    center / contain no-repeat;
}

.ap-pagination .wp-block-query-pagination-previous-arrow {
  transform: rotate(180deg);
}

.ap-pagination .wp-block-query-pagination-previous,
.ap-pagination .wp-block-query-pagination-next {
  color: var(--wp--preset--color--contrast);
}

.ap-pagination .wp-block-query-pagination-numbers .page-numbers:hover,
.ap-pagination .wp-block-query-pagination-previous:hover,
.ap-pagination .wp-block-query-pagination-next:hover,
.ap-pagination .wp-block-query-pagination-numbers .page-numbers:focus-visible,
.ap-pagination .wp-block-query-pagination-previous:focus-visible,
.ap-pagination .wp-block-query-pagination-next:focus-visible {
  border-color: var(--wp--preset--color--primary);
  color: var(--wp--preset--color--primary);
}

.ap-pagination .wp-block-query-pagination-numbers .page-numbers.current {
  background-color: var(--wp--preset--color--primary);
  border-color: var(--wp--preset--color--primary);
  color: var(--wp--preset--color--base);
}

/* The dots between page runs are a label, not a control. */
.ap-pagination .wp-block-query-pagination-numbers .page-numbers.dots {
  background: none;
  border-color: transparent;
  color: var(--wp--preset--color--contrast-muted);
}

/* The previous and next labels are emptied in the template so the arrow is
   the whole control; core still emits its own arrow glyph as the content. */
.ap-pagination
  .wp-block-query-pagination-previous
  .wp-block-query-pagination-previous-arrow,
.ap-pagination
  .wp-block-query-pagination-next
  .wp-block-query-pagination-next-arrow {
  margin: 0;
}

.ap-blog__empty {
  margin-block: var(--wp--preset--spacing--50);
  color: var(--wp--preset--color--contrast-muted);
}

@media (max-width: 781px) {
  .ap-bloghero {
    padding-block: var(--wp--preset--spacing--40);
  }

  .ap-bloglist {
    margin-block-start: var(--wp--preset--spacing--40);
  }
}

/* -------------------------------------------------------------------------
 * Blog toolbar — ap/post-filter and ap/post-sort
 *
 * Both blocks are navigation dressed as controls: the pills are links to term
 * archives and the sort is a GET form, so a filtered or sorted view is a real
 * URL. Nothing here depends on JavaScript.
 * ---------------------------------------------------------------------- */

.ap-blogtoolbar {
  gap: 1rem;
  margin-block-start: var(--wp--preset--spacing--40);
}

/* align-items, or the pills stretch to whatever the tallest thing in the
   toolbar row happens to be instead of keeping their own height. */
.ap-postfilter {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem;
}

/* border-box, or min-height sizes the content box and the padding and border
   are added on top — a 3rem pill renders 70px tall. Nothing in this theme sets
   a global box-sizing reset, so components state it themselves. */
.ap-postfilter .ap-postfilter__pill {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  min-height: 3rem;
  padding: 0.625rem 2rem;
  background-color: var(--wp--preset--color--base);
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 999px;
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.9375rem;
  font-weight: 700;
  text-decoration: none;
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease,
    color 0.15s ease;
}

.ap-postfilter .ap-postfilter__pill:hover,
.ap-postfilter .ap-postfilter__pill:focus-visible {
  border-color: var(--wp--preset--color--primary);
  color: var(--wp--preset--color--primary);
}

.ap-postfilter .ap-postfilter__pill.is-active {
  background-color: var(--wp--preset--color--primary);
  border-color: var(--wp--preset--color--primary);
  color: var(--wp--preset--color--base);
}

.ap-postsort {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-block: 0;
}

/* The toolbar is space-between, which parks a lone child at the start. When
   the filter hides itself the sort should still sit at the end of the row. */
.ap-blogtoolbar > .ap-postsort:only-child {
  margin-inline-start: auto;
}

.ap-postsort .ap-postsort__label {
  color: var(--wp--preset--color--contrast-muted);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.875rem;
}

/* 1rem rather than the 0.875rem the pill would otherwise take, for the same
   reason as the header search field: iOS Safari zooms the page when a focused
   control is under 16px, and a <select> is no exception — tapping it to sort
   would leave the archive scaled up with no way back. Applied at every width so
   the control reads identically on touch and mouse. */
.ap-postsort .ap-postsort__select {
  box-sizing: border-box;
  min-height: 2.75rem;
  padding: 0.25rem 0.5rem;
  background-color: var(--wp--preset--color--base);
  border: 1px solid var(--wp--preset--color--outline);
  border-radius: 8px;
  color: var(--wp--preset--color--contrast);
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1rem;
  font-weight: 700;
}

.ap-postsort .ap-postsort__select:focus-visible {
  border-color: var(--wp--preset--color--primary);
  outline: 2px solid rgb(69 123 210 / 25%);
  outline-offset: 1px;
}

/* Shown only when scripting is unavailable — the inline script hides it and
   submits on change instead. Styled anyway so the no-JS path is not ugly. */
.ap-postsort .ap-postsort__submit {
  box-sizing: border-box;
  min-height: 2.75rem;
  padding: 0.25rem 0.75rem;
  background-color: var(--wp--preset--color--primary);
  border: 0;
  border-radius: 8px;
  color: var(--wp--preset--color--base);
  cursor: pointer;
  font-family: var(--wp--preset--font-family--ui);
  font-size: 0.875rem;
  font-weight: 700;
}

@media (max-width: 600px) {
  .ap-blogtoolbar {
    align-items: flex-start;
    flex-direction: column;
  }
}

/* commerce-connect gift card blocks: the plugin leaves these buttons without a
   font-family, so they fall back to the browser's default button font rather
   than inheriting the page's. */
.commerce-connect-gift-card__add-to-cart,
.commerce-connect-gift-card__amount-btn,
.commerce-connect-gc-page__tab,
.commerce-connect-gc-balance__check-btn {
  font-family: var(--wp--preset--font-family--ui);
}

/* Gift card form labels: the "Select amount" / "Enter amount" heading and the
   per-field labels (name, email, message, gift card code). */
.commerce-connect-gift-card__label,
.commerce-connect-gift-card__field-label {
  font-family: var(--wp--preset--font-family--ui);
  font-size: 1rem;
  text-transform: none;
}

/* Amount option buttons: match the site's button radius (theme.json
   elements.button, 8px) instead of the plugin's 2px. */
.commerce-connect-gift-card__amount-btn {
  border-radius: 8px;
  font-size: 1rem;
}

/* Gift Card Page tabs ("Purchase digital gift card" / "Check gift cards balance"). */
.commerce-connect-gc-page__tab {
  font-size: 1rem;
}

/* "Digital gift card" heading above the card preview on the Gift Card Page.
   The plugin renders it as a <p>, so it misses the .ap-postbody h2 treatment
   that the "Check gift card balance" <h2> on the other tab picks up. Mirror
   that treatment here so the two tabs' headings agree. */
.commerce-connect-gc-page__card-label {
  color: var(--wp--preset--color--accent);
  font-family: var(--wp--preset--font-family--display);
  font-size: clamp(1.75rem, 2.6vw, 2.25rem);
  font-weight: 700;
  line-height: 1.15;
}
