/* 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;
}

/* === The inactive control tier - a flat pair, never a text opacity ===
 * Foundry #1153. The signed-in "Add to cart" label measured 1.62 against a 4.5
 * floor on the variants PLP and the flagship PDP, at every viewport, and only in
 * the authenticated pass - the control is hidden from anonymous visitors, so no
 * design run before the persona step could see it at all.
 *
 * WHERE THE 0.65 COMES FROM, since it is in none of this theme's sheets. Swift's
 * Paragraph/Swift-v2_ProductAddToCart.cshtml writes a `disabled` attribute onto
 * the button whenever the product is a variant master with no variant chosen
 * (`whenVariantsExist == "disable"`), out of stock, or discontinued - which is
 * EVERY card on a variants PLP and the master PDP. Bootstrap then dims the whole
 * control with `opacity: var(--bs-btn-disabled-opacity)`, 0.65.
 *
 * WHY THAT LANDS AT 1.62 AND NOT SOMEWHERE HARMLESS. The opacity is on the
 * BUTTON, so it multiplies the label's own alpha: declared white at alpha 1 x
 * 0.65 composites to #dbe8e2 over the painted fill. The fill is pale to begin
 * with - Swift builds it as `rgba(var(--dw-color-button-primary-rgb), 0.8)`, so
 * the measured ground is #97bead rather than the token itself - and white on
 * #97bead is only 2.04 even at full alpha. Two translucencies stacked, and the
 * failing colour is in no stylesheet: grepping the palette for #dbe8e2 finds
 * nothing, which is the whole reason the gate measures effective alpha.
 *
 * THE FIX. An inactive control states its state with a FLAT, opaque pair and
 * keeps its label at full alpha. Darkening the label instead is the trap in this
 * class: the same 0.65 multiplies whatever is put there, so a darker declared ink
 * lands LOWER than the value it was meant to fix.
 *
 * MEASURED: --td-slate #3E4C59 on --td-hairline #E4E7EB = 7.10:1. The label is
 * 16px / 600, which is not large text by either threshold (24px, or 18.66px at
 * 700), so the floor that applies is 4.5 and not 3.0. Both values are existing
 * theme tokens, so a brand palette carries the tier without a new declaration. */
.btn:disabled,
.btn.disabled,
.btn[disabled],
fieldset:disabled .btn,
.btn-check[disabled] + .btn,
.btn-check:disabled + .btn {
  opacity: 1;
  background-color: var(--td-hairline);
  border-color: var(--td-slate-light);
  color: var(--td-slate);
}

/* The quantity stepper beside it is dimmed the same way and by the same rule
 * class, so it gets the same treatment rather than a second convention. */
.form-control[type="number"][data-qty]:disabled {
  opacity: 1;
  background-color: var(--td-hairline);
  border-color: var(--td-slate-light);
  color: var(--td-slate);
}

/* === 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 reference 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 reference 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. (measured: 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 as measured) 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 (measured: 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; }

/* =============================================================================
 * #21 CONTAINER WIDTH 4 IN MAIN — full width and edge-to-edge are the SAME
 * setting in stock Swift, so restore the gutter (Foundry #545).
 *
 * Stock swift.css declares
 *   [data-dw-container-width="4"]{--dw-container-width:100% !important;
 *                                 --dw-container-gutter:0rem}
 * and then re-adds the gutter ONLY under the header and footer landmarks:
 *   :is(header,[data-dw-itemtype=swift-v2_header],
 *       footer,[data-dw-itemtype=swift-v2_footer])
 *     [data-dw-container-width="4"]{--dw-container-gutter: calc(2rem)}
 *
 * Width 4 is designed for a row whose PARAGRAPH supplies its own inset — a poster
 * carries --swift-poster-padding. A Text, Feature or product-list row at width 4
 * has none, and Swift's other padding mechanism, --swift-content-padding, only
 * applies to grid columns carrying a colour scheme. So a site-wide "make it full
 * width" pass puts main content at x=0: measured on /en-ca/about, the H1 and body
 * copy went from a 24px edge gap to 0px at 1440, and at 390 the breadcrumb,
 * product titles and the primary CTA all went from 16px to 0px.
 *
 * RESTORE THE GUTTER, NEVER THE WIDTH. Narrowing --dw-container-width back would
 * cancel the setting the author chose; re-adding --dw-container-gutter keeps the
 * band full-bleed while the content inside it breathes. The value is calc(2rem),
 * Swift's own header/footer value, so main matches the chrome instead of
 * introducing a third measure.
 *
 * Two exclusions, both deliberate:
 *   - NESTED containers. A width-4 container inside another width-4 container
 *     would add a second gutter on top of the inherited one and double the inset.
 *     Custom properties inherit, so the nested container already receives the
 *     ancestor's value; its own stock 0rem then wins for its own box, which is
 *     the correct outcome.
 *   - DELIBERATE full-bleed rows. A poster, a full-width image band or a map is
 *     supposed to touch the viewport edge. Mark the row (or any ancestor of it)
 *     with data-td-full-bleed — the CSS-class/attribute field of the grid row in
 *     the Visual Editor is enough, no template edit and no serialized DB content
 *     of ours — and the opt-out rule below returns it to zero.
 *
 * Header and footer are untouched: they already carry Swift's own calc(2rem)
 * re-add, and nothing here is scoped outside main.
 *
 * Validation is measured, never eyeballed: record edgeGap per container per
 * viewport before and after (Foundry scripts/b9-measure.mjs), and assert
 * main, header and footer all report a 16px edge gap at 1440/1920/2512/390 with
 * body overflow 0.
 *
 * Rejected: shipping zero gutter as "what stock Swift does" — it is measurably
 * broken on mobile and on left-aligned desktop copy. Also rejected: giving every
 * row a colour scheme to trigger --swift-content-padding, which paints
 * backgrounds nobody asked for.
 * ========================================================================== */
[data-td-block="21"] { --td-block: 21; }

main [data-dw-container-width="4"]:not([data-dw-container-width="4"] [data-dw-container-width="4"]) {
  --dw-container-gutter: calc(2rem);
}

/* Opt-out: a row that is MEANT to bleed to the edge. */
main [data-dw-container-width="4"][data-td-full-bleed],
main [data-td-full-bleed] [data-dw-container-width="4"] {
  --dw-container-gutter: 0rem;
}

/* ==========================================================================
 * BLOCK 22 - THE EDGE. An opt-in boundary motif, and the clearance that makes
 * it safe.
 *
 * WHAT IT IS. One path on a 1440x75 viewBox, applied as a CSS mask to a
 * pseudo-element that carries a flat background-color. The mask carves the
 * shape; the colour comes from a token. That is why one asset serves every
 * colour scheme, and why a brand changes the motif by changing a variable
 * rather than by shipping a second image per scheme.
 *
 * THE SHAPE HERE IS A PLACEHOLDER, and deliberately the dullest one available:
 * a barely-perceptible shallow bevel. This layer is brand-free, and a motif with
 * a personality is a brand decision made in the wrong place - a theme that ships
 * a signature silhouette hands every consuming site somebody else's signature.
 * The same path ships as a readable file at Images/Brand/edge.svg; edit both
 * together, exactly as the token block here and the copy in
 * DefaultHeadInclude.cshtml are edited together.
 *
 * HOW A BRAND SETS ITS OWN. One declaration in the brand's own sheet, loaded
 * after this one. Nothing else changes - not this block, not the clearance
 * rules, not a template. Take the d= attribute out of the brand's SVG,
 * percent-encode the commas, and:
 *
 *   :root { --td-edge-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 75' preserveAspectRatio='none'%3E%3Cpath d='M0%2C58%20L91.4%2C29.1%20Q120%2C20%20148.6%2C29.1%20L211.4%2C48.9%20Q240%2C58%20268.6%2C48.9%20L331.4%2C29.1%20Q360%2C20%20388.6%2C29.1%20L451.4%2C48.9%20Q480%2C58%20508.6%2C48.9%20L571.4%2C29.1%20Q600%2C20%20628.6%2C29.1%20L691.4%2C48.9%20Q720%2C58%20748.6%2C48.9%20L811.4%2C29.1%20Q840%2C20%20868.6%2C29.1%20L931.4%2C48.9%20Q960%2C58%20988.6%2C48.9%20L1051.4%2C29.1%20Q1080%2C20%201108.6%2C29.1%20L1171.4%2C48.9%20Q1200%2C58%201228.6%2C48.9%20L1291.4%2C29.1%20Q1320%2C20%201348.6%2C29.1%20L1440%2C58%20L1440%2C75%20L0%2C75%20Z'/%3E%3C/svg%3E"); }
 *
 * Images/Brand/edge-truvio.svg beside the placeholder is the worked example and
 * the source of the line above: a softened zigzag with gently rounded peaks -
 * industrial, not a sine wave - which is the Truvio edge. That swap is the whole
 * of the brand-time edit, and it is what the branding step of an e2e run
 * performs on a Truvio-branded site.
 *
 * IT IS INERT. Nothing below paints until a class is applied, and no Swift grid
 * row can carry one: the stock row template emits no class attribute from
 * content and grid-row serialization has no cssClass key. Block 26 of
 * default_custom.js is what opts the standing pages in, and is where the three
 * instances this theme applies are named.
 *
 * THE CLEARANCE RULE, which is the whole reason this block is longer than the
 * mask. A pseudo-element paints wherever it is positioned, inside its owner's
 * border box or outside it, and every box-model measurement reads healthy either
 * way - the elements genuinely do not overlap, so a geometry probe finds nothing
 * and the text is still sliced. The fix is SPACING, never stacking: padding sized
 * off the motif's own clamp is reserved on the element whose INK the band would
 * cover, so the reservation tracks the motif at every viewport automatically.
 * Raising z-index on the content instead is the tempting fix and the wrong one:
 * it repaints the text above the crest and leaves the design looking like a
 * mistake rather than a motif.
 *
 * THE RECEIVER, not the owner (2.2.2, Foundry #1152). The element that declares
 * the motif is not usually the element the motif paints over, and reserving the
 * room on the declaring element is the defect that shipped three instances at
 * 0.00px, -71px and -1700px of painted clearance at once. A bottom edge paints
 * inside its owner, so there the owner IS the receiver; a top edge paints above
 * its owner, so the receiver is whatever precedes it - and for the footer crest
 * that is <main>, whose end padding is the only thing the band can rise into.
 *
 * ONE TOKEN, THREE KNOBS.
 *   --td-edge-fill  the painted colour. Defaults to the neutral page ground, so
 *                   an un-themed edge reads as a carved boundary, not a stripe.
 *                   Set it to the colour of the section the edge points INTO.
 *   --td-edge-h     the height. clamp(40px, 5vw, 75px) - 72px at 1440, 40px at
 *                   390, and the same expression drives every clearance below,
 *                   which is what keeps the two in step.
 *   --td-edge-mask  the shape. Swap it for a different silhouette without
 *                   touching any rule here.
 *
 * GATE NOTE. Pseudo-element paint is invisible to every probe except the
 * painted-clearance one, so every edge instance a site ships needs its own
 * paintClearance entry naming its owner selector. A sitewide footer crest and a
 * single band boundary are two entries, not one.
 * ========================================================================== */
[data-td-block="22"] { --td-block: 22; }

:root {
  --td-edge-h:    clamp(40px, 5vw, 75px);
  --td-edge-fill: var(--td-white);
  --td-edge-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 75' preserveAspectRatio='none'%3E%3Cpath d='M0%2C66%20C480%2C74%20960%2C74%201440%2C66%20L1440%2C75%20L0%2C75%20Z'/%3E%3C/svg%3E");
}

/* The shared pseudo-element. Full-bleed horizontally, clamped vertically,
 * pointer-transparent, and masked to the path. No fallback is declared: a
 * browser without mask support simply does not paint it, which is the correct
 * degradation for a decorative shape. */
.td-edge-bottom::after,
.td-edge-top::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: var(--td-edge-h);
  background-color: var(--td-edge-fill);
  -webkit-mask-image: var(--td-edge-mask);
          mask-image: var(--td-edge-mask);
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  pointer-events: none;
}

/* An owner must establish a containing block for the absolutely-positioned
 * pseudo-element, and must not clip it. */
.td-edge-bottom,
.td-edge-top {
  position: relative;
  overflow: visible;
}

/* BOTTOM: the motif sits on the owner's own bottom edge and points down into
 * whatever follows. bottom -1px closes the sub-pixel seam that fractional
 * layout heights open between the mask and the edge. */
.td-edge-bottom::after {
  bottom: -1px;
}

/* TOP: the motif sits ABOVE the owner's border box and rises into the section
 * before it - `bottom: 100%`, so the band is exactly [ownerTop - H, ownerTop] and
 * its ink is flush with the owner's own top edge. `top: 0` was the defect: it put
 * the band INSIDE the owner, where a vertical flip pointed the crest up while the
 * ink still sat over the owner's own first H pixels of content. No flip is needed
 * now - the mask's ink is flush with the band's bottom edge already, which is the
 * edge that meets the owner.
 *
 * Painting above the owner is only safe because the RECEIVER below reserves the
 * room (the clearance rules), which is the whole difference between this and the
 * negative-offset failure class: a band above the owner's box is somebody's
 * clearance, and that somebody is named. */
.td-edge-top::before {
  bottom: 100%;
}

/* A second top edge adjacent to the first would read as one repeated stamp, so
 * the alternate flips on X. Apply it to the second of two consecutive
 * boundaries. */
.td-edge-top.td-edge-alt::before,
.td-edge-bottom.td-edge-alt::after {
  transform: scaleX(-1);
}

/* --------------------------------------------------------------------------
 * CLEARANCE. Mandatory whenever the motif is on. Every value is sized off the
 * same clamp as the motif, so nothing has to be re-tuned per breakpoint.
 * -------------------------------------------------------------------------- */

/* THE RULE. Padding goes on the element whose INK the band would cover - the
 * RECEIVER - and never on the owner just because the owner declares the motif.
 * Both of the shipped clearance rules got this wrong before 2.2.2 and both read
 * healthy in every box-model check while the band sliced text.
 *
 * There are exactly two receivers, one per direction:
 *   BOTTOM edge  the band is [ownerBottom + 1 - H, ownerBottom + 1], i.e. INSIDE
 *                its owner. The ink it covers is the owner's OWN last line, so
 *                the owner is the receiver and takes padding-block-end.
 *   TOP edge     the band is [ownerTop - H, ownerTop], ABOVE its owner. The ink
 *                it covers belongs to whatever precedes the owner, so THAT
 *                element takes padding-block-end - never the owner.
 *
 * A padding PROPERTY, not --dw-row-space-*. Swift resolves a grid row's padding
 * from an attribute rule, `[data-swift-gridrow][data-dw-row-space-bottom="0"]`,
 * whose (0,2,0) out-specifies a (0,1,0) class, so a class that sets the variable
 * is silently discarded on every row an editor has set to spacing step 0 - which
 * is every poster hero. The variable is set too, at attribute specificity, so
 * Swift's own padding declaration computes the same number and an editor reading
 * the row's spacing sees no contradiction. */

/* BOTTOM edge: the owner is the receiver. */
.td-edge-bottom {
  padding-block-end: calc(var(--td-edge-h) + 14px);
}
.td-edge-bottom[data-swift-gridrow] {
  --dw-row-space-bottom: calc(var(--td-edge-h) + 14px);
}

/* TOP edge inside main: the element BEFORE the owner is the receiver. Block 26
 * no longer creates one of these (see below), but the utility stays safe for any
 * site that applies .td-edge-top to a row by hand. */
main > *:has(+ .td-edge-top) {
  padding-block-end: calc(var(--td-edge-h) + 14px);
}
main > [data-swift-gridrow]:has(+ .td-edge-top) {
  --dw-row-space-bottom: calc(var(--td-edge-h) + 14px);
}

/* The footer crest, which is the sitewide instance and the one that was 0.00px
 * clear in both identities on every page. Its receiver is MAIN: the band rises
 * out of the footer into the gap at the end of the page, so the page's own end
 * padding is the clearance and the footer's top padding is irrelevant to it. The
 * old rule padded the footer - the owner - and reserved nothing at all where the
 * band actually paints.
 *
 * Keyed on the footer carrying the crest, so the reservation appears only when
 * block 26 has applied the class and the motif is inert without it. */
main:has(+ footer.td-edge-top) {
  padding-block-end: calc(var(--td-edge-h) + 14px);
}

/* WHY THERE IS NO TOP-EDGE INSTANCE INSIDE MAIN ANY MORE. A top-anchored band is
 * measured against the ink of the scope it paints into, and for an owner inside
 * <main> that scope is all of main - including every row BELOW the owner. A
 * boundary row halfway down the page therefore measures its band against ink
 * 1700px underneath it, and no padding value anywhere can close a gap that is
 * negative by construction. The band is not the problem; the arrangement is. A
 * top anchor is sound only when its owner terminates the ink it threatens, which
 * is true of exactly one element on a page: the footer. Every boundary inside
 * main is therefore drawn as a BOTTOM edge on the row above it - the same band in
 * the same place, with the ink it covers owned by the element that reserves it. */

/* =============================================================================
 * #23 THE LAPTOP BAND - the header line has to fit between 992 and 1440 as well
 * The sheet carries three breakpoints - 767.98, 991.98 and 992 - so as far as
 * every rule above is concerned a 1366 laptop and a 1440 desktop are one tier.
 * They are not. The header lays the logo lockup, the megamenu nav row, the icon
 * cluster and the search field out on one flex line, and that line's min-content
 * width is a fixed budget: the logo figure is inline-hardcoded at 210px, the
 * nav decorations this sheet adds above (gap, padding-inline, caret) are sized
 * in rem and em, and the header container keeps its stock gap. None of it
 * shrinks. At 1440 the budget fits with a few pixels to spare. At 1366 it does
 * not, and the page is pushed wider than the viewport - measured at 1432 against
 * a 1366 viewport on /home AND on the PLP, the same 66px on two unrelated page
 * bodies, which is the proof that the source is the header they share and not
 * either body.
 *
 * The search field is the casualty, not the cause. The relaxation above zeroes
 * the 260px minimum Swift ships on its inner wrapper and supplies no basis in
 * its place, so the field is the one item on the line that can absorb the
 * overcommit - and it absorbs all of it, collapsing to a measured 0 x 58 while
 * the line still overflows. Removing the victim floor was never going to fix an
 * overcommitted line.
 *
 * So this block fixes the budget instead. Between 768 and 1440 the four
 * contributions that are sized for 1440 and refuse to shrink are closed to
 * values the mobile tier already proves usable, and the search field is given a
 * basis so it grows into whatever that frees. The arithmetic, at the 7-item bar
 * HEADER-01 measured: the logo returns 40px, the nav gap 2.4px per gap, the
 * per-item inline padding 6.4px, the caret 2.1px, which is 40 + 14.4 + 44.8 +
 * 14.6 = 113.8px against a 66px overcommit. Every term except the logo is
 * per-item, so the margin widens as the bar does - the direction a fix has to
 * fail safe in.
 *
 * The field keeps the min-width: 0 the relaxation gave it. That is deliberate:
 * a hard floor here would be a new fixed budget on the same line, which is the
 * bug this block exists to remove. It is given a flex basis and allowed to
 * shrink, so it can never be the thing that pushes the document again.
 *
 * Nothing here sets overflow on any header element - the standing guard: an
 * overflow context on the header subtree clips the megamenu and offcanvas
 * panels, which at this width still have to open.
 * ========================================================================== */
[data-td-block="23"] { --td-block: 23; }

@media (min-width: 768px) and (max-width: 1439.98px) {
  /* No header child keeps an automatic minimum size it is not willing to give up. */
  [data-swift-page-header] [data-swift-container] > * { min-width: 0; }

  /* The logo lockup. Block #13 clamps the figure below 768 because the icon
   * cluster needs the room; between 768 and 1440 the line needs it just as much,
   * and the SVG scales inside whatever the figure is given. */
  header [data-dw-itemtype="swift-v2_logo"] figure { width: 170px !important; }
  header [data-dw-itemtype="swift-v2_logo"] svg { width: 100%; height: auto; }

  /* The header container's own gap, tightened the way block #13 tightens it below
   * 768 - the stock gap is sized for a line that has slack. */
  [data-swift-page-header] [data-swift-container] { gap: .5rem !important; }

  /* The nav row's own decorations, which are what this sheet adds to the budget. */
  .megamenu-wrapper > nav.d-flex { gap: .1rem; }
  .megamenu-wrapper > nav > .nav-item > .nav-link { gap: .2rem; padding-inline: .1rem; }
  .megamenu-wrapper > nav > .nav-item.dropdown > .nav-link::after {
    width: .34em;
    height: .34em;
    margin-left: 0;
  }

  /* The search field grows into the space the four rules above return. Basis, not
   * floor: it still shrinks before the line does. */
  [data-dw-itemtype="swift-v2_searchfield"] { flex: 1 1 16.25rem !important; }
  [data-dw-itemtype="swift-v2_searchfield"] .js-async-fetch-placeholder { width: 100%; }
}

/* ==========================================================================
 * BLOCK 24 - THE PRICE LOCK. The signed-out affordance where a price would be.
 *
 * WHAT IT DRESSES. The markup emitted by the theme's own
 * Paragraph/Swift-v2_ProductPrice/PriceWithSignIn.cshtml variant, and nothing
 * else. Three elements: a padlock glyph, a label, and - on a detail page only -
 * a sign-in anchor. Stock Swift renders an empty div in this state, so a shopper
 * who is not signed in currently sees a blank column where the price belongs.
 *
 * IT IS INERT TWICE OVER. Nothing here paints unless (a) a paragraph's Template
 * field names the variant AND (b) the area's AnonymousUsers field carries
 * "price" and the visitor is anonymous. On a site that shows prices to everyone
 * the variant never emits .td-price-lock, so this block costs zero pixels.
 *
 * THE NAMESPACE IS td-, like every other class this sheet owns. A lock badge is
 * theme surface, not content: a re-skin restyles it by overriding these four
 * rules and never by editing the template that emits it.
 *
 * LIST VS DETAIL. BOTH carry the anchor (#1159). A listing that names a gate and
 * offers no way past it is a dead end on the one surface a buyer scans, and it was
 * the last remnant of the marine "PLP anon price substitute" row. The two differ in
 * SHAPE only: td-price-lock--pdp is the detail modifier and stacks the anchor onto
 * its own full-width line, while the list instance stays a chip that WRAPS - the
 * anchor drops under the label when the column is narrow instead of pushing the row
 * wider. That wrap is what keeps #1156's width budget intact: the price cell is
 * flex: 0 0 auto, so anything it cannot wrap it charges to the row.
 *
 * NO COLOUR OF ITS OWN. Everything below resolves through the --td-* tokens and
 * currentColor, so a palette swap carries the badge with it and a dark colour
 * scheme does not have to be special-cased.
 * ========================================================================== */
[data-td-block="24"] { --td-block: 24; }

.td-price-lock {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  min-width: 0;
  gap: .375rem;
  padding: .25rem .5rem;
  border: 1px solid var(--td-hairline);
  border-radius: var(--td-radius-sm);
  background-color: var(--td-accent-soft);
  color: var(--td-slate-mid);
  font-size: .8125rem;
  line-height: 1.25;
}

.td-price-lock__icon {
  flex: 0 0 auto;
  opacity: .8;
}

.td-price-lock__label {
  white-space: nowrap;
}

/* The detail-page instance carries the anchor, so it stops being a chip: it
 * stacks, aligns to the start of its column, and the anchor gets its own line
 * rather than fighting the label for the row. */
.td-price-lock--pdp {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .5rem;
  padding: .5rem .75rem;
}

.td-price-lock--pdp .td-price-lock__icon,
.td-price-lock--pdp .td-price-lock__label {
  display: inline-block;
  vertical-align: middle;
}

/* The list chip's anchor is a compact second line, never a stretched block: it
 * wraps under the label rather than widening a column the row cannot spare. */
.td-price-lock__action {
  max-width: 100%;
  text-align: center;
  white-space: nowrap;
}

.td-price-lock--pdp .td-price-lock__action {
  align-self: stretch;
}

/* ==========================================================================
 * BLOCK 25 - THE ANCHOR STRIP. Dressing for the TC_AnchorNav item type the
 * surface layer ships.
 *
 * WHAT IT DRESSES. One <nav data-td-anchornav> holding a flat list of in-page
 * jump links. The strip renders its shell even with no links in it - an element
 * that vanishes when its data is missing is a section nobody can measure - so
 * these rules have to look deliberate when the list is empty as well as full.
 *
 * STICKY, BUT ONLY WHERE THERE IS ROOM. Below 768 a sticky strip eats a third
 * of a phone viewport on a page whose whole point is long-form content, so it
 * sticks from the small-tablet tier up and scrolls away on a phone.
 *
 * SCROLL-MARGIN IS THE OTHER HALF. A sticky strip and an #id target collide by
 * construction: the browser scrolls the heading to the viewport top and the
 * strip is already sitting there. scroll-margin-block-start on the headings the
 * strip points at is the fix, sized off the same variable the strip's own height
 * is, so the two cannot drift.
 *
 * NO COLOUR OF ITS OWN - --td-* tokens and currentColor throughout.
 * ========================================================================== */
[data-td-block="25"] { --td-block: 25; }

:root {
  --td-anchornav-h: 3rem;
}

.td-anchornav {
  border-block-end: 1px solid var(--td-hairline);
  background-color: var(--td-white);
}

.td-anchornav__list {
  min-height: var(--td-anchornav-h);
  align-items: center;
  overflow-x: auto;
  scrollbar-width: none;
}

.td-anchornav__list::-webkit-scrollbar { display: none; }

.td-anchornav__link {
  display: inline-block;
  padding-block: .5rem;
  color: var(--td-slate-mid);
  text-decoration: none;
  white-space: nowrap;
  border-block-end: 2px solid transparent;
}

.td-anchornav__link:hover,
.td-anchornav__link:focus-visible {
  color: var(--td-accent);
  border-block-end-color: var(--td-accent);
}

@media (min-width: 768px) {
  .td-anchornav {
    position: sticky;
    top: 0;
    z-index: 2;
  }

  /* The strip's own height, reserved above every heading it can jump to, so a
   * jump does not land the heading underneath the strip. */
  main h2[id] {
    scroll-margin-block-start: calc(var(--td-anchornav-h) + 1rem);
  }
}

/* =============================================================================
 * #27 THE SIGNED-IN PLP ROW HAS A WIDTH BUDGET - blocks #10 and #15 sized the
 * card for the ANONYMOUS column set
 *
 * Block #10's desktop discipline pins five of the seven card columns to fixed,
 * NON-SHRINKABLE bases - image 72, number 150, short description 280 with a
 * matching max-width, stock 100, and `flex: 0 0 auto` on price and cart - and
 * gives only the header `min-width: 0`. Every one of those was measured on the
 * ANONYMOUS card, where the price column is a small badge and the cart column is
 * a stub. Signed in the same row gains a real price figure and a real cart
 * control, and nothing in the row is allowed to give the width back: `flex-shrink`
 * is 0 on five of seven, the row does not wrap, and the one column with a
 * max-width equal to its basis cannot even absorb its own text.
 *
 * MEASURED, before (Foundry #1156, legs run 20260913-111100, both roots):
 *   1440 authenticated   documentElement 1454 / 1440,  body 1454 / 1440   -> 14px
 *   1366 authenticated   documentElement 1469 / 1366,  body 1469 / 1366   -> 103px
 *   1440 anonymous       overflowX 0        1366 anonymous  overflowX 0
 *   both viewports, both identities, mobile: overflowX 0
 *   5 of 5 cards: swift-v2_productshortdescription display:flex 0 x 136 px,
 *                 still carrying 77-89 characters
 * The 14-vs-103 spread is the point: the wrap point sits BETWEEN the two widths,
 * which is why one desktop measurement would have read as a rounding error.
 *
 * THE ZERO-WIDTH CELL IS THE SYMPTOM, NOT THE CAUSE - the same shape as block #23
 * and the search field. A line that is overcommitted picks a victim, and the
 * victim is whichever item the cascade lets collapse; removing the victim's floor
 * or widening the victim moves the casualty, it does not close the overcommit.
 * So this block fixes the BUDGET, and the description's floor is a consequence of
 * that rather than the fix itself.
 *
 * THE BUDGET, at 992 and up. The non-shrinkable floor of the signed-in row was
 * 72 + 150 + 280 + 100 + price + cart, with the header the only column able to
 * give anything back and the description unable to give back even its own
 * max-width. It is now 72 + stock + price + cart, and every other column is
 * shrinkable against a stated floor:
 *   image        0 0 72px      unchanged - the one genuinely fixed column
 *   number       0 1 150px     shrinkable; SKU is the column a buyer scans, so it
 *                              keeps its basis and gives width only under pressure
 *   header       1 1 12rem     with min-width 0, as before
 *   description  1 1 10rem     min-width 6rem, max-width none, and it WRAPS -
 *                              block #10's nowrap/overflow/ellipsis trio is what
 *                              turned a too-narrow cell into a zero-height line
 *                              instead of a second line of text
 *   stock        0 0 auto      min-width 5.5rem, still margin-left auto
 *   price, cart  0 0 auto      min-width 0, so they contribute their content and
 *                              not an automatic minimum on top of it
 * and the row WRAPS. A row that wraps cannot push the document: the cart drops to
 * a second line of the same card before the line overflows, which is the failure
 * mode this page is allowed to have. Nothing here sets `overflow` on the row, the
 * card or either root - the probe measures both roots precisely to catch a theme
 * hiding the scrollbar that would have shown a human the same defect.
 *
 * BETWEEN 992 AND 1440 the budget is tightened again, the way block #23 tightens
 * the header line over the same band: the image returns 16px and the number and
 * description floors come down, because 1366 is where the measurement says the
 * line actually breaks.
 *
 * NOT MEASURED AFTER, and this is stated rather than implied: this session is
 * read-only on foundry.mydwsite4.com, so the sheet has not been staged and the
 * after-widths are the computed floor above, not a measurement. The e2e session
 * validates it with `pwsh runs\v5-prep\Invoke-V5Legs.ps1 -Legs design
 * -PersonaUser TruvioBuyer` - the two COLLAPSE-01 and two VISIBLE-01
 * AUTHENTICATED rows must print overflowX 0 on both roots while the anonymous
 * rows stay PASS. A -SkipPersona run cannot validate it: the two columns that
 * cause the overcommit do not exist there.
 * ========================================================================== */
[data-td-block="27"] { --td-block: 27; }

@media (min-width: 992px) {
  /* The row itself: it may wrap, and no child keeps an automatic minimum it is
   * not willing to give up. */
  .product-list article.product section > [data-swift-container] {
    flex-wrap: wrap;
    column-gap: .75rem;
    row-gap: .35rem;
  }
  .product-list article.product section > [data-swift-container] > * { min-width: 0; }

  .product-list article.product [data-dw-itemtype="swift-v2_productnumber"] {
    flex: 0 1 150px !important;
  }
  .product-list article.product [data-dw-itemtype="swift-v2_productheader"] {
    flex: 1 1 12rem !important;
    min-width: 0;
  }
  /* The description shrinks and wraps instead of collapsing. The floor is a floor,
   * not a basis: it is the width below which the cell stops being readable, and
   * the row wraps rather than taking it below that. */
  .product-list article.product [data-dw-itemtype="swift-v2_productshortdescription"] {
    flex: 1 1 10rem !important;
    min-width: 6rem;
    max-width: none;
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
  }
  .product-list article.product [data-dw-itemtype="swift-v2_productstock"] {
    flex: 0 0 auto !important;
    min-width: 5.5rem;
  }
  .product-list article.product [data-dw-itemtype="swift-v2_productprice"],
  .product-list article.product [data-dw-itemtype="swift-v2_productaddtocart"] {
    min-width: 0;
  }
}

@media (min-width: 992px) and (max-width: 1439.98px) {
  .product-list article.product [data-dw-itemtype="swift-v2_productdefaultimage"] {
    flex: 0 0 56px !important;
  }
  .product-list article.product [data-dw-itemtype="swift-v2_productnumber"] {
    flex: 0 1 8.75rem !important;
  }
  .product-list article.product [data-dw-itemtype="swift-v2_productheader"] {
    flex: 1 1 9rem !important;
  }
  .product-list article.product [data-dw-itemtype="swift-v2_productshortdescription"] {
    flex: 1 1 8rem !important;
    min-width: 5rem;
  }
}
