/* leatherman_custom.css — Tier-1 project overrides for the Leatherman Tool Group demo.
 * Authored 2026-09-07 for the mobile pass (design-gate run 20260907-171458).
 * Loaded by Custom/LeathermanHeadInclude.cshtml, which is registered on the
 * CustomHeadInclude field of the Swift-v2_Master area item for area 3 (storefront)
 * and area 26 (Digital Assets Portal).
 *
 * SCOPE DISCIPLINE
 *   - Layout only. No colour, no typography, no token redefinition: the palette
 *     (accent #FFD737 fill-only with slate #393B42 on top) is settled and is NOT touched here.
 *   - Every rule below is inside a max-width media query, so 1440 is untouched,
 *     except block 4 which is a desktop-only guard for a slider that overhangs.
 *   - theme-default (Custom/default_custom.css) is NOT loaded on this solution —
 *     the CustomHeadInclude field was empty, so its shipped mobile pass never
 *     reached the browser. These blocks are the minimum delta, not a theme import.
 *
 * AUTHORING GUARD: never type the two-character comment-close sequence inside
 * comment prose — it closes the comment early and silently eats the next rule.
 * Each block opens with an inert marker rule so a CSSOM assert can prove it parsed.
 */

/* --- 1  Footer NColumnsFlex rows must wrap below md.
 *     Swift renders footer grid rows as `[data-swift-gridrow] > [data-swift-container].d-flex`
 *     with Bootstrap `.flex-fill` (flex: 1 1 auto !important) on every column and NO
 *     responsive wrap class, so at 390 all four footer columns stay on one line and
 *     stretch the document canvas to 714px. Measured with document.body.scrollWidth
 *     (documentElement is masked by overflow-x on body).
 *     `!important` is load-bearing: Bootstrap's utilities are !important and win
 *     against any specificity. --- */
[data-ltg-block="1"] { --ltg-block: 1; }
@media (max-width: 767.98px) {
  body > footer [data-swift-gridrow] > [data-swift-container],
  [data-swift-page-footer] [data-swift-gridrow] > [data-swift-container] {
    flex-wrap: wrap !important;
  }
  body > footer [data-swift-gridrow] > [data-swift-container] > [data-swift-gridcolumn],
  [data-swift-page-footer] [data-swift-gridrow] > [data-swift-container] > [data-swift-gridcolumn] {
    flex: 1 1 100% !important;
    min-width: 0 !important;
  }
}

/* --- 2  Header NColumnsFlex rows must wrap below md.
 *     Dynamicweb serves a DIFFERENT header content page per user-agent: a phone UA
 *     gets the 2-row offcanvas header (which already fits), a desktop UA at the same
 *     390px width gets the wide desktop header. The gate probes at 390 with the
 *     default desktop UA, so the wide header is the document it measures — on /dap
 *     that header row stretched the canvas to 624px.
 *     Columns get `flex: 0 1 auto`, NOT 100%: these header rows carry many empty
 *     spacer columns, and a 100% basis would turn each empty spacer into its own
 *     full-width line. Auto lets the populated columns wrap onto a second line and
 *     the empty ones collapse. --- */
[data-ltg-block="2"] { --ltg-block: 2; }
@media (max-width: 767.98px) {
  body > header [data-swift-gridrow] > [data-swift-container],
  [data-swift-page-header] [data-swift-gridrow] > [data-swift-container] {
    flex-wrap: wrap !important;
  }
  body > header [data-swift-gridrow] > [data-swift-container] > [data-swift-gridcolumn],
  [data-swift-page-header] [data-swift-gridrow] > [data-swift-container] > [data-swift-gridcolumn] {
    flex: 0 1 auto !important;
    min-width: 0 !important;
    max-width: 100% !important;
  }
}

/* --- 3  Poster (hero) height cap below md.
 *     Swift maps [data-swift-poster-height="4"] to --swift-poster-height: 85vh.
 *     At 390x844 that is 717.4px, which is exactly the design gate's
 *     image-band-height cap (0.85 * innerHeight = 717px) plus rounding, so the
 *     assert fails by a fraction of a pixel — and an 85vh hero fills the whole
 *     phone screen anyway. Re-point the custom property below md; desktop keeps 85vh.
 *     Overriding the property (not min-height) keeps Swift's own cascade intact. --- */
[data-ltg-block="3"] { --ltg-block: 3; }
@media (max-width: 767.98px) {
  [data-swift-poster-height="4"] { --swift-poster-height: 68vh; }
  [data-swift-poster-height="3"] { --swift-poster-height: 50vh; }
}

/* --- 4  Slider arrows must not overhang the viewport.
 *     `slider-nav-outside-expand` gives the swiffy nav buttons margin-inline: -3.5rem
 *     at >= 62rem, which parks them 56px outside the slider. Swift's page gutter is
 *     only 16px, so the right-hand arrow overhangs the document by 32px and the PDP
 *     carries a horizontal scrollbar at 1440. Arrow placement is left on
 *     outside-expand because every ProductComponentSlider on the solution is set
 *     that way (it is the intended look, not a stray value); the overhang is
 *     trimmed to the 16px page gutter instead, which is the widest value that
 *     still fits. Separate, unrelated config bug fixed alongside this: the PDP
 *     'Others also bought' slider (paragraph 20201, item 37) had SliderLayout
 *     8390 - not one of Swift's 2..6 - so it emitted the meaningless class
 *     slider-item-show8390 and fell back to one full-width slide per view. Set
 *     to 4, matching the other four sliders on the solution. --- */
[data-ltg-block="4"] { --ltg-block: 4; }
@media (min-width: 62rem) {
  .swiffy-slider.slider-nav-outside-expand .slider-nav { margin-left: -1rem !important; }
  .swiffy-slider.slider-nav-outside-expand .slider-nav.slider-nav-next { margin-right: -1rem !important; }
}

/* --- 5  Shop-landing intro band, shown ONLY on the shop root.
 *     /en-us/shop, every PLP and every PDP are all page 5862, so the intro
 *     paragraph (22303, grid row 17111) added to give the shop landing a heading
 *     renders on all three. Swift-v2_ProductListInfo already prints the group
 *     name + description on a PLP, so the band is suppressed wherever that
 *     component actually rendered (.item_swift-v2_productlistinfo present) and
 *     wherever the product list is absent altogether (no
 *     .item_swift-v2_productlistfacets = a PDP). Both arms are CONTENT-driven,
 *     not positional: they read what the page rendered, so a re-sorted row or a
 *     new category cannot silently re-target them.
 *     The :has() argument is anchored to an explicit direct-child chain so it
 *     matches only the band's own section, never every ancestor row, and the
 *     paragraph id is written in attribute form because #22303 is not valid CSS.
 *     RETIRE THIS BLOCK when the shop root gets its own page separate from the
 *     PLP/PDP page (5862), or when Swift-v2_ProductListInfo gains a fallback
 *     title/description for the un-grouped root; until then, deleting it puts a
 *     duplicate heading on every category and product page. --- */
[data-ltg-block="5"] { --ltg-block: 5; }
body[data-dw-itemtype="swift-v2_shop"] main:has(.item_swift-v2_productlistinfo) section[data-swift-gridrow]:has(> [data-swift-container] > [data-swift-gridcolumn][id="22303"]),
body[data-dw-itemtype="swift-v2_shop"] main:not(:has(.item_swift-v2_productlistfacets)) section[data-swift-gridrow]:has(> [data-swift-container] > [data-swift-gridcolumn][id="22303"]) {
  display: none !important;
}

/* --- 6  Collapse a footer navigation column that resolved to no links.
 *     The Digital Assets Portal footer (area 26) carries two Swift-v2_Navigation
 *     paragraphs rooted at the permission-gated Product assets / Digital assets
 *     pages. Both roots resolve to nothing, so the column paints an <h5>
 *     heading over an empty <ul> — a heading with nothing under it.
 *     MEASURED, not assumed: the tree is empty for an anonymous visitor AND for
 *     a signed-in dealer (verified as devin.park@cascadeoutdoor.demo) — neither
 *     root page has a menu-visible child, so this is not a permissions effect.
 *     The condition is read from what the navigation actually rendered (no <li>
 *     anywhere in the column), so the column returns by itself if a child page
 *     is ever added under either root — nothing to retire by hand.
 *     Flat single-level :not(:has()) on one selector: nested :has() is invalid
 *     and would drop the whole rule. Applies to every footer on the solution;
 *     a footer nav column with links is untouched. --- */
[data-ltg-block="6"] { --ltg-block: 6; }
footer [data-swift-gridcolumn][data-dw-itemtype="swift-v2_navigation"]:not(:has(li)) {
  display: none !important;
}


/* --- 7  PLP line view: lay the seven-field B2B card out as one row.
 *     The repeater is set GridLayoutDesktop/Mobile = 'list', which is the marine
 *     reference layout. Swift then emits each card as
 *     `.product.js-product.d-flex.flex-column.g-col-12`, i.e. a FULL-WIDTH COLUMN,
 *     so the seven component rows (image, name, SKU, teaser, stock, price, cart)
 *     stack vertically and the card reads as a 292px tall block with two thirds of
 *     the page empty beside it. That is the same defect the stock 'list' setting
 *     produced this afternoon; the layout below is what makes line view usable, and
 *     is the part of the marine design that does NOT come from configuration.
 *     Each child is its own `section[data-swift-gridrow]`, so the columns are
 *     selected by the component they CONTAIN, never by position -- re-ordering the
 *     rows in admin cannot silently re-target a width.
 *     `!important` on flex-direction is load-bearing: Bootstrap's .flex-column is
 *     itself !important and wins on any specificity without it. --- */
[data-ltg-block="7"] { --ltg-block: 7; }
@media (min-width: 992px) {
  .product.js-product {
    flex-direction: row !important;
    align-items: center;
    column-gap: 1.25rem;
    padding: .75rem .25rem;
    border-bottom: 1px solid rgba(var(--dw-color-foreground-rgb), .12);
  }
  /* Swift puts its top/bottom spacing on the row as padding utilities, which are
     !important. Seven stacked rows therefore add ~130px of dead height to a line
     that only needs one. Zero them on the card only. */
  .product.js-product > section[data-swift-gridrow] {
    margin: 0 !important; padding-top: 0 !important; padding-bottom: 0 !important;
  }
  .product.js-product > section[data-swift-gridrow] > [data-swift-container] {
    padding-top: 0 !important; padding-bottom: 0 !important;
  }

  .product.js-product > section[data-swift-gridrow]:has([data-dw-itemtype="swift-v2_productdefaultimage"]) {
    flex: 0 0 104px; max-width: 104px;
  }
  .product.js-product > section[data-swift-gridrow]:has([data-dw-itemtype="swift-v2_productheader"]) {
    flex: 0 0 210px; max-width: 210px;
  }
  .product.js-product > section[data-swift-gridrow]:has([data-dw-itemtype="swift-v2_productnumber"]) {
    flex: 0 0 140px; max-width: 140px;
  }
  .product.js-product > section[data-swift-gridrow]:has([data-dw-itemtype="swift-v2_productshortdescription"]) {
    flex: 1 1 auto; min-width: 0;
  }
  .product.js-product > section[data-swift-gridrow]:has([data-dw-itemtype="swift-v2_productstock"]) {
    flex: 0 0 120px; max-width: 120px;
  }
  .product.js-product > section[data-swift-gridrow]:has([data-dw-itemtype="swift-v2_productprice"]) {
    flex: 0 0 130px; max-width: 130px; text-align: right;
  }
  .product.js-product > section[data-swift-gridrow]:has([data-dw-itemtype="swift-v2_productaddtocart"]) {
    flex: 0 0 210px; max-width: 210px;
  }

  /* The teaser is the only elastic column, so it is the one that can push the row
     wide. Clamp it to two lines rather than letting a long description set the
     row height for every other card in the list. */
  .product.js-product [data-dw-itemtype="swift-v2_productshortdescription"] {
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .product.js-product [data-dw-itemtype="swift-v2_productdefaultimage"] img {
    max-height: 88px; width: auto; object-fit: contain;
  }
}

/* --- 8  PLP line view below lg: keep it a card, not a squashed row.
 *     Line view at 390 must not attempt seven columns. The card stays a column
 *     (Swift's own default) and only the image is capped, so the row does not
 *     become a full-bleed photograph on a phone. --- */
[data-ltg-block="8"] { --ltg-block: 8; }
@media (max-width: 991.98px) {
  .product.js-product { padding-bottom: 1rem; border-bottom: 1px solid rgba(var(--dw-color-foreground-rgb), .12); }
  .product.js-product [data-dw-itemtype="swift-v2_productdefaultimage"] img {
    max-height: 140px; width: auto; object-fit: contain;
  }
}


/* --- 9  PDP tab strip.
 *     Swift ships no tabs item type, so Custom/leatherman_custom.js promotes the
 *     Overview / Specifications / Documents / Parts bands into panes and inserts the
 *     strip below. This block styles that strip and nothing else: if the script does
 *     not run, none of these selectors match and every band renders stacked, which is
 *     a complete page.
 *     BRAND RULE, load-bearing: the accent is a FILL only. #FFD737 on white measures
 *     about 1.3:1, so an active-tab underline or active-tab text in yellow would be
 *     effectively invisible. The active tab is therefore a yellow fill carrying slate
 *     text, which measures 8:1 and is how leatherman.com uses the colour on its own
 *     solid buttons. Do not restyle this as a yellow rule or yellow label. --- */
[data-ltg-block="9"] { --ltg-block: 9; }
.ltg-tabs {
  display: flex; flex-wrap: wrap; gap: .25rem;
  border-bottom: 1px solid rgba(var(--dw-color-foreground-rgb), .14);
  margin: 0 0 1.5rem;
}
.ltg-tab {
  appearance: none; background: transparent; border: 0; cursor: pointer;
  padding: .6rem 1.05rem; font-size: .95rem; font-weight: 600; line-height: 1.2;
  color: rgba(var(--dw-color-foreground-rgb), .62);
  border-radius: 4px 4px 0 0;
}
.ltg-tab:hover { color: rgb(var(--dw-color-foreground-rgb)); background: rgba(var(--dw-color-foreground-rgb), .05); }
.ltg-tab.is-active { background: #FFD737; color: #393B42; }
.ltg-tab:focus-visible { outline: 2px solid #393B42; outline-offset: 2px; }

/* A pane keeps its own row spacing, which was tuned for a stacked band. Inside a tab
   the top gap reads as a gap under the tab strip, so it is removed on the pane only. */
.ltg-pane { padding-top: 0 !important; margin-top: 0 !important; }


/* --- 10  PDP hero: keep the gallery compact so the tab strip stays near the fold.
 *     Swift-v2_ProductMedia renders the thumbnail strip as `div.d-flex.flex-wrap.gap-3`
 *     holding `div.ratio` tiles that measure 128px wide with no cap, so five thumbs wrap
 *     onto a second row and push the tab strip down by a full tile plus its gap.
 *     Capping the tile keeps all five on one line. `flex-grow-0 flex-shrink-0` is already
 *     on the tile, so only the width needs setting.
 *     Scoped to the media component: the PLP card image is untouched. --- */
[data-ltg-block="10"] { --ltg-block: 10; }
@media (min-width: 992px) {
  [data-dw-itemtype="swift-v2_productmedia"] .d-flex.flex-wrap > .ratio {
    width: 84px !important;
  }
}

/* --- 11  Make the `hidden` attribute actually hide inside a tab pane.
 *     leatherman_custom.js hides the band's own heading (which would otherwise repeat the
 *     tab label directly beneath it) by setting `heading.hidden = true`. Swift renders that
 *     heading with Bootstrap's `.d-inline-block`, and every Bootstrap display utility is
 *     `!important`, so it beats the browser's default `[hidden] { display: none }` and the
 *     heading stays on screen. Scoped to panes, so nothing else on the site changes
 *     behaviour. --- */
[data-ltg-block="11"] { --ltg-block: 11; }
.ltg-has-tabs .ltg-pane [hidden] { display: none !important; }


/* --- 12  Sticky buy column on the PDP hero.
 *     The gallery column is much taller than the buy panel, so scrolling the images
 *     scrolls the price and the add-to-cart off screen. Marine solved this with a sticky
 *     buy column and it is the single biggest desktop comfort win on a product page.
 *     Anchored on the component selector, which is what pulls the buy panel (page 6864)
 *     into the PDP grid, so it cannot attach to any other two-column row.
 *     `align-self: start` is required: a stretched flex/grid item is already full height,
 *     and `position: sticky` on a full-height item has nothing to travel within. --- */
[data-ltg-block="12"] { --ltg-block: 12; }
@media (min-width: 992px) {
  [data-swift-gridcolumn]:has(> .item_swift-v2_productcomponentselector),
  [data-swift-gridcolumn][data-dw-itemtype="swift-v2_productcomponentselector"] {
    position: sticky; top: 1.5rem; align-self: start;
  }
}

/* --- 13  Specification pairs two-up.
 *     The field display group renders one label/value pair per line at full container
 *     width, so on a 1440 page the eye travels the better part of a thousand pixels from
 *     `Closed length:` to `4 in`. Two columns halve that and make the block read as a
 *     spec table rather than a list. Applies only inside the accordion body, so the
 *     Tools included panel (one long value, no pairs) is left alone by the single-item
 *     guard below. --- */
[data-ltg-block="13"] { --ltg-block: 13; }
@media (min-width: 768px) {
  .item_swift-v2_productfielddisplaygroups table, .item_swift-v2_productfielddisplaygroupsaccordion .accordion-body ul {
    columns: 2; column-gap: 3rem;
  }
  .item_swift-v2_productfielddisplaygroups table, .item_swift-v2_productfielddisplaygroupsaccordion .accordion-body ul > li {
    break-inside: avoid; display: flex; justify-content: space-between;
    gap: 1rem; padding: .3rem 0;
    border-bottom: 1px solid rgba(var(--dw-color-foreground-rgb), .10);
  }
  /* A group with a single long value (Tools included) must stay one column. */
  .item_swift-v2_productfielddisplaygroups table, .item_swift-v2_productfielddisplaygroupsaccordion .accordion-body ul:has(> li:only-child) {
    columns: 1;
  }
}


/* --- 14  Specification TABLE, always open.
 *     The accordion variant was replaced with `Swift-v2_ProductFieldDisplayGroups`
 *     (Layout = table), so the specs are on screen without a click. Note the field is
 *     `DisplayGroups`, NOT `FieldDisplayGroups` — the two components do not share a
 *     field name, which is why swapping one for the other silently renders nothing if
 *     the array is copied across under the old key.
 *     Two columns on a wide screen, label left and value right, so the eye does not
 *     travel the full container width between a label and its value. --- */
[data-ltg-block="14"] { --ltg-block: 14; }
.item_swift-v2_productfielddisplaygroups table { width: 100%; }
.item_swift-v2_productfielddisplaygroups table td,
.item_swift-v2_productfielddisplaygroups table th {
  padding: .4rem .25rem;
  border-bottom: 1px solid rgba(var(--dw-color-foreground-rgb), .10);
  vertical-align: top;
}
.item_swift-v2_productfielddisplaygroups table td:first-child,
.item_swift-v2_productfielddisplaygroups table th:first-child {
  color: rgba(var(--dw-color-foreground-rgb), .62); width: 42%;
}
.item_swift-v2_productfielddisplaygroups table td:last-child { font-weight: 600; }


/* --- 15  Specification groups as 2-up STACKED label-over-value cards.
 *     This follows marine's own design TARGET, not its live page. Its p26 review is
 *     explicit on both counts: "Spec groups = 2-up grid of stacked label-over-value pairs
 *     inside a card, collapsing to 1-up on mobile. Never a bordered key/value table with
 *     the value flushed right." Its live PDP does the flushed-right table and the review
 *     measures the cost: "~500-600px of void between a label and its value, no dot leader,
 *     no column rule."
 *     So: each row becomes a stacked pair (muted label above, ink value below), the pairs
 *     sit in a 2-up grid, and the whole group sits in a card with one hairline under its
 *     heading. One column below lg.
 *     A single-value group (Tools included) spans both tracks and reads as prose. --- */
[data-ltg-block="15"] { --ltg-block: 15; }
.item_swift-v2_productfielddisplaygroups .mw-75ch,
.item_swift-v2_productfielddisplaygroups [class*="mw-"] { max-width: none !important; }
.item_swift-v2_productfielddisplaygroups table {
  display: block !important; width: 100% !important;
}
.item_swift-v2_productfielddisplaygroups table thead { display: none; }

.item_swift-v2_productfielddisplaygroups table tbody {
  display: grid; grid-template-columns: 1fr; gap: .1rem;
  background: rgba(var(--dw-color-foreground-rgb), .035);
  border-radius: 6px; padding: 1rem 1.25rem;
}
@media (min-width: 768px) {
  .item_swift-v2_productfielddisplaygroups table tbody {
    grid-template-columns: 1fr 1fr; column-gap: 3rem;
  }
}
@media (min-width: 1200px) {
  .item_swift-v2_productfielddisplaygroups table tbody { grid-template-columns: 1fr 1fr 1fr; }
}
.item_swift-v2_productfielddisplaygroups table tbody tr {
  display: block; padding: .55rem 0;
  border-bottom: 1px solid rgba(var(--dw-color-foreground-rgb), .08);
}
.item_swift-v2_productfielddisplaygroups table tbody tr td {
  display: block; border: 0; padding: 0;
}
.item_swift-v2_productfielddisplaygroups table tbody tr td:first-child {
  font-size: .8rem; letter-spacing: .01em;
  color: rgba(var(--dw-color-foreground-rgb), .58);
}
.item_swift-v2_productfielddisplaygroups table tbody tr td:last-child {
  font-weight: 600; font-variant-numeric: tabular-nums;
}
/* A group with one long value spans the grid and reads as prose, not as a pair. */
.item_swift-v2_productfielddisplaygroups table tbody:has(tr:only-child) { grid-template-columns: 1fr; }
.item_swift-v2_productfielddisplaygroups table tbody tr:only-child { border-bottom: 0; }

/* --- 16  Section heading treatment and product-title type contrast.
 *     marine's review names its own worst typographic fault: "The product name is the 4th
 *     largest type on the page. H1 = 18px/700; h1 : body = 1.20", against a target of
 *     "steep type contrast (h1 about 2.2x body)". Swift gives the PDP title `.h5`, so the
 *     same fault is inherited here. The title is raised and the group heading gets the
 *     one hairline the review asks for, and nothing else. --- */
[data-ltg-block="16"] { --ltg-block: 16; }
@media (min-width: 992px) {
  [data-dw-itemtype="swift-v2_productheader"] h1,
  [data-dw-itemtype="swift-v2_productheader"] .h5 {
    font-size: 2.1rem !important; line-height: 1.15; font-weight: 700;
  }
}
.item_swift-v2_productfielddisplaygroups h3,
.item_swift-v2_productfielddisplaygroups h4 {
  padding-bottom: .4rem; margin-bottom: .9rem;
  border-bottom: 1px solid rgba(var(--dw-color-foreground-rgb), .18);
}


/* --- 17  Neutralise Swift's table cell paint, and keep the buy panel single column.
 *     Bootstrap's `.table` sets a per-cell background via `--bs-table-bg` plus a box-shadow
 *     `inset`, which paints a WHITE bar behind every cell. Against the tinted spec card
 *     that reads as a row of stray white blocks behind the values. Cells must be
 *     transparent for the card tint to show through.
 *     The buy panel is a half-width column, so the 2-up/3-up grid from block 15 puts three
 *     pairs in about 200px each and the labels wrap. Inside the component selector (the
 *     buy panel) the grid is forced back to one column regardless of viewport. --- */
[data-ltg-block="17"] { --ltg-block: 17; }
.item_swift-v2_productfielddisplaygroups table,
.item_swift-v2_productfielddisplaygroups table tbody,
.item_swift-v2_productfielddisplaygroups table tr,
.item_swift-v2_productfielddisplaygroups table td {
  background: transparent !important;
  box-shadow: none !important;
  --bs-table-bg: transparent;
  --bs-table-accent-bg: transparent;
}
.item_swift-v2_productfielddisplaygroups table tbody {
  background: rgba(var(--dw-color-foreground-rgb), .035) !important;
}
[data-dw-itemtype="swift-v2_productcomponentselector"] .item_swift-v2_productfielddisplaygroups table tbody,
.item_swift-v2_productcomponentselector .item_swift-v2_productfielddisplaygroups table tbody {
  grid-template-columns: 1fr !important;
}


/* --- 18  PDP to match leatherman.com's own product page.
 *     Measured from https://www.leatherman.com/.../products/wave in a real browser, not
 *     eyeballed. Their tokens: body 16px in rgb(57,59,66) — the same slate we already use —
 *     on white; H1 53.3px weight 500 in TradeGothic Bold Condensed, UPPERCASE; price
 *     26.2px; section headings 26.2px weight 500.
 *     We do not have TradeGothic, so the H1 uses a condensed stack that is present on the
 *     presenting machine (Arial Narrow) rather than pulling a webfont at demo time. The
 *     shape — very large, condensed, uppercase, tight tracking — is what carries the
 *     resemblance, not the exact face.
 *     Their hero sits on a light grey panel with a hairline border; ours sat on bare white,
 *     which is the single biggest difference at a glance. --- */
[data-ltg-block="18"] { --ltg-block: 18; }

/* Hero media panel */
@media (min-width: 992px) {
  [data-dw-itemtype="swift-v2_productmedia"] > div:first-child {
    background: #F1F1F1; border: 1px solid rgba(var(--dw-color-foreground-rgb), .10);
    border-radius: 2px; padding: 1.5rem;
  }
}

/* Product title: large, condensed, uppercase */
[data-dw-itemtype="swift-v2_productheader"] h1,
[data-dw-itemtype="swift-v2_productheader"] .h5 {
  font-family: "Oswald", "Arial Narrow", "Helvetica Neue Condensed", Arial, sans-serif;
  text-transform: uppercase; letter-spacing: .005em; font-weight: 700 !important;
  line-height: 1.03; margin-bottom: .35rem;
}
@media (min-width: 992px) {
  [data-dw-itemtype="swift-v2_productheader"] h1,
  [data-dw-itemtype="swift-v2_productheader"] .h5 { font-size: 3.1rem !important; }
}

/* Price, to their 26px weight 500 */
[data-dw-itemtype="swift-v2_productprice"] .fs-2,
[data-dw-itemtype="swift-v2_productprice"] .price {
  font-size: 1.62rem !important; font-weight: 500;
}

/* Buttons: uppercase and letterspaced, as on their page */
.item_swift-v2_productaddtocart .btn,
[data-dw-itemtype="swift-v2_productaddtocart"] .btn {
  text-transform: uppercase; letter-spacing: .08em; font-weight: 700;
  border-radius: 2px; padding-top: .8rem; padding-bottom: .8rem;
}

/* Section headings to their 26px weight 500 */
.item_swift-v2_productfielddisplaygroups > h3,
.item_swift-v2_productlongdescription h2,
.item_swift-v2_productlongdescription h3 {
  font-family: "Oswald", "Arial Narrow", "Helvetica Neue Condensed", Arial, sans-serif;
  text-transform: uppercase; letter-spacing: .01em; font-weight: 700;
}

/* --- 19  Attribute chips above the product title, and the tool-count badge.
 *     leatherman.com puts small dark chips ("Bestseller", "Customizable") directly above
 *     the H1, and a circular "18 TOOLS" badge over the top-left of the hero panel. Both are
 *     injected by leatherman_custom.js from data already on the page, so nothing is
 *     asserted that the PIM does not hold. Styling only lives here. --- */
[data-ltg-block="19"] { --ltg-block: 19; }
.ltg-chips { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .55rem; }
.ltg-chip {
  display: inline-block; background: #393B42; color: #fff;
  font-size: .72rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  padding: .3rem .55rem; border-radius: 2px;
}
.ltg-chip--accent { background: #FFD737; color: #393B42; }

.ltg-toolbadge {
  position: absolute; top: 1.6rem; left: 1.6rem; z-index: 3;
  width: 84px; height: 84px; border-radius: 50%;
  border: 1.5px solid rgba(var(--dw-color-foreground-rgb), .55);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  line-height: 1; background: transparent; pointer-events: none;
}
.ltg-toolbadge b { font-size: 1.65rem; font-weight: 700; }
.ltg-toolbadge span { font-size: .62rem; letter-spacing: .12em; text-transform: uppercase; margin-top: .18rem; }
@media (max-width: 991.98px) { .ltg-toolbadge { width: 62px; height: 62px; top: .6rem; left: .6rem; } .ltg-toolbadge b { font-size: 1.2rem; } }


/* --- 20  Hero panel: one background, and no dead grey below the image.
 *     Two faults were visible once the grey panel landed. First, Swift's aspect-ratio box
 *     paints its own white background inside the panel, so the image sat in a white card
 *     inside a grey card. Second, the media column is a stretched grid item, so the panel
 *     grew to match the taller buy column and left a large grey void under the image —
 *     the same "dead space under the gallery" that marine's own review measures at
 *     246-421px on its pages.
 *     Inner boxes are made transparent so the panel is the only ground, and the media
 *     column is released from the stretch so the panel ends where the content ends. --- */
[data-ltg-block="20"] { --ltg-block: 20; }
@media (min-width: 992px) {
  [data-dw-itemtype="swift-v2_productmedia"],
  [data-swift-gridcolumn]:has(> * > [data-dw-itemtype="swift-v2_productmedia"]),
  [data-swift-gridcolumn][data-dw-itemtype="swift-v2_productmedia"] {
    align-self: start;
  }
  [data-dw-itemtype="swift-v2_productmedia"] .ratio,
  [data-dw-itemtype="swift-v2_productmedia"] .ratio > *,
  [data-dw-itemtype="swift-v2_productmedia"] .h-100 {
    background: transparent !important;
  }
  [data-dw-itemtype="swift-v2_productmedia"] > div:first-child { height: auto !important; }
  /* The main image keeps its own proportions inside the panel rather than filling it. */
  [data-dw-itemtype="swift-v2_productmedia"] .carousel-item img,
  [data-dw-itemtype="swift-v2_productmedia"] .swiffy-slider img { object-fit: contain; }
}


/* --- 21  Re-assert the hero panel ground.
 *     Block 20 blanks inner boxes with `.h-100`, and the panel element itself carries that
 *     class, so it lost its own grey. The panel background is re-declared after that rule
 *     with matching specificity. Kept as its own block rather than edited into 18 so the
 *     ordering dependency is visible: this MUST come after block 20. --- */
[data-ltg-block="21"] { --ltg-block: 21; }
@media (min-width: 992px) {
  [data-dw-itemtype="swift-v2_productmedia"] > div:first-child,
  [data-dw-itemtype="swift-v2_productmedia"] > div:first-child.h-100 {
    background: #F1F1F1 !important;
  }
}


/* --- 22  PLP line rows as designed cards.
 *     leatherman.com's own listing is a consumer GRID: 309x583 cards, white ground, 1px
 *     rgb(225,226,227) hairline, badge chips, hover image swap. That grid is the wrong
 *     shape for a dealer ordering screen — a buyer scanning for a SKU and a price wants a
 *     line, not a poster — so the LAYOUT stays a line and their VISUAL LANGUAGE is what is
 *     borrowed: the hairline card, the light image tile, the chip, the uppercase condensed
 *     product name.
 *     Row rhythm: each product is its own card with breathing room between, rather than
 *     rows separated by a rule, so the list reads as a set of objects instead of a table. */
[data-ltg-block="22"] { --ltg-block: 22; }
@media (min-width: 992px) {
  .product.js-product {
    border: 1px solid #E1E2E3;
    border-bottom: 1px solid #E1E2E3;
    border-radius: 3px;
    background: #fff;
    margin-bottom: .85rem;
    padding: 1rem 1.25rem;
    transition: box-shadow .15s ease, border-color .15s ease, transform .15s ease;
    column-gap: 1.5rem;
  }
  .product.js-product:hover {
    border-color: #C9CBCE;
    box-shadow: 0 2px 10px rgba(57, 59, 66, .09);
  }
  /* Image sits on the same light tile as the PDP hero, so the two pages share a ground. */
  .product.js-product > section[data-swift-gridrow]:has([data-dw-itemtype="swift-v2_productdefaultimage"]) {
    flex: 0 0 116px; max-width: 116px;
    background: #F1F1F1; border-radius: 3px; padding: .45rem !important;
  }
  .product.js-product [data-dw-itemtype="swift-v2_productdefaultimage"] img {
    max-height: 76px; width: auto; object-fit: contain; mix-blend-mode: multiply;
  }
  /* Product name in the brand voice, matching the PDP title treatment. */
  .product.js-product [data-dw-itemtype="swift-v2_productheader"] * {
    font-family: "Oswald", "Arial Narrow", "Helvetica Neue Condensed", Arial, sans-serif;
    text-transform: uppercase; letter-spacing: .01em; font-weight: 700;
    font-size: 1.06rem; line-height: 1.15;
  }
  /* SKU as a quiet monospaced-feeling identifier, which is what a dealer scans for. */
  .product.js-product [data-dw-itemtype="swift-v2_productnumber"] {
    font-size: .82rem; letter-spacing: .04em;
    color: rgba(var(--dw-color-foreground-rgb), .55);
  }
  /* Price is the second thing scanned after the SKU, so it gets weight and tabular figures. */
  .product.js-product [data-dw-itemtype="swift-v2_productprice"] * {
    font-size: 1.18rem !important; font-weight: 700; font-variant-numeric: tabular-nums;
  }
  .product.js-product [data-dw-itemtype="swift-v2_productstock"] { font-size: .85rem; }
}

/* --- 23  PLP chrome: heading, filter bar and list rhythm.
 *     The filter bar sat as bare dropdowns directly on the page with nothing separating it
 *     from the list. It now reads as a toolbar: a light ground, a hairline under it, and
 *     space before the first card. Purely presentational; no facet behaviour changes. --- */
[data-ltg-block="23"] { --ltg-block: 23; }
@media (min-width: 992px) {
  [data-dw-itemtype="swift-v2_productlistfacets"] {
    background: #FAFAFA;
    border: 1px solid #E1E2E3;
    border-radius: 3px;
    padding: .55rem .9rem !important;
    margin-bottom: 1.4rem;
  }
  [data-dw-itemtype="swift-v2_productlistinfo"] h1 {
    font-family: "Oswald", "Arial Narrow", "Helvetica Neue Condensed", Arial, sans-serif;
    text-transform: uppercase; letter-spacing: .01em; font-weight: 700;
    font-size: 2.5rem; line-height: 1.05;
  }
}


/* --- 24  PLP row height and the empty cart column.
 *     Two things made the line taller and emptier than it needed to be. The image tile is a
 *     flex child and stretched to the card height, so a 76px image sat in a 150px tile. And
 *     for an ANONYMOUS visitor the add-to-cart component renders nothing (area setting
 *     `AnonymousUsers = cart`), leaving a 210px reserved column of white at the end of every
 *     row. The column is collapsed when it contains no control, so it costs nothing signed
 *     out and returns to full width signed in — the state the dealer actually sees. --- */
[data-ltg-block="24"] { --ltg-block: 24; }
@media (min-width: 992px) {
  .product.js-product > section[data-swift-gridrow]:has([data-dw-itemtype="swift-v2_productdefaultimage"]) {
    align-self: center; max-height: 96px;
  }
  .product.js-product > section[data-swift-gridrow]:has([data-dw-itemtype="swift-v2_productaddtocart"]):not(:has(button)):not(:has(input)) {
    flex: 0 0 0 !important; max-width: 0 !important; padding: 0 !important; overflow: hidden;
  }
  .product.js-product { align-items: center; }
}


/* --- 25  Alignment pass, measured.
 *     PDP: the hero panel and the buy column both start at y=232, but the attribute chips
 *     started at y=280 — 48px low — because the buy panel's first row carried top spacing.
 *     That spacing is now 0 on the row, and this block removes the residual padding Swift
 *     puts on the row container, so the chips sit on the same line as the top of the image
 *     panel.
 *
 *     PLP: column LEFT edges were already exact — 68 / 201 / 435 / 599 / 1105 / 1249 on
 *     every row, and title, filter bar and cards all flush at 16. The fault was VERTICAL:
 *     tops spread 31-32px within each row because `align-items: center` centres a
 *     two-line teaser against single-line cells, so no two first lines shared a baseline.
 *     The row is switched to top alignment with one shared inset, so every first line
 *     starts on the same y. The image keeps its own centring. --- */
[data-ltg-block="25"] { --ltg-block: 25; }

@media (min-width: 992px) {
  /* PDP — chips flush with the top of the hero panel */
  [data-dw-itemtype="swift-v2_productcomponentselector"] section[data-swift-gridrow]:first-of-type {
    padding-top: 0 !important; margin-top: 0 !important;
  }
  [data-dw-itemtype="swift-v2_productcomponentselector"] section[data-swift-gridrow]:first-of-type
    > [data-swift-container] { padding-top: 0 !important; }

  /* PLP — one baseline across the row */
  .product.js-product { align-items: start; }
  .product.js-product > section[data-swift-gridrow] { padding-top: .1rem !important; }
  .product.js-product > section[data-swift-gridrow]:has([data-dw-itemtype="swift-v2_productdefaultimage"]) {
    align-self: center; padding-top: .45rem !important;
  }
}


/* --- 26  Overview pane: styled prose with an ambient image.
 *     The Overview tab was a single column of unstyled body text at full band width, which
 *     is the worst measure for reading. The band is now `2Columns_8-4` — marine's own
 *     "prose at 8/12" rule — with the long description left and a lifestyle image right.
 *     The first paragraph is treated as a lead: larger, lighter, with a shorter measure, so
 *     the block has a way in rather than four identical paragraphs.
 *     The image is one of the Leatherman lifestyle assets already on the solution (the same
 *     library the Digital Assets Portal serves), so nothing new is introduced and nothing is
 *     hotlinked. --- */
[data-ltg-block="26"] { --ltg-block: 26; }
.item_swift-v2_productlongdescription p { max-width: 68ch; line-height: 1.62; margin-bottom: .95rem; }
@media (min-width: 992px) {
  .item_swift-v2_productlongdescription p:first-of-type {
    font-size: 1.18rem; line-height: 1.5;
    color: rgba(var(--dw-color-foreground-rgb), .92);
  }
}
.item_swift-v2_productlongdescription p:last-of-type em {
  color: rgba(var(--dw-color-foreground-rgb), .62);
}
/* Ambient image: fill its column, matched corner radius, never taller than the prose. */
#22318, [id="22318"] { align-self: start; }
[id="22318"] img, .ltg-pane [data-dw-itemtype="swift-v2_image"] img {
  width: 100%; height: auto; border-radius: 3px;
  max-height: 420px; object-fit: cover;
}


/* --- 27  A grid gap can never be wider than the grid.
 *     MEASURED REGRESSION, found by the design gate and bisected in the browser: the
 *     Overview band (row 18907) was set to gapX 5, which Swift renders as a 48px
 *     column-gap on a TWELVE-track `.grid`. Eleven gaps is 528px. Inside the 358px
 *     content box of a 390px viewport the tracks collapse to 0px and every
 *     `g-col-12` child lays out at 528px wide, so the document canvas stretched to
 *     544px and `overflowX` failed at 154px. The named offender was the off-canvas
 *     nav, which is a red herring: a fixed 390px box parked at the right edge of a
 *     canvas that something else had already widened.
 *     Nothing on a phone needs a 48px column gap, because every column is
 *     `g-col-12` there and there is no second column to separate. The gap is
 *     clamped below lg so that 11 gaps can never exceed the container, whatever a
 *     row's gapX is set to in admin. Desktop keeps the designed 48px.
 *     This is deliberately GENERAL rather than a fix to row 18907: the same
 *     configuration on any future row would ship the same broken canvas, and
 *     `save_grid_rows` is not reliable on this host (see the pass-1 carryover), so
 *     the data-side value cannot be relied on to stay corrected. --- */
[data-ltg-block="27"] { --ltg-block: 27; }
@media (max-width: 991.98px) {
  [data-swift-container].grid,
  main .grid[data-dw-container-width] {
    column-gap: 1rem !important;
  }
}


/* PDP-OVERVIEW-ALIGN start
 * --- 29  Overview pane: square off the bottom of the two-column band.
 *
 *     REPORTED with the empty Overview panel: "missing content, design and ALIGNMENT"
 *     (demo owner, 2026-09-08). With the copy and the image both fixed, one alignment
 *     residue is left and it is measurable, not a matter of taste.
 *
 *     MEASURED at 1440 on the Wave+ PDP after the content fix
 *     (scripts/o1_overview_probe.mjs, tag "after"):
 *       prose column   923 x 455
 *       image          437 x 280   (block 26: height auto, max-height 420)
 *     so the right half of the band ends 175px above the left half and the row bottom
 *     is ragged. On a short description the imbalance simply runs the other way: the
 *     Nylon Sheath PDP measures prose 100px against the same 280px image.
 *
 *     FIX: on lg and up, let the image stretch to the height the row already has and
 *     crop with object-fit: cover, so the two columns finish on the same line whichever
 *     one is taller. Capped at 520px so a future long description cannot turn an ambient
 *     image into the main event, and floored at 240px so a one-line description does not
 *     squash it to a strip.
 *
 *     SCOPE: the id of the Overview image paragraph plus the tab-pane class, so this
 *     cannot reach the product gallery, the PLP or any other swift-v2_image on the site.
 *     Below lg the columns stack and there is no second column to align to, so the
 *     block is desktop-only and block 26's `height: auto` still rules the phone. */
[data-ltg-block="29"] { --ltg-block: 29; }
@media (min-width: 992px) {
  /* beats block 26's `align-self: start` on the same element */
  .ltg-pane [id="22318"] { align-self: stretch; }
  .ltg-pane [id="22318"] figure { height: 100%; margin: 0; }
  .ltg-pane [id="22318"] img {
    height: 100%;                 /* fill the stretched column */
    min-height: 240px;
    max-height: 520px;            /* vs block 26's 420px cap */
    object-fit: cover;
    object-position: center center;
  }
}
/* PDP-OVERVIEW-ALIGN end */


/* PLP-THUMB-ALIGN start
 * --- 28  PLP card thumbnail: the image must fill the grey tile, not overflow it.
 *
 *     REPORTED: "on the PLP product image thumbnails are misaligned within their grey
 *     background" (demo owner, 2026-09-08).
 *
 *     MEASURED at 1440 on /en-us/shop, /en-us/shop/multi-tools and /en-us/shop/knives.
 *     The numbers are IDENTICAL on every card, so this is geometry, not data: the source
 *     assets are all 360x360, consistent, and nothing is wrong with the images.
 *
 *       grey tile  section[data-dw-itemtype="swift-v2_rowflex"]   116 x 96, padding 7.2px
 *                  (block 22 sets 116px + F1F1F1 + .45rem, block 24 the 96px cap)
 *                  -- interior therefore 101.6 x 81.6
 *       img box    120 x 76, at +7.2 x and +29.2 y from the tile origin
 *       gaps       L 7.2   R -11.2   T 29.2   B -9.2   centre offset +9.2 x, +19.2 y
 *
 *     Three causes stack, and all three are markup, not assets:
 *
 *       a) Swift-v2_ProductDefaultImage.cshtml writes an inline `style="width: 120px"`
 *          onto the figure whenever the paragraph Width field is not `auto`. 120 is wider
 *          than the 101.6px tile interior, and an inline style outranks block 22's
 *          `width: auto`, so the image column overflows the tile by 18.4px.
 *
 *       b) the figure wraps the image in Bootstrap `.ratio` at the default 1:1, so the
 *          image box is 120px TALL while the tile offers only 81.6px. The inner
 *          `align-items: center` then letterboxes the 76px-capped image 22px down inside
 *          that box, and those 22px are the whole of the "sitting low" look.
 *
 *       c) block 22's `max-height: 76px` leaves 5.6px of the tile interior unused even
 *          after (a) and (b), and its companion `width: auto` never applied at all --
 *          swift.css `.w-100 { width: 100% !important }` outranks it. A declaration that
 *          greps as present is not a declaration that wins the cascade.
 *
 *     FIX: size the figure to exactly the tile interior and let the image fill it with
 *     object-fit: contain, so the artwork is centred in the grey on BOTH axes and cannot
 *     cross a tile edge whatever aspect ratio a future source asset turns out to have.
 *     `!important` appears three times and each one beats one named opponent, called out
 *     inline below; none of it is defensive noise.
 *
 *     SCOPE: every selector hangs off [data-dw-itemtype="swift-v2_productlistitemrepeater"],
 *     the product-list app's own grid column. Measured 2026-09-08 by
 *     scripts/t3_pdp_scope_check.mjs: that root matches 1 element on /en-us/shop and on
 *     /en-us/shop/knives, and ZERO on the Wave and Charge TTi PDPs, which carry no
 *     .product.js-product at all. This block therefore cannot reach a product page.
 *     Desktop only: blocks 8 and 22 make the grey tile a min-width:992 treatment, and the
 *     390 measurement finds no painted panel behind the card image at all.
 */
[data-ltg-block="28"] { --ltg-block: 28; }
@media (min-width: 992px) {
  /* The tile interior, derived from block 22 (padding .45rem) and block 24
     (max-height 96px) rather than restated, so the two cannot silently drift. */
  [data-dw-itemtype="swift-v2_productlistitemrepeater"] .product.js-product {
    --ltg-thumb-tile-h: 96px;
    --ltg-thumb-tile-pad: .45rem;
  }

  /* (a) A flex item's min-width defaults to auto, so the image column refused to shrink
         to the tile and pushed its own 120px content width back out. */
  [data-dw-itemtype="swift-v2_productlistitemrepeater"] .product.js-product
    > section[data-swift-gridrow]:has([data-dw-itemtype="swift-v2_productdefaultimage"])
    [data-swift-gridcolumn] {
    min-width: 0;
    width: 100%;
    justify-content: center;
  }

  /* (a) width beats the template's inline width on the figure.
     (b) a definite height gives .ratio below something to be 100% of.
         margin zeroes the UA figure margin-bottom, another 16px inside the tile. */
  [data-dw-itemtype="swift-v2_productlistitemrepeater"] .product.js-product
    figure.item_swift-v2_productdefaultimage {
    width: 100% !important;                                                             /* vs the inline width on figure */
    max-width: 100%;
    height: calc(var(--ltg-thumb-tile-h) - 2 * var(--ltg-thumb-tile-pad)) !important;    /* vs swift.css .h-100 */
    margin: 0 !important;
  }

  /* (b) --bs-aspect-ratio is written INLINE on div.ratio by the template, so only
         !important can reach it. 0% collapses the Bootstrap ::before spacer and the box
         takes its height from the figure instead of from a 1:1 ratio. */
  [data-dw-itemtype="swift-v2_productlistitemrepeater"] .product.js-product
    figure.item_swift-v2_productdefaultimage .ratio {
    --bs-aspect-ratio: 0% !important;
    height: 100% !important;
  }

  /* (c) fill the tile interior exactly and centre the artwork within it. */
  [data-dw-itemtype="swift-v2_productlistitemrepeater"] .product.js-product
    [data-dw-itemtype="swift-v2_productdefaultimage"] img {
    width: 100% !important;                 /* vs swift.css .w-100 */
    height: 100% !important;                /* vs swift.css .h-100 */
    max-height: none !important;            /* vs block 22's 76px cap */
    object-fit: contain;
    object-position: center center;
  }
}
/* PLP-THUMB-ALIGN end */

/* PDP-SIMILAR-ALIGN start
 * --- 30  "Similar products" slider: bottom-anchor the buy block so the ADD TO CART
 *         buttons sit on one baseline whatever the description length.
 *
 *     REPORTED: "Similar products are not aligned with add to cart button"
 *     (demo owner, 2026-09-08, with a screenshot of the Wave+ PDP).
 *
 *     THE CARD. Each slide is a flex COLUMN whose children are bare <section>s, one per
 *     paragraph of the "Product List Card" component page (5861):
 *       li.d-flex.flex-column
 *         > a.stretched-link
 *         > section  -> item_swift-v2_productdefaultimage
 *         > section  -> item_swift-v2_productheader            (name)
 *         > section  -> item_swift-v2_productnumber            (sku)
 *         > section  -> item_swift-v2_productshortdescription  (2-4 lines, VARIABLE)
 *         > section  -> item_swift-v2_productstock         \
 *         > section  -> item_swift-v2_productprice          }  the buy block
 *         > section  -> item_swift-v2_productaddtocart     /
 *
 *     MEASURED signed in (scripts/y1_similar_align_probe.mjs, tag "before"), offsets are
 *     card-relative, spread = max - min across the six cards:
 *
 *       1600   card 348px   btnTop spread 24.0   desc heights 96/72/72/96/72/72
 *       1440   card 308px   btnTop spread  0.0   desc heights 96/96/96/96/96/96
 *       1200   card 218px   btnTop spread 48.0   desc heights 168/120/120/120/120/120
 *        390   card 214px   btnTop spread 48.0   desc heights 168/144/120/144/120/120
 *
 *     ROOT CAUSE, and note what it is NOT. The cards are ALREADY equal height at every
 *     width measured (cardH spread 0.0 everywhere) -- the slider stretches them. What is
 *     wrong is the distribution INSIDE that equal height: the column is packed from the
 *     top, so the slack lands under the button and the buy block starts wherever the
 *     description happened to stop. A 3-line description therefore drops its button
 *     exactly one line-height (24px) below a 2-line neighbour's -- the "roughly 30px"
 *     in the report. 1440 measures clean only by luck: at a 308px card every one of the
 *     six descriptions wraps to four lines. This is a layout defect at all widths, not
 *     a mobile one, which is why the fix is not inside a media query.
 *
 *     FIX: give the FIRST section of the buy block `margin-top: auto`. In a flex column
 *     an auto margin eats all the free space, so the buy block is pinned to the bottom
 *     of the card and the slack moves to where it belongs -- under the description.
 *     No truncation: the descriptions stay whole, which is why nothing here clamps.
 *
 *     Bottom-anchoring rather than top-aligning the block is deliberate and is the
 *     stronger of the two: discounts are live, so a card can show a struck-through
 *     price and carry a two-line price block. Anchored from the bottom the button stays
 *     on the baseline and only that card's stock line rides 24px lower; anchored from
 *     the top the extra price line would shove that card's button off the baseline --
 *     i.e. it would reintroduce exactly the reported defect on any discounted card.
 *
 *     Three rules, not one, because the buy block does not always begin with stock. The
 *     :not() chain picks whichever of stock / price / add-to-cart is actually the first
 *     one present, so only ONE section ever gets the auto margin (two auto margins would
 *     SPLIT the free space instead of collapsing it to the bottom).
 *
 *     ANONYMOUS IS A DELIBERATE NO-OP. Area setting AnonymousUsers=cart-price plus the
 *     LTG-ANON-STOCK guard mean an anonymous card carries no stock, no price and no
 *     add-to-cart at all -- so none of the three selectors match, nothing is anchored,
 *     and the anonymous card renders byte-identical to before. Measured: anonymous
 *     btnTop spread is null (no buttons) and cardH spread 0.0 before and after.
 *
 *     SCOPE: #ComponentSliderProducts_20201 -- the AJAX target div of paragraph 20201,
 *     the "Similar products" slider, and nothing else. The card component (page 5861) is
 *     shared with the PLP, and the PLP card alignment was fixed this morning in block 28,
 *     so this block must NOT be able to reach it: an id selector on the slider's own
 *     container is the tightest scope available and cannot match on a product list page.
 *     The only other live product slider, paragraph 22315 ("Parts & accessories for the
 *     Wave+", same page), measures 0px tall -- it renders nothing today -- so it is left
 *     untouched; if it is ever populated, add its id to the selector list below and it
 *     inherits the same fix. The remaining four ProductComponentSlider paragraphs in the
 *     database (20539/20667/21183/21540) hang off areas 11/12/13/25, which no longer
 *     exist.
 */
[data-ltg-block="30"] { --ltg-block: 30; }

/* The slide is already `d-flex flex-column` from the template and already stretched by
   the slider; both are restated as no-ops so the auto margin below cannot be quietly
   disarmed by a future template change that drops one of them. */
#ComponentSliderProducts_20201 .slider-container > * {
  display: flex;
  flex-direction: column;
  align-self: stretch;
}

/* a) the normal card: the buy block starts at the stock line. */
#ComponentSliderProducts_20201 .slider-container > *
  > section:has(.item_swift-v2_productstock) {
  margin-top: auto;
}

/* b) no stock line on this product -> the price is the first buy-block section. */
#ComponentSliderProducts_20201 .slider-container > *:not(:has(.item_swift-v2_productstock))
  > section:has(.item_swift-v2_productprice) {
  margin-top: auto;
}

/* c) neither stock nor price -> anchor on the add-to-cart itself. */
#ComponentSliderProducts_20201 .slider-container
  > *:not(:has(.item_swift-v2_productstock)):not(:has(.item_swift-v2_productprice))
  > section:has(.item_swift-v2_productaddtocart) {
  margin-top: auto;
}
/* PDP-SIMILAR-ALIGN end */
