/* ==========================================================================
   Mouldshop theme — Swift v2 (M2 / TASK-0006, slice 1) — DEC-0005
   Swift = functional substrate only. This is OUR presentation layer for the
   RECONSTRUCTED chrome (own markup in MouldshopHeader.cshtml / MouldshopSlideText.cshtml,
   rendered by our Swift-v2_Page.cshtml override). No Swift header CSS is relied on:
   our markup uses only .ms-* classes, so Swift's demo-header styles paint nothing.
   Loaded after swift.css via the CustomHeadInclude hook (MouldshopHead.cshtml).
   Builds to DESIGN_SPEC.md §0 (tokens) + §1 (utility) + §2 (header) + §4 (slide-text).
   Mega-menu styles live in mouldshop-megamenu.css.
   ========================================================================== */

/* Brand fonts — SELF-HOSTED since TASK-0063. See mouldshop-fonts.css.
   The @import that used to sit here pulled Montserrat and Roboto from fonts.googleapis.com
   on every page view: measured, it was 3 external requests per load (the CSS plus two
   woff2). That is a render-blocking third-party dependency and it hands a client
   storefront's visitor IPs to Google, so TASK-0063's halt-bar forbids it. The woff2 files
   Google was serving are now committed under Custom/fonts/ and declared locally — the
   SAME files, fetched once at build time from the same URLs, so the swap is like-for-like
   and changes no glyph.
   The old comment here warned that DW's cache-buster corrupts a Google Fonts query string
   on a <link>. That is still true and is now moot: a local stylesheet has no query string
   to corrupt, so mouldshop-fonts.css is registered normally in MouldshopHead.cshtml. */

/* --------------------------------------------------------------------------
   0. Design tokens (RESOLVED — Mouldshop BDG, DESIGN_SPEC.md §0)
   -------------------------------------------------------------------------- */
:root {
  /* Brand palette */
  --ms-navy:       #0A2979;  /* Navy Blue 01 — primary */
  --ms-navy-deep:  #001D52;  /* Dark Blue 04 — deepest bg / gradient end */
  --ms-blue:       #6CB2DE;  /* Blue 02 — ACCENT (replaces Mouldshop yellow) */
  --ms-black:      #000000;
  --ms-white:      #FFFFFF;
  --ms-gradient:   linear-gradient(-45deg, #0A2979, #001D52); /* BDG Gradient I */

  /* Functional navy/blue tints (UI only, derived) */
  --ms-navy-80:    rgba(10, 41, 121, .80);
  --ms-navy-60:    rgba(10, 41, 121, .60);
  --ms-navy-40:    rgba(10, 41, 121, .40);
  --ms-blue-60:    rgba(108, 178, 222, .60);

  /* THE RULE COLOUR - every divider in the project. Operator, 2026-09-06: "all lines in the
     entire project, any divider". Taken from the reference's own `--BON-BORDER`, which is
     rgba(10, 41, 121, 0.35) - the brand navy at 35%, not a grey. Card separators and the
     category-grid crosses both resolve to this; changing it here changes every line. */
  --ms-rule:       rgba(10, 41, 121, .35);

  /* Functional neutrals (non-brand greys) */
  --ms-grey-5:     #F2F4F7;
  --ms-grey-20:    #E4E7EC;

  /* Typography */
  /* TASK-0063: the families are declared ONCE, in mouldshop-fonts.css, as --ms-font-display
     and --ms-font-body with their full system fallback stacks. These two lines are aliases so
     that every existing surface keeps working untouched — item 5 of that packet is that
     nothing else is restyled, and there are 14 call sites for --ms-font-head in this file
     alone. They are ALIASES rather than copies on purpose: two independent definitions of the
     same stack drift, and this file loads AFTER mouldshop-fonts.css, so a copy here would
     silently win and the fallback stack in the font file would be dead code.
     Retiring --ms-font-head in favour of --ms-font-display is a later packet: it needs every
     call site moved in one go, across seven stylesheets. */
  --ms-font-head:  var(--ms-font-display);           /* headings, titles, nav */
  --ms-font-body:  var(--ms-font-body-stack);        /* body / running text */

  /* Chrome dimensions (DESIGN_SPEC.md §1–§2; from mouldshop-header.css) */
  --ms-utilitybar-h: 44px;
  --ms-header-h:     100px;
  --ms-chrome-h:     144px;  /* utility + header (mobile overlay offset) */
  /* --ms-page-width is now a STEP of the container ladder below, not a constant.
     It was 1200px (Mouldshop page_width, config/settings_data.json) and that value was
     what the operator measured in the header before TASK-0031. */
}

/* ==========================================================================
   THE CONTAINER LADDER — ruling 13, TASK-0031. ONE BLOCK, and this is it.

     >= 1400        ->  1288px
     1200 - 1399    ->  1080px
     < 1200         ->  full width, less the page gutter

   CHROME AND BODY ARE ONE LADDER HERE AND WERE TWO BEFORE. Measured before this
   packet, on /en-us/shop: the chrome capped at 1200 at every width above it, while the
   body ran on vendor Swift's own container tokens — and THE SIGN OF THE MISMATCH FLIPS.
   At 1440 the body was 1393 and the chrome 1200 (body wider by 193). At 1300 the body was
   1138 and the chrome 1200 (body NARROWER by 62). Checking that at one width would have
   given the wrong answer about which way it ran, which is why it went unrecorded.

   WHERE EACH SIDE'S WIDTH ACTUALLY CAME FROM, read out of the CSSOM, not assumed:
     chrome : .ms-header-inner / .ms-utilitybar-inner, max-width: var(--ms-page-width),
              declared right above. A sweep of every element on the page computing
              max-width: 1200px returned those two and nothing else. THE 1200 WAS OURS —
              not Mouldshop's, not vendor Swift's.
     body   : swift.css, [data-swift-container] { max-width: calc(var(--dw-container-width)
              - var(--dw-container-gutter)) }, with --dw-container-width stepping
              100% -> 90vw at 992 -> min(105rem, 100%) at 1400, gutter calc(2rem).

   NOTE THE VENDOR ALREADY BREAKS AT 1400. Ruling 13's 1400 is new to our stylesheets and
   to the runbook; it is NOT new to the platform. The two steps coincide exactly.

   WHY THE BODY SIDE RE-POINTS THE TOKEN INSTEAD OF SETTING max-width.
   Overriding max-width outright would strand the vendor's gutter arithmetic and its
   per-paragraph width variants. Re-pointing --dw-container-width keeps both. The body
   value is the ladder step PLUS the gutter, because the vendor subtracts the gutter again
   in its own calc — so the RENDERED width lands exactly on the ladder step, which is what
   "chrome and body measure the same width" has to mean.

   TWO THINGS THE OVERRIDE DELIBERATELY DOES NOT TOUCH, and the second one BIT on the
   first run — it is excluded by measurement, not by assumption:

     1. THE EXPLICIT WIDTH VARIANTS. data-dw-container-width="1" (text width, 80ch),
        "2" (65vw) and "4" (full bleed, 100%) are deliberate per-ROW choices. The home
        page alone carries five "1" containers at 687px, six "2" at 904px and two "4" running
        the full 1425. They are excluded by selector. "3" is the default and is what the page
        container uses; an absent attribute is covered too, which is why this is three :not()
        clauses rather than [data-dw-container-width="3"].

        THE EXCLUSION IS WHAT HONOURS THEM. DEC-0057 / TASK-0081, and it is worth stating
        because the decision that ordered the fix read this rule the other way round —
        as "the ladder is applied to 3 and 1, 2 and 4 are ignored, so any other value escapes
        with no width at all". Nothing escapes. This rule re-points a TOKEN and never sets
        max-width; vendor's single max-width: calc(var(--dw-container-width) -
        var(--dw-container-gutter)) does the sizing for every container on the page. Exclude a
        value here and it falls through to vendor's own rule for that value, which is exactly
        DEC-0057 ruling 4 — "the same widths DW gives vendor rows, measured from vendor rows,
        not invented".

        MEASURED AT SIX VIEWPORTS, OURS == VENDOR TO THE PIXEL (client 1905/1425/1285/1085/
        753/375; scripts/verify/row-width.mjs part B re-measures it every run):

            1  687 / 687 / 687 / 642 / 721 / 343      vendor 80ch, 75ch below 1200
            2 1216 / 904 / 813 / 683 / 721 / 343      vendor 65vw
            3 1288 /1288 /1080 /1053 / 721 / 343      OURS, the DEC-0014 ladder
            4 1905 /1425 /1285 /1085 / 753 / 375      vendor 100%, gutter 0
          abs 1288 /1288 /1080 /1053 / 721 / 343      == 3, via this same :not() chain

        Below 992 vendor declares no rule for 1 or 2, so both collapse onto the base
        container — which is why 1, 2 and 3 all read 721 and 343 at the bottom two steps.
        That is vendor behaviour and it is not ours to correct here.

        SO DO NOT ADD PER-VALUE RULES FOR 1, 2 OR 4. Restating 65vw or 80ch as our constant
        is a second copy of a value we do not own, and it diverges the moment vendor or the
        root font size moves. Q-0137.

     2. NESTED CONTAINERS — and the first draft of this rule got them wrong.
        On /en-us/shop every nested [data-swift-container] computes max-width: none, so a
        token re-point is inert on them and the exclusion looked unnecessary. THAT WAS TRUE OF
        ONE PAGE. The home page has a nested container that DOES cap:
        div.position-relative.z-1, depth 1, no width attribute, sitting inside the full-bleed
        "4" hero. With the rule unscoped it went from running the full 1425 to capping at
        1288 — a real change to the hero's inner width, on a surface TASK-0023 owns and this
        packet does not.
        Ruling 13 may well want the hero's CONTENT on the ladder while its BACKGROUND bleeds;
        that is a design call, it belongs to the operator, and it is not made here by accident.
        So the selector now excludes every descendant of a container, and vendor nesting
        behaves exactly as it did before this packet. Logged for TASK-0023.
   ========================================================================== */

/* THE CHROME'S SIDE PADDING IS PART OF THE LADDER, NOT A CONSTANT — operator, 2026-09-03:
   "remove side padding from ms-header-inner, keep only on full width containers".

   Why it matters, measured: the body container has NO inner padding, so its content starts at
   the container's own edge. The chrome carried 1.5rem inside its container, so even once
   TASK-0031 made the two boxes the same width and the same centre, chrome content still sat
   24px further in than body content at every capped width. The boxes lined up and the CONTENT
   did not.

   At the capped regimes (1288 and 1080) the container edge already IS the page margin, so the
   padding is doing nothing but breaking that alignment: it goes to 0. At the full-width regime
   the container runs to within 16px of the viewport, and there the padding is the only thing
   keeping the logo off the screen edge: it stays.

   The <= 749 rule further down sets it to 0 again — that is Mouldshop's full-bleed mobile
   header and it predates this, so it is left alone rather than folded in. */
:root {
  /* < 1200: full width less the vendor page gutter (2rem = 32px), so the chrome sits on
     the same margin the body already had rather than running edge to edge. */
  --ms-page-width: calc(100% - 2rem);
  --ms-page-gutter: 1.5rem;
}

@media (min-width: 1200px) {
  :root { --ms-page-width: 1080px; --ms-page-gutter: 0px; }
}

@media (min-width: 1400px) {
  :root { --ms-page-width: 1288px; --ms-page-gutter: 0px; }
}

/* BODY. The +gutter is not a fudge: vendor's rule is
   max-width: calc(var(--dw-container-width) - var(--dw-container-gutter)),
   so adding the gutter here makes the rendered width equal --ms-page-width exactly. */
[data-swift-container]:not([data-dw-container-width="1"]):not([data-dw-container-width="2"]):not([data-dw-container-width="4"]):not([data-swift-container] *) {
  --dw-container-width: calc(var(--ms-page-width) + var(--dw-container-gutter));
}

/* --------------------------------------------------------------------------
   Typography foundation — Roboto body, Montserrat headings/nav.
   Swift resolves fonts through its own --dw-font-family chain; set THAT, then
   re-scope on headings/nav for Montserrat. mouldshop.css loads after Swift's
   typography sheet, so these win.
   -------------------------------------------------------------------------- */
body {
  --dw-font-family: var(--ms-font-body);
  font-family: var(--ms-font-body);
}
h1, .h1, .dw-h1, h2, .h2, .dw-h2, h3, .h3, .dw-h3,
h4, .h4, .dw-h4, h5, .h5, .dw-h5, h6, .h6, .dw-h6 {
  --dw-font-family: var(--ms-font-head);
  font-family: var(--ms-font-head);
}

/* ==========================================================================
   1–2. Site header — sticky shell (utility bar + header bar as one unit)
   ========================================================================== */
.ms-site-header {
  position: sticky;
  top: 0;
  z-index: 1000;
  background: var(--ms-navy);
  transition: transform .3s ease;
}
/* hide-on-scroll-down (toggled by mouldshop.js) */
.ms-site-header.ms-header-hidden {
  transform: translateY(-100%);
}

/* ── 1. Utility bar — Mouldshop: LIGHT strip, navy text, contact left / links+icons right ──
   (skin from _reference/Designs/Mouldshop_Web-26_home.jpg; structure = Mouldshop top-bar) */
.ms-site-header { background: #efefef; }   /* shell matches the light utility strip */
.ms-utilitybar {
  height: var(--ms-utilitybar-h);
  background: #efefef;   /* operator eyedrop from mockup */
  border-bottom: 1px solid var(--ms-grey-20);
  display: flex;
  align-items: center;
}
.ms-utilitybar-inner {
  max-width: var(--ms-page-width, 1200px);   /* same container as header → the row ends over the header's right edge */
  width: 100%;
  margin: 0 auto;
  /* Same gutter token as .ms-header-inner, deliberately. The utility bar sits directly above
     the header in the same container; padding one and not the other would misalign the two
     rows of chrome against each other. */
  padding: 0 var(--ms-page-gutter, 1.5rem);
  display: flex;
  /* ALL RIGHT (operator, 2026-09-24: "very top bar - mail and phone to the right next to person
     icon"). Was space-between, contact left and account right. The gap between the two groups
     is the contact group's own 1.5rem, so phone, mail and the person icon read as one row. */
  justify-content: flex-end;
  align-items: center;
  gap: 1.5rem;
}
.ms-util-contact { display: flex; align-items: center; gap: 1.5rem; }
.ms-util-right   { display: flex; align-items: center; gap: 1.25rem; }
.ms-util-link {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-family: var(--ms-font-body);
  font-size: 13px;
  color: var(--ms-ink);
  text-decoration: none;
  transition: color .2s ease;
}
.ms-util-link:hover { color: var(--ms-blue); }
.ms-util-ico { flex: 0 0 auto; }
.ms-util-navlink {
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  font-size: 12px;
}
.ms-util-icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ms-ink);
  text-decoration: none;
  transition: color .2s ease;
}
.ms-util-icon:hover { color: var(--ms-blue); }
.ms-accent { color: var(--ms-blue); font-weight: 500; }   /* retained: slide-text emphasis */
@media (max-width: 749px) {
  .ms-util-contact .ms-util-link span { display: none; }   /* icons only on phones */
  .ms-util-navlink { display: none; }
}

/* ── 2. Header bar (DESIGN_SPEC.md §2) ── */
.ms-header-bar {
  position: relative;          /* anchor for the full-width mega-menu dropdown */
  min-height: var(--ms-header-h);
  background: var(--ms-navy);
}
/* page-width container (Mouldshop/Dawn .header.page-width), centred — replaces the 1800
   left-packing flex that left dead navy space on the right. Grid distributes the three
   zones: logo (left) | search (fills middle) | nav/actions (flush right). */
.ms-header-inner {
  max-width: var(--ms-page-width, 1200px);
  width: 100%;
  margin: 0 auto;
  min-height: var(--ms-header-h);
  padding: 0 var(--ms-page-gutter, 1.5rem);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 2rem;
}
@media (max-width: 749px) {   /* Mouldshop ≤749: header runs full-bleed (no page padding) */
  .ms-header-inner { padding-left: 0; padding-right: 0; column-gap: .75rem; }
}

/* Logo — white logotype (desktop) / white signet (< 750px). Never recoloured/distorted. */
.ms-logo { display: inline-flex; align-items: center; flex: 0 0 auto; text-decoration: none; }
.ms-logo-desktop { height: 40px; width: auto; display: block; }   /* = search pill height (mockup) */
.ms-logo-signet  { height: 40px; width: auto; display: none; }
@media (max-width: 749px) {
  .ms-logo-desktop { display: none; }
  .ms-logo-signet  { display: block; }
}

/* Primary nav — grid col 3, flush to the container's right edge (Mouldshop actions zone). */
.ms-primary-nav {
  display: flex;
  align-items: center;
  gap: .25rem;
  justify-self: end;
  height: var(--ms-header-h);
}
.ms-nav-link {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  height: var(--ms-header-h);
  padding: 0 1rem;
  background: transparent;
  border: 0;
  cursor: pointer;
  color: var(--ms-white);
  font-family: var(--ms-font-head);
  font-weight: 600;
  font-size: 15px;
  text-decoration: none;
  position: relative;
  transition: color .2s ease;
}
.ms-nav-link:hover,
.ms-nav-link.is-open,
.ms-nav-link[aria-expanded="true"] { color: var(--ms-blue); }
/* NO UNDERLINE (operator, 2026-09-24: "this strange underline animation on hover menu? i dont
   like this"). The animated scaleX underline is removed; hover and open state are the colour
   change above (white -> blue). */
.ms-mm-caret { transition: transform .2s ease; }
.ms-nav-link[aria-expanded="true"] .ms-mm-caret { transform: rotate(180deg); }

/* SHOP IS A CAPSULE BUTTON (operator, 2026-09-24: "SHOP - change this element into the capsule
   button - blue - with white text"). Only the mega-menu trigger; the plain nav links keep the
   text style. Pill radius as every other button (--ms-buybox-radius), height of the search pill
   (34px) instead of the full 100px bar. NAVY text in EVERY state (operator, same day: "shop text
   in button - navy"; it was white). The default link hover turns text blue, which would vanish
   on a blue ground, so every state is pinned. Navy on Blue 02 is about 5.6:1, above WCAG AA. */
.ms-primary-nav .ms-nav-link.menu-btn {
  height: 34px;
  padding: 0 1.25rem;
  border-radius: var(--ms-buybox-radius, 999px);
  background: var(--ms-blue);
  color: var(--ms-navy);
}
.ms-primary-nav .ms-nav-link.menu-btn:hover,
.ms-primary-nav .ms-nav-link.menu-btn.is-open,
.ms-primary-nav .ms-nav-link.menu-btn[aria-expanded="true"] { color: var(--ms-navy); }
.ms-primary-nav .ms-nav-link.menu-btn:focus-visible {
  outline: 2px solid var(--ms-white);
  outline-offset: 2px;
}

@media (max-width: 989px) {
  .ms-primary-nav { display: none; }   /* mobile uses the drawer (Mouldshop 989/990 boundary) */
}

/* ── Mobile menu button (hamburger) — accent fill; desktop hides it (Products trigger covers desktop) ── */
.ms-icon-btn {
  width: 52px;
  height: 52px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  cursor: pointer;
  color: var(--ms-white);
  text-decoration: none;
  flex: 0 0 auto;
  transition: background .2s ease, color .2s ease;
}
.ms-icon-btn svg { stroke: currentColor; }
/* WHITE CIRCLE, NAVY HAMBURGER, THE LOGO'S HEIGHT (operator, 2026-09-24: "mobile menu - white
   circle with navy hamburger - same height as logo"). Was a 52px blue rounded square. 40px =
   .ms-logo-signet / .ms-logo-desktop height. Hover swaps to the blue it used to be. */
.ms-menu-btn {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--ms-white);
  color: var(--ms-navy);
}
.ms-menu-btn:hover { background: var(--ms-blue); color: var(--ms-navy); }
.ms-menu-btn:focus-visible { outline: 2px solid var(--ms-white); outline-offset: 2px; }
@media (min-width: 990px) {
  .ms-menu-btn { display: none; }   /* desktop uses the Products trigger */
}

/* ── Cart count badge — on the utility-bar cart icon ── */
.ms-cart-btn { position: relative; }
.ms-cart-count {
  position: absolute;
  top: -6px; right: -8px;
  min-width: 16px; height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--ms-navy);
  color: var(--ms-white);
  font-family: var(--ms-font-head);
  font-size: 10px; font-weight: 700;
  line-height: 16px; text-align: center;
}

/* ── Search — PERSISTENT centred pill (Mouldshop): white OUTLINE on the navy bar, fills its middle ── */
.ms-search {
  justify-self: center;     /* centre within the 1fr middle track */
  width: 100%;
  max-width: 560px;
  display: flex;
  align-items: center;
  gap: .25rem;
  height: 34px;             /* 40 -15%, operator 2026-09-24 */
  padding: 0 1rem;
  /* OUTLINE, not a white fill (operator, 2026-09-24): the white pill was the highest-contrast,
     largest shape in the bar and pulled the eye off the logo. No background, 1px white border,
     white icon, text and placeholder. */
  background: transparent;
  border: 1px solid var(--ms-white);
  border-radius: 999px;
}
/* LIVE SEARCH (TASK-0163a, operator 2026-09-28 "pill keeps focus"): while the search drawer is
   open the header sits ABOVE Bootstrap's overlay (1040) and below the drawer (1045), so the pill
   stays visible and clickable. Both elements, because either can be the stacking root: the
   wrapper at the top of the page, the sticky .section-header once the page has scrolled.
   Toggled on <html> by Custom/mouldshop-live-search.js. Not drawer CSS: the drawer is untouched. */
html.ms-search-drawer-open .section-header.mouldshop-header-sticky,
html.ms-search-drawer-open .mouldshop-header-wrapper {
  z-index: 1041;
}
/* DRAWER BUTTONS ARE PILLS (operator, 2026-09-28: "button - quick basket and search results -
   rounded as all button on page"). One rule for both drawer instances, the basket's Cart and
   the search's See all results, at the radius every other button uses (--ms-buybox-radius). */
.ms-cart-drawer-actions .btn {
  border-radius: var(--ms-buybox-radius, 999px);
}
.ms-search-submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px; height: 28px;
  flex: 0 0 auto;
  background: transparent;
  border: 0;
  cursor: pointer;
  color: var(--ms-white);
}
.ms-search-input {
  flex: 1 1 auto;
  width: 100%;
  border: 0;
  background: transparent;
  color: var(--ms-white);
  font-family: var(--ms-font-body);
  font-size: 15px;
  outline: none;
}
.ms-search-input::placeholder { color: var(--ms-white); opacity: 1; }   /* Firefox dims placeholders by default */
/* NO FIELD WHEN ACTIVE (operator, 2026-09-24: "on active some strange field appears as a bg for
   the text", then, with DevTools forcing :focus-visible: "SAME ... remove bg and border").
   THE CAUSE was swift.css's global focus ring - `:where(:focus-visible) { box-shadow: 0 0 0
   .25rem rgba(var(--bs-body-color-rgb), .25) }` - a translucent box around the input inside the
   pill. Code first blamed browser autofill (`874c8be`) after searching for a focus BACKGROUND
   and not a box-shadow; that guess was wrong. The ring is removed on this input only; the pill
   is the field, and the caret shows focus. The autofill neutraliser below stays: Chrome's
   autofill preview would paint the same kind of box. */
.ms-search-input:focus,
.ms-search-input:focus-visible {
  box-shadow: none;
  outline: none;
  background: transparent;
  border: 0;
}
.ms-search-input:-webkit-autofill,
.ms-search-input:-webkit-autofill:hover,
.ms-search-input:-webkit-autofill:focus,
.ms-search-input:autofill {
  -webkit-text-fill-color: var(--ms-white);
  caret-color: var(--ms-white);
  -webkit-box-shadow: 0 0 0 1000px var(--ms-navy) inset;
  box-shadow: 0 0 0 1000px var(--ms-navy) inset;
  transition: background-color 600000s 0s;
}
.ms-search-input { caret-color: var(--ms-white); -webkit-appearance: none; appearance: none; }
@media (max-width: 749px) {
  .ms-search { max-width: none; margin: 0 .6rem; height: 36px; }   /* 42 -15% (35.7), operator 2026-09-24 */
}

/* ==========================================================================
   4. Slide-text strip — infinite cross-fade rotator (DESIGN_SPEC.md §4)
   Standalone strip (slice-1: under the header; relocates under the home
   product slider in P4). Auto-only; pause on hover/focus; reduced-motion holds.
   ========================================================================== */
.ms-slidetext {
  background: var(--ms-gradient);
  min-height: 48px;
}
.ms-slidetext-inner {
  position: relative;
  max-width: 1800px;
  margin: 0 auto;
  min-height: 48px;
  padding: 0 2rem;
  display: grid;          /* stack messages in one cell for cross-fade */
}
.ms-slidetext-msg {
  grid-area: 1 / 1;
  margin: 0;
  align-self: center;
  text-align: center;
  color: var(--ms-white);
  font-family: var(--ms-font-body);
  font-size: 15px;
  opacity: 0;
  visibility: hidden;
  transition: opacity .6s ease, visibility .6s ease;
}
.ms-slidetext-msg.is-active { opacity: 1; visibility: visible; }
.ms-slidetext-strong { font-family: var(--ms-font-head); font-weight: 700; color: var(--ms-blue); }
.ms-slidetext-rest { margin-left: .4rem; }
@media (prefers-reduced-motion: reduce) {
  .ms-slidetext-msg { transition: none; }
}

/* ==========================================================================
   Mobile drill-down drawer (DESIGN_SPEC.md §3) — slide panels by mouldshop-megamenu.js
   ========================================================================== */
.ms-drawer {
  position: fixed;
  top: 0; bottom: 0; left: 0;
  width: min(420px, 88vw);
  background: var(--ms-navy);
  color: var(--ms-white);
  z-index: 1100;
  transform: translateX(-100%);
  transition: transform .3s ease;
  display: flex;
  flex-direction: column;
}
.ms-drawer.is-open { transform: translateX(0); }
.ms-drawer-header {
  display: flex;
  align-items: center;
  gap: .5rem;
  min-height: var(--ms-header-h);
  padding: 0 1rem;
  border-bottom: 1px solid var(--ms-navy-60);
}
.ms-drawer-title { font-family: var(--ms-font-head); font-weight: 700; font-size: 16px; margin-right: auto; }
.ms-drawer-back, .ms-drawer-close {
  display: inline-flex; align-items: center; gap: .35rem;
  background: transparent; border: 0; cursor: pointer;
  color: var(--ms-white); font-family: var(--ms-font-body); font-size: 14px;
}
.ms-drawer-back .ms-drawer-back-label { color: var(--ms-blue); }
.ms-drawer-panels { position: relative; flex: 1; overflow: hidden; }
/* TASK-0010: every panel (one per branch node) stacks absolutely; only .is-open shows,
   so the drawer drills to the channel's full depth (level-1 panel open at root). */
.ms-drawer-list {
  list-style: none; margin: 0; padding: .5rem 0;
  position: absolute; inset: 0;
  background: var(--ms-navy);
  overflow-y: auto;
  display: none;
}
.ms-drawer-list.is-open { display: block; }
.ms-drawer-item {
  width: 100%;
  display: flex; align-items: center; justify-content: flex-start;
  gap: .65rem;
  padding: 14px 1.25rem;
  background: transparent; border: 0; cursor: pointer;
  color: var(--ms-white);
  font-family: var(--ms-font-body); font-size: 15px; text-align: left;
  text-decoration: none;
  transition: background .2s ease, color .2s ease;
}
.ms-drawer-item:hover { background: var(--ms-navy-80); color: var(--ms-blue); }
.ms-drawer-item > svg:last-child { margin-left: auto; }   /* expand chevron right-aligned */
.ms-drawer-item .ms-mm-ico { flex: 0 0 auto; width: 18px; height: 18px; color: inherit; }
.ms-drawer-viewall { color: var(--ms-blue); font-weight: 600; }
@media (min-width: 990px) {
  .ms-drawer { display: none; }   /* drawer is mobile-only */
}

/* Foundation marker — confirms the Mouldshop layer is loaded. */
:root { --ms-theme-loaded: 1; }

/* ==========================================================================
   A ROW THAT RENDERS NOTHING OCCUPIES NOTHING — TASK-0082 defect 2.

   THE COMPLAINT, operator 2026-09-18: *"there are no variants but we see the
   section (grey field) — this component shouldn't be rendered when we don't have
   variants."*

   THE COMPONENT IS NOT THE PROBLEM. `Mouldshop_ProductVariantTable_Custom` already
   renders nothing on a product without variants — ruling 37.6, `@if (msRows.Any())`,
   and it has done since TASK-0051. What paints is its ROW: a vendor `1Column`
   section on the product detail page carrying `data-dw-colorscheme="light_1"` and
   96px of block padding top and bottom. Measured on the no-variant product at 1440:
   **192px tall, 0 characters of HTML in its only column.**

   A PARAGRAPH CANNOT REMOVE ITS OWN ROW. The row wraps the paragraph, it belongs to
   page 8454's grid, and it is rendered by the vendor row template, which we do not
   edit (DEC-0037). So the row is suppressed from the cascade instead — and the
   condition is read off what the row actually rendered, not off a marker we would
   have to remember to emit from every component.

   IT WAS NOT ONE ROW. AC 0 measured the no-variant product and found **576px across
   THREE rows**, all of them 192px of padding around nothing:
     · the second gallery paragraph (#23258) — a one-image product has no thumb strip
     · a row whose column holds NO PARAGRAPH AT ALL (empty column, no itemtype)
     · the variant-table row — the grey one the operator pointed at
   Fixing only the third would have left 384px of blank page and failed the smoke on
   its own acceptance line, "no gap where the table would be". The rule is therefore
   written on the condition, not on the component.

   SCOPE, MEASURED ON SIX PAGES AT 1440 / 1100 / 390 — this rule changes nothing that
   renders anything:
     Home, Home test, News article    0 rows,  0px          (no app column)
     Shop                             1 row,  -32px         see the note below
     PDP with 65 variants             1 row, -192px         the empty column
     PDP with no variants             3 rows, -576px        the operator's page
   Identical at all three viewports. Every row it hides reports 0 chars of innerHTML
   in every one of its columns.

   THE SHOP HIT IS REPORTED, NOT HIDDEN. On /en-us/shop the rule removes one 32px row
   whose column is `swift-v2_productlistinfo` rendering empty. Shop and the PDP are
   the same app paragraph (19174, `eCom_ProductCatalog`, on page 5862), so there is no
   selector that separates them, and 32px of padding around nothing is the same defect
   in a smaller size. Named here because the packet's out-of-scope note says report
   rather than extend silently.

   ADMIN IS DELIBERATELY UNAFFECTED. In the visual editor the variant table renders
   `div.alert` — "This product has no variants" — so the column HAS an element child,
   the row does not match, and the operator still sees the notice while authoring.
   The rule is invisible in admin by construction, not by a mode check.

   WHY `> *` AND NOT `:empty`. Razor emits whitespace text nodes into the column, so
   `:empty` never matches and a rule written on it would be a rule that cannot fire.
   `:not(:has(> ... > [data-swift-gridcolumn] > *))` asks the only question that
   matters: did any column produce an element?
   ========================================================================== */
[data-swift-gridcolumn][data-dw-itemtype="swift-v2_app"] > section[data-swift-gridrow]:not(:has(> [data-swift-container] > [data-swift-gridcolumn] > *)) {
  display: none;
}

/* ==========================================================================
   APP-RENDERED ROWS GET THEIR CONTAINER WIDTH BACK — TASK-0082 defect 1, DEC-0059.

   THE COMPLAINT, operator 2026-09-18: *"it's not a matter of settings but layout of
   this page — the container above any of section is fixed and narrow… any section
   inside this container has forced width from its parent."* The shape was right and
   the lever was not. The parent is not what disables the setting.

   WHAT THE PDP ACTUALLY IS. Paragraph 19174 lives on page **5862** and is the
   `eCom_ProductCatalog` app; the product detail page **8454** is rendered *inside*
   that paragraph's column. So every "row" on the PDP is a nested row, and the same
   is true of /en-us/shop. Neither content nesting nor a template wrapper — it is how
   DW composes an app page, and both pages are ordinary editable pages.

   THE CAUSE IS A MISSING UNIT IN VENDOR CSS.

       swift.css:  [data-swift-container] { max-width: calc(var(--dw-container-width)
                                                          - var(--dw-container-gutter)) }
       swift.css:  [data-swift-container] :where([data-dw-itemtype=swift-v2_app])
                   [data-swift-container] { --dw-container-gutter: 0 }    <-- unitless

   `calc(<length> - 0)` is invalid CSS — a length minus a plain number — so `max-width`
   falls back to its initial value **`none`**, and a nested container ignores its own
   width value completely. (The sibling rule `[data-swift-container]
   [data-swift-container]{--dw-container-gutter: 0}` carries the identical bug but sits
   inside `@media(max-width: 991.98px)`, which is why the Home hero's nested container
   still computes a real `max-width` at 1440 and stops doing so below 992.)

   PROVEN BY EXPERIMENT, NOT BY READING — eight steps on one element, PDP at 1440:

       outer 3 · inner 1   ->  1288   unchanged      max-width none
       outer 3 · inner 2   ->  1288   unchanged      max-width none
       outer 4 · inner 2   ->  1425   STILL ignores its own value
       outer 4 · inner 2 + gutter 2rem  ->   904     max-width 904px
       outer 3 · inner 2 + gutter 2rem  ->   904     max-width 904px   <-- cause proven

   Step 3 is the one that matters: **releasing the outer container does not give the
   row its setting back**, it only makes every inner row full-bleed. Setting page
   5862's row to Full — the obvious one-dropdown admin fix — does not work, and that
   is why this is code and not a click-list.

   THE REPAIR IS A UNIT, AND `0px` RATHER THAN `2rem` IS DELIBERATE. Vendor's intent
   for a nested container is no gutter — otherwise the page gutter is paid twice, once
   by the outer container and again by the inner one. The zero is right; only the unit
   is missing. Consequence worth knowing, and these are measured rather than predicted:
   an app row set to Small measures 65vw with no gutter subtracted — **936** at a 1440
   viewport — where a TOP-LEVEL vendor row at Small measures 65vw minus 2rem, **904**.
   Readability is **719** here against **687** top-level. The 32px in both cases is
   vendor's own nested-gutter intent, not a divergence of ours. (65vw counts the
   scrollbar, so it is 65% of 1440 and not of the 1425 client width — which is why the
   first draft of this comment said 926.)

   RULE 2 EXISTS SO THAT NOTHING GETS NARROWER — DEC-0059 ruling 2. With the calc
   valid again, a value-3 row would fall to vendor's base `--dw-container-width`, which
   is `90vw` between 992 and 1399: the PDP and Shop would drop from 1053 to **990px**,
   a 63px narrowing nobody asked for. Re-pointing the token to `100%` puts Regular and
   absent on the row's own box, which is exactly what they render today. Measured
   either side: 1288 / 1053 / 343 at client 1425 / 1085 / 375, unchanged.

   THE HERO IS CARVED OUT BY SELECTOR — DEC-0059 ruling 3. `:not([data-dw-container-width="4"] *)`
   on both rules. The container the ruling protects is

       div.grid[container=4] > div#20229[swift-v2_poster] > div.d-grid… > div.position-relative.z-1

   on the home page: nested, no width attribute, and it bleeds because its ancestor is
   a Full container. It is not inside an app column, so the scope alone already misses
   it — the `:not()` makes that a property of the rule rather than an accident of the
   current page tree, and `pdp-variant-table.mjs` fails if the hero ever stops bleeding.
   The same clause means a Full app row keeps full-bleed children, which is the vendor
   behaviour an author choosing Full is asking for.

   MEASURED SCOPE, six pages at 1440 / 1100 / 390: Home, Home test and the news article
   have no app column and move zero pixels. Shop and both PDPs keep every width they
   render today, and their rows now honour their own setting.
   ========================================================================== */

/* 1. restore the unit so vendor's own calc() is valid again. */
[data-swift-gridcolumn][data-dw-itemtype="swift-v2_app"] > section[data-swift-gridrow] > [data-swift-container]:not([data-dw-container-width="4"] *) {
  --dw-container-gutter: 0px;
}

/* 2. Regular and absent keep the width they render today: the row's own box. */
[data-swift-gridcolumn][data-dw-itemtype="swift-v2_app"] > section[data-swift-gridrow] > [data-swift-container]:not([data-dw-container-width="1"]):not([data-dw-container-width="2"]):not([data-dw-container-width="4"]):not([data-dw-container-width="4"] *) {
  --dw-container-width: 100%;
}

/* ==========================================================================
   THE APP HOST RELEASES ITS CAP ON A PRODUCT DETAIL PAGE — TASK-0083a.

   THE COMPLAINT, operator 2026-09-18, reading the DOM: *"we have main section —
   hardcoded — then a hardcoded container, which is the problem because it has fixed
   width — then our page view, i.e. the sections we can add and remove."*

   THE CHAIN, and why TASK-0082 left `4 == 3`:

     section[data-swift-gridrow="1Column"]                    <- page 5862's row
       div.grid[data-swift-container][data-dw-container-width="3"]   <- 1288, THE CAP
         div.g-col-12#19174[data-dw-itemtype="swift-v2_app"]
           section[data-swift-gridrow] ...                    <- page 8454's rows

   TASK-0082 gave those inner rows their setting back; it could not give them width
   they do not have. A row two levels inside a 1288 box cannot exceed 1288, so Full
   measured 1288 and the packet recorded `4 == 3` as vendor behaviour. It is vendor
   behaviour — of a box that should not have been capping in the first place.

   THE SCOPE IS CONTENT-SNIFFED, AND THAT IS NOT A PREFERENCE. /en-us/shop and every
   product detail page are THE SAME PARAGRAPH — 19174, `eCom_ProductCatalog`, page
   5862 — and the DOM they produce is identical down to the attribute:

     body[data-dw-page-id="5862"][data-dw-itemtype="swift-v2_shop"]
     section[data-swift-gridrow="1Column"] > div.grid[data-dw-container-width="3"]
       > div.g-col-12#19174[data-dw-itemtype="swift-v2_app"]

   Same page id, same item type, same row, same column, same id. **There is no
   attribute anywhere on the page that says "this is a detail page".** The only
   difference is what the app rendered inside: a detail page carries
   `swift-v2_productcomponentselector` and our buy box; a listing carries
   `swift-v2_productlistcomponentselector` and `swift-v2_productlistitemrepeater`.
   Two tokens are matched rather than one so that removing either component in admin
   does not silently switch the page's layout model.

   WHAT THE RELEASE DOES NOT DO. It neutralises a width cap and nothing else — no
   padding change, no grid change, no repositioning. The inner rows then resolve
   exactly as DEC-0057 says a row resolves: the section runs full width and carries
   the colour scheme, the container inside it takes the row's own setting.

   REGULAR MUST NOT MOVE, so rule 2 below replaces TASK-0082's `100%` with the
   DEC-0014 ladder for the rows inside a RELEASED host only. With the host still
   capped, `100%` of the host and the ladder happen to agree; once the host runs to
   the viewport they do not, and `100%` would silently make every Regular row
   full-bleed. Measured either side, content width in px:

       client   1425   1285   1085    375
       Regular  1288   1080   1053    343      <- unchanged, both before and after
       Full     1425   1285   1085    375      <- the packet's point
       Small     936    845    715    343
       Readab.   719    719    674    343

   ONE VISIBLE CHANGE, AND IT IS THE INTENDED ONE: the variant-table row's grey
   `light_1` band now spans the viewport instead of stopping at 1288, because that
   row is a full-width section carrying a colour scheme — DEC-0057 ruling 1, and
   TASK-0083a build item 2 asks for it by name. Its CONTENT stays at 1288.

   THE LISTING IS UNTOUCHED BY SCOPE, not by luck: /en-us/shop never matches the
   `:has()`, so its host keeps the 1288 cap and its rows keep resolving against it.
   Measured: 0 containers move on /en-us/shop at any viewport.

   ─────────────────────────────────────────────────────────────────────────────────────
   SUPERSEDED IN PART BY TASK-0103 (2026-09-22). THE PARAGRAPH ABOVE IS NOW HISTORY, NOT
   BEHAVIOUR: the listing is no longer untouched, because it was never meant to be. The
   release below now fires on the host in EVERY state — listing, category and detail.

   WHY THE CONTENT SNIFF WENT. It existed because TASK-0083a could find no attribute that
   said "this is a detail page", and it was right about that. It was asking the wrong
   question. The defect is not "which page is the app showing"; it is "is this container
   deciding the width of ANOTHER page's rows", and that IS in the DOM — the app column has
   a `section[data-swift-gridrow]` inside it. Structure, not content.

   BLAST RADIUS, MEASURED (TASK-0102, `d9764e4`): the whole site is 165 pages — every
   area-3 page active and inactive, every area-26 page, plus `/` and `/en-us/`. **46 carry
   a `swift-v2_app` column; exactly ONE puts grid rows inside it**: page 5862 *Shop*,
   paragraph 19174, serving `/en-us/shop`, ~322 category URLs and ~362 product URLs. The
   other 45 app pages — sign-in, search, the whole customer centre, the Digital Assets
   Portal — host apps that render no rows, so the `:has()` cannot reach them. Widening the
   sniff did not widen the blast radius; it removed a distinction inside the one host.

   WHAT MOVES, AND IT IS ONE THING. No container inside the host changes width: every one
   is Regular, and rule 2 pins Regular to the DEC-0014 ladder. There is no
   `data-dw-container-width="4"` anywhere inside the host in either state — counted, zero.
   What changes is the SECTION, and only where something paints it: the breadcrumb band
   (mouldshop-breadcrumb.css rule 1b) now reaches the viewport on category pages exactly
   as it already does on product pages. Colour schemes inside the host: none — the two
   `dark_1` rows on `/en-us/shop/bikes` are below the app column, in page 5862's own
   footer, and are not reached by any of this.
   ========================================================================== */

/* 1. release the cap — the app host, in every state.

      THE `section[data-swift-gridrow] >` PREFIX IS LOAD-BEARING AND IT IS NOT DECORATION.
      Without it this rule is (0,4,0) and the DEC-0014 ladder rule above is (0,5,0) —
      three :not([attr]) clauses plus [data-swift-container] inside the last one — so the
      ladder won --dw-container-width and the cap never lifted. The symptom was a clean
      lie: the selectors were present in the CSSOM, the gutter half of the rule DID apply,
      and Full still measured 1288. Anchoring the parent row adds an element and an
      attribute, which beats the ladder honestly rather than by `!important`.

      TASK-0103 RAISES THE MARGIN RATHER THAN NARROWING IT. `:has()` takes the specificity
      of its most specific argument, and the new argument ends in an ELEMENT —
      `section[data-swift-gridrow]` — where the old one ended in an attribute. So this
      selector goes from (0,5,1) to (0,5,2) against the same (0,5,0) ladder. The swap
      cannot reintroduce the failure the paragraph above describes; it moves further from
      it. Stated because that failure was silent, and the next person editing this line
      deserves to know which way the margin runs. */
section[data-swift-gridrow] > [data-swift-container]:has(> [data-swift-gridcolumn][data-dw-itemtype="swift-v2_app"] section[data-swift-gridrow]) {
  --dw-container-width: 100%;
  --dw-container-gutter: 0px;
}

/* 2. inside a RELEASED host, Regular and absent take the DEC-0014 ladder rather than
      the host's full width — otherwise every Regular row would go edge to edge. */
[data-swift-container]:has(> [data-swift-gridcolumn][data-dw-itemtype="swift-v2_app"] section[data-swift-gridrow]) [data-swift-gridcolumn][data-dw-itemtype="swift-v2_app"] > section[data-swift-gridrow] > [data-swift-container]:not([data-dw-container-width="1"]):not([data-dw-container-width="2"]):not([data-dw-container-width="4"]):not([data-dw-container-width="4"] *) {
  --dw-container-width: calc(var(--ms-page-width) + var(--dw-container-gutter));
}

/* 3. BELOW 992, Readability and Small collapse onto the page width — vendor's own
      behaviour, reproduced rather than invented. Vendor declares no rule for values 1
      or 2 under 992 (its `[data-dw-container-width="1"|"2"]` rules live inside
      @media(min-width: 992px)), so those values fall back to the base 100%. On a
      CAPPED host that base was the host's own 1053/343 and the point never arose; once
      the host runs to the viewport, 100% means edge to edge, and a row set to
      Readability would bleed on a phone.

      Measured on a TOP-LEVEL vendor row at a 375 client (TASK-0081's table): value 1
      renders 343 and value 2 renders 343 — the same as Regular. This reproduces that.
      Value 4 stays excluded: Full is supposed to bleed at every width. */
@media (max-width: 991.98px) {
  section[data-swift-gridrow] > [data-swift-container]:has(> [data-swift-gridcolumn][data-dw-itemtype="swift-v2_app"] section[data-swift-gridrow]) [data-swift-gridcolumn][data-dw-itemtype="swift-v2_app"] > section[data-swift-gridrow] > [data-swift-container]:not([data-dw-container-width="4"]):not([data-dw-container-width="4"] *) {
    --dw-container-width: calc(var(--ms-page-width) + var(--dw-container-gutter));
  }
}

/* ==========================================================================
   HEADER ACTIONS - menu + basket + hamburger (operator, 2026-09-24).
   The basket left the utility bar for the main bar, right of the menu. The three
   sit in one flex group in the header grid's third column; below 990 the menu is
   display:none and the group is basket + hamburger.
   The basket is .ms-icon-btn (the hamburger's 52px white icon button). Its count
   badge was navy-on-grey in the utility bar and would vanish on the navy bar, so
   here it takes the accent: blue badge, deep-navy figures.
   ========================================================================== */
.ms-header-actions {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: .25rem;
}
.ms-header-actions .ms-primary-nav { justify-self: auto; }
/* VIP pill (operator, 2026-09-25) - the search pill's outline (1px white, 999px radius, 34px),
   "VIP" in Montserrat 900. Inactive: no hover, no pointer. Hidden below 990 with the menu, so the
   phone bar stays basket + hamburger. */
.ms-vip-pill {
  display: inline-flex;
  align-items: center;
  height: 34px;
  padding: 0 1rem;
  margin-inline: .5rem;
  border: 1px solid var(--ms-white);
  border-radius: 999px;
  color: var(--ms-white);
  font-family: var(--ms-font-head);
  font-weight: 900;
  font-size: 15px;
  letter-spacing: .06em;
  line-height: 1;
  white-space: nowrap;
  cursor: default;
  user-select: none;
}
@media (max-width: 989px) {
  .ms-vip-pill { display: none; }
}
/* ONLYCOPY MARK (operator, 2026-09-27: "logo from top - add it to menu - between vip and basket").
   The OC monogram, white, 28px tall (the basket icon's size) in the VIP pill's 34px row. Hover
   blue, as the basket. Hidden below 990 with VIP. */
.ms-oc-mark {
  display: inline-flex;
  align-items: center;
  height: 34px;
  margin-inline: .25rem;
  color: var(--ms-white);
}
.ms-oc-mark svg { display: block; width: auto; height: 28px; }
a.ms-oc-mark:hover { color: var(--ms-blue); }
@media (max-width: 989px) {
  .ms-oc-mark { display: none; }
}
.ms-header-cart { color: var(--ms-white); }
.ms-header-cart:hover { color: var(--ms-blue); }
/* BIGGER BASKET (operator, 2026-09-24: "bigger basket"). No size given: the icon 20px -> 28px,
   inside the same 52px hit area; the count badge moves out with it. */
.ms-header-cart svg { width: 28px; height: 28px; }
.ms-header-cart .ms-cart-count {
  top: 2px;
  right: 0;
  background: var(--ms-blue);
  color: var(--ms-navy-deep);
}

/* ==========================================================================
   "MOULDSHOP - 2 COLUMNS" (MsRow_2Col) STAYS TWO COLUMNS DOWN TO 576 (operator,
   2026-09-24: "I need to keep two columns until we have two products slides").
   The row template emits g-col-{mobile} g-col-lg-6, so it stacked below Bootstrap lg
   (992). The product slider shows 2 cards down to 576, so this row now matches it:
   side by side at 576-991, stacked below 576. The equal 6/6 row only - the 3-9 /
   4-8 / 8-4 / 9-3 variants are untouched. In this band it outranks the row's admin
   "Mobile layout" widths; below 576 that setting still decides.
   ========================================================================== */
@media (min-width: 576px) and (max-width: 991.98px) {
  [data-swift-gridrow="MsRow_2Col"] > [data-swift-container] > [data-swift-gridcolumn] {
    grid-column: auto / span 6;
  }
}

/* Mouldshop Home Combo (TASK-0136 spacing fix) - see Paragraph/Mouldshop_HomeCombo/Default.cshtml.
   Its own flex column with an auto height: the hero, slider and stripe sit as separate flex items,
   as they do one-per-column when standalone, and the slider wrapper's h-100 has no definite height
   to stretch to inside a Section row's CSS grid. */
.ms-home-combo {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* Home Combo hero: no bottom padding (operator, 2026-09-25: "top banner in combo - ms-hero - remove
   bottom padding"). The Combo item was saved with the hero fields' DEFAULTS, including Indicators
   outside slider = on, and swiffy's .slider-indicators-outside adds padding-bottom:
   var(--swiffy-slider-indicator-outside-size). The standalone hero (22235) has that setting off.
   Scoped to the Combo; the standalone hero is untouched. (0,3,0) beats swiffy's (0,2,0). */
.ms-home-combo .ms-hero-slider.slider-indicators-outside {
  padding-bottom: 0;
}

/* Mobile banner image (TASK-0146, DEC-0085). A banner with a mobile image wraps its <img> in a
   <picture class="ms-picture"> whose <source> answers (max-width: 767.98px), the banner image
   switch (DEC-0094). display: contents keeps the wrapper out of layout, so every existing rule on the
   <img> (block, width, aspect, object-fit) applies exactly as before. A banner without a mobile
   image has no wrapper and is untouched. */
.ms-picture {
  display: contents;
}

/* Parallax passes the mobile image's own focal point as --ms-op-m. The desktop focal point is an
   inline style, so !important is the only way a stylesheet can override it; it is scoped to the
   wrapper, which exists only when a mobile image is set.
   MS-SWITCH-BANNER: the banner image switch, 767.98 / 768 (DEC-0094); layout and menu stay on MS-SWITCH, 989 / 990. Full list in WORKLOG (TASK-0152a). */
@media (max-width: 767.98px) {
  .ms-picture > .ms-parallax__img {
    object-position: var(--ms-op-m) !important;
  }
}

/* Mobile banner image is SQUARE below the banner image switch (TASK-0149, DEC-0092). Every rule here is
   reachable only when a mobile image is set (.ms-picture / .ms-imagelink--mobile exist only
   then), so desktop and the no-mobile-image path are untouched. GetImage already crops the mobile
   image square on its focal point; object-fit: cover does the same for the direct route (avif /
   svg / gif), centred, since that route cannot read a focal point.
   NOT SQUARE, by design (TASK-0149 DIAG 2): Parallax (frame height is viewport-driven) and Video
   (the frame is the video's ratio and later holds the video). There the mobile image fills the
   frame with cover on its own focal point, --ms-op-m.
   MS-SWITCH-BANNER: the banner image switch, 767.98 / 768 (DEC-0094); layout and menu stay on MS-SWITCH, 989 / 990. Full list in WORKLOG (TASK-0152a). */
@media (max-width: 767.98px) {
  /* Hero / campaign slide: the image is in flow and sets the card height, so a square image makes
     a square slide. The copy overlay is inset: 0 over it. */
  .ms-picture > .ms-hero-bg-img {
    aspect-ratio: 1 / 1;
    object-fit: cover;
  }

  /* Image Link: the figure is the frame, whatever its Aspect. (0,2,1) and (0,3,1) beat the
     component's own aspect and fit rules at (0,1,0) and (0,3,0). */
  figure.ms-imagelink.ms-imagelink--mobile {
    aspect-ratio: 1 / 1;
  }
  figure.ms-imagelink.ms-imagelink--mobile .ms-imagelink__link {
    width: 100%;
    height: 100%;
  }
  figure.ms-imagelink.ms-imagelink--mobile .ms-imagelink__img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  /* News banner: 1:1 replaces 1488/415 (and 16/10 below 768) when a mobile image is set
     (DEC-0092 point 4). object-fit: cover is the component's own. */
  .ms-picture > .ms-news-banner__img {
    aspect-ratio: 1 / 1;
  }

  /* Video, fixed Aspect only (Auto has no frame): the facade poster fills it, cover, on the
     mobile poster's focal point. (0,4,0) beats the component's fit rules at (0,3,0). */
  .ms-video:not(.ms-video--ar-auto) .ms-picture > .ms-video__poster {
    object-fit: cover;
    object-position: var(--ms-op-m, 50% 50%);
  }
}

/* ==========================================================================
   NEWS LIST — vendor posts app (Swift-v2 post list, /en-us/posts). Operator, 2026-09-28.
   CSS only: the vendor template is untouched. Hooked on the app's own attributes —
   input[name="postq"], the Filter button's data-bs-target="#Post_Facet_…", and
   [data-swift-posts-list] — so nothing else on the site is matched.
   ========================================================================== */
/* Search: no doubled border on focus. Bootstrap's .form-control:focus ring and swift.css's global
   :focus-visible box-shadow stacked a second ring outside the pill's own border. The border stays;
   the ring goes (same cure as .ms-search-input above). */
input[name="postq"].form-control:focus,
input[name="postq"].form-control:focus-visible {
  box-shadow: none;
  outline: none;
}
/* Filter button: a pill like every other button; the count is a grey circle with white figures.
   Grey = Bootstrap's own --bs-gray-600 (white on it ~4.7:1), not a new colour. */
.btn[data-bs-target^="#Post_Facet_"] {
  border-radius: var(--ms-buybox-radius, 999px);
  padding-inline: 1.25rem;
}
.btn[data-bs-target^="#Post_Facet_"] .badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.35rem;
  height: 1.35rem;
  margin-top: 0 !important;         /* vendor's mt-1 pushed the count off-centre */
  padding: 0 .3rem;
  border-radius: 999px;
  background-color: var(--bs-gray-600, #6c757d) !important;   /* beats .text-bg-secondary's !important */
  color: var(--ms-white) !important;
  font-size: .75rem;
  line-height: 1;
}
/* Post cards: square corners, image included. */
[data-swift-posts-list] .card {
  --bs-card-border-radius: 0;
  --bs-card-inner-border-radius: 0;
  border-radius: 0;
}
[data-swift-posts-list] .card-img-top { border-radius: 0; }
/* Category badge on the image: white, navy text, the card markers' pill and padding
   (.ms-badge + .ms-markers-top .ms-badge: .15rem / .65rem, --ms-marker-radius). */
[data-swift-posts-list] .card .badge {
  padding: .15rem .65rem;
  border-radius: var(--ms-marker-radius, 999px);
  background-color: var(--ms-white) !important;
  color: var(--ms-navy) !important;
}
/* NEWS FILTER DROPDOWN (operator, 2026-09-28: a simple dropdown, not the drawer). Markup:
   Custom/MouldshopPostFilter.cshtml, picked through QueryPublisher/MouldshopPostGrid.cshtml. The
   trigger, panel and rows are the sort dropdown's own classes (mouldshop-listbar-sort.css); only
   what differs is here. The panel anchors LEFT - the filter sits left, beside the search - where
   the sort's anchors right. */
.ms-postfilter {
  position: relative;
}
.ms-postfilter.is-open {
  z-index: 900;                     /* the sort's value: above the cards, under the header */
}
.ms-postfilter .ms-sort-list {
  left: 0;
  right: auto;
  min-width: 16rem;
}
.ms-postfilter-option {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin: 0;
  color: inherit;
  cursor: pointer;
}
.ms-postfilter-label { flex: 1 1 auto; }
.ms-postfilter-n { opacity: .75; }
.ms-postfilter .ms-sort-option:hover { color: var(--ms-sort-option-hover, #0A2979); }
.ms-postfilter .ms-sort-option:has(input:checked) { color: var(--ms-sort-option-on, #6CB2DE); }
.ms-postfilter-heading {
  color: var(--ms-navy);
  font-weight: 700;
  cursor: default;
}
/* The count: the same grey circle with white figures as on the drawer-era Filter button. */
.ms-postfilter-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.35rem;
  height: 1.35rem;
  padding: 0 .3rem;
  border-radius: 999px;
  background-color: var(--bs-gray-600, #6c757d);
  color: var(--ms-white);
  font-size: .75rem;
  line-height: 1;
}
.ms-postfilter-count[hidden] { display: none; }
