/* ==========================================================================
 * afx-components.css - the component layer for BOTH storefronts.
 *
 * Loaded LAST (body-end, after afx-base.css) from
 * Sm/autostore/Magento_Theme/templates/html/legacy_fixes.phtml, so it wins over the legacy
 * head sheets (styles.css, ytextend.css, yttheme.css, custom.css) without needing !important
 * on every line. Where !important IS used it is because the rule being overridden carries
 * !important itself, or is an inline Tailwind utility on the element - each such case is noted.
 *
 * Everything here reads the tokens from afx-tokens.css (Spares/autostore_child/web/css), so
 * both stores get identical geometry and semantics while their own afx-store.css keeps supplying
 * the brand hue. No template, layout or business logic is involved.
 * ========================================================================== */

/* ==========================================================================
   1. TYPOGRAPHY SCALE
   The legacy sheets set heading sizes in percentages against varying parents
   (yttheme: h1 2.6666em, h2 2em; ytextend: h1 2.5rem; custom.css: .page-title h1
   276.92%), so the same heading level rendered at different sizes depending on which
   page it was on. Pin the scale to the token ramp so h1/h2/h3 mean one thing site-wide.

   Sizes are the live reference storefronts' own, measured on spares.net: page heading
   33px/42px, h2 24px, h3 20px, all at normal weight except the page heading. Section
   headings ("FEATURED PRODUCTS") are a separate, heavier style and live in §26 - they are a
   component, not a bare h2, and treating them as one is what used to make every h2 on a CMS
   page shout.
   ========================================================================== */
.page-main h1,
.page-main .page-title,
.page-title h1,
.page-title-wrapper .title-raw-sku h1.page-title,
.page-title-wrapper .title-raw-sku .page-title,
.categories-wrapper .page-title-wrapper h1,
.categories-wrapper .page-title {
    font-size: var(--afx-heading-page);
    line-height: var(--afx-heading-lh);
    /* Weight comes from the face, not from font-weight - see --afx-font-heading. Measured
       before this: the same h1 was 400 on category/search/blog, 500 on /categories and 700
       on cart/login/register/about/makes, i.e. three different-looking page titles on one
       site. */
    font-family: var(--afx-font-heading);
    font-weight: var(--afx-weight-normal);
    color: var(--afx-text-heading);
    letter-spacing: -0.01em;
}

/* The category/search heading needs a longer selector than .page-main .page-title. Its real
   ancestry is .page-title-wrapper > .title-raw-sku > h1.page-title, and custom.css sizes it
   through `.col2-layout .title-raw-sku .page-title { font-size: 276.92% }` - a percentage of an
   inherited size, which landed around 38px and ignored the viewport entirely. The path below is
   (0,3,1) so it wins on specificity rather than !important.
   `.categories-wrapper .page-title-wrapper h1 { font-size: xx-large !important }` also exists in
   custom.css for the category-landing pages; that one genuinely needs !important to beat. */
.page-title-wrapper .title-raw-sku h1.page-title,
.page-title-wrapper .title-raw-sku .page-title {
    font-size: var(--afx-heading-page);
    line-height: var(--afx-heading-lh);
    color: var(--afx-text-heading);
}

.categories-wrapper .page-title-wrapper h1,
.categories-wrapper .page-title {
    font-size: var(--afx-heading-page) !important;
    line-height: var(--afx-heading-lh);
    /* custom.css pins this one page's title with `font-weight: 500 !important`, which left
       /categories as the only page whose h1 rendered at a different weight. Matching
       !important is the only way to bring it back into the system. */
    font-weight: var(--afx-weight-normal) !important;
    color: var(--afx-text-heading);
}

.page-main h2 {
    font-size: var(--afx-text-2xl);
    line-height: var(--afx-leading-tight);
    font-weight: var(--afx-weight-normal);
    color: var(--afx-text-heading);
}

.page-main h3 {
    font-size: var(--afx-text-xl);
    line-height: var(--afx-leading-snug);
    font-weight: var(--afx-weight-normal);
    color: var(--afx-text-heading);
}

/* ==========================================================================
   2. HEADER CART BUTTON - INTENTIONALLY NOT STYLED HERE

   The minicart button is the approved design and is owned entirely by custom.css, which
   builds it as a tab hanging below the header (a ::after pseudo-element with the white
   border and bottom-rounded corners) in each store's own brand colour - green on
   spares.net, red on sparesusa.net, both from --color-primary in afx-store.css.

   An earlier pass here replaced it with a compact inline button. That was wrong: the tab is
   the approved header treatment. Those overrides are removed so custom.css wins again, and
   nothing in this file now targets the cart button.

   Do not re-add rules for it - adjust custom.css instead, so there is one source of truth.
   One gotcha if anyone tries: the markup prints the item count twice (once in the .text
   span, once inside the counter element). The original CSS positions the counter absolutely
   so only one is visible; anything that turns the button into a normal flex row exposes both
   and it reads "MY CART 0 item 0 0 item".
   ========================================================================== */

/* ==========================================================================
   3. BUTTONS
   One geometry for every button on the storefront. Before this the same page could show
   a sharp-cornered green rectangle (UPDATE), a fully-rounded green pill (UPLOAD ORDER)
   and a small dark square (the SKU cart icon) side by side, at three different heights.
   ========================================================================== */
.page-main .btn,
.page-main button.action,
.page-main a.action.primary,
.page-main button.action-primary,
.page-main .action.tocart,
.page-main .afx-plp-tocart,
.page-main .action.update,
.page-main .action.continue,
.page-main .afx-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--afx-space-2);
    min-height: var(--afx-touch-min);
    padding: 0 var(--afx-space-5);
    border: 1px solid transparent;
    border-radius: var(--afx-radius);
    font-size: var(--afx-text-sm);
    font-weight: var(--afx-weight-bold);
    letter-spacing: .02em;
    text-transform: uppercase;
    text-decoration: none;
    text-align: center;
    cursor: pointer;
    transition: background-color var(--afx-duration) var(--afx-ease),
                border-color var(--afx-duration) var(--afx-ease),
                color var(--afx-duration) var(--afx-ease);
}

/* Add to Cart is the primary action of the whole storefront, so the listing's button carries
   the theme base colour, exactly like the product page's.
   The !important is load-bearing and must stay: both custom.css
   (`button:not(.owl-prev)…{background:#E94F35}`) and the runtime-generated
   config_<store>.css (`button:not(.owl-prev):not(.owl-next):not(.slick-arrow)…`) target this
   element through long :not() chains whose specificity (0,3,1) outranks any plain descendant
   selector this layer can write. Checked with CSS.getMatchedStylesForNode, not guessed. */
.afx-plp .afx-plp-tocart,
.page-main .afx-plp-tocart {
    background-color: var(--afx-primary) !important;
    color: var(--afx-on-primary) !important;
    border-radius: var(--afx-radius) !important;
    font-weight: var(--afx-weight-bold);
    letter-spacing: .02em;
}

.afx-plp .afx-plp-tocart:hover,
.page-main .afx-plp-tocart:hover {
    background-color: var(--afx-primary-hover) !important;
}

/* Primary = the brand colour. Add to Cart is the primary action of the whole site, so it
   must not be the neutral dark it used to be on the listing while the product page showed
   it in green - the same action looked like two different controls. */
.page-main .action.tocart,
.page-main .afx-plp-tocart,
.page-main button.action.primary,
.page-main a.action.primary,
.page-main .afx-btn-primary {
    background-color: var(--afx-primary);
    color: var(--afx-on-primary);
}

.page-main .action.tocart:hover,
.page-main .afx-plp-tocart:hover,
.page-main button.action.primary:hover,
.page-main a.action.primary:hover,
.page-main .afx-btn-primary:hover {
    background-color: var(--afx-primary-hover);
    color: var(--afx-on-primary);
}

/* Secondary = neutral dark, for "continue shopping" / "view details" style actions. */
.page-main .afx-btn-secondary,
.page-main .action.continue {
    background-color: var(--afx-secondary);
    color: var(--afx-on-secondary);
}

.page-main .afx-btn-secondary:hover,
.page-main .action.continue:hover {
    background-color: var(--afx-secondary-hover);
    color: var(--afx-on-secondary);
}

/* Destructive stays quiet until hovered - a red delete control should not compete with
   Add to Cart for attention on a page full of products. */
.page-main .action-delete,
.page-main .action.delete,
.page-main .afx-btn-danger {
    background-color: transparent;
    border-color: var(--afx-border-strong);
    color: var(--afx-error-text);
}

.page-main .action-delete:hover,
.page-main .action.delete:hover,
.page-main .afx-btn-danger:hover {
    background-color: var(--afx-error-bg);
    border-color: var(--afx-error);
    color: var(--afx-error-text);
}

.page-main .btn:disabled,
.page-main button.action:disabled,
.page-main .action.tocart:disabled,
.page-main [disabled] {
    opacity: .55;
    cursor: not-allowed;
}

/* One visible focus treatment for every control. Never remove this. */
.page-main .btn:focus-visible,
.page-main button:focus-visible,
.page-main a:focus-visible,
.page-main input:focus-visible,
.page-main select:focus-visible,
.page-main textarea:focus-visible {
    outline: var(--afx-focus-width) solid var(--afx-focus-color);
    outline-offset: 2px;
}

/* ==========================================================================
   4. FORM CONTROLS
   The B2B cart row showed four inputs at three different heights with one select
   rendering its text two sizes larger than its neighbours, and labels sitting on
   different baselines. One geometry fixes the whole row.
   ========================================================================== */
.page-main input[type="text"],
.page-main input[type="email"],
.page-main input[type="password"],
.page-main input[type="tel"],
.page-main input[type="number"],
.page-main input[type="search"],
.page-main select,
.page-main textarea {
    min-height: var(--afx-touch-min);
    padding: var(--afx-space-2) var(--afx-space-3);
    border: 1px solid var(--afx-border-strong);
    border-radius: var(--afx-radius);
    background-color: var(--afx-surface);
    font-size: var(--afx-text-base);
    font-weight: var(--afx-weight-normal);
    color: var(--afx-text);
    line-height: 1.4;
    box-sizing: border-box;
}

.page-main textarea {
    min-height: 7rem;
}

.page-main input::placeholder,
.page-main textarea::placeholder {
    color: var(--afx-text-subtle);
    opacity: 1;
}

.page-main input:focus,
.page-main select:focus,
.page-main textarea:focus {
    border-color: var(--afx-focus-color);
    box-shadow: 0 0 0 3px var(--afx-focus-ring);
    outline: none;
}

/* Field-level error state, so an invalid input is obvious without reading the message. */
.page-main input.mage-error,
.page-main select.mage-error,
.page-main textarea.mage-error,
.page-main input[aria-invalid="true"] {
    border-color: var(--afx-error);
    background-color: var(--afx-error-bg);
}

.page-main div.mage-error,
.page-main .field-error {
    margin-top: var(--afx-space-1);
    color: var(--afx-error-text);
    font-size: var(--afx-text-sm);
    font-weight: var(--afx-weight-medium);
}

.page-main label,
.page-main .label {
    font-size: var(--afx-text-sm);
    font-weight: var(--afx-weight-medium);
    color: var(--afx-text);
}

/* ==========================================================================
   5. PRODUCT LISTING  —  the shared list/grid component
   ==========================================================================
   Owns ALL geometry for the product listing. Both listing routes render the same
   template (Magento_Catalog::product/list.phtml in Spares/autostore_child), so category
   pages and /catalogsearch/result/ share this component; there is no second
   implementation to keep in step.

   Markup contract (see the template header):
     .afx-plp                       listing root, mode class .afx-plp-list / .afx-plp-grid
       ol.afx-plp-items
         li.afx-plp-item
           .afx-plp-card
             .afx-plp-row           the UNIFORM part: media | info | buy
               .afx-plp-media       fixed aspect box, image contained
               .afx-plp-info        name / sub-name / availability
               .afx-plp-buy         price + qty + add to cart
             .afx-plp-extra         optional full-width continuation

   WHY A GRID, NOT NESTED FLEX. The previous markup was `li > .afx-plp-card` with the li
   made `display:flex` (so `h-full` on the card could resolve in grid mode). In list mode
   that turned the card into a flex ITEM with `flex: 0 1 auto`, which shrink-wraps to its
   content — so a card carrying a replacement table measured 856px, one carrying a longer
   table 937px and a plain one 616px, inside the same 937px column. That, not the image or
   the title, is why "box sizes" looked random. Rows are grid tracks now: the side tracks
   are fixed, so the media box and the buy column start at the same x on every row, and
   the card always fills the column.

   No fixed row height is imposed anywhere — the row is uniform because each slot reserves
   a predictable amount of space, so it still grows for a genuinely taller viewport or a
   larger font size.
   ========================================================================== */

/* Component-local geometry. Overridable per breakpoint below rather than repeated. */
.afx-plp {
    --afx-plp-media-w: 220px;     /* media track on desktop list rows */
    --afx-plp-media-ratio: 4 / 3; /* every image box, list and grid alike */
    --afx-plp-buy-w: 240px;       /* price + qty + button track */
    --afx-plp-pad: var(--afx-space-5);
    --afx-plp-title-lines: 2;
    --afx-plp-desc-lines: 2;
}

.afx-plp-items {
    display: flex;
    flex-direction: column;
    gap: var(--afx-gutter);
    margin: 0;
    padding: 0;
    list-style: none;
}

.afx-plp-grid .afx-plp-items {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    align-items: stretch;
}

/* The li is a flex container purely so the card can fill it in both axes; the card is
   given `flex: 1` (NOT the default `0 1 auto`) so it can never shrink-wrap to content. */
.afx-plp .afx-plp-items > .afx-plp-item {
    display: flex;
    min-width: 0;
}

.afx-plp .afx-plp-items > .afx-plp-item > .afx-plp-card {
    flex: 1 1 auto;
    min-width: 0;
}

/* "This part is included in:" — a full-width heading between the simple products and the
   grouped assemblies, not a card. */
.afx-plp-separator {
    display: block;
    grid-column: 1 / -1;
}

.afx-plp-separator strong {
    display: block;
    font-size: var(--afx-text-base);
    font-weight: var(--afx-weight-bold);
    color: var(--afx-text-heading);
}

.afx-plp-card {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--afx-border);
    border-radius: var(--afx-radius);
    background-color: var(--afx-surface);
    overflow: hidden;
    transition: border-color var(--afx-duration) var(--afx-ease),
                box-shadow var(--afx-duration) var(--afx-ease);
}

.afx-plp-card:hover {
    border-color: var(--afx-border-strong);
    box-shadow: var(--afx-shadow);
}

/* --------------------------------------------------------------------------
   The uniform row
   -------------------------------------------------------------------------- */
.afx-plp-list .afx-plp-row {
    display: grid;
    grid-template-columns: var(--afx-plp-media-w) minmax(0, 1fr) var(--afx-plp-buy-w);
    align-items: start;
    gap: var(--afx-plp-pad);
    padding: var(--afx-plp-pad);
}

.afx-plp-grid .afx-plp-row {
    display: flex;
    flex-direction: column;
    gap: var(--afx-space-3);
    padding: var(--afx-space-4);
    flex: 1 1 auto;   /* lets .afx-plp-buy be pushed to the card's bottom edge */
}

/* --------------------------------------------------------------------------
   Media
   One fixed aspect box for every product, so a 3648x2432 photograph and a 135x135
   placeholder occupy exactly the same space and neither can stretch its row.
   `object-fit: contain` keeps parts un-cropped and un-distorted — these are technical
   photographs where the whole part matters, so `cover` is the wrong choice here.
   -------------------------------------------------------------------------- */
.afx-plp-media {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: var(--afx-plp-media-ratio);
    overflow: hidden;
    border-radius: var(--afx-radius);
    background-color: var(--afx-surface-media);
}

.afx-plp-list .afx-plp-media {
    width: var(--afx-plp-media-w);
}

.afx-plp-grid .afx-plp-media {
    width: 100%;
}

.afx-plp-media-link {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    padding: var(--afx-space-2);
}

/* The width/height attributes Magento prints on the <img> describe the SOURCE, not the
   slot; without these two rules a 3648px-wide source lays out at 3648px for one frame and
   then snaps back, which is a layout shift on every listing. */
.afx-plp-media img,
.afx-plp-img {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain;
}

.afx-plp-label {
    position: absolute;
    top: var(--afx-space-2);
    display: inline-block;
    padding: 2px var(--afx-space-2);
    border-radius: var(--afx-radius-sm);
    font-size: var(--afx-text-xs);
    font-weight: var(--afx-weight-bold);
    line-height: 1.4;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--afx-text-inverse);
}

.afx-plp-label-sale { left: var(--afx-space-2);  background-color: var(--afx-error); }
.afx-plp-label-new  { right: var(--afx-space-2); background-color: var(--afx-primary); }

/* --------------------------------------------------------------------------
   Information
   -------------------------------------------------------------------------- */
.afx-plp-info {
    display: flex;
    flex-direction: column;
    gap: var(--afx-space-2);
    min-width: 0;
}

.afx-plp-link {
    display: block;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

.afx-plp-link:hover .afx-plp-title { color: var(--afx-primary); }

/* Long names are clamped rather than allowed to wrap forever, and the block reserves the
   clamped height whether the name uses it or not — that is what keeps the availability
   line and the price on the same baseline across rows. `overflow-wrap` handles the long
   unbroken SKUs that used to push the row wider than its track. */
.afx-plp-title {
    display: -webkit-box;
    -webkit-line-clamp: var(--afx-plp-title-lines);
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-height: calc(var(--afx-text-base) * var(--afx-leading-snug) * var(--afx-plp-title-lines));
    font-size: var(--afx-text-base);
    font-weight: var(--afx-weight-bold);
    line-height: var(--afx-leading-snug);
    text-transform: uppercase;
    color: var(--afx-text-heading);
    overflow-wrap: anywhere;
    transition: color var(--afx-duration) var(--afx-ease);
}

/* The grouped-product name is three separate lines (make+model / type / drawing), so it
   clamps each line to one and does not reserve the two-line title height twice. */
.afx-plp-name-grouped .afx-plp-title {
    -webkit-line-clamp: 1;
    min-height: 0;
}

.afx-plp-sub {
    display: -webkit-box;
    -webkit-line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
    font-size: var(--afx-text-sm);
    line-height: var(--afx-leading-snug);
    color: var(--afx-text-muted);
    overflow-wrap: anywhere;
}

/* Reserves the full clamped height, not one line. On a narrow card "RACING ESPRESSO SET"
   fits on one line while "BENTLEY B CUFFLINKS NAVY" needs two, which was the last remaining
   source of ragged rows at 320px (454px vs 474px cards). Costs nothing on desktop, where the
   media box is taller than this column anyway. */
.afx-plp-desc {
    display: -webkit-box;
    -webkit-line-clamp: var(--afx-plp-desc-lines);
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-height: calc(var(--afx-text-xs) * var(--afx-leading-snug) * var(--afx-plp-desc-lines));
    font-size: var(--afx-text-xs);   /* 12px — the reference's product sub-name size */
    line-height: var(--afx-leading-snug);
    color: var(--afx-text-muted);
    overflow-wrap: anywhere;
}

.afx-plp-desc :is(p, div, span) {
    display: inline;
    margin: 0;
}

/* Availability reserves one line's height even when the status string is empty, so
   "In stock" / "Available to order" / "This part is now replaced and no longer available
   to order" all leave the rest of the row where it was. */
.afx-plp-status {
    min-height: calc(var(--afx-text-xs) * var(--afx-leading-snug));
    margin-top: auto;   /* list mode: sit at the foot of the info column */
}

.afx-plp-grid .afx-plp-status { margin-top: 0; }

.afx-plp-tip {
    position: relative;
    display: inline-block;
    max-width: 100%;
}

.afx-plp-stock {
    display: inline-flex;
    align-items: flex-start;
    gap: var(--afx-space-2);
    font-size: var(--afx-text-xs);
    font-weight: var(--afx-weight-normal);
    line-height: var(--afx-leading-snug);
}

.afx-plp-stock-icon {
    flex: 0 0 auto;
    width: 1rem;
    height: 1rem;
    margin-top: .1em;
}

/* Availability strings range from "In stock" to "This part is now replaced and no longer
   available to order". Clamp the long one so it cannot take a third line and shift the row. */
.afx-plp-statuslabel {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    font-weight: var(--afx-weight-normal);
    overflow-wrap: anywhere;
}

.afx-plp-stock.available { color: var(--afx-stock-in); }
.afx-plp-stock.unavailable { color: var(--afx-text-muted); }
.afx-plp-stock.superseded { color: var(--afx-text-heading); }

.afx-plp-tiptext {
    position: absolute;
    left: 0;
    top: 100%;
    z-index: 20;
    display: none;
    width: 16rem;
    max-width: 80vw;
    padding: var(--afx-space-2);
    border: 1px solid var(--afx-border);
    border-radius: var(--afx-radius);
    background-color: var(--afx-surface);
    box-shadow: var(--afx-shadow-lg);
    font-size: var(--afx-text-xs);
    color: var(--afx-text-muted);
}

.afx-plp-tip:hover .afx-plp-tiptext,
.afx-plp-tip:focus-within .afx-plp-tiptext { display: block; }

.afx-plp-tipinfo { margin: 0; padding: 0; list-style: none; }
.afx-plp-tipinfo li + li { margin-top: var(--afx-space-1); }

.afx-plp-meta:empty { display: none; }

/* --------------------------------------------------------------------------
   Buy column: price, qty, add to cart
   -------------------------------------------------------------------------- */
.afx-plp-buy {
    display: flex;
    flex-direction: column;
    gap: var(--afx-space-3);
    min-width: 0;
}

.afx-plp-grid .afx-plp-buy { margin-top: auto; }

/* The price block reserves its own height so a product with a "Retail Price" line and one
   without still put the Add to Cart button at the same offset. */
.afx-plp-pricebox {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-height: calc(var(--afx-text-lg) * var(--afx-leading-tight));
}

.afx-plp-netprice {
    font-size: var(--afx-text-xs);
    line-height: var(--afx-leading-snug);
    color: var(--afx-text-muted);
}

.afx-plp-netprice-label { font-weight: var(--afx-weight-normal); }

.afx-plp-price {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--afx-space-1);
}

.afx-plp-gross {
    font-size: var(--afx-text-lg);
    font-weight: var(--afx-weight-bold);
    line-height: var(--afx-leading-tight);
    color: var(--afx-text-heading);
    white-space: nowrap;
}

.afx-plp-tax {
    font-size: var(--afx-text-xs);
    color: var(--afx-text-muted);
}

.afx-plp-form { margin: 0; }

.afx-plp-qty {
    display: flex;
    align-items: stretch;
    gap: var(--afx-space-2);
}

.afx-plp-qty-input {
    flex: 0 0 4.25rem;
    width: 4.25rem;
    min-height: var(--afx-touch-min);
    padding: 0 var(--afx-space-2);
    border: 1px solid var(--afx-border-strong);
    border-radius: var(--afx-radius);
    font-size: var(--afx-text-sm);
    font-weight: var(--afx-weight-bold);
    text-align: center;
    color: var(--afx-text);
    background-color: var(--afx-surface);
}

.afx-plp-qty-input:focus {
    outline: none;
    border-color: var(--afx-focus-color);
    box-shadow: 0 0 0 var(--afx-focus-width) var(--afx-focus-ring);
}

/* Geometry only. The COLOUR of this button is owned by §3 (brand primary, same as the
   product page's Add to Cart) — declaring it here as well is how the listing ended up with
   two contradictory background-colour rules for one control. */
.afx-plp-tocart {
    flex: 1 1 auto;
    min-width: 0;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
}

/* --------------------------------------------------------------------------
   Continuation blocks (replacement chain, used parts)
   Full card width, visually separated from the uniform row above.
   -------------------------------------------------------------------------- */
.afx-plp-extra {
    padding: 0 var(--afx-plp-pad) var(--afx-plp-pad);
}

.afx-plp-extra > * + * { margin-top: var(--afx-space-4); }

.afx-plp-extra .afx_replace_by_container,
.afx-plp-extra .used-product-list {
    padding-top: var(--afx-space-4);
    border-top: 1px solid var(--afx-border-subtle);
}

/* The replacement partial is legacy Bootstrap markup (.row / .col-md-*) whose floats and
   percentage widths were authored for a full-width Luma page. Inside a card track they
   overflowed; normalise them to a simple wrapping flex row here rather than editing a
   partial that the product page also renders. */
.afx-plp-extra .afx_replace_by_block .row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--afx-space-2);
    margin: 0;
}

.afx-plp-extra .afx_replace_by_block .row > [class*="col-md-"] {
    float: none;
    width: auto;
    max-width: 100%;
    padding: 0;
}

.afx-plp-extra .afx_replace_by_block .replacement-part h3 {
    margin: 0;
    font-size: var(--afx-text-base);
    font-weight: var(--afx-weight-bold);
    color: var(--afx-text-heading);
}

.afx-plp-extra .afx_replace_by_block .replacedetailinfo {
    font-size: var(--afx-text-xs);
    color: var(--afx-text-muted);
}

.afx-plp-extra .replacedby {
    display: grid;
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.4fr) minmax(0, 1.4fr) auto;
    align-items: center;
    gap: var(--afx-space-3);
    float: none;
    width: 100%;
    max-width: 100%;
    margin-top: var(--afx-space-2);
    padding: var(--afx-space-3);
    border: 1px solid var(--afx-border);
    border-radius: var(--afx-radius);
    background-color: var(--afx-surface-alt);
}

/* custom.css sizes these children with percentage widths (30/25/25/15%) meant for a
   full-width Luma row. Inside a grid track those percentages resolve against the track,
   not the page, which is what broke "SINGLE IGNITION COIL" onto five one-letter lines.
   The track widths above are the layout now, so the children go back to auto. */
.afx-plp-extra .replacedby > div {
    width: auto;
    min-width: 0;
    padding-left: 0;
    border-right: 0;
}

.afx-plp-extra .replacedby p { margin: 0; }

.afx-plp-extra .replacedby,
.afx-plp-extra .replacedby * {
    font-size: var(--afx-text-sm);
}

/* Only the identifier columns get aggressive breaking — SKUs are long unbroken tokens.
   Prose and prices must not be broken mid-word. */
.afx-plp-extra .replace-product-item-name { overflow-wrap: anywhere; }
.afx-plp-extra .replacedby .product.attribute.price,
.afx-plp-extra .replacedby .netprice { white-space: nowrap; }

.afx-plp-extra .replacedby > div + div {
    padding-left: var(--afx-space-3);
    border-left: 1px solid var(--afx-border-strong);
}

.afx-plp-extra .replace-product-item-link {
    font-weight: var(--afx-weight-bold);
    color: var(--afx-text-heading);
}

.afx-plp-extra .replacedby .replcae-qty .add-to-cart-form {
    display: flex;
    align-items: stretch;
    gap: var(--afx-space-2);
}

.afx-plp-extra .replacedby input[type="number"] {
    width: 3.5rem;
    min-height: var(--afx-touch-min);
    padding: 0 var(--afx-space-1);
    border: 1px solid var(--afx-border-strong);
    border-radius: var(--afx-radius);
    text-align: center;
}

.afx-plp-extra .replacedby .usedAddtocart {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--afx-touch-min);
    min-height: var(--afx-touch-min);
    border: 0;
    border-radius: var(--afx-radius);
    background-color: var(--afx-secondary);
    cursor: pointer;
}

.afx-plp-extra .replacedby .usedAddtocart:hover { background-color: var(--afx-secondary-hover); }
.afx-plp-extra .replacedby .usedAddtocart img { width: 20px; height: 20px; }

/* --------------------------------------------------------------------------
   Responsive
   Desktop keeps the three tracks. Below 1024px the buy column drops under the info
   column (the three tracks do not fit next to a 305px filter sidebar). Below 640px the
   row stacks completely: image, then information, then price, then qty + full-width
   Add to Cart — the order the spec asks for on mobile.
   -------------------------------------------------------------------------- */
@media (max-width: 1279px) {
    .afx-plp { --afx-plp-media-w: 180px; --afx-plp-buy-w: 210px; --afx-plp-pad: var(--afx-space-4); }
}

@media (max-width: 1023px) {
    .afx-plp-list .afx-plp-row {
        grid-template-columns: var(--afx-plp-media-w) minmax(0, 1fr);
    }

    /* Second row of the same grid, under the info column, so the media box still governs
       the left edge and the price/button stay aligned down the page. */
    .afx-plp-list .afx-plp-buy {
        grid-column: 2;
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        flex-wrap: wrap;
        gap: var(--afx-space-3);
    }

    .afx-plp-list .afx-plp-pricebox { min-height: 0; }
}

@media (max-width: 639px) {
    .afx-plp {
        --afx-plp-media-w: 100%;
        --afx-plp-pad: var(--afx-space-4);
    }

    .afx-plp-items { gap: var(--afx-space-4); }

    .afx-plp-list .afx-plp-row {
        display: flex;
        flex-direction: column;
        gap: var(--afx-space-3);
    }

    /* A full-width 4:3 box is 240px tall on a 390px phone and, for the many parts that only
       have the placeholder, that is a quarter of the screen spent on nothing. Cap it so
       roughly two products fit per screen while the photo is still large enough to read. */
    .afx-plp-list .afx-plp-media {
        width: 100%;
        max-width: 240px;
        margin-inline: auto;
        aspect-ratio: 3 / 2;
    }

    .afx-plp-list .afx-plp-buy {
        flex-direction: column;
        align-items: stretch;
        gap: var(--afx-space-3);
        padding-top: var(--afx-space-3);
        border-top: 1px solid var(--afx-border-subtle);
    }

    .afx-plp-list .afx-plp-status { margin-top: 0; }

    /* Qty stays a fixed box, the button takes the rest of the line: two thumb-sized
       targets rather than one full-width button with a cramped number field above it. */
    .afx-plp-qty { gap: var(--afx-space-2); }
    .afx-plp-qty-input { flex: 0 0 5rem; width: 5rem; }

    /* The legacy Bootstrap replacement table cannot hold four tracks at 320px. */
    .afx-plp-extra { padding: 0 var(--afx-plp-pad) var(--afx-plp-pad); }

    .afx-plp-extra .replacedby {
        grid-template-columns: minmax(0, 1fr);
        gap: var(--afx-space-2);
    }

    .afx-plp-extra .replacedby .replcae-qty .add-to-cart-form { justify-content: flex-start; }
}

/* ==========================================================================
   6. TOOLBAR + FILTERS
   ========================================================================== */
.toolbar-products {
    border: 1px solid var(--afx-border);
    border-radius: var(--afx-radius);
    background-color: var(--afx-surface-alt);
}

.toolbar-products select {
    min-height: 2.5rem;
    font-size: var(--afx-text-sm);
}

/* The Type filter listed every vehicle variant unbounded, making the sidebar ~1100px tall
   and pushing the first product below the fold on a laptop. Cap the tall lists and let
   them scroll in place. */
.sidebar .filter-options-content ol,
.sidebar .filter-options-content ul,
.block-layered-nav .filter-options-content ol {
    max-height: 17rem;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* ==========================================================================
   7. EMPTY STATES
   The empty cart was two lines of unstyled text on a white page with an underlined
   "here" link - it read as an error rather than a state. Same treatment for empty
   listings and the product page's "no assemblies" panel.
   ========================================================================== */
.cart-empty,
.afx-empty-state,
.message.info.empty,
.message.notice.empty {
    max-width: 34rem;
    margin: var(--afx-space-12) auto;
    padding: var(--afx-space-10) var(--afx-space-8);
    border: 1px solid var(--afx-border);
    border-radius: var(--afx-radius-lg);
    background-color: var(--afx-surface);
    box-shadow: var(--afx-shadow-sm);
    text-align: center;
    font-size: var(--afx-text-base);
    color: var(--afx-text-muted);
}

.cart-empty p,
.afx-empty-state p {
    margin: 0 0 var(--afx-space-3);
}

.afx-empty-state .afx-empty-icon {
    width: 3.5rem;
    height: 3.5rem;
    margin: 0 auto var(--afx-space-4);
    color: var(--afx-text-subtle);
}

.afx-empty-state .afx-empty-title {
    margin: 0 0 var(--afx-space-2);
    font-size: var(--afx-text-2xl);
    font-weight: var(--afx-weight-bold);
    color: var(--afx-text-heading);
}

/* The inline link stays an inline link. Button-ifying every <a> in the card turned the "here"
   in "Click here to continue shopping." into a green box sitting in the middle of the
   sentence - so the CTA is a separate element and only that gets the button treatment. */
.afx-empty-state .afx-cart-empty-link {
    color: var(--afx-primary);
    font-weight: var(--afx-weight-medium);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.afx-empty-state .afx-cart-empty-link:hover {
    color: var(--afx-primary-hover);
}

.afx-empty-state .afx-empty-cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--afx-touch-min);
    margin-top: var(--afx-space-5);
    padding: 0 var(--afx-space-6);
    border-radius: var(--afx-radius);
    background-color: var(--afx-primary);
    color: var(--afx-on-primary);
    font-size: var(--afx-text-sm);
    font-weight: var(--afx-weight-bold);
    letter-spacing: .02em;
    text-transform: uppercase;
    text-decoration: none;
    transition: background-color var(--afx-duration) var(--afx-ease);
}

.afx-empty-state .afx-empty-cta:hover {
    background-color: var(--afx-primary-hover);
    color: var(--afx-on-primary);
}

/* ==========================================================================
   8. LOADING STATE
   A CSS-only shimmer for anything that marks itself busy. No library, no JS.
   ========================================================================== */
[aria-busy="true"] {
    position: relative;
    color: transparent !important;
    background: linear-gradient(90deg,
        var(--afx-surface-alt) 25%,
        var(--afx-border-subtle) 37%,
        var(--afx-surface-alt) 63%);
    background-size: 400% 100%;
    animation: afx-shimmer 1.4s ease-in-out infinite;
    border-radius: var(--afx-radius-sm);
}

@keyframes afx-shimmer {
    from { background-position: 100% 50%; }
    to   { background-position: 0 50%; }
}

@media (prefers-reduced-motion: reduce) {
    [aria-busy="true"] { animation: none; }
}

/* ==========================================================================
   9. PRODUCT DETAIL PAGE
   ========================================================================== */
.catalog-product-view .product-description-block .short-description h2 {
    font-size: var(--afx-text-2xl);
    font-weight: var(--afx-weight-bold);
    color: var(--afx-text-heading);
}

.catalog-product-view .product-description-block .article-specifications h3 {
    font-size: var(--afx-text-lg);
    font-weight: var(--afx-weight-bold);
    color: var(--afx-text-heading);
}

/* The brand row was rendered as a bordered two-cell table that read as a disabled form
   field. Present it as what it is: a spec row. */
.catalog-product-view .article-specifications .attribute-row,
.catalog-product-view .article-specifications li {
    border-bottom: 1px solid var(--afx-border-subtle);
}

/* yttheme.css puts a hard 500px floor under .page-main, which on a short product page
   (one with no assemblies and no related products) becomes a block of dead white space
   above the footer. The sticky footer is already handled by min-height:100vh on
   .page-wrapper, so the floor is redundant. */
.page-main {
    min-height: 0;
}

/* ==========================================================================
   10. MOBILE
   Touch targets and a search field that does not trigger iOS zoom (anything under 16px
   in a focused input makes Safari zoom the whole page).
   ========================================================================== */
@media (max-width: 767px) {
    /* The desktop h1 (2.25rem) wrapped "FRONT STRUCTURE" onto two lines and ate a third of
       the first screen before a single product was visible. */
    .page-main h1,
    .page-main .page-title,
    .page-title h1 {
        font-size: var(--afx-text-2xl);
    }

    /* Same two selectors as the desktop rules above - see the comment there. */
    .page-title-wrapper .title-raw-sku h1.page-title,
    .page-title-wrapper .title-raw-sku .page-title {
        font-size: var(--afx-text-2xl);
    }

    .categories-wrapper .page-title-wrapper h1,
    .categories-wrapper .page-title {
        font-size: var(--afx-text-2xl) !important;
    }

    .page-main h2 { font-size: var(--afx-text-xl); }
    .page-main h3 { font-size: var(--afx-text-lg); }

    /* The toolbar is `flex flex-wrap justify-between`, which on a narrow screen pushed its two
       or three surviving children to opposite edges and left a large hole between them (the
       pagination strip rendered as a wide empty bar). Centre and stack instead. */
    .toolbar-products {
        justify-content: center;
        gap: var(--afx-space-3);
        text-align: center;
    }

    .toolbar-products .pages,
    .toolbar-products .toolbar-sorter,
    .toolbar-products .toolbar-amount,
    .toolbar-products .limiter {
        flex: 0 1 auto;
        margin: 0;
    }

    /* The pager's own item list is spread edge-to-edge, so with the "previous" arrow absent on
       page 1 the row rendered as [gap][1][2][3][gap][next] - two large holes. Centre it. */
    .toolbar-products .pages .items,
    .toolbar-products .pages ul {
        justify-content: center;
        gap: var(--afx-space-1);
        width: auto;
    }

    /* Nothing on a product page should scroll the page sideways. The search/vehicle panel is
       absolutely positioned with `left:0; right:165px` (custom.css), which on a 375px screen
       resolves to a negative width and pushed the document wider than the viewport. */
    .search-wrapper {
        right: 0 !important;
        max-width: 100vw;
    }

    /* Only .page-main, deliberately NOT .page-wrapper: the wrapper contains the header, and
       clipping it would cut off the Make/Model/Type dropdown panels that open out of the
       vehicle selector. `clip` rather than `hidden` so no scroll container is created. */
    .page-main {
        max-width: 100vw;
        overflow-x: clip;
    }

    .page-main input[type="text"],
    .page-main input[type="email"],
    .page-main input[type="password"],
    .page-main input[type="search"],
    .page-main input[type="tel"],
    .page-main select,
    .page-main textarea {
        font-size: 1rem;
    }

    /* The listing's own buy row is NOT included here: section 5 owns it, and forcing
       width:100% on .afx-plp-tocart wrapped it under the qty box instead of sitting
       beside it. */
    .page-main .btn,
    .page-main button.action,
    .page-main .action.tocart {
        width: 100%;
        min-height: 3rem;
    }

    /* ...and neither is the pager. The toolbar's previous/next arrows carry .btn too, so the
       rule above stretched each of them to 178px inside a 390px viewport: measured at
       left:-34px and right:424px, i.e. hanging off BOTH edges of the screen. They are icon
       buttons and want to stay their own size. */
    .page-main .pages .btn,
    .page-main .pager .btn,
    .page-main .toolbar-products .btn {
        width: auto;
    }
}

/* ==========================================================================
   11. HEADER SEARCH + VEHICLE SELECTOR

   Two problems, both on every page.

   (a) `.header-search` is `height: 0` (custom.css) so the search/vehicle panel floats
       clear of the header box. That is how the live reference storefronts do it, and it is
       what keeps the header at 180px: measured on https://spares.net, .header-container
       h=180, .header-top h=45, .header-middle h=135, .header-search h=0, panel painting
       y=101..233.

       An earlier pass here put the panel back in flow instead. That did stop it covering
       the breadcrumb, but it grew the header to 253px - 40% taller than the reference, and
       on every page - which is the "homepage header is too large" report. The panel floats
       again; §24 handles the clearance the float needs, the same way the reference does.

   (b) `.revolvo.art-search` carried `inset 0 0 20px rgba(0,0,0,.2)` on top of its green
       gradient. That inner dark glow is what read as a heavy near-black outline around the
       widget - it looked like a debug border rather than a designed edge.
   ========================================================================== */
.header-search {
    height: 0;
    top: 0;
}

.revolvo.art-search {
    border-radius: var(--afx-radius-lg);
    box-shadow: var(--afx-shadow);          /* drops the inset dark glow */
    background: var(--afx-primary);         /* flat brand, not a 3-stop gradient */
}

.header-container.sticky .revolvo.art-search {
    box-shadow: var(--afx-shadow-lg);
}

/* NO overflow:hidden here. It was set so the container's corner radius would clip its inner
   sections cleanly, but the Make/Model/Type selects open absolutely-positioned dropdown panels
   that have to escape this box - clipping the container cut every MMT dropdown off at the
   panel's bottom edge and made the vehicle selector unusable. The corners are rounded on the
   first and last inner sections instead, which needs no clipping. */
.revolvo.art-search .smas-container {
    border-radius: var(--afx-radius-lg);
    overflow: visible;
}

/* Round the top of whichever section comes first inside the panel. */
.revolvo.art-search .smas-container > *:first-child {
    border-top-left-radius: var(--afx-radius-lg);
    border-top-right-radius: var(--afx-radius-lg);
}

/* The open dropdown must sit above the hero/slider that follows the header. */
.revolvo.art-search .smas-container .chosen-container .chosen-drop,
.revolvo.art-search .smas-container .smas-attr-inner .chosen-drop,
.revolvo.art-search .smas-container select + div[class*="drop"] {
    z-index: 300;
}

/* The 3.5px white rules between the search field, the tab strip and the dropdown row were
   thick enough to read as separate stacked boxes. Hairlines in the brand's own tint instead. */
.revolvo.art-search .smas-container .smas-caption,
.revolvo.art-search .smas-container .smas-form-wrap {
    border-top: 1px solid rgba(255, 255, 255, .22);
}

.revolvo.art-search .smas-container .smas-form-wrap {
    border-radius: 0 0 var(--afx-radius-lg) var(--afx-radius-lg);
}

/* ==========================================================================
   12. FOOTER

   .footer-style-3 paints a photograph of a car interior behind the whole footer and sets
   text to #bbb. Over the bright parts of that photo the links were close to invisible.
   background-blend-mode lets a dark wash be multiplied over the image without having to
   restate the image URL (which is relative to a different stylesheet's directory).
   ========================================================================== */
.footer-style-3 {
    background-color: rgba(18, 18, 18, .88);
    background-blend-mode: multiply;
    color: #e5e5e5;                          /* ~13:1 on the blended ground */
}

.footer-style-3 a {
    color: #d8d8d8;
    transition: color var(--afx-duration) var(--afx-ease);
}

.footer-style-3 a:hover {
    color: #fff;
}

.footer-style-3 .title-footer {
    color: #fff;
    font-size: var(--afx-text-base);
    font-weight: var(--afx-weight-bold);
    letter-spacing: .04em;
    text-transform: uppercase;
}

/* The four columns hold very different numbers of links (Categories 10, Information 2), so
   align them to the top and give the rows a consistent rhythm instead of letting each column
   distribute its own way. */
.footer-style-3 .footer-middle .row {
    align-items: flex-start;
}

.footer-style-3 .content-footer li + li {
    margin-top: var(--afx-space-2);
}

/* ==========================================================================
   13. PRODUCT PAGE - SPECIFICATIONS AND EMPTY SECTIONS
   ========================================================================== */

/* "Brand: | Ferrari" rendered as a bordered two-cell box that read as a disabled input.
   Present it as a spec row: muted label, strong value, hairline between rows. */
.article-specifications ul.item-options {
    margin: 0;
    padding: 0;
    list-style: none;
    border: 1px solid var(--afx-border);
    border-radius: var(--afx-radius);
    overflow: hidden;
}

.article-specifications ul.item-options.card > li,
.article-specifications ul.item-options > li {
    display: grid;
    grid-template-columns: minmax(8rem, 32%) 1fr;
    gap: var(--afx-space-4);
    margin: 0;
    padding: var(--afx-space-3) var(--afx-space-4);
    border: 0;
    border-bottom: 1px solid var(--afx-border-subtle);
}

.article-specifications ul.item-options > li:last-child {
    border-bottom: 0;
}

.article-specifications ul.item-options > li:nth-child(even) {
    background-color: var(--afx-surface-alt);
}

/* These two need the full `.article-specifications .item-options.card li .attr-*` path: custom.css
   pins the label to `width: 25%` and the value to `width: 60%` at that specificity. Inside the
   grid column above, 25% resolved to about 32px and the word "Brand:" wrapped one letter per
   line. Matching the selector (and loading later) lets width go back to auto. */
.article-specifications .item-options.card li .attr-name {
    width: auto;
    padding: 0;
    border-right: 0;
    color: var(--afx-text-muted);
    font-size: var(--afx-text-sm);
    font-weight: var(--afx-weight-medium);
}

.article-specifications .item-options.card li .attr-value {
    width: auto;
    padding: 0;
    color: var(--afx-text-heading);
    font-size: var(--afx-text-sm);
    font-weight: var(--afx-weight-bold);
}

/* "Linked Vehicles and Assemblies" was a filled green tab above a bordered box containing one
   line of grey text. Section heading with a brand rule under it, and a real empty state in
   the box. */
#assambly_linkage > h2,
#replace-chain > h2 {
    padding: 0 0 var(--afx-space-3);
    border-bottom: 2px solid var(--afx-primary);
    background: none;
    color: var(--afx-text-heading);
    font-size: var(--afx-text-xl);
    font-weight: var(--afx-weight-bold);
    border-radius: 0;
}

.mmt-empty,
#assambly_linkage .card > p:only-child {
    margin: 0;
    padding: var(--afx-space-8) var(--afx-space-4);
    color: var(--afx-text-muted);
    font-size: var(--afx-text-sm);
    text-align: center;
}

#assambly_linkage .card {
    border: 1px solid var(--afx-border);
    border-radius: var(--afx-radius);
    background: var(--afx-surface);
}

/* ==========================================================================
   14. TABLET

   Between ~768px and ~1199px the header shows the mobile hamburger AND the desktop cart
   button, and the search panel is still at its desktop width. The result was the cart's
   "MY CART / 0 item" text colliding with the hamburger and the search panel overlapping
   the logo. Drop the cart to icon+badge and let the search panel use the available width.
   ========================================================================== */
@media (min-width: 768px) and (max-width: 1199px) {
    .header-search {
        max-width: 100%;
        padding: 0 var(--afx-space-4);
    }
}

/* ==========================================================================
   15. BLOG LISTING

   The listing printed each post's whole opening section - ten-plus lines including the
   article's own <h2> ("Market Context and Scale") rendered at heading size, competing with
   the post title above it. Two posts filled several screens and the page could not be
   scanned. Clamp the excerpt and demote any headings inside it; the full article is one
   click away.
   ========================================================================== */
.post-description {
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
    /* -webkit-line-clamp alone is unreliable here: it only counts line boxes when every child
       is inline-level, and these excerpts are raw article HTML that can contain lists, divs and
       images. max-height guarantees the cut regardless - 4 lines at the excerpt's own
       leading-relaxed (1.625) line-height. */
    max-height: 6.5em;
}

/* Headings that appear inside an excerpt are body copy in that context. */
.post-description h1,
.post-description h2,
.post-description h3,
.post-description h4 {
    display: inline;
    font-size: inherit;
    font-weight: var(--afx-weight-bold);
    color: inherit;
}

.post-description p {
    display: inline;
}

/* The post title was text-lg/font-normal - lighter than the excerpt's own headings, so the
   hierarchy read backwards. */
.post-title a {
    font-size: var(--afx-text-xl) !important;   /* beats the inline text-lg utility */
    font-weight: var(--afx-weight-bold) !important;
    color: var(--afx-text-heading) !important;
    line-height: var(--afx-leading-snug);
}

.post-title a:hover {
    color: var(--afx-primary) !important;
}

/* ==========================================================================
   16. NATIVE FORM CONTROLS

   Radios and checkboxes were the browser default blue - on a green (or red) storefront that
   is the single most obvious "unstyled default" tell. accent-color re-tints the native
   control, which keeps the platform's own accessibility and keyboard behaviour instead of
   replacing it with a div that only looks like a checkbox.
   ========================================================================== */
.page-main input[type="checkbox"],
.page-main input[type="radio"] {
    accent-color: var(--afx-primary);
    width: 1.05rem;
    height: 1.05rem;
    cursor: pointer;
}

/* Password strength meter: it rendered as a grey bar butted against the field with the label
   sitting on top of it. Detach it and colour it by state. */
.page-main .password-strength-meter,
.page-main #password-strength-meter-container {
    margin-top: var(--afx-space-2);
    padding: var(--afx-space-1) var(--afx-space-2);
    border-radius: var(--afx-radius-sm);
    background-color: var(--afx-surface-alt);
    font-size: var(--afx-text-xs);
    color: var(--afx-text-muted);
}

.page-main .password-weak    { color: var(--afx-error-text); }
.page-main .password-medium  { color: var(--afx-warning-text); }
.page-main .password-strong,
.page-main .password-very-strong { color: var(--afx-success-text); }

/* The "Show" password toggle was unstyled text sitting inside the input's box. */
.page-main .action.hide-password,
.page-main .action.show-password,
.page-main button[class*="show-password"] {
    min-height: 0;
    padding: 0 var(--afx-space-2);
    background: none;
    border: 0;
    color: var(--afx-primary);
    font-size: var(--afx-text-sm);
    font-weight: var(--afx-weight-medium);
    text-transform: none;
    letter-spacing: 0;
}

/* The register/login action row sat on a grey panel inside otherwise white cards. */
.page-main .form .actions-toolbar,
.page-main .actions-toolbar {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--afx-space-4);
    margin-top: var(--afx-space-6);
    padding: var(--afx-space-4) 0 0;
    border-top: 1px solid var(--afx-border-subtle);
    background: none;
}

.page-main .actions-toolbar .secondary a.action,
.page-main .actions-toolbar .action.back {
    min-height: 0;
    padding: 0;
    background: none;
    color: var(--afx-text-muted);
    font-weight: var(--afx-weight-medium);
    text-transform: none;
    letter-spacing: 0;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.page-main .actions-toolbar .secondary a.action:hover,
.page-main .actions-toolbar .action.back:hover {
    color: var(--afx-text-heading);
}

/* Required-field asterisks were pure red on grey; give them the semantic error tone. */
.page-main .field.required > .label:after,
.page-main .field._required > .label:after {
    color: var(--afx-error);
    font-weight: var(--afx-weight-bold);
}

/* ==========================================================================
   17. HOMEPAGE SECTION RHYTHM

   The featured block carried roughly 150px of empty space under its cards before the next
   section, and its heading was decorated with bullet dots either side
   ("• FEATURED PRODUCTS •") - a dated flourish. Consistent vertical rhythm from the token
   spacing scale, and a plain heading with the eyebrow above it.
   ========================================================================== */
.tab-listing-products {
    padding-top: var(--afx-space-12);
    padding-bottom: var(--afx-space-12);
}

.tab-listing-products .slider-product {
    margin-bottom: 0;
}

.title-group {
    margin-bottom: var(--afx-space-8);
    text-align: center;
}

/* Eyebrow above the section title. */
.title-group h3 {
    margin: 0 0 var(--afx-space-1);
    color: var(--afx-primary);
    font-size: var(--afx-text-sm);
    font-weight: var(--afx-weight-bold);
    letter-spacing: .1em;
    text-transform: uppercase;
}

.title-group h2 {
    margin: 0;
    font-size: var(--afx-text-3xl);
    font-weight: var(--afx-weight-bold);
    color: var(--afx-text-heading);
    letter-spacing: -0.01em;
}

/* Drop the decorative dots flanking the section titles. */
.title-group h2:before,
.title-group h2:after {
    content: none;
    display: none;
}

/* ==========================================================================
   18. HOMEPAGE / SLIDER PRODUCT CARDS

   These come from the listing-tabs slider, not the category grid, so they need their own
   rules. They showed a truncated name and a price at body size, with cards ending at
   different heights depending on how far the name was cut.
   ========================================================================== */
.slider-product .product-item-details,
.slider-product .product-item-info {
    text-align: center;
}

.slider-product .product-item-name,
.slider-product .product-item-link {
    display: block;
    font-size: var(--afx-text-sm);
    font-weight: var(--afx-weight-bold);
    line-height: var(--afx-leading-snug);
    color: var(--afx-text-heading);
    /* Two lines for every card, so a long name cannot make one card taller than its
       neighbours. Replaces a hard single-line ellipsis that cut names mid-word. */
    min-height: calc(1.4em * 2);
}

.slider-product .price,
.slider-product .price-box .price {
    font-size: var(--afx-text-lg);
    font-weight: var(--afx-weight-bold);
    color: var(--afx-text-heading);
}

.slider-product .product-image-container {
    background-color: var(--afx-surface-media);
    border-radius: var(--afx-radius);
}

/* The homepage "Featured Products" tabs render through a different wrapper
   (.tab-listing-products > .products.list.items > .item.product.product-item), so none of
   the .slider-product rules above reach them. Their outer cards were already a uniform
   339px, but the white .product-item-info panel inside was 339px or 318px depending on
   whether the part name wrapped to two lines or three - which is the ragged row of boxes in
   the reported screenshot. Let the panel fill the card and reserve the name's tallest form. */
.tab-listing-products .products.list.items {
    align-items: stretch;
}

.tab-listing-products .item.product.product-item {
    display: flex;
}

.tab-listing-products .item.product.product-item > .product-item-info {
    display: flex;
    flex-direction: column;
    width: 100%;
    height: 100%;
}

.tab-listing-products .product-item-details {
    flex: 1 1 auto;
}

.tab-listing-products .product-item-link {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    /* 3 x the 1.65 line-height set in §26 - the tallest name in the set, so nothing is
       truncated and every card reserves the same space. */
    min-height: calc(0.8125rem * 1.65 * 3);
}

/* ==========================================================================
   19. LISTING TAB PILLS
   The active pill's label sat hard against its rounded edges and looked clipped.
   ========================================================================== */
.sm-listing-tabs .tab-title,
.sm-listing-tabs li a {
    padding: var(--afx-space-2) var(--afx-space-5);
    border-radius: var(--afx-radius-full);
    font-size: var(--afx-text-xs);
    font-weight: var(--afx-weight-bold);
    letter-spacing: .04em;
    line-height: 1.4;
    white-space: nowrap;
}

/* Active tab pill. The real classes are .ltabs-tab / .ltabs-tab.tab-sel with the text in a
   nested .ltabs-tab-label - not the .active/.tab-title names guessed earlier, which is why the
   first attempt changed nothing. The selected pill was rendering its label in a pale tint on the
   brand fill, so the one pill that should have been the most legible was the least. */
.ltabs-tabs .ltabs-tab,
.ltabs-tabs .ltabs-tab .ltabs-tab-label {
    border-radius: var(--afx-radius-full);
    font-size: var(--afx-text-xs);
    font-weight: var(--afx-weight-bold);
    letter-spacing: .04em;
    line-height: 1.4;
}

.ltabs-tabs .ltabs-tab.tab-sel,
.ltabs-tabs .ltabs-tab.tab-sel .ltabs-tab-label {
    background-color: var(--afx-primary) !important;
    color: var(--afx-on-primary) !important;
    opacity: 1 !important;
}

/* Slider product names were held on one line and clipped by overflow, so "…COMPLETE ENGINE"
   became "…COMPLETE EN". Let them wrap into the two lines already reserved above. */
.slider-product .product-item-name,
.slider-product .product-item-link,
.slider-product .product-item-name a {
    white-space: normal !important;
    overflow: visible !important;
    text-overflow: clip !important;
}

/* ==========================================================================
   20. HOMEPAGE "LATEST BLOGS" CARDS

   The thumbnails were `<img width="1" height="1">` (fixed in
   Magefan_Blog/templates/widget/slider-post.phtml) inside a box with no aspect ratio, so the
   image had no reliable size and the cards rendered as tall narrow portraits. Give the media a
   16:10 landscape box and let the image cover it.

   NOTE: on this dataset the thumbnails still show the store placeholder, and that is NOT a CSS
   problem - pub/media/magefan_blog/ contains only 123.png and a white JPG, so the two files the
   posts reference are missing and Magento answers 200 with its placeholder for both. The images
   have to be re-uploaded to the posts in the admin.
   ========================================================================== */
.slider-post .image-blog,
.item-post .image-blog {
    aspect-ratio: 16 / 10;
    overflow: hidden;
    border-radius: var(--afx-radius) var(--afx-radius) 0 0;
    background-color: var(--afx-surface-media);
}

.slider-post .image-blog a,
.item-post .image-blog a {
    display: block;
    width: 100%;
    height: 100%;
}

/* Beats the width/height attributes and any legacy sizing. */
.slider-post .image-blog img,
.item-post .image-blog img {
    width: 100% !important;
    height: 100% !important;
    max-width: none;
    object-fit: cover;
    display: block;
}

.slider-post .item-post,
.item-post {
    border: 1px solid var(--afx-border);
    border-radius: var(--afx-radius);
    background: var(--afx-surface);
    overflow: hidden;
}

.slider-post .info-blog {
    padding: var(--afx-space-4);
}

.slider-post .date-post {
    color: var(--afx-text-subtle);
    font-size: var(--afx-text-xs);
    letter-spacing: .04em;
}

.slider-post .postTitle {
    margin: var(--afx-space-2) 0 var(--afx-space-3);
}

.slider-post .postTitle .post-item-link {
    font-size: var(--afx-text-base);
    font-weight: var(--afx-weight-bold);
    line-height: var(--afx-leading-snug);
    color: var(--afx-text-heading);
    text-transform: none;   /* it was uppercased, which made 4-line titles unreadable */
}

.slider-post .postTitle .post-item-link:hover {
    color: var(--afx-primary);
}

/* "Read More" here rendered in neutral dark while the same action on the blog listing is brand
   coloured - same link, two looks on the same site. */
/* Needs the .info-blog step: custom.css sets the dark fill at
   `.slider-post .info-blog .read-more a` (0,3,1), which a shorter selector cannot reach. Its
   :hover was already brand green, so the button read backwards - dark at rest, brand on hover. */
.slider-post .info-blog .read-more a,
.item-post .info-blog .read-more a,
.slider-post .read-more a,
.item-post .read-more a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 2.5rem;
    padding: 0 var(--afx-space-5);
    border-radius: var(--afx-radius);
    background-color: var(--afx-primary);
    color: var(--afx-on-primary);
    /* 13px / normal, matching the reference's Read More. This selector chain has to be
       long enough to beat `.slider-post .info-blog .read-more a { font-size: 107.69% }`
       in custom.css AND config_<store>.css, so the weight is set here rather than in §26 -
       one rule for this control, not two competing ones. */
    font-size: 0.8125rem;
    font-weight: var(--afx-weight-normal);
    letter-spacing: .04em;
    text-transform: uppercase;
    text-decoration: none;
    transition: background-color var(--afx-duration) var(--afx-ease);
}

.slider-post .info-blog .read-more a:hover,
.item-post .info-blog .read-more a:hover,
.slider-post .read-more a:hover,
.item-post .read-more a:hover {
    background-color: var(--afx-primary-hover);
    color: var(--afx-on-primary);
}



/* ==========================================================================
   21. BREADCRUMB SPACING - ROOT-CAUSE FIX

   Symptom reported: on some pages the area after the breadcrumb looked wrong - content
   either sat under the header panel or was pushed down behind a large blank gap.

   Cause: custom.css pads the breadcrumb by a ladder of magic numbers whose only job was to
   clear the Make/Model/Type search panel, which used to float over the page because
   `.header-search` was `height: 0`:

       .breadcrumbs                        padding-top: 60px
       @media 768-991px  .breadcrumbs      padding-top: 100px !important
       @media <=767px    .breadcrumbs      padding-top: 195px !important
       @media <=575px    .breadcrumbs      padding-top: 122px !important
       @media <=767px    body:not(:has(.breadcrumbs)) .page-title-wrapper.modified
                                           margin-top: 170px !important

   A fixed offset can never match the panel's real height on every page and breakpoint, so
   depending on the page it either left the content underneath the panel or opened a gap -
   which is exactly the reported behaviour. The panel is now in normal flow (section 11), so
   the compensation is not just unnecessary, it double-counts: measured a ~195px empty band
   between the panel and the breadcrumb on a 390px viewport.

   Fix: drop the compensation and give the breadcrumb ordinary spacing from the token scale.
   The panel now reserves its own height, so the breadcrumb lands directly beneath it at every
   width with no magic numbers. !important is needed only where the original used it.
   ========================================================================== */
.breadcrumbs {
    padding-top: var(--afx-space-4);
    padding-bottom: var(--afx-space-2);
    margin-bottom: 0;
}

@media (max-width: 991px) {
    .breadcrumbs {
        padding-top: var(--afx-space-4) !important;
        padding-bottom: var(--afx-space-2) !important;
    }
}

/* Same compensation, applied to the page title on pages that have no breadcrumb at all. */
body:not(:has(.breadcrumbs)) .page-title-wrapper.modified {
    margin-top: var(--afx-space-6);
}

@media (max-width: 767px) {
    body:not(:has(.breadcrumbs)) .page-title-wrapper.modified {
        margin-top: var(--afx-space-6) !important;
    }
}

/* ==========================================================================
   22. STICKY HEADER
   Paired with the stickyHeader() block in afx-hyva-base.js, which reserves the header's
   full flow height on .page-wrapper the moment the header goes fixed.

   The .header-top strip carries the phone number, the countdown and - importantly -
   Login / Register. It used to be hidden at every width while stuck, purely to keep the
   fixed header short. On desktop that cost too much: it removed the only Login / Register
   entry point for the whole scrolled page, and it broke the cart. The MY CART panel is
   taller than the shortened header, so with the strip gone its top was clipped (the basket
   icon was sliced off against the viewport edge) and its base spilled out below the header
   onto the page content.

   So the strip now stays while stuck, at every width. The stuck header is then exactly the
   flow height that stickyHeader() already measured and reserved on .page-wrapper, so the
   stuck header is pixel-identical to the top-of-page one and nothing shifts.

   The earlier note here justified hiding it with "348px of an 844px phone viewport - 41%,
   permanently". That figure is stale: it predates the vehicle-selector collapse below, which
   is what actually made the phone header huge. Re-measured at 390x844 with the strip kept:

       spares.net  132px = 16% of the viewport   (was 87px with the strip hidden)
       sparesusa   145px = 17% of the viewport   (was 100px)

   i.e. the strip costs 45px, not 245px, and the selector stays collapsed either way. 45px is
   worth paying: without the strip the MY CART panel is taller than the stuck header, so its
   top was clipped (measured at -6px - the basket icon sliced off against the viewport edge)
   and Login / Register was unreachable for the whole scrolled page.

   Nothing else in the theme hides .header-top, so simply not hiding it is enough - no
   !important, no override. Verified by grepping every stylesheet for the selector.
   ========================================================================== */

/* On a phone the stuck header was still 303px of an 844px viewport, because the whole
   Make/Model/Type vehicle selector rode along with it. Measured breakdown at 390px:
   logo + menu + cart 100px, search field 50px, vehicle tabs 40px, three selects 120px.
   Only the first two belong in persistent chrome; the selector is a deliberate,
   start-of-journey action and is one flick away at the top of the page. Collapsing it
   while stuck leaves ~150px of header — and the text search, the single most-used control
   on a parts site, stays reachable from anywhere on the page.

   The full header, vehicle selector included, is untouched at the top of every page and at
   every width from 768px up. */
@media (max-width: 767px) {
    /* Specificity has to clear custom.css's `.revolvo.art-search .smas-container
       .smas-caption { display: flex }` (0,4,0) — hence the long selector rather than an
       !important. Confirmed with CSS.getMatchedStylesForNode. */
    .header-container.sticky .revolvo.art-search .smas-container .smas-caption {
        display: none;
    }

    /* .smas-form-wrap is the target of the vehicle-selector's Alpine `x-show`, so it carries
       an inline `display: block` that no selector can outrank. !important is the only way in,
       and it is safe here: x-show's own hidden state also writes `display: none`, so this can
       only ever hide an element Alpine wanted shown — never reveal one it wanted hidden. */
    .header-container.sticky .revolvo.art-search .smas-container .smas-form-wrap {
        display: none !important;
    }
}

/* ==========================================================================
   23. CMS CONTENT SAFETY NET
   CMS page and block bodies are authored in the WYSIWYG, so their markup is not something
   a template change can reach. What they consistently get wrong is horizontal sizing at
   phone widths.

   Found on /about-us: `<div style="display: flex; justify-content: space-evenly">` holding
   two images. On a 390px viewport the row cannot wrap (nothing sets flex-wrap), so the
   second image laid out at x=351..663 - half of it off the side of the screen, hidden only
   because .page-main clips overflow. Editing that one page would fix that one page; these
   rules fix the pattern wherever it appears, in either store, today and after the next
   content edit.

   Deliberately scoped to CMS bodies. The storefront's own flex rows are laid out by the
   theme and must keep their nowrap behaviour.
   ========================================================================== */
.cms-page-view .page-main [style*="display: flex"],
.cms-page-view .page-main [style*="display:flex"],
.cms-index-index .page-main [style*="display: flex"],
.cms-index-index .page-main [style*="display:flex"] {
    flex-wrap: wrap;
}

/* A flex item's min-width defaults to its content size, which is what let a 650px-wide
   image refuse to shrink inside a 312px track. */
.cms-page-view .page-main [style*="display: flex"] > *,
.cms-page-view .page-main [style*="display:flex"] > *,
.cms-index-index .page-main [style*="display: flex"] > *,
.cms-index-index .page-main [style*="display:flex"] > * {
    min-width: 0;
}

/* Blanket protection for content images regardless of how they are wrapped. */
.cms-page-view .page-main img,
.cms-index-index .page-main img {
    max-width: 100%;
    height: auto;
}

/* WYSIWYG tables routinely carry width="600" or an inline pixel width. Let them scroll in
   their own box rather than widening the page. */
.cms-page-view .page-main table,
.cms-index-index .page-main table {
    max-width: 100%;
}

@media (max-width: 767px) {
    .cms-page-view .page-main .table-wrapper,
    .cms-index-index .page-main .table-wrapper {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }
}

/* ==========================================================================
   24. CLEARANCE UNDER THE FLOATING SEARCH PANEL
   ==========================================================================
   The search/vehicle panel is deliberately out of flow (§11), so the first thing after the
   header has to leave room for it or the panel paints on top. That is the "content is
   hidden behind the breadcrumb" report: `document.elementFromPoint()` over the middle of
   the breadcrumb returned `div.smas-attr` on the category page, the search page and the
   vehicle pages alike. It looked like a vehicle-page bug only because that page's
   breadcrumb ("Home > Brand > Ferrari > Ferrari F430 > F430 2005 - 2009") is long enough to
   run underneath the panel, while "Home > Brakes & Wheels" stops short of it.

   The reference solves it with `padding-top: 60px` on .breadcrumbs, which clears its own
   233px panel bottom against a 180px header. A fixed 60px is only correct at the width it
   was measured at, though - the panel is 136px tall on desktop and 216px on a phone - and
   the per-breakpoint ladder of magic numbers that used to live here (60 / 100 / 195 / 122px)
   is exactly why some pages ended up with a gap and others with an overlap.

   So the number is measured instead: syncPanelClearance() in afx-hyva-base.js reads the
   panel's real overhang past the header and publishes it as --afx-crumb-clear. The 60px
   fallback below is the reference's own value, so the layout is already right before the
   script runs and stays right if it never does.
   ========================================================================== */
.breadcrumbs {
    padding-top: var(--afx-crumb-clear, 60px);
}

@media (max-width: 991px) {
    .breadcrumbs {
        padding-top: var(--afx-crumb-clear, 60px) !important;   /* beats custom.css's ladder */
    }
}

/* Pages with no breadcrumb still need the room - the page title is then the first thing
   under the floating panel.

   The homepage is excluded IN THIS SELECTOR rather than by a follow-up override. A separate
   `.cms-index-index .page-main > *:first-child { margin-top: 0 }` reads as if it works but
   loses on specificity: `:not(:has(.breadcrumbs))` contributes the specificity of its most
   specific argument, making the rule below (0,3,1) against the override's (0,3,0). The
   homepage kept the 69px reservation and opened a white band between the vehicle panel and
   the hero - measured 69px against the reference's 0px, which is the reported "white space
   after MMT". One rule, no override, no ambiguity. */
body:not(:has(.breadcrumbs)):not(.cms-index-index) .page-main > *:first-child {
    margin-top: var(--afx-crumb-clear, 60px);
}

/* ==========================================================================
   25. MAKE / MODEL DROPDOWN — no cramped scroller, no scroll chaining
   ==========================================================================
   The vehicle selector's option panel capped its list at a flat `max-height: 240px`
   (afx-base.css). With five makes that is fine; with the thirty Ferrari models it produced
   a 240px window over a 1297px list — a scrollbar inside a dropdown, with the last row
   sliced in half. Two things follow from that and both were reported:

     * the nested scroller itself, and
     * the page moving: once the inner list hit its end, the wheel/touch scroll chained
       straight through to the document.

   `overscroll-behavior: contain` ends the chaining. The height now follows the viewport
   instead of a magic number, so ordinary lists simply fit and no scroller appears at all;
   only genuinely long ones scroll, and those now also get the filter field (JS shows it
   above eight options), which is the faster way through thirty models anyway.

   The Make → Model → Type chain, its AJAX and the session it writes are untouched — this
   is presentation only.
   ========================================================================== */
.smas-custom-panel .smas-opts {
    max-height: min(60vh, 26rem);
    overscroll-behavior: contain;
    scrollbar-width: thin;
}

/* Short lists: no filter field, so the list should not carry its spacing either. */
.smas-custom-panel .smas-search[hidden] {
    display: none;
}

@media (max-width: 767px) {
    /* A dropdown must never be taller than the screen it opens on. */
    .smas-custom-panel .smas-opts {
        max-height: min(50vh, 18rem);
    }
}

/* ==========================================================================
   26. TYPOGRAPHY SYSTEM — measured against the live reference storefronts
   ==========================================================================
   Every value below was read off https://spares.net with
   CSS.getMatchedStylesForNode / getComputedStyle, not chosen by eye. The local build had
   drifted from it in both directions at once, which is why the site read as inconsistent:

     element              local (before)        reference (spares.net)
     ---------------------------------------------------------------------
     hero h1              42px w300 lh48        40px w400 lh50
     page h1              36px w400 lh43        33px w400 lh42  UPPERCASE
     section h2           30px w500 lh36        33px w900 lh42  UPPERCASE
     h3                   20px w500             20px w400
     product title (list) 16px w500 #404040     16px w700 #404040 UPPERCASE
     product title (slid) 14px w500 #404040     13px w700 #404040 UPPERCASE
     product sub-name     14px w400 #686868     12px w400 #686868
     availability         14px w700             12px w400
     Add to Cart          12px w700             13px w400 UPPERCASE
     blog card title      16px w500 no-case     15px w500 UPPERCASE
     Read More            12px w700             13px w400 UPPERCASE

   The pattern in the reference is consistent and worth stating, because it is the opposite
   of what "make it lighter" usually means: TITLES carry the weight (700/900) and CONTROLS
   are light (400). The local build had it backwards — bold buttons and bold stock labels
   competing with the product names — which is what made pages look heavy while the actual
   headings looked weak.

   Font family is NOT changed: local already renders in grandis_extended everywhere, the
   same family the reference uses. See §27 for the Rubik request that was being loaded on
   top of it for nothing.

   NOTE ON SYNTHETIC BOLD: the grandis faces are registered as separate @font-face families
   (grandis_extendedregular / -medium / -bold), each declared at weight 400. So
   `font-weight: 700` on grandis_extendedregular is synthesised by the browser — and that is
   exactly what the reference does for its product titles. Never set
   `font-synthesis-weight: none` here; it silently flattens every bold title on the site.
   ========================================================================== */

/* --- Section headings ------------------------------------------------------------------
   The generic h1/h2/h3 ramp lives in §1. This is the widget/section title only — the
   "FEATURED PRODUCTS" style — which the reference renders at 33px/900/uppercase. Scoping it
   to the title wrappers rather than to every h2 is deliberate: on a CMS page the reference's
   bare h2 is 24px at normal weight, and blanket-styling h2 made those shout too.
   --------------------------------------------------------------------------------------- */
.page-main .title-group h2,
.page-main .block-title-section h2,
.page-main .products-title h2,
.page-main .section-title h2 {
    font-size: var(--afx-heading-section);
    line-height: var(--afx-heading-lh);
    font-weight: 900;
    text-transform: uppercase;
    color: var(--afx-text-heading);
}

/* The hero headline is the one place the light face leads. */
.cms-index-index .page-main h1,
.page-main .hero h1 {
    font-size: var(--afx-heading-hero);
    line-height: 1.25;
    font-weight: var(--afx-weight-normal);
}

/* --- Product and card titles: bold, but at body-text colour, never near-black ---------- */
.page-main .product-item-name,
.page-main .product-item-name a,
.page-main .product-item-link,
.page-main .product.name a,
.page-main .afx-plp-title {
    font-weight: var(--afx-weight-bold);
    color: var(--afx-text);
    text-transform: uppercase;
}

/* Sliders and tab widgets show the same title in a smaller slot. The real ancestry on the
   homepage is `.products.list.items > .item.product.product-item`, not `.item-product`. */
.page-main .products.list.items .product-item-link,
.page-main .item.product.product-item .product-item-link,
.page-main .owl-carousel .product-item-link,
.page-main .tab-listing-products .product-item-link {
    font-size: 0.8125rem;   /* 13px */
    line-height: 1.65;      /* 21.45px, as the reference */
}

/* The green kicker above a section heading ("TOP POPULAR ON THIS WEEK"). It is an h3, so the
   generic 20px h3 ramp in §1 would size it wrongly; the reference renders it at 13px/800. */
.page-main .title-group h3 {
    font-size: 0.8125rem;   /* 13px */
    line-height: 1.2;
    font-weight: 800;
    text-transform: uppercase;
}

.page-main .product-item-name a:hover,
.page-main .product-item-link:hover,
.page-main .product.name a:hover,
.page-main .afx-plp-link:hover .afx-plp-title {
    color: var(--afx-primary);
}

/* --- Blog ----------------------------------------------------------------------------- */
.page-main .item-post .post-item-link,
.page-main .postTitle a {
    font-size: 0.9375rem;   /* 15px */
    line-height: 1.2;
    font-weight: var(--afx-weight-medium);
    text-transform: uppercase;
    color: var(--afx-text);
}

.page-main .date-post,
.page-main .post-item-date {
    font-size: 0.8125rem;   /* 13px */
    font-weight: var(--afx-weight-normal);
    color: #aaaaaa;
}

/* --- Controls: light, not bold. This is the single biggest source of visual weight. ----- */
/* Scoped to .page-wrapper, not .page-main: the newsletter Subscribe button lives in the
   footer, which is outside .page-main, and was the last control still rendering at 700. */
.page-wrapper .afx-plp-tocart,
.page-wrapper .action.tocart,
.page-wrapper .post-read-more,
.page-wrapper button.action.primary,
.page-wrapper a.action.primary,
.page-wrapper .action.primary,
.page-wrapper button.action.submit,
.page-wrapper .action.subscribe,
.page-wrapper .btn,
.page-wrapper .btn-primary,
.page-wrapper .btn-secondary,
.page-wrapper button.action,
.page-wrapper .afx-btn {
    /* One button weight site-wide. Measured before this: 400 on the listing, 600 on the cart
       and registration forms, 700 on /about-us - the same control rendered three ways. The
       reference uses 400 throughout, which is also what stops buttons out-shouting the
       product titles beside them. */
    font-size: 0.8125rem;   /* 13px */
    font-weight: var(--afx-weight-normal);
    letter-spacing: .02em;
    text-transform: uppercase;
}

/* --- Supporting copy and metadata ----------------------------------------------------- */
.page-main .product-item-description,
.page-main .short-description {
    font-size: 0.75rem;     /* 12px */
    color: var(--afx-text-muted);
}

.page-main .afx-plp-tax,
.page-main .afx-plp-netprice {
    font-weight: var(--afx-weight-normal);
    color: var(--afx-text-subtle);
}

/* --- Mobile: the reference is a desktop measurement; do not shrink below legibility ----- */
@media (max-width: 767px) {
    .page-main h1,
    .page-main .page-title,
    .page-title-wrapper .title-raw-sku .page-title,
    .page-main .title-group h2 {
        font-size: var(--afx-text-2xl);   /* 24px */
        line-height: 1.25;
    }

    .cms-index-index .page-main h1,
    .page-main .hero h1 {
        font-size: var(--afx-text-3xl);   /* 30px */
    }

    .page-main h3 { font-size: var(--afx-text-lg); }

    /* The brand tiles carry single-token labels ("LamborghiniParts") from CMS content. Held
       at 20px by `.brands-full .brands-content .brand-item h3 { font-size: 20px !important }`
       in afx-base.css, that token cannot fit a half-width tile at 390px and breaks mid-word
       - "Lamborgh / iniParts". Matching !important is the only way past that declaration.
       `hyphens: manual` stops the browser inventing a break point once it fits. */
    .brands-full .brands-content .brand-item h3,
    .brands-content .brand-item h3 {
        font-size: var(--afx-text-sm) !important;
        line-height: var(--afx-leading-snug);
        hyphens: manual;
        overflow-wrap: normal;
    }
}
