/*
 * Tesla Product Card — the single canonical general product card.
 * ---------------------------------------------------------------------------
 * Hand-authored plain CSS at a fixed, theme-version-independent path, following the
 * assets/qsm/qsm.css precedent. Deliberately NOT part of the style.less monolith: the card is
 * the general product presentation and must render identically regardless of which asset
 * generation ThemeCssVersionUrl selects.
 *
 * ONE card. Context differences are outer-layout modifiers on the SAME markup tree, never a
 * second card implementation:
 *
 *     .tc-grid            desktop / mobile product grid   (hairline separators)
 *     .tc-row             horizontal product row/slider   (dark section)
 *     --tc-media-ratio    media aspect, per context
 *
 * GEOMETRY IS MEASURED, NOT GUESSED. Values below come from the native-resolution references
 * in G:\Projects\Design\plp (all @2x):
 *
 *   PLP-FullPage @1920      card pitch 341.25 · 4 columns · 1px hairline between cards
 *                           grid 1365 wide · sidebar ~318 on the inline-start side
 *   plp-mobile-1 @414       card 195 · 2 columns · gap 8.5 · outer 8 · media 195x259 = 3:4
 *   products-row-slider     card 252 · gap 24 · media 252x301 = 5:6 · section background #000
 *   @1440
 *
 * Colour tokens follow the Ryan palette (assets/v6/css/variables.less):
 *   @color-error-200  #ED2E7E  discount %, special-sale label, selected swatch ring
 *   @color-success-200 #00BA88 free shipping
 *   @color-info-200   #18A0FF  expedited shipping
 *   @color-secondary  #2F2F2F  primary text
 *   @color-secondary-500 #979797 muted / old price
 *   @color-secondary-300 #E6E6E6 hairlines, swatch borders
 *   @color-warning-200 #F4B740 rating star
 *
 * RTL is the storefront default. Every directional rule uses logical properties so the LTR
 * sheet needs no card-specific counterpart.
 *
 * Typography: the card sets size/weight/line-height only. It never sets font-family — the
 * Ryan face is inherited.
 */

/* ==========================================================================
   1. CARD
   ========================================================================== */

.tc-card {
    --tc-accent: #ED2E7E;
    --tc-success: #00BA88;
    --tc-info: #18A0FF;
    --tc-danger: #ED303D;
    --tc-ink: #2F2F2F;
    --tc-ink-soft: #5E5E5E;
    --tc-muted: #979797;
    --tc-hairline: #E6E6E6;
    --tc-star: #F4B740;
    --tc-media-bg: #F5F5F5;

    /* Media aspect. 3:4 for grids; the row context overrides it. */
    --tc-media-ratio: 3 / 4;
    --tc-media-radius: 8px;

    /* Reserved region heights. These are what make every card in a row the same height and
       put the QSM host, title, swatches and price on shared baselines. Overridden per
       breakpoint below. */
    --tc-badge-h: 18px;
    /* Title metrics measured on plp-mobile-1 (card 195): glyph band 15.5 tall -> ~15px, and the
       first line sits 19.0 below the media edge. */
    --tc-title-lh: 22px;
    --tc-title-h: 44px;      /* exactly two lines */
    --tc-swatch-h: 24px;
    --tc-meta-h: 17px;
    --tc-price-h: 26px;
    --tc-oldprice-h: 15px;
    /* --tc-pad-x is the card's own horizontal inset; --tc-text-pad-x insets the text columns
       relative to the media. On mobile the reference shows the media running the full card
       width (card 195 -> media 195 -> 260 tall at 3:4) with only the text inset, so the card
       inset is 0 there and the text carries its own. Desktop insets the media instead. */
    /* CARD CONTENT SAFE-AREA.
       --tc-pad-x insets the media; --tc-text-pad-x insets the text column inside it. The media
       may run the full card width, but the badge, title, swatches and price must never touch the
       card edge. Where the media is full-bleed (mobile, row) the text carries the whole inset. */
    --tc-pad-x: 0px;
    --tc-text-pad-x: 10px;
    /* Deep enough to hold the hanging old-price row (15) plus a little air. Uniform on
       every card, so it reads as card padding rather than a reserved discount slot. */
    --tc-pad-b: 20px;
    --tc-gap: 4px;

    /* Per-seam gaps. Each falls back to --tc-gap, so a context only has to name the seams it
       actually re-times. Measured seam-by-seam from the references rather than assumed to be
       one uniform rhythm - they are not. */
    /* Mobile seams, measured off plp-mobile-1 (card 195, media 195x259):
           media bottom 330 -> title glyph 352      seam ~4 on the box
           title bottom  385 -> swatch chips 403    seam 18
           swatch bottom 427 -> meta glyph  445     seam 20
           meta bottom   460 -> price glyph 470     seam 16
       These are structural gaps in the design system, so they are reserved even where this
       catalogue has no rating or discount to put in them - but nothing fake is rendered. */
    /* Box seam, not optical seam: the title's half-leading (line-height 22 over a 15px glyph)
       eats ~3.5px, so a 14px box seam rendered as 17.5px of visible air under the media and the
       title read as glued to it. 18 lands the glyph at ~21.5, which is the reference's 22. */
    --tc-gap-media-title: 18px;
    --tc-gap-title-swatch: 13px;
    --tc-gap-swatch-meta: 16px;
    --tc-gap-meta-price: 6px;

    /* Seams for the COLLAPSED neighbourings (MP5). When a section is absent the price must not
       inherit the seam of a section that is no longer there: 6px is the meta->price seam, and
       reading it as a swatch->price seam glues the price to the swatch rail. These are the
       section-level seams the price falls back to as its predecessor changes. They are seams,
       not placeholders - nothing is reserved and nothing is rendered. */
    --tc-gap-swatch-price: 18px;   /* swatches present, meta collapsed */
    --tc-gap-title-price: 14px;    /* swatches AND meta collapsed */

    /* Price-row direction.
       row          -> price on the inline-start (right in RTL), discount % on the inline-end.
                       This is plp-mobile-1.
       row-reverse  -> price on the inline-end, discount % on the inline-start.
                       This is products-row-slider.
       The two reference sets genuinely disagree here, so the arrangement is a context token on
       ONE markup tree rather than a second card. --tc-oldprice-align follows it so the old
       price always sits directly under the price it replaces. */
    --tc-price-dir: row;
    --tc-oldprice-align: flex-start;

    position: relative;
    display: flex;
    flex-direction: column;
    width: 100%;
    height: 100%;
    padding: 0 var(--tc-pad-x) var(--tc-pad-b);
    /* No card surface: both reference sets show the card sitting directly on the page/section
       background, separated by rules rather than by a tile. */
    background: transparent;
    color: var(--tc-ink);
    box-sizing: border-box;
}

.tc-card *,
.tc-card *::before,
.tc-card *::after { box-sizing: inherit; }

.tc-card a { text-decoration: none; color: inherit; }

/* ==========================================================================
   2. BADGE STRIP
   ========================================================================== */

/*
 * Always occupies its reserved height, badge or not, so the media top edge is identical on
 * every card in the row. This is the first of the shared baselines.
 */
.tc-card__badges {
    display: flex;
    align-items: center;
    justify-content: flex-start;   /* inline-start = right in RTL, as in both references */
    padding-inline: var(--tc-text-pad-x);
    height: var(--tc-badge-h);
    min-height: var(--tc-badge-h);
    flex: 0 0 auto;
}

/*
 * Sampled off products-row-slider (card 2 badge band, CSS y 91.0-103.5, media top 114):
 *     colour  #E7113D      glyph height 12.5  ->  ~13px
 *     flush against the card's inline-start edge, 10.5 above the media
 *
 * Note the reference uses a RED for this badge and the pink #ED2E7E only for the discount
 * percentage - two different colours. Using --tc-danger here keeps that separation, which also
 * reads correctly: Special Sale is not a discount.
 */
.tc-card__badge--special {
    font-size: 13px;
    font-weight: 700;
    line-height: 1;
    color: var(--tc-danger);
    white-space: nowrap;
}

/* ==========================================================================
   3. MEDIA
   ========================================================================== */

.tc-card__media {
    position: relative;
    width: 100%;
    aspect-ratio: var(--tc-media-ratio);
    overflow: hidden;
    border-radius: var(--tc-media-radius);
    background: var(--tc-media-bg);
    flex: 0 0 auto;
}

@supports not (aspect-ratio: 1 / 1) {
    .tc-card__media::before { content: ""; display: block; padding-top: 133.33%; }
    .tc-card__media > .tc-card__viewport,
    .tc-card__media > .tc-card__media-link { position: absolute; inset: 0; }
}

.tc-card__viewport,
.tc-card__media > .tc-card__media-link { width: 100%; height: 100%; }

.tc-card__slide,
.tc-card__media-link {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
}

.tc-card__img {
    display: block;
    width: 100%;
    height: 100%;
    max-width: 100%;
    /* The references show product photography filling the media frame edge to edge. */
    object-fit: cover;
}

/*
 * Minimal Swiper structural CSS, scoped to the card.
 *
 * assets/v6/css/style.css pulls the Swiper bundle in only through a plain CSS @import on
 * line 5, AFTER other rules have already been emitted. A CSS @import is honoured only when it
 * precedes every rule except @charset, so browsers drop it and Swiper's layout rules never
 * arrive: .swiper-wrapper is not a flex row and the slides stack vertically, showing the next
 * image underneath the current one.
 *
 * Rather than reorder the shared sheet (which would touch every slider on the site), the card
 * carries the few structural rules it needs, scoped to .tc-card__viewport. Harmless if the
 * bundle ever does load.
 */
/* GESTURE OWNERSHIP.
 *
 * The gallery Swiper is live on touch (measured: 9/9 PLP grid cards initialised at 390px),
 * but the viewport declared no touch-action, so it inherited auto. The browser therefore kept
 * BOTH axes for native scrolling and surrendered the horizontal one only after its own
 * scroll-intent heuristic had watched the gesture for several frames - which is the lag that
 * reads as a slow, sticky, half-ignored swipe, and why a quick reverse flick is often dropped.
 *
 * pan-y hands the horizontal axis to Swiper immediately while leaving the vertical axis to the
 * page: a vertical drag still scrolls the PLP, a horizontal drag moves the photos with no
 * arbitration delay. The tap through to the product link is unaffected - touch-action governs
 * panning, not clicks.
 *
 * Scoped to .swiper so it applies only once the gallery has actually initialised; a
 * single-image card keeps plain auto behaviour. No threshold change, no longer transition,
 * no gesture JS - Swiper touchAngle 40 / threshold 5 stay put and now get a clean signal. */
/* GESTURE OWNERSHIP - PRODUCT ROW (mobile Swiper engine only).
 * Proven, not assumed. On a 15-card row at 390px with touch-action:auto the row Swiper saw
 * ONE touchMove out of sixteen and setTranslate never fired. With pan-y it sees 16/16,
 * sliderMove 16, setTranslate 16, touches.diff 270 and translate lands at 612 - the browser
 * stops arbitrating the horizontal axis and hands it straight to Swiper, while vertical stays
 * with the page.
 *
 * Scoped by the mode attribute that init() sets, so the native-rail fallback (which wants the
 * browser to own both axes) and desktop are untouched. */
.tc-row__track.swiper[data-tc-row-mode="swiper-mobile"] {
    touch-action: pan-y;
}

.tc-card__viewport.swiper {
    touch-action: pan-y;
    position: relative;
    display: block;
    height: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden;
    list-style: none;
    z-index: 1;
}

.tc-card__viewport .swiper-wrapper {
    position: relative;
    display: flex;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    box-sizing: content-box;
    transition-property: transform;
    transform: translate3d(0, 0, 0);
    z-index: 1;
}

.tc-card__viewport .swiper-slide {
    position: relative;
    flex-shrink: 0;
    width: 100%;
    height: 100%;
    transition-property: transform;
}

/* ==========================================================================
   4. GALLERY DOTS
   ==========================================================================
   Refined to the reference language: small, circular, tightly and evenly spaced, subtle.
   Inactive reads as a soft ring, active as a solid ink dot. Deliberately not Swiper's
   default heavy blue bullets.
   ========================================================================== */

/*
 * MEASURED off products-row-slider (card 1 media 252x302 @1440, sampled at native @2x):
 *
 *     bullet diameter   4.0      bullet gap    3.0     bullet pitch  7.0
 *     bottom offset    12.5      side offset  12.5
 *
 * The rail sits on the media's inline-END (left in RTL), opposite the QSM host, in both
 * reference sets.
 */
.tc-card__dots {
    position: absolute;
    inset-block-end: 12px;
    inset-inline-end: 12px;        /* opposite the QSM host, as both references show */
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 3px;
    width: auto !important;
    line-height: 0;
    pointer-events: none;          /* the rail must never block the media link */
}

/*
 * Dots sit directly on product photography, which may be light or dark. A single-colour
 * treatment disappears on one of the two, so inactive is a light fill and active is ink,
 * each carrying a faint counter-coloured ring for separation. Reads on both.
 */
/* Inactive: light centre inside a thin visible ring. The ring is drawn as a real border so it
   reads at 4px; a box-shadow hairline optically vanished against product photography. */
.tc-card__dots .swiper-pagination-bullet {
    pointer-events: auto;
    width: 5px;
    height: 5px;
    margin: 0 !important;
    border-radius: 50%;
    background: rgba(255, 255, 255, .9);
    border: 1px solid rgba(47, 47, 47, .55);
    box-sizing: border-box;
    box-shadow: none;                       /* no glow */
    opacity: 1;
    transition: background-color .18s ease, border-color .18s ease;
}

/* Active: solid dark, same diameter, so the rail never shifts as slides change. The two states
   differ by fill, which is what makes them separable at this size. */
.tc-card__dots .swiper-pagination-bullet-active {
    background: #2F2F2F;
    border-color: #2F2F2F;
}


/* ==========================================================================
   5. QSM HOST  —  SLOT ONLY (see firewall note)
   ==========================================================================
   QSM FIREWALL.

   QSM (assets/qsm/qsm.js + qsm.css) owns everything inside this host: markup, classes,
   dimensions, colours, icon, radius, shadow, states and behaviour. The card owns ONLY the
   slot — where the host sits and how much room is reserved — so the control lands at the
   identical position on every card in a row.

   No rule here targets a QSM-owned selector (.vp-product-item-theme-v2,
   .vp-quantity-supermarket-v3, .vp-quantity-theme-v1, .vp-input, …). An earlier revision set
   background/border-radius/box-shadow on .vp-product-item-theme-v2; that was a firewall
   violation and has been removed.

   THE CARD ADAPTS AROUND QSM. QSM DOES NOT ADAPT TO THE CARD.
   ========================================================================== */

/*
 * POSITION ONLY. Nothing here touches how QSM looks or behaves.
 *
 * Target measured off products-row-slider (card 1, media 252x302 @1440):
 *     control 32x32, bottom offset 12.0, inline-start (right in RTL) offset 12.0
 *
 * The control itself is 35x35 and is drawn entirely by qsm.css - unchanged, and out of scope.
 * The host box measures 0x0 because the control inside it is `position: relative` and overflows
 * it, and it renders 16px ABOVE the host's own bottom edge. With the host pinned at 10px the
 * control therefore landed 26px above the media bottom instead of 12. The -4px cancels that
 * 16px overhang: -4 + 16 = 12. It is an anchor correction on the card-owned slot, not a QSM
 * change - verified by re-measuring the control's own box, which is still 35x35.
 */
.tc-card__quickadd {
    position: absolute;
    inset-block-end: -2px;
    inset-inline-start: 12px;
    z-index: 3;
    /* Anchored to the media box, so its position is identical on every card regardless of
       title length, swatches, rating or discount. */
}

/* ==========================================================================
   6. BODY  —  reserved regions
   ========================================================================== */

.tc-card__body {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    padding-block-start: var(--tc-gap-media-title);
    padding-inline: var(--tc-text-pad-x);
}

/*
 * Region order is explicit so a context can re-sequence the SAME markup instead of forking it.
 * Default (plp-mobile-1 / grid):  title, swatches, meta, pricing.
 * Horizontal row (products-row-slider): title, meta, pricing, swatches - see section 8.
 */
.tc-card__title-link { display: block; order: 1; }
.tc-card__swatches   { order: 2; }
.tc-card__meta       { order: 3; }
.tc-card__pricing    { order: 4; }

.tc-card__title {
    margin: 0;
    font-size: 15px;
    /* The reference title is the anchor of the text block - heavier than the price label and
       clearly ahead of the meta line. 500 read as a caption; 600 reads as a title. */
    font-weight: 600;
    line-height: var(--tc-title-lh);
    color: var(--tc-ink);
    text-align: start;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
    /* Two-line CEILING, not a two-line box. The clamp above already stops a long title at two
       lines; reserving the second line as height meant a one-line title carried an empty line of
       air into the seam below it. As a max-height the box is exactly as tall as the lines it
       has, and the grid still equalises neighbouring cards. */
    max-height: var(--tc-title-h);
}

/* Swatches ---------------------------------------------------------------- */

.tc-card__swatches {
    display: flex;
    align-items: center;
    justify-content: flex-start;   /* inline-start, as in the references */
    gap: 6px;
    margin: 0;
    padding: 0;
    list-style: none;
    height: var(--tc-swatch-h);
    min-height: var(--tc-swatch-h);
    flex: 0 0 auto;
    margin-block-start: var(--tc-gap-title-swatch);
}

/* COLLAPSED, not reserved. A product with no variations must not carry a swatch-sized hole:
   `display: none` takes the rail's height AND its seam margin with it, so the body closes up.
   Equal card height is still guaranteed - the grid stretches every card to the tallest cell
   (min-height: 100%) - but the slack now lands at the card's bottom instead of in the middle
   of the text block. */
.tc-card__swatches--empty { display: none; }

.tc-card__swatch {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
}

.tc-card__swatch-chip {
    display: block;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 1px solid var(--tc-hairline);
    background: #FFFFFF;
    object-fit: cover;
}

.tc-card__swatches--image .tc-card__swatch-chip { border-color: rgba(0, 0, 0, .10); }

/* SELECTED-SWATCH RING — WITHDRAWN.
 *
 * The ring existed so that clicking a colour could select its related gallery image. That
 * interaction is not shipping in this round, so a card was painting a pink selection state that
 * nothing had selected and that the shopper could not change - it read as an accidental
 * highlight on one arbitrary chip per card.
 *
 * WHAT IS WITHDRAWN IS THE DECORATION, NOT THE STATE. `_ProductCard.Swatches` still emits
 * `is-selected` from `ProductAttributeValueModel.IsPreSelected`, the swatch data and
 * `data-tc-swatch-image` are untouched, and product-card.js still previews a swatch's picture.
 * When variation selection lands, restoring the treatment is re-declaring the box-shadow here -
 * one rule, one place - rather than re-plumbing markup or model data.
 *
 * Declared as `none` rather than deleted for exactly that reason: the hook stays visible in the
 * sheet, and it also overrides any older cached generation of this file that still paints a ring.
 * box-shadow costs no layout in either direction, so this is not a layout change. */
.tc-card__swatch.is-selected .tc-card__swatch-chip {
    box-shadow: none;
}

.tc-card__swatch-chip--blank {
    background: repeating-linear-gradient(45deg, #F7F7F7 0 4px, #EDEDED 4px 8px);
}

.tc-card__swatch-more {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 22px;
    padding-inline: 5px;
    border-radius: 11px;
    background: #F5F5F5;
    color: var(--tc-muted);
    font-size: 10px;
    font-weight: 600;
    line-height: 1;
}

/* Meta: rating on the inline-end, shipping/stock on the inline-start ------- */

.tc-card__meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-direction: row-reverse;   /* rating (first in DOM) lands on the inline-end */
    gap: 8px;
    height: var(--tc-meta-h);
    min-height: var(--tc-meta-h);
    flex: 0 0 auto;
    margin-block-start: var(--tc-gap-swatch-meta);
}

.tc-card__rating { display: inline-flex; align-items: center; gap: 4px; }
.tc-card__rating-star { width: 13px; height: 13px; fill: var(--tc-star); flex: 0 0 auto; }
.tc-card__rating-value { font-size: 12px; font-weight: 600; color: var(--tc-ink); }

.tc-card__shipping {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    font-weight: 600;
    color: var(--tc-success);
}
.tc-card__shipping-icon { width: 15px; height: 15px; fill: var(--tc-success); flex: 0 0 auto; }

/* Pricing ----------------------------------------------------------------- */

.tc-card__pricing {
    display: flex;
    flex-direction: column;
    /* The price is the card's bottom anchor: neighbouring cards must share a price baseline even
       when one has swatches and the other does not. The auto margin absorbs whatever equal-height
       slack the grid hands the card, so the flexible space lands BETWEEN the optional content and
       the price - never below the price, and never as a reserved placeholder row. The collapsed
       seams above still apply as the minimum gap. */
    margin-top: auto;
    padding-block-start: var(--tc-gap-meta-price);
}

/* Row 1: price and discount % on opposite edges; which edge is which is the context token. */
.tc-card__price-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    flex-direction: var(--tc-price-dir);
    gap: 8px;
    height: var(--tc-price-h);
    min-height: var(--tc-price-h);
}

/* flex: 0 0 auto keeps the amount and its currency mark welded together. Without it the price
   group is a flex item that can be stretched by the row, which pushes the currency mark to the
   far edge and leaves a gap in the middle of the price. */
.tc-card__price {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    flex: 0 0 auto;
    white-space: nowrap;
}

/* The row-reverse arrangement (price on the inline-end, % on the inline-start) is the reference's
   only when BOTH elements are there to balance each other. On an undiscounted card the price is
   the row's only child and lands alone against the far card edge, reading as floating away from
   the title above it. A lone price returns to the inline-start, under the title. The row shows
   the same defect - a 252 card left ~142px between the title edge and its own price - so this
   is card-level, not context-level. */
.tc-card__price-row > .tc-card__price:only-child { margin-inline-end: auto; }

/* products-row-slider price glyph band is 21.5 CSS tall (digits plus the comma descender), which
   puts the numeral around 19-20px - the price is one of the card's two visual anchors, alongside
   the title. 15px read as body copy. */
.tc-card__price-value {
    font-size: 22px;
    font-weight: 700;
    line-height: 1.2;
    color: var(--tc-ink);
    direction: ltr;
    unicode-bidi: embed;
}

/* Official Toman asset (pro-toman.svg), scaled off the numeral so the two read as one unit:
   ~0.95x the price font-size, sitting on the digits' optical baseline. Held slightly back in
   weight so the amount stays dominant, exactly as the reference does. */
/* Specificity is deliberate: a theme-level `img { width: 100% }` was winning over `width: auto`
   here, so the mark's CSS box stretched to the whole price line (97.6px for a 24px-tall mark).
   preserveAspectRatio then centred the artwork inside that box, which is what pushed the mark
   ~37px away from the numeral and made it read as small and detached. */
.tc-card__price .tc-card__price-currency {
    width: auto;
    max-width: none;
    min-width: 0;
    /* The asset carries internal padding inside its 25x26 viewBox, so the VISIBLE wordmark is
       smaller than the CSS box. Sized off the rendered glyph, not the box, to sit next to the
       numeral at the reference's optical weight. */
    height: 26px;
    flex: 0 0 auto;
    align-self: center;
    position: relative;
    top: 1px;
    opacity: 1;
}

.tc-card__discount-badge {
    display: inline-flex;
    align-items: center;
    font-size: 15px;
    font-weight: 700;
    color: var(--tc-accent);
    direction: rtl;
}

/* Row 2: old price, always reserved so discounted and undiscounted cards match in height.
   Aligned to the same edge the price sits on, so it reads as the value being replaced. */
.tc-card__oldprice-row {
    display: flex;
    align-items: center;
    justify-content: var(--tc-oldprice-align);
    height: var(--tc-oldprice-h);
    min-height: var(--tc-oldprice-h);
    /* The old price HANGS below the current price instead of lifting it. In ProductRowSample the
       current-price line of a discounted card is level with its neighbours' and the struck price
       sits underneath; if the old-price row consumed layout height it would push the bottom-
       anchored price block up by its own height and break that line. The negative end margin
       takes it out of the height calculation while leaving it in normal flow - no absolute
       positioning, and no reserved slot on cards that have no old price. */
    margin-block-end: calc(-1 * var(--tc-oldprice-h));
}

.tc-card__old-price {
    font-size: 14px;
    font-weight: 500;
    color: var(--tc-muted);
    text-decoration: line-through;
    direction: ltr;
    unicode-bidi: embed;
}

/* Optional rows collapse when the product genuinely has no data for them. Same rule as the
   swatch rail: no fake content, no reserved hole. */
.tc-card__meta--empty,
.tc-card__oldprice-row--empty { display: none; }

/* ...and the price re-times itself against whatever section actually precedes it. DOM order is
   title, swatches, meta, pricing, so the adjacent-sibling chain reads the collapsed state
   directly off the markup - no extra class on the pricing block, no JS, no reserved row. */
.tc-card__meta--empty + .tc-card__pricing {
    padding-block-start: var(--tc-gap-swatch-price);
}

.tc-card__swatches--empty + .tc-card__meta--empty + .tc-card__pricing {
    padding-block-start: var(--tc-gap-title-price);
}

/* Non-price states -------------------------------------------------------- */

.tc-card__state {
    display: inline-flex;
    align-items: center;
    /* `.tc-card__pricing` is a column flex container, so a bare inline-flex child still STRETCHES
       to the full card width - which rendered "ناموجود" as a full-width grey bar rather than as a
       pill. align-self makes it hug its own text.

       Which EDGE it hugs has to follow the price, and that differs by context: the mobile grid
       puts the price on the inline-start, the desktop grid and the row on the inline-end. That is
       already encoded in --tc-oldprice-align (the struck price tracks the same edge), so this
       reuses it rather than introducing a second token that could drift out of step. */
    align-self: var(--tc-oldprice-align);
    min-height: 24px;
    padding-inline: 8px;
    border-radius: 6px;
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
}
.tc-card__state--out { background: #F5F5F5; color: var(--tc-muted); }
.tc-card__state--soon,
.tc-card__state--call { border: 1px solid var(--tc-success); color: var(--tc-success); }

/* ==========================================================================
   7. GRID CONTEXT
   ==========================================================================
   Desktop: 4 columns of 341.25 with 1px hairlines between them and no gutter — cards butt
   together and are separated by rules, exactly as PLP-FullPage shows.
   Mobile: 2 columns of 195 with an 8.5px gutter and no rules.
   ========================================================================== */

/*
 * The grid container is an existing theme element (.km-category-grid-product-list), so these
 * rules must neutralise the widths/floats the theme sets on .product-5 cells. Everything is
 * scoped under .tc-grid, so any list that does not opt in is untouched.
 */
.tc-grid {
    display: grid !important;
    grid-template-columns: repeat(var(--tc-cols, 2), minmax(0, 1fr));
    column-gap: var(--tc-col-gap, 12px);
    row-gap: var(--tc-row-gap, 24px);
    align-items: stretch;          /* equal card heights across the row */
    padding-inline: var(--tc-grid-pad, 16px);
    float: none;
}

/*
 * MOBILE GUTTER — the grid owns it, and it had to be taken back from the theme.
 *
 * BEFORE (measured, iPhone 12 and 414): column gap 8.5, outer inset 6.8 / 7.8, media gutter 8.5.
 * The card is flat and its media is full-bleed `object-fit: cover`, so 8.5px between two
 * photographs is not read as a gutter at all - the two columns fused into one band of imagery and
 * the outer 7px let that band run into the screen edge. plp-mobile-1's 8.5 was measured on a
 * reference whose cards still had a plate around them; once the plate went (section 10) the same
 * number stopped separating anything.
 *
 * AFTER: column gap 12, outer inset 16. 16 is not a new number - it is the CRI section inset that
 * `.vp-product-slider.tc-row` already uses on mobile, so the PLP grid and the product rows now
 * line up on one page margin. At 390 that leaves a 173px card, which lands on the mobile ROW card
 * width (172, section 9c): the two mobile product presentations are finally the same size.
 *
 * WHY THIS SELECTOR. The outer inset is not a specificity flourish. `.tc-grid` is (0,1,0) and the
 * theme's own `.km-category-grid-style .km-category-grid-product-list` (0,2,0) sets
 * `padding: 6px 0` inside `max-width: 959px` - which is why the declaration above computed to 0px
 * and the grid had no outer inset at all. Matching the theme's own shape and adding the opt-in
 * class is the minimum that actually wins; the gaps are left on `.tc-grid` because nothing
 * competes for those.
 */
@media only screen and (max-width: 991.98px) {
    .tc-grid {
        --tc-col-gap: 12px;
        --tc-row-gap: 24px;
        --tc-grid-pad: 16px;
    }

    .km-category-grid-style .km-category-grid-product-list.tc-grid {
        padding-inline: var(--tc-grid-pad, 16px);
    }
}

.tc-grid > .tc-grid__cell,
.tc-grid > * {
    min-width: 0;
    width: auto !important;
    max-width: none !important;
    margin: 0 !important;
    padding: 0;
    float: none !important;
    display: flex;
}

/* min-height, not height: with `height: 100%` the card's height resolves against a grid row that
   was itself sized before the card's reserved regions were counted, so growing a region made the
   card overflow its cell instead of growing the row. min-height keeps the equal-height contract
   (every card still fills the tallest cell) while letting the row track the content. */
.tc-grid > * > .tc-card { min-height: 100%; }

/* Hairline separators are drawn by the cell, not the card, so the card stays context-free. */
.tc-grid__cell { border: 0 solid var(--tc-hairline, #E6E6E6); }

@media (min-width: 992px) {
    .tc-grid {
        --tc-cols: 4;
        --tc-col-gap: 0px;
        --tc-row-gap: 0px;
        --tc-grid-pad: 0px;
    }

    /* 1px hairline between columns, and between stacked rows. */
    .tc-grid > * {
        border-inline-start: 1px solid var(--tc-hairline);
        border-block-end: 1px solid var(--tc-hairline);
    }
    .tc-grid > *:nth-child(4n) { border-inline-start: 0; }

    .tc-card {
        --tc-pad-x: 16px;
        --tc-text-pad-x: 0px;
        --tc-pad-b: 24px;   /* holds the hanging old-price row (18) */
        --tc-gap: 6px;
        /* Seams measured on the POPULATED PLP-FullPage cards (column 1, CSS @1920):
               title line 2 bottom 1076   -> meta band  1092.5   16.5
               meta bottom          1105  -> price      1118.5   13.5
               price bottom         1138.5-> old price  1150.5   12.0 */
        --tc-gap-title-swatch: 16px;
        --tc-gap-swatch-meta: 14px;
        --tc-gap-meta-price: 12px;
        --tc-gap-swatch-price: 18px;
        --tc-gap-title-price: 16px;
        --tc-title-lh: 23px;
        --tc-title-h: 46px;
        /* Same correction as mobile, one step wider: the desktop card is ~325 across, so the
           media edge needs a little more air under it than the 195 mobile card does. */
        --tc-gap-media-title: 20px;
        --tc-badge-h: 20px;
        --tc-swatch-h: 28px;
        --tc-meta-h: 20px;
        --tc-price-h: 28px;
        --tc-oldprice-h: 18px;
        --tc-media-radius: 6px;
    }

    .tc-card__title { font-size: 16px; }
    .tc-card__price-value { font-size: 23px; }
    .tc-card__price-currency { height: 28px; }
    .tc-card__swatch-chip { width: 24px; height: 24px; }

    /* The POPULATED desktop cards in PLP-FullPage put the price on the inline-end and the pink
       percentage on the inline-start - the same arrangement as products-row-slider, and the
       opposite of plp-mobile-1. Two of the three reference sets agree, so the desktop grid
       follows them and mobile keeps its own. Same markup, one token. */
    .tc-card {
        --tc-price-dir: row-reverse;
        --tc-oldprice-align: flex-end;
    }
}

/* --------------------------------------------------------------------------
   7b. PLP LISTING WIDTH
   --------------------------------------------------------------------------
   PLP-FullPage @1920 puts the grid at x 122 - 1487: 1365 wide, pitch 341.25, sitting flush
   against the page container's margin edge rather than inside its 20px padding. The theme's
   own split (`.size` padding 20 + `grid-template-columns: 300px minmax(0,1fr)` + `gap: 16`)
   left the grid 1320 wide - a 3.2% shortfall that showed up as a visibly narrower card.

           reference   122 .. 1487   =  1365   pitch 341.25
           before      141 .. 1462   =  1321   pitch 330.30
           after       122 .. 1487   =  1365   pitch 341.25

   Deliberately scoped to the listing page shell (`.container-plp-page`) and to desktop only:
   `.size` is a site-wide container and this must not re-proportion any other page. The sidebar
   column keeps its 300px; the 45px comes from the container padding (40) and the column gap
   (16 -> 11), so no filter control is squeezed.
   -------------------------------------------------------------------------- */

@media (min-width: 992px) {
    .container-plp-page > .size { padding-inline: 0; }
    .container-plp-page .km-page-category-content { gap: 11px; }
}

/* ==========================================================================
   8. HORIZONTAL ROW CONTEXT
   ==========================================================================
   Applied by the eight shared row components (HomepageNewProducts, HomepageProducts,
   HomepageBestSellers, RelatedProducts, SimilarProducts, ProductsAlsoPurchased,
   CrossSellProducts, RecentlyViewedProductsBlock, SmartProductBasedUserViewedProducts). They
   all emit the same section/track markup, so opting in is one class per view and the row
   behaves identically everywhere.

   Everything here is an OUTER-CONTEXT override on the same card markup: no row-specific
   partial, no row-specific class on any card element.

   GEOMETRY — measured from products-row-slider.png (2880x1208 @2x, i.e. a 1440 viewport).
   Vertical anchors are relative to the media top; each was read off the reference's own
   band map, not interpolated from a single uniform gap:

       badge strip      22      glyph band 91.0 - 103.5
       media            302     114.0 - 414.5   (252 x 302.4 = 5:6)
       -> title         14      title line 1 glyph top 435.0
       title            42      two lines at 21
       -> meta          11      meta glyph band 485.5 - 501.0
       meta             20
       -> price          7      price glyph band 512.5 - 534.0
       price            26
       -> old price      4      old price glyph band 542.5 - 555.0
       old price        18
       -> swatches      14      swatch chips 575.0 - 594.0
       swatches         24      chips 20 diameter, 6 apart
                        ---
       card height     ~508     reference ~505 - 516

   Card width 252, card-to-card gutter 32. Because the card carries its own width, the row runs
   Swiper in `slidesPerView: 'auto'` - see TcProductRow in assets/product-card/product-card.js.

   ENGINE: SWIPER. The row is not an OwlCarousel and no `.owl-*` rule takes part in its layout
   or behaviour; sliders-rtl.js / sliders-ltr.js hand every .tc-card slider straight over.
   ========================================================================== */

/* Row-level tokens. Declared on the section so the card, the slide that measures it and the
   navigation all read the same numbers - and so none of them is a page-width decision. */
.tc-row {
    --tc-row-card-w: 252px;
}

.tc-row .tc-card {
    /* SQUARE media. Re-measured off ProductRowSample.png at native @2x: the hairline between
       two ProductBoxes runs y 230..1069, and its top edge IS the media top. The gallery dot rail
       sits at 12px above the media bottom (raw y 652..661 -> media bottom 685), so the media box
       is raw 230..685 = 455 tall against a 456-wide content box. That is 1:1, not the 5:6 this
       was carrying.

       The design set names the two ProductBox families directly and confirms it independently:
           productBox-moraba-*.png    ("moraba"  = square)    195 card, media 195x195  -> ROW
           productBox-mostatil-*.png  ("mostatil"= rectangle) 390 card, media 390x506  -> GRID (3:4)
       The grid already runs 3:4; the row is the square one. */
    /* Ratio is NOT restated here any more. The row pinned desktop to 1/1 while the mobile
       block below pinned the same cards to 3/4, so one card family rendered two different
       aspects purely by viewport - which is the defect. The base token (section 2,
       --tc-media-ratio: 3 / 4) is now the single default for grid and row at every width,
       and section 9b's .tc-media-square / .tc-media-portrait modifiers remain the supported
       way to set a different aspect per section without editing this file. */
    --tc-media-radius: 10px;

    --tc-pad-x: 0px;                 /* media runs the full card width, as the reference shows */
    --tc-text-pad-x: 10px;           /* ...but the text column keeps the card safe-area */
    --tc-pad-b: 24px;   /* holds the hanging old-price row (18) */

    --tc-badge-h: 22px;
    --tc-title-lh: 21px;
    --tc-title-h: 42px;
    --tc-meta-h: 20px;
    --tc-price-h: 26px;
    --tc-oldprice-h: 18px;
    --tc-swatch-h: 24px;

    /* Same half-leading correction the grid got: 14 rendered as ~17 of visible air and the title
       read as glued to the media. */
    --tc-gap-media-title: 18px;
    --tc-gap-title-swatch: 14px;     /* swatches sit last here, so this is the price seam */
    --tc-gap-swatch-meta: 11px;
    --tc-gap-meta-price: 7px;

    /* ProductRowSample reads as four separate groups - title, swatches, meta, price - so the
       seam under the swatch rail is a GROUP seam, not the 7px meta->price tick it was inheriting.
       That inheritance is what glued the price to the chips. */
    --tc-gap-swatch-price: 20px;
    --tc-gap-title-price: 16px;

    /* ProductRowSample.png is the row's authority. RE-MEASURED at native @2x rather than
       inherited from the previous reading, which had it backwards:

           card 3 (no discount)  content box x 831..1287   price ink x  831..1027   -> inline-END
           card 4 (discount)     content box x 1351..1807  price ink x 1351..1543
                                                           percentage x 1735..1805  -> inline-START
                                                           old price under the price, same edge

       So the CURRENT price sits on the inline-END (left in RTL) and the pink percentage on the
       inline-START (right), with the struck old price directly beneath the current price. In an
       RTL flex row that is `row-reverse`, and the old-price row follows it to `flex-end`. */
    --tc-price-dir: row-reverse;
    --tc-oldprice-align: flex-end;

    /* ONE source of truth for row card width. The Swiper slide reads the same token (see the
       Track section), so the card and the slide that measures it can never disagree. This is
       CARD geometry - it is not, and must not become, a page-width decision. */
    width: var(--tc-row-card-w);
}

/* An undiscounted card must keep the price on the SAME edge as a discounted one, or a row mixes
   two price alignments. ProductRowSample's card 3 has no discount and still puts its price on the
   inline-end, so the card-level "lone price returns to the inline-start" nudge is switched off
   here: in row-reverse, space-between already parks a single child on the correct edge. */
.tc-row .tc-card__price-row > .tc-card__price:only-child { margin-inline-end: 0; }

/* The row uses the CANONICAL sequence - media, name, swatches, meta, price - exactly like the
   grid (ProductRowSample.png). It no longer re-orders the swatch rail below the price: the rail
   belongs with the product name, and the price is the bottom anchor in every context. Only
   sizing and seam values stay context-specific. */

.tc-row .tc-card__title { font-size: 15px; }
.tc-row .tc-card__price-value { font-size: 23px; }
/* Same theme `img { width: 100% }` fight as the grid, one specificity step higher because the
   slider's own image rules also apply here. */
.tc-row .tc-card__price .tc-card__price-currency { width: auto; height: 28px; }
.tc-row .tc-card__old-price { font-size: 15px; }
/* productBox-moraba-color.png (native 1x, card 195) measures the chips at 22 across with an 8
   gap. 20 read as a row of dots rather than as selectable variations. */
.tc-row .tc-card__swatch-chip { width: 22px; height: 22px; }
.tc-row .tc-card__swatches { gap: 6px; }

/* Track --------------------------------------------------------------------
   The row runs on SWIPER (TcProductRow in assets/product-card/product-card.js). Swiper's own
   bundle supplies the flex wrapper, the transform track and the touch behaviour; everything
   below is only what the CARD needs from the track.

   `slidesPerView: 'auto'` means the slide takes its width from the card inside it, so the
   canonical 252 card is the single source of card width at every container width.

   WIDTH OWNERSHIP. Nothing in this file gives the row a width. The chain is

       div.size2              max-width: 1336px + auto margins   <- OWNS THE PAGE WIDTH
         section.tc-row       100% of parent
           .tc-row__content   100% of parent
             .swiper          100% of parent, overflow hidden
               .tc-card       252px  <- card geometry, not page geometry

   Change the parent to .size (or anything else) and the row follows it with no edit here. */
/*
 * STRUCTURAL SWIPER CSS FOR THE ROW TRACK — and it is not optional.
 *
 * assets/v6/css/style.css pulls the Swiper bundle in through a plain CSS `@import` on line 5,
 * AFTER other rules have already been emitted. A CSS @import is honoured only when it precedes
 * every rule except @charset, so every browser drops it and Swiper's layout rules never arrive.
 * Without them .swiper-wrapper is not a flex row: the slides stack VERTICALLY, the track grows
 * to ~6200px tall, and every horizontal translate Swiper computes lands on nothing.
 *
 * The card gallery already carries its own copy of these rules for exactly this reason (see
 * section 3, scoped to .tc-card__viewport). The row carries the ones it needs, scoped to
 * .tc-row__track, rather than reordering the shared sheet - which would touch every slider on
 * the site. All of it is harmless if the bundle ever does load properly.
 */
.tc-row__track.swiper {
    position: relative;
    display: block;
    width: 100%;
    margin: 0;
    padding: 0;
    list-style: none;
    z-index: 1;
    /* The ONLY clip in the row, and it clips the track alone. The navigation is a sibling of
       this element precisely so it escapes it. */
    overflow: hidden;
}

.tc-row__track > .swiper-wrapper {
    position: relative;
    display: flex;
    flex-direction: row;
    width: 100%;
    /* height: auto, NOT 100% - the wrapper is sized by its tallest slide, which is what gives
       every card in the row a shared bottom and therefore a shared price baseline. */
    height: auto;
    margin: 0;
    padding: 0;
    box-sizing: content-box;
    transition-property: transform;
    transform: translate3d(0, 0, 0);
    z-index: 1;
}

.tc-row__track > .swiper-wrapper > .swiper-slide {
    flex-shrink: 0;
    transition-property: transform;
}

/* PARTIALLY VISIBLE EDGE CARD — never show a half-rendered price.
   ------------------------------------------------------------------------------------------
   The track is 1304 wide at a 284 pitch, so 4.59 cards fit and the last one is a 66%-visible
   "peek". The price sits on the card's inline-END, which is the edge the clip cuts, so on that
   card the track sliced the numeral mid-way and left just its last two zeros showing. At card
   scale that reads as an infinity symbol - and worse, it reads as a PRICE OF ZERO. `750,000`
   rendering as `۰۰` is misinformation, not a cosmetic blemish.

   ProductRowSample does the same thing structurally: its peek card's price is off-canvas
   entirely, so the peek shows imagery and a clipped title and no price at all. That is what is
   reproduced here - the media, title and swatch rail still peek and still signal "more to the
   left", but the price block is withheld until the card it belongs to is actually whole.

   TcProductRow marks the fully-visible slides (see markFullyVisible), re-evaluated on init,
   resize and every transition, so this tracks the real geometry rather than assuming which
   index is the edge. */
.tc-row__track > .swiper-wrapper > .swiper-slide:not(.tc-row__slide--whole) .tc-card__pricing {
    visibility: hidden;
}

/* SCOPING IS LOAD-BEARING. Every row rule below matches the row's OWN wrapper via the direct
   child chain `.tc-row__track > .swiper-wrapper > .swiper-slide`. A descendant selector such as
   `.tc-row__content .swiper-slide` also matches the CARD GALLERY's slides, because each card
   carries its own nested Swiper (.tc-card__viewport) inside the row - which handed every gallery
   slide the row's width, the row's stretch and the row's gutter separator. */
.tc-row__track > .swiper-wrapper { align-items: stretch; }

/* With `slidesPerView: 'auto'` Swiper measures the slide, so the slide must carry the width.
   It is the SAME token the card uses, so card width is stated once for the whole row. */
.tc-row__track > .swiper-wrapper > .swiper-slide {
    width: var(--tc-row-card-w);
    height: auto;
    display: flex;
}

.tc-row__track > .swiper-wrapper > .swiper-slide > .tc-card { min-height: 100%; }

/* Track width — CONTAINER-BASED, NOT FULL-BLEED ----------------------------
   The row track stays inside the normal centred Ryan container and lines up with every other
   homepage section. There is deliberately NO viewport break-out here.

   A previous revision pulled the track out to the viewport edges with
   `margin-inline: calc(50% - 50vw)` plus `overflow-x: clip`, on the reading that
   ProductRowSample shows partial cards bleeding off both edges. That is a property of the
   reference ARTBOARD, not of this storefront's layout: it made the product rows the only
   full-width sections on the page and broke their alignment with the banners, the category
   tiles and the section headings above them.

   Two things this section must NOT do, because they break navigation placement:
     * clip .tc-row or .tc-row__content on the x axis - the arrows live outside the track
     * pull the track full-bleed - the arrows would then have no side margin to sit in */
/* Header -------------------------------------------------------------------
   Reference composition: title on the inline-start, a view-all action on the opposite edge
   with a chevron, and the cards beginning below. The heading keeps the container inset while
   the track bleeds, which is what the reference shows. */
.tc-row__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
}

.tc-row__more {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    color: inherit;
}

.tc-row__more-chevron { width: 7px; height: 12px; fill: currentColor; flex: 0 0 auto; }

/* Row surface --------------------------------------------------------------
   ProductRowSample.png is the authority: the ProductBox row sits on the page's own LIGHT
   surface, not on a black band. The former dark treatment (full-bleed black pseudo-element,
   inverted ink tokens, held-back title, inverted Toman mark, light-on-dark swatch rings and
   carousel arrows) was deleted rather than recoloured, and the surface now belongs to EVERY row
   rather than to a `--dark` modifier. `.tc-row--dark` is still emitted by the HomepageNewProducts
   view; it carries nothing of its own now, so the class is inert and the two homepage rows are
   byte-identical in styling. */
.tc-row {
    position: relative;
    padding-block: 24px 28px;
}

/* Row surface: the RYAN PANEL, owned by the theme -----------------------------
   `.vp-product-slider { background: #fff; border-radius: 16px }` (assets/css/style.css:20399)
   is the theme's own product-row panel. The nine row views used to opt out of it with `no-bg`
   and this file re-asserted it; the views now simply do not opt out, so the surface comes from
   the theme unaided and nothing is duplicated here.

   This replaced a full-bleed `::before` that used `inset-inline: calc(50% - 50vw)`. That band
   spanned the viewport, which is what made the rows read as the only full-width sections on the
   homepage. The panel now ends exactly where the `.size2` container ends. */

/* The track runs edge to edge of that panel, so the panel needs an inset or the first card's
   media sits flush against the rounded corner - and Ryan's -22px navigation has nothing to be
   mounted on. Row layout only; it carries no page-width decision. */
.vp-product-slider.tc-row { padding-inline: 16px; }

/* Separation between neighbouring ProductBoxes ------------------------------
   The separator belongs to the GUTTER, not to the card. Drawn on the card it read as a border
   welded to the product image. Swiper puts the `spaceBetween` gutter in each slide's own margin,
   so the rule is drawn by the SLIDE, centred in that margin and inset vertically so it spans the
   card body rather than running the full media height.

   `inset-inline-start: -16px` is half the gutter on the slide's leading edge, which is the side
   Swiper actually margins in both directions - so one logical rule serves RTL and LTR.

   GUTTER WIDTH. ProductRowSample @2x: hairline pitch 520, card content box 456 -> gutter 64, i.e.
   32 CSS with the rule 16 from each neighbour. `spaceBetween: 32` in TcProductRow
   (assets/product-card/product-card.js) is the other half of this pair - the two must move
   together, and a change to either alone puts the rule off-centre. */
.tc-row__track > .swiper-wrapper > .swiper-slide { position: relative; }

.tc-row__track > .swiper-wrapper > .swiper-slide::before {
    content: "";
    position: absolute;
    inset-inline-start: -16px;
    top: var(--tc-badge-h, 22px);
    bottom: 0;
    width: 1px;
    background: var(--tc-hairline, #E6E6E6);
    pointer-events: none;
}

/* The leading edge of the first slide is the row's own edge, not a gutter between two cards. */
.tc-row__track > .swiper-wrapper > .swiper-slide:first-child::before { content: none; }

/* Carousel navigation ------------------------------------------------------
   RYAN'S PRODUCT-SLIDER CONTROL, driven by Swiper.

   THE AUTHORITY IS NOT THE GENERIC RYAN SLIDER NAV. Our rows carry `nav-slider product`, and
   the theme has a dedicated control for exactly that pair - a TALL ROUNDED PILL, not a square:

       assets/css/style.css:16308  .nav-slider .owl-nav [class*='owl-'] i
           width 40 · border-radius 16 · colour #5E5E5E · font-size 34
           background rgba(255,255,255,.3) · backdrop-filter blur(15px)
           box-shadow 0 0 8px rgba(0,0,0,.08)
       assets/css/style.css:16404  .nav-slider.product .owl-nav [class*='owl-'] i
           height 81                       <-- the product-slider override
       :16330/:16346  owl-prev { right: -22px }   owl-next { left: -22px }
       :16337/:16353  960-1199 -> -18px          :16341/:16357  <960 -> 0
       :16322  disabled { opacity: 0; visibility: hidden }
       :16326  :hover i { background: #fff }
       :16333/:16349  prev i { padding-left: 3px }  next i { padding-right: 3px }

   Measured on a live Owl `nav-slider product` host still on this page, the glyph plate computes
   40 x 81 - which is why an earlier revision that forced `height: auto` on it, and the 40x40
   square that replaced it, both read as foreign controls. The pill IS the Ryan product-slider
   navigation.

   Swiper cannot match those `.owl-*` selectors, so the declarations are restated against the
   row's own elements. Nothing here is invented; the only deliberate deviation from the Owl rule
   is the vertical anchor, which centres on the MEDIA band rather than on 50% of the whole card
   (the card is much taller than a legacy Ryan slide, so 50% put the control down by the title).

   The buttons are siblings of .swiper (see TcProductRow.buildNav) because .swiper is
   overflow:hidden; inside it, a control mounted outside the track edge would be clipped away. */
.tc-row {
    /* Ryan's product-slider plate: 40 wide, 81 tall, radius 16. */
    --tc-nav-w: 40px;
    --tc-nav-h: 81px;
    /* Ryan hangs it off the track edge. The row's white panel is inset 16px from the track, so
       -22px lands the pill mounted on the panel's edge, which is how Ryan's own product sliders
       read. Ryan's own breakpoint steps are kept. */
    --tc-nav-offset: -22px;
    /* The card's own tokens live on .tc-card, which is a DESCENDANT of the nav's ancestor chain,
       not an ancestor of it - so they are out of scope here and are restated. Keeping them as
       row-level tokens is what makes the calc below resolve at all; referencing --tc-badge-h
       here would silently invalidate the whole declaration. */
    --tc-nav-badge-h: 22px;   /* badge strip above the media, mirrors --tc-badge-h */
    /* Vertical centre of the MEDIA band, measured from the track's top edge. The media is 1:1
       and runs the full card width, so its height IS the card width token. */
    --tc-nav-top: calc(var(--tc-nav-badge-h) + var(--tc-row-card-w) / 2 - var(--tc-nav-h) / 2);
}

@media only screen and (min-width: 960px) and (max-width: 1199px) {
    .tc-row { --tc-nav-offset: -18px; }
}

@media only screen and (max-width: 959px) {
    .tc-row { --tc-nav-offset: 0px; }
}

/* The nav is positioned against the TRACK's box, so the anchor is stated rather than inherited
   from whatever the theme happens to set on .vp-content. */
.tc-row__content { position: relative; }

.tc-row__nav {
    position: absolute;
    top: var(--tc-nav-top);
    z-index: 4;
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--tc-nav-w);
    height: var(--tc-nav-h);
    padding: 0;
    border: 0;
    border-radius: 16px;
    background: rgba(255, 255, 255, .3);
    -webkit-backdrop-filter: blur(15px);
    backdrop-filter: blur(15px);
    box-shadow: 0 0 8px rgba(0, 0, 0, .08);
    color: #5E5E5E;
    cursor: pointer;
    transition: all .3s ease-in-out;
}

.tc-row__nav i {
    font-size: 34px;
    line-height: 1;
    color: inherit;
}

/* Ryan nudges the chevron 3px against its own side so it reads optically centred in the pill. */
.tc-row__nav--prev { inset-inline-start: var(--tc-nav-offset); }
.tc-row__nav--next { inset-inline-end: var(--tc-nav-offset); }
.tc-row__nav--prev i { padding-inline-start: 3px; }
.tc-row__nav--next i { padding-inline-end: 3px; }

/* Ryan's own hover: the frosted plate goes solid. */
.tc-row__nav:hover { background: #FFFFFF; color: #5E5E5E; }

/* Swiper writes these classes (see the navigation config in TcProductRow). `--disabled` is an
   edge; `--locked` is a row short enough not to need navigation at all. Ryan hides rather than
   greys out, so both go away completely. */
.tc-row__nav--disabled,
.tc-row__nav--locked {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

/* ==========================================================================
   8b. DEALS CONTEXT  (پیشنهاد شگفت‌انگیز / تخفیف‌های ویژه)
   ==========================================================================
   The Deals section was the last general product presentation still running the
   pre-Haxamanesh `.product_box.offer` card. Themes/Ryan/Views/Shared/_HomepageDealsProductBox
   now renders the canonical _ProductCard, so everything below is OUTER CONTEXT on the same
   card - no deals card partial, no deals-only class on any card element. The only markup
   this context adds is the countdown, which the canonical card has no concept of.

   Membership: the desktop track is a `.vp-slider` holding `.tc-card`, so TcProductRow claims
   it and it becomes a Swiper row like every other; the mobile branch is a native scroller and
   carries `tc-row` in the markup for the same tokens.

   WHY A PLATE HERE AND NOWHERE ELSE. Every other row sits on the theme's white
   `.vp-product-slider` panel, so the canonical card is deliberately transparent. This section
   paints its OWN decorative background (an admin-chosen image or colour on
   `.vp-special-discount`), and card text on top of that artwork is unreadable. The plate is
   therefore container presentation for this surface - the card inside it is untouched.
   ========================================================================== */

/* Neither host owns page width; both sit inside the section's own padding. */
.tc-deals.tc-row { padding-block: 0; }

/* The row must be able to SHRINK to its container.
 *
 * The mobile Deals section (`.amazing-offer`) is a flex container in the theme's own CSS, so this
 * row host is a flex item - and a flex item defaults to `min-width: auto`, meaning it refuses to
 * shrink below its content's min-content width. This item's content is a Swiper track whose
 * natural width is the sum of every slide (2 x 252 + 32 = 568 on this catalogue), so the row blew
 * the section out to 568px inside a 390px viewport and the whole page scrolled sideways.
 *
 * `min-width: 0` is the flexbox opt-out of that floor, not a magic number: it lets `width: 100%`
 * on `.tc-row__track.swiper` finally resolve against the space the container actually has. The row
 * still owns no page width - it takes whatever `.amazing-offer` / `.size2` give it. */
.tc-deals {
    min-width: 0;
    width: 100%;
}

/* EQUAL HEIGHT is inherited, never declared.
 *
 * `min-height`, deliberately not `height: 100%`. A flex item with a definite cross size is not
 * stretched, so `height: 100%` made the plate opt OUT of any equalising parent and size to its own
 * content - which is exactly how the mobile row ended up with an 18px plate-height spread and two
 * different timer baselines. With min-height the plate takes the row's line height when there is
 * one and still fills its slide, so no fixed height and no per-product rule is needed anywhere. */
.tc-deal {
    display: flex;
    flex-direction: column;
    width: 100%;
    min-height: 100%;
    padding: 10px 10px 14px;
    border-radius: 16px;
    background: #FFFFFF;
    box-sizing: border-box;
}

/* The card fills the plate; the plate, not the card, carries the width. Without this the
   252px on `.tc-row .tc-card` would fight the plate's own padding and overflow it. */
.tc-deal .tc-card {
    width: 100%;
    flex: 1 1 auto;
    /* the plate supplies the safe-area, so the card's own text inset would double it */
    --tc-text-pad-x: 0px;
    /* the countdown sits below, so the card no longer needs to reserve the hanging row's air */
    --tc-pad-b: 10px;
}

/* The slide carries the row's line height - `.tc-row__track > .swiper-wrapper` already stretches
   every slide to the tallest one, in section 8 - so the plate only has to fill the slide it is
   given. That single rule is the whole equalisation: no fixed height, no per-product rule, and
   nothing that has to know which products have a rating or a discount.
   Both breakpoints run this same path now - the mobile branch emits the same .vp-slider markup -
   which is why there is no mobile-only equalisation rule here. */
.tc-deals .tc-row__track > .swiper-wrapper > .swiper-slide > .tc-deal { height: 100%; }

/* No gutter hairline in this context: the plates already separate one deal from the next, and
   a rule drawn between two white tiles on a photographic background reads as damage. */
.tc-deals .tc-row__track > .swiper-wrapper > .swiper-slide::before { content: none; }

/* Countdown ---------------------------------------------------------------
   Same rail the legacy deals card showed - digits, clock glyph, sale progress bar - re-seated
   under the canonical card. Colours are the card's own danger token, not a new palette. */
.tc-deal__timer {
    flex: 0 0 auto;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--tc-hairline, #E6E6E6);
}

.tc-deal__timer .vp-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    color: var(--tc-danger, #ED303D);
}

.tc-deal__timer .vp-discount-timer-time-v2 {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    font-size: 15px;
    font-weight: 700;
    line-height: 1;
    direction: ltr;
}

.tc-deal__timer .vp-row i { font-size: 15px; line-height: 1; }

.tc-deal__timer .vp-progress {
    height: 6px;
    margin-top: 8px;
    border-radius: 999px;
    background: #FADBDD;
    overflow: hidden;
}

.tc-deal__timer .vp-progress-bar {
    height: 100%;
    border-radius: inherit;
    background: var(--tc-danger, #ED303D);
}

/* ==========================================================================
   9. RESPONSIVE / ACCESSIBILITY
   ========================================================================== */

@media (max-width: 991.98px) {
    /* Larger touch target for the dots without changing how they look. */
    .tc-card__dots .swiper-pagination-bullet { position: relative; }
    .tc-card__dots .swiper-pagination-bullet::after { content: ""; position: absolute; inset: -8px; }
}

@media (prefers-reduced-motion: reduce) {
    .tc-card__viewport.swiper .swiper-wrapper { transition-duration: 0ms !important; }
    .tc-card__dots .swiper-pagination-bullet { transition: none; }
}

/* ==========================================================================
   9b. MEDIA RATIO — CONFIGURABLE PER ROW
   ==========================================================================
   The card's media aspect has always been a token (`--tc-media-ratio`); what was missing was a
   supported way to SET it per section without editing this file. These modifiers are that way.

   Put one on the row section - or on any ancestor of the cards - and every card inside follows:

       <section class="vp-product-slider tc-row tc-row--media-portrait"> ...
       <section class="vp-product-slider tc-row tc-row--media-square">  ...

   They are context classes on the SAME card markup - not a second card, not a per-product flag -
   so a row switches ratio by changing one class in its view, and a future third ratio is one
   more modifier here. The `.tc-media-*` aliases exist so a non-row container (a widget zone, a
   landing block) can opt in with the same vocabulary.

   Written after the row and grid context blocks and matched at (0,2,0), so a modifier beats the
   context default it sits inside, at every breakpoint.
   ========================================================================== */

.tc-row--media-portrait .tc-card,
.tc-media-portrait .tc-card { --tc-media-ratio: 3 / 4; }

.tc-row--media-square .tc-card,
.tc-media-square .tc-card { --tc-media-ratio: 1 / 1; }

/* ==========================================================================
   9c. MOBILE PRODUCT ROW  —  native rail, single image, no arrows
   ==========================================================================
   BREAKPOINT 991.98px. This is not a free choice: it is the same edge as
   CONFIG.desktopMinWidth (992) in the gallery controller and ROW_DESKTOP_MIN_WIDTH (992) in
   TcProductRow, and the same edge the cart-recommendation sheet switches its own rail at. A
   viewport between two different edges would get JS from one mode and CSS from the other.

   INTERACTION MODEL. One model for the whole mobile storefront: a native overflow-x rail with
   scroll-snap. The cart-recommendation sheet already browses products this way, so the rows do
   too instead of running Swiper here. TcProductRow adds `.tc-row__track--rail` and leaves the
   .swiper-wrapper / .swiper-slide markup alone, so the rail reuses the same boxes and crossing
   the breakpoint is only a class swap.

   GEOMETRY comes from the CRI card, which is the agreed benchmark: 172 wide, 3:4 portrait
   media, 8px between neighbours.
   ========================================================================== */

@media only screen and (max-width: 991.98px) {

    /* CRI card geometry, adopted wholesale. 252 was a desktop number: on a 390px screen it
       showed one and a half products; 172 shows two and a bit, which is what the sheet does. */
    .tc-row {
        --tc-row-card-w: 172px;
        --tc-row-rail-gap: 8px;
    }

    /* PORTRAIT is the mobile default, matching the CRI card. The row's desktop default is
       square; that stays. An explicit .tc-row--media-square still wins - it is declared in 9b
       against `.tc-card`, one specificity step above this.
       Radius 0 for the same reason: the CRI media is a flat square-cornered image, and a 10px
       rounded tile next to it reads as a different card family. */
    .tc-row .tc-card {
        --tc-media-ratio: 3 / 4;
        --tc-media-radius: 0px;
    }

    /* ---- the rail ---- */
    .tc-row__track--rail {
        display: block;
        overflow-x: auto;
        overflow-y: hidden;
        -webkit-overflow-scrolling: touch;
        overscroll-behavior-x: contain;
        scroll-snap-type: x proximity;
        scrollbar-width: none;
    }

    .tc-row__track--rail::-webkit-scrollbar { display: none; }

    /* Swiper's transform must not survive a mode swap; `width: max-content` is what lets the
       flex line grow past the viewport so it can actually be scrolled. */
    .tc-row__track--rail > .swiper-wrapper {
        display: flex;
        flex-direction: row;
        align-items: stretch;
        width: max-content;
        gap: var(--tc-row-rail-gap);
        transform: none !important;
        transition: none !important;
    }

    .tc-row__track--rail > .swiper-wrapper > .swiper-slide {
        flex: 0 0 auto;
        width: var(--tc-row-card-w);
        height: auto;
        /* Swiper writes the gutter as a slide margin; the rail uses `gap` instead, so any
           leftover inline margin would double it. */
        margin: 0 !important;
        scroll-snap-align: start;
    }

    /* The gutter hairline belongs to the Swiper gutter, which the rail does not have. */
    .tc-row__track--rail > .swiper-wrapper > .swiper-slide::before { content: none; }

    /* ---- no arrows on mobile ----
       Touch-first: the rail is dragged, not clicked. TcProductRow does not build the buttons in
       rail mode; this also covers a row left over from a desktop->mobile resize. */
    .tc-row__nav { display: none !important; }

    /* ---- single image per card in a row ----
       The gallery controller skips row cards on mobile (see product-card.js), so the server's
       multi-slide markup is left inert. Show the first slide, drop the rest and the dot rail.
       Scoped by `.vp-slider` - the row track - so the PLP / category grid keeps its galleries. */
    .vp-slider .tc-card__media--gallery .tc-card__viewport > .swiper-wrapper {
        display: block;
        width: 100%;
        transform: none !important;
    }

    .vp-slider .tc-card__media--gallery .tc-card__viewport > .swiper-wrapper > .swiper-slide {
        width: 100% !important;
        margin: 0 !important;
    }

    .vp-slider .tc-card__media--gallery .tc-card__viewport > .swiper-wrapper > .swiper-slide + .swiper-slide {
        display: none;
    }

    .vp-slider .tc-card__dots { display: none; }

    /* ======================================================================
       CRI IS THE VISUAL AUTHORITY FOR THE WHOLE MOBILE ROW
       ======================================================================
       Not just the card box - the section header, its seams, the ink and the price
       composition too. Every number below was read off the rendered
       .tc-cart-rec__section on an iPhone 12, so the two read as one component family:

           section inline padding   16 / 16
           section block padding    14 top
           section title            15px / 700 / #292D32, start-aligned
           head padding             0 0 12       (the 12 IS the head->rail seam)
           card title               13px / 400 / line-height 1.75 / 2 lines reserved
           ink                      #292D32      (the row was #2F2F2F)
           old price                #BCBCBC 12/400
           discount %               #ED2E7E 12/700
           price block              price on line 1, old price + % TOGETHER on line 2
       ====================================================================== */

    /* --- section shell --- */
    .vp-product-slider.tc-row {
        padding-block: 14px 20px;
    }

    /* --- section header ---
       The theme's .header-section carries its own 16px padding and an m-b-16 margin; CRI's head
       has neither - its only seam is a 12px bottom padding, which is also the head->rail gap. */
    .tc-row .tc-row__head {
        padding: 0 0 12px;
        margin: 0;
    }

    /* `h2` as well as `h3`: the head selector was written against the row views that emit an
       h3, but SimilarProducts/Default.cshtml emits `<h2 class="vp-title">`. It therefore missed
       that one heading, which fell through to a generic rule and rendered #4A5F73 / 27px while
       every other product-row heading on both pages measured #292D32 / 24px. Adding the tag is
       what makes the shared contract actually shared - no new colour is introduced here.

       Font size is deliberately left to lose: `.vp-product-slider .vp-header > .vp-title`
       (0,3,0) keeps its 18px over the 15px below, exactly as it already does for the home rows'
       `h3.vp-title`. So the corrected heading matches the HOME `.vp-title` headings it belongs
       with - 18px / 700 / 24px / #292D32 / YekanBakhEn - rather than being forced into the
       `.header-section__title` family it is not part of. */
    .tc-row .header-section__title,
    .tc-row .tc-row__head h2,
    .tc-row .tc-row__head h3 {
        font-size: 15px;
        font-weight: 700;
        line-height: 24px;
        color: #292D32;
    }

    /* CRI's restrained secondary text (#8f8f8f / 12px) is the model for the row's view-all
       action - it is a supporting link, not a second heading competing with the title. */
    .tc-row .tc-row__more {
        font-size: 12px;
        font-weight: 500;
        color: #494A5C;
    }

    .tc-row .tc-row__more-chevron { width: 6px; height: 10px; }

    /* --- card ink + type, from the CRI card --- */
    .tc-row .tc-card {
        --tc-ink: #292D32;
        --tc-muted: #8F8F8F;
        --tc-gap-media-title: 10px;
        --tc-gap-title-swatch: 8px;
        /* SWATCH -> PRICE SEAM.
           Measured before: the swatch rail's bottom sat 10px above the price glyph, and with the
           chips being circles the optical distance was smaller still - the price read as a seventh
           chip on the same rail rather than as the next region of the card.
           The other three contexts already separate them properly (mobile grid 18, desktop row 21,
           deals 34), so this was the mobile row alone drifting, not a system-wide value.
           18 puts it on the mobile GRID's number, which is the one measured off plp-mobile-1, and
           inside the 16-20 band. The pair below sums to the same 18 for the rating case, so the
           seam does not change depending on whether a product happens to have reviews.
           No card grows a fixed height: the seam is a minimum, .tc-card__pricing still carries
           `margin-top: auto`, and the rail still stretches every slide - so the price stays
           bottom-anchored and neighbouring price baselines are unchanged. */
        --tc-gap-swatch-meta: 12px;
        --tc-gap-meta-price: 6px;
        --tc-gap-swatch-price: 18px;
        --tc-gap-title-price: 10px;
        --tc-pad-b: 12px;
        /* CRI reserves two 22.75px lines for the title so neighbouring cards keep one baseline */
        --tc-title-lh: 23px;
        --tc-title-h: 46px;
    }

    /* 400, not 600: CRI's product name is regular weight. The heavier row title was the single
       most obvious "different design system" tell next to the sheet. */
    .tc-row .tc-card__title {
        font-size: 13px;
        font-weight: 400;
        line-height: 23px;
        color: #292D32;
        /* CRI RESERVES the two-line box (min-height 46) rather than treating it as a ceiling.
           In a rail of independent cards that reserve is what keeps every price on one
           baseline - without it a one-line title lifts its card's price above its neighbour's.
           The desktop row keeps the max-height-only behaviour. */
        min-height: var(--tc-title-h);
    }

    .tc-row .tc-card__price-value { font-size: 19px; font-weight: 700; color: #292D32; }
    .tc-row .tc-card__price .tc-card__price-currency { height: 21px; }
    .tc-row .tc-card__old-price { font-size: 14px; font-weight: 400; color: #BCBCBC; }
    .tc-row .tc-card__discount-badge { font-size: 14px; font-weight: 700; color: #ED2E7E; }
    .tc-row .tc-card__swatch-chip { width: 18px; height: 18px; }
    .tc-row .tc-card__swatches { gap: 6px; }

    /* --- price composition, rebuilt to CRI's ---
       CRI:  line 1  price + Toman, on the inline-START
             line 2  struck old price and the % TOGETHER, gap 8
       The row put the % on the price line at the opposite edge and the old price alone beneath.
       `display: contents` lifts the two markup rows out of the way so all four elements become
       items of one grid - so the composition changes with NO change to the shared card markup,
       and the desktop/PLP arrangement is untouched. */
    .tc-row .tc-card__pricing {
        display: grid;
        grid-template-columns: auto auto 1fr;
        grid-template-rows: auto minmax(var(--tc-oldprice-h, 17px), auto);
        column-gap: 8px;
        row-gap: 4px;
        align-items: center;
    }

    .tc-row .tc-card__price-row,
    .tc-row .tc-card__oldprice-row { display: contents; }

    .tc-row .tc-card__price {
        grid-column: 1 / -1;
        grid-row: 1;
        justify-self: start;
        margin-inline-end: 0;
    }

    .tc-row .tc-card__old-price { grid-column: 1; grid-row: 2; justify-self: start; }
    .tc-row .tc-card__discount-badge { grid-column: 2; grid-row: 2; justify-self: start; }

    /* The non-priced states replace the whole block, so they must not inherit the grid. */
    .tc-row .tc-card__pricing > .tc-card__state {
        grid-column: 1 / -1;
        justify-self: start;
    }

    /* A native rail has no clipped peek card - every slide is marked whole by the controller -
       so the desktop peek-price guard must not linger and blank a price here. */
    .tc-row__track--rail > .swiper-wrapper > .swiper-slide .tc-card__pricing { visibility: visible; }
}

/* ==========================================================================
   10. MOBILE FLAT PRESENTATION
   ==========================================================================
   MOBILE ONLY. Listing items are presented FLAT - no frame, no plate, no shadow, no outer
   radius - so the content owns the width instead of a chrome box owning it.

   BREAKPOINT. 959.98px is the theme's own mobile/tablet boundary, not a new number: it is the
   edge `.justMobileTablet` / `.justDesktop` switch on, and the same edge the row navigation
   already changes offset at (assets/css/style.css:16341/16357). Everything in this section is
   inside the query, so DESKTOP IS UNTOUCHED - there is no rule here that can reach it.

   WHAT COUNTS AS A FRAME. Measured on an iPhone 12, these were the containers painting a
   surface around listing items:

       section.vp-product-slider.tc-row    #fff, radius 16, padding-inline 16   (homepage rows)
       div.tc-deal                         #fff, radius 16, padding 10/10/14    (deal item)
       .vp-selected-brands .vp-item        #fff, radius 16, 1px border, shadow  (brand item)
       .vp-post-item-theme                 #fff, radius 16, shadow              (post item)

   SECTION vs ITEM. Where a section carries a background ON PURPOSE - the Deals section paints an
   admin-chosen image or colour, and the brand section is the page's own panel - the section is
   left alone and only the ITEM boxes inside it are flattened. Where the panel IS the frame and
   nothing else needs it, as on the product rows, the panel goes.

   WHAT IS NOT TOUCHED. Gutters, seams and alignment are untouched everywhere: spacing is what
   keeps flat items readable, so removing it would trade one defect for another. Banner sliders
   keep their radius - a rounded photograph is artwork, not a card frame.
   ========================================================================== */

@media only screen and (max-width: 959.98px) {

    /* --- homepage product rows -------------------------------------------
       The white panel IS the frame here: the cards inside are already transparent, and nothing
       else in the section depends on the surface. Dropping it and its 16px inline padding hands
       32px of width back to the cards. `.tc-row` raises specificity over the theme's own
       `.vp-product-slider` rule (assets/css/style.css:20399) so this does not depend on which
       stylesheet loads last. Vertical rhythm is kept - only the box goes. */
    .vp-product-slider.tc-row {
        background: transparent;
        border-radius: 0;
        box-shadow: none;
        /* 16px, not 0 - CRI is the authority for the mobile section's horizontal inset and its
           .tc-cart-rec__section uses 16/16. An edge-to-edge row read as a different component
           from the sheet, which was the point of the correction. The 32px the panel's own
           padding used to waste is still gone; this is the sheet's inset, not the panel's. */
        padding-inline: 16px;
    }

    /* --- product rows KEEP the panel on the PDP and the HOME PAGE ---------
       EXCEPTION to the flat presentation directly above.

       The flat rule was written on the premise that a white panel on those pages was pure
       chrome. That held for pages whose own ground is white; it does not hold here. Both of
       these pages sit on `body { background: #f2f2f2 }` (assets/css/style.css), and on both of
       them every OTHER block - the PDP's gallery, price box, features and specs; the home page's
       banners and category tiles - already reads as a white card floating on that grey. A
       transparent product row was the only block on either page with no surface, so its heading
       sat on bare grey while the cards below it floated unattached. The panel is what makes the
       row read as one section instead of a heading and some loose cards.

       SCOPE, and why it is these two class names ------------------------------
       `.page-product`   ViewBag.BodyCssClass, Views/Product/ProductTemplate.*.cshtml
       `.html-home-page` the home page's own body class

       `.page-home` and `.page-index` are NOT usable here even though the home body carries them:
       the PDP body carries them too (measured: `page-extra page-product page-index page-home
       vp-has-mini-header`), so they are not home-page markers at all and scoping to them would
       be a selector that means something other than what it says. `.html-home-page` appears on
       the home body and nowhere else, so it is both the narrowest and the honest choice.

       Every other page - category, search, manufacturer, tag, cart - is untouched and keeps the
       flat presentation. Both selectors raise specificity over the flat rule above without a
       single `!important`, so this does not depend on rule order within the block.

       WHICH ROWS. One selector covers every canonical row on both pages: the three PDP
       components (Views/Shared/Components/{SimilarProducts,RelatedProducts,ProductsAlsoPurchased})
       and the home rows, so they cannot drift apart. The deals section is NOT matched - it is
       `section.vp-special-discount > .vp-content.tc-deals`, not a `.vp-product-slider`, so its
       admin-chosen campaign artwork is untouched, exactly as the deals note below intends. The
       promotional banner pairs between home rows are not `.vp-product-slider` either.

       WHAT IS NOT CHANGED. The 16px inline padding above is the section's inset and is already
       correct, so it is inherited rather than restated; the 14/20 block padding (section 9) and
       the 24px bottom margin (`.vp-product-slider`, assets/css/style.css) are likewise the
       existing rhythm and are left to their owners - that 24px is the grey gap that keeps the
       panels reading as separate blocks. Only the surface is added back. The heading is already
       inside <section> in every one of these views, so it - and the home rows' `همه` view-all
       link and chevron - come onto the panel with no markup change.

       RADIUS. 16px is the theme's own `.vp-product-slider` radius and the same number every
       other white block on these pages uses - not a new value invented for this row.

       SCROLL. The panel paints a background; it sets no overflow. The only clip in the row is
       `.tc-row__track { overflow: hidden }` / the `.scrollMode` overflow-x on the rail, both
       INSIDE the panel and both untouched, so the swipe and the card geometry are exactly as
       they were. */
    body.page-product .vp-product-slider.tc-row,
    body.html-home-page .vp-product-slider.tc-row {
        background: #fff;

        /* FLAT, NOT A CARD. The flat presentation above is kept in full - the ONLY thing this
           exception changes is the surface COLOUR. No radius, no shadow, no border, so the
           section reads as a band of the page rather than a plate sitting on it. */
        border-radius: 0;
        box-shadow: none;
        border: 0;

        /* FULL BLEED. Ryan's own break-out pattern, the same four declarations the PDP gallery
           already uses (assets/css/style.css:36239, from css/product.less). It is safe against
           page overflow because `body { max-width: 100%; overflow-x: hidden }` (css/base.less)
           is the page's own guard - measured after this change, scrollWidth still equals
           innerWidth on both pages.

           Needed because the section's container carries a gutter that differs per page - .size2
           on home left an 8px band each side, .size on the PDP ~15px - so a white surface that
           stopped at the container read as an inset card, and read as a DIFFERENT WIDTH card on
           each page. Breaking out of the container is what makes one rule produce one result. */
        width: 100vw;
        max-width: 100vw;
        margin-right: calc(50% - 50vw);
        margin-left: calc(50% - 50vw);

        /* The rail takes the whole width. The 16px section inset above exists to hold cards off
           a ROUNDED CORNER and to give the desktop nav arrows something to mount on; neither
           applies here - the corner is gone and `.tc-row__nav { display: none !important }`
           (section 9) removes the arrows at this breakpoint. Keeping it would spend 32px of a
           390px viewport on nothing, so the first card now starts at the right edge and the last
           finishes at the left. The card-to-card gutter is untouched and remains the only
           separation between cards. */
        padding-inline: 0;
    }

    /* The HEADING does not follow the rail to the edge - text needs a reading inset, and its
       16px matches the page's other headings. This is the one place where head and rail are
       deliberately given DIFFERENT horizontal padding. Block padding stays with the CRI rule in
       section 9: `padding-inline` is a side-only shorthand, so the 12px head->rail seam there
       is untouched. */
    body.page-product .vp-product-slider.tc-row > .tc-row__head,
    body.html-home-page .vp-product-slider.tc-row > .tc-row__head {
        padding-inline: 16px;
    }

    /* The RAIL takes 8px, not the section's old 16 and not the 0 it briefly had. 0 read as a
       collision - the card media met the screen edge with no air at all - while 16 spent 32px of
       a 390px viewport on nothing. 8 is the smallest inset that still reads as deliberate.

       It sits on `.tc-row__content`, the rail's WRAPPER, not on the section and not on the
       scroller: the section must stay edge-to-edge white, and padding on the scroller itself
       behaves differently at the two scroll extremes. With the wrapper inset the geometry is
       symmetric by construction - the first card starts 8px from the right edge at the RTL
       start, and the last card finishes 8px from the left edge at the far end.

       Card-to-card spacing is untouched: this is an edge inset, not a gutter change. */
    body.page-product .vp-product-slider.tc-row > .tc-row__content,
    body.html-home-page .vp-product-slider.tc-row > .tc-row__content {
        padding-inline: 8px;
    }

    /* --- deals -----------------------------------------------------------
       SEPARATED PLATES, not one continuous ground.

       This sub-block used to do the opposite: it flattened every `.tc-deal` to
       transparent and moved a single opaque `background: #FFFFFF` onto `.tc-deals`,
       the list they share. That was done for legibility - the section paints an
       administrator's campaign artwork, and card text on artwork is unreadable - and
       it did solve legibility. What it cost was the section itself: measured at 390px
       the white ground ran the full 374px width and 501px height of the list, so below
       the heading band the artwork was completely covered and the two deals, sitting
       8px apart on one white slab, read as a single undivided row rather than as two
       products on a campaign surface.

       The plates are the treatment that satisfies BOTH. Section 8b already defines them
       - `background: #FFFFFF; border-radius: 16px; padding: 10px 10px 14px` - and every
       word of that block's reasoning ("this section paints its OWN decorative
       background ... card text on top of that artwork is unreadable ... the plate is
       container presentation for this surface") applies at this breakpoint exactly as it
       does above it. So mobile no longer overrides them at all: each deal gets its own
       opaque tile, text stays on white, and the artwork shows through the gutter between
       the tiles and around the list. One definition, both breakpoints.

       The three declarations removed here are removed as a set, because each existed only
       to serve the flattening:
         .tc-deal { background/border-radius/box-shadow/padding }  the flattening itself
         .tc-deals { background/border-radius/padding-block }      the substitute ground
         .tc-deal .tc-card { --tc-text-pad-x: 10px }               the substitute text inset
       That last one has to go with them: section 8b sets `--tc-text-pad-x: 0px` on the
       card precisely BECAUSE the plate supplies the safe-area, so leaving the 10px here
       would inset the card's text twice inside a plate that already pads it.

       Equal height is untouched: `.tc-deal` is still `min-height: 100%` inside a slide
       the row stretches (section 8b), and the padding restored is uniform across every
       plate, so all plates grow by the same amount and the timer and progress-bar
       baselines stay level with each other. */

    /* The rail takes an 8px edge inset so the plates do not touch the section's sides.
       Without it the first plate's edge landed exactly on the section's right edge
       (measured: plate right 382, section right 382) and read as a crop rather than a
       card. 8px is not a new number - it is the same edge inset the home and PDP rows
       already take on `.tc-row__content` a few rules above, and it matches the 8px
       card-to-card gutter the row itself sets, so the artwork shows in an even rhythm
       around and between the tiles.

       It sits on the rail WRAPPER, which is what `.tc-deals` is (it computes to
       `.vp-content.tc-deals.tc-row.tc-row__content`), not on the scroller inside it -
       padding on a scroller behaves differently at the two scroll extremes, while on the
       wrapper the geometry is symmetric by construction.

       Block padding stays 0 from `.tc-deals.tc-row` in section 8b: the section's own
       `padding: 16px 0` and its heading's `margin-bottom: 24px` (style.css,
       `.amazing-offer`) already open and close the list, so adding more here would just
       widen the band of artwork above and below without adding separation. */
    .tc-deals {
        padding-inline: 8px;
    }
    /* --- brand / manufacturer items --------------------------------------
       A white bordered tile inside a white panel - the frame was the only thing separating it
       from its own background. Margins stay, so the items keep their spacing and never touch.

       The selector list and the two !important flags are NOT padding: the theme paints this item
       from three places, and the strongest of them wins on both counts.
           style.css  .vp-selected-brands .vp-item                       box-shadow / radius !important
           style.css  .vp-selected-brands.vp-all .vp-content .vp-item    (0,4,0) bg / shadow / radius
           style.css  same selector inside max-width:959px               1px border
       Matching (0,4,0) and re-declaring only the two properties the theme itself marked important
       is what it takes to actually turn the frame off; a weaker rule silently loses. */
    .vp-selected-brands .vp-item,
    .vp-content-brands .vp-item,
    .vp-selected-brands.vp-all .vp-content .vp-item,
    .vp-selected-brands.vp-all .vp-content-brands .vp-item {
        background: transparent;
        border: 0;
        border-radius: 0 !important;
        box-shadow: none !important;
    }

    /* --- post / article listing items ------------------------------------
       Same boxed treatment, same listing-slider role, so it follows the same rule. Background and
       radius are won on load order alone; only the shadow needs !important, because the theme's
       own mobile rule declares it that way (style.css, max-width:959px). */
    .vp-post-item-theme {
        background: transparent;
        border-radius: 0;
        box-shadow: none !important;
    }
}

/* ==========================================================================
   11. STACKING
   ==========================================================================
   ONE HIERARCHY, AND THE CARD IS AT THE BOTTOM OF IT.

   The storefront's page-level layers are already numbered, and they are not ours to renumber:

       .vp-bottom-sheet / .vp-toolbar / mini header        20     persistent chrome
       .km-category-compare-products                       12     persistent PLP utility widget
       .before-class                                      145     modal backdrop
       div[class*="bottomsheet-"]                         150     bottom-sheet / dialog content
       .tc-cart-rec                                      1300     recommendation sheet

   The card's own overlays are numbered relative to EACH OTHER, and only to each other:

       media / gallery viewport                             1
       gallery dots                                         2
       QSM host                                             3     (QSM's own control sits at 5
                                                                   INSIDE the host - see the
                                                                   firewall note in section 5)
       row navigation (.tc-row__nav, section 8c)            4     row-scoped, not card-scoped

   THE DEFECT THIS PREVENTS. `.tc-card` is `position: relative` with `z-index: auto`, which is NOT
   a stacking context. Every number in the second list therefore resolved against the ROOT stacking
   context, side by side with the page-level numbers in the first - the two scales were competing
   in one namespace, and the card's numbers only stayed underneath by being small. Anything that
   raised one of them (a QSM state, a promoted Swiper wrapper, a future badge or overlay, a theme
   rule reaching in) would have painted a card control straight through a modal backdrop, and no
   amount of raising the backdrop would fix it for the next one.

   `isolation: isolate` closes that namespace. It makes each card a stacking context whose children
   are sealed inside it, so the whole card composites as ONE layer at its natural document position:
   card overlays can never outrank page chrome, a backdrop, or a dialog, whatever number they carry
   internally, and the internal ordering above keeps working exactly as before.

   Deliberately `isolation`, not `z-index: 0`: it creates the stacking context without giving the
   card a paint order of its own, so cards keep compositing in document order and nothing in the
   grid or the row changes position. It also costs no layout and no repaint.

   The corollary is that no rule outside this file needs a bigger number. The backdrop stays at 145
   and the dialog at 150; the persistent widgets stay at 12 and 20 and now genuinely outrank every
   card that scrolls behind them, because a card's ceiling is its own box. */
.tc-card { isolation: isolate; }

/* The row's navigation is row chrome, not card chrome, so it sits OUTSIDE the card's context and
   keeps its own number - but it is scoped to `.tc-row__content`, which is `position: relative`
   with `z-index: auto`. Sealing it the same way keeps the arrows above the track they belong to
   and below everything the page puts on top of the row. */
.tc-row__content { isolation: isolate; }

/* ============================================================================
 * EMPTY RESULT — the grid's own child, not a card.
 *
 * ArchiveProducts emits the "no products" section as a direct child of this
 * grid, so `.tc-grid > *` was laying it out as a single 1fr flex cell: on
 * desktop a ~200px column with the icon, headline and helper text stacked into
 * narrow vertical strips. It is not a card - it spans the whole track.
 *
 * The !important declarations answer `.tc-grid > *`, which sets width, max-width
 * and margin with !important of its own. Mobile Search already corrects this in
 * assets/mobile-search-header/mobile-search-header.css and keeps its own 340px
 * column; this rule owns the desktop side, where the grid is 4 columns wide.
 * ============================================================================ */
@media (min-width: 992px) {
    .tc-grid > .km-category-grid-product-list-empty,
    .km-category-grid-style .km-category-grid-product-list.tc-grid > .km-category-grid-product-list-empty {
        grid-column: 1 / -1;
        display: block;
        width: 100% !important;
        max-width: 560px !important;
        margin: 0 auto !important;
        padding: 56px 16px 64px;
        min-height: 0;
        text-align: center;
    }
}

/* ============================================================================
 * 14. MOBILE ROW DENSITY — issues 1 and 3 of the final mobile hotfix.
 *
 * Appended as its own section rather than edited into sections 9/12, so the
 * whole of this round is one reversible block and the reasoning stays with the
 * numbers. Everything here is inside the same 991.98px breakpoint the mobile row
 * rules already use, and every selector is prefixed with `.tc-row`: the PLP grid
 * (`.tc-grid`) is a sibling composition and is not touched by any of it.
 * ========================================================================== */

@media only screen and (max-width: 991.98px) {

    /* --- ISSUE 1: cards read as vertically stretched -----------------------
       `.tc-card__pricing { margin-top: auto }` (section 5) is the cause, and it
       is correct on desktop: slides in a Swiper track are equal height, the auto
       margin absorbs that slack, and neighbouring cards therefore share a price
       baseline instead of each floating its price at its own height.

       On a 390px row the same mechanism reads as a defect. The row's height is
       set by its tallest card - typically a two-line title plus swatches - and
       every shorter card in the row has that entire difference injected as one
       empty band between its title and its price, which is exactly the gap in
       the reference screenshot. The card is not padded too generously; it is
       being stretched, and the stretch is pooling in one place.

       Two lines, both scoped to `.tc-row`:

       - releasing the auto margin stops the slack pooling mid-card, so the
         pricing block follows the meta at its normal `--tc-gap-meta-price` seam;
       - `align-items: flex-start` on the track stops the slack existing at all,
         so each card ends at its own content and the row is as tall as its
         tallest card rather than every card being as tall as the tallest.

       Order matters between them: without the second line the released margin
       would simply move the same empty band from mid-card to below the price.
       The price baseline the auto margin was protecting is a desktop contract -
       desktop keeps it, untouched, outside this query. */
    .tc-row .tc-card__pricing {
        margin-top: 0;
    }

    .tc-row .tc-row__track.swiper > .swiper-wrapper,
    .tc-row .tc-row__track > .swiper-wrapper {
        align-items: flex-start;
    }

    /* --- ISSUE 3: -16px after the title, -16px after the row ---------------
       Both reductions are taken from the section/row spacing owners in section
       12, never from card internals.

       AFTER THE ROW: `.vp-product-slider.tc-row { padding-block: 14px 20px }`.
       The trailing 20px is the entire space between the last card and whatever
       section follows, so 20 -> 4 is the -16 exactly, with 4px left so the row
       still closes rather than colliding with the next section.

       AFTER THE TITLE: all 16px come out of the title->cards gap, and none out
       of the space above the title. That gap is owned by a single declaration,
       `.tc-row .tc-row__head { padding: 0 0 12px }`, and 12 is everything it
       has - so 12px come off there and the last 4 are pulled by the track.

       The -4px is safe, and specifically it does not clip the heading. The head
       lays out at `font-size: 15px / line-height: 24px` (section 12), so its
       line box carries (24 - 15) / 2 = 4.5px of half-leading BELOW the last
       glyph before the box even ends. The pull consumes that empty leading and
       stops 0.5px short of the descender line; nothing overlaps the text. It is
       applied to `.tc-row__content`, the track's wrapper, so it moves the cards
       and not the head - the head keeps its own box and a long or wrapped title
       still pushes the cards down normally.

       The row's leading padding is deliberately left at its original 14px. An
       earlier revision of this block took the last 4px from there instead; that
       removed the right total but from the wrong side of the title. */
    .tc-row .tc-row__head {
        padding-bottom: 0;
    }

    .tc-row > .tc-row__content {
        margin-top: -4px;
    }

    .vp-product-slider.tc-row {
        padding-block: 14px 4px;
    }

    /* `.tc-deals.tc-row` zeroes its own padding-block in section 12 and lays its
       plates out itself, so the row rule above cannot reach it. The deals plate
       is the composition in the reference screenshot, so it takes the same -16
       explicitly. The `.tc-row > .tc-row__content` rule above already covers its
       track, since the deals section carries `tc-row` too. */
    .tc-deals.tc-row .tc-row__head {
        padding-bottom: 0;
    }
}

/* ============================================================================
 * 15. DEALS PLATE DENSITY - mobile only, .tc-deals only.
 *
 * Measured on the running homepage at 390: the plate is 484px tall for a 152px-wide
 * card, and the band between the title and the price is 64px. That band is NOT
 * empty - it is the swatch row (24px + 8px margin) and the meta row (20px + 12px
 * margin) - so nothing here removes content; it removes the air around it, which is
 * what makes the plate read as stretched next to its own image.
 *
 * Every selector is prefixed .tc-deals and every rule sits inside the mobile query,
 * so the product rows, the PLP grid and the desktop deals plate are untouched. The
 * card's own component rules in sections 5 and 9 keep their values; these override
 * only within the deals context.
 * ========================================================================== */

@media only screen and (max-width: 991.98px) {

    /* The plate's own frame. 10/10/14 was set when the countdown still needed room to
       breathe under the card; the countdown carries its own top spacing now, so the
       bottom 14 is doubling up. */
    .tc-deals .tc-deal {
        padding: 8px 8px 10px;
    }

    /* Swatches sit directly under the title and read as part of it - 8px of separation
       is a paragraph break where a line break is wanted. */
    .tc-deals .tc-card__swatches {
        margin-top: 4px;
    }

    /* The meta row is a single 20px line; 12px above it on a 152px-wide card is the
       single largest piece of slack in the stack. */
    .tc-deals .tc-card__meta {
        margin-top: 6px;
    }

    /* --tc-gap-meta-price is the seam between the meta row and the price block. The
       meta row above already carries its own margin, so this is the second half of the
       same gap. */
    .tc-deals .tc-card__pricing {
        padding-block-start: 4px;
    }
}

/* ============================================================================
 * 16. DEALS MEDIA IS SQUARE ON MOBILE.
 *
 * The plate measures 472px tall at 390, and 208 of that - 44% - is the image. The
 * card's aspect is already a token (`--tc-media-ratio`, section 2), so this is a
 * one-value change in the deals context rather than any new geometry: 3/4 portrait
 * becomes the 1/1 the theme already defines for square media at section 1453. On a
 * 152px-wide plate that is 208px -> 152px, and the image stays large enough to read
 * because the plate is narrow to begin with.
 *
 * Nothing structural moves. The countdown keeps its place inside the plate, the
 * min-height equalisation from section 8 is untouched so neighbouring plates still
 * share a baseline, and the swiper is not involved. Desktop keeps 3/4: the rule is
 * inside the mobile query and scoped to .tc-deals, so .tc-row, the PLP grid and the
 * ordinary product card are all out of its reach.
 *
 * The `@supports not (aspect-ratio)` fallback at section 2 still hard-codes 133.33%
 * for every card. Browsers without aspect-ratio therefore keep the tall deals media;
 * that path is years out of support and is left alone rather than forked here.
 * ========================================================================== */

@media only screen and (max-width: 991.98px) {

    .tc-deals .tc-deal .tc-card {
        --tc-media-ratio: 1 / 1;
    }
}
