/* default_custom.css — the CSS that makes stock Swift look great.
 * theme-default: the starting point of every customer re-skin — not a brand.
 * Tier 1 of the re-skin escalation ladder; customers overwrite the tokens below.
 * Loaded via DefaultHeadInclude.cshtml after swift.css and the Style asset sheets.
 * Pitfall guards applied throughout:
 *   P4:  [data-dw-button] background always uses :not() chain for outline/ghost variants
 *   P5:  page-footer paint scoped to body > footer / [data-swift-page-footer]
 *   P10: nav rules scoped via data-attributes / .megamenu-wrapper, not bare nav selectors
 *
 * Authoring guards (folded from gate findings — read before extending this sheet):
 *   G1 (Foundry #50): NEVER type the two-character comment-close sequence inside
 *        comment PROSE. It closes the banner early; the orphaned prose becomes a
 *        selector prelude that swallows the next real rule, and every byte-level
 *        check still reports the deploy healthy. Spell it out in words instead.
 *   G2 (Foundry #50): every numbered block below opens with an inert marker rule
 *        [data-td-block="<n>"]. Nothing in a Swift document carries that attribute,
 *        so it paints nothing — it exists so a CSSOM assert can prove the block
 *        PARSED and reached document.styleSheets, not merely that its bytes landed.
 *   G3 (Foundry #65): Bootstrap/Swift utilities (.flex-fill, .d-flex, .order-*) are
 *        declared !important. Any rule overriding a platform-managed flex column must
 *        carry !important in EVERY tier it appears in, or the tiers silently disagree
 *        across a band of viewport widths. Grep the rendered column for utility
 *        classes before authoring a flex override.
 *   G4: never write an id selector that starts with a digit (a DW page id is numeric
 *        and `#8653` is invalid CSS) — scope by attribute instead, see block #18.
 */

/* === Neutral tokens (render-critical; also in DefaultHeadInclude <style> for cache resilience).
 * A customer re-skin overrides these first. === */
:root {
  --td-ink:          #1F2933;
  --td-ink-soft:     #323F4B;
  --td-slate:        #3E4C59;
  --td-slate-mid:    #52606D;
  --td-slate-light:  #CBD2D9;
  --td-grey-light:   #F5F7FA;
  --td-hairline:     #E4E7EB;
  --td-white:        #FFFFFF;
  --td-accent:       #3E4C59;                 /* the interaction accent — restrained slate */
  --td-accent-soft:  rgba(62, 76, 89, .07);
  /* Shape + elevation system (fresh-pass structural defaults). Foreground-tinted
   * so a customer re-skin that only swaps the palette keeps a coherent depth model. */
  --td-radius:       12px;                     /* surfaces: cards, media figures */
  --td-radius-sm:    8px;                      /* small chips, thumbnails, inputs */
  --td-shadow-soft:  0 1px 2px rgba(31, 41, 51, .06), 0 12px 28px -14px rgba(31, 41, 51, .22);
}

/* === Retiring a token: ALIAS it, never delete it (Foundry #34) ===
 * A token is bound in more places than the sheet you are editing. Two survive a
 * delete every time:
 *   (1) DB-authored content — a paragraph can carry an inline
 *       style="background-color:var(--td-legacy)" that no stylesheet edit reaches
 *       (content is not ours to rewrite; SPEC-06 keeps this layer disk-only), and
 *   (2) the render-critical copy inlined in DefaultHeadInclude.cshtml for cache
 *       resilience — this layer ships exactly such a block.
 * Delete the token from this sheet and the head-include copy becomes its ONLY
 * definition, so every consumer silently falls back to the retired value.
 * Rule: re-point a retired token at its successor and mark it !important —
 * custom-property !important is legal and beats a same-specificity :root declared
 * in a template <style> regardless of source order:
 *     :root { --td-legacy: var(--td-accent) !important; }
 * Drop the alias only once a scan proves zero remaining references in content AND
 * in every head include. theme-default currently retires no tokens; the convention
 * is here so a re-skin that renames one does it safely. */

/* === Brand accent slot (convention) ===
 * --dw-color-accent (+ -rgb / -contrast) is the first-class home for a brand's
 * accent color, set per scheme in the brand's ColorSchemes CSS (re-skin ladder
 * step 2) — never by hijacking --dw-color-button-primary. Every consumer below
 * falls back to button-primary, so the neutral default ships no values and
 * nothing changes until a brand opts in. Distinct from --td-accent above,
 * which is the theme's static interaction tint. */
.text-accent {
  color: var(--dw-color-accent, var(--dw-color-button-primary)) !important;
}
.bg-accent {
  background-color: var(--dw-color-accent, var(--dw-color-button-primary)) !important;
  color: var(--dw-color-accent-contrast, var(--dw-color-button-primary-contrast)) !important;
}

/* === Eyebrow / section kicker — the tracked-caps voice as one utility ===
 * The pattern every strong brand site repeats above its section headings.
 * Previously existed only ad hoc (PDP spec-group labels below; poster scopes
 * in brand themes). Apply to the kicker paragraph above a section h2.
 * Foundry #35: authored as a <p> under a light scheme, this utility (0,1,0) lost to
 * the secondary-text softener further down (0,1,2) and rendered as muted body ink on
 * every light band while the dark band rendered correctly — the classic "works on one
 * scheme only" report. Fixed at the softener, which now excludes .dw-eyebrow, so the
 * kicker is safe in ANY element (a <p> from the Swift heading Title field included)
 * and no author has to know to reach for a <span>. */
.dw-eyebrow {
  display: block;
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .3em;
  text-transform: uppercase;
  line-height: 1.2;
  color: var(--dw-color-accent, var(--dw-color-button-primary));
}

/* === Primary button hover — filled variant only (P4) === */
[data-dw-button]:not([data-dw-button="outline"]):not([data-dw-button="ghost"]):not([data-dw-button="secondary"]):hover {
  background-color: var(--td-ink-soft);
  border-color: var(--td-ink-soft);
  transition: background-color 0.1s ease, border-color 0.1s ease;
}

/* === Outline/ghost button hover — soft neutral tint (P4 compliant) === */
[data-dw-button="outline"]:hover,
[data-dw-button="ghost"]:hover {
  background-color: var(--td-accent-soft);
  border-color: var(--td-accent);
  color: var(--td-accent);
  transition: background-color 0.1s ease, border-color 0.1s ease, color 0.1s ease;
}

/* === Page-level site footer — scoped to landmark only (P5) === */
body > footer,
[data-swift-page-footer] {
  background-color: var(--td-ink);
  color: var(--td-slate-light);
}

body > footer a,
[data-swift-page-footer] a {
  color: var(--td-slate-light);
  text-decoration: none;
}

body > footer a:hover,
[data-swift-page-footer] a:hover {
  color: var(--td-white);
}

/* === Site header / top navigation — scoped via Swift data-attribute (P10) === */
[data-swift-page-header] a:hover,
[data-dw-navigation] a:hover {
  color: var(--td-accent);
  text-decoration: none;
}

/* === Logo placement === */
[data-swift-logo-image] img {
  max-height: 1.75rem;
  width: auto;
}

/* === Focus ring — visible neutral ring for keyboard accessibility === */
:focus-visible {
  outline: 2px solid var(--td-slate-mid);
  outline-offset: 2px;
}

/* === Product / content card — rounded, hairline border, soft lift on hover ===
 * Elevation model: resting = hairline foreground border + no shadow; hover =
 * stronger hairline + soft layered shadow + 2px lift. 150ms transition. === */
[data-dw-colorscheme="light"] .card,
[data-dw-colorscheme="lightgrey1"] .card,
[data-dw-colorscheme="lightgrey2"] .card {
  box-shadow: none;
  border: 1px solid rgba(31, 41, 51, .08);
  border-radius: var(--td-radius);
  overflow: hidden;
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}

[data-dw-colorscheme="light"] .card:hover,
[data-dw-colorscheme="lightgrey1"] .card:hover,
[data-dw-colorscheme="lightgrey2"] .card:hover {
  border-color: rgba(31, 41, 51, .16);
  box-shadow: var(--td-shadow-soft);
  transform: translateY(-2px);
}

/* =============================================================================
 * Header menu-bar affordance — folded into theme-default.
 * Turns a flat Swift-v2_MenuRelatedContent megamenu bar into a bar that READS as
 * a menu: dropdown carets, hover + active states, and reachable dropdowns. Pure
 * disk-overlay CSS — NO template edit, NO custom code, NO serialized DB content
 * (SPEC-06 disk-overlay-only).
 *
 * PREREQUISITE (data, not CSS): the top navigation groups/pages must have
 * children so Swift renders the dropdown/megamenu panel
 * (`nodesExist = rootNode.Nodes.Any()` in Swift-v2_MenuRelatedContent/Menu.cshtml).
 * A childless bar stays flat text no matter what this CSS does — the base
 * contract records this data prerequisite (base.contract.json navDepth).
 * ========================================================================== */

/* --- Borderless band on the desktop category bar (no top/bottom hairline) --- */
.megamenu-wrapper > nav.d-flex { padding-block: .15rem; gap: .25rem; }

/* The (icon + caret) nav row shares a flex row with the header search field,
 * whose inner wrapper carries a hardcoded min-width:260px that refuses to
 * shrink and forces horizontal overflow around ~1440px. Relax it so the search
 * fits (stays usable at ~190px). Scoped to the search field. */
[data-dw-itemtype="swift-v2_searchfield"],
[data-dw-itemtype="swift-v2_searchfield"] .js-async-fetch-placeholder { min-width: 0 !important; }

/* --- Lay out icon + label + caret on one row on the top-level items.
 * Tighter horizontal padding + gap keeps the icon/caret additions from widening
 * the nav row enough to push the header search field past the viewport. --- */
.megamenu-wrapper > nav > .nav-item > .nav-link {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding-inline: .3rem;
  border-radius: .25rem;
  transition: background-color .12s ease, color .12s ease, box-shadow .12s ease;
}

/* --- Icon hook (OPT-IN, generic; ships icon-free) ----------------------------
 * Icons are keyed on a neutral hook — a `data-nav-icon="<name>"` attribute a
 * demo sets on the nav node's CSS-class/attributes field. The box below is
 * inert until a node opts in AND a binding maps <name> to an SVG. theme-default
 * ships NO icon files and NO bindings — bind against the DW10 stock icon set
 * (/Files/Images/Icons/, ~80 SVGs) in the consuming solution:
 *   (1) pick <name>.svg from /Files/Images/Icons/,
 *   (2) add one line `[data-swift-menu] a.nav-link[data-nav-icon="<name>"]::before{
 *       -webkit-mask-image:url("/Files/Images/Icons/<name>.svg");
 *       mask-image:url("/Files/Images/Icons/<name>.svg"); }`,
 *   (3) set the nav node's field to data-nav-icon="<name>".
 * Painted with mask-image + currentColor so each icon inherits the nav text
 * colour and recolours on hover/active for free. */
[data-swift-menu] a.nav-link[data-nav-icon]::before {
  content: "";
  display: inline-block;
  flex: 0 0 auto;
  width: 1em;
  height: 1em;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;  mask-position: center;
  -webkit-mask-size: contain;     mask-size: contain;
  vertical-align: -0.15em;
}

/* --- Caret on top items that have a dropdown; rotates when open/hovered --- */
.megamenu-wrapper > nav > .nav-item.dropdown > .nav-link::after {
  content: "";
  display: inline-block;
  flex: 0 0 auto;
  width: .42em;
  height: .42em;
  margin-left: .05rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform .2s ease;
}
.megamenu-wrapper > nav > .nav-item.dropdown > .nav-link[aria-expanded="true"]::after {
  transform: translateY(1px) rotate(-135deg);
}
/* LRN-nav-04: Swift's link classes (text-decoration-underline-hover) reuse the
 * SAME ::after pseudo as this caret; when the panel opens they repurpose ::after
 * into an underline and zero the caret's side border, so the chevron VANISHES on
 * open. Force the chevron to own ::after in every open state (hover / .show /
 * aria-expanded) — border AND position, or the border-restored caret lands UNDER
 * the item (the underline rule positions ::after absolutely at bottom-left). */
.megamenu-wrapper > nav > .nav-item.dropdown:hover > .nav-link::after,
.megamenu-wrapper > nav > .nav-item.dropdown > .nav-link.show::after,
.megamenu-wrapper > nav > .nav-item.dropdown > .nav-link[aria-expanded="true"]::after {
  position: static !important;
  inset: auto !important;
  display: inline-block !important;
  width: .42em !important;
  height: .42em !important;
  border: 0 !important;
  border-right: 1.5px solid currentColor !important;
  border-bottom: 1.5px solid currentColor !important;
  transform: translateY(1px) rotate(-135deg);
}

/* --- Hover + active affordances (desktop) --- */
@media (min-width: 992px) {
  /* open the megamenu / dropdown on hover, not only on click */
  .megamenu-wrapper > nav > .nav-item.dropdown:hover > .dropdown-menu { display: block; }
  .megamenu-wrapper > nav > .nav-item.dropdown:hover > .nav-link::after { transform: translateY(1px) rotate(-135deg); }

  /* Keep the dropdown reachable while the mouse travels to it. TWO dead zones:
   *
   *   LRN-nav-05 (horizontal) — the stock dropdown min-width (~192px) is narrower
   *      than a long category label, leaving a dead strip beside the panel.
   *      min-width:100% (of the position:relative nav-item = the trigger width)
   *      makes the panel underlap the whole label so every downward path lands on it.
   *
   *   LRN-nav-03 (vertical) — Popper positions the panel ~16px BELOW the trigger
   *      with an inline transform (margin-top / CSS top cannot beat it). Bridge that
   *      gap with the ITEM's ::after, gated on :has(> .show) — the OPEN state, NOT
   *      :hover, which is already lost inside the gap. As the item's own pseudo it
   *      hit-tests as the item (no item.mouseleave -> panel stays open); z-index lifts
   *      it over the header layout; height must exceed the offset (1.25rem = 20px).
   *
   * 03 (vertical) + 05 (horizontal) together are the complete reach fix; neither
   * alone suffices. Requires CSS :has() (all current evergreen browsers). */
  .megamenu-wrapper > nav > .nav-item.dropdown > .dropdown-menu,
  .megamenu-wrapper > nav > .nav-item.dropdown > .megamenu {
    min-width: 100%;
  }
  .megamenu-wrapper > nav > .nav-item.dropdown:has(> .dropdown-menu.show)::after,
  .megamenu-wrapper > nav > .nav-item.dropdown:has(> .megamenu.show)::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    height: 1.25rem;
    z-index: 1001;
  }

  /* soft hover background on the whole top item */
  .megamenu-wrapper > nav > .nav-item > .nav-link:hover,
  .megamenu-wrapper > nav > .nav-item.dropdown:hover > .nav-link {
    background-color: var(--td-accent-soft);
    color: var(--td-accent);
  }

  /* active category — accent bottom border */
  .megamenu-wrapper > nav > .nav-item > .nav-link[aria-current="page"] {
    box-shadow: inset 0 -3px 0 0 var(--td-accent);
    color: var(--td-accent);
    border-radius: .25rem .25rem 0 0;
  }
}

/* --- Dropdown / megamenu panel polish: soft shadow + tidy links --- */
.megamenu-wrapper .megamenu,
.megamenu-wrapper .dropdown-menu.dropdown-menu-shadow {
  box-shadow: 0 10px 26px rgba(31, 41, 51, .12);
}
.megamenu-wrapper .megamenu-col .nav-link:hover { color: var(--td-accent); }
.megamenu-wrapper .dropdown-menu .dropdown-item:hover {
  background-color: var(--td-accent-soft);
  color: var(--td-accent);
}

/* --- Leaf subcategory landing pages: suppress stock empty-state placeholders ---
 * A childless (leaf) product group renders two stock empty-state alerts — the
 * ProductListNavigation editor placeholder ("navigation will be shown here") and
 * an empty ProductListItemRepeater bar. On a category with real subgroups/products
 * these item types render actual links/tiles instead, so hiding the empty alert is
 * a no-op there. Result: a clicked subcategory shows a clean landing (breadcrumb +
 * heading + description) instead of unfinished-looking grey placeholders. */
[data-dw-itemtype="swift-v2_productlistnavigation"] .alert.alert-dark,
[data-dw-itemtype="swift-v2_productlistitemrepeater"] .alert.alert-dark {
  display: none !important;
}

/* =============================================================================
 * Image-height caps — durable, image-agnostic (RUN-DISTRIBUTION-QUALITY T1-01, D-A).
 * Swift-v2_Image ships NO height/aspect field: rendered height = column width ×
 * whatever `r=` crop the author baked into the image URL, UNCAPPED. Any demo that
 * swaps in its own photo reproduces the tall-portrait / full-bleed-filler defect.
 * The serialized `r=` normalization (T1-02) fixes the Distribution's OWN images;
 * THIS block is the load-bearing half — it survives a fresh deserialize with ANY
 * new photo. Pure disk-overlay CSS (SPEC-06): no template edit, no serialized DB.
 *
 * Mechanic: force a landscape box (aspect-ratio) + a viewport-relative max-height
 * on the paragraph wrapper, and object-fit:cover the image so a tall source is
 * CROPPED to the box, never allowed to tower. Focal point stays honoured — Swift
 * bakes the focal crop server-side into ToGetImage(); cover only trims the excess.
 * ========================================================================== */

/* --- Swift-v2_Image paragraph bands (figure > img.img-fluid) --- */
[data-dw-itemtype="swift-v2_image"] figure {
  aspect-ratio: 16 / 9;
  max-height: min(60vh, 640px);
  width: 100%;
  overflow: hidden;
}
[data-dw-itemtype="swift-v2_image"] figure > img.img-fluid,
[data-dw-itemtype="swift-v2_image"] figure > img {
  width: 100%;
  height: 100%;
  max-height: min(60vh, 640px);
  object-fit: cover;
}

/* --- Swift-v2_Slider featured-carousel cover cards (CardCoverNavInline) ---
 * The cover card carries an INLINE `min-height:25rem` (≈400px) and the swiffy
 * item width tops out at 25rem, so each slide reads as a tall near-square tile
 * dominating the fold. Cap the card to a viewport-relative band height (inline
 * min-height must be beaten with !important) and keep the cover image cropped. */
[data-dw-itemtype="swift-v2_slider"] .swiffy-slider .card {
  min-height: 0 !important;
  height: clamp(15rem, 34vh, 21rem);
  aspect-ratio: auto;
}
[data-dw-itemtype="swift-v2_slider"] .swiffy-slider .card > .card-img {
  height: 100% !important;
  object-fit: cover;
}

/* === Utility: table data density === */
table {
  font-size: 0.9em;
  border-collapse: collapse;
}

/* =============================================================================
 * FRESH PASS — close the design gap to a modern headless storefront.
 * A structural version of the marine-demo re-skin, generalized to the neutral
 * theme: shape + elevation system, muted secondary-text tier, calmer motion,
 * a slim two-row header, rounded imagery. Disk-overlay only (SPEC-06): no
 * template edit, no serialized DB content. Everything paints with the --td-*
 * tokens / currentColor, so a customer palette swap carries it for free.
 * P-guards honoured: P4 (button variant :not() chains — unchanged above),
 * P5 (footer scoped to the landmark — unchanged above), P10 (nav scoped via
 * data-attributes / .megamenu-wrapper — unchanged above).
 * ========================================================================== */

/* --- Header density: budget the standard two-row header <=170px desktop.
 *     Section padding is scoped to `main` — NEVER to the header/footer
 *     landmarks — so tightening content rhythm never re-inflates the chrome
 *     (the header/footer trims below stay independent of this). --- */
main [data-swift-container] {
  padding-top: 1.25rem;
  padding-bottom: 1.25rem;
}
[data-swift-page-header] section > .grid {
  padding-top: .45rem;
  padding-bottom: .45rem;
}
[data-swift-page-header] [data-swift-container] {
  padding-top: .25rem;
  padding-bottom: .25rem;
}

/* --- Secondary text tier: soften body copy on light schemes (~78% fg).
 *     Headings are unaffected (rule targets <p>); CTAs/buttons contain no <p>.
 *     Foundry #35: .dw-eyebrow is EXCLUDED. A section kicker authored as a <p> is
 *     a kicker, not body copy, and this softener out-specified the utility on every
 *     light scheme. Excluding here (rather than bumping .dw-eyebrow) keeps the
 *     utility usable in any element and leaves its specificity at (0,1,0) so a brand
 *     can still recolour it with one plain class rule. --- */
main [data-dw-colorscheme="light"] p:not(.dw-eyebrow),
main [data-dw-colorscheme="lightgrey1"] p:not(.dw-eyebrow),
main [data-dw-colorscheme="lightgrey2"] p:not(.dw-eyebrow) {
  color: rgba(31, 41, 51, .78);
}

/* --- Breadcrumbs: quiet wayfinding, not a heading. Dimmed via color-mix, NOT a
 *     component opacity: the old opacity .65 MULTIPLIED under Bootstrap's own
 *     rgba(.75) on .breadcrumb-item.active to an effective alpha of .488 —
 *     #939597 on white, 3.02:1 (Foundry #145's emergent-alpha class, measured
 *     live by the gate contrast probe). color-mix pins ONE effective alpha of
 *     .72 on every crumb — above the .635 floor for the default ink on white
 *     (composite ~5.1:1) — and overrides Bootstrap's own .75 on .active so
 *     alphas can no longer stack. Follows currentColor on dark schemes. --- */
[data-dw-itemtype="swift-v2_breadcrumbnavigation"] {
  font-size: .85rem;
}
[data-dw-itemtype="swift-v2_breadcrumbnavigation"] .breadcrumb-item,
[data-dw-itemtype="swift-v2_breadcrumbnavigation"] .breadcrumb-item.active,
[data-dw-itemtype="swift-v2_breadcrumbnavigation"] .breadcrumb-item > a {
  color: color-mix(in srgb, currentColor 72%, transparent);
}

/* --- Motion: 120ms color/bg/border on links; cards + buttons carry 150ms /
 *     100ms above. No layout-affecting transitions. --- */
main a {
  transition: color .12s ease, background-color .12s ease, border-color .12s ease;
}

/* --- Rounded imagery: content + product media pick up the surface radius.
 *     The full-bleed hero poster stays square (it is not swift-v2_image and is
 *     capped square by the image-height block above). --- */
main [data-dw-itemtype="swift-v2_image"] figure,
main [data-dw-itemtype="swift-v2_image"] figure > img,
main [data-dw-itemtype="swift-v2_image"] figure > img.img-fluid,
main [data-dw-itemtype="swift-v2_productmedia"] img {
  border-radius: var(--td-radius);
}
main [data-dw-itemtype="swift-v2_productdefaultimage"] img {
  border-radius: var(--td-radius-sm);
  border: 1px solid rgba(31, 41, 51, .06);
}

/* --- Accordion (specs / FAQ): rounded panels, hairline borders. --- */
main .accordion-item {
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid rgba(31, 41, 51, .08);
}
main .accordion-item + .accordion-item { margin-top: .5rem; }
main .accordion-button { font-weight: 600; }

/* --- Facet / sort dropdown toggles: pill chips. --- */
main .dropdown > .btn,
main .dropdown-toggle { border-radius: 999px; }

/* --- Feature (USP) tiles: subtle lift like a headless value band. --- */
main [data-dw-itemtype="swift-v2_feature"] { transition: transform .15s ease; }
main [data-dw-itemtype="swift-v2_feature"]:hover { transform: translateY(-2px); }

/* =============================================================================
 * Structural template-gap mitigations (disk-overlay). Each mirrors a Part B
 * template finding; the durable fix lives upstream in the Swift design package,
 * these keep the default demo clean until it lands.
 * ========================================================================== */

/* --- Poster-first rows (Part B-3): the row grid's default ~20px top padding
 *     reads as a white gap between a sticky header and a full-bleed hero.
 *     Zero it only when the first section holds a poster, so PLP/breadcrumb
 *     rows keep their breathing room. --- */
main > section:first-child:has([data-swift-poster]) > .grid { padding-top: 0 !important; }

/* --- Long BOM (Part B-5): a Swift-v2_ProductBom with 30+ rows dominates the
 *     page. Cap it to a scroll frame so it never runs the page length. --- */
[data-dw-itemtype="swift-v2_productbom"] {
  max-height: 640px;
  overflow-y: auto;
  padding-right: .5rem;
}

/* --- Slider card text run-on (Part B-4): the category/product slider item
 *     concatenates title + text with no separator ("PropulsionGenuine drive
 *     components..."). Force the card title to a block so the text starts on
 *     its own line. (CSS mitigation of a missing spacing element in the slider
 *     item template — the durable fix is the template, upstream.) --- */
main [data-dw-itemtype="swift-v2_slider"] .card .card-title,
main [data-dw-itemtype="swift-v2_productgroupslider"] .card .card-title,
main [data-dw-itemtype="swift-v2_productcomponentslider"] .card .card-title {
  display: block;
  margin-bottom: .25rem;
}

/* =============================================================================
 * MOBILE PASS — structural version of the marine-demo second pass (2026-07-18,
 * brief Part B addendum #8-13). Kills the small-viewport layout blowout, wraps
 * flex rows below md, gives the list-mode PLP real column discipline, and opens
 * the spec accordion into a readable two-column table. Disk-overlay only
 * (SPEC-06): no template edit, no serialized DB content. Everything paints with
 * the --td-* tokens / currentColor, so a customer palette swap carries it free.
 * P-guards honoured throughout: P4 (button variant :not() chains — the anon CTA
 * pill below only sets border/color, never a variant background), P5 (footer
 * wrap scoped to the body>footer / [data-swift-page-footer] landmarks), P10
 * (nav rules scoped via data-attributes, never a bare `nav`).
 *
 * Probe lesson (record it, don't just trust it): `overflow-x:hidden` on <body>
 * MASKS a horizontal blowout from `document.documentElement.scrollWidth` — the
 * fresh canvas measured clean while the content still overflowed. Measure
 * `document.body.scrollWidth` at 390px. (marine: 390px canvas read 1356px with
 * the mega-menu present, footer alone stretched it to 704px.)
 * ========================================================================== */

/* --- #8  THE mobile bug: the desktop mega-menu never collapses. Swift-v2's
 *     MenuRelatedContent renders a fixed-width `.nav-wrapper` megamenu bar
 *     (~1282px on marine) at EVERY viewport; at 390px it stretched the document
 *     canvas to 1356px — content squeezed into a left column, giant blank right
 *     margin, and broken lazy-image paint that reads as "missing PLP images".
 *     Constrain the bar to a swipeable, horizontally-scrollable category strip
 *     below the lg breakpoint. Data-attribute scoped (P10); the desktop
 *     affordance block above (>=992px) is untouched. A real burger/offcanvas is
 *     the durable fix and belongs upstream in the Swift menu template. --- */
[data-td-block="8"] { --td-block: 8; }
@media (max-width: 991.98px) {
  [data-dw-itemtype="swift-v2_menurelatedcontent"] {
    max-width: 100vw;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  [data-dw-itemtype="swift-v2_menurelatedcontent"]::-webkit-scrollbar { display: none; }
  [data-dw-itemtype="swift-v2_menurelatedcontent"] .nav-wrapper { width: max-content; }
}

/* --- #9  Rowflex columns must wrap below md (USP band, feature rows). Any
 *     NColumnsFlex row keeps all columns on one line at 390 by default. Wrap is
 *     the sane small-screen default. (Data trap noted upstream: changing a row's
 *     definitionId without setting `flexibleColumns` int[] leaves the columns
 *     without responsive classes — that is a serialized-content concern, not
 *     theme CSS.)
 *     Foundry #65: the wrap and the column base now carry !important. Swift marks
 *     whichever column is flexible with Bootstrap's .flex-fill, declared
 *     `flex: 1 1 auto !important`, and Bootstrap ships .flex-nowrap the same way —
 *     so the plain declarations here lost to a utility class regardless of how much
 *     more specific the attribute selectors are, and the row only wrapped when it
 *     happened not to fit. Both tiers of a responsive pair must be !important or
 *     they disagree across a band of widths. --- */
[data-td-block="9"] { --td-block: 9; }
@media (max-width: 767.98px) {
  main [data-swift-gridrow$="ColumnsFlex"] > [data-swift-container] { flex-wrap: wrap !important; }
  main [data-swift-gridrow$="ColumnsFlex"] > [data-swift-container] > [data-swift-gridcolumn] { flex: 1 1 100% !important; }
}

/* --- #9  Footer columns wrap too — the footer sits OUTSIDE <main> and still
 *     stretched the mobile canvas (marine: to ~704px). Scoped to the footer
 *     landmark only (P5). --- */
@media (max-width: 767.98px) {
  [data-swift-page-footer] [data-swift-gridrow] > [data-swift-container],
  body > footer [data-swift-gridrow] > [data-swift-container] { flex-wrap: wrap !important; }
  [data-swift-page-footer] [data-swift-gridrow] [data-swift-gridcolumn],
  body > footer [data-swift-gridrow] [data-swift-gridcolumn] { flex: 1 1 100% !important; }
}

/* --- Mobile rhythm: halve section padding so the buy panel and content rows
 *     sit together instead of floating in dead space (the FRESH PASS 1.25rem
 *     desktop budget re-tightened for small screens). Scoped to `main`. --- */
@media (max-width: 767.98px) {
  main [data-swift-container] { padding-top: .5rem; padding-bottom: .5rem; }
  main [data-swift-gridrow] > .grid { padding-top: .35rem; padding-bottom: .35rem; }
}

/* --- #10 Product line rows, mobile stack (PLP AND the PDP BOM — the BOM lacked
 *     the .product-list scope so its rows clipped the same way). Shared stacked
 *     layout below the lg breakpoint. --- */
[data-td-block="10"] { --td-block: 10; }
@media (max-width: 991.98px) {
  main article.product section > [data-swift-container] { flex-wrap: wrap; gap: .35rem .75rem; }
  main article.product [data-dw-itemtype="swift-v2_productdefaultimage"] { flex: 0 0 56px; max-width: 56px; }
  main article.product [data-dw-itemtype="swift-v2_productnumber"] { flex: 1 1 40%; }
  main article.product [data-dw-itemtype="swift-v2_productheader"] { flex: 1 1 100%; order: 5; }
  main article.product [data-dw-itemtype="swift-v2_productshortdescription"] { flex: 1 1 40%; order: 6; }
  main article.product [data-dw-itemtype="swift-v2_productstock"] { flex: 0 0 auto; order: 7; margin-left: 0; }
  main article.product [data-dw-itemtype="swift-v2_productprice"] { flex: 0 0 auto; text-align: left; order: 2; }
  main article.product [data-dw-itemtype="swift-v2_productaddtocart"] { flex: 1 1 100%; order: 8; max-width: 100%; }
  main article.product [data-dw-itemtype="swift-v2_productaddtocart"] [data-dw-button] { max-width: 100%; }
}

/* --- #10 Desktop PLP column discipline. Swift-v2's list-mode PLP (the
 *     Distribution's default product-list layout) carries Bootstrap `.flex-fill`
 *     (`flex:1 1 auto !important`) on every grid column, which silently defeats
 *     any plain flex base — every column grew with its content and the CTA
 *     landed on a different x per row. Reassert honest column bases with
 *     !important and pin the stock/price/CTA cluster to the right edge. The
 *     durable fix is the template dropping `.flex-fill` when a layout is meant
 *     to be columnar (deferred upstream); this keeps the default PLP aligned
 *     meanwhile. Structural for theme-default because the list-mode PLP ships as
 *     the Distribution default. --- */
@media (min-width: 992px) {
  .product-list article.product [data-dw-itemtype="swift-v2_productdefaultimage"] { flex: 0 0 72px !important; }
  .product-list article.product [data-dw-itemtype="swift-v2_productnumber"] { flex: 0 0 150px !important; }
  .product-list article.product [data-dw-itemtype="swift-v2_productheader"] { flex: 1 1 auto !important; min-width: 0; }
  .product-list article.product [data-dw-itemtype="swift-v2_productshortdescription"] {
    flex: 0 0 280px !important; max-width: 280px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .product-list article.product [data-dw-itemtype="swift-v2_productstock"] { flex: 0 0 100px !important; margin-left: auto; }
  .product-list article.product [data-dw-itemtype="swift-v2_productprice"] {
    flex: 0 0 auto !important;
    display: flex; justify-content: flex-end; align-items: center;
  }
  .product-list article.product [data-dw-itemtype="swift-v2_productaddtocart"] { flex: 0 0 auto !important; }
}

/* --- #11 Anon B2B CTA. The "sign in for dealer pricing" pill renders INSIDE
 *     swift-v2_productPRICE (add-to-cart is a narrow stub), so selectors hunting
 *     it via add-to-cart miss it entirely. WHICH component owns the anon CTA
 *     needs normalizing in the template (deferred upstream). CSS-able here: give
 *     the pill a calm resting state and full accent on row hover — token-driven,
 *     border/color only, so the P4 filled-variant hover rule is untouched. --- */
[data-td-block="11"] { --td-block: 11; }
.product-list article.product [data-dw-itemtype="swift-v2_productprice"] [data-dw-button] {
  font-size: .8rem;
  --dw-btn-padding-y: .4rem;
  --dw-btn-padding-x: 1.1rem;
  border-color: var(--td-accent-soft);
  transition: border-color .15s ease, background-color .15s ease, color .15s ease;
}
.product-list article.product:hover [data-dw-itemtype="swift-v2_productprice"] [data-dw-button] {
  border-color: var(--td-accent);
}

/* --- #12 Specifications: open spec table, headless style — no harmonica.
 *     Collapsed-by-default field display groups show NOTHING on the PDP. Force
 *     them open and restyle the `li > strong + span` rows as two-column spec
 *     lines with hairline separators. The component SHOULD offer a native "open
 *     table" display mode (deferred upstream — this CSS is the interim). More
 *     specific than the generic `main .accordion-item` rounding above, so it
 *     wins for this itemtype only. --- */
[data-td-block="12"] { --td-block: 12; }
[data-dw-itemtype="swift-v2_productfielddisplaygroupsaccordion"] .accordion-collapse {
  display: block !important;
  height: auto !important;
}
[data-dw-itemtype="swift-v2_productfielddisplaygroupsaccordion"] .accordion-button {
  pointer-events: none;
  cursor: default;
  padding-left: 0;
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--td-slate-mid);
  background: transparent;
  box-shadow: none;
}
[data-dw-itemtype="swift-v2_productfielddisplaygroupsaccordion"] .accordion-button::after { display: none; }
[data-dw-itemtype="swift-v2_productfielddisplaygroupsaccordion"] .accordion-item {
  border: 0 !important;
  border-radius: 0 !important;
  overflow: visible;
  background: transparent;
}
[data-dw-itemtype="swift-v2_productfielddisplaygroupsaccordion"] .accordion-item + .accordion-item { margin-top: 1rem; }
[data-dw-itemtype="swift-v2_productfielddisplaygroupsaccordion"] .accordion-body { padding: 0; }
/* the li rows -> two-column spec lines with hairline separators */
[data-dw-itemtype="swift-v2_productfielddisplaygroupsaccordion"] .accordion-body ul {
  list-style: none !important;
  opacity: 1 !important;
  line-height: 1.4 !important;
}
[data-dw-itemtype="swift-v2_productfielddisplaygroupsaccordion"] .accordion-body ul li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: .45rem 0;
  border-bottom: 1px solid rgba(31, 41, 51, .08);
  font-size: .875rem;
}
[data-dw-itemtype="swift-v2_productfielddisplaygroupsaccordion"] .accordion-body ul li strong {
  font-weight: 400;
  color: rgba(31, 41, 51, .6);
}
[data-dw-itemtype="swift-v2_productfielddisplaygroupsaccordion"] .accordion-body ul li span {
  font-weight: 600;
  text-align: right;
}

/* --- #13 Header logo lockup width is inline-hardcoded (a 210px figure), and
 *     the img-era `[data-swift-logo-image] img` hook above misses an inline-SVG
 *     logo. Clamp the figure itself so the icon cluster fits a 390px viewport,
 *     and let the SVG scale inside it. --- */
[data-td-block="13"] { --td-block: 13; }
@media (max-width: 767.98px) {
  header [data-dw-itemtype="swift-v2_logo"] figure { width: 150px !important; }
  header [data-dw-itemtype="swift-v2_logo"] svg { width: 100%; height: auto; }
}

/* --- #13 (cluster) Mobile header fit: a text-labelled Favorites link pushed
 *     the cart icon past the viewport. Make Favorites icon-only like account/cart
 *     and tighten the header container gap.
 *     Foundry #70: the original `font-size: 0` on the anchor is GONE from here. It
 *     hid the label by destroying it rather than by hiding it, which is exactly the
 *     idiom that strips a control's accessible name. Block #17 below now does the
 *     hiding with the sanctioned clip + clip-path recipe; this block keeps only the
 *     sizing/gap half. --- */
@media (max-width: 767.98px) {
  header [data-dw-itemtype="swift-v2_favorites"] a { gap: .25rem !important; }
  header [data-dw-itemtype="swift-v2_favorites"] svg { width: 20px; height: 20px; }
  [data-swift-page-header] [data-swift-container] { gap: .4rem !important; }
}

/* --- #14 Mobile PLP row consistency (real-device fix). A phone screenshot showed
 *     ONE PLP row wrapping its CTA to its own line while its neighbours stayed
 *     inline — mixed alignments that read as broken. Cause: the #10 mobile bases
 *     above carry NO !important, so Bootstrap `.flex-fill` (`flex:1 1 auto !important`,
 *     on every list column) beat them and let the SKU column grow content-driven
 *     (298px on the longest product number), tipping only the longest-SKU rows over
 *     the edge. The desktop #10 block already !importants every base for exactly this
 *     reason; the mobile bases need it too. Fix:
 *       (a) re-assert the mobile flex bases with !important so .flex-fill can't grow
 *           them — the SKU column pinned to `0 1 auto` (content width, never grows),
 *       (b) uniform 56px square thumbs,
 *       (c) compact SKU type,
 *       (d) right-anchor the price pill (margin-left:auto + justify-content:flex-end,
 *           nowrap, compact padding) so it sits at the right edge BOTH when it fits
 *           inline (~430px) and when it wraps (~390px), independent of column widths.
 *     PLP-scoped (.product-list) so the PDP BOM that #10 mobile also covers is left
 *     alone, and so these win the cascade over #10 mobile (main-scoped) + the #11
 *     pill rule. P4 honoured: the pill sets type / padding / flow only, never a
 *     variant background. --- */
[data-td-block="14"] { --td-block: 14; }
@media (max-width: 991.98px) {
  .product-list article.product [data-dw-itemtype="swift-v2_productdefaultimage"] { flex: 0 0 56px !important; max-width: 56px !important; }
  .product-list article.product [data-dw-itemtype="swift-v2_productdefaultimage"] img { width: 56px; height: 56px; object-fit: cover; }
  .product-list article.product [data-dw-itemtype="swift-v2_productnumber"] { flex: 0 1 auto !important; font-size: .75rem; }
  .product-list article.product [data-dw-itemtype="swift-v2_productheader"] { flex: 1 1 100% !important; }
  .product-list article.product [data-dw-itemtype="swift-v2_productshortdescription"] { flex: 1 1 40% !important; }
  .product-list article.product [data-dw-itemtype="swift-v2_productstock"] { flex: 0 0 auto !important; }
  .product-list article.product [data-dw-itemtype="swift-v2_productprice"] {
    flex: 0 0 auto !important;
    margin-left: auto !important;
    display: flex; justify-content: flex-end;
    text-align: right;
  }
  .product-list article.product [data-dw-itemtype="swift-v2_productprice"] [data-dw-button] {
    font-size: .78rem;
    --dw-btn-padding-y: .35rem;
    --dw-btn-padding-x: .9rem;
    white-space: nowrap;
  }
  .product-list article.product [data-dw-itemtype="swift-v2_productaddtocart"] { flex: 1 1 100% !important; }
}

/* =============================================================================
 * APPLIED-LEARNING PASS — structural versions of gate findings folded back from
 * the Foundry issue tracker (2026-07-27). Disk-overlay only (SPEC-06): no template
 * edit, no serialized DB content. Everything paints with the --td-* tokens /
 * currentColor, so a customer palette swap carries it for free. P-guards honoured
 * throughout: P4 (button variant :not() chains — untouched), P5 (footer paint/wrap
 * scoped to the body>footer / [data-swift-page-footer] landmarks — untouched),
 * P10 (nav rules scoped via data-attributes / .megamenu-wrapper — untouched).
 * ========================================================================== */

/* --- #15 Line-view PLP: a title that can actually hold a line (Foundry #46).
 *     Block #10 gave the desktop list row honest column bases but made the title the
 *     ONLY shrinkable child of a nowrap flex line whose other columns are fixed and
 *     non-shrinkable (thumb 72 + SKU 150 + description 280 + stock 100 + price + gaps).
 *     Once those over-commit the content lane the title collapses to width 0 with
 *     overflow visible, so its text stacks ONE WORD PER LINE (measured up to 9 lines)
 *     and paints straight across the description lane — an unreadable row that four
 *     gate PASSes never saw, because nothing asserted that two sibling boxes do not
 *     intersect. Fix, three parts, all structural for a B2B line-view PLP:
 *       (a) the title gets a real basis (0 1 320px) plus a min-width floor so it can
 *           shrink but never to nothing,
 *       (b) a hard 2-line clamp on the title's heading so a long name truncates
 *           instead of growing the row height,
 *       (c) the description stops being a fixed 280px block and becomes the
 *           shrinkable single-line ellipsis lane (1 1 140px, min-width 0) — it is the
 *           least load-bearing text in the row, so it is what should give.
 *     Supersedes the #10 desktop title/description bases by source order at equal
 *     specificity (both .product-list-scoped, both !important, per guard G3). --- */
[data-td-block="15"] { --td-block: 15; }
@media (min-width: 992px) {
  .product-list article.product [data-dw-itemtype="swift-v2_productheader"] {
    flex: 0 1 320px !important;
    min-width: 180px;
  }
  .product-list article.product [data-dw-itemtype="swift-v2_productheader"] .h6,
  .product-list article.product [data-dw-itemtype="swift-v2_productheader"] h2,
  .product-list article.product [data-dw-itemtype="swift-v2_productheader"] h3,
  .product-list article.product [data-dw-itemtype="swift-v2_productheader"] h4,
  .product-list article.product [data-dw-itemtype="swift-v2_productheader"] h5,
  .product-list article.product [data-dw-itemtype="swift-v2_productheader"] h6 {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
  }
  .product-list article.product [data-dw-itemtype="swift-v2_productshortdescription"] {
    flex: 1 1 140px !important;
    max-width: none;
    min-width: 0;
  }
}

/* =============================================================================
 * #16 FLOATING / OVERLAY HEADER — opt-in, brand-generic (Foundry #36, #39, #55,
 * #66, #69).
 *
 * Swift 2.4 exposes NO native transparent/overlay-header switch: the header is a
 * full-width sticky bar in normal flow, and a hero-behind-the-menu composition is
 * therefore CSS-only. Every brand that wants the motif re-derives the same four
 * mechanics and gets the same four things wrong, so they live here instead.
 *
 * OPT-IN HOOK — nothing below applies until a build adds the class
 * `td-header-overlay` to any element inside the page header (the CSS-class field of
 * the header's grid row in the Visual Editor is enough — no template edit, and no
 * serialized DB content of ours). The neutral default therefore renders exactly as
 * before. The tokens are always defined, so a brand only overrides values.
 *
 * The four mechanics, and why each is shaped this way:
 *
 *  (1) fixed, not sticky. position:sticky keeps the bar IN FLOW, so a hero can never
 *      flow behind it. The override carries !important per guard G3 — .sticky-top is
 *      a platform-managed utility class.
 *  (2) one pill painted by ::before, and NO overflow:hidden on it (the standing
 *      header guard behind Foundry #70). A rounded pill drawn with overflow:hidden
 *      clips the megamenu and offcanvas panels; painting the radius on the
 *      pseudo-element and leaving overflow alone satisfies both constraints. One pill
 *      behind transparent header sections also hides the seam between header rows.
 *  (3) clearance keyed on the DOM the SERVER SENT, not on a breakpoint (Foundry #55,
 *      #69). Dynamicweb selects between two header content pages server-side BY
 *      USER-AGENT: a phone UA receives a short 2-row header carrying
 *      swift-v2_offcanvasnavigation; a desktop UA at the SAME width receives the tall
 *      3-row header. A media query cannot tell those two documents apart — they
 *      arrive at identical widths — so any clearance token keyed on a breakpoint is
 *      fitted to whichever document happened to be measured and is silently wrong for
 *      the other (measured: a 94px dead band on real phones, invisible to four
 *      consecutive headless PASSes). The body:has() rule below sits OUTSIDE every
 *      media query, wins over :root on specificity, and self-corrects at every width —
 *      which is why it also fixed tablets without a tablet-specific rule. It fails
 *      safe: no :has() support means the :root default applies, i.e. over-clearance,
 *      never occluded content. A hand-fitted constant is the anti-pattern this
 *      replaces — it re-broke on every header edit, shipping a 45px under-clearance at
 *      one tier and a 51px white gap at another. Where a constant must remain (the
 *      desktop tier below), pair it with the shipped painted-clearance probe
 *      PAINT-01 (Foundry tools/harness/design-paint-probes.mjs — point its
 *      paint.owner selector at the header pill), which measures painted geometry
 *      at the SAME viewport and user-agent. A brand build that keeps a constant
 *      without wiring PAINT-01 is repeating the defect this block replaces.
 *  (4) the container's hidden 32px (Foundry #66). Swift sets
 *      max-width: calc(-32px + min(<cap>, 100%)) on [data-swift-container] — 32px
 *      NARROWER than its parent — and then centres it with auto margins, which below
 *      the cap resolve to 16px per side. Any horizontal budget computed as
 *      viewport - 2*inset - 2*padding is 32px optimistic, and narrowing the bar inset
 *      does not move the logo by the amount you asked for. Restoring
 *      min(cap, 100%) inside the overlay bar drops the phantom margin while keeping
 *      the cap, which is the composition's real max content width.
 *
 * Poster note (Foundry #39): a subject that must clear the floating bar cannot be
 * fixed with the image focal point. object-fit:cover on a fixed-height poster box is
 * WIDTH-driven, so the subject's vertical position scales with viewport WIDTH while
 * the bar height is a fixed px — measured clearance varied about 1.5x across
 * 1280..1920 on one unchanged sheet and image. Pair the TOP-anchored crop set below
 * with a master image that carries headroom above the subject (subject in the lower
 * third); nudging the focal point only moves the failure to another width.
 * ========================================================================== */
[data-td-block="16"] { --td-block: 16; }

:root {
  --td-bar-top:       0px;                 /* float inset from the top of the viewport */
  --td-bar-inset:     0px;                 /* horizontal inset of the bar pill */
  --td-bar-h:         170px;               /* desktop header document height */
  --td-bar-h-phone:   84px;                /* phone (offcanvas) header document height */
  --td-bar-radius:    var(--td-radius);
  --td-bar-bg:        var(--td-ink);
  --td-container-cap: 1680px;              /* Swift's [data-swift-container] max-width cap */
  --td-bar-clearance: calc(var(--td-bar-top) + var(--td-bar-h));
}

/* Clearance keyed on the served DOM, outside every media query (Foundry #55). */
body:has([data-swift-page-header] [data-dw-itemtype="swift-v2_offcanvasnavigation"]) {
  --td-bar-clearance: calc(var(--td-bar-top) + var(--td-bar-h-phone));
}

[data-swift-page-header]:has(.td-header-overlay) {
  position: fixed !important;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1030;
  background: transparent;
  padding: var(--td-bar-top) var(--td-bar-inset) 0;
}
[data-swift-page-header]:has(.td-header-overlay)::before {
  content: "";
  position: absolute;
  top: var(--td-bar-top);
  left: var(--td-bar-inset);
  right: var(--td-bar-inset);
  bottom: 0;
  z-index: -1;
  border-radius: var(--td-bar-radius);
  background-color: var(--td-bar-bg);
}
[data-swift-page-header]:has(.td-header-overlay) section { background: transparent; }
[data-swift-page-header]:has(.td-header-overlay) [data-swift-container] {
  max-width: min(var(--td-container-cap), 100%);
}
body:has([data-swift-page-header] .td-header-overlay) main {
  padding-top: var(--td-bar-clearance);
}
/* A poster in the first row flows BEHIND the bar — it needs no clearance, and the
 * poster-first-row exception further up already zeroes that row's own padding. */
body:has([data-swift-page-header] .td-header-overlay) main:has(> section:first-child [data-swift-poster]) {
  padding-top: 0;
}
/* Top-anchored poster crop, so the subject's clearance is viewport-stable. */
body:has([data-swift-page-header] .td-header-overlay) main > section:first-child [data-swift-poster] img {
  object-position: 50% 0%;
}

/* --- #17 Visually-hidden: the one sanctioned idiom (Foundry #70).
 *     Making a header control icon-only needs its label hidden from view but kept in
 *     the accessibility tree. The classic recipe uses overflow:hidden to crop the 1x1
 *     box, which a standing guard bans anywhere in the header subtree because it clips
 *     the megamenu and offcanvas panels. clip-path:inset(50%) performs the identical
 *     crop with NO overflow declaration, so the two constraints were never really in
 *     conflict — the idiom was simply older than clip-path. Ship it once, use it
 *     everywhere. NEVER substitute display:none or font-size:0: both remove the label
 *     from the accessible-name computation, turning an icon-only control into an
 *     unlabelled one. The `clip` line is the legacy partner of `clip-path` and is kept
 *     deliberately.
 *     Applied here to the mobile Favorites label, replacing the font-size:0 that block
 *     #13 used to carry. It targets element-wrapped labels only — a bare text node has
 *     nothing to clip, so a build that renders one wraps it and applies
 *     .td-visually-hidden. --- */
[data-td-block="17"] { --td-block: 17; }
.td-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  border: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
@media (max-width: 767.98px) {
  header [data-dw-itemtype="swift-v2_favorites"] a > span:not(:has(svg, img, picture)) {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    padding: 0;
    border: 0;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* --- #18 Content-vs-catalog scoping contract (Foundry #45).
 *     Every Swift build eventually needs to give ONE content page a different voice
 *     from the WHOLE catalog with no leak either direction, and there was no
 *     documented collision-free hook for it. There is one, and every Swift build has
 *     it: the master emits
 *         body[data-dw-page-id="<id>"][data-dw-itemtype="<page item system name>"]
 *     and the catalog root, every PLP and every PDP all render the SAME body itemtype
 *     `swift-v2_shop`, while content pages render `swift-v2_page`. So:
 *         body[data-dw-page-id="1234"]            -> exactly one content page
 *         body[data-dw-itemtype="swift-v2_shop"]  -> the entire catalog
 *         body[data-dw-itemtype="swift-v2_page"]  -> every content page
 *     Guard G4 is the trap here: a DW page id is numeric and `#1234` is an INVALID
 *     selector (an id may not start with a digit) — always use the attribute form.
 *     theme-default ships no rule on either hook, which is exactly what keeps them
 *     collision-free for a consuming build; the marker below is the contract's
 *     presence proof. Prove a split with a computed-style leak check in BOTH
 *     directions, not only the one that was changed. --- */
[data-td-block="18"] { --td-block: 18; }

/* --- #19 Palette deploy contract: a brand colour lives in more than one file
 *     (Foundry #96, #97).
 *     Swift buttons paint from --dw-color-button-primary, which this hand-authored
 *     sheet deliberately never declares. It is declared in
 *     System/Styles/ColorSchemes/<design>.css — a file the admin Styles editor
 *     GENERATES — which hardcodes the colour as BOTH a hex and an rgb triplet once per
 *     scheme (this layer ships 7 schemes, so 14 literals). A custom sheet loaded after
 *     it cannot override a variable it never mentions, which is why a token-only
 *     rebrand turns eyebrows, links and icon tiles but leaves every primary button —
 *     the largest single colour area on the site — on the old brand. Declaring
 *     --dw-color-button-primary from here is the WRONG fix: it leaves the generated
 *     file lying, the admin Styles editor keeps showing the old swatch, and the next
 *     design save reverts the site.
 *     The generated .css is not the source of truth either. A sibling <design>.json
 *     holds the same values as a model (Schemes[] with BackgroundColor /
 *     ForegroundColor / PrimaryButtonColor / SecondaryButtonColor / CustomColors) and
 *     the editor writes BOTH files in one operation. Hand-edit only the .css and any
 *     regeneration silently undoes it — days later, with no deploy to blame.
 *     Contract, carried by the re-skin ladder in this layer's README:
 *       1. treat a palette change as a MULTI-FILE deploy — this sheet AND the
 *          ColorSchemes pair, both .css and .json, in the same pass;
 *       2. enumerate every literal of the OUTGOING colour in hex AND rgb-triplet
 *          notation across both files and assert an exact expected count per pattern,
 *          so a silent miss aborts the deploy instead of shipping a half-rebrand;
 *       3. pre-flight the .json: it must still parse, and every scheme must carry the
 *          new value, before upload.
 *     The accent slot near the top of this sheet is the other half of the contract: a
 *     brand accent belongs in --dw-color-accent, never in --dw-color-button-primary.
 *     --- */
[data-td-block="19"] { --td-block: 19; }

/* --- #20 PDP gallery media weight: preload is NOT CSS-reachable — the whole fix is
 *     upstream in the Swift templates (Foundry #161).
 *     Swift-v2 emits every PDP gallery asset up to THREE times (inline gallery,
 *     lightbox modal, thumbnail strip) and hard-codes preload="auto" on each video
 *     element: Paragraph/Swift-v2_ProductMediaGallery.cshtml (three sites) plus
 *     Components/VideoPlayer.cshtml. One 5MB gallery video therefore costs a ~15MB
 *     PDP before any interaction — measured 15,318 KB on a real product.
 *     This sheet ships NO rule for it, deliberately:
 *       - preload is an HTML ATTRIBUTE. CSS cannot read, set or remove one, and no
 *         property, at-rule or media feature suppresses a media fetch. Hiding does
 *         not help either: a hidden video element with preload="auto" still fetches,
 *         so the duplicate modal and thumbnail copies cost full weight while
 *         invisible. There is no partial CSS mitigation, and authoring one anyway
 *         would be a rule that paints without moving a single byte.
 *       - The two files that emit the attribute are stock Swift DESIGN templates.
 *         theme-default is disk-overlay only (SPEC-06) and forks no Swift template:
 *         overlaying either one freezes it at a Swift version and silently wins over
 *         every later release.
 *     Deferred upstream on purpose — same disposition as the asks at blocks #8, #10,
 *     #11 and #12.
 *     Upstream ask (Swift): preload="none" plus a poster on every gallery video, and
 *     render each asset ONCE with the modal and thumbnail referencing it instead of
 *     three independent media elements.
 *     Consuming build, until then: a solution-owned Custom script that swaps each
 *     gallery video for its poster and fetches the source on click. Proven on a real
 *     demo PDP — 15,318 KB down to 133 KB with zero media bytes before interaction,
 *     a 5.2 MB fetch on click, playback verified. That asset belongs to the CONSUMING
 *     build, not to the neutral theme: it changes PDP behaviour, and no gate edition
 *     ships a product carrying a gallery video to prove it against.
 *     --- */
[data-td-block="20"] { --td-block: 20; }
