/* CANONICAL-REV: 2026-09-15.86
   The single source for every ET template's bridge. Fixes go HERE, never
   into a template. A template records the rev it was last checked at; when
   this rev moves, every template below it is due a re-check. Per-template
   blocks are debt - see 33-VMT-ET-Template-Modernization-Pipeline.md. */
/*
 * Serenity template — bs2-bs5-bridge.css
 *
 * BS2-to-BS5 visual aliasing for BS2 grid + utility classes that survive in:
 *   (1) custom-HTML module content stored in jos_modules.content
 *   (2) VirtueMart's older view templates that still emit .row-fluid / .span* markup
 *
 * The Skill 26 modernize-stack.py helper rewrites BS2 classes in index.php, but
 * MODULE CONTENT is data not code — those BS2 classes survive intact. Without
 * this bridge, BS5 doesn't know what .row-fluid / .spanN mean, so module rows
 * collapse to stacked-vertically block elements.
 *
 * This file is loaded LAST in the <head> CSS stack so its rules win the cascade.
 *
 * Reference: Avant's avant-fixups.css (early Phase 3b version) — same pattern
 * applied to module content that contains legacy markup.
 */

/* ---------- BS2 .row-fluid → BS5 flex row ---------- */

.row-fluid {
    display: flex;
    flex-wrap: wrap;
    margin-right: 0;
    margin-left: 0;
    box-sizing: border-box;
    width: 100%;
}

/* CS-PATCH: rowfluid-where-completion 2026-07-24 (found on Explore)
   This block was only PARTIALLY :where()'d in the 2026-07-14 FlatStore
   refactor (span1/3/6/9/12 + the [class*=span] child block were left at
   full specificity) — a canonical regression from the parallel-tab
   nights. The un-:where'd 15px PADDING gutters + BS5 12-col widths beat
   the BS2-faithful :where() grid below (margins + 14.53/23.08/...%):
   Explore 768 showed sidebar moduletable 146 vs live 161 and partner
   logos at HALF size (li.span2 got BS2 width but kept the 15px padding).
   BS2 used margin-left gutters and NO padding — the :where() standalone
   .spanN block below owns widths+gutters; this block only keeps the
   flex-child plumbing. */
.row-fluid > [class*="span"]:where(:not(ul[class*="product"] > *)) {
    box-sizing: border-box;
    min-height: 1px;
    flex: 0 0 auto;
    /* BS2's real gutter model: margin-left, NOT padding. Must sit at real
       specificity — reset.css's element-level margin:0 beats a :where()
       margin (found on Explore browse view: span9 lost its 2.564% gutter
       and the whole main column sat 29px left of live). */
    margin-left: 2.564102564%;
}
.row-fluid > [class*="span"]:first-child {
    margin-left: 0;
}

:where(.row-fluid > .span1)   { width: 8.333333%;  }
:where(.row-fluid > .span2)   { width: 16.666667%; }
:where(.row-fluid > .span3)   { width: 25%;        }
:where(.row-fluid > .span4)   { width: 33.333333%; }
:where(.row-fluid > .span5)   { width: 41.666667%; }
:where(.row-fluid > .span6)   { width: 50%;        }
:where(.row-fluid > .span7)   { width: 58.333333%; }
:where(.row-fluid > .span8)   { width: 66.666667%; }
:where(.row-fluid > .span9)   { width: 75%;        }
:where(.row-fluid > .span10)  { width: 83.333333%; }
:where(.row-fluid > .span11)  { width: 91.666667%; }
:where(.row-fluid > .span12)  { width: 100%;       }

/* Standalone .spanN (outside .row-fluid) — float-based BS2 behaviour */

/* 2026-07-14 FlatStore: wrapped in :where() so template.css same-specificity
   overrides (e.g. .sp20 five-across cells) win like they did over load-FIRST
   BS2. The bridge loads LAST; without :where it hijacked those grids. */
:where(.span1, .span2, .span3, .span4, .span5, .span6,
.span7, .span8, .span9, .span10, .span11, .span12) {
    box-sizing: border-box;
    float: left;
    min-height: 1px;
    margin-left: 2.564102564%;
}

:where(.span1, .span2, .span3, .span4, .span5, .span6, .span7, .span8,
.span9, .span10, .span11, .span12):where(:first-child) {
    margin-left: 0;
}

:where(.span1) { width: 5.982905983%; }
:where(.span2) { width: 14.52991453%; }
:where(.span3) { width: 23.07692308%; }
:where(.span4) { width: 31.62393162%; }
:where(.span5) { width: 40.17094017%; }
:where(.span6) { width: 48.71794872%; }
:where(.span7) { width: 57.26495726%; }
:where(.span8) { width: 65.81196581%; }
:where(.span9) { width: 74.35897436%; }
:where(.span10) { width: 82.90598291%; }  /* 1170-grid %% (was 970-grid px) — fill container, not bunch-left */
:where(.span11) { width: 91.45299145%; }  /* 1170-grid %% (was 970-grid px) — fill container, not bunch-left */
:where(.span12) { width: 100%; }  /* 1170-grid %% (was 970-grid px) — fill container, not bunch-left */

/* ---------- BS2 .offset* ---------- */

.row-fluid > .offset1   { margin-left: 8.333333%;  }
.row-fluid > .offset2   { margin-left: 16.666667%; }
.row-fluid > .offset3   { margin-left: 25%;        }
.row-fluid > .offset4   { margin-left: 33.333333%; }
.row-fluid > .offset5   { margin-left: 41.666667%; }
.row-fluid > .offset6   { margin-left: 50%;        }
.row-fluid > .offset7   { margin-left: 58.333333%; }
.row-fluid > .offset8   { margin-left: 66.666667%; }
.row-fluid > .offset9   { margin-left: 75%;        }
.row-fluid > .offset10  { margin-left: 83.333333%; }
.row-fluid > .offset11  { margin-left: 91.666667%; }

/* ---------- BS2 .pull-left / .pull-right → BS5 equivalents ---------- */

.pull-left  { float: left  !important; }
.pull-right { float: right !important; }

/* ---------- BS2 .hidden-phone / .visible-phone (block context) ---------- */
/* Inline-context (.d-inline d-sm-none / .d-none d-sm-inline) handled in index.php. */
/* These are for div-context usage in module content. */

@media (max-width: 767px) {
    .hidden-phone  { display: none !important; }
    .visible-phone { display: block !important; }
}
@media (min-width: 768px) {
    .visible-phone { display: none  !important; }
    /* .hidden-phone intentionally NOT declared at >=768 — see
       CS-PATCH: hidden-phone-no-desktop-override (2026-07-24, Explore) */
}

/* ---------- BS2 button aliases ---------- */

.btn-inverse  { background-color: #212529; border-color: #212529; color: #fff; }
.btn-inverse:hover  { background-color: #000; border-color: #000; color: #fff; }
.btn-default  { background-color: #6c757d; border-color: #6c757d; color: #fff; }
.btn-default:hover  { background-color: #5c636a; border-color: #5c636a; color: #fff; }

/* ---------- Mobile responsiveness — collapse multi-col rows on small screens ---------- */

@media (max-width: 767px) {
    .row-fluid {
        flex-direction: column;
    }
    .row-fluid > [class*="span"] {
        width: 100% !important;
        margin-left: 0 !important;
    }
}

/* ---------- Sticky header — white background so it stays visible on scroll ---------- */
/* Locked 2026-06-15: The fixed-position #top-handler had no background, so when scrolling
   over the slideshow / content images the menu blended into the page and got lost. */

#top-handler.fixed,
#top-handler.scrolled {
    background: #ffffff;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}

#top-handler.scrolled {
    background: #ffffff;
}

/* ---------- Testimonials section — tighten the over-tall gray box ---------- */
/* Locked 2026-06-15: #testimonials .container had min-height: 460px hard-coded.
   With single-testimonial visible the gray box towers above the content. */

#testimonials .container {
    min-height: auto;
}

#testimonials .container .row-fluid {
    padding: 50px 0px;
}

#testimonials {
    margin-bottom: 0;
}

.customers-box-handler .owl-theme .owl-controls .owl-pagination {
    margin-top: 10px;
}

/* ---------- Manufacturers/Partners carousel — reduce gap before pagination dots ---------- */
/* Locked 2026-06-15: Visual gap of ~150px between brand-logo row bottom and the
   pagination dots. Caused by owl-carousel inheriting auto-height of taller items. */

.man-main-handler {
    padding-bottom: 0;
}

.man-main-handler .owl-wrapper-outer {
    padding: 0;
}

.man-main-handler .owl-pagination,
.moduletable_manufacturers .owl-pagination {
    margin-top: 10px;
}

.moduletable_manufacturers .owl-item {
    padding: 0 10px;
}

.moduletable_manufacturers .owl-item img {
    max-height: 80px;
    width: auto;
    margin: 0 auto;
}

/* ---------- Sticky header — white background so it stays visible on scroll ---------- */
/* Locked 2026-06-15: The fixed-position #top-handler had no background, so when scrolling
   over the slideshow / content images the menu blended into the page and got lost. */

#top-handler.fixed,
#top-handler.scrolled {
    background: #ffffff;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}

#top-handler.scrolled {
    background: #ffffff;
}

/* ---------- Testimonials section — tighten the over-tall gray box ---------- */
/* Locked 2026-06-15: #testimonials .container had min-height: 460px hard-coded.
   With single-testimonial visible the gray box towers above the content. */

#testimonials .container {
    min-height: auto;
}

#testimonials .container .row-fluid {
    padding: 50px 0px;
}

#testimonials {
    margin-bottom: 0;
}

.customers-box-handler .owl-theme .owl-controls .owl-pagination {
    margin-top: 10px;
}

/* ---------- Manufacturers/Partners carousel — reduce gap before pagination dots ---------- */
/* Locked 2026-06-15: Visual gap of ~150px between brand-logo row bottom and the
   pagination dots. Caused by owl-carousel inheriting auto-height of taller items. */

.man-main-handler {
    padding-bottom: 0;
}

.man-main-handler .owl-wrapper-outer {
    padding: 0;
}

.man-main-handler .owl-pagination,
.moduletable_manufacturers .owl-pagination {
    margin-top: 10px;
}

.moduletable_manufacturers .owl-item {
    padding: 0 10px;
}

.moduletable_manufacturers .owl-item img {
    max-height: 80px;
    width: auto;
    margin: 0 auto;
}

/* ---------- Cart product images — force aspect-ratio preservation ---------- */
/* Locked 2026-06-15: BS5 doesn't auto-apply height:auto to images the way BS4 did.
   The legacy span.cart-images rule sets width:45px but no height, so under certain
   parent-height contexts the image stretches vertically into a tall narrow strip.
   Force aspect ratio + cap height defensively. */

span.cart-images img,
.cart-images img,
.cart-product-image img,
.cartpanel img.featuredProductImage,
td .cart-images img {
    width: 45px !important;
    height: auto !important;
    max-height: 60px !important;
    object-fit: contain;
}

/* And the mini-cart in header */
.cartpanel .vm_cart_products img,
.cartpanel img {
    width: 45px !important;
    height: auto !important;
    max-height: 60px !important;
    object-fit: contain;
}

/* ---------- Related-products / "You May Also Be Interested" grid images ---------- */
/* Locked 2026-06-15 (Serenity, Round 5 fix): The VirtueMart cart-bottom related-
   products block uses `<ul class="vmproduct product-details row-fluid"><li class="span6 floatleft">`
   markup with `<img class="featuredProductImage" width="585" height="585">` inside.
   Same BS5 image-stretch issue as the cart row: the legacy span6 column flexes
   to fill the row's tallest content; without `height: auto !important` the image
   stretches vertically and the products look tall + narrow. Cap defensively. */

.moduletable_products img.featuredProductImage,
.moduletable_products .pr-img-handler img,
.vmgroup_products img.featuredProductImage,
.vmgroup_products .pr-img-handler img,
.vmproduct .pr-img-handler img,
.pr-img-handler img.featuredProductImage,
.pr-img-handler img,
.show-pop-up-image img.featuredProductImage,
.featured-view img.featuredProductImage,
.latest-view img.featuredProductImage,
.topten-view img.featuredProductImage,
.recent-view img.featuredProductImage {
    max-width: 100% !important;
    width: 100% !important;
    height: auto !important;
    object-fit: contain;
    aspect-ratio: 1 / 1;
}

/* Belt-and-suspenders: any image inside any .vmproduct li directly */
.vmproduct li img,
.vmgroup_products li img,
.moduletable_products li img {
    max-width: 100% !important;
    height: auto !important;
    object-fit: contain;
}

/* Cap the spacer container's flex stretch so the row doesn't blow up vertically */
.vmproduct .spacer .pr-img-handler {
    overflow: hidden;
}

/* ---------- Category-view images (Round 9 — Tim caught the /online-store page) ---------- */
/* Locked 2026-06-16: VirtueMart's category-view markup uses
   <div class="category floatleft span3"><div class="spacer"><h2><a>
       <img width="585" height="585" src="..."/>
   The image has NO class — only the wrapper hierarchy. The earlier related-
   products fix that targeted .featuredProductImage doesn't reach these. */

.category-view .category img,
.category-view .row-fluid .category img,
.category .spacer img,
.row-fluid .spacer img,
.category-view .spacer img,
.category-view .category .spacer h2 a img {
    max-width: 100% !important;
    width: 100% !important;
    height: auto !important;
    object-fit: contain;
    aspect-ratio: 1 / 1;
}

/* Cap the spacer container so flex parent doesn't stretch the row vertically */
.category-view .category .spacer,
.category .spacer {
    overflow: hidden;
}

/* ---------- Breadcrumbs — strip list bullets, inline display (Round 11) ---------- */
/* Locked 2026-06-16: Joomla 5's mod_breadcrumbs ships a <ul class="breadcrumb"><li>...
   markup. Without `list-style: none` + `display: inline`, each crumb shows a bullet. */

.breadcrumb,
ul.breadcrumb,
#breadcrumb-line .breadcrumb,
[role="navigation"] .breadcrumb {
    list-style: none !important;
    padding-left: 0 !important;
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4em;
}

.breadcrumb > li,
ul.breadcrumb > li {
    list-style: none !important;
    display: inline-flex;
    align-items: center;
}

.breadcrumb > li::before,
.breadcrumb > li::marker {
    content: none !important;
}

/* ---------- Breadcrumbs tighten (Round 12) ---------- */
/* Locked 2026-06-16: round 11 set gap:0.4em which left visible whitespace
   between "/" and the next crumb. Tim wants tighter — natural single-space
   look like "Home / Using Joomla!" rather than "Home / [gap] Using Joomla!".
   Drop the flex gap and let the inline content's natural whitespace handle it. */

.breadcrumb,
ul.breadcrumb,
#breadcrumb-line .breadcrumb,
[role="navigation"] .breadcrumb {
    gap: 0;
}

.breadcrumb > li,
ul.breadcrumb > li {
    margin: 0;
    padding: 0;
}

/* Normalize the divider span so the "/" sits with consistent single-space
   padding either side, regardless of whether the source HTML has tabs/newlines
   inside the <span class="divider">. */
.breadcrumb .divider {
    display: inline-block;
    padding: 0 0.35em;
    white-space: normal;
}

/* ---------- UNIVERSAL image aspect-ratio defense (Round 13) ---------- */
/* Tim 2026-06-16: instead of catching the stretched-image pattern page-by-page,
   apply max-width:100% + height:auto to EVERY image with HTML width/height
   attributes (the VirtueMart marketplace pattern). Modern browsers + the HTML
   attrs give the natural aspect-ratio; CSS just keeps it from overflowing.
   This subsumes the round 5 (related products), round 9 (category view) and
   round 10 (single contact) per-page rules and catches everything else.

   Exclusions: explicit icon/emoji classes, social icons, payment icons —
   those need pixel-precise sizing from the template's own rules. */

img[width][height]:not([class*="icon"]):not([class*="socicon"]):not([class*="emoji"]):not([alt*="separator"]):not(.camera_wrap img):not(.camera_src img) {
    max-width: 100% !important;
    height: auto !important;
}

/* Safety net — any img inside known VM / Joomla content containers
   (covers cases where the img has no width/height attrs but still needs sizing) */
.main-image img,
.vm-additional-images img,
.vm-additional-images a img,
.vm-product-media-container img,
.vm-product-media-container a img,
.product-fields-list img,
.product-related-products img,
.product-field-display img,
.product-field-type-R img,
.virtuemart_view_productdetails img,
.productdetails-view img,
.product-container img,
.vmproduct img,
.vmgroup_products img,
.category-view img,
.featured-view img,
.latest-view img,
.topten-view img,
.recent-view img,
.related-products-view img,
.browse-view img,
.search-result-products img,
.com-content-article img,
.article-content img {
    max-width: 100% !important;
    height: auto !important;
}


/* ===========================================================================
 * Megamenu hover-gap fix (locked 2026-06-16, Round 19 final)
 *
 * Architecture: vendor's `top: 90px` is the CORRECT position — header is 90px
 * tall (`topheight` param), logo+menu share the row, dropdown lands right below.
 * The 40-50px visible gap from menu items to dropdown is intentional design.
 *
 * Real bug: superfish's hover-delay JS only wires to the `usedropdown` code
 * path, NOT `usemegamenu`. Megamenu is pure CSS, so mouse crossing the gap
 * instantly drops `:hover` and the dropdown vanishes mid-traversal.
 *
 * Fix: CSS transition-delay on `top` — dropdown stays at top:90 for 500ms
 * after hover-out before reverting to top:-999em (the hidden state).
 *
 * ⛔ Three traps to avoid (don't repeat the Round 17-18 mistakes):
 *   - `top: 100%` — `.main-li-container` is `position:static!important`,
 *     so 100% measures against next positioned ancestor; dropdown escapes.
 *   - `top:<smaller value>` — logo is centered in the 90px row, overlaps any
 *     value below ~90px.
 *   - Pseudo-element bridge with pointer-events:auto — `.ul-container` spans
 *     full container width, so bridge above it covers sibling menu items
 *     and blocks lateral mouse movement.
 * =========================================================================== */

/* Hide state — declare transition with 500ms delay (applies when hovering OUT) */
#megamenu-handler #mega-menu.menu-nav > li > ul.ul-container {
    transition: top 0s linear 500ms;
}

/* Hover state — no delay (applies when hovering IN).
   2026-07-14 (GliseStore): the hardcoded `top: 90px !important` here was the
   2026-06-16 donor's header height and OVERRODE other templates' own inline
   `top: topheight+116px` rule (GliseStore's panel opened 86px too high,
   covering the nav bar). The hover `top` must come from the TEMPLATE's own
   CSS — the bridge only owns the transition-delay mechanic. (Same principle
   as the bare-#top-handler fix: never hardcode one donor's geometry.) */
#megamenu-handler #mega-menu.menu-nav > li:hover > ul.ul-container,
#megamenu-handler #mega-menu.menu-nav > li.sfHover > ul.ul-container {
    transition-delay: 0s;
}

/* ---------- Mobile: standalone .spanN blocks stack full-width (footer cols, payment/social bar, .moduletable/.tf-module.spanN) ---------- */
/* Added 2026-07-14 (QStore): standalone .spanN percentage widths are desktop-grid only;
   without this they stay side-by-side and cramp on phones. */
@media (max-width: 767px) {
    .span1, .span2, .span3, .span4, .span5, .span6,
    .span7, .span8, .span9, .span10, .span11, .span12 {
        width: 100% !important; margin-left: 0 !important; float: none !important;
    }
    .owl-wrapper-outer, .owl-item, .sl-products, .vmgroup_products, .moduletable_products { max-width: 100% !important; }
}

/* ---------- Tabbed-products nav: strip BS5 .row gutter from non-col tab children + keep labels one line ---------- */
/* Added 2026-07-14 (QStore): the tab module outer .row is a BS5 flex row, so BS5 pads its
   tab children 12px (BS2 .row-fluid never did) -> reads as a stray column offsetting tabs. */
.nav-tabs-handler.row > .tabs-nav-handler,
.nav-tabs-handler.row > .tabs-handler,
.tabs-nav-handler { padding-left: 0 !important; padding-right: 0 !important; }
.tabs-nav-handler ul.nav-tabs > li,
.tabs-nav-handler ul.nav-tabs > li .tab_heading,
.tabs-nav-handler ul.nav-tabs > li a { white-space: nowrap; }

/* ============================================================
   CS-PATCH: container-1170-cap  2026-07-14  (found on Energize)
   BS5 css caps .container at 1320px @>=1400 viewport; the BS2 grid
   this bridge emulates capped it at 1170px @>=1200 (724 @768-979,
   940 @980-1199, fluid below 768). Without this, the whole boxed
   layout widens to 1320 and every %-based .spanN widens with it.
   Also: BS2 sized absolutely-positioned .spanN blocks (e.g. header
   #top-handler.span12.th-abs) with FIXED px so they matched the
   container; the bridge's percentage widths resolve against the
   VIEWPORT for abs-positioned elements unless the container is the
   containing block — so make .container position:relative.
   ============================================================ */
.container:not(.container-fluid) { position: relative; }
@media (max-width: 767px) { .container:not(.container-fluid) { max-width: 100%; } }
@media (min-width: 768px) and (max-width: 979px) { .container:not(.container-fluid) { max-width: 724px; } }
@media (min-width: 980px) and (max-width: 1199px) { .container:not(.container-fluid) { max-width: 940px; } }
@media (min-width: 1200px) { .container:not(.container-fluid) { max-width: 1170px !important; } }
/* CS-PATCH addendum 2026-07-14 (HyperShop): BS2 .container also self-centered (margin auto).
   Footer containers that relied on it hug the left edge without this. */
/* 2026-08-11 (found on Evolution, ET): auto margins must be !important.
   The BS2 source of this contract was bootstrap-responsive.css, which shipped
    -
   important precisely so it could beat a template's own #id rule. Phase 5d
   strips that file, so the bridge inherits the obligation. Evolution's
    (specificity 1,0,0)
   silently beat the plain version and the whole page - header, slideshow,
   4-up strip, footer band - hugged the left edge with a 230px gap on the
   right. Same reason max-width above is already !important. */
.container:not(.container-fluid) { margin-left: auto !important; margin-right: auto !important; }

/* ============================================================
   CS-PATCH: fa6-social-reset  2026-07-14  (found on Energize)
   Legacy sprite CSS hides social link text via text-indent:-9999px
   (+ background sprite). After Phase 10c the FA6 <i> glyph inherits
   the indent and vanishes off-screen. :has(i) scopes the reset to
   anchors that actually carry an icon element, so sprite-based
   templates that have not been converted yet are unaffected.
   ============================================================ */
#social-links li a:has(i) { text-indent: 0; background-image: none; text-align: center; text-decoration: none; }
#social-links li a i { font-size: 15px; line-height: 38px; }
#social-links .sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }

/* ============================================================
   CS-PATCH: camera-img-maxwidth  2026-07-14  (found on Energize @768)
   The bridge's responsive-img rule (img[width][height]:not(...)) has
   higher specificity than camera.css's `.camera_wrap img{max-width:none}`,
   so camera.js cover-crop sizing (inline width/height px) gets capped to
   the box width and the slideshow letterboxes instead of cover-filling.
   Exclude slideshow imagery explicitly.
   ============================================================ */
.camera_wrap img, .camera_src img, .cameraSlide img { max-width: none !important; }

/* =====================================================================
   CS-PATCH: row-spanN-mirror  2026-07-14  (mandated by Pipeline v2 Phase 5;
   applied during HyperShop run). Phase 3a rewrites inline markup
   .row-fluid → .row, so legacy .spanN children now sit inside a BS5 flex
   .row. BS5's `.row > *` sets width:100% at equal specificity (0,1,0) to
   the bare .spanN width rules above — the bridge only wins because it
   loads last. This mirror makes the win structural (0,2,0), not
   load-order luck. Values are IDENTICAL to the bare standalone .spanN
   1170-grid percentages, so computed layout on shipped templates is
   unchanged. Gutter margins still come from the bare .spanN rules.
   Mobile stacking unchanged: the ≤767px .spanN {width:100%!important}
   block still wins via !important.
   FIXED 2026-07-31 (Oxygen J6 run): this block shipped wrapped in :where(),
   which ZEROES specificity to (0,0,0) - the exact opposite of the (0,2,0)
   this comment claims. It therefore lost to BS5 `.row > *` {width:100%} and
   module-content .spanN columns stacked full-width. :where() removed.
   ===================================================================== */
.row > .span1  { flex: 0 0 auto; width: 5.982905983%; }
.row > .span2  { flex: 0 0 auto; width: 14.52991453%; }
.row > .span3  { flex: 0 0 auto; width: 23.07692308%; }
.row > .span4  { flex: 0 0 auto; width: 31.62393162%; }
.row > .span5  { flex: 0 0 auto; width: 40.17094017%; }
.row > .span6  { flex: 0 0 auto; width: 48.71794872%; }
.row > .span7  { flex: 0 0 auto; width: 57.26495726%; }
.row > .span8  { flex: 0 0 auto; width: 65.81196581%; }
.row > .span9  { flex: 0 0 auto; width: 74.35897436%; }
.row > .span10 { flex: 0 0 auto; width: 82.90598291%; }
.row > .span11 { flex: 0 0 auto; width: 91.45299145%; }
.row > .span12 { flex: 0 0 auto; width: 100%; }

/* =====================================================================
   CS-PATCH: bs2-body-reset  2026-07-14  (found on HyperShop)
   BS2 bootstrap.min.css carried `body { margin: 0 }`. When modernize-stack
   strips the BS2 CSS, the browser default 8px body margin comes back and
   the whole page shifts 8px down/right vs the live donor. Restore the reset.
   (font-size/line-height/color come from template.css on every donor seen
   so far — only the margin was lost.)
   ===================================================================== */
body { margin: 0; }

/* =====================================================================
   CS-PATCH: bs2-container-clearfix  2026-07-14  (found on HyperShop @375)
   BS2 bootstrap.min.css gave .container a table-clearfix
   (.container:before/:after { display:table; content:"" } + :after { clear:both }).
   BS5's .container has none. Containers whose children are all FLOATED
   (e.g. HyperShop's #megamenu-handler .megamenu-background bar) collapse to
   ~0 height once BS2 CSS is stripped, pulling everything below them up and
   stacking sections on top of each other (slideshow overlapped the mobile
   Shop Menu button). Faithful restoration of the BS2 behavior.
   ===================================================================== */
.container:not(.container-fluid)::before,
.container:not(.container-fluid)::after { display: table; content: ""; line-height: 0; }
.container:not(.container-fluid)::after { clear: both; }

/* =====================================================================
   CS-PATCH: bs2-responsive-span-stack  2026-07-14  (found on HyperShop @375)
   BS2 bootstrap-responsive.min.css stacked EVERY grid cell at <=767 via
   `.row-fluid [class*="span"] { display:block; float:none; width:100%;
   margin-left:0 }` — an attribute selector, so it also caught odd cells
   like HyperShop's slideshow caption `span0` that the .span1-.span12
   class list above misses. Mirror it for both .row-fluid and .row (Phase
   3a rewrites markup). display stays NON-important so .hidden-phone
   (display:none !important) still wins, exactly as in BS2.
   ===================================================================== */
@media (max-width: 767px) {
    .row-fluid [class*="span"], .row [class*="span"] {
        display: block;
        float: none !important;
        width: 100% !important;
        margin-left: 0 !important;
    }
}

/* =====================================================================
   CS-PATCH: mobile-colmd-stack  2026-08-06  (Energize footer, Tim flagged)
   COMPANION to the Phase 2 spanN->col-md rewrite. The span-stack block above
   only catches `[class*="span"]`; once a donor's grid classes are rewritten to
   `col-md-*`, a 5-across position that still emits its BS2 inline
   `style="width:17.x%"` hack (index.php: `if ($n == 5) style="width:17.7%"`)
   keeps that width on phones — col-md-* has only an IMPLICIT sub-md 100% (no
   !important), so the inline width wins and the columns render skinny + wrap
   ("Site"/"Map"). The old `.spanN {width:100% !important}` used to beat it.
   Re-assert BS5's own sub-md stacking with !important. Cohort mobile = all stack.
   ===================================================================== */
@media (max-width: 767px) {
    [class*="col-md-"] { width: 100% !important; float: none !important; }
}

/* =====================================================================
   CS-PATCH: bs2-tab-pane  2026-07-14  (found on HyperShop)
   BS2 bootstrap.min.css carried the tab-panel visibility contract:
   .tab-content > .tab-pane { display:none } + .tab-content > .active
   { display:block }. Templates with custom tab JS (jQuery .active class
   toggling) rely on it. Without it ALL tab panels render stacked (three
   product grids at once on HyperShop home). Faithful BS2 restoration.
   ===================================================================== */
.tab-content > .tab-pane, .pill-content > .pill-pane { display: none; }
.tab-content > .active, .pill-content > .active { display: block; }

/* =====================================================================
   CS-PATCH: bs2-base-typography  2026-07-14  (found on HyperShop)
   BS2 bootstrap.min.css carried the base type scale (h1 38.5/40, h2 31.5,
   h3 24.5, h4 17.5, h5 14, h6 11.9, p and list margins). Templates rely on
   it for any heading template.css does not size itself — without it module
   content headings fall to UA defaults and text banners collapse (HyperShop
   top-long promo rendered 103px tall vs 273px on live).
   Wrapped in :where() so specificity is ZERO: these beat UA defaults but
   lose to every template.css rule — faithfully reproducing BS2s
   loaded-before-template.css cascade position even though the bridge
   loads last.
   ===================================================================== */
:where(p) { margin: 0 0 10px; }
:where(h1, h2, h3, h4, h5, h6) { margin: 10px 0; font-family: inherit; font-weight: bold; line-height: 20px; color: inherit; text-rendering: optimizelegibility; }
:where(h1, h2, h3) { line-height: 40px; }
:where(h1) { font-size: 38.5px; }
:where(h2) { font-size: 31.5px; }
:where(h3) { font-size: 24.5px; }
:where(h4) { font-size: 17.5px; }
:where(h5) { font-size: 14px; }
:where(h6) { font-size: 11.9px; }
:where(ul, ol) { padding: 0; margin: 0 0 10px 25px; }

/* =====================================================================
   CS-PATCH: row-clearfix-and-offsets  2026-07-14  (found on HyperShop)
   Phase 3a rewrites .row-fluid -> .row in template markup. BS2 .row-fluid
   carried (a) a table-clearfix so FLOATED .spanN children give the row
   height (HyperShop top-long banner collapsed 193px -> 3px without it),
   and (b) .offsetN margins (banner text block .span7.offset5 hugged the
   left edge without it). Mirror both onto .row. The ::before/::after
   boxes are zero-size and harmless if a genuinely-BS5 flex .row gets them.
   ===================================================================== */
.row::before, .row::after { content: ""; display: table; line-height: 0; }
.row::after { clear: both; }
.row > .offset1  { margin-left: 8.333333%;  }
.row > .offset2  { margin-left: 16.666667%; }
.row > .offset3  { margin-left: 25%;        }
.row > .offset4  { margin-left: 33.333333%; }
.row > .offset5  { margin-left: 41.666667%; }
.row > .offset6  { margin-left: 50%;        }
.row > .offset7  { margin-left: 58.333333%; }
.row > .offset8  { margin-left: 66.666667%; }
.row > .offset9  { margin-left: 75%;        }
.row > .offset10 { margin-left: 83.333333%; }
.row > .offset11 { margin-left: 91.666667%; }

/* =====================================================================
   CS-PATCH: fluid-descendant-offsets  2026-07-14  (found on HyperShop)
   BS2 fluid offsets applied to DESCENDANTS of a fluid row (module content
   like <div class="custom"><div class="span7 offset5">), not just direct
   children, using the fluid model: offsetN = N*8.547% (+2.564% gutter when
   not :first-child). Verified against live computed style (offset5
   :first-child = 354.688px / 830px = 42.735%). These land AFTER the
   simple-12col offset mirrors above and win by file order, matching the
   BS2 render exactly.
   ===================================================================== */
.row-fluid .offset1, .row .offset1   { margin-left: 11.111111%; }
.row-fluid .offset2, .row .offset2   { margin-left: 19.658120%; }
.row-fluid .offset3, .row .offset3   { margin-left: 28.205128%; }
.row-fluid .offset4, .row .offset4   { margin-left: 36.752137%; }
.row-fluid .offset5, .row .offset5   { margin-left: 45.299145%; }
.row-fluid .offset6, .row .offset6   { margin-left: 53.846154%; }
.row-fluid .offset7, .row .offset7   { margin-left: 62.393162%; }
.row-fluid .offset8, .row .offset8   { margin-left: 70.940171%; }
.row-fluid .offset9, .row .offset9   { margin-left: 79.487179%; }
.row-fluid .offset10, .row .offset10 { margin-left: 88.034188%; }
.row-fluid .offset11, .row .offset11 { margin-left: 96.581197%; }
.row-fluid .offset1:first-child, .row .offset1:first-child   { margin-left: 8.547009%; }
.row-fluid .offset2:first-child, .row .offset2:first-child   { margin-left: 17.094017%; }
.row-fluid .offset3:first-child, .row .offset3:first-child   { margin-left: 25.641026%; }
.row-fluid .offset4:first-child, .row .offset4:first-child   { margin-left: 34.188034%; }
.row-fluid .offset5:first-child, .row .offset5:first-child   { margin-left: 42.735043%; }
.row-fluid .offset6:first-child, .row .offset6:first-child   { margin-left: 51.282051%; }
.row-fluid .offset7:first-child, .row .offset7:first-child   { margin-left: 59.829060%; }
.row-fluid .offset8:first-child, .row .offset8:first-child   { margin-left: 68.376068%; }
.row-fluid .offset9:first-child, .row .offset9:first-child   { margin-left: 76.923077%; }
.row-fluid .offset10:first-child, .row .offset10:first-child { margin-left: 85.470085%; }
.row-fluid .offset11:first-child, .row .offset11:first-child { margin-left: 94.017094%; }

/* =====================================================================
   CS-PATCH: bs2-responsive-utilities-full  2026-07-14  (found on HyperShop @768)
   The bridge carried only the phone pair (.hidden-phone/.visible-phone).
   BS2 responsive-utilities also had tablet + desktop variants — without
   them .hidden-tablet content (HyperShop blue promo strip) shows at
   768-979 where live hides it, and .visible-desktop leaks into phone/
   tablet. Exact BS2.3.2 block, appended last so it wins over the partial
   phone-only rules above.
   ===================================================================== */
.visible-phone { display: none !important; }
.visible-tablet { display: none !important; }
.hidden-desktop { display: none !important; }
.visible-desktop { display: inherit !important; }
@media (min-width: 768px) and (max-width: 979px) {
  .hidden-desktop { display: inherit !important; }
  .visible-desktop { display: none !important; }
  .visible-tablet { display: inherit !important; }
  .hidden-tablet { display: none !important; }
}
@media (max-width: 767px) {
  .hidden-desktop { display: inherit !important; }
  .visible-desktop { display: none !important; }
  .visible-phone { display: inherit !important; }
  .hidden-phone { display: none !important; }
}


/* ============================================================
   CS-PATCH: vmproduct-ul-span-grid  2026-07-14  (found on Illusion)
   Children of block product ULs must use BS2 fluid-grid widths
   (span3 = 23.0769% + 2.5641% gutter, first-child 0) — the bridge's
   flex-row .spanN splits (25% etc.) overflow the row by the gutter
   and wrap the last tile to the next line.
   ============================================================ */
:where(ul.row-fluid.vmproduct > [class*="span"], ul.row-fluid.sl-products > [class*="span"], ul.row-fluid[class*="products"] > [class*="span"]) {
    float: left; display: block; box-sizing: border-box;
    margin-left: 2.564102564%; padding-left: 0; padding-right: 0; min-height: 1px;
}
:where(ul.row-fluid.vmproduct > [class*="span"]:first-child, ul.row-fluid.sl-products > [class*="span"]:first-child, ul.row-fluid[class*="products"] > [class*="span"]:first-child) { margin-left: 0; }
:where(ul.row-fluid.vmproduct > .span1, ul.row-fluid.sl-products > .span1, ul.row-fluid[class*="products"] > .span1) { width: 5.982905983%; }
:where(ul.row-fluid.vmproduct > .span2, ul.row-fluid.sl-products > .span2, ul.row-fluid[class*="products"] > .span2) { width: 14.529914530%; }
:where(ul.row-fluid.vmproduct > .span3, ul.row-fluid.sl-products > .span3, ul.row-fluid[class*="products"] > .span3) { width: 23.076923077%; }
:where(ul.row-fluid.vmproduct > .span4, ul.row-fluid.sl-products > .span4, ul.row-fluid[class*="products"] > .span4) { width: 31.623931624%; }
:where(ul.row-fluid.vmproduct > .span5, ul.row-fluid.sl-products > .span5, ul.row-fluid[class*="products"] > .span5) { width: 40.170940171%; }
:where(ul.row-fluid.vmproduct > .span6, ul.row-fluid.sl-products > .span6, ul.row-fluid[class*="products"] > .span6) { width: 48.717948718%; }
:where(ul.row-fluid.vmproduct > .span7, ul.row-fluid.sl-products > .span7, ul.row-fluid[class*="products"] > .span7) { width: 57.264957265%; }
:where(ul.row-fluid.vmproduct > .span8, ul.row-fluid.sl-products > .span8, ul.row-fluid[class*="products"] > .span8) { width: 65.811965812%; }
:where(ul.row-fluid.vmproduct > .span9, ul.row-fluid.sl-products > .span9, ul.row-fluid[class*="products"] > .span9) { width: 74.358974359%; }
:where(ul.row-fluid.vmproduct > .span10, ul.row-fluid.sl-products > .span10, ul.row-fluid[class*="products"] > .span10) { width: 82.905982906%; }
:where(ul.row-fluid.vmproduct > .span11, ul.row-fluid.sl-products > .span11, ul.row-fluid[class*="products"] > .span11) { width: 91.452991453%; }
:where(ul.row-fluid.vmproduct > .span12, ul.row-fluid.sl-products > .span12, ul.row-fluid[class*="products"] > .span12) { width: 100%; }

/* =====================================================================
   CS-PATCH: bs5modal-on-bs2  2026-07-14  (found on GliseStore)
   VMT cohort loads Joomla's BS5 JS bundle but NO Bootstrap CSS. BS5's
   Modal JS works (adds .show + injected .modal-backdrop.fade.show) but
   nothing styles the box or backdrop. Recreate the BS2 modal look keyed
   to BS5's state classes. Scoped to .modal/.modal-backdrop only — no
   global reboot fallout. Wrapper divs (.modal-dialog/.modal-content,
   injected per Phase 6 for BS5 _getDialog) stay transparent blocks.
   ===================================================================== */
.modal { position: fixed; top: 10%; left: 50%; z-index: 1050; width: 560px; margin-left: -280px;
         background-color: #fff; border: 1px solid rgba(0,0,0,.3); border-radius: 6px;
         box-shadow: 0 3px 7px rgba(0,0,0,.3); outline: none; display: none; }
.modal.show { display: block; }
.modal .modal-dialog, .modal .modal-content { display: block; position: static; }
.modal .modal-header { padding: 9px 15px; border-bottom: 1px solid #eee; }
.modal .modal-body { position: relative; max-height: 60vh; padding: 15px; overflow-y: auto; }
.modal .modal-footer { padding: 14px 15px 15px; text-align: right; background-color: #f5f5f5;
                       border-top: 1px solid #ddd; border-radius: 0 0 6px 6px; }
.modal-backdrop { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 1040; background-color: #000; }
.modal-backdrop.fade { opacity: 0; transition: opacity .15s linear; }
.modal-backdrop.show, .modal-backdrop.fade.show { opacity: .8; }
@media (max-width: 620px) { .modal { width: auto; left: 4%; right: 4%; margin-left: 0; } }

/* =====================================================================
   CS-PATCH: bs2-container-fixed-width  2026-07-14  (found on GliseStore)
   BS2 gave .container an EXPLICIT width per breakpoint (not max-width),
   so a .container nested in a NARROWER parent overflowed it on purpose —
   GliseStore's #tabs .container is 1170px inside a 1080px section on
   live. The cap patch above (max-width) cannot reproduce that; this
   :where() block adds the BS2 fixed widths at ZERO specificity so any
   template.css width rule still wins, exactly like BS2's loaded-first
   position. The max-width cap above continues to protect top-level
   containers from BS5's 1320px default.
   ===================================================================== */
@media (min-width: 1200px) { :where(.container:not(.container-fluid)) { width: 1170px; max-width: none; } }
@media (min-width: 980px) and (max-width: 1199px) { :where(.container:not(.container-fluid)) { width: 940px; max-width: none; } }
@media (min-width: 768px) and (max-width: 979px) { :where(.container:not(.container-fluid)) { width: 724px; max-width: none; } }
@media (max-width: 767px) { :where(.container:not(.container-fluid)) { width: auto; } }

/* =====================================================================
   CS-PATCH: bs2-nav-block-anchors  2026-07-14  (found on Furniture)
   BS2 carried `.nav > li > a { display: block }`. Without it tab/nav
   anchors are UA inline; a block child span (e.g. .hidden-phone label)
   fragments the inline box and the li collapses to the label width —
   anchor padding vanishes from layout and tab labels render cramped
   together (Furniture LATEST/SALE/FEATURED). :where() = zero specificity.
   ===================================================================== */
:where(.nav) { margin-left: 0; margin-bottom: 20px; list-style: none; }
:where(.nav > li > a) { display: block; }

/* =====================================================================
   CS-PATCH: hidden-phone-no-desktop-override  2026-07-24  (found on Explore)
   Supersedes hidden-phone-inherit-fix (2026-07-14, Furniture). At >=768
   this donor cohort's BS2 build declares NO display for .hidden-phone —
   elements keep their own authored display. Both earlier bridge variants
   broke that: `block !important` fragmented inline tab-label spans
   (Furniture), and `inherit !important` flattened authored non-inherited
   displays (Explore slideshow caption h2 `display:table` shrink-to-fit
   bubbles rendered as full-width block bars). Fix: declare NOTHING at
   >=768; the <=767 `display:none !important` band above is unchanged.
   Furniture's tab anchors stay safe via bs2-nav-block-anchors.
   ===================================================================== */
@media (min-width: 768px) {
  .visible-phone { display: none !important; }
}

/* =====================================================================
   CS-PATCH: bs2-mobile-body-padding  2026-07-14  (found on Flexo @375)
   BS2 bootstrap-responsive.min.css gave <body> 20px side padding at
   <=767 — the mobile "gutter" every BS2 donor shows. Without it,
   containers that carry BS2 negative row margins bleed PAST the viewport
   edge (Flexo container at x=-10 vs live x=+10). :where() so any
   template mobile body rule still wins.
   ===================================================================== */
/* 2026-08-11 (found on Fusion, ET): :where() is TOO weak. It was chosen so a
   template mobile body rule could win, but specificity 0 also loses to a plain
   CSS reset - Fusion loads css/reset.css with body{padding:0}, so after Phase 5d
   stripped bootstrap-responsive.css the mobile gutter vanished entirely and
   content ran to the screen edge (stage container x=0 w=375 vs live x=20 w=335).
   Plain  (0,0,1) reproduces the ORIGINAL cascade exactly: that is the
   specificity BS2 shipped, and bootstrap-responsive.css loaded after the reset
   just as this bridge does. A donor rule that is genuinely more specific, or a
   later @media block of its own, still wins.
   OPT-OUT: a donor that was already responsive on its own never had the BS2
   gutter and must not gain one - HighLevel (ET) ships no css/responsive.css and
   its live mobile layout is full-bleed, so the restored 20px pushed its
   container to x=20 and created 20px of horizontal overflow. helpers-concern/
   et-gutter-optout.py tags <body class="cs-no-bs2-gutter"> for exactly those
   donors (rule: css/responsive.css absent), which keeps the discriminator a
   build-time fact instead of a per-template bridge block. */
@media (max-width: 767px) {
  body:not(.cs-no-bs2-gutter) { padding-left: 20px; padding-right: 20px; }
}

/* =====================================================================
   CS-PATCH: bs2-collapse  2026-07-14  (found on Explore)
   BS2 bootstrap.min.css carried the collapse contract:
   .collapse { height:0; overflow:hidden } + .collapse.in { height:auto }.
   Accordion menus (sidebar category menus) rely on it — without it every
   collapsed submenu occupies full layout height (Explore's menu module
   ballooned to 1223px and shoved the other sidebar modules 1400px down).
   BS5 JS toggles .show; BS2-era markup/JS toggles .in — honor both.
   ===================================================================== */
.collapse:not(.show):not(.in) { position: relative; height: 0; overflow: hidden; }
.collapse.in, .collapse.show { height: auto; }

/* =====================================================================
   CS-PATCH: rowfluid-no-stretch  2026-07-14  (found on Explore)
   BS2 .row-fluid was block+float — children NEVER stretched to the row
   height, so percentage heights on module chrome (.moduletable_menu
   { height:100% }) resolved to auto. The bridge's flex model stretches
   by default (align-items:stretch), turning that 100% into the full row
   height (Explore's sidebar menu ballooned to 1222px and shoved its
   sibling modules below the page). flex-start restores BS2 behavior.
   ===================================================================== */
.row-fluid { align-items: flex-start; }

/* ---------------------------------------------------------------------
   2026-08-11 (found on Evolution, ET) - the SAME bug through .row.
   Phase 3a rewrites the donor's literal class="span3" to class="col-md-3",
   and .row-fluid to .row, so from that point the stretching row is a REAL
   BS5 .row served by media/vendor/bootstrap/css/bootstrap.min.css - the
   patch above never sees it. Evolution's #col-left held two modules whose
   donor CSS sets height:100%; under BS5's align-items:stretch each resolved
   to the full 945px row height, so the second module (Short About Us) began
   956px down and painted over the footer.
   Rows that WANT equal-height columns opt in with .flex-stretch or a BS5
   align-items-* utility, so both keep working.
   --------------------------------------------------------------------- */
.row:not(.flex-stretch):not([class*="align-items-"]) { align-items: flex-start; }



/* ============================================================
   CS-PATCH: bs5-utility-classes  2026-07-28  (found on Trento)
   Joomla 6 core modules emit BOOTSTRAP 5 UTILITY CLASSES and assume BS5 core
   CSS is loaded. These templates deliberately do NOT load BS5 core (an explicit
   BS5 load defeats the bridge's :where() rules - BoaxStore 2026-07-15), so any
   core markup relying on a BS5 utility silently renders unstyled.

   Burned on Trento: mod_finder ships its search label as
   `<label class="visually-hidden finder">Search</label>` - correct, accessible
   markup - but with no BS5 CSS the class did nothing and the label painted as
   tiny grey text above the search field. Tim flagged it as unreadable AND
   inaccessible on the grad-test walkaround.

   Supply the utilities rather than hiding the markup, so screen readers keep
   their accessible names. Detect: any stray micro-text near a core module.
   ============================================================ */
.visually-hidden,
.sr-only,
.visually-hidden-focusable:not(:focus):not(:focus-within) {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
}


/* ============================================================
   CS-PATCH: search-panel-sizing  2026-07-28  (standard, set on Trento)
   Tim, grad-test walkaround: "the search box is very small" - twice, once for
   mobile and again for desktop. The donors ship a ~170px-wide dropdown with a
   ~29px-tall field, which is unusable on a phone and mean on a desktop.

   STANDARD (apply to every template from here on):
     * mobile/tablet (<=979px): full-width panel directly UNDER the header,
       with a >=44px touch-height field at >=16px font (below 16px iOS zooms
       the page on focus).
     * desktop (>=980px): keep it a dropdown under the magnifier, but >=320px
       wide with a >=40px field.

   Three traps this had to solve on Trento, all likely on siblings:
     1. `#searchpanel`'s real offsetParent was `#search-position-handler`
        (position:relative, 40px wide) - it must be neutralised or the panel is
        pinned to a 40px box. Same for `#search-position`.
     2. template.css carries `#search-position #searchpanel { top:-6px }` -
        specificity (2,0,0). A plain `#searchpanel` rule LOSES no matter how
        late it is, so match the two-id specificity.
     3. the donors pin the panel to a fixed `height:40px`, so a 44px field
        spills out and paints behind the slideshow. Force `height:auto`.
   ============================================================ */
@media (max-width: 979px) {
    #search-position,
    #search-position-handler {
        position: static;
    }
    #search-position #searchpanel,
    #searchpanel {
        position: absolute !important;
        left: 0 !important;
        right: 0 !important;
        top: 100% !important;
        width: auto !important;
        max-width: none;
        box-sizing: border-box;
        padding: 10px 12px;
        z-index: 30;
        height: auto !important;
        min-height: 0;
        overflow: visible;
    }
    #search-position #searchpanel > *,
    #searchpanel form,
    #searchpanel .mod-finder,
    #searchpanel .search { float: none !important; width: 100%; }
    #searchpanel input[type="text"],
    #searchpanel input[type="search"],
    #searchpanel .js-finder-search-query,
    #searchpanel .inputbox,
    #searchpanel .awesomplete {
        width: 100% !important;
        height: 44px;
        box-sizing: border-box;
        font-size: 16px;
        padding: 6px 10px;
    }
}
@media (min-width: 980px) {
    #search-position #searchpanel,
    #searchpanel {
        width: 340px !important;
        padding: 12px !important;
        height: auto !important;
    }
    #search-position #searchpanel input[type="text"],
    #search-position #searchpanel input[type="search"],
    #searchpanel .js-finder-search-query,
    #searchpanel .inputbox {
        width: 100% !important;
        height: 40px !important;
        box-sizing: border-box;
        font-size: 15px;
        padding: 6px 10px;
    }
    #searchpanel .awesomplete,
    #searchpanel form,
    #searchpanel .mod-finder { width: 100% !important; display: block; }
}
/* NOTE: the panel's background is intentionally NOT set here - it must match the
   host template's header colour. Set it in the per-template bridge copy tail. */


/* ============================================================
   CS-PATCH: mobile-utility-row-order  2026-07-28  (standard, set on Trento)
   Tim: the secondary-menu hamburger goes FAR LEFT of the utility row and search
   FAR RIGHT - which is the order the Master-Checklist mobile utility row already
   specifies: [secondary-menu toggle] [account] [register] [search] [cart].
   Also: zero the per-item margins and let ONE `gap` do the spacing, otherwise the
   log-panel <li>s add their own and the row spaces unevenly (Trento measured
   9 / 18 / 11px before, 10 / 10 / 10px after).

   ⚠️ Pairs with the codified hamburger split (Master-Checklist "Top ham: main
   menu only"): the LARGE top hamburger opens the MAIN menu; this SMALL row
   hamburger opens the off-canvas / secondary menu.
   ============================================================ */
@media (max-width: 979px) {
    .main-menu-handler { gap: 10px; }
    .main-menu-handler .open-off-canvas,
    .main-menu-handler #shop-menu-btn { order: 1 !important; }
    .main-menu-handler #tpr-nav { order: 2 !important; }
    .main-menu-handler #search-position,
    .main-menu-handler #search-position-handler { order: 3 !important; }
    .main-menu-handler #tpr-nav ul.log-panel { gap: 10px; }
    .main-menu-handler #tpr-nav ul.log-panel > li { margin: 0 !important; padding: 0 !important; }
    .main-menu-handler #searchOpenButton,
    .main-menu-handler .open-off-canvas,
    .main-menu-handler #tpr-nav ul.log-panel > li > a { margin: 0 !important; }
}

/* ==========================================================================
   CS-PATCH: joomla-icon-classes-to-fa6   PROMOTED TO CANONICAL 2026-07-31

   Joomla core emits its OWN icon vocabulary in front-end views:
       <span class="icon-eye icon-fw" aria-hidden="true"></span>      password toggle
       <span class="icon-angle-left"></span>                          pagination
   Cassiopeia ships the CSS that maps `icon-*` onto Font Awesome. A THIRD-PARTY
   TEMPLATE DOES NOT. Loading FA6 + v4-shims is not enough either — v4-shims only
   translates the old `fa-*` v4 names onto v6 and knows nothing about `icon-*`.

   Measured consequences on templates without this block (2026-07-31, headless
   render of the live demos): the password show/hide control on Login and
   Registration is an EMPTY SQUARE, and the Previous / Next / First / Last
   pagination buttons are COMPLETELY BLANK — `content: none`, computed width 0px,
   font-family falling back to the body face. Confirmed dead on Nexxy (5/5 glyphs),
   Trento (4/4) and Progressive (5/5) before this was promoted here.

   Origin: authored on Presence 2026-07-30, proven fleet-wide 2026-07-31.
   Every template that copies the canonical bridge now inherits it.
   ========================================================================== */

/* CS-PATCH: kill-bs2-glyphicons-sprite  2026-08-06  (Energize, Tim found it)
   Donors frequently bake the BS2 glyphicons-halflings SPRITE straight into their
   OWN template.css (`[class^="icon-"]{background-image:...glyphicons-halflings.png}`
   + `.icon-eye{background-position:...}`). Phase 3b.6 purges the standalone BS2
   files (bootstrap.css/responsive.css) but NOT sprite rules embedded in the kept,
   loaded template.css — so the old 14px glyphicon (e.g. an off-centre-pupil eye,
   pinned top-left of the icon ELEMENT) paints BEHIND our ::before glyph and peeks
   out. Kill the legacy sprite on icon-* elements; all glyphs come from ::before
   (FA6 map + SVG eye). Nothing post-modernize uses the BS glyphicons sprite. */
[class^="icon-"], [class*=" icon-"] { background-image: none !important; }

/* base: give every Joomla icon-* the FA6 face + metrics */
[class^="icon-"]::before,
[class*=" icon-"]::before {
  font-family: "Font Awesome 6 Free";
  font-weight: 900;
  font-style: normal;
  font-variant: normal;
  line-height: 1;
  text-rendering: auto;
  -webkit-font-smoothing: antialiased;
  display: inline-block;
}

/* brands need the 400 weight face */
.icon-facebook::before, .icon-twitter::before, .icon-youtube::before,
.icon-linkedin::before, .icon-instagram::before {
  font-family: "Font Awesome 6 Brands";
  font-weight: 400;
}

/* fixed-width helper, same contract as fa-fw */
.icon-fw { display: inline-block; width: 1.25em; text-align: center; }

/* --- the glyphs Joomla core emits in FRONT-END views --------------------- */
/* CS 2026-08-06 (promoted from Energize, Tim-approved, FINAL/3rd pass):
   FontAwesome's eye has NO dead-centre pupil (SOLID glances up-left; REGULAR has
   an off-centre crescent), and the white-eye/green-hole SVG read as too subtle.
   Replaced with an explicit two-colour background-image SVG: a WHITE almond eye
   with a SOLID DARK pupil circle at the EXACT centre (cx=cy=12 of a 0 0 24 24
   box). Unmistakably centred; eye-slash adds a diagonal bar. Colours are fixed
   (works on the toggle's coloured button); other icon-* stay on the FA6 map. */
.icon-eye::before, .icon-eye-open::before,
.icon-eye-slash::before, .icon-eye-close::before {
  content: "" !important; display: inline-block;
  width: 1.15em; height: 1.15em; vertical-align: -0.2em;
  background-color: transparent;
  -webkit-mask: none; mask: none;
  background-repeat: no-repeat; background-position: center; background-size: contain;
}
.icon-eye::before, .icon-eye-open::before {
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20viewBox=%270%200%2024%2024%27%3E%3Cpath%20fill=%27%23fff%27%20d=%27M12%204.5C5.5%204.5%201.3%209.8%200%2012c1.3%202.2%205.5%207.5%2012%207.5S22.7%2014.2%2024%2012C22.7%209.8%2018.5%204.5%2012%204.5Z%27/%3E%3Ccircle%20cx=%2712%27%20cy=%2712%27%20r=%273.3%27%20fill=%27%23324%27/%3E%3C/svg%3E");
}
.icon-eye-slash::before, .icon-eye-close::before {
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20viewBox=%270%200%2024%2024%27%3E%3Cpath%20fill=%27%23fff%27%20d=%27M12%204.5C5.5%204.5%201.3%209.8%200%2012c1.3%202.2%205.5%207.5%2012%207.5S22.7%2014.2%2024%2012C22.7%209.8%2018.5%204.5%2012%204.5Z%27/%3E%3Ccircle%20cx=%2712%27%20cy=%2712%27%20r=%273.3%27%20fill=%27%23324%27/%3E%3Crect%20x=%271%27%20y=%2711%27%20width=%2722%27%20height=%272.2%27%20rx=%271.1%27%20fill=%27%23324%27%20transform=%27rotate(45%2012%2012)%27/%3E%3C/svg%3E");
}
.icon-user::before              { content: "\f007"; }
.icon-lock::before              { content: "\f023"; }
.icon-unlock::before            { content: "\f09c"; }
.icon-envelope::before,
.icon-mail::before              { content: "\f0e0"; }
.icon-search::before            { content: "\f002"; }
.icon-calendar::before          { content: "\f133"; }
.icon-checkmark::before,
.icon-check::before             { content: "\f00c"; }
.icon-cancel::before,
.icon-remove::before,
.icon-delete::before            { content: "\f00d"; }
.icon-warning::before,
.icon-exclamation::before       { content: "\f071"; }
.icon-info::before,
.icon-info-circle::before       { content: "\f05a"; }
.icon-question::before          { content: "\f059"; }
.icon-star::before              { content: "\f005"; }
.icon-print::before             { content: "\f02f"; }
.icon-pencil::before,
.icon-edit::before              { content: "\f303"; }
.icon-plus::before              { content: "\f067"; }
.icon-minus::before             { content: "\f068"; }
.icon-chevron-left::before,
.icon-arrow-left::before        { content: "\f053"; }
.icon-chevron-right::before,
.icon-arrow-right::before       { content: "\f054"; }
.icon-chevron-up::before,
.icon-arrow-up::before          { content: "\f077"; }
.icon-chevron-down::before,
.icon-arrow-down::before        { content: "\f078"; }
.icon-list::before              { content: "\f03a"; }
.icon-home::before              { content: "\f015"; }
.icon-cog::before,
.icon-options::before           { content: "\f013"; }

/* --- Joomla PAGINATION arrows (cookstown fix-set item 2) -----------------
   Joomla 4+ emits .icon-angle-left / -right / -double-left / -double-right for
   Previous / Next / First / Last. They are part of the same `icon-*` Joomla
   vocabulary as the block above and were equally unmapped. MEASURED on the live
   Presence category list: all four resolved to `content: none` at width 0px —
   i.e. the four pagination buttons were completely blank. Font-family was
   already correct ("Font Awesome 6 Free"), only the glyph was missing.        */
.icon-angle-left::before,
.icon-previous::before                { content: "\f104"; }
.icon-angle-right::before,
.icon-next::before                    { content: "\f105"; }
.icon-angle-up::before                { content: "\f106"; }
.icon-angle-down::before              { content: "\f107"; }
.icon-angle-double-left::before,
.icon-first::before                   { content: "\f100"; }
.icon-angle-double-right::before,
.icon-last::before                    { content: "\f101"; }

/* --- featured / blog intro clipping (cookstown fix-set item 3) ------------
   J6's featured view is a CSS grid and blog.css sets .blog-item{overflow:hidden},
   which clips a tall single-column intro article to its title. Applied
   defensively: Presence's own featured view measured unclipped (height ==
   scrollHeight) because it renders multi-column, but a customer who sets the
   featured layout to one column would hit it.                                 */
/* SCOPED 2026-07-31 (Oxygen J6 run): was unscoped and collapsed every
   multi-column featured grid to one stacked column. The clipping this
   fixes only occurs in the one-column layout, so scope it there. */
.blog-featured .blog-items.columns-1 { display: block; }
.blog-featured .blog-items.columns-1 .blog-item { display: block; overflow: visible; height: auto; }


/* the toggle button itself was 26px wide — give it a usable target */
.input-password-toggle {
  min-width: 42px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}


/* =====================================================================
   CS-PATCH: navbar-fixed-top-zindex  2026-07-31  (found on Oxygen, J6 run)
   BS2 had `.navbar-fixed-top { position:fixed; ...; z-index:1030 }`. BS5 renamed
   the class to `.fixed-top`, so donors that kept the BS2 class name lose every
   declaration once BS2 CSS is stripped -- including the stacking context. Any
   header sitting over a positioned slideshow (`.camera_wrap` is
   position:relative; z-index:0) then paints UNDERNEATH it: present in the DOM,
   correct size, completely invisible. Measured on Oxygen @768/@375.
   Restores the z-index only; `position` is left to the per-family patches,
   which decide the breakpoint at which the header is fixed.
   ===================================================================== */
.navbar-fixed-top,
.navbar-fixed-bottom { z-index: 1030; }


/* =====================================================================
   CS-PATCH: rewritten-row-gutter-strip  2026-07-31  (found on Oxygen, J6 run)
   Phase 3a rewrites `.row-fluid` -> `.row`. BS2's .row-fluid had NO gutter;
   BS5's .row carries negative side margins plus per-child padding, which BS5
   expects a .container's padding to absorb. A rewritten donor row normally sits
   in a full-bleed wrapper, so nothing does.
   MEASURED on Oxygen: #tab-modules-handler.row rendered left:-12 right:387 in a
   375px viewport -> scrollWidth 387 vs live 375 (12px horizontal scroll on every
   phone); and the row measured 1424 not 1400 at desktop, making .span3 356px
   against live's 350px.
   Scoped via :has() to rows that really do hold legacy .spanN children, so
   genuine BS5 .col-* rows keep their gutters.
   ===================================================================== */
.row:has(> [class*="span"]),
.row:has(> [class*="col-md-"]) {
    --bs-gutter-x: 0;
    margin-left: 0;
    margin-right: 0;
}


/* =====================================================================
   CS-PATCH: container-fluid-negative-margin-bleed  2026-08-03  (Oxygen, Tim flagged)
   BS2 donors bleed full-width bands past the mobile body gutter with negative side
   margins (responsive-template.css @<=767: `#nav-line, #main-handler, .info-area …
   { margin: 0 -20px }`), paired with BS2's `body{padding:0 20px}`.

   BS2 gave `.container-fluid` no width, so `width:auto` + negative margins widened
   the box to the full viewport. BS5 sets `.container-fluid { width: 100% }`, and a
   percentage width resolves against the CONTAINING BLOCK while ignoring the element's
   own negative margins — so the box keeps its old width and merely shifts LEFT,
   bleeding off the left edge and leaving a gap of the same size down the RIGHT.

   MEASURED on Oxygen's blog page @375 (body content box 20..355):
     #nav-line.container-fluid  x=0  right=335   (wanted 0..375)
     .breadcrumbs               x=20 right=315   (40px short on the right)
     .tmp-content-area          x=7  right=328   (skinny left, huge right margin)

   Restoring auto width is identical to width:100% when the margins are 0, and is the
   only thing that bleeds symmetrically when they are negative.
   ===================================================================== */
@media (max-width: 767px) {
    .container-fluid { width: auto; }
}


/* =====================================================================
   CS-PATCH: rewritten-row-first-child-gutter  2026-08-06  (found on Creativa, J6 run)
   TWIN to rewritten-row-gutter-strip (2026-07-31, Oxygen). That patch removed
   BS5's gutter from a Phase-3a-rewritten `.row`; this one restores BS2's
   first-child margin RESET, which the same rewrite also orphaned.

   BS2 always shipped these as a PAIR:
       [class*="span"]                          { margin-left: 20px }  <- donor responsive.css
       .row-fluid > [class*="span"]:first-child { margin-left: 0    }  <- BS2 bootstrap.css
   Phase 3a rewrites the markup `.row-fluid` -> `.row`, so the SECOND rule stops
   matching while the FIRST keeps firing on every child. The bridge's standalone
   `:where(.spanN):where(:first-child){margin-left:0}` cannot rescue it, because
   `:where()` is specificity 0 and the donor's `[class*="span"]` is (0,1,0).

   MEASURED on Creativa @1400: footer .span4:first-child took margin-left:20px
   (live: 0px), so 3x span4 + 3x 20px gutter overflowed the row and the 3-across
   footer wrapped 2+1; the 4-across feature row wrapped 3+1. Creativa's own
   responsive.css supplies the 20px at (0,1,0) in BOTH @media(min-width:1200px)
   and @media(768px-979px), with no :first-child twin in either block.

   :not([class*="offset"]) is REQUIRED — the offsetN:first-child rules earlier in
   this file are also (0,3,0), and an unguarded rule appended here would win the
   tie and zero every first-child offset.
   ===================================================================== */
.row > [class*="span"]:not([class*="offset"]):first-child,
.row-fluid > [class*="span"]:not([class*="offset"]):first-child {
    margin-left: 0;
}


/* =====================================================================
   CS-PATCH: container-bs5-padding-strip  2026-08-06  (found on Creativa, J6 run)
   BS5's .container carries `padding: 0 calc(var(--bs-gutter-x) * .5)` = 12px a
   side. BS2's .container had NO horizontal padding — verified against the pinned
   Creativa donor: .container = 940px @ x=223 with padding-left/right both 0px.

   Because these donors size .container by WIDTH (not max-width), BS5's padding is
   subtracted from the content box instead of added outside it: every container's
   children render 24px narrower and 12px to the right of live.

   MEASURED on Creativa @1400: #footer 916 @ x=235 vs live 940 @ x=223 — which is
   also what pushed the span math over the edge and forced the wraps above.

   Zeroing the CUSTOM PROPERTY rather than the padding keeps this surgical:
   a template that sets its own .container padding still wins normally, and any
   genuine BS5 .row inside re-declares --bs-gutter-x: 1.5rem on itself, so real
   .col-* gutters are untouched.
   ===================================================================== */
.container:not(.container-fluid) {
    --bs-gutter-x: 0;
}


/* =====================================================================
   CS-PATCH: rewritten-row-percent-gutter  2026-08-06  (found on Creativa, J6 run)
   THIRD member of the "the .row-fluid rule never got a .row twin after Phase 3a"
   family, with rewritten-row-gutter-strip (07-31) and
   rewritten-row-first-child-gutter (above).

   The bridge deliberately keeps `.row-fluid > [class*="span"]`'s 2.564% gutter at
   REAL specificity (see the Explore note at the top of this file) so a donor's
   element-level margin resets cannot beat it. That rule never gained a `.row`
   twin, so on a Phase-3a-rewritten row the gutter falls through to whatever the
   donor's own BS2-era responsive.css says — typically a FIXED `20px`.

   Mixing the bridge's PERCENTAGE widths with the donor's FIXED gutters yields a
   grid that cannot tile. MEASURED on Creativa @1400 (container pinned to 940px by
   template.css at every desktop width, so the 940 vs 1170 grid ratio differs):
       3 x span4 @ 31.6239% (297.3) + 2 x 20px  = 931.9  -> 8px short of 940
       4 x span3 @ 23.0769% (219.0) + 3 x 20px  = 936.0  -> 13px short
   i.e. the last footer column and the last feature column never reach the
   container edge — the classic "bunch-left / short fill" signature.

   With the gutter ALSO owned by the bridge the model closes exactly, at ANY
   container width, because the two were derived together:
       3 x 31.62393162% + 2 x 2.564102564% = 100%
       4 x 23.07692308% + 3 x 2.564102564% = 100%
   This is why the pair must always be applied together — taking the bridge's
   widths while leaving the donor's gutters is what breaks fill-width.

   The <=767px mobile block later in this file zeroes these with !important, so
   mobile stacking is unaffected.
   ===================================================================== */
.row > [class*="span"] {
    box-sizing: border-box;
    min-height: 1px;
    margin-left: 2.564102564%;
}
.row > [class*="span"]:not([class*="offset"]):first-child {
    margin-left: 0;
}


/* =====================================================================
   CS-PATCH: superfish-submenu-mobile-overflow  2026-08-06  (found on Creativa, J6 run)
   Superfish-era donors park their dropdown submenus off-screen with
       .menu-nav ul { position: absolute; top: -999em; width: 10em; }
   Note it sets TOP but never LEFT. On desktop that is fine — the submenu sits
   above the viewport and its static horizontal offset is inside the container.

   At phone widths the top-level <li>s wrap, which pushes the nested absolute
   <ul>s' static offset far to the right. A negative `top` does not create
   scrollable area, but a positive `left` beyond the viewport does, so the page
   grows a horizontal scrollbar even though nothing is visible out there.

   MEASURED on Creativa @375: documentElement.clientWidth 360 vs scrollWidth 673
   — 240 elements overflowing, the widest being nested menu <ul>/<li> at
   x=553..673. The pinned live donor has NO horizontal scroll, because it dumps
   the menu as a flat static list instead, so this is a genuine regression the
   donor-parity check cannot attribute.

   Scoped to <=767px so desktop hover dropdowns are untouched. `display:none`
   rather than `left:-999em` because a tap can latch :hover on touch devices and
   `.menu-nav li:hover ul {left:0}` would otherwise drag them back on screen.

   NOTE: this only CONTAINS the overflow. Making the sub-items reachable on a
   phone is the Skill 33 Mobile-header UX standard (hamburger + accordion), which
   requires a DOM-family decision — Creativa matches none of the three families.
   ===================================================================== */
@media (max-width: 767px) {
    .menu-nav ul { display: none !important; }
}


/* =====================================================================
   CS-PATCH: row-wrapping-row-gutter-strip  2026-08-06  (found on Creativa, J6 run)
   Addendum to rewritten-row-gutter-strip (2026-07-31, Oxygen).

   That patch scopes itself with `:has(> [class*="span"])` / `:has(> [class*="col-md-"])`
   so genuine BS5 `.col-*` rows keep their gutters. But donors also use `.row` as a bare
   LAYOUT WRAPPER around another `.row` — that outer row has neither a span nor a col child,
   so it falls through the scoping and ends up with **asymmetric** margins: BS2's
   `.row { margin-left: -20px }` from the donor's responsive.css on one side, and BS5's
   `margin-right: calc(-.5 * var(--bs-gutter-x))` = -12px on the other.

   MEASURED on Creativa @1585 after the container went to 1440: the wrapper around
   #main-content-handler computed `margin-left:-20px; margin-right:-12px`, so it rendered
   **1472px** — 32px wider than its 1440 container, hanging 20px off the left edge and 11px
   off the right. The sidebar and content columns inside inherited the offset.

   A row whose direct child is itself a row is a wrapper, not a grid: BS5 never nests
   `.row > .row` directly (it goes `.row > .col-* > .row`), so this is a safe signal.
   ===================================================================== */
.row:has(> .row) {
    --bs-gutter-x: 0;
    margin-left: 0;
    margin-right: 0;
}

/* =====================================================================
   CS-PATCH: orphan-row-block  2026-08-08  (found on Acceler8, J6 run)
   Sixth member of the .row-twin family. Companion to
   row-wrapping-row-gutter-strip (2026-08-06): that one handles a .row whose
   child is another .row; this one handles a .row with NO grid child at all.

   Phase 3a rewrites class="row-fluid" into class="row". Where the row is a
   real grid row (a span or col child) BS5 flex is what we want. But donors
   also use .row-fluid as a PLAIN BLOCK WRAPPER whose children carry no grid
   class. Acceler8's header is exactly that: site-name-handler, then menu,
   then top-quick-nav, none of them carrying a grid class.

   In BS2 that was block plus floats: the logo floated left at 150px and the
   nav (display inline-block) sat beside it, giving a 34px bar.

   Under BS5 TWO rules break it, and fixing only the first is not enough:
     .row     sets display flex        - children blockify, row grows tallest
     .row > * sets width 100% + gutter padding - every child goes full width

   MEASURED on Acceler8 at 1400 before then after (live donor in brackets):
       #top height          171  ->  34                      [34]
       #menu                x=-12 w=1424 block -> x=150 w=310 inline-block
                                                              [x=150 w=310]
       #site-name-handler   x=-12 -> x=0                      [x=0]

   Restore BS2 block flow for orphan rows ONLY; any row with a grid child
   keeps flex. The second selector is mandatory - display alone leaves every
   child at width 100% and the header still stacks.

   AUTHORING NOTE: never write a star followed by a slash inside a CSS
   comment. The first draft of this block described the grid selectors as
   "span" star slash "col" and that sequence CLOSED THE COMMENT EARLY, so the
   remaining prose parsed as garbage and silently swallowed the rule below it.
   The bridge still linted, still had balanced braces, and still served 72KB -
   the rule was simply absent from the CSSOM. Assert on the CSSOM, not the file.
   ===================================================================== */
.row:not(:has(> [class*="col-"])):not(:has(> [class*="span"])) {
    display: block;
    --bs-gutter-x: 0;
    margin-left: 0;
    margin-right: 0;
}
.row:not(:has(> [class*="col-"])):not(:has(> [class*="span"])) > * {
    width: auto;
    max-width: none;
    padding-left: 0;
    padding-right: 0;
    margin-top: 0;
}

/* =====================================================================
   CS-PATCH: swiper-slideshow-height  2026-08-08  (found on Brisk, J6 run)

   Phase 5a migrates the donor's camera.js slideshow to Swiper 11 and emits
   #ph-camera-slideshow with .swiper-wrapper / .swiper-slide children whose
   image is a CSS background, not an img element. Background images have no
   intrinsic height, and Swiper does not size its own container, so the whole
   slideshow collapses to zero height. The donor's own rule only sets
   min-height on the WRAPPER, #slideshow-handler, which keeps reserving the
   right amount of space - so the page height is correct and the slideshow is
   simply invisible. Every numeric parity check passes.

   MEASURED on Brisk at 1400 (live camera_wrap in brackets):
       #slideshow-handler   560   [560]   <- correct, min-height applied
       .swiper                0   [560]
       .swiper-wrapper        0     -
       .swiper-slide          0     -
   After this patch all three read 560 and the hero matches live exactly.

   min-height:inherit pulls the handler's value down the chain without needing
   a definite height anywhere, so it works whatever the template param is set to.
   ===================================================================== */
#slideshow-handler {
    position: relative;
}
/* 2026-08-11 (MagnetaShop): the width pin below is scoped away from grid
   COLUMNS. `#slideshow-handler` is (1,0,0) and beat `.row > .span9` (0,2,0), so
   on every template that keeps camera.js and puts its slideshow in a span the
   hero went full width and wrapped below the menu column it should sit beside.
   Templates in the Swiper cohort have a plain wrapper here, so they still match
   and still get the pin. */
#slideshow-handler:not([class*="span"]):not([class*="col-"]) {
    /* width MUST be pinned: the Phase 5a block gives this element an
       aspect-ratio, and an aspect-ratio on a box that also carries the donor's
       min-height resolves in the OTHER direction - width = min-height * ratio.
       On Acceler8 (min-height 400px, ratio 2/1) that produced an 800px-wide
       handler inside a 375px viewport and 426px of horizontal scroll; on Brisk
       it produced 1400px. Pinning width lets the ratio drive HEIGHT only. */
    width: 100%;
    max-width: 100%;
}
#ph-camera-slideshow.swiper { min-height: inherit; height: 100%; }
#ph-camera-slideshow .swiper-wrapper { min-height: inherit; height: 100%; }
#ph-camera-slideshow .swiper-slide { min-height: inherit; height: auto; }

/* =====================================================================
   CS-PATCH: container-fluid-padding-strip  2026-08-08  (found on Concern, J6 run)
   Twin of container-bs5-padding-strip, which only ever covered .container.

   Bootstrap 5 gives BOTH .container and .container-fluid
   padding-left/right: calc(var(--bs-gutter-x) * .5) = 12px a side.
   Bootstrap 2, which these donors were built on, gave .container-fluid NO
   padding at all. The existing strip zeroes the custom property on .container
   but not on .container-fluid, and these donors nest
   .container INSIDE .container-fluid - so the inner container inherits a 12px
   inset from its parent and every page sits 12px right of where the donor puts it.

   MEASURED on Concern, inner .container (live donor in brackets):
       @375   x=12  w=351   ->  x=0    w=375    [x=0   w=375]
       @1400  x=127 w=1170  ->  x=115  w=1170   [x=115 w=1170]

   Note the desktop half: the harness's container check compares WIDTH, which
   was already 1170, so a 12px horizontal shift of the entire page passed every
   numeric gate. Only the mobile gutter check caught it.
   ===================================================================== */
.container-fluid {
    --bs-gutter-x: 0;
    padding-left: 0;
    padding-right: 0;
}

/* =====================================================================
   CS-PATCH: icon-eye-currentcolor  2026-08-08  (found on Acceler8, Tim's walkaround)
   Supersedes the colour half of the SVG eye above.

   That eye is drawn with a HARDCODED WHITE body and a dark pupil, and its own
   comment says the colours are fixed because it sits on "the toggle's coloured
   button". On Acceler8 the password toggle is .btn-secondary with a WHITE
   background, so the white eye body disappears into the button and only the
   dark pupil survives - the control reads as a single DOT. Hovering darkens the
   button, the white body reappears, and the icon seems to materialise out of
   nowhere. Measured: button background rgb(255,255,255), span colour
   rgb(128,128,128), ::before content "" with a white-filled background-image.

   Fix: stop painting a fixed colour. Use the eye as a MASK and fill it with
   currentColor, so it always inherits the button's own ink and can never be
   invisible against its own background - on any template, any button colour.
   ===================================================================== */
.icon-eye::before, .icon-eye-open::before,
.icon-eye-slash::before, .icon-eye-close::before {
  background-image: none !important;
  background-color: currentColor !important;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
}
.icon-eye::before, .icon-eye-open::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20fill-rule%3D%27evenodd%27%20d%3D%27M12%204.5C5.5%204.5%201.3%209.8%200%2012c1.3%202.2%205.5%207.5%2012%207.5S22.7%2014.2%2024%2012C22.7%209.8%2018.5%204.5%2012%204.5ZM12%207.7a4.3%204.3%200%201%200%200%208.6%204.3%204.3%200%200%200%200-8.6ZM12%209.8a2.2%202.2%200%201%201%200%204.4%202.2%202.2%200%200%201%200-4.4Z%27%2F%3E%3C%2Fsvg%3E") !important;
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20fill-rule%3D%27evenodd%27%20d%3D%27M12%204.5C5.5%204.5%201.3%209.8%200%2012c1.3%202.2%205.5%207.5%2012%207.5S22.7%2014.2%2024%2012C22.7%209.8%2018.5%204.5%2012%204.5ZM12%207.7a4.3%204.3%200%201%200%200%208.6%204.3%204.3%200%200%200%200-8.6ZM12%209.8a2.2%202.2%200%201%201%200%204.4%202.2%202.2%200%200%201%200-4.4Z%27%2F%3E%3C%2Fsvg%3E") !important;
}
.icon-eye-slash::before, .icon-eye-close::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20fill-rule%3D%27evenodd%27%20d%3D%27M12%204.5C5.5%204.5%201.3%209.8%200%2012c1.3%202.2%205.5%207.5%2012%207.5S22.7%2014.2%2024%2012C22.7%209.8%2018.5%204.5%2012%204.5ZM12%207.7a4.3%204.3%200%201%200%200%208.6%204.3%204.3%200%200%200%200-8.6ZM12%209.8a2.2%202.2%200%201%201%200%204.4%202.2%202.2%200%200%201%200-4.4Z%27%2F%3E%3Crect%20x%3D%271%27%20y%3D%2711%27%20width%3D%2722%27%20height%3D%272.2%27%20rx%3D%271.1%27%20transform%3D%27rotate%2845%2012%2012%29%27%2F%3E%3C%2Fsvg%3E") !important;
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20fill-rule%3D%27evenodd%27%20d%3D%27M12%204.5C5.5%204.5%201.3%209.8%200%2012c1.3%202.2%205.5%207.5%2012%207.5S22.7%2014.2%2024%2012C22.7%209.8%2018.5%204.5%2012%204.5ZM12%207.7a4.3%204.3%200%201%200%200%208.6%204.3%204.3%200%200%200%200-8.6ZM12%209.8a2.2%202.2%200%201%201%200%204.4%202.2%202.2%200%200%201%200-4.4Z%27%2F%3E%3Crect%20x%3D%271%27%20y%3D%2711%27%20width%3D%2722%27%20height%3D%272.2%27%20rx%3D%271.1%27%20transform%3D%27rotate%2845%2012%2012%29%27%2F%3E%3C%2Fsvg%3E") !important;
}

/* =====================================================================
   CS-PATCH: bs2-fade-in-to-show  2026-08-09  (found on Encore)
   Donors that ship their OWN Bootstrap 2 CSS mark an open modal/tab/alert
   with .in; Bootstrap 5 marks it with .show. BS2's blanket ".fade {
   opacity: 0 }" and "div.modal.fade { top: -25% }" therefore stay applied
   forever: BS5 opens the dialog, adds the backdrop and traps focus, but
   the box itself is invisible off the top of the viewport. Re-point the
   BS2 "shown" rules at BS5's class. div.modal.fade.in is 0,3,1 so the
   .show twin must carry the same weight and be later in the file.
   ===================================================================== */
.fade.show { opacity: 1; }
/* !! 2026-08-11 (found on ET Magnetic) - a donor that sets the modal's opacity
   through an ID (`#LoginForm.modal { opacity: .5 }`, 1,1,0) outranks any
   class-only rule, and BS5 never asserts opacity:1 on an open dialog - it just
   drops `.fade:not(.show){opacity:0}` and lets the cascade decide. Measured:
   the dialog opened, drew its backdrop and trapped focus at opacity 0.5. */
.modal.show, .modal.fade.show { opacity: 1 !important; }
div.modal.fade.show, .modal.fade.show { top: 10%; }
.modal.fade.show .modal-dialog { transform: none; }

/* =====================================================================
   CS-PATCH: mobile-root-overflow-clip  2026-08-09  (found on Encore)
   Match the live donors' effective behaviour: nothing scrolls sideways at
   phone width. Beyond the obvious cosmetic win this keeps window.innerWidth
   equal to the visible viewport, which every position:fixed overlay - the
   mobile drawer, scrim, search and social panels - sizes itself against.
   ===================================================================== */
@media (max-width: 767px) {
  html, body { overflow-x: hidden; max-width: 100%; }
}

/* =====================================================================
   CS-PATCH: mobile-fixed-width-wrapper-cap  2026-08-09  (found on Energy)
   Donor header/site wrappers carrying a hard pixel width with no media
   query. Clipping them is not enough - anything anchored to the end of the
   row (the injected hamburger) ends up outside the screen - so cap the
   wrapper itself. width:auto rather than 100% so any padding still fits.
   ===================================================================== */
@media (max-width: 767px) {
  .w-site-width, #top, #top-handler, #menu-handler, #header, .inner-handler,
  #site-name-handler, #slideshow-handler {
    width: auto !important;
    max-width: 100% !important;
    margin-left: auto !important;
    margin-right: auto !important;
  }
}

/* =====================================================================
   CS-PATCH: desktop-search-vcentre  2026-08-10  (found on Encore)
   The donor floats its search module into a header row and never aligns
   it, so it hugs the top of the bar - measured 30px tall in a 39px row,
   offset 0, 9px of slack underneath. The float means there is no flex
   context to centre within, so shift it by half the row and pull back by
   half its own height. Height-agnostic, and it does not affect the row's
   own height or the other floated items beside it.
   ===================================================================== */
@media (min-width: 768px) {
  /* Align the ROW that contains the search, rather than nudging the search
     itself: a percentage `top` resolves to 0 against an auto-height parent, so
     position:relative + top:50% + translateY(-50%) only applied the translate and
     pulled the box 15px ABOVE the bar. :has() scopes this to the one row that
     actually holds the search, so no other floated header row is touched. */
  #top .row:has(> #search-position),
  #top-handler .row:has(> #search-position),
  .row:has(> #search-position) {
    display: flex !important;
    align-items: center !important;
  }
  .row:has(> #search-position) > #search-position { float: none !important; }
}

/* =====================================================================
   CS-PATCH: cs-mobile-header-ux-generic   2026-08-08
   Skill 33 Mobile-header UX standard, generic build for the "no DOM family /
   selectnav-removed" ET cohort. Chrome is injected by js/cs-mobile.js, which
   detects its anchors at runtime; this block styles it and retires the donor's
   own phone chrome. Everything here is inert at >=768.

   Carries every correction from the Acceler8 graduation walkaround:
     - burger sized via flex-basis, not width (width alone loses to flex-shrink)
     - the secondary toggle is a small HAMBURGER, not three dots
     - open and closed states share one geometry so the X cannot move
     - the header rises above the scrim (a fixed header is a stacking context)
     - accordion chips are static flex items; the anchor gets a ZERO flex-basis
     - the logo is forced back into flow (donors position it absolutely)
     - social icons lay out as one centred wrapping row
   ========================================================================== */
#cs-menu-toggle, #cs-utility-row, #cs-drawer, #cs-search-panel,
#cs-social-panel, #cs-secondary-panel, #cs-scrim { display: none; }

@media (max-width: 767px) {

  /* ---- retire the donor's own phone chrome ---- */
  #social, #top-quick-nav, #topmenu, #search-position,
  .social-links-handler, div.menupanel, a#menupanel, #quick-menu { display: none !important; }
  /* 2026-08-18 (Energize): `nav#menu, #menu` used to sit in the blanket above on
     the assumption that #menu is only ever the desktop nav. On Energize-shaped
     donors the chain is `#top-handler > nav#menu > #menu-handler > .row >
     #site-name-handler` - the LOGO IS INSIDE IT - so the phone header rendered a
     burger and no brand at all. MEASURED @375 before: #site-name-handler 0x0,
     display:none, logo not painted. The donor's own CSS does not hide it; this
     file did. Hide the nav only where it does NOT contain the brand, then hide
     the nav LISTS unconditionally so Energize still loses its desktop menu.
     AFTER @375: brand 25,4 325x76 visible, burger 278,20 44x44, centre-Y delta 0,
     desktop nav still hidden, scrollWidth 375 = innerWidth, 0 console errors.
     No-op on Progressive / Oxygen / Crystal / Vision / Acceler8 / Impulsa /
     MubleStore, whose #menu holds no #site-name-handler and so still matches. */
  nav#menu:not(:has(#site-name-handler)),
  #menu:not(:has(#site-name-handler)) { display: none !important; }
  #menu ul.menu-nav, #menu-handler ul.menu-nav, #nav.menu-nav { display: none !important; }

  /* The donor's menu MODULES also render in flow on inner pages - a 6-item,
     210px-tall ul.menu sat above the article on Encore's single-article page, and
     the footer menu wrapped "Contact Us" onto two lines. Navigation on mobile is
     the two hamburgers; these are the source the drawer and secondary panel are
     built from, so once cloned they have no reason to paint. */
  .moduletable ul.menu, .moduletable_menu ul.menu, .module-content ul.menu,
  #footer-handler ul.menu, #footer ul.menu, footer ul.menu,
  #sidebar ul.menu, .sidebar ul.menu { display: none !important; }
  /* ...but never the copies living inside our own chrome */
  #cs-drawer ul, #cs-drawer ul.menu,
  #cs-secondary-panel ul, #cs-secondary-panel ul.menu { display: block !important; }

  /* Footer copy centres on a phone. Encore shipped one paragraph left-aligned and
     one right-aligned, which looked like a mistake at 375px. */
  #footer-handler, #footer, footer,
  #footer-handler p, #footer p, footer p,
  #footer-handler .custom, #footer .custom, footer .custom { text-align: center !important; }

  /* Site name and slogan stack. The slogan was overlapping the hamburger - it
     measured 26px wide at x=237 with the burger at x=271 - instead of sitting on
     its own line under the logo. */
  #site-name-handler, #top-handler #site-name-handler {
    flex-direction: column !important; align-items: flex-start !important;
    justify-content: center !important;
  }
  /* The slogan is a bare <h2> (no id, no class) wrapping an IMAGE that measured
     495px wide inside a 375px screen, starting at x=237 - which is why it faded
     out under the hamburger at x=271. Selectors keyed to #h2 never touched it.
     Give the h2 its own full-width line under the logo and cap the image. */
  #top-handler h2, header h2, #sn-position h2, #site-name-handler h2 {
    display: block !important; float: none !important; clear: both !important;
    width: 100% !important; max-width: 100% !important;
    margin: 4px 0 0 !important; padding: 0 !important;
    position: static !important; text-align: left !important;
  }
  #top-handler h2 img, header h2 img, #sn-position h2 img, #site-name-handler h2 img {
    max-width: 100% !important; width: auto !important; height: auto !important;
    display: block !important;
  }
  /* let the logo row wrap so the slogan drops to its own line rather than
     competing with the burger for the same one */
  #site-name-handler, #sn-position { flex-wrap: wrap !important; }

  #site-name-handler #h2, #sn-position #h2, #site-name-handler h2, #sn-position h2 {
    display: block !important; visibility: visible !important; opacity: 1 !important;
    position: static !important; float: none !important; clear: both !important;
    width: 100% !important; max-width: 100% !important;
    margin: 2px 0 0 !important; padding: 0 !important;
    font-size: 13px !important; line-height: 1.25 !important; text-align: left !important;
    white-space: normal !important; overflow: visible !important;
  }

  /* ---- logo row ---- */
  #top-handler { height: auto !important; min-height: 0 !important; }
  /* the header is sometimes a .container; drop its gutters at phone width so the
     injected row is naturally full-bleed (100vw overflows by the scrollbar width) */
  header#top-handler.container, #top-handler.container, #top-handler.container-fluid {
    width: 100% !important; max-width: 100% !important; padding-left: 0 !important; padding-right: 0 !important;
    margin-left: 0 !important; margin-right: 0 !important;
  }
  #top { min-height: 0; height: auto !important; }
  #top, #top > .row, #top > .row-fluid, #menu-handler {
    position: relative !important; min-height: 56px; padding: 4px 0 !important;
  }
  /* the donor's desktop nav cell is replaced by the drawer */
  #top-nav-handler, #top > .row > .span7, #top > .span7 { display: none !important; }
  /* donors hide the logo below ~500px and position #h1 absolutely, which makes
     its parent measure zero — force it back into flow so flex can centre it */
  #top-handler #site-name-handler, #site-name-handler {
    display: flex !important; visibility: visible !important;
    align-items: center !important; float: none !important;
    width: auto !important; height: auto !important;
    min-height: 56px; padding-left: 12px; padding-right: 72px;
  }
  #top-handler #sn-position, #sn-position {
    display: flex !important; align-items: center !important;
    width: auto !important; height: auto !important; min-height: 0 !important;
    flex: 0 0 auto !important; overflow: visible !important;
  }
  #site-name-handler > *, #sn-position > * { align-self: center !important; }
  #top-handler #sn-position > #h1,
  #top-handler #h1,
  #sn-position > #h1 { position: static !important; top: auto !important; left: auto !important; }
  #h1 { float: none !important; margin: 0 !important; width: auto !important; }
  #top-handler #h1 a, #h1 a { display: inline-block !important; line-height: 0; }
  #top-handler #h1, #h1 { width: auto !important; min-width: 0 !important; overflow: visible !important; }
  #top-handler #h1 img, #top-handler #site-name-handler img, #h1 img {
    /* CS-PATCH 2026-09-15 (WCW/AllBusiness): max-width:100% is relative to #h1,
       which is width:auto -- so it never actually constrains anything, and a
       logo wider than the space left by the burger + header CTA overflows and
       clips. Cap on the WIDTH axis against the viewport instead. Deliberately
       NOT a max-height: Tim's 2026-08-10 note below records that forcing a
       height on this image is what scaled the slogan graphic to 495px wide.
       Width-capping preserves the natural aspect ratio, so that regression
       cannot come back. Templates needing a different cap can set
       --cs-logo-mobile-maxw. */
    max-width: min(100%, var(--cs-logo-mobile-maxw, 58vw)) !important;
    max-height: none !important;
    width: auto !important;
    /* height: 34px !important;  <- Tim, 2026-08-10: forcing a height on this image
       is what scaled the slogan graphic up to 495px wide. Let it size naturally and
       the slogan renders as smaller text under the logo. */
    display: block !important;
  }
  /* #h2 is no longer hidden - it is stacked under the logo instead (see above) */

  /* 0 unless cs-mobile.js measured an OUT-OF-FLOW header. An in-flow header
     already occupies its height, so reserving more is dead space. */
  body { padding-top: var(--cs-header-h, 0px) !important; }

  /* Nothing inside the header may exceed the screen. Encore's #sn-position ran
     679px wide and its slogan h2/img 495px on a 375px phone, which is what put
     the site subtext off to the right. */
  #top-handler, #top-handler *, header *,
  #site-name-handler, #site-name-handler *, #sn-position, #sn-position * {
    max-width: 100% !important;
  }
  #top-handler img, header img { height: auto; }
  #site-name-handler h2, #sn-position h2, #h2 {
    max-width: 100% !important; overflow: hidden !important;
    text-overflow: ellipsis; white-space: nowrap;
  }

  /* ---- 1. primary hamburger: 56x56 box, 34x26 glyph, top-right, logo row ---- */
  /* 2026-08-18 (Energize/Progressive, Tim - REPEAT of the Intense round-4 report
     "the X is offset well to the left"). This group used to list
     `body.cs-panel-open #cs-menu-toggle` alongside `#cs-menu-toggle`, meaning to
     make open and closed share one geometry. It did the exact opposite: the
     compound selector is (0,2,0) while every later override is (0,1,0), so the
     OPEN state silently reverted every correction the closed state received.
     MEASURED @375 before - Energize closed 303,14 44x44 -> open 291,8 56x56 (the
     56px here beat the 44px of mobile-ui-round5); Progressive additionally
     reverted `top` from --cs-burger-top 33px to this rule's 50% = 61.125px, so
     its X fell 28px as well. `#cs-menu-toggle` alone already matches in BOTH
     states - the body class never stops it matching - so the compound selector
     bought nothing but the bug. AFTER: centre drift 0.0px open AND on reclose,
     on Energize / Progressive / Crystal / Oxygen / MubleStore / Impulsa, X still
     hit-testable above the scrim. */
  #cs-menu-toggle {
    display: flex; position: absolute !important; top: 50% !important;
    right: 8px !important; left: auto !important; transform: translateY(-50%) !important;
    width: 56px !important; height: 56px !important;
    align-items: center; justify-content: center;
    background: transparent !important; box-shadow: none !important;
    /* a hairline square so the control reads as a button rather than three loose
       bars on white; colour follows the same ink the bars use */
    border: 1px solid color-mix(in srgb, var(--cs-burger-ink, #fff) 45%, transparent) !important;
    border-radius: 6px !important;
    padding: 0; cursor: pointer; z-index: 1055 !important;
  }
  #cs-menu-toggle .cs-burger-bars {
    display: block !important; position: relative !important;
    flex: 0 0 34px !important; min-width: 34px !important; max-width: 34px !important;
    height: 26px !important;
  }
  #cs-menu-toggle .cs-burger-bars span {
    position: absolute !important; left: 0 !important; right: auto !important;
    width: 34px !important; height: 4px !important; border-radius: 2px;
    background: var(--cs-burger-ink, #fff); transition: transform .2s, opacity .2s;
  }
  #cs-menu-toggle .cs-burger-bars span:nth-child(1) { top: 0 !important; }
  #cs-menu-toggle .cs-burger-bars span:nth-child(2) { top: 11px !important; }
  #cs-menu-toggle .cs-burger-bars span:nth-child(3) { top: 22px !important; }
  body.cs-menu-open #cs-menu-toggle .cs-burger-bars span:nth-child(1) { transform: translateY(11px) rotate(45deg); }
  body.cs-menu-open #cs-menu-toggle .cs-burger-bars span:nth-child(2) { opacity: 0; }
  body.cs-menu-open #cs-menu-toggle .cs-burger-bars span:nth-child(3) { transform: translateY(-11px) rotate(-45deg); }

  /* The SECONDARY hamburger animates to an X too, on the same principle as the
     primary one: whatever opened a panel is what closes it, so it has to show the
     close state. Bars sit at 0 / 6 / 12 with a 3px height, so the throw is 6px
     rather than the primary's 11px. (Tim, 2026-08-10 - codified for all templates.) */
  body.cs-secondary-open #cs-secondary-toggle .cs-burger-bars span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
  body.cs-secondary-open #cs-secondary-toggle .cs-burger-bars span:nth-child(2) { opacity: 0; }
  body.cs-secondary-open #cs-secondary-toggle .cs-burger-bars span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }
  #cs-secondary-toggle .cs-burger-bars span { transition: transform .2s, opacity .2s; }

  /* ---- 2. one centred utility row ---- */
  #cs-utility-row {
    position: relative !important; display: flex; flex-wrap: nowrap;
    width: 100% !important; max-width: 100% !important;
    box-sizing: border-box;
    align-items: center; justify-content: center; gap: 14px;
    padding: 11px 8px; background: #f4f4f4; border-top: 1px solid rgba(0,0,0,.08);
  }
  /* Applied by cs-mobile.js when the row sits inside a wrapper narrower than the
     screen and the header has no full-width ancestor to move it to. Breaks out
     of the wrapper in place rather than re-parenting it somewhere unpredictable.
     100vw would include the scrollbar gutter, hence 100% of the viewport via
     the documentElement-relative calc. */
  #cs-utility-row.cs-full-bleed {
    width: 100vw !important; max-width: 100vw !important;
    margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw);
  }
  #cs-utility-row > button, #cs-utility-row > a {
    display: flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; flex: 0 0 44px;
    background: #fff; border: 1px solid rgba(0,0,0,.18); border-radius: 4px;
    color: #333; text-decoration: none; cursor: pointer; padding: 0;
    background-image: none !important; text-shadow: none !important;
  }
  #cs-utility-row i { font-size: 20px; line-height: 1; }
  #cs-utility-row > button[aria-expanded="true"] { background: #2b9fb3; border-color: #2b9fb3; color: #fff; }
  /* .75 2026-08-19: was a hardcoded `#fff`. Rev .65 gave the CLOSED-state bars
     `var(--cs-accent-ink)` (derived from the accent's own relative luminance) but
     this OPEN-state rule was missed, and at (0,2,2)!important it outranks the .65
     rule at (0,1,1), so the close-X flipped back to white the moment the panel
     opened. On a light accent that is white-on-light: MEASURED on Impulsa @375,
     drawer open, bars rgb(255,255,255) on chip #f7930f = 2.30:1, under the 3:1
     WCAG 2.1 SC 1.4.11 floor, on the only control that closes the mobile menu.
     Its own closed state measured 9.14:1. Same variable, so open now agrees with
     closed on every template instead of disagreeing on the light-accent ones. */
  #cs-utility-row > button[aria-expanded="true"] .cs-burger-bars span { background: var(--cs-accent-ink, #fff) !important; }

  /* secondary toggle: a SMALL HAMBURGER (it read as three dots at 4px wide) */
  #cs-secondary-toggle .cs-burger-bars {
    display: block !important; position: relative !important;
    flex: 0 0 22px !important; min-width: 22px !important; max-width: 22px !important;
    height: 16px !important;
  }
  #cs-secondary-toggle .cs-burger-bars span {
    position: absolute !important; left: 0 !important; right: auto !important;
    width: 22px !important; height: 3px !important; border-radius: 2px;
    background: var(--cs-burger-ink-2, #333);
  }
  #cs-secondary-toggle .cs-burger-bars span:nth-child(1) { top: 0 !important; }
  #cs-secondary-toggle .cs-burger-bars span:nth-child(2) { top: 6px !important; }
  #cs-secondary-toggle .cs-burger-bars span:nth-child(3) { top: 12px !important; }

  /* ---- scrim, and the header above it ---- */
  body.cs-panel-open #cs-scrim {
    display: block; position: fixed; top: 0; bottom: 0; left: 0; right: auto;
    width: 100vw; max-width: 100vw; background: rgba(0,0,0,.45); z-index: 1044;
  }
  /* Stacking order, low to high:
       scrim 1044 < drawer 1050 < panels 1052 < header 1054 < controls 1055.
     Raising the header is only needed for the slide-DOWN panels (search, social,
     section menu), which sit under it. The drawer covers the whole screen, and
     Gravity's #top-handler is position:absolute and tall enough to overlap it, so
     a raised header there made the first menu link unreachable. Excluding the
     drawer state is safe: the burger and utility row carry their own z-index and
     the header does not become a stacking context while its z-index is auto. */
  body.cs-panel-open:not(.cs-menu-open) #top-handler { z-index: 1054 !important; }
  /* 2026-08-18 (.61, Impulsa - Tim, with a screenshot @375): with the DRAWER
     open the burger animates to an X, and that X painted UNDERNEATH the scrim -
     greyed and half-buried, so the one control you must press to close the menu
     read as disabled. MEASURED before, drawer open @375:
       #cs-menu-toggle  position:absolute, z-index 1055, box 271,9 56x56
       #cs-scrim        z-index 1044   #cs-drawer  z-index 1050
       document.elementFromPoint(299,37)  ->  DIV#cs-scrim      <- the X's own centre
     The burger's z-index was never the problem. Impulsa's donor CSS gives
     `#top-handler { position:relative; z-index:999 }`, and a positioned element
     with a z-index is a STACKING CONTEXT: every z-index inside it - the burger's
     1055, the utility row's 1055, this donor's `#top { z-index:1050 }` - is
     resolved against 999, so the whole header sits below the 1044 scrim. An
     absolutely positioned child cannot climb out of an ancestor's context, which
     is why raising the button alone does nothing.

     So the fix has to move the HEADER, and the rule above deliberately refuses to
     while the drawer is open: on Gravity the header is out of flow and tall, and
     once it outranked the drawer its box swallowed the first menu link. Both
     needs are met by choosing the value with care rather than by dropping the
     exclusion - 1046 is ABOVE the scrim (1044) and BELOW the drawer (1050):
       - the header, the logo and the X stop being dimmed and hit-test to
         themselves, which is all Tim asked for;
       - the drawer still outranks the header in BOTH paint and hit-testing, so
         Gravity's first menu link stays exactly as reachable as it is today.
     Nothing here can regress a donor whose header overlaps its drawer, because
     against the drawer this rule changes nothing at all.
     MEASURED after: elementFromPoint(299,37) -> SPAN inside BUTTON#cs-menu-toggle;
     clicking it drops cs-menu-open and returns the drawer to translateX(-100%).
     The CLOSED burger is untouched (44x44 at 283,15, centre-Y 37 = logo 37). */
  body.cs-menu-open #top-handler,
  body.cs-menu-open .cs-mob-header { z-index: 1046 !important; }
  body.cs-panel-open #cs-utility-row { position: relative; z-index: 1055 !important; }

  /* ---- 3. drawer ---- */
  #cs-drawer {
    /* display:block is REQUIRED here — the base rule above hides every #cs-*
       element, and the drawer is only ever revealed by a transform, so without
       this it stays display:none and every link measures zero. */
    display: block;
    position: fixed; top: var(--cs-drawer-top, 104px); left: 0; right: auto;
    width: 100vw; max-width: 100vw;
    /* NOT bottom:0 — if any ancestor carries a transform/filter, a fixed box
       resolves against THAT ancestor and top+bottom collapses to zero height
       (measured on Brilliance: top 290, bottom 0, height 0). An explicit
       viewport-relative height is immune to the containing-block question. */
    height: calc(100vh - var(--cs-drawer-top, 104px));
    max-height: calc(100vh - var(--cs-drawer-top, 104px));
    background: #fff; z-index: 1050; overflow-y: auto;
    transform: translateX(-100%); transition: transform .25s ease;
  }
  body.cs-menu-open #cs-drawer { transform: none; }
  #cs-drawer .cs-drawer-menu,
  #cs-drawer .cs-drawer-menu ul,
  #cs-drawer .cs-drawer-menu li,
  #cs-drawer .cs-drawer-menu a,
  #cs-drawer .cs-drawer-menu span {
    float: none !important; width: auto !important; height: auto !important;
    background: none !important; text-indent: 0 !important; line-height: normal !important;
  }
  #cs-drawer .cs-drawer-menu { list-style: none; padding: 0 !important; margin: 0 !important; }
  #cs-drawer .cs-drawer-menu li {
    position: relative !important; list-style: none;
    border-bottom: 1px solid rgba(0,0,0,.07); padding: 0 !important;
  }
  #cs-drawer .cs-drawer-menu li.clear { display: none; }
  #cs-drawer .cs-drawer-menu a, #cs-drawer .cs-drawer-menu span {
    display: block; padding: 16px 14px !important; font-size: 17px; color: #2b6ea8; text-decoration: none;
  }
  /* superfish parks dropdowns off-screen; release that geometry inside the drawer */
  #cs-drawer .cs-drawer-menu ul,
  #cs-secondary-panel ul ul {
    position: static !important; top: auto !important; left: auto !important; right: auto !important;
    min-width: 0 !important; max-width: none !important; margin: 0 !important;
    box-shadow: none !important; background: transparent !important;
    display: none !important;
  }
  #cs-drawer .cs-drawer-menu li.cs-open > ul,
  #cs-secondary-panel li.cs-open > ul { display: block !important; }
  #cs-drawer .cs-drawer-menu ul a { padding-left: 34px !important; font-size: 15px; }
  #cs-drawer .cs-drawer-menu ul ul a { padding-left: 50px !important; }

  /* ---- accordion chip: static flex item; anchor gets a ZERO flex-basis ---- */
  .cs-chip {
    display: flex; align-items: center; justify-content: center;
    width: 34px; height: 32px; margin-right: 10px;
    background: #fff; border: 1px solid rgba(0,0,0,.25); border-radius: 3px;
    font-size: 20px; line-height: 1; color: #666; cursor: pointer; padding: 0;
  }
  #cs-drawer li:has(> .cs-chip),
  #cs-secondary-panel li:has(> .cs-chip) {
    display: flex !important; flex-wrap: wrap !important; align-items: center !important;
  }
  #cs-drawer li:has(> .cs-chip) > a,
  #cs-secondary-panel li:has(> .cs-chip) > a {
    flex: 1 1 0% !important; width: auto !important; min-width: 0 !important;
  }
  #cs-drawer li:has(> .cs-chip) > .cs-chip,
  #cs-secondary-panel li:has(> .cs-chip) > .cs-chip {
    position: static !important; top: auto !important; transform: none !important;
    flex: 0 0 auto !important;
  }
  #cs-drawer li:has(> .cs-chip) > ul,
  #cs-secondary-panel li:has(> .cs-chip) > ul { flex: 0 0 100% !important; }

  /* ---- 4/5/6. full-width panels under the header ---- */
  /* box-sizing is the whole story here: these are 100vw wide with 14px padding,
     so without border-box they measured 403px on a 375px screen. That pushed the
     search submit button to x=345-389 (off the right edge) and left the secondary
     menu's "+" chips at a -4px gap, touching the screen edge. */
  #cs-search-panel, #cs-social-panel, #cs-secondary-panel,
  #cs-drawer, #cs-utility-row {
    box-sizing: border-box !important;
  }
  #cs-search-panel *, #cs-social-panel *, #cs-secondary-panel *, #cs-drawer * {
    box-sizing: border-box;
  }
  #cs-search-panel, #cs-social-panel, #cs-secondary-panel {
  /* A position:fixed box with left:0;right:0 resolves against the INITIAL
     CONTAINING BLOCK, which is the page's overflow width - not the visible
     screen. Eclipse's owl carousel track pushes that to 397px, so the drawer
     hung 22px off the right edge even with the root overflow clipped. vw is the
     visual viewport, so pin the width to it and let right: auto follow. */
    display: none; position: fixed; top: var(--cs-drawer-top, 104px); left: 0; right: auto;
    width: 100vw; max-width: 100vw;
    background: #fff; z-index: 1052; padding: 14px;
    box-shadow: 0 6px 14px rgba(0,0,0,.15); overflow-y: auto;
    max-height: calc(100vh - var(--cs-drawer-top, 104px));
  }
  body.cs-search-open    #cs-search-panel    { display: block; }
  body.cs-social-open    #cs-social-panel    { display: block; }
  body.cs-secondary-open #cs-secondary-panel { display: block; }

  #cs-search-panel form { display: flex; flex-direction: row; align-items: center; gap: 8px; margin: 0; }
  /* The `> input` arms only work when the input is a DIRECT child of the form.
     VirtueMart wraps it - Aeon's is `form > div.search > input` - so the form's
     only flex child was that block-level div, and the input and the submit
     stacked inside it on two lines instead of sitting side by side (measured:
     input at y=287, button at y=333). Make any wrapper a flex row too. */
  #cs-search-panel form > .awesomplete,
  #cs-search-panel form > input[type="text"],
  #cs-search-panel form > input[type="search"] { flex: 1 1 auto; min-width: 0; }
  #cs-search-panel form > div {
    display: flex; flex-direction: row; align-items: center; gap: 8px;
    flex: 1 1 auto; min-width: 0;
  }
  #cs-search-panel form > div > input[type="text"],
  #cs-search-panel form > div > input[type="search"],
  #cs-search-panel form > div > .awesomplete { flex: 1 1 auto; min-width: 0; }
  #cs-search-panel input[type="text"], #cs-search-panel input[type="search"] {
    width: 100%; height: 44px; font-size: 16px; padding: 0 10px;
    border: 1px solid rgba(0,0,0,.3); border-radius: 4px;
  }
  /* 2026-08-11 (Aeon): this used to force `background:#fff !important;
     background-image:none !important`, which threw away the donor's own
     search-icon.png AND left the donor's white `color` on a white box - a
     blank white square with no visible icon at all. Keep the donor's
     background (live renders a blue button with the magnifier) and only
     impose geometry + a 44px touch target. `font-size: 0` collapses the
     value="Search" text so the icon reads cleanly; the border keeps the
     control visible on any donor that ships no icon. */
  .cs-search-submit, #cs-search-panel button[type="submit"], #cs-search-panel input[type="submit"] {
    flex: 0 0 auto; width: 44px; height: 44px;
    background-repeat: no-repeat !important;
    background-position: center center !important;
    border: 1px solid rgba(0,0,0,.25); border-radius: 4px;
    font-size: 0; text-shadow: none !important; cursor: pointer;
  }
  #cs-search-panel .finder li, #cs-search-panel .js-finder-searchword-label { display: none; }

  /* social: one centred wrapping row (it cloned as a vertical stack) */
  #cs-social-panel .cs-social-list, #cs-social-panel ul {
    display: flex !important; flex-direction: row !important; flex-wrap: wrap !important;
    justify-content: center !important; align-items: center !important;
    gap: 10px; list-style: none !important; margin: 0 !important; padding: 0 !important;
  }
  #cs-social-panel li { float: none !important; width: auto !important; margin: 0 !important; }
  #cs-social-panel a.closeMenu { display: none !important; }
  /* Flat, like the utility-row buttons. The previous dark #3b3b3b disc sat behind
     the glyph and the donor's own :hover rule swapped its colour underneath, which
     read as "something in the background that moves". White with a hairline border
     and a dark glyph is legible on every donor palette and does not animate. */
  #cs-social-panel li a {
    display: flex !important; align-items: center !important; justify-content: center !important;
    width: 44px !important; height: 44px !important; border-radius: 4px !important;
    background: #fff !important; background-image: none !important;
    border: 1px solid rgba(0,0,0,.18) !important;
    color: #333 !important; text-indent: 0 !important; text-shadow: none !important;
    box-shadow: none !important; transition: none !important;
  }
  #cs-social-panel li a:hover, #cs-social-panel li a:focus {
    background: #f4f4f4 !important; border-color: rgba(0,0,0,.32) !important;
    color: #111 !important;
  }
  #cs-social-panel li a i { color: inherit !important; font-size: 19px !important;
    background: none !important; line-height: 1 !important; }

  #cs-secondary-panel .moduletable_menu > h3 { color: #333 !important; }
  #cs-secondary-panel ul { list-style: none; padding: 0; margin: 0; }
  #cs-secondary-panel li { border-bottom: 1px solid rgba(0,0,0,.07); }
  #cs-secondary-panel a { display: block; padding: 14px 4px; color: #2b6ea8; text-decoration: none; }
}

@media (min-width: 768px) {
  #cs-menu-toggle, #cs-utility-row, #cs-drawer, #cs-search-panel,
  #cs-social-panel, #cs-secondary-panel, #cs-scrim { display: none !important; }
}
/* /CS-PATCH: cs-mobile-header-ux-generic */

  /* ---- social panel icons ----
     The cloned anchors inherit the donor's #social-links styling: a dark chip that
     also swaps colour on hover, which read as "something in the background that
     moves". Render them as flat buttons matching the utility row instead. */
  #cs-social-panel { display: none; }
  body.cs-social-open #cs-social-panel { display: block; }
  #cs-social-panel ul, #cs-social-panel ol {
    display: flex !important; flex-wrap: wrap !important; gap: 10px !important;
    justify-content: center !important; list-style: none !important;
    margin: 0 !important; padding: 0 !important;
  }
  #cs-social-panel li { float: none !important; margin: 0 !important; width: auto !important; }
  #cs-social-panel a {
    display: flex !important; align-items: center !important; justify-content: center !important;
    width: 44px !important; height: 44px !important;
    background: #fff !important; background-image: none !important;
    border: 1px solid rgba(0,0,0,.18) !important; border-radius: 4px !important;
    color: #333 !important; text-indent: 0 !important; text-shadow: none !important;
    box-shadow: none !important; transition: none !important; overflow: visible !important;
  }
  #cs-social-panel a:hover, #cs-social-panel a:focus {
    background: #f4f4f4 !important; border-color: rgba(0,0,0,.32) !important;
  }
  #cs-social-panel a i, #cs-social-panel a .icon {
    color: inherit !important; font-size: 19px !important; line-height: 1 !important;
    background: none !important; width: auto !important; height: auto !important;
    margin: 0 !important; text-indent: 0 !important;
  }
  /* the label is for screen readers, not for painting next to a 19px glyph */
  #cs-social-panel a .sr-only {
    position: absolute !important; width: 1px !important; height: 1px !important;
    padding: 0 !important; margin: -1px !important; overflow: hidden !important;
    clip: rect(0,0,0,0) !important; white-space: nowrap !important; border: 0 !important;
  }

  /* keep the accordion chips off the screen edge even at the widest nesting */
  #cs-secondary-panel li:has(> .cs-chip) > .cs-chip,
  #cs-drawer li:has(> .cs-chip) > .cs-chip { margin-right: 4px !important; }

@media (max-width: 767px) {
  /* The injected search submit inherited the donor's white button text, so a white
     magnifying glass sat on a white button and the control read as empty. */
  #cs-search-panel .cs-search-submit,
  #cs-search-panel button[type="submit"] {
    background: #2b9fb3 !important; border: 1px solid #2b9fb3 !important;
    color: #fff !important; border-radius: 4px !important;
    width: 44px !important; height: 44px !important; flex: 0 0 44px !important;
    display: flex !important; align-items: center !important; justify-content: center !important;
  }
  #cs-search-panel .cs-search-submit i,
  #cs-search-panel button[type="submit"] i {
    color: #fff !important; font-size: 17px !important;
  }
  #cs-search-panel .cs-search-submit:hover,
  #cs-search-panel button[type="submit"]:hover { background: #23808f !important; }
}

/* =====================================================================
   CS-PATCH: desktop-search-row-margins  2026-08-10  (found on Progressive)
   desktop-search-vcentre makes this row a centred flex container, but a
   child carrying its own vertical margin still sits off-centre - and the
   spare margin inflates the band. Zero the vertical margins of the row's
   children (and of the usual breadcrumb wrappers inside them) so the
   centring is true and the band is only as tall as its content.
   >=768 only; the mobile stack is untouched.

   !! 2026-08-11 (found on ET Magnetic) - `:has(#search-position)` is an
   UNBOUNDED-DESCENDANT match, so it also selected every ANCESTOR row that
   merely contained the search. Magnetic puts its search six levels down, in
   the left sidebar, so this rule reached the page's main content row and its
   `margin-bottom: 0 !important` cancelled the donor's faux-column hack
   (.left-handler { padding-bottom: 99999px; margin-bottom: -99999px }).
   In a BS5 flex row that turned 99999px of padding into real height:
   measured .container.main-container-fluid 101244px vs the live donor's 2386px.
   Depth-bounded to two levels - Progressive (1), Encore (1) and Vision (2)
   still match; Magnetic (6) no longer does. */
@media (min-width: 768px) {
  .row:has(> #search-position, > * > #search-position) > * { margin-top: 0 !important; margin-bottom: 0 !important; }
  .row:has(> #search-position, > * > #search-position) #brcr,
  .row:has(> #search-position, > * > #search-position) .breadcrumbs,
  .row:has(> #search-position, > * > #search-position) .mod-breadcrumbs {
    margin-top: 0 !important; margin-bottom: 0 !important;
  }
}

/* CS-PATCH: cs-dropdown-hover-bridge */
/* Donor menus position each submenu with a HARDCODED offset rather than against
   its parent, leaving dead space that closes the menu before the pointer arrives:
   measured 23px vertical and 30px horizontal on Progressive, 28px on Delta.

   Attach them to the parent instead of bridging the gap. An earlier version made
   the gap hoverable with a transparent pseudo-element; that kept the menu open but
   left it visually detached, and an invisible overlay across the menu bar is its
   own trap.

   Selectors are deliberately STRUCTURAL (`li > ul`), not keyed to a menu id or
   class. An earlier version listed #menu/.menu/.nav/#nav and silently missed
   Delta, whose menu wrapper carries neither - the rule simply never applied and
   the gap stayed. `top`/`left` are inert on statically positioned elements, so an
   ordinary nested list in article content is unaffected.

   !! 2026-08-11 (found on Concern) - the rules MUST be scoped to the OPEN state.
   Superfish-derived donors do not hide a submenu with `display:none`; they park it
   OFF-SCREEN with `.menu-nav ul { position:absolute; top:-999em; }` and bring it
   back with `.menu-nav li:hover ul { top:100%; left:0 }`. An UNSCOPED
   `li > ul { top:100% !important }` therefore does not reposition a hidden menu -
   it DEFEATS THE HIDING MECHANISM, and every submenu renders permanently open with
   no pointer anywhere near it. Measured on Concern's position-1 menu at 1400px:
   on a cold load both drop-downs painted at y=62, 169x190 and 169x100, before any
   hover. Scoping to `:hover`/`.sfHover` applies the correction at exactly the
   moment the submenu is on screen - the only moment its position is observable -
   and leaves the resting state to the donor, whichever idiom it uses
   (`display:none` donors are unaffected either way).

   Rule B is more specific so it wins for the THIRD level, which must not drop
   below its parent - it flies out sideways, flush against its column. Margins are
   left alone: donors use them for fine alignment. */
@media (min-width: 768px) {
  /* !important is required, not stylistic: a bare `li > ul` is one class-less
     selector and loses to the donor's own `#menu li ul` rule. Broadening the
     selector while dropping !important made Eclipse regress from pass to fail.
     `.sfHover` is carried alongside `:hover` because every donor's own reveal
     rule pairs the two, and some cohorts still set the class from script. */
  li:hover > ul,
  li.sfHover > ul { top: 100% !important; }
  li li:hover > ul,
  li li.sfHover > ul { top: 0 !important; left: 100% !important; margin-left: 0 !important; }
}

/* =====================================================================
   CS-PATCH: fluid-descendant-spans  2026-08-11  (found on UltimateShop, ET
   VirtueMart J5 run)

   TWIN to fluid-descendant-offsets, for WIDTHS instead of offsets, and the
   fourth member of the "the .row-fluid rule never got a .row twin after
   Phase 3a" family.

   Bootstrap 2 sized fluid columns with a DESCENDANT selector at (0,2,0):
       .row-fluid .span8 { width: 65.812% }      <- bootstrap-responsive.min.css
   The bridge only carries the STANDALONE form, and it ships wrapped in
   `:where(.span8)`, which is specificity (0,0,0). Any donor rule with a single
   class therefore beats it.

   MEASURED on UltimateShop @1400: #top-modules holds two module boxes whose
   `span8` / `span4` come from the module class suffix - DATA in #__modules, so
   Phase 3a never rewrites them - nested one level inside `.tf-module.col-md-12`,
   i.e. NOT direct children of the rewritten `.row`, so the existing
   `.row > .spanN` mirror (0,2,0) does not match them either.

       live  .span8 = 770px (65.812% of 1170)   .span4 = 370px
       stage .span8 = 570px                     .span4 = 570px

   template.css's `.moduletable_style8 { width: 100% }` (0,1,0) was beating
   `:where(.span8)` (0,0,0); both boxes then flex-split the row evenly and the
   promo band grew 200px taller than the donor's.

   Restoring BS2's own descendant selector at BS2's own specificity fixes the
   class of bug wherever a legacy spanN sits deeper than one level inside a
   rewritten row - which is every module-class-suffix grid in the fleet.

   Guarded to >=768px so the <=767 stacking blocks later in this file are
   untouched; BS2 did the same thing (these percentages come from its
   @media (min-width: 1200px) 1170-grid block).
   ===================================================================== */
@media (min-width: 768px) {
    .row-fluid .span1:where(:not(ul[class*="product"] > *)), .row .span1:where(:not(ul[class*="product"] > *)) { width: 5.982905983%; }
    .row-fluid .span2:where(:not(ul[class*="product"] > *)), .row .span2:where(:not(ul[class*="product"] > *)) { width: 14.52991453%; }
    .row-fluid .span3:where(:not(ul[class*="product"] > *)), .row .span3:where(:not(ul[class*="product"] > *)) { width: 23.07692308%; }
    .row-fluid .span4:where(:not(ul[class*="product"] > *)), .row .span4:where(:not(ul[class*="product"] > *)) { width: 31.62393162%; }
    .row-fluid .span5:where(:not(ul[class*="product"] > *)), .row .span5:where(:not(ul[class*="product"] > *)) { width: 40.17094017%; }
    .row-fluid .span6:where(:not(ul[class*="product"] > *)), .row .span6:where(:not(ul[class*="product"] > *)) { width: 48.71794872%; }
    .row-fluid .span7:where(:not(ul[class*="product"] > *)), .row .span7:where(:not(ul[class*="product"] > *)) { width: 57.26495726%; }
    .row-fluid .span8:where(:not(ul[class*="product"] > *)), .row .span8:where(:not(ul[class*="product"] > *)) { width: 65.81196581%; }
    .row-fluid .span9:where(:not(ul[class*="product"] > *)), .row .span9:where(:not(ul[class*="product"] > *)) { width: 74.35897436%; }
    .row-fluid .span10:where(:not(ul[class*="product"] > *)), .row .span10:where(:not(ul[class*="product"] > *)) { width: 82.90598291%; }
    .row-fluid .span11:where(:not(ul[class*="product"] > *)), .row .span11:where(:not(ul[class*="product"] > *)) { width: 91.45299145%; }
    .row-fluid .span12:where(:not(ul[class*="product"] > *)), .row .span12:where(:not(ul[class*="product"] > *)) { width: 100%; }
}

/* =====================================================================
   CS-PATCH: rewritten-row-colmd-percent-grid  2026-08-11  (found on the ET
   Envisage/EvoStore/FashionShop/HyperonShop/LightShop/MagnetaShop tranche)
   The MISSING TWIN of rewritten-row-percent-gutter.

   rewritten-row-gutter-strip zeroes BS5's own gutter on any rewritten row:
       .row:has(> [class*="span"]), .row:has(> [class*="col-md-"]) {
           --bs-gutter-x: 0; margin-left: 0; margin-right: 0; }
   rewritten-row-percent-gutter then hands the BS2 2.564% gutter back — but only
   to `.row > [class*="span"]`. Phase 3a/3a2 rewrites `spanN` -> `col-md-N`, so on
   a FULLY rewritten row every child is `col-md-*`, the BS5 gutter is stripped and
   the BS2 gutter is never restored. The columns then tile edge to edge at BS5's
   own N/12 widths: no gap at all, and each column wider than the donor's.

   MEASURED on Envisage @1400 (container 1170), stage vs the live donor:
       bottom 3-across   live .span4  370px at x=108/508/908   (400px pitch)
                         stage .col-md-4 390px at x=108/497/887 (389px pitch)
       header 2-across   live .span6  570px      stage .col-md-6 585px
   i.e. the 30px inter-module gaps the design depends on had vanished and every
   module had grown by its share of them.

   The fix is the same closed model the span twin uses — widths and gutter derived
   together, so a row tiles to exactly 100% at any container width:
       3 x 31.623931623931625% + 2 x 2.564102564102564% = 100%
       4 x 23.076923076923077% + 3 x 2.564102564102564% = 100%
       2 x 48.717948717948715% + 1 x 2.564102564102564% = 100%
   These are BS2 2.3.2's own .row-fluid figures, so a rewritten row lands pixel
   for pixel where the donor's did.

   Widths are NOT !important: the <=767px mobile-colmd-stack block sets
   `width:100% !important`, so phone stacking still wins.
   ===================================================================== */
.row > [class*="col-md-"] {
    box-sizing: border-box;
    min-height: 1px;
    margin-left: 2.564102564102564%;
    float: none;
}
.row > [class*="col-md-"]:not([class*="offset"]):first-child {
    margin-left: 0;
}
.row > .col-md-1  { width: 5.982905982905983%; }
.row > .col-md-2  { width: 14.52991452991453%; }
.row > .col-md-3  { width: 23.076923076923077%; }
.row > .col-md-4  { width: 31.623931623931625%; }
.row > .col-md-5  { width: 40.17094017094017%; }
.row > .col-md-6  { width: 48.717948717948715%; }
.row > .col-md-7  { width: 57.26495726495726%; }
.row > .col-md-8  { width: 65.81196581196582%; }
.row > .col-md-9  { width: 74.35897435897436%; }
.row > .col-md-10 { width: 82.90598290598291%; }
.row > .col-md-11 { width: 91.45299145299145%; }
.row > .col-md-12 { width: 100%; margin-left: 0; }


/* =====================================================================
   CS-PATCH: rewritten-row-content-box  2026-08-11  (found on Envisage)
   BS2 set `box-sizing: border-box` on grid COLUMNS only. BS5's Reboot sets it on
   `*`, so once Bootstrap 5's stylesheet is loaded every donor element that has a
   percentage width AND horizontal padding silently loses the padding from its
   content width. The donor's section rows are exactly that shape: template.css
   pads them and BS2's `.row-fluid { width: 100% }` sizes them.

   MEASURED on Envisage @1400: `#tab-modules-handler` carries
   `padding: 0 58.5px` from template.css.
       live   content-box -> content width 1170, four tab modules x=186..1199
       stage  border-box  -> content width 1053, four tab modules x=186..1082
   a 117px (2 x 58.5) short fill that reads as the whole promo strip inset from
   the design. The columns inside were correct; the row they sat in was not.

   Restoring content-box on the row itself reproduces the donor exactly. Rows
   without padding are unaffected — content-box and border-box agree when the
   padding is zero — so this is safe fleet-wide.
   ===================================================================== */
.row, .row-fluid {
    box-sizing: content-box;
}

/* =====================================================================
   CS-PATCH: row-flexitem-min-width  2026-08-11  (found on UniversalShop, ET
   VirtueMart J5 run)

   A `.row` / `.row-fluid` that is itself a FLEX ITEM of some donor wrapper
   cannot shrink below its own min-content width, because flex items default to
   `min-width: auto`. Under BS2 that never bit: the row's children were block
   and floats, so min-content stayed small. Once Bootstrap 5 core is loaded
   (Phase 3b.1) some of those children become flex - BS5's `.nav { display: flex }`
   is the common one - and the row's min-content jumps to the SUM of its
   children's intrinsic widths.

   MEASURED on UniversalShop @1400:
       #tabs-1.nav-tabs-handler.row      1816px wide   (live .row-fluid: 1168)
       document.scrollWidth              1935px        (live: 1385)
   i.e. 550px of horizontal page scroll, from a product-tab carousel whose
   overflow the donor clips but which now forced its ancestor wide instead.
   Adding min-width:0 alone: #tabs-1 -> 1168 and scrollWidth -> 1385, both
   exactly the donor's numbers.

   Safe by construction: min-width:0 changes nothing for a row in normal block
   flow; it only restores a flex item's ability to shrink, which is the BS2
   behaviour the bridge exists to reproduce.
   ===================================================================== */
.row, .row-fluid { min-width: 0; }


/* =====================================================================
   CS-PATCH: phase3a-colmd-grid  2026-08-11  (found on Evolution/FlatGear, ET)
   FOURTH member of the "the .row-fluid rule never got a .row twin after
   Phase 3a" family, and the one that matters most: Phase 3a rewrites the
   donor's literal class="spanN" to class="col-md-N", so from that point the
   bridge's ENTIRE BS2 grid model - the 1170-grid percentage widths and the
   2.564102564% gutter derived with them - stops matching the markup it was
   written for. Everything then falls through to whatever real BS5 does.

   Two measured failures on this batch @1400:
     * Evolution - .container's --bs-gutter-x is zeroed by
       container-bs5-padding-strip (correctly), so BS5's `.row > *` padding
       resolved to 0 and the 4-up module strip lost every gutter: four cards
       painted as one continuous bordered strip, where live shows four
       separated cards.
     * FlatGear - the full-bleed rows (#menu-handler, #slideshow-header) are
       NOT inside a .container, so nothing zeroed their gutter and BS5's
       `.row { margin-inline: -12px }` survived: documentElement.scrollWidth
       1412 vs clientWidth 1400, a 12px horizontal scrollbar the live donor
       does not have.

   Fix: give col-md-* the same model spanN already has, and zero BS5's own
   gutter on the rows the bridge is sizing (matched with :has(), so a genuine
   BS5 core-module row using col-sm-* or col-lg-* keeps its own gutters).
   The width table is character-for-character the spanN table above - the two
   must stay identical or rows built from a mix of both cannot tile.
   ===================================================================== */
/* 2026-08-11 addendum (FlatGear): scoping this to rows that HAVE grid children
   missed the donor rows whose children are plain wrappers - FlatGear's header
   .row wraps #site-name-handler and #menu-handler, neither of which carries a
   grid class, so the row kept BS5's -12px side margins and pushed scrollWidth
   to 1412 against a 1400 clientWidth. Every .row in these donors came from a
   BS2 .row-fluid (Phase 3a rewrites the class), and .row-fluid had no negative
   margins at all, so the default for this family must be gutter-less. Only a
   row that opts into BS5's own responsive columns keeps BS5's gutter. */
.row:not(:has(> [class*="col-sm-"], > [class*="col-lg-"], > [class*="col-xl-"])) {
    --bs-gutter-x: 0;
}

@media (min-width: 768px) {
    .row > [class*="col-md-"] {
        box-sizing: border-box;
        min-height: 1px;
        padding-left: 0;
        padding-right: 0;
        margin-left: 2.564102564%;
    }
    .row > [class*="col-md-"]:not([class*="offset"]):first-child {
        margin-left: 0;
    }
    .row > .col-md-1  { flex: 0 0 auto; width: 5.982905983%; }
    .row > .col-md-2  { flex: 0 0 auto; width: 14.52991453%; }
    .row > .col-md-3  { flex: 0 0 auto; width: 23.07692308%; }
    .row > .col-md-4  { flex: 0 0 auto; width: 31.62393162%; }
    .row > .col-md-5  { flex: 0 0 auto; width: 40.17094017%; }
    .row > .col-md-6  { flex: 0 0 auto; width: 48.71794872%; }
    .row > .col-md-7  { flex: 0 0 auto; width: 57.26495726%; }
    .row > .col-md-8  { flex: 0 0 auto; width: 65.81196581%; }
    .row > .col-md-9  { flex: 0 0 auto; width: 74.35897436%; }
    .row > .col-md-10 { flex: 0 0 auto; width: 82.90598291%; }
    .row > .col-md-11 { flex: 0 0 auto; width: 91.45299145%; }
    .row > .col-md-12 { flex: 0 0 auto; width: 100%; }
}

/* Mobile: mirror bs2-responsive-span-stack for the rewritten class names.
   BS5 already stacks col-md-* below 768, but the explicit widths above would
   otherwise have to be unset one by one; this is the same contract BS2's
   bootstrap-responsive.css applied to [class*="span"]. */
@media (max-width: 767px) {
    .row > [class*="col-md-"] {
        width: 100% !important;
        margin-left: 0 !important;
        padding-left: 0;
        padding-right: 0;
        float: none;
    }
}


/* =====================================================================
   CS-PATCH: bs2-fixed-inner-container  2026-08-11  (found on Evolution, ET)
   This donor family wraps each full-width band in a SECOND, inner container
   whose width is hard-coded to the BS2 940-grid and marked !important:
       Evolution .container1 { width: 920px !important; margin: auto !important }
       Extend    .container1 { width: 1000px !important }
       Fluency   .container1 { width:  940px !important }
   The outer .container is clamped by this bridge to the 1170 grid, so the
   inner one no longer fills it: MEASURED on Evolution @1400, #bot-modules and
   #footer painted 920px wide inside a 1170px container, i.e. the dark footer
   band sat inset with 125px of page background either side while every other
   band ran the full width. Live has no such inset - there the outer container
   is 940 and the inner one fills it exactly.

   Let the inner container follow its parent instead of the retired grid. Its
   own `margin: auto !important` still centres it, and the <=1199px media
   blocks above narrow the outer container as before.
   ===================================================================== */
.container1 {
    width: auto !important;
    max-width: 100% !important;
}

/* =====================================================================
   CS-PATCH: bs2-donor-chrome-content-box  2026-08-11  (found on ET HighPulse)

   TWIN to rewritten-row-content-box, for the donor's HEADER CHROME instead of
   its rows, and the same root cause: Bootstrap 2 never set `box-sizing` on `*`,
   Bootstrap 5's Reboot does, so the moment Phase 3b.1's BS5 stylesheet loads,
   every donor element that pairs an EXPLICIT height with vertical padding loses
   the padding out of its content box and collapses.

   MEASURED on HighPulse @1400. The donor's own inline <style> sets
   `#top-handler { height: 40px }` and layout.css adds `padding: 32px 0`:
       live   content-box -> header 40 + 32 + 32 = 104px tall
       stage  border-box  -> header max(40, 64)  =  64px tall
   The 40px loss is not the defect people see. `#top`'s floated children
   (#site-name-handler, #megamenu-handler) bottom out at y=72, so in the short
   header they escape 8px below it — and the next section, `#story-slideshow`,
   carries `overflow:hidden` from camera.css, which makes it a BFC that SHRINKS
   TO AVOID FLOATS. Measured: the whole slideshow band rendered 168px wide at
   x=315 instead of 1400px at x=0, and camera.js then sized every slide to that.
   A 40px box-sizing delta in the header destroyed the hero band.

   The same reset shrinks the camera pagination dots (18 -> 12px) and the k2
   rating stars, both of which pair an explicit height with padding.

   Scoped to the chrome that measurably needs it rather than a blanket
   `* { content-box }`: the blanket form was tested first and moved geometry on
   released templates (Oxygen bodyH +143px, Progressive footer +5px), because
   their post-BS5 layout is what Tim approved. This list was regression-tested
   by injection at 1400px against Vision, Crystal, Progressive, Oxygen, Quadra,
   Encore and Delta — no geometry change on any of them — and it restores
   HighPulse's header to 104px and its slideshow to the full 1400px.
   ===================================================================== */
#top-handler, #top, #logo, #searchpanel,
#site_wrapper,
.camera_pag li, .camera_loader {
    box-sizing: content-box;
}

/* #site_wrapper added 2026-08-12 (CorporateImpact). Same root cause one level
   up - the PAGE wrapper, not the header chrome:
       #site_wrapper { width: 920px; padding: 10px 20px 0 20px }
   content-box -> 960px outer, 920px content (what the donor draws)
   border-box  -> 920px outer, 880px content
   Measured @1400: the slideshow went 233,182 920x220 (live) to 253,182 880x220,
   every band inset 20px, and the narrower text column pushed the page from
   1918px to 1975px tall. `#site_wrapper` exists in exactly ONE stylesheet in
   the whole ET fleet, so this cannot move any released template. */

/* =====================================================================
   CS-PATCH: parked-submenu-scroll-clip  2026-08-11  (found on ET HighPulse)

   Superfish parked its drop-downs off-screen with `.menu-nav ul {
   position:absolute; top:-999em }` AND hid them from script on init — it adds
   `sf-js-enabled` to the root list and `display:none` to each submenu. Phase 7
   removes superfish (its call site throws under jQuery 3.7 and takes the rest
   of its script block with it), so the CSS parking survives and the scripted
   hiding does not: the submenu still sits 14,000px above the viewport, where
   nobody can see it, but it is now `display:block` and its full width counts
   toward the document scroll area.

   MEASURED on HighPulse @1400: the mega-menu submenu is 640px wide with its
   right edge at x=1586, so `document.documentElement.scrollWidth` reads 1586
   against a 1400 client width — a horizontal scrollbar the live donor does not
   have. (The page cannot actually pan, because the overflow is entirely above
   the viewport; the scrollbar is the only symptom, and it is a real difference
   from live.)

   Clipping the root is the same rule mobile-root-overflow-clip already applies
   at <=767, generalised: nothing scrolls sideways, ever. `clip` rather than
   `hidden` so the root does not become a scroll container. Regression-tested by
   injection at 1400px against Vision, Crystal, Progressive, Oxygen and Quadra —
   no geometry change on any of them.

   Deliberately NOT fixed by hiding `li > ul` structurally: `visibility` is not
   inert on a statically positioned list the way `top`/`left` are, so a bare
   `li > ul { visibility: hidden }` would also blank ordinary nested lists in
   article content — the trap cs-dropdown-hover-bridge documents just above.
   ===================================================================== */
@media (min-width: 768px) {
  html { overflow-x: clip; }
}

/* =====================================================================
   CS-PATCH: bs2-popover-tooltip-hidden  2026-08-11  (found on EvoStore)
   Sibling of bs2-tab-pane: another visibility contract that lived in the BS2
   stylesheet Phase 5d strips.

   BS2 shipped `.popover { position: absolute; display: none }` and revealed it
   with `.in`. VirtueMart's cart module PRE-RENDERS its popover into the page and
   relies on that default. BS5 changed the model completely: `.popover` is
   `display: block`, and `position: absolute` arrives at RUNTIME as an inline
   style from Popper when JS instantiates one. Server-rendered popover markup
   with no Popper instance therefore becomes a visible, in-flow block.

   MEASURED on EvoStore @1400:
       live   .popover  position absolute  display none   rect 0,0,0,0
       stage  .popover  position static    display block  rect x1239 w172
   -> documentElement.scrollWidth 1411 against clientWidth 1385: 26px of
   horizontal scroll on every page, and the cart contents printed beside the
   cart button. The live donor still loads its own BS2 bootstrap.min.css, so the
   parity diff pointed at the popover but not at the reason.

   `.show`/`.in` keep BS5's own JS-driven popovers working — Popper's inline
   `position` beats this rule anyway, so a real popover is unaffected.
   ===================================================================== */
.popover, .tooltip {
    position: absolute;
    display: none;
}
.popover.in, .popover.show, .tooltip.in, .tooltip.show {
    display: block;
}

/* CS-PATCH: bs2-fullheight-wrapper-chain
   BS2 donors paint the content column's side shadows with a chain of nested
   "full height" panels, each carrying height:100%. Under the modernized stack
   that percentage resolves against a definite ancestor height instead of
   collapsing to auto, so the panel CLIPS the column it is supposed to wrap: the
   content overflows and the next band (bot-modules / footer) paints straight
   over the article list.
   Measured on Diversity 2026-08-11: wrapper chain 885px vs a 1747px content
   column; with this rule the chain measures 1747px and the page height goes
   2005px -> 2248px against a 2308px live donor.
   These panels are decorative - their height must follow their content. */
.box-left,
.box-right {
    height: auto !important;
}


/* =====================================================================
   CS-PATCH: standalone-colmd-grid  2026-08-11  (found on ET Synchronize)

   SIBLING to phase3a-colmd-grid, for the columns that are NOT in a row.

   phase3a-colmd-grid rebuilt the BS2 grid for `.row > [class*="col-md-"]`,
   which covers the common case. But the donors also put grid classes on
   elements whose parent is NOT a row - the standalone spans the bridge has
   always handled with its `:where(.spanN)` block. Phase 3a renames those to
   `.col-md-N` too, and nothing was catching them afterwards.

   With no rule they get Bootstrap 5's own `.col-md-N` width and NO float. BS5
   expects a flex parent to lay columns out; a plain block parent just stacks
   them.

   MEASURED on Synchronize @1400. `#menumenu.container` is not a row, and it
   holds `#menu-handler` + `#social-panel`, `.span7` and `.span5` on the donor:
       live   float:left, 670px + 470px side by side  -> #menumenu 51px tall
       stage  no float, full-width blocks stacked     -> #menumenu 85px tall
   The header nav and the social strip sat on two lines instead of one.

   Widths are the 1170-grid BS2 percentages, (100n-30)/1170, identical to the
   `:where(.spanN)` block - not BS5's gutter-in-padding values - so a converted
   template measures the same as its donor.

   Specificity is deliberate: `:not(.row) >` makes these 0-2-0, enough to beat
   Bootstrap 5's own `.col-md-N` at 0-1-0, while a donor's own two-class rule
   still wins. Rows are excluded so phase3a-colmd-grid keeps that case.
   ===================================================================== */
:not(.row) > :where(.col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12) {
    box-sizing: border-box;
    float: left;
    min-height: 1px;
    margin-left: 2.564102564%;
}
:not(.row) > :where(.col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12):where(:first-child) {
    margin-left: 0;
}
:not(.row) > .col-md-1 { width: 5.982905983%; }
:not(.row) > .col-md-2 { width: 14.52991453%; }
:not(.row) > .col-md-3 { width: 23.07692308%; }
:not(.row) > .col-md-4 { width: 31.62393162%; }
:not(.row) > .col-md-5 { width: 40.17094017%; }
:not(.row) > .col-md-6 { width: 48.71794872%; }
:not(.row) > .col-md-7 { width: 57.26495726%; }
:not(.row) > .col-md-8 { width: 65.81196581%; }
:not(.row) > .col-md-9 { width: 74.35897436%; }
:not(.row) > .col-md-10 { width: 82.90598291%; }
:not(.row) > .col-md-11 { width: 91.45299145%; }
:not(.row) > .col-md-12 { width: 100%; }

@media (max-width: 767px) {
    :not(.row) > :where(.col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12) {
        float: none;
        width: 100%;
        margin-left: 0;
    }
}

/* =====================================================================
   CS-PATCH: camera-slide-cover  2026-08-11  (found on Evolution/Fluency, ET)
   camera.js sizes the slideshow wrapper from the cam_height parameter and then
   sizes each slide IMAGE from the image's own aspect ratio. Those two only
   agree at the container width the donor was authored for. After the bridge
   moves the container from the BS2 940 grid to the 1170 grid, the wrapper grows
   by the height ratio but the image does not quite keep up, leaving a strip of
   background along the bottom of the slideshow:
       Evolution @1400  wrap 725  image 714  (11px)
       Fluency   @1400  wrap 386  image 378  ( 8px)
   Live never shows it because live is still on the 940 grid.

   min-height:100% makes the image at least as tall as the wrapper and
   object-fit:cover crops the excess instead of stretching it, which is the
   behaviour camera.js documents (cover) and what the donor gets by accident
   at its original width.
   ===================================================================== */
#ph-camera-slideshow .cameraSlide img,
#ph-camera-slideshow .cameracurrent img,
.camera_wrap .cameraSlide img,
.camera_wrap .cameracurrent img {
    min-height: 100%;
    object-fit: cover;
}


/* =====================================================================
   CS-PATCH: bs2-grid-child-display  2026-08-11  (found on Aeon)
   BS2's grid child rule was `.row-fluid [class*="span"] { display:block;
   float:left; width:... }`. The bridge ports the widths, the gutter and the
   flex plumbing but never ported `display`. That is invisible on a <div>
   (already block) and glaring on an <li>: Aeon's brand strip is
   `<ul class="row-fluid vmmanufacturer"><li class="span2">`, so once BS2 CSS
   is stripped each li falls back to the UA default `display:list-item` and
   renders a bullet beside every manufacturer logo. Live shows `display:block`
   from BS2 and no bullets.
   Real specificity, matching the sibling `.row-fluid > [class*="span"]` block,
   so it beats the UA default without outranking template.css.
   ===================================================================== */
.row-fluid > [class*="span"],
.row > [class*="span"] { display: block; }


/* =====================================================================
   CS-PATCH: navbar-fixed-top-offsets  2026-08-11  (found on Aeon)
   Companion to navbar-fixed-top-zindex above, which deliberately restores
   ONLY the stacking context and leaves `position` to the per-family patches.
   BS2's full rule was
     `.navbar-fixed-top { position:fixed; top:0; right:0; left:0; z-index:1030 }`
   and the OFFSETS were never ported either. A donor that sets `position:fixed`
   itself (Aeon does, in layout.css) then has a fixed element with `top/left/
   right: auto`, which resolves to its STATIC position and shrink-to-fit width.
   Measured on Aeon: the header rendered at y=104 (the body's own padding-top,
   reserved for the very bar that was supposed to fill it) and 579px wide
   instead of y=0 and 1400px -- a full-width band of white space above the site
   and a menu jammed against the left edge.
   Offsets only. `position` is still the donor's / the family patch's call, and
   offsets are simply ignored on a statically-positioned element, so a family
   patch that unfixes the header at a breakpoint is unaffected.
   ===================================================================== */
.navbar-fixed-top,
.navbar-fixed-bottom { right: 0; left: 0; }
.navbar-fixed-top { top: 0; }
.navbar-fixed-bottom { bottom: 0; }


/* =====================================================================
   CS-PATCH: social-sprite-vs-fa6  2026-08-11  (found on Aeon)
   Phase 10 replaces the donor's 2013 social set with Font Awesome 6 brand
   glyphs, but the donor's own CSS paints a SPRITE on the same element:

     live   <a id="twitter"><span>Follow Us</span></a>
            #social-links li a span { background: url(social-sprite.png) }

     stage  <a id="twitter" class="sc-1">
              <span class="icon"><i class="fab fa-x-twitter"></i></span>

   The `.icon` span still matches the donor's sprite rule, so the sprite paints
   BEHIND and the FA glyph paints IN FRONT - two icons in one 24x22 box.
   Measured on Aeon: background-image social-sprite.png at 0px 0px, with an
   <i> in "Font Awesome 6 Brands" inside it.

   Guarded with :has() so it fires ONLY where an FA glyph actually replaced the
   sprite. Phase 10 deliberately KEEPS a donor's local socicon font when it
   matches live, and those templates have no <i class="fa*"> - so they are not
   touched by this rule.
   ===================================================================== */
#social-links li a span:has(> i[class*="fa-"]),
#social-links li a .icon:has(> i[class*="fa-"]) {
    background-image: none !important;
}

/* The generated .sr-only label inherited the sprite too, painting a 1x1
   fragment of it. Make it a proper visually-hidden label. */
#social-links .sr-only {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
    background-image: none !important;
}


/* =====================================================================
   CS-PATCH: aeon-round3  2026-08-11  (Tim review of Aeon)

   (a) PRODUCT-TILE METRICS BELONG TO THE DONOR.
   VirtueMart product lists size their own tiles with a second class beside the
   spanN - `<li class="span2 sp20 floatleft">` - and `vm-aeon.css` carries
   `.span2.sp20 { width:19.2%; margin-left:1% }`. That is specificity (0,2,0),
   exactly the same as this bridge's `.row-fluid .span2`, so the winner is
   decided purely by load order - and the bridge is deliberately loaded LAST.
   The bridge therefore beat the donor and every tile came out 120px with a
   21px gutter instead of 158px with an 8px gutter: five tiles ended at x=1114
   inside an 825px column whose right edge is 1255, leaving a 141px gap down the
   right of the product grid. Measured against live, which is 158/8.25.
   Fixed above by excluding `ul[class*="product"] > *` from the bridge's gutter
   and >=768px width rules, so the donor's own metrics apply untouched.
   NOT excluded by element type: `.vmmanufacturer li.span2` carries no donor
   sizing class and measured 161px identical on both sides - it genuinely needs
   the bridge's BS2 width.

   (b) SOCIAL GLYPH CENTRING.
   The FA6 glyph Phase 10 nests inside the donor's 32x32 circle inherits the
   donor's line-height (20.8px) and renders a 38px-tall box, so it sits low and
   off-centre. Make the circle a flex centring context.

   (c) CHECKBOXES ARE NOT TEXT INPUTS.
   A full-width `.inputbox` rule was matching `input[type=checkbox]`, so the
   login modal's Remember Me box rendered 459px wide on its own line (live: 13px
   inline). Exempt checkbox/radio, and lay the row out with the box to the LEFT
   of its label per Tim 2026-08-11 - the donor's DOM order is label-then-input,
   so `order` does it without touching markup.

   (d) The modal's helper-link list ships bullets on both stage and live; Tim
   asked for plain links.
   ===================================================================== */

/* (b) */
#social-links li a:has(i[class*="fa-"]) {
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
}
#social-links li a:has(i[class*="fa-"]) > span:not(.sr-only) {
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    width: auto !important;
    height: auto !important;
    line-height: 1 !important;
}
#social-links li a i[class*="fa-"] { line-height: 1 !important; }

/* (c) */
#LoginForm input[type="checkbox"],
#LoginForm input[type="radio"],
.modal input[type="checkbox"],
.modal input[type="radio"] {
    width: auto !important;
    height: auto !important;
    min-width: 0 !important;
    display: inline-block !important;
    margin: 0 !important;
    vertical-align: middle !important;
}
#form-login-remember,
.modal #form-login-remember {
    display: flex !important;
    align-items: center !important;
    gap: .4em !important;
}
#form-login-remember label { order: 2; margin: 0 !important; }
#form-login-remember input[type="checkbox"] { order: 1; }

/* (d) */
#LoginForm ul,
.modal #LoginForm ul { list-style: none !important; padding-left: 0 !important; margin-left: 0 !important; }
#LoginForm ul > li,
.modal #LoginForm ul > li { list-style: none !important; }


/* =====================================================================
   CS-PATCH: slideshow-arrows-touch  2026-08-11  (Tim review of Aeon)
   camera.js ships its prev/next arrows at `opacity: 0` and fades them in on
   hover of the slideshow. A touch device never fires hover, so on a phone the
   slideshow has no visible way to advance or go back. Measured on Aeon at
   375px: both .camera_prev and .camera_next present, visible, 76x99 - and
   opacity 0. The live donor behaves identically, so this is not a
   modernization regression; it is a defect the donors have always shipped and
   which only shows up once you actually drive the page on a phone.

   Keyed on `hover: none` rather than a width breakpoint: the thing that makes
   the arrows unreachable is the absence of hover, not the size of the screen.
   A small window on a desktop keeps the donor's hover behaviour.

   Swiper's own nav buttons are opacity-1 by default and are unaffected; they
   are listed only so a template mid-migration behaves the same way.
   ===================================================================== */
@media (hover: none) {
    .camera_prev,
    .camera_next,
    .swiper-button-prev,
    .swiper-button-next { opacity: 1 !important; }
}


/* =====================================================================
   CS-PATCH: mobile-header-order  2026-08-11  (Tim review of Aeon)
   Donors put a utility bar (currency, cart) ABOVE the logo. On a phone that
   wastes the top of the screen on the least important controls and pushes the
   brand and the burger down - Aeon measured cart y=0, currency y=9, logo y=63,
   burger y=139.
   `cs-mobile-generic.js` tags the header `.cs-mob-header` and whichever of its
   direct children contains the logo row `.cs-mob-logo-branch`; the ordering is
   done here so it applies ONLY on a phone and reverts by itself at desktop,
   where the donor's order is deliberate.
   The burger is absolutely positioned inside the logo row; centre it on that
   row's midline rather than leaving it wherever the donor's offsets put it.

   REVISED 2026-08-11: the first attempt did this with `display:flex;
   flex-direction:column` on the header plus `order`. That collapsed the logo
   branch - a `.container` whose only in-flow content is an absolutely
   positioned burger shrink-wrapped to 10px and the logo rendered at y=-64.
   Changing a donor header's display model is too blunt. The reordering is now
   a DOM move in cs-mobile-generic.js, bound to a matchMedia listener so it
   reverses at desktop; only the burger centring is left here.
   ===================================================================== */
/* REVISED 2026-08-17 (.57, Impulsa grad): `top:50%` resolves against the
   burger's CONTAINING BLOCK - the nearest POSITIONED ancestor - which is not
   necessarily the logo row and is never guaranteed to be the logo's own box.
   cs-mobile-generic.js's burgerMidline() measures the logo and publishes
   --cs-burger-top; where the two boxes already agree it publishes nothing and
   the 50% fallback is used, so nothing signed off to date can move. */
@media (max-width: 767px) {
    #cs-menu-toggle {
        top: var(--cs-burger-top, 50%) !important;
        bottom: auto !important;
        transform: translateY(-50%) !important;
    }
}


/* =====================================================================
   CS-PATCH: mobile-logo-unpin  2026-08-11  (Tim review of Aeon)
   Donors pull the logo UP out of its own row so it visually lands inside the
   utility bar above it - Aeon: `#site-name-handler { position:absolute;
   top:-71px }`. That is coherent only while the utility bar sits above the
   logo row. `cs-mobile-generic.js` now moves the logo row to the top of the
   header on a phone (so the brand and the burger get the best position rather
   than the currency selector and the cart), which strands that negative offset
   off-screen: the logo measured y=-67 against a container at y=0.
   Inside the branch we moved, put the logo holder back in normal flow. Scoped
   to `.cs-mob-logo-branch` so it can only ever affect a header this script
   actually reordered, and only at phone width.
   ===================================================================== */
@media (max-width: 767px) {
    .cs-mob-logo-branch #site-name-handler,
    .cs-mob-logo-branch #sn-position,
    .cs-mob-logo-branch #logo,
    .cs-mob-logo-branch .logo,
    .cs-mob-logo-branch #h1 {
        position: static !important;
        top: auto !important; right: auto !important;
        bottom: auto !important; left: auto !important;
        transform: none !important;
        margin-top: 0 !important;
    }
    /* the row now carries the logo AND the burger - give it room and align them */
    .cs-mob-logo-branch > .row,
    .cs-mob-logo-branch > .row-fluid {
        display: flex !important;
        align-items: center !important;
        min-height: 56px !important;
    }
}


/* =====================================================================
   CS-PATCH: mobile-search-icon  2026-08-11  (Tim review of Aeon)
   The search submit inside #cs-search-panel is a clone of the donor's
   `input[type=submit]`, and the donor's icon is painted by a rule the clone
   does not inherit - measured background-image: none. With `font-size: 0`
   collapsing the value="Search" text (needed, or the word overflows a 44px
   box) the control rendered as a blank square: before this run it was white on
   white and completely invisible, which is what Tim reported.
   Ship an inlined SVG magnifier as the FALLBACK so the button always reads as
   search, on any donor, with no dependency on an outside host or on the
   donor's own sprite. A donor whose own icon does reach the clone still wins:
   this is not !important, and any donor rule that sets background-image at all
   is more specific than this one.
   ===================================================================== */
@media (max-width: 767px) {
    .cs-search-submit,
    #cs-search-panel button[type="submit"],
    #cs-search-panel input[type="submit"] {
        background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.2' stroke-linecap='round'%3E%3Ccircle cx='10.5' cy='10.5' r='6.5'/%3E%3Cpath d='M15.5 15.5 L21 21'/%3E%3C/svg%3E");
        background-size: 22px 22px;
    }
}


/* =====================================================================
   CS-PATCH: mobile-header-tidy  2026-08-11  (Tim review of Aeon, round 2)
   Four things, all consequences of the donor's phone header having been
   designed around the utility bar sitting ABOVE the logo.

   (a) `responsive-template.css @(max-width:767px)` pins
       `#top-nav-line, #top-nav-line1 { min-height: 130px }`. With the bar now
       BELOW the logo that reserves 130px for a 37px cart and a 35px currency
       selector, both of which then sit at the top of a mostly empty band.
       Tim asked for this specific rule to be dropped on mobile.

   (b) `.sn-underline` is a decorative band the donor absolutely positions
       behind the brand. Measured x=48 (the logo's own left edge) by 371 wide
       and y=-50 by 130 tall - so it started 46px in from the container's left,
       leaving a slab of `#top1`'s darker grey showing beside the logo (the
       "grey square"), and it overhung the row top and bottom, which is what
       made the burger look off-plane against it. Pin it to the row it decorates.

   (c) With the band no longer forcing 130px, centre the cart and the currency
       selector on the row instead of letting them hang from its top edge.

   (d) The currency <select> and its chevron sat 8px above the row's midline
       because the donor's dropkick wrapper is gone; centre them too.
   ===================================================================== */
@media (max-width: 767px) {

    /* (a) */
    #top-nav-line,
    #top-nav-line1,
    #top-nav-line2 { min-height: 0 !important; }

    /* (b) REVISED: the first attempt made `.row` the positioning context and
       used top:0/bottom:0. The band then anchored to the row (inset 5px from
       the header) and collapsed to h=0, so the whole header fell back to
       `#top1`'s darker grey and the donor's blue disappeared entirely.
       Anchor to the header block itself, size it explicitly, and paint it
       behind the content with a negative z-index - a positioned element at
       z-index:-1 paints before in-flow content, so the brand and the burger
       stay on top without needing a stacking context of their own. */
    .cs-mob-logo-branch > .container1,
    .cs-mob-logo-branch > .container { position: relative !important; }
    .cs-mob-logo-branch .row { position: static !important; }
    .cs-mob-logo-branch .sn-underline {
        position: absolute !important;
        left: 0 !important; right: 0 !important; top: 0 !important;
        bottom: auto !important;
        width: auto !important; height: 100% !important;
        margin: 0 !important; z-index: -1 !important;
    }

    /* (c) */
    #top-nav-line1 {
        display: flex !important;
        flex-direction: row !important;
        align-items: center !important;
        justify-content: flex-end !important;
        gap: 10px !important;
        padding: 8px 12px !important;
    }
    #top-nav-line1 > #cart-position,
    #top-nav-line1 > #currency {
        float: none !important;
        display: flex !important;
        align-items: center !important;
        margin: 0 !important;
        height: auto !important;
    }
    /* the donor paints currency then cart; DOM order here is cart-position
       first, so put it back with `order` rather than moving nodes. */
    #cart-position { flex: 0 0 auto; order: 2; }
    #currency      { flex: 0 1 auto; order: 1; }

    /* (d) */
    #currency form { display: flex !important; align-items: center !important; margin: 0 !important; }
    #currency select {
        height: 32px !important;
        line-height: 32px !important;
        vertical-align: middle !important;
        background-position: right 8px center !important;
        background-repeat: no-repeat !important;
    }
    #currency, #currency * { background-position-y: center !important; }
}


/* =====================================================================
   CS-PATCH: mobile-module-gutter  2026-08-11  (Tim review of Aeon, round 3)
   The donor pads its module wrappers `0 30px`. On a 375px phone that is 60px -
   16% of the screen - and it does not just narrow the modules, it pushes their
   content out of alignment: `#mod-custom48` (the "3 Easy Steps" band) measured
   x=32 with only 2px of clearance on the right, because the module renders
   341px wide inside a 311px content box and overflows the right padding. In
   Tim's screenshot that clips "3 Checkout" off the edge.
   30px is a desktop gutter. Keep a small one on phones so text does not touch
   the bezel, and let the module box centre properly.
   ===================================================================== */
@media (max-width: 767px) {
    #bootmod3,
    #bot-modules3,
    #main-module-handler-a3 {
        padding-left: 10px !important;
        padding-right: 10px !important;
    }
    /* The same 30px donor gutter, expressed as a MARGIN rather than padding.
       `#brcr` (the position-2 wrapper carrying breadcrumbs and the "3 Easy
       Steps" band) has `margin-left: 30px` and no right margin, so its content
       sat at x=32 with 2px of clearance on the right - shifted, not centred,
       and clipping the last step off the edge. Make it a symmetric phone
       gutter so the band centres. */
    #brcr {
        margin-left: 10px !important;
        margin-right: 10px !important;
    }
}


/* =====================================================================
   CS-PATCH: mobile-utility-row-tidy  2026-08-11  (Tim review of Aeon, round 4)

   (a) `#content-handler3` is the third place the donor's 30px desktop gutter
       shows up (after the `#bootmod3/#bot-modules3/#main-module-handler-a3`
       padding and `#brcr`'s margin). Measured `0px 30px` at 375px. Zero it.

   (b) The currency <select> had `padding-left: 0` while its chevron sits 8px
       in from the right (`background-position: calc(100% - 8px) 50%`), so the
       text was flush against the left edge of its box. Match the two, and keep
       enough right padding that a long currency name cannot run under the
       chevron.

   (c) The cart BOX was already on the row's midline (cy 189) but the icon
       inside it was not - the anchor measured cy 193 against the select's 190.
       Centre the link within its module.

   (d) The row was `justify-content: flex-end`, leaving 141px of dead space to
       the left of the pair and 12px to the right. Centre them on the row.
   ===================================================================== */
@media (max-width: 767px) {

    /* (a) */
    #content-handler3 { padding-left: 0 !important; padding-right: 0 !important; }

    /* (b) moved out of this media query - see the all-widths rule below.
       Tim 2026-08-11: the gutter and the margin look right on desktop too. */

    /* (c) REVISED 2026-08-11: this originally flexed `#vmCartModule` ITSELF.
       That module also contains the cart dropdown `.panel2`, so flexing the
       module turned the panel into a flex item and squeezed it to the width of
       the 28px button - tapping the cart opened a 28px-wide grey strip instead
       of the 300px panel it renders on desktop. Only the button row may be
       flexed; the panel has to stay out of it. */
    #vmCartModule .show-cart {
        display: flex !important;
        align-items: center !important;
        justify-content: center !important;
    }
    #vmCartModule .show-cart a { margin: 0 !important; }
    /* keep the dropdown out of the button's flow and let it size itself */
    #vmCartModule .panel2,
    #vmCartModule .cartpanel { flex: none !important; }

    /* (d) */
    #top-nav-line1 { justify-content: center !important; }
    /* `#quick-menu` and `#top-quick-nav` are retired on phones and measure 0x0,
       but they are still flex ITEMS at order 0 - so they sit ahead of the pair
       and each still contributes its 10px gap, pushing the group 20px right
       (measured 74px left / 54px right). A zero-size item is not a no-op in a
       gapped flex row; take them out of the layout entirely. Cloning for the
       secondary panel is unaffected - cloneNode does not care about display. */
    #top-nav-line1 > #quick-menu,
    #top-nav-line1 > #top-quick-nav { display: none !important; }
    /* BS2's clearfix on `.container` is `::before/::after { display: table;
       content: "" }`. Turning the row into a flex container promotes BOTH
       pseudo-elements into flex ITEMS - zero width, but each still claims a
       10px gap, so the pair never lands on centre however you justify it
       (measured 74px left / 54px right). A clearfix has no purpose in a flex
       row anyway; take them out of the layout. */
    #top-nav-line1::before,
    #top-nav-line1::after { display: none !important; }
}


/* =====================================================================
   CS-PATCH: currency-select-gutter  2026-08-11  (Tim, Aeon)
   ALL WIDTHS, not just phones. The currency <select> had `padding-left: 0`
   while its chevron sits 8px in from the right edge
   (`background-position: calc(100% - 8px) 50%`), so the label was flush left
   against its own border. Match the two, keep enough right padding that a long
   currency name cannot run under the chevron, and set Tim's 8px left margin so
   the control is not jammed against whatever precedes it.
   ===================================================================== */
#currency select {
    padding-left: 8px !important;
    padding-right: 28px !important;
    margin-left: 8px;
}


/* =====================================================================
   CS-PATCH: mobile-cart-dropdown  2026-08-11  (Tim, Aeon)
   Tapping the cart on a phone opened a 28px-wide grey strip.

   `layout.css` gives the dropdown `#vmCartModule div.panel2 { width: 300px;
   position: absolute; right: 0 }`. The bridge's own
   `#top-handler, #top-handler *, header * { max-width: 100% !important }`
   - "nothing inside the header may exceed the screen", added for Encore's
   679px #sn-position - also matches this panel. 100% of its containing block
   is the 28px cart button, so the 300px panel was clamped to 28px and `left`
   resolved to 0. Desktop was unaffected because that rule is phone-only.

   The header-fit rule is right in general; the cart dropdown is the exception,
   because it is deliberately WIDER than the control that opens it. Give it an
   explicit width and centre it on the viewport rather than hanging it off the
   module's right edge, which is what put it half off-screen.
   ===================================================================== */
@media (max-width: 767px) {
    #vmCartModule .panel2,
    #vmCartModule .panel2 *,
    #vmCartModule .cartpanel,
    #vmCartModule .cartpanel * { max-width: none !important; }

    /* `position: absolute` + `left: 50%` centres against the CONTAINING BLOCK,
       which is the 28px cart button - the panel landed at x=130 and ran 94px
       off the right edge. To centre on the SCREEN it has to be viewport-
       positioned. Anchor the top to the same `--cs-drawer-top` the mobile
       drawer uses, so it opens just below the header like every other panel. */
    #vmCartModule .panel2 {
        position: fixed !important;
        top: var(--cs-drawer-top, 120px) !important;
        left: 50% !important;
        right: auto !important;
        bottom: auto !important;
        margin-left: 0 !important;
        transform: translateX(-50%) !important;
        width: calc(100vw - 24px) !important;
        max-width: 340px !important;
        z-index: 1060 !important;
    }
    /* the little pointer no longer lines up with a centred panel */
    #vmCartModule .panel2 > .arrow { display: none !important; }
}


/* =====================================================================
   CS-PATCH: cookstown-search-btn  2026-08-11
   Item 4 of the 2026-07-22 cookstown maintenance fix-set (Michael Mullan's
   Expression report). A global `.btn { margin: ... }` in the donor's CSS
   detaches the search submit from its input-group, so the button floats away
   from the field it belongs to. Items 2 and 3 of that fix-set were already
   carried here; item 4 was not, and was still missing on Aeon at its release
   ceremony - which is how the whole fix-set turned out never to have been
   wired into the pipeline. Now applied to every template via bridgesync.
   ===================================================================== */
#search-position .btn,
.mod-finder__search .btn,
.search .btn { margin: 0 !important; }


/* =====================================================================
   CS-PATCH: fa6-social-reset-generic  2026-08-12  (found on CorporateImpact)
   The 2026-07-14 fa6-social-reset above is scoped to #social-links, which is
   the id socialset-modernize.py emits. Sprite-based donors that keep their own
   list id (CorporateImpact renders the social bar as #top-navigation-links)
   inherit none of it: the donor's per-network id rules carry
   text-indent:-9999px plus a background sprite at specificity 1,0,0, so after
   the Phase 10c conversion the FA6 glyph is pushed 9999px off-screen and the
   dead sprite image is still painted behind it. Six blank boxes where the
   social icons should be, and every file-level gate green.
   Scoped by SHAPE instead of by id - an anchor whose child is the generated
   span.icon wrapper around a Font Awesome i - so it can only ever match markup
   this pipeline itself emitted, on any donor, without an opt-in class.
   ===================================================================== */
a:has(> span.icon > i[class*="fa-"]) {
    text-indent: 0 !important;
    background-image: none !important;
    text-align: center;
    text-decoration: none;
}
a:has(> span.icon > i[class*="fa-"]) > span.icon { display: inline; }
a > span.icon > i[class*="fa-"] { font-style: normal; }
a:has(> span.icon > i[class*="fa-"]) > span.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0,0,0,0);
    white-space: nowrap;
    border: 0;
}


/* =====================================================================
   CS-PATCH: bs2-fixed-inner-strip  2026-08-11  (found on ET Ngine)

   SIBLING of bs2-fixed-inner-container. That block covers the donor family's
   inner BAND wrapper (.container1). This one covers the same lineage's
   decorative SEPARATOR STRIPS - a few-pixels-tall div whose only job is to
   paint a shadow/gradient under a band, hard-pinned to the retired 940/960
   grid and centred with margin:auto:

       Ngine  #content-bg-2 { width: 960px; height: 8px; margin: 0 auto }

   Once Phase 3b.1 + this bridge move the page to the 1170 grid the strip no
   longer spans its band, and below 960 it becomes the widest box on the page.
   MEASURED on Ngine stage against the live donor:
       @1400  strip 960px inside 1170px bands - a 105px inset per side that
              live does not have (there the outer grid is 960 and it fits)
       @768   document scrollWidth 960 against a 768 viewport - the strip WAS
              the entire 192px of horizontal overflow
       @375   strip right edge 980 on a 375 screen
   Relax the width only; the explicit height is the design (it is a background
   image slice) and `margin: auto` still centres it.
   ===================================================================== */
#content-bg-2,
#content-bg-1,
#content-bg {
    width: auto !important;
    max-width: 100% !important;
}


/* =====================================================================
   CS-PATCH: modmenu-nav-block-flow  2026-08-12  (found on CorporateImpact)

   Joomla's mod_menu emits `<ul class="mod-menu mod-list nav">` for EVERY menu
   module. In Bootstrap 2 `.nav` was a plain vertical list - `margin-left:0;
   list-style:none`, display block - and the horizontal variants got their
   direction from `.nav-tabs`/`.nav-pills` floating the `li`. Bootstrap 5
   redefined the base class as `display:flex; flex-wrap:wrap`.

   So the moment Phase 3b.1 loads BS5 core, every sidebar menu on the site
   turns HORIZONTAL. Measured on CorporateImpact's left column at 1400px:
       live   ul 260px wide, 5 li stacked at 20px each, list h=100
       stage  ul display:flex, li 43/62/39/89/105px side by side, wrapping
              to two rows, list h=48 - and the 210px the column lost pushed
              the footer 208px down the page.
   Nothing in the phase suite looks for it, and a screenshot of a short menu
   reads as a styling choice.

   Restores the BS2 contract for MENU MODULE lists only, so tab strips
   (`.nav-tabs`, `.nav-pills`, Joomla's own `nav-tabs` UI) keep BS5's flex.
   Donor rules that float the `li` still make their main menu horizontal -
   floats work identically inside a block container, which is exactly how the
   BS2 layout worked.
   ===================================================================== */
ul.mod-menu.nav,
ul.mod-list.nav {
    display: block;
}


/* =====================================================================
   CS-PATCH: bs2-base-body-type  2026-08-12  (found on CorporateImpact)

   Bootstrap 2's Reboot equivalent set `body { font-size: 14px; line-height:
   20px }`. Some donors load a FULL `css/bootstrap.css` AFTER their own template
   CSS, so that rule out-cascaded the template's own older `body { font-size:
   12px }` and 14px/20px is what the live demo actually renders. Phase 5d strips
   that stylesheet, and the page silently drops to the smaller size - measured on
   CorporateImpact @1400: stage 12px / 17.4px against the donor's 14px / 20px,
   taking every text block, the sidebar and the page height down with it.

   OPT-IN, and it has to be. The bridge loads last, so an unconditional `body`
   rule would out-cascade every donor whose base size is deliberate - Concern
   renders at 15.9px and would have been silently reset. The discriminator is a
   build-time fact ("did this donor load a BS2 stylesheet after its own CSS?"),
   applied by helpers-concern/et-bs2-basetype.py, exactly like the
   cs-no-bs2-gutter opt-out.
   ===================================================================== */
body.cs-bs2-base-type {
    font-size: 14px;
    line-height: 20px;
}


/* =====================================================================
   CS-PATCH: nonresponsive-donor-fluidize  2026-08-12  (found on CorporateImpact)

   Some donors have NO responsive mode at all: CorporateImpact pins every band
   to the 960 grid and its live demo simply scrolls sideways on a phone
   (documentElement.scrollWidth 960 at a 375 viewport). Once the modernized
   stack adds the mobile chrome, `html, body { overflow-x: hidden }` comes with
   it - so the 960px page is CLIPPED at 375 instead of scrolled, and 585px of
   every band is unreachable. Strictly worse than the donor, and no gate sees it
   because the file checks all pass and the desktop render is perfect.

   Fluidize the fixed-width chrome below 768 instead. Scoped with
   `html:has(#content_background)` because the inner ids are NOT unique across
   the fleet - `#top-navigation` is in 29 stylesheets, `#top-modules` in 149 -
   while the outer wrapper id is in exactly one. Naming the discriminator in a
   `:has()` prefix keeps a donor-shaped rule out of every other template without
   making it per-template debt.
   ===================================================================== */
@media (max-width: 767px) {
    html:has(#content_background) #content_background,
    html:has(#content_background) #site_wrapper,
    html:has(#content_background) #top-wrapper,
    html:has(#content_background) #top-handler,
    html:has(#content_background) #top-navigation,
    html:has(#content_background) #top,
    html:has(#content_background) #foo,
    html:has(#content_background) #footer-con1,
    html:has(#content_background) #footer-line,
    html:has(#content_background) #footer-left,
    html:has(#content_background) #footer-right,
    html:has(#content_background) [id^="mod-custom"],
    html:has(#content_background) #slideshow,
    html:has(#content_background) #slideshow-handler,
    html:has(#content_background) #top-modules,
    html:has(#content_background) #bot-modules,
    html:has(#content_background) #main-cols,
    html:has(#content_background) #col-left,
    html:has(#content_background) #col-center,
    html:has(#content_background) #col-main,
    html:has(#content_background) #column-right,
    html:has(#content_background) #footer,
    html:has(#content_background) #footer-handler {
        width: auto !important;
        min-width: 0 !important;
        max-width: 100% !important;
        float: none !important;
    }
    html:has(#content_background) #site_wrapper {
        padding-left: 10px !important;
        padding-right: 10px !important;
    }
    html:has(#content_background) img {
        max-width: 100%;
        height: auto;
    }
}


/* =====================================================================
   CS-PATCH: mobile-ui-round5  2026-08-12  (Tim review)

   (a) UTILITY-ROW BUTTONS MUST ALL LOOK THE SAME. They are one control set,
       so one size, one shape, one colour treatment and one midline. Measured
       on Coga: all five were 44x44 on a common midline, but `#cs-register`
       rendered WHITE while the other four carried the donor's accent - it read
       as a different kind of control. The secondary hamburger must be the
       accent colour with WHITE bars; on several donors it inherited black-on-
       white and disappeared into the row.

   (b) THE PRIMARY BURGER IS OVERSIZED next to that row (56px against 44px).
       Bring it to the same 44px so the header reads as one system.

   (c) ACCOUNT-FORM FIELDS ARE UNUSABLY NARROW ON A PHONE. Measured on the
       registration page at 375px: username/password 191px, but email, company
       and first/middle name only 75px - a third of the screen, with the rest
       of the width empty to their right. The donor sizes them for a desktop
       two-column layout that no longer applies once the form stacks.

   (d) BREADCRUMBS WRAP ONE CRUMB PER LINE instead of flowing as a single line.
   ===================================================================== */
@media (max-width: 767px) {

    /* (a) one control set, one treatment */
    #cs-utility-row > button,
    #cs-utility-row > a {
        width: 44px !important;
        height: 44px !important;
        min-width: 44px !important;
        display: flex !important;
        align-items: center !important;
        justify-content: center !important;
        border-radius: 6px !important;
        padding: 0 !important;
    }
    #cs-utility-row > button > i,
    #cs-utility-row > a > i { font-size: 20px !important; line-height: 1 !important; }

    /* the secondary hamburger: accent background, white bars */
    /* `#cs-secondary-toggle span` also matched the .cs-burger-bars WRAPPER,
       painting a solid 22x16 white block over the button - Tim saw a white
       square. Only the three inner bars may be white; the wrapper stays
       transparent, exactly as the primary burger's does. */
    #cs-secondary-toggle { background: var(--cs-accent, #992946) !important; }
    #cs-secondary-toggle .cs-burger-bars { background: transparent !important; }
    #cs-secondary-toggle .cs-burger-bars > span { background: var(--cs-accent-ink, #fff) !important; }
    #cs-secondary-toggle > i { color: var(--cs-accent-ink, #fff) !important; }

    /* (b) primary burger sized to match the row */
    #cs-menu-toggle { width: 44px !important; height: 44px !important; }
    #cs-menu-toggle .cs-burger-bars { width: 26px !important; height: 20px !important; }

    /* (c) account/registration fields fill the width once the form stacks */
    .com-users-registration input[type="text"],
    .com-users-registration input[type="email"],
    .com-users-registration input[type="password"],
    .com-users-profile input[type="text"],
    .com-users-profile input[type="email"],
    .com-users-profile input[type="password"],
    .registration input[type="text"],
    .registration input[type="email"],
    .registration input[type="password"],
    .profile-edit input[type="text"],
    .profile-edit input[type="email"],
    .profile-edit input[type="password"] {
        width: 100% !important;
        max-width: 100% !important;
        box-sizing: border-box !important;
    }
    .com-users-registration .control-group,
    .com-users-registration .controls,
    .registration .control-group,
    .registration .controls { width: 100% !important; max-width: 100% !important; }

    /* (d) breadcrumbs on one flowing line */
    .breadcrumb, ul.breadcrumb, .mod-breadcrumbs__wrapper ul {
        display: flex !important;
        flex-wrap: wrap !important;
        align-items: center !important;
        gap: 4px 6px !important;
        padding-left: 0 !important;
        margin-bottom: 10px !important;
    }
    .breadcrumb > li, ul.breadcrumb > li, .mod-breadcrumbs__wrapper li {
        display: inline-flex !important;
        align-items: center !important;
        float: none !important;
        width: auto !important;
        white-space: nowrap !important;
    }
}


/* =====================================================================
   CS-PATCH: mobile-ui-round6  2026-08-12  (Tim review)

   (a) `#cs-register` rendered WHITE while the other four utility buttons
       carried the donor's accent, so the pencil read as a different class of
       control. One set, one treatment.

   (b) The cart sits in its own row as a bare icon. Give it the same 44px
       accent-filled shape as the utility set so it reads as part of the same
       system rather than an orphan.

   (c) THE ACCOUNT FORM IS A TABLE. VirtueMart renders registration/profile as
       `table.adminForm.user-details`, not Joomla's `com-users-registration`
       divs - which is why the previous attempt's selectors matched nothing.
       Measured at 375px: `input#email_field` 75px inside a 145px `<td>` inside
       a 295px table, with the rest of the screen empty to its right. Collapse
       the table to blocks so every field gets the full width.
   ===================================================================== */
@media (max-width: 767px) {

    /* (a) */
    #cs-register {
        background: var(--cs-accent, #992946) !important;
        color: var(--cs-accent-ink, #fff) !important;
    }
    #cs-register > i { color: var(--cs-accent-ink, #fff) !important; }

    /* (b) */
    #vmCartModule .show-cart a,
    #cart-position a.cart-button {
        width: 44px !important;
        height: 44px !important;
        display: flex !important;
        align-items: center !important;
        justify-content: center !important;
        background: var(--cs-accent, #992946) !important;
        border-radius: 6px !important;
        color: var(--cs-accent-ink, #fff) !important;
    }
    #vmCartModule .show-cart a i,
    #cart-position a.cart-button i { color: var(--cs-accent-ink, #fff) !important; font-size: 20px !important; }

    /* (c) */
    table.adminForm.user-details,
    table.adminForm.user-details > tbody,
    table.adminForm.user-details > tbody > tr,
    table.adminForm.user-details > tbody > tr > td {
        display: block !important;
        width: 100% !important;
        max-width: 100% !important;
        box-sizing: border-box !important;
    }
    table.adminForm.user-details > tbody > tr > td { padding: 2px 0 !important; }
    table.adminForm.user-details input[type="text"],
    table.adminForm.user-details input[type="email"],
    table.adminForm.user-details input[type="password"],
    table.adminForm.user-details select,
    table.adminForm.user-details textarea {
        width: 100% !important;
        max-width: 100% !important;
        box-sizing: border-box !important;
    }
}


/* =====================================================================
   CS-PATCH: burger-on-brand-midline  2026-08-13  (Tim review of Coga)
   The burger is absolutely positioned, so it escapes the logo row's
   `align-items: center` and lands wherever its offsets put it - measured on
   Coga at cy=54 against the logo's cy=32, i.e. 22px low, hanging off the
   bottom edge of the branded bar instead of sitting in it.
   Put it back IN FLOW as the last flex item on that row and let the row centre
   it, which is what `align-items: center` is already doing for the logo.
   `margin-left: auto` keeps it hard right without needing offsets at all.
   ===================================================================== */
@media (max-width: 767px) {
    /* REVERTED 2026-08-13: taking the burger out of absolute positioning was
       supposed to let the logo row centre it. It is not a sibling of the logo,
       so it simply dropped onto its own line BELOW the brand - measured 50px
       off the logo's midline against 22px before the change. Worse, not
       better. The burger stays absolutely positioned; centring it on the
       branded bar needs that bar's own box, which I have not identified on
       this donor (.sn-underline exists on Aeon, not on Coga). */
    /* the adopted cart is one of the six now */
    #cs-utility-row > .cs-adopted-cart { flex: 0 0 auto; margin: 0 !important; }
}


/* =====================================================================
   CS-PATCH: utility-row-spacing  2026-08-13  (Tim review of Coga)
   Six 44px buttons did not fit and were unevenly spaced. Measured at 375px:

     gaps  24 | 19 | 19 | 24 | 19        first left edge 6, last right edge 375

   Two causes, both about the row using TWO spacing mechanisms at once:
     - the row sets `gap: 14px`, but the buttons ALSO carry `margin: 5px` --
       except `#cs-register` and the adopted `#vmCartModule`, which carry 0.
       gap+10 on some pairs and gap+5 on others is exactly the 24/19 pattern.
     - 6x44 plus those gaps exceeds the screen, so the first and last buttons
       were clipped at both edges.

   One mechanism only: zero the margins and let `gap` own the spacing. 10px
   gives 264 + 50 = 314px inside a 359px content box, so the row centres with
   ~22px clear at each end.
   ===================================================================== */
@media (max-width: 767px) {
    #cs-utility-row {
        gap: 10px !important;
        justify-content: center !important;
        flex-wrap: nowrap !important;
    }
    #cs-utility-row > * { margin: 0 !important; }
}


/* =====================================================================
   CS-PATCH: desktop-header-icons  2026-08-13  (Tim review of Coga)

   (a) The donor's own top-right controls are each sized independently, so they
       read as five unrelated things. Measured at 1400px:

         .open-off-canvas    16x26  icon 18px   cy 33
         .open-register-form 30x24  icon 14px   cy 32
         .v_register         32x24  icon 14px   cy 32
         #searchOpenButton   28x24  icon 10px   cy 32
         #cartpanel          16x22  icon 14px   cy 35

       height spread 16px, midline spread 6px, icon sizes from 10px to 18px.
       Give them one box, one icon size and one midline. Note these are the
       DONOR's controls, not `#cs-utility-row` -- that one is phone-only and is
       already uniform.

   (b) The slideshow caption carries `.on-right`, so the donor intends the text
       in the right half, but the block spans the full 1170px and the copy lands
       on top of the product image. Constrain `.on-right` to the right half and
       align its text there.
   ===================================================================== */
@media (min-width: 768px) {

    /* (a)
       2026-08-18 (Creativa/Progressive/Energize/Vision/Oxygen/Crystal, Tim):
       this group was authored against the VMT donor set, where every one of these
       controls is an ICON BUTTON carrying an <i> and addressed by CLASS. On the ET
       donors the same controls are BARE TEXT LINKS addressed by ID, so the rule
       failed twice over:
         - SELECTOR MISS: ET emits id="v_register" / id="v_toggle", and `.v_register`
           as a class never matched, so one sibling got the 32px box and its
           neighbour kept the donor's own size. MEASURED midline spread once .62
           applies: Creativa 8.0, Vision 8.0, Progressive 7.2, Oxygen 7.5, Energize
           2.6, Acceler8 1.1.
         - TEXT CLIPPING: a hard 32px box clips a label. MEASURED overflow
           (scrollWidth - clientWidth): Crystal 18px ("Login Form", 50 vs 32),
           Energize 12px, Progressive 10px, Oxygen 5px.
       So size on WHAT THE CONTROL IS, not on what we assumed it is. `:has(> i)`
       keeps the hard box for genuine icon buttons - VERIFIED a no-op on the VMT
       donors, where all four/five controls carry an <i> and stay 32x32 on their
       existing midline (Impulsa cy=121 @1400/1024, cy=116 @768; MubleStore cy=49)
       - and everything else sizes to content off a 32px floor, which also leaves
       an empty graphic control (ET's `<div id="searchOpenButton">`, an icon drawn
       as a background-image with no child at all) at exactly 32x32.
       AFTER: midline spread 0.0 and zero clipping on all seven ET templates at
       1400 / 1024 / 768; Impulsa and MubleStore bit-for-bit unchanged. */
    .open-off-canvas,
    .open-register-form,
    .v_register,
    #v_register,
    #v_toggle,
    #searchOpenButton,
    #cartpanel.cart-button {
        display: inline-flex !important;
        align-items: center !important;
        justify-content: center !important;
        box-sizing: border-box !important;
        line-height: 1 !important;
        vertical-align: middle !important;
        /* 2026-08-17 (Impulsa): the block promised "one midline" but each donor
           control kept its own vertical margin, so they still sat on three lines.
           MEASURED on Impulsa @1400: .log-panel a cy=116, .open-off-canvas cy=122
           (margin: 6px 13px), #cartpanel cy=136. Zero the VERTICAL margins only -
           the horizontal ones are the donor spacing and must survive. */
        margin-top: 0 !important;
        margin-bottom: 0 !important;
    }
    /* (a-i) genuine ICON BUTTONS keep the hard 32px box. This is the branch the
       VMT donors take for every one of their controls, so it reproduces .62
       exactly there. */
    .open-off-canvas:has(> i),
    .open-register-form:has(> i),
    .v_register:has(> i),
    #v_register:has(> i),
    #v_toggle:has(> i),
    #searchOpenButton:has(> i),
    #cartpanel.cart-button:has(> i) {
        width: 32px !important;
        height: 32px !important;
        padding: 0 !important;
    }
    /* (a-ii) everything else sizes to CONTENT off a 32px floor, so a text label
       is never clipped and an empty background-image control still measures
       32x32 (min-width wins when there is no content to grow past it). */
    .open-off-canvas:not(:has(> i)),
    .open-register-form:not(:has(> i)),
    .v_register:not(:has(> i)),
    #v_register:not(:has(> i)),
    #v_toggle:not(:has(> i)),
    #searchOpenButton:not(:has(> i)),
    #cartpanel.cart-button:not(:has(> i)) {
        width: auto !important;
        min-width: 32px !important;
        height: auto !important;
        min-height: 32px !important;
        /* 2026-08-19 (Vision/Creativa, Tim: "the words are half cut off ...
           looks like they're touching the icon"). The old `padding: 0 8px
           !important` here was the whole defect. On Vision and Creativa the
           donor draws the glyph as a BACKGROUND-IMAGE on the anchor and
           reserves room for it with `a#v_toggle, a#v_register { padding-left:
           20px }` in layout.css. `padding: 0 8px !important` wiped that
           reservation out, so the label started at x=8 and sat ON TOP of a
           glyph occupying x=0..16. MEASURED @1400/1024/768 before the fix:
           #v_toggle text at x=8 inside a 72x32 box, #v_register text at x=8
           inside 57x32, on BOTH templates - i.e. the accepted Creativa carried
           the identical defect Tim reported on Vision.
           NB scrollWidth == clientWidth on both, so the standard clipping test
           reports CLEAN here: the text is not clipped by its own box, it is
           painted over the background glyph. Assert on the text's own rect
           against the glyph, not on scrollWidth.
           The horizontal padding now lives in a SEPARATE, LOWER-SPECIFICITY
           rule below so a donor that reserves glyph space keeps its own value.
           Only the vertical padding is forced here, which is what "one midline"
           actually needs. */
        padding-top: 0 !important;
        padding-bottom: 0 !important;
        /* Second half of the same defect: the donor sized these anchors 16px
           tall and pinned the glyph to `0 0`, which was vertically centred at
           16px. `min-height: 32px` above doubles the box but leaves the glyph
           at the top, 8px above the now-centred label. Centre the glyph on the
           Y axis and leave X to the donor.
           VERIFIED a no-op everywhere else on the fleet: the only other
           background-image controls among the ten are Acceler8's and Oxygen's
           `#searchOpenButton`, both already at `50% 50%`. If a future donor
           paints these controls from a SPRITE SHEET, where the Y offset selects
           the glyph, this rule would pick the wrong sprite - check before
           assuming it is still safe. */
        background-position-y: center;
    }

    /* (a-iii) horizontal breathing room for a text label - deliberately WITHOUT
       `!important` and deliberately WITHOUT the `a` type selector, so it is
       specificity (1,0,0) for `#v_toggle` / (0,1,0) for `.open-register-form`.
       A donor rule that reserves glyph space (`a#v_toggle` = (1,0,1)) therefore
       WINS and keeps its 20px; a donor with no opinion loses to this and gets
       the 8px, because canonical loads last. That is the correct rule: "the
       donor's own padding when it has one, 8px when it does not".
       MEASURED AFTER: Vision/Creativa padding-left 20px (label clear of the
       glyph); Acceler8/Encore/Energize/Oxygen/Progressive unchanged at 8px;
       Crystal unchanged at 4px (its tail already beat this with !important);
       MubleStore/Impulsa untouched - they take the (a-i) icon branch, whose
       `padding: 0 !important` still overrides this. */
    .open-off-canvas,
    .open-register-form,
    .v_register,
    #v_register,
    #v_toggle,
    #searchOpenButton,
    #cartpanel.cart-button {
        padding-left: 8px;
        padding-right: 8px;
    }

    /* (a-iv) the DONOR'S OWN CONTAINER must not clip the taller chip this block
       creates. This is the part of Tim's "the words are half cut off" that is
       literally true, and neither `scrollWidth > clientWidth` on the control nor
       any measurement of the control alone can see it - the clipping is done by
       an ANCESTOR.
         Vision/Creativa layout.css:  #log-panel { height: 16px; overflow: hidden; }
       That box was correct for the donor's own 16px-tall inline links. `min-height:
       32px` above doubles the chip, so the bottom 16px of every chip - i.e. the
       bottom half of the label - was cut off by the parent.
       MEASURED @1400/1024/768 before the fix: Vision #log-panel 173x26 at y=30,
       chips 32px tall at y=40..72, overhang 16px past the container's bottom edge,
       with overflow:hidden. Creativa identical (container 16px tall, overhang 16px;
       48px at 768 where #v_toggle wraps).
       Give the container back a height that matches what it now holds. MEASURED
       AFTER: overhang 0 on both, and the chips do not move - Vision cy 56 before
       and after, Creativa cy 88 before and after; header height (Vision 90,
       Creativa 128), document height and horizontal overflow all unchanged.
       VERIFIED a no-op on the other eight: an ancestor-clipping sweep over all ten
       at all three widths found a clipping ancestor on Vision and Creativa ONLY. */
    #log-panel,
    #log-panel-buttons-handler {
        height: auto !important;
        min-height: 32px !important;
        overflow: visible !important;
    }

    /* (a-v) ...and, on the two donors whose chips are FLOATS, it must not wrap
       them. `#log-panel` is a float whose shrink-to-fit width is its children's
       max-content width, so once (a-iii) restores the donor's 20px glyph
       reservation the two chips need 173px in a box that resolves to 172 - one
       sub-pixel over - and the second chip drops to a new line.
       MEASURED on Creativa @768 after (a-iii)/(a-iv): #v_toggle cy=120 against
       #v_register cy=88, i.e. a 32px midline spread on a template Tim has already
       accepted. Not visible at 1024/1400, where the box is wider.
       `:has(> a)` is a STRUCTURAL discriminator, not a guess: `#log-panel` exists
       on eight of the ten, in two different shapes -
         Vision, Creativa            <div id="log-panel"><a>...</a>   (float:right)
         Acceler8, Crystal, Encore,   <ul id="log-panel"><li><a>...   (float:none)
         Energize, Oxygen, Progressive
       and only the first shape has direct <a> children. This matters: a blanket
       flex rule on `#log-panel` was MEASURED to REVERSE the visual order of Login
       and Register on all five li-based donors (their DOM order is their visual
       order, whereas the float:right pair reads back-to-front) - so `row-reverse`,
       which is what the float pair needs, is exactly wrong for them.
       MEASURED AFTER on the two that match: Creativa @768 both chips cy=88, order
       preserved (#v_toggle left of #v_register), horizontal overflow 0; Vision
       byte-identical at all three widths (#v_toggle x=561, #v_register x=655,
       cy=56). */
    #log-panel:has(> a) {
        display: flex !important;
        flex-direction: row-reverse !important;
        flex-wrap: nowrap !important;
        align-items: center !important;
        justify-content: flex-start !important;
    }
    /* The cart's wrapper `.show-cart` starts at y=0 and is 56px tall, while
       every other icon's parent starts at y=20 -- so the cart sat 4px below the
       shared midline (cy 40 vs 36). Give the wrapper the same 20px top offset
       the donor puts on `.main-menu-handler` and the cart lands on the line.  */
    /* First attempt added `padding-top: 20px` to match the donor's header
       padding. Wrong: the anchor already carried a 24px offset inside this
       wrapper, so the padding compounded it and pushed the cart to cy=60 -
       further off than the 40 it started at.
       The wrapper starts at y=0 and the shared midline is y=36, so a wrapper of
       exactly 2x36 with its content centred puts the cart on the line without
       depending on whatever offset the donor gave the anchor. */
    #vmCartModule .show-cart {
        display: flex !important;
        align-items: center !important;
        justify-content: flex-end !important;
        /* 2026-08-17 (Impulsa): 72px was Coga's number (2 x its 36px midline) and
           it is wrong everywhere else. On Impulsa the wrapper top is y=100 and the
           sibling icons midline is y=116, so the correct box is 32px; 72px put the
           cart at cy=136 - 20px below its five siblings - and, because #cl-handler
           is a stretched flex item, dragged the whole header row from 42px to 72px.
           cs-mobile-generic.js now MEASURES the donor's own midline and sets
           --cs-hdr-cart-box. The 72px default keeps Coga byte-identical if the
           measurement never runs. */
        height: var(--cs-hdr-cart-box, 72px) !important;
        padding: 0 !important;
        box-sizing: border-box !important;
    }
    #vmCartModule .show-cart > a { margin: 0 !important; top: 0 !important; }
    .open-off-canvas > i,
    .open-register-form > i,
    .v_register > i,
    #v_register > i,
    #v_toggle > i,
    #searchOpenButton > i,
    #cartpanel.cart-button > i {
        font-size: 16px !important;
        line-height: 1 !important;
        width: auto !important;
        height: auto !important;
        margin: 0 !important;
        vertical-align: middle !important;
    }

    /* 2026-08-18 (Impulsa, Tim): the four controls DID all measure 32x32 at
       cy=116 - and the row was still visibly wrong, because the ORANGE BAR's
       own midline is cy=121. The cluster sat 5px high: the cart's 32px white
       disc started at y=100, exactly the bar's top edge, with 10px of orange
       below it and none above, which is what reads as "the cart is lower and a
       different size" beside three bare 16px glyphs. The live donor puts every
       control on cy=121 in a bar of the same y=100 h=42.
       CAUSE is this file's own `.row:not(.flex-stretch):not([class*="align-items-"])
       { align-items: flex-start }` (2026-08-11, Evolution). The header bar's row
       is 42px tall (its search column sets that); the utility column shrink-wraps
       to 32px; flex-start pins it to the top. That patch has to stay - it is what
       stops donor `height:100%` module chrome ballooning to the row height - so
       opt this ONE column out of it rather than weakening it.
       MEASURED on Impulsa @1400 before: bar .ms-handler y=100 h=42 cy=121;
       #cl-handler y=100 h=32 cy=116; hamburger / account / register / cart all
       cy=116. After: #cl-handler y=105 h=32 cy=121, all four controls cy=121.
       align-self:center can only move a column that is SHORTER than its row, and
       a short header utility column belongs on the bar's midline, so this is a
       no-op on donors whose column already fills the row. */
    .row > #cl-handler { align-self: center !important; }
    /* 2026-08-18 (Oxygen): correcting the selectors above still left the cluster
       off the bar's midline, because the donor puts a computed `padding-top:13px`
       (topheight/2 - 22) on its utility container under TWO ids, which this file
       never neutralised. MEASURED @1400 before: header cy=55 but the controls sat
       at cy 62.5 / 61.5 / 55 - spread 7.5. Three ids out-specify the donor.
       AFTER: all three controls cy=55 = header cy=55, spread 0.0 at 1400 / 1024 /
       768. Inert on donors that do not carry #top-quick-nav inside #top (Impulsa,
       MubleStore, Creativa, Vision, Progressive: no match at all). */
    #top-handler #top #top-quick-nav {
        padding-top: 0 !important;
        display: flex !important;
        align-items: center !important;
        height: 100% !important;
    }
    /* 2026-08-18 (Crystal, caught verifying the above end-to-end): once (a-ii)
       lets a text control size to its label, the controls get wider - and on
       donors that hang them off a `ul` inside the utility container, that `ul`
       is the real row. Crystal's is `#top-quick-nav > ul#log-panel`, still an
       inline-block row, so the two wider items no longer fitted and the second
       one WRAPPED: MEASURED @1400 with (a-ii) but without this rule, li y=169.8
       and y=201.8, container 64px tall, midline spread 32.0 - worse than the 0.0
       it started at. Flexing the row with nowrap is what actually holds them on
       one line; `flex-wrap: nowrap` on the CONTAINER does nothing here because
       the container's only flex item is the `ul` itself.
       AFTER: spread 0.0 on Crystal at 1400 / 1024 / 768, and no change anywhere
       else - Oxygen, Energize, Progressive, Acceler8, Impulsa and MubleStore all
       stay at the 0.0 they already had. */
    #top-quick-nav > ul,
    #top-quick-nav ul.log-panel,
    #top-quick-nav > ul#log-panel {
        display: flex !important;
        flex-wrap: nowrap !important;
        align-items: center !important;
    }
    #top-quick-nav > ul > li {
        white-space: nowrap !important;
        float: none !important;
    }
    /* Same trait under the family's other column names. Kept in a SEPARATE rule
       because an unsupported :has() invalidates the whole selector list it sits
       in, which would take the #cl-handler rule above down with it. */
    .row > [class*="col-"]:has(> .cl-handler),
    .row > [class*="col-"]:has(> #tpr-nav),
    .row > [class*="col-"]:has(> #vmCartModule),
    .row-fluid > [class*="span"]:has(> #vmCartModule) {
        align-self: center !important;
    }

    /* (b) */
    .camera_caption .on-right,
    .cameraContent .on-right,
    .camera_wrap .on-right {
        width: 50% !important;
        margin-left: auto !important;
        margin-right: 0 !important;
        text-align: right !important;
        box-sizing: border-box !important;
        padding-right: 3% !important;
    }
    .camera_caption .on-right .container,
    .camera_wrap .on-right .container { width: 100% !important; max-width: 100% !important; }
}


/* =====================================================================
   CS-PATCH: cart-panel-and-breadcrumbs  2026-08-13  (Tim review of Coga)

   (a) CART PANEL. The real element is `#cart-panel2.panel1` -- NOT the
       `#vmCartModule .panel2` that Aeon uses. Same product, two different
       cart markups; assuming one from the other is why this went unfound.
       Measured: `position:absolute; width:280px; top:100px`, positioned by a
       hardcoded `left`:

         @375   left:95px    -> x=95, right edge exactly 375, hanging off screen
         @1400  left:-212px  -> x=1005, 280px wide, jammed to the right

       A cart summary is a viewport-level overlay, not something anchored to the
       button's box. Fix it to the viewport and centre it, bigger on desktop
       where there is room for the line items to breathe.

   (b) BREADCRUMBS. The list is `ul.breadcrumb1` -- again not the `.breadcrumb`
       every earlier rule targeted, which is why they kept rendering untouched.
       `display:block`, `list-style:disc`, `padding-left:32px`, so each crumb
       took its own line with a bullet beside it. Measured 4 crumbs on 4 rows.
       Selector below covers `breadcrumb`, `breadcrumb1` and any other suffix.
   ===================================================================== */

/* ---- (a) cart panel ---- */
#cart-panel2.panel1,
#vmCartModule .panel1 {
    position: fixed !important;
    left: 50% !important;
    right: auto !important;
    transform: translateX(-50%) !important;
    margin: 0 !important;
    z-index: 1060 !important;
    max-height: 80vh !important;
    overflow-y: auto !important;
    box-shadow: 0 8px 32px rgba(0,0,0,.28) !important;
}
/* template.css carries `#vmCartModule div.panel1 { width:280px !important }` -
   specificity (1,1,1) with !important, which outranks a (1,1,0) selector no
   matter how many !importants it has. Match the donor's own shape and add the
   id to clear it. */
@media (max-width: 767px) {
    #vmCartModule div#cart-panel2.panel1,
    #vmCartModule div.panel1,
    div#cart-panel2.panel1 {
        top: var(--cs-drawer-top, 110px) !important;
        width: calc(100vw - 24px) !important;
        max-width: 360px !important;
    }
}
@media (min-width: 768px) {
    #vmCartModule div#cart-panel2.panel1,
    #vmCartModule div.panel1,
    div#cart-panel2.panel1 {
        top: 12vh !important;
        width: 520px !important;
        max-width: calc(100vw - 40px) !important;
    }
}
#cart-panel2 .cartpanel,
#vmCartModule .panel1 .cartpanel { width: 100% !important; box-sizing: border-box !important; }

/* ---- (b) breadcrumbs on one flowing line, no bullets ---- */
ul[class^="breadcrumb"],
ol[class^="breadcrumb"],
.breadcrumb, .breadcrumb1,
.mod-breadcrumbs__wrapper ul {
    display: flex !important;
    flex-wrap: wrap !important;
    align-items: center !important;
    gap: 4px 6px !important;
    list-style: none !important;
    padding-left: 0 !important;
    margin: 0 0 10px !important;
}
ul[class^="breadcrumb"] > li,
ol[class^="breadcrumb"] > li,
.breadcrumb > li, .breadcrumb1 > li,
.mod-breadcrumbs__wrapper li {
    display: inline-flex !important;
    align-items: center !important;
    list-style: none !important;
    float: none !important;
    width: auto !important;
    white-space: nowrap !important;
}
ul[class^="breadcrumb"] > li::marker,
.breadcrumb1 > li::marker,
.breadcrumb > li::marker { content: "" !important; }

/* =====================================================================
   CS-PATCH: product-ul-tabpane-not-flex  2026-08-17  (found on Impulsa)

   `.row-fluid { display: flex }` at the top of this file is the BS2 grid
   mirror, and it is right for grid rows. But this donor family also puts
   `row-fluid` on a **product UL that is a TAB-PANE CONTAINER**:

       <ul class="vmproduct product-details row-fluid tft-modules">
         <div class="tf-module">…Best Deals pane…</div>
         <div class="tf-module">…Hot Offer pane…</div>   <- one visible at a time
         …

   Under BS2 that UL was `display:block`, so the panes stacked in one place
   and the tab script showed one. Made a flex container, every pane becomes a
   flex ITEM and they all render at once, one under another.

   MEASURED on Impulsa @1400, home page:
       live   ul display:block  — 4 panes all at top 747, width 1162 (stacked)
       stage  ul display:flex   — panes at top 614 / 696 / 779 / 862, 599px wide
   i.e. all four tab panes visible simultaneously as a long single-column list
   of products with stray list bullets. Page height 4668 vs live 3887.

   Scoped to `ul[class*="product"]`, which is the SAME carve-out vocabulary the
   span-width rules above already use (`:where(:not(ul[class*="product"] > *))`) — this
   file has always treated product ULs as "not our grid to lay out". Block also
   restores BS2's float model for any `li.spanN` product grid inside, which flex
   would have ignored.
   ===================================================================== */
ul[class*="product"].row-fluid,
ul[class*="product"].row {
    display: block;
}

/* =====================================================================
   CS-PATCH: product-li-not-list-item  2026-08-17  (found on Impulsa)

   VirtueMart product tiles are <li class="product-container spanN floatleft">.
   Under BS2 the grid rule `[class*="span"] { display: block; float: left }`
   made them block, so no list marker ever rendered. This bridge sets WIDTH on
   spans but never display, so the li keeps its default `display: list-item`
   and paints a stray bullet beside every product tile.

   MEASURED on Impulsa @1400 home: 20 product tiles, each rendering a disc
   marker to the left of the thumbnail. Live computes display:block on the same
   element; stage computed display:list-item.

   Scoped to product ULs so ordinary content lists keep their bullets.
   ===================================================================== */
ul[class*="product"] li[class*="span"],
ul[class*="product"] li.product-container {
    display: block;
}

/* =====================================================================
   CS-PATCH: grad-walkthrough-standards  2026-08-17
   PROMOTED FROM Intense's per-template block after Tim reported the identical
   defect list on Impulsa. Each item below is a thing Tim has asked for twice.
   Treat this block as the mobile/desktop chrome STANDARD for the VMT donor set.

   Checklist form (also in Skill 33) — for every template, verify:
     1 no #mobile-menu-toggle rendering at >=768
     2 no duplicate account/register icons in the black top bar on phones
     3 cart lives in the utility row on phones, not the header
     4 logo row is ONE row: logo left, primary burger right, on the same line
     5 no dead whitespace band above the mobile header
     6 footer menu links visible on phones
     7 footer social icons centred on phones
     8 back-to-top is a circle with its glyphs centred
     9 breadcrumb band reserves no empty space on phones
    10 login/registration fields full width on phones
   ===================================================================== */

/* ---- 1. the retired J3-era mobile toggle must never paint on desktop ----
   Templates that still load an older mobile-accordion script inject
   #mobile-menu-toggle unconditionally; the 2026-08-11 generic chrome
   (cs-mobile-generic.js) self-gates <=767 and is the only one that should
   render. Retire the script where present; this is the belt-and-braces. */
#mobile-menu-toggle { display: none !important; }
@media (max-width: 767px) {
  /* if a template genuinely has no cs-mobile-generic chrome, let it back in */
  body:not(:has(#cs-menu-toggle)) #mobile-menu-toggle { display: inline-block !important; }
}

/* ---- 8. back-to-top: circle, glyphs centred (was a 48x40 oval with the
   arrow and label pushed low by padding-top) ---- */
.jump-to-top.visible { height: 48px; }
.jump-to-top a {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  width: 48px; height: 48px; padding: 0; line-height: 1;
}
.jump-to-top a br { display: none; }
.jump-to-top a span { line-height: 1; margin-top: 2px; }

@media (max-width: 767px) {
  /* ---- 2. the black top bar's account/register duplicate the utility row.
     (#LoginForm is a body child, so hiding the panel cannot strand the modal.) */
  /* 2026-08-18 (Progressive): `.log-panel` as a CLASS inside `#top` matched ZERO
     nodes on ET donors, whose chain is `HEADER#top-handler > DIV#top-nav-line >
     DIV > UL#log-panel` - an ID, and not a descendant of #top. So the donor's own
     "MY ACCOUNT / REGISTER" text row kept painting directly under the utility
     row's account+register icons. MEASURED on Progressive @375 before: account 2,
     register 2 (utility-row icons at y=142 plus the donor's text row at y=197).
     AFTER: account 1, register 1. Creativa carries the same `<div id="log-panel">`
     shape and was harmless only by luck. Covering both forms costs nothing on the
     VMT donors, whose UL.log-panel is already hidden with its #cl-handler column. */
  #top .log-panel, .top .log-panel,
  #log-panel, #top-handler #log-panel, #top-handler .log-panel { display: none !important; }

  /* ---- 5 + 4. one logo row: the theme-painted site-name bar spans the
     viewport and the burger sits on it. The donor's phone rule is
     `#top-handler #site-name-handler {width:270px}` (two ids) so this must
     match that specificity.

     2026-08-18 (Acceler8): the old form hard-coded `width: calc(100% + 40px)` with
     -20px side margins because "the container insets 20px per side" - true on the
     VMT donors, and false the moment it is not. On Acceler8 `#top-handler` is
     `position:absolute` and escapes the body padding, so the containing block is
     ALREADY full width and the rule bled a further 20px each side. MEASURED
     document scrollWidth before: 395 / 434 / 380 against viewports 375 / 414 / 360.
     `calc(50% - 50vw)` is self-correcting: it resolves to exactly the old -20px
     when the container is inset 20px (Impulsa @375: parent 335 wide, 335/2 - 375/2
     = -20) and to 0 when the container is already full width. VERIFIED bit-for-bit
     unchanged on Impulsa and MubleStore at 375 / 414 / 360 (brand 375/414/360 at
     x=0, exactly as before); Acceler8 overflow goes to zero at all three. */
  #top-handler #site-name-handler, #site-name-handler {
    display: flex !important; align-items: center !important;
    box-sizing: border-box !important; min-height: 64px; height: auto !important;
    width: 100vw !important; max-width: 100vw !important;
    margin-left: calc(50% - 50vw) !important; margin-right: calc(50% - 50vw) !important;
    padding-left: 30px !important; padding-right: 70px !important;
  }
  .row.tft-modules, .row-fluid.tft-modules { position: relative !important; }
  /* --cs-burger-top is measured from the LOGO by cs-mobile-generic.js's
     burgerMidline(). MEASURED on Impulsa @375 before: this .row is a BS5
     `display:flex; flex-wrap:wrap` row and the donor's nav column wrapped
     onto a second flex line, so the row ran 126px tall (#site-name-handler
     66px + #st-navigation 60px). 50% of THAT centred the burger at y=67
     while the logo's midline was y=37 - 30px apart, visibly two planes.
     The fallback is the old 50%, so donors whose row IS the logo row are
     bit-for-bit unchanged. */
  #cs-menu-toggle { top: var(--cs-burger-top, 50%) !important; bottom: auto !important; transform: translateY(-50%) !important; }
  /* Ink is measured against the donor's own bar by cs-mobile-generic.js's
     burgerInk() - a fixed #fff was right for Intense's dark bar and rendered the
     burger invisible on Impulsa's white one (MEASURED @375: bars #fff, bar #fff).
     The fallbacks reproduce the old hard-coded values exactly. */
  #cs-menu-toggle .cs-burger-bars span { background: var(--cs-burger-ink, #fff) !important; }
  #cs-menu-toggle { border-color: var(--cs-burger-edge, rgba(255,255,255,.65)) !important; }

  /* ---- 3. the donor's desktop nav column owns menu + cart + search; the
     chrome owns all three on phones. Hiding it also removes the stray cart
     block that sat on top of the burger. */
  .row.tft-modules > .st-nav-span, .row-fluid.tft-modules > .st-nav-span,
  .row.tft-modules > .megamenu-col, .row-fluid.tft-modules > .megamenu-col { display: none !important; }
  /* Same family of column under a third donor name: Impulsa's `#cl-handler`
     (a col-md-3 holding #vmCartModule + #tpr-nav's log-panel + .open-off-canvas).
     MEASURED @375 before: a second orange icon bar at y=446 duplicating the
     utility row account / register icons. The cart is moved into the utility
     row by cs-mobile-generic.js adoptCart() BEFORE this hides its old home,
     so hiding the column does not lose the cart. `#top .log-panel` above cannot
     reach it - #cl-handler is a sibling of #top, not a descendant. */
  #cl-handler { display: none !important; }

  /* Same family of column under a FOURTH donor shape: Impulsa's `#st-navigation`
     (a col-md-6 whose ENTIRE payload is the donor's `nav#menu`, i.e. the
     position-1 main menu). The chrome's drawer is built from that very menu, so
     on phones the column is pure empty space: MEASURED on Impulsa @375 before,
     #st-navigation was x=20 y=70 320x60 with `display:block` but its only child
     `nav#menu` already computed `display:none` and the column had ZERO visible
     descendants. Because the parent `.row` is BS5 flex-wrap, that empty 60px box
     still claimed a second flex line and ran the logo row to 126px (logo 66px on
     line 1). After: row 66px, #top 134px -> 74px, and because the row IS now the
     logo row cs-mobile-generic.js's burgerMidline() drops --cs-burger-top and the
     CSS 50% fallback is once again correct (burger/logo midlines both y=37).

     Scoped deliberately. `#st-navigation` is a shared donor-family id present in
     33 ET templates, and in most of them it is the column that owns quick-menu /
     search / cart / log-panel / megamenu - blanking it would gut signed-off
     templates. `:has(> nav#menu:only-child)` fires ONLY where the column holds
     nothing but the main nav, which across the whole fleet is Impulsa alone.
     Intense and Theon also carry a `nav#menu` here but pair it with search+cart,
     so they keep their existing `.st-nav-span` handling untouched.
     The hide is CSS-only, so the node survives in the DOM and the drawer's
     cloneNode() source is unaffected (VERIFIED: drawer still 6 items). */
  #st-navigation:has(> nav#menu:only-child) { display: none !important; }

  /* ---- 6. footer menu links were caught by the mobile nav-hide and vanished */
  #bottom-bg ul.menu, #footer ul.menu { display: block !important; height: auto !important; }
  #bottom-bg ul.menu li, #footer ul.menu li { display: list-item !important; }

  /* ---- 7. footer social icons centred */
  #bottom-bg .custom p:has(> a > i[class*="fa-"]),
  #footer .custom p:has(> a > i[class*="fa-"]) { text-align: center !important; }
  #bottom-bg #socialModal, #footer #socialModal { display: block !important; float: none !important;
    width: 100% !important; text-align: center !important; }
  #bottom-bg ul#social-links, #footer ul#social-links { display: inline-flex !important;
    float: none !important; justify-content: center !important; margin: 0 auto !important; padding: 0 !important; }

  /* ---- slideshow caption: drop the donor's percentage top-offset on phones.
     Camera-era donors offset the caption's content block by a percentage of the
     hero (Impulsa: `[class*="fade"] > div > div { top: 36% }` from the
     cam_captionTop2 param). Under camera that class was TRANSITIONAL - it only
     existed while a fade effect was running, so most of the time the caption sat
     at the top and fitted. Phase 5a's Swiper stamps `swiper-fade` / `fadeIn`
     PERMANENTLY, so the offset became constant.
     MEASURED on Impulsa @375: hero 201..401 (the cam_minHeight floor), caption
     `.container` computed top:72px (36% of 200) and the SHOP NOW button landed at
     y=437 - 36px past the container's overflow:hidden edge, so the CTA was gone.
     Live, caught mid-cycle without a fade class, had top:0 and the button inside
     the hero at y=257 in a 110..310 hero. Reproduce live's normal state.
     (0,3,0) out-specifies the donor's (0,1,2); desktop is untouched, where the
     hero is tall enough for the offset to be the intended design.) */
  .camera_wrap .camera_caption > div > .container,
  .camera_wrap .camera_caption > div > div,
  .swiper .camera_caption > div > div { top: 0; }

  /* ---- 9. breadcrumb band reserved 86px of nothing */
  #breadcrumb-line.background-photo, #breadcrumb-line { padding-bottom: 0 !important; }

  /* ---- the template's own spacing is enough on phones ---- */
  #content-handler { padding: 0 !important; }

  /* ---- 10. login / registration / account fields full width ---- */
  .userdata .width30, .com-form-login .width30, .com-form-registration .width30 { width: 100% !important; float: none !important; }
  .userdata input.inputbox, .com-form-login input.inputbox, .com-form-registration input.inputbox,
  #LoginForm input[type="text"], #LoginForm input[type="password"], #LoginForm input[type="email"] {
    width: 100% !important; max-width: 100% !important; box-sizing: border-box !important;
  }

  /* ---- drawer rhythm + a single search submit in the chrome panel ---- */
  #cs-drawer .cs-drawer-menu a, #cs-drawer .cs-drawer-menu span { padding: 7px 14px !important; }
  #cs-search-panel input[type="image"] { display: none !important; }

  /* ---- tabbed-module nav (e.g. Best Deals / Hot Offer / New Items): all tabs
     on ONE line, centred (Tim, Impulsa 2026-08-17).
     The first attempt scoped this to `.nav-tabs-handler`, a wrapper Impulsa does
     not have, so nothing matched. MEASURED on Impulsa @375 before: the floated
     `li.tabs-main-title` rendered at x=-60 (outside the 20px container inset) and
     the three tabs ran 74 -> 354 in a 320px pane, i.e. "Great Sale" overflowed.
     Unfloat the module title onto its own centred row and let the real tabs share
     one centred line: 88 + 97 + 95 = 280px fits 320px with room to spare. */
  ul.nav-tabs {
    display: block !important; text-align: center !important;
    padding-left: 0 !important; padding-right: 0 !important;
  }
  /* The donor pins the title with `#tabs-1 .nav-tabs > li.tabs-main-title
     { float: left !important }` - (1,2,2)+!important, which a plain
     `ul.nav-tabs > li.tabs-main-title` (0,2,2) cannot reach however many
     !importants it carries. MEASURED after the first attempt: the title still
     computed float:left, and because our `width:auto` had beaten the donor's own
     `@media(max-width:767px) .tabs-main-title{width:100%}`, it shrink-wrapped to
     134px and left a 186px channel beside it - so the three tabs stacked on
     THREE lines (y=1095 / 1162 / 1229) instead of one.
     Match the donor's specificity and keep its 100% width: a full-width,
     unfloated title takes its own row, exactly as the live donor does, and the
     tabs then centre on one line (88 + 97 + 95 = 280px in a 320px pane). */
  #tabs-1 .nav-tabs > li.tabs-main-title,
  ul.nav-tabs > li.tabs-main-title {
    float: none !important; display: block !important;
    width: 100% !important; margin: 0 !important; text-align: center !important;
  }
  ul.nav-tabs > li {
    float: none !important; display: inline-block !important;
    vertical-align: middle !important;
  }

  /* 2026-08-18 (MubleStore, Tim): the rules above CENTRE the tabs but assume they
     already FIT - the Impulsa note reasons from "88 + 97 + 95 = 280px fits 320px".
     When the donor's tabs are wider than the pane they still wrap, and centring a
     wrapped row just centres two lines.
     MEASURED on MubleStore @375 before: pane 320px; three tabs 129 + 124 + 129 =
     382.5px plus 3 x 4px margins = 394.5px needed, so "GREAT SALE" dropped to a
     second line at y=2633 while the other two sat at y=2595.
     Fix: on a tab list that has NO `li.tabs-main-title`, lay the tabs out as a
     nowrap flex row that is allowed to SHRINK, and let the labels wrap their own
     text instead of the row wrapping. Shrink only engages when the natural widths
     overflow, so a list that already fits is byte-identical to before.
     Kept OFF the `li.tabs-main-title` shape on purpose: that layout needs the
     block/`width:100%` title row above, which a flex parent would destroy.
     In its OWN rule block - an unsupported :has() invalidates the whole selector
     list it sits in, and must not be able to take the plain rules above down
     with it (same reason as the #cl-handler split). */
  ul.nav-tabs:not(:has(> li.tabs-main-title)) {
    display: flex !important;
    flex-wrap: nowrap !important;
    justify-content: center !important;
    align-items: stretch !important;
  }
  /* No `gap` and no margin reset on purpose: the donor's own inter-tab margin is
     the correct spacing, and a `gap` here would ADD to it. MEASURED on MubleStore
     @375 with `gap:4px` + `margin:0`: the donor pins its margin with
     `#tabs-1 .nav-tabs > li` (1,1,1), which outranks this rule's (0,2,2) however
     many !importants it carries, so the 4px survived and stacked with the gap to
     8px - leaving the row 4px off centre (leftGap 0 / rightGap 4). */
  ul.nav-tabs:not(:has(> li.tabs-main-title)) > li {
    flex: 0 1 auto !important;
    min-width: 0 !important;
  }
  /* The nowrap row can only shrink if the label is allowed to wrap; the generic
     `white-space: nowrap` at the top of this file (tabs-nav-handler) would
     otherwise hold each tab at its max-content width and re-overflow the pane. */
  ul.nav-tabs:not(:has(> li.tabs-main-title)) > li > a,
  ul.nav-tabs:not(:has(> li.tabs-main-title)) > li > .tab_heading,
  ul.nav-tabs:not(:has(> li.tabs-main-title)) > li > a > .tab_heading {
    white-space: normal !important;
    overflow-wrap: break-word !important;
    padding-left: 10px !important;
    padding-right: 10px !important;
  }
}


/* =====================================================================
   CS-PATCH: bs2-nav-clearfix  2026-08-17  (found on Impulsa @1400)

   BS2's bootstrap.css contained a micro-clearfix on `.nav`:

       .nav:before, .nav:after { display: table; line-height: 0; content: ""; }
       .nav:after { clear: both; }

   BS5 dropped it. Without it a FLOATED child of a nav can hang BELOW the nav's
   own content box, and the next float row in the document then snags on it.

   MEASURED on Impulsa's home page @1400, "Best Deals" tabbed module:
       ul.nav.nav-tabs        y=515  h=67   (its inline-block tabs set the line)
       li.tabs-main-title     y=515  h=77   float:left  -> hangs to y=592
       first product row      y=582            <- INSIDE the float's band
   so the first product tile was pushed right to x=241 and only 3 of 4 fitted on
   row 1, giving Tim's 3 / 4 / 1 grid. Live (BS2, clearfix present) measured the
   nav at h=68, products at y=747 and a clean 4 x 291.5 = 1166px row.

   NOT a width bug - the tiles measured 291.5px on both sides. Restoring the
   clearfix makes the nav contain its own float, so the product floats start on
   a fresh line at x=110.

   Scoped to the BS2 nav variants rather than bare `.nav`: BS5's `.nav` is a flex
   container, where `clear` is inert and the pseudo-elements would only add two
   empty flex items.
   ===================================================================== */
.nav-tabs::before, .nav-tabs::after,
.nav-pills::before, .nav-pills::after,
.nav-list::before, .nav-list::after {
    display: table;
    line-height: 0;
    content: "";
}
.nav-tabs::after,
.nav-pills::after,
.nav-list::after { clear: both; }


/* =====================================================================
   CS-PATCH: legacy-inputbox-content-box  2026-08-17  (found on Impulsa @1400)

   BS5's reboot ships `*, ::before, ::after { box-sizing: border-box }`. BS2 did
   NOT apply it to text inputs, so every donor that sized a search field as
   `height: <content>` + `padding` now loses the padding out of the declared
   height and the field - and the row it sets - collapses.

   MEASURED on Impulsa's header search @1400:
       live   input.inputbox  height:22px  padding:6px 12px  -> box 34px, row 42px
       stage  input.inputbox  height:22px  padding:6px 12px  -> box 22px, row 30px
   The submit control is `position:absolute` at y=104 and 34px tall, so it ran to
   y=138 inside a row that now ended at y=130: Tim's "Search for products button
   is cut off - the row is not tall enough".

   `.inputbox` is the Joomla 1.5/2.5-era form class; it only ever appears on
   donor-authored fields, which is exactly the set that was authored content-box.
   Specificity (0,1,1) beats the `*` reboot without !important.
   ===================================================================== */
input.inputbox,
input[type="text"].inputbox,
input[type="search"].inputbox {
    box-sizing: content-box;
}


/* =====================================================================
   CS-PATCH: swiper-fade-classname-collision  2026-08-17  (found on Impulsa)

   Phase 5a swaps camera.js for Swiper 11 with `effect: 'fade'`. Swiper then
   stamps the class `swiper-fade` on the CONTAINER. Camera-era donors style
   their slide captions through attribute substring selectors on camera's own
   `fadeIn` / `fadeOut` caption classes, e.g. Impulsa ships (in index.php's
   inline <style>, from the cam_captionTop2 param):

       [class*="fade"] > div > div { top: 36%; }

   Under camera that read ".camera_caption.fadeIn > div > div" - the caption's
   inner text block, offset down the hero. Under Swiper the SAME selector now
   also reads ".swiper-fade > .swiper-wrapper > .swiper-slide", so every slide
   is shoved down 36% of the hero height.

   MEASURED on Impulsa @1400 after the hero was restored to its live 364px:
       .swiper-wrapper   y=142  h=364
       .swiper-slide     y=273  h=364   computed top: 131.125px  (= 36% of 364)
   i.e. a 131px white band above the slide and the caption's SHOP NOW button
   clipped off the bottom by the container's overflow:hidden. At the old flat
   200px hero the same bug showed as a quieter 72px band.

   Swiper positions slides with transforms, never with box offsets, so pinning
   the offsets to auto is safe for every effect. (0,3,0) out-specifies the
   donor's (0,1,2) regardless of source order, so no !important is needed and
   the donor's own caption offset - which IS wanted - is untouched.
   ===================================================================== */
.swiper > .swiper-wrapper > .swiper-slide {
    top: auto;
    right: auto;
    bottom: auto;
    left: auto;
}

/* =====================================================================
   CS-PATCH: product-ul-block-clearfix  2026-08-17  (found on VMBright)

   Companion to `product-ul-tabpane-not-flex` (.49) and
   `product-li-not-list-item` (.50). Those two correctly return a product UL
   and its tiles to BS2's block+float model. BS2 also shipped a clearfix on
   `.row-fluid`, and without it a block parent whose children are all floats
   takes no height from them at all.

   MEASURED on VMBright @1400 immediately after the .57 resync:
       live   0 collapsed float parents
       stage  ul.vmproduct.product-details.row-fluid  height 15px, needs 301px
              ul.sl-products.vmproduct…#slider108     height 15px, needs 305px
   The row's own background/border paints as a 15px sliver and whatever follows
   rides up into the product tiles.

   This is the BS2 micro-clearfix verbatim, scoped to exactly the selectors
   .49 switches to block. Same principle as .51: if we borrow a rule from BS2,
   borrow the machinery that made it work.
   ===================================================================== */
ul[class*="product"].row-fluid::before,
ul[class*="product"].row-fluid::after,
ul[class*="product"].row::before,
ul[class*="product"].row::after {
    display: table;
    content: "";
    line-height: 0;
}

ul[class*="product"].row-fluid::after,
ul[class*="product"].row::after {
    clear: both;
}


/* =====================================================================
   CS-PATCH: promoted-2026-08-18-rev63
   Blocks that were carried per-template but are donor-family GENERIC. Each was
   found by a graduation walkaround on one template and verified here against the
   VMT donors before promotion.
   ===================================================================== */

/* ---- camera slideshow arrows: touch-sized box, chevron drawn in CSS ----
   PROMOTED FROM Progressive (`cs-slideshow-arrows-size` 2026-07-30).
   canonical's `slideshow-arrows-touch` only lifts these arrows to opacity:1 under
   `hover:none`; it never sized them. The donor ships a 76x99 box whose chevron is a
   SPRITE on a 62x62 span pinned at "5px -1px", so it is both oversized and visibly
   off-centre. Camera is fleet-wide (this file already carries `.camera_pag li` and
   the `.camera_wrap` caption rules), and nothing here is Progressive-specific. */
@media (max-width: 767px) {
  .camera_wrap .camera_prev,
  .camera_wrap .camera_next {
    width: 44px !important; height: 44px !important;
    top: 50% !important; margin-top: -22px !important;
    display: flex !important; align-items: center !important; justify-content: center !important;
    border-radius: 4px;
    background-color: rgba(0,0,0,.35) !important;
  }
  .camera_wrap .camera_prev { left: 8px !important; right: auto !important; }
  .camera_wrap .camera_next { right: 8px !important; left: auto !important; }
  .camera_wrap .camera_prev > span,
  .camera_wrap .camera_next > span {
    width: 44px !important; height: 44px !important;
    background-image: none !important; background: none !important;
    display: flex !important; align-items: center !important; justify-content: center !important;
    margin: 0 !important; text-indent: 0 !important;
  }
  .camera_wrap .camera_prev > span::before,
  .camera_wrap .camera_next > span::before {
    content: ""; display: block; width: 11px; height: 11px;
    border-top: 3px solid #fff; border-right: 3px solid #fff;
  }
  .camera_wrap .camera_prev > span::before { transform: rotate(-135deg); margin-left: 3px; }
  .camera_wrap .camera_next > span::before { transform: rotate(45deg);  margin-right: 3px; }
}

/* ---- camera pagination: squares -> dots on a phone ----
   PROMOTED FROM Energize (`energize-slideshow-pag-dots`), renamed
   `camera-pag-dots-mobile`. Skill 33 Mobile UX item 6: no numeric index counters
   on a phone. Donor colours are untouched; only the shape changes. */
@media (max-width: 767px) {
  .camera_pag_ul li {
    width: 14px !important; height: 14px !important;
    border-radius: 50% !important; overflow: hidden;
  }
  .camera_pag_ul li > span { display: none !important; }
}

/* ---- Swiper slideshow sizing (Phase 5a cohort) ----
   PROMOTED FROM Crystal (R6-6). `.ph-swiper-slideshow` is emitted by the Phase 5a
   camera->Swiper conversion, which is fleet-wide; this file had ZERO references to
   it, and a Swiper slide has no intrinsic height, so without this the home
   slideshow collapsed (Crystal MEASURED 462px -> 231px with the caption clipped).
   The active-bullet colour was Crystal's hard-coded #ed8a00; it now follows the
   donor's own accent, with Crystal's value as the fallback. */
.ph-swiper-slideshow {
  aspect-ratio: 3 / 1; max-height: 480px; min-height: 280px;
  width: 100%; overflow: hidden; position: relative;
}
.ph-swiper-slideshow .swiper-slide.ph-slide {
  background-size: cover; background-position: center; background-repeat: no-repeat;
  min-height: 280px;
}
.ph-swiper-slideshow .swiper-button-prev,
.ph-swiper-slideshow .swiper-button-next {
  color: #ffffff; background: rgba(0,0,0,0.35);
  width: 44px; height: 44px; border-radius: 50%;
  top: 50%; transform: translateY(-50%); z-index: 5;
  transition: background 0.15s ease;
}
.ph-swiper-slideshow .swiper-button-prev { left: 12px; }
.ph-swiper-slideshow .swiper-button-next { right: 12px; }
.ph-swiper-slideshow .swiper-button-prev:hover,
.ph-swiper-slideshow .swiper-button-next:hover { background: rgba(0,0,0,0.6); }
.ph-swiper-slideshow .swiper-button-prev::after,
.ph-swiper-slideshow .swiper-button-next::after { font-size: 18px !important; font-weight: 700; }
.ph-swiper-slideshow .swiper-pagination-bullet { background: #ffffff; opacity: 0.6; width: 10px; height: 10px; }
.ph-swiper-slideshow .swiper-pagination-bullet-active { opacity: 1; background: var(--cs-accent, #ed8a00); }
.ph-swiper-slideshow .ph-slide-caption,
.ph-swiper-slideshow .camera_caption {
  position: absolute !important; left: 5% !important; right: 5% !important; top: 20% !important;
  max-width: 70%; z-index: 4; color: #ffffff;
}
@media (max-width: 767px) {
  .ph-swiper-slideshow { aspect-ratio: 4 / 3; min-height: 240px; }
  .ph-swiper-slideshow .swiper-button-prev,
  .ph-swiper-slideshow .swiper-button-next { background: rgba(0,0,0,0.5); }
}

/* ---- BS2 row-fluid child clamp ----
   PROMOTED FROM Energize (`rowfluid-child-clamp`, found on Xeno). This file
   already carries the whole `.row-fluid > [class*="span"]` grid but never the
   plain child clamp, so a flex child with intrinsic content (a slideshow holding a
   natural-size img) could grow to max-content. Mirrors BS5's own `.row > *`. */
.row-fluid > * { max-width: 100%; }

/* ---- phone text inset ----
   PROMOTED FROM Progressive (`cs-mobile-text-inset-20260811`). Tim: "a little bit
   of padding inside of left and right for text, such as the title of the module."
   Donors inset body copy but let HEADINGS run full-bleed, so at 390px the
   page-header H1 and custom-module headings sat 2px off both edges while the
   paragraphs beside them sat at 22px. This file had ZERO `.page-header` references.
   The inset goes on the headings and article wrappers, never on the module box.
   `box-sizing` is load-bearing: on a content-box donor a 12px inset would push the
   block 24px past the viewport and bring back horizontal scroll. */
@media (max-width: 767px) {
  .page-header, .category-desc, .item-content,
  .mod-custom > h1, .mod-custom > h2, .mod-custom > h3, .mod-custom > h4, .mod-custom > h5,
  .custom > h1, .custom > h2, .custom > h3, .custom > h4 {
    padding-left: 12px !important;
    padding-right: 12px !important;
    box-sizing: border-box !important;
  }
}

/* ---- mod_finder input-group ----
   PROMOTED FROM Progressive (`cs-finder-inputgroup`). With show_button ON, Joomla
   wraps field + submit in `div.mod-finder__search`, so flexing the FORM does
   nothing and the button dropped to a second line. This file previously touched
   only `.mod-finder__search .btn { margin: 0 }`, which does not lay the row out. */
#search-position .mod-finder__search,
#cs-search-panel .mod-finder__search {
  display: flex !important; align-items: center; gap: 6px; flex-wrap: nowrap;
}
#search-position .mod-finder__search > .awesomplete,
#cs-search-panel .mod-finder__search > .awesomplete { flex: 1 1 auto; min-width: 0; }
#search-position .mod-finder__search input[type="text"],
#cs-search-panel .mod-finder__search input[type="text"] { width: 100% !important; min-width: 0; box-sizing: border-box; }
#search-position .mod-finder__search .btn,
#cs-search-panel .mod-finder__search .btn { flex: 0 0 auto; white-space: nowrap; }
@media (max-width: 767px) {
  #cs-search-panel .mod-finder__search .btn { min-height: 44px; padding: 0 14px; }
}

/* ---- horizontal forms stack on a phone ----
   PROMOTED FROM Crystal (R4-2). grad-walkthrough-standards item 10 covers the
   VirtueMart/Joomla LOGIN surface (`.userdata .width30`, `input.inputbox`,
   `#LoginForm input`); it does not reach `form.form-horizontal`, which is what
   contact and most component forms render. Crystal MEASURED fields at 202px and
   293px inside a 336px column. Complementary to item 10, not a duplicate. */
@media (max-width: 767px) {
  form.form-validate.form-horizontal, form.form-validate { display: block !important; }
  form.form-horizontal .control-group, form .control-group {
    display: block !important; margin-bottom: 14px !important;
  }
  form.form-horizontal .control-label, form .control-label {
    display: block !important; width: 100% !important; float: none !important;
    text-align: left !important; margin: 0 0 6px 0 !important; padding: 0 !important;
  }
  form.form-horizontal .controls, form .controls {
    display: block !important; width: 100% !important;
    margin-left: 0 !important; padding: 0 !important;
  }
  form.form-horizontal .controls input[type="text"],
  form.form-horizontal .controls input[type="email"],
  form.form-horizontal .controls input[type="url"],
  form.form-horizontal .controls input[type="tel"],
  form.form-horizontal .controls input[type="number"],
  form.form-horizontal .controls input[type="password"],
  form.form-horizontal .controls textarea,
  form.form-horizontal .controls select,
  form .controls input[type="text"],
  form .controls input[type="email"],
  form .controls input[type="url"],
  form .controls input[type="tel"],
  form .controls input[type="number"],
  form .controls input[type="password"],
  form .controls textarea,
  form .controls select {
    width: 100% !important; max-width: 100% !important; box-sizing: border-box !important;
  }

  /* ---- tap targets (added rev .64) ----
     The stacking above makes the fields FULL WIDTH but leaves them at the
     donor's desktop HEIGHT. Measured on Crystal /contact at 375: inputs 31px
     high at 12px type - under touch guidance, and small enough that iOS zooms
     the viewport on focus. Crystal's original per-template block carried these
     three declarations; only the stacking half was promoted at .63, so they
     were dropped. Crystal after: inputs 40px/15px, submit 336x46.

     SUBMIT SELECTOR IS DELIBERATELY SCOPED to `form.form-horizontal` and
     `form.form-validate` rather than a bare `form button[type=submit]`.
     MEASURED REASON: MubleStore's VirtueMart cart carries
     `button[name=checkout]` (147x36) inside a CLASSLESS form, beside
     `input[type=submit]` coupon/quantity buttons at 76-81px that these rules
     cannot reach. The unscoped selector blew checkout to 320x46 and left the
     coupon buttons small - a full-width primary next to two tiny secondaries.
     Scoping to the two form classes this block already stacks keeps every
     Joomla component form (contact, registration, login, profile) and leaves
     the VirtueMart commerce surface bit-for-bit unchanged. VERIFIED no-op on
     MubleStore cart (checkout / Submit / setcoupon all unchanged) and on
     Impulsa (`input.addtocart-button` 99x30 unchanged on the product grid). */
  form .controls input[type="text"],
  form .controls input[type="email"],
  form .controls input[type="url"],
  form .controls input[type="tel"],
  form .controls input[type="number"],
  form .controls input[type="password"],
  form .controls textarea,
  form .controls select {
    min-height: 40px !important;
    padding: 8px 10px !important;
    font-size: 15px !important;
  }
  form .controls textarea { min-height: 120px !important; }
  form.form-horizontal button[type="submit"],
  form.form-validate button[type="submit"] {
    width: 100% !important;
    min-height: 46px !important;
    font-size: 16px !important;
  }
}

/* ---- sidebar-menu accordion (DESKTOP navigation affordance) ----
   PROMOTED FROM Oxygen, where it was carried as `sidebar-menu-accordion` CSS plus a
   per-template `js/cs-sidebar-acc.js` marked "PENDING PROMOTION TO CANONICAL".
   Tim, 2026-08-03: "That's great making it a side menu, but it still needs to be
   clicked on a header and opened up so you can see submenus ... we still want to be
   able to navigate different levels in desktop."
   The behaviour is NOT Oxygen-specific - it exists because this file's chrome JS is
   a MOBILE script and builds its accordion only inside the phone drawer, so nothing
   ever applied `.cs-sidebar-acc` to a sidebar menu MODULE at desktop width. The
   matching builder now lives in cs-mobile-generic.js, so the per-template shim can
   be deleted on resync. MEASURED on Oxygen /our-blog with the shim disabled: ul.menu
   2954px at 1400 and 4315px at 768, 90 visible links, 0 chips. With the promotion:
   182px / 163px, 4 visible top-level links, 20 chips. No-op on Impulsa and
   MubleStore (no `.cl-left`/`.cl-right` menu module - 0 nodes matched).
   Applies at EVERY width on purpose: the phone drawer is separate and untouched.
   The chip colour was Oxygen's hard-coded #298bcc, which is Oxygen's OWN accent
   (rgb(41,139,204)); it now follows --cs-accent with that value as the fallback, so
   Oxygen renders identically. */
.cs-sidebar-acc li { position: relative; }
.cs-sidebar-acc .cs-acc-chip {
  display: flex !important; align-items: center; justify-content: center;
  position: absolute; top: 0; right: 0; width: 34px; height: 34px;
  padding: 0; border: 0; background: transparent;
  color: var(--cs-accent, #298bcc); cursor: pointer; line-height: 1;
}
.cs-sidebar-acc .cs-acc-chip i { font-size: 13px; }
.cs-sidebar-acc .cs-acc-chip:hover { color: #1c1c1c; }
.cs-sidebar-acc li > ul { display: none; margin: 0; padding: 0; list-style: none; }
.cs-sidebar-acc li.cs-open > ul { display: block; }
.cs-sidebar-acc li.cs-has-sub > a { padding-right: 38px; }
.cs-sidebar-acc li li > a { padding-left: 18px; }
.cs-sidebar-acc li li li > a { padding-left: 34px; }
.cs-sidebar-acc li li { border-top: 1px solid rgba(0, 0, 0, .06); }

/* ---- admin dark-mode for our own custom form fields ----
   PROMOTED FROM Crystal (R6-5). `.cs-fullwidth-help` and `.cs-color-wrap` are OUR
   custom admin form fields, shipped with every modernized template, so styling them
   for one template only is per-template debt by definition. In Atum dark the default
   text goes dark-on-dark. Inert on the front end, where neither class ever appears.
   NOTE: kept here so it stops being lost at resync; whether the ADMIN loads this
   file is template-dependent and was NOT verified in this rev - see GAP 3. */
@media (prefers-color-scheme: dark) {
  .cs-fullwidth-help {
    background: rgba(255,255,255,0.06) !important;
    border-color: rgba(255,255,255,0.15) !important;
    border-left-color: #6db0dd !important;
    color: #dbe4ec !important;
  }
  .cs-fullwidth-help code { background: rgba(255,255,255,0.08) !important; }
  .cs-color-wrap input.cs-coloris {
    background: rgba(255,255,255,0.05);
    color: #e6edf3;
    border-color: rgba(255,255,255,0.15);
  }
}
/* Atum stamps data-color-scheme="dark" on <html> - the more reliable signal. */
html[data-color-scheme="dark"] .cs-fullwidth-help {
  background: rgba(255,255,255,0.06) !important;
  border-color: rgba(255,255,255,0.15) !important;
  border-left-color: #6db0dd !important;
  color: #dbe4ec !important;
}
html[data-color-scheme="dark"] .cs-fullwidth-help code {
  background: rgba(255,255,255,0.08) !important; color: #e6edf3 !important;
}
html[data-color-scheme="dark"] .cs-color-wrap input.cs-coloris {
  background: rgba(255,255,255,0.05) !important;
  color: #e6edf3 !important;
  border-color: rgba(255,255,255,0.15) !important;
}
/* /CS-PATCH: promoted-2026-08-18-rev63 */


/* =====================================================================
   CANONICAL GAPS - RECORDED 2026-08-18 (rev .63), DELIBERATELY NOT FIXED HERE
   Each is reproducible and measured. None has a fix in this rev; they are
   written down so the next rev starts from evidence rather than rediscovery.

   GAP 1 - Vision, horizontal overflow at 768.
     document.scrollWidth 998 against a 768 viewport. Cause is the donor's
     absolutely-positioned NESTED desktop dropdowns: `ul.columns-2` is 490px wide
     and the third-level `ul` is pinned at `left:245px`, so the menu paints far
     outside the viewport while still contributing scroll width. Not a phone
     defect - the phone chrome hides this nav - so it needs a desktop-side answer
     (clamp or contain the dropdown), which is a bigger change than this rev.

   GAP 2 - Energize, Smart Search input overflows at every width.
     The donor hard-widths the input to 220px inside a `col-md-2`, overflowing by
     57px at 1400 and 70px at 768. This file's `max-width:100%` blanket is scoped
     to `@media (max-width: 767px)`, so nothing caps it at desktop. The fix is a
     desktop-side width cap on the finder input, which wants its own verification
     pass across the fleet before it ships.

   GAP 4 - Energize carries ~417 lines of UNMARKED CSS appended after this
     file's content: an old BS2 grid restatement duplicating the .row-fluid /
     .spanN / hidden-phone rules already here, plus VirtueMart image /
     breadcrumb / megamenu fixes and a truncated orphan comment. It carries no
     CS-PATCH: marker, so blockdebt.py cannot see it and now reports Energize
     as "0 blocks not in canonical" - which OVERSTATES how clean that template
     is. Needs a read-and-sort pass in a follow-up rev: delete what duplicates
     this file, promote or mark what does not.

   GAP 5 - AUDIT BLIND SPOT, found while fixing the register chip at .64.
     blockdebt.py only ever reads THIS file. It is blind to any other
     template-local stylesheet the template's index.php loads AFTER the bridge -
     e.g. vision-fixups.css, creativa-fixups.css. Such a file can and did
     silently outrank canonical rules, and every audit run to date has been
     blind to it. The tool needs to parse index.php for post-bridge stylesheet
     links and report them alongside the bridge's own block inventory.

   GAP 3 - admin delivery of the promoted dark-mode block (above) is unverified.
     The block is correct and generic, but it only helps if the ADMIN actually
     loads this stylesheet. Confirm on one template, then either keep it here or
     move it to the admin-side asset.
   ===================================================================== */


/* =====================================================================
   CS-PATCH: utility-chip-specificity-floor  2026-08-18  (Tim review of Vision @375)

   THE BUG THIS CLOSES, and the second one hiding underneath it.

   (1) vision-fixups.css / creativa-fixups.css each carried
         #cs-utility-row > a, #cs-utility-row > button { background: #fff !important }
       at (0,1,1), loaded AFTER this file. That outranked
         #cs-register { background: var(--cs-accent) !important }   (0,1,0)
       and painted the register chip white while this file still applied white
       ink to it - MEASURED 1.00:1 at 375 on both templates, a blank chip. The
       accent landed at .63; before that the chip had no accent and the icon
       inherited #444, so the collision was latent and invisible.

   (2) Deleting that fixups declaration fixed the register chip and immediately
       exposed what it had ALSO been masking: donors ship a bare TYPE selector
       with !important for their button colour. Vision's style1.css:
         .button, button, a.button, ... { background: #a13500 !important }
       !important beats specificity, so that (0,0,1) rule beat this file's
       NON-important #cs-utility-row > button, > a { background:#fff } (0,1,1)
       and painted every <button> chip in the row with the donor accent under
       the donor's #444 ink - MEASURED 1.40:1 on account / search / social.
       One latent bug was hiding another.

   THE FIX IS A SPECIFICITY FLOOR, NOT A PER-TEMPLATE PATCH. Give the chip set
   an !important white floor at (0,1,1) so no donor TYPE selector can reach
   into the row, then lift the two chips this file deliberately accents to
   (0,2,0) via #cs-utility-row > #id so they still outrank that floor.

   WHY THIS IS FLEET-GENERIC, not a Vision fix wearing a canonical hat: the
   donor `button { background: <accent> !important }` idiom lives in the ET base
   stylesheet family (style1.css / style6.css), so any donor can carry it. It
   stayed invisible only because the two templates that DO carry it also
   happened to carry a fixups file that masked it.

   MEASURED, all at 375 on home:
     Vision   register 1.00:1 -> 6.94:1 accent; account/search/social 1.40:1 -> 9.74:1 white
     Creativa register 1.00:1 -> 3.39:1 accent; other four unchanged 9.74:1 white
     Acceler8 unchanged in every respect (its donor rule already paints #fff)
   Chip GEOMETRY is untouched everywhere: 44x44 on centres 72/126/180/234/288.

   CONTRAST NOTE, recorded not fixed: white ink on the donor accent clears 4.5:1
   on Vision (6.94) but NOT on Creativa (3.39) - nor on Acceler8 itself (3.27),
   which has looked correct all day. That is a property of this file's
   white-on-accent chip treatment fleet-wide, not of this patch. Both clear the
   3:1 that WCAG 2.1 SC 1.4.11 asks of a non-text UI component, which is the
   applicable bar for a 20px glyph. Raising it further means either an ink that
   reacts to accent luminance or dropping the accent fill - a design decision,
   not a bug fix, and out of scope here.
   ===================================================================== */
@media (max-width: 767px) {
    /* the floor: no donor TYPE selector reaches the chip set */
    #cs-utility-row > button,
    #cs-utility-row > a { background: #fff !important; }

    /* the two chips this file accents, lifted to (0,2,0) to clear the floor.
       The adopted cart is NOT listed: its own rules are already (0,1,2) via
       #vmCartModule .show-cart a / #cart-position a.cart-button, which
       outrank the floor on their own. */
    #cs-utility-row > #cs-secondary-toggle,
    #cs-utility-row > #cs-register { background: var(--cs-accent, #992946) !important; }
}


/* =====================================================================
   CS-PATCH: utility-chip-ink-floor  2026-08-18  (rev .65)

   THE SYMMETRIC PARTNER OF THE .64 BACKGROUND FLOOR.

   .64 gave the chip set a BACKGROUND floor so no donor TYPE selector could
   repaint a chip. It did not give it an INK floor. Chip ink was still whatever
   the donor's header rules said, so on six of ten templates the glyph stayed
   the donor's header white and landed on .64's new white chip:

       MEASURED at 375 on home, account/search/social, all three identical:
         Crystal 1.00  Energize 1.00  MubleStore 1.00
         Oxygen  1.00  Progressive 1.00  Impulsa 1.00

   1.00:1 is not "low contrast", it is an invisible control. The chip was there,
   sized 44x44 on the right centre, and painted nothing.

   THE RULE: a chip's ink is a function of ITS OWN background, never inherited
   from a donor. Two floors, at the same (0,1,1) reach as the background floor:

     - plain chips are white, so their ink is #333       -> 12.63:1
     - accented chips take --cs-accent-ink, which
       cs-mobile-generic.js derives from the accent's own
       relative luminance (white or black, whichever wins)

   WHY LUMINANCE-DERIVED AND NOT HARDCODED WHITE. The accent is the DONOR's, so
   its luminance is not ours to assume. White-on-accent measured 1.88:1 on
   Progressive and 2.30:1 on Impulsa - the accent chips were nearly as unreadable
   as the white ones, and no fixed ink colour can serve both a pale lime accent
   and a dark maroon one. Deriving it costs one luminance calculation and is
   correct on every donor, including ones not in this cohort.

   (The note at .64 that white-on-#992946 is "~3.4:1" was wrong - it measures
   7.61:1, which is why Encore, the one template still on the #992946 fallback,
   passes on its accent chips untouched. The templates that failed did so on
   their OWN accents, not on the fallback.)

   BLAST RADIUS, measured by applying this candidate and then DELETING it at
   runtime and re-measuring, per template, at 375 on home:

     Acceler8     account/search/social 3.95 -> 12.63   accent chips 3.27 -> 6.43
     Creativa     account/search/social 9.74 -> 12.63   accent chips 3.39 -> 6.19
     Crystal      account/search/social 1.00 -> 12.63   accent chips 2.55 -> 8.24
     Encore       account/search/social 2.99 -> 12.63   accent chips 7.61 unchanged
     Energize     account/search/social 1.00 -> 12.63   accent chips 2.72 -> 7.72
     MubleStore   account/search/social 1.00 -> 12.63   accent chips 6.60 unchanged
     Oxygen       account/search/social 1.00 -> 12.63   accent chips 3.71 -> 5.66
     Progressive  account/search/social 1.00 -> 12.63   accent chips 1.88 -> 11.18
     Vision       account/search/social 9.74 -> 12.63   accent chips 6.94 unchanged
     Impulsa      account/search/social 1.00 -> 12.63   accent chips 2.30 -> 9.14
                  cart chip 2.30 -> 9.14

   Acceler8, Creativa and Vision DO move on the three named chips - they went
   from a donor ink that happened to be readable to this file's #333. That is a
   deliberate, measured change, not an accident: their old values were donor
   coincidence, and 3.95:1 on Acceler8 was below the 4.5 bar anyway. Chip
   GEOMETRY is untouched on all ten: 44x44 on centres 72/126/180/234/288.

   THE GLYPH `inherit` LINE is load-bearing. Donors paint the <i> directly as
   often as they paint the control, so flooring the chip alone leaves the glyph
   free. Forcing the glyph to inherit means one declaration - the chip's -
   decides, which is the whole point of a floor.
   ===================================================================== */
@media (max-width: 767px) {
    /* plain chips: dark ink, same (0,1,1) reach as the background floor */
    #cs-utility-row > button,
    #cs-utility-row > a { color: #333 !important; }

    /* the glyph never gets its own opinion - it takes its chip's */
    #cs-utility-row > button i,   #cs-utility-row > a i,
    #cs-utility-row > button svg, #cs-utility-row > a svg,
    #cs-utility-row > button > span:not(.cs-burger-bars),
    #cs-utility-row > a > span:not(.cs-burger-bars) {
        color: inherit !important;
        fill: currentColor !important;
    }

    /* accented chips, lifted to (0,2,0) to clear the floor above, exactly as
       the .64 background floor lifts the same two. */
    #cs-utility-row > #cs-secondary-toggle,
    #cs-utility-row > #cs-register { color: var(--cs-accent-ink, #fff) !important; }
}


/* =====================================================================
   CS-PATCH: parked-submenu-scrollwidth  2026-08-18  (rev .65)

   HORIZONTAL SCROLLBAR AT TABLET WIDTHS, FROM MENUS NOBODY CAN SEE.

     MEASURED, scrollWidth vs clientWidth, home/interior/account all identical:
       Vision   1024: 1074 vs 1024 (+50)    768: 998 vs 768 (+230)
       Crystal  1024: 1085 vs 1024 (+61)    768:  825 vs 768  (+57)

   The ET family's superfish menu parks its dropdown panels with
   `.menu-nav ul { position:absolute; top:-999em }`. That hides them
   VERTICALLY. It does nothing horizontally, so a third-level panel sitting at
   `left:100%` of a 490px `ul.columns-2` still reports a right edge ~998px and
   still grows the document's scroll width. The page is horizontally
   scrollable at tablet width while nothing visibly overflows - which is
   exactly why this survived every visual review.

   The panels are emitted by every template in the cohort (all ten carry the
   `ul.columns-N { width: <param>px !important }` block in index.php); Vision is
   worst only because its `dropdownhandler2` parameter defaults to 490 where the
   rest default to 360. At 360 the level chain is still 360+180+360 = 900px, so
   this is a fleet property, not a Vision one, and the fix belongs here rather
   than in a template parameter.

   THE FIX: park them horizontally too. A panel whose parent is not hovered is
   by definition closed, so it can sit at -99999px, where it cannot contribute
   scroll width (overflow to the LEFT never grows scrollWidth in LTR). Every
   state that OPENS a panel - the donor's `li:hover ul`, `li.sfHover ul`,
   `li li:hover ul` and the `columns-N li ul { left:100% !important }` chain -
   re-specifies `left`, and this rule stops matching the moment the parent is
   hovered, so no open state is touched.

   SCOPED `min-width: 768px` DELIBERATELY, for two reasons: the defect only
   exists where the desktop nav is rendered, and below 768 this markup is
   cloned into #cs-drawer, whose accordion opens submenus by CLASS, not by
   hover. Excluding the drawer by breakpoint is safer than excluding it by
   selector.

   `:focus-within` is included so keyboard traversal still opens a panel.

   VERIFIED after applying: Vision +230 -> 0 and Crystal +57 -> 0 at 768,
   Vision +50 -> 0 and Crystal +61 -> 0 at 1024, with the level-1 panel still
   opening on hover at both 1400 and 768 on both templates (panel measured
   on-screen, visible, non-zero box), and the other eight unchanged at 0.
   ===================================================================== */
@media (min-width: 768px) {
    .menu-nav li:not(:hover):not(:focus-within):not(.sfHover):not(.open) > ul {
        left: -99999px !important;
    }
}


/* =====================================================================
   CS-PATCH: search-input-column-cap  2026-08-18  (rev .65)

   CLOSES GAP 2, recorded at .63.

   A hard-width search input inside a column the donor narrows at tablet
   widths. Energize: `#search-position input.form-control { width: 220px;
   max-width: none }` inside a `#search-position` the donor caps at 150px
   (768-979) and 125px (980-1199). MEASURED at 768: input right edge 776 against
   a 768 client box, +8px of document scroll.

   Canonical's existing `max-width: 100%` blanket is inside
   `@media (max-width: 767px)`, so nothing capped this at tablet or desktop.

   SCOPED 768-1199 ON PURPOSE. The 220px is not an accident - it is a deliberate
   donor-parity rule ("live look = BS2 fixed 220px ... overhanging the right box
   edge slightly", template.css) and at >=1200 it overhangs INSIDE the page and
   costs no scroll width. Capping only the two bands where the donor itself
   narrows the column fixes the overflow and leaves the intended desktop look
   alone. `!important` is required: the donor rule carries an explicit
   `max-width: none`.

   Nine of ten templates have no hard px width on this input, so this is a
   no-op for them; it is written generically because the idiom, not the
   template, is the hazard.
   ===================================================================== */
@media (min-width: 768px) and (max-width: 1199px) {
    #search-position input,
    #search-position .awesomplete {
        max-width: 100% !important;
        box-sizing: border-box !important;
    }
}


/* =====================================================================
   CS-PATCH: donor-social-tab-phone  2026-08-18  (Tim review of Crystal)

   The ET donors ship a slide-out social flyout pinned to the right edge:

     <div id="social-panel">                 position:fixed; right:-30px; top:18%
       <div id="joinus-hs"><strong>Follow Us</strong></div>   the 20px pull-tab
       <nav id="social"><ul id="social-links">...             the icons

   `#social-panel:hover { right: 0 }` slides it in. There is no click handler --
   the tab is a HOVER affordance, so on a touch device it can never do anything.

   At phone width the canonical mobile chrome has already adopted `#social-links`
   into `#cs-social-panel` behind the `#cs-social-toggle` chip, and hides the
   donor's `nav#social`. The donors' own responsive sheets then set
   `#social-panel { position: static; width: 100% }` and `#joinus-hs
   { display: none }`, so the whole block is supposed to collapse to nothing.

   MEASURED on Crystal @375 before this patch: `#social-panel` rendered as a
   371x99 block at y=3785 in a 3884px document -- a bar across the foot of the
   page holding a 20x84 dark pill with "Follow Us" rotated inside it, hit-testing
   to an <h3> behind it. Not clickable, no hover on touch, and duplicated by the
   social chip 3700px above it.

   Guarded on `#cs-social-toggle` so a template where the canonical chrome did
   NOT adopt the social links keeps the donor's block as its only social path.
   ===================================================================== */
@media (max-width: 767px) {
    body:has(#cs-social-toggle) #joinus-hs { display: none !important; }
}


/* =====================================================================
   CS-PATCH: top-utility-bar-centring  2026-08-18  (Tim review of Crystal)

   SIBLING to desktop-header-icons above, which gave the donor's account/search/
   cart controls one midline. It never covered the OTHER top-bar group: the
   quick-menu links (FAQ / Help / Support) in `#top-nav-line`.

   The donor floats that group right --  `#quick-menu { float: right }` -- inside
   a bar whose height is set by a TALLER sibling in normal flow. A float's top
   edge is the top of its line box, so the group can never centre itself, and no
   amount of padding fixes it because the bar height differs per template.

     MEASURED @1400, centre-Y of the links vs the bar's own midline:
       Crystal      bar #top-nav-line h=38 (set by #search-position, 38px)
                    links h=27.6, cy 23.8 vs midline 29   -> 5.2px HIGH
       Progressive  bar #top-nav-line h=32 (set by #log-panel, 32px)
                    links h=17.6, cy  8.8 vs midline 16   -> 7.2px HIGH

   Every child of the bar's container is `float: right` on both templates, and
   right-floats stack right-to-left in DOM order -- which `flex-direction:
   row-reverse` reproduces EXACTLY, so the horizontal layout is untouched while
   `align-items: center` finally gives the group a midline. VERIFIED: every
   child's x and width are bit-identical before and after on both templates
   (Crystal #search-position x=1071 w=214, #quick-menu x=894 w=177; Progressive
   #quick-menu x=1112 w=174, #log-panel x=928 w=184), and centre-Y deviation
   goes 5.2 -> 0.0 and 7.2 -> 0.0.

   An earlier candidate made the container a plain `display:flex` row. That
   centred correctly but laid the children out in DOM order instead of float
   order, and moved Crystal's search box from x=1071 to x=115 -- from the right
   of the bar to the left. Hence row-reverse, not row.

   Scoped to `#top-nav-line` because that is the bar whose children are all
   right-floated; VERIFIED a no-op on the eight templates that do not have one.
   ===================================================================== */
@media (min-width: 768px) {
    #top-nav-line > .container,
    #top-nav-line > .container-fluid {
        display: flex !important;
        flex-direction: row-reverse !important;
        justify-content: flex-start !important;
        align-items: center !important;
        flex-wrap: nowrap !important;
    }
    #top-nav-line > .container > *,
    #top-nav-line > .container-fluid > * { float: none !important; }
    /* the links inside the group are floated too, which collapses the <ul> to
       height 0; make the list a flex row so it has a real box to centre. */
    #top-nav-line #quick-menu > ul { display: flex !important; align-items: center !important; }
    #top-nav-line #quick-menu li { display: flex !important; align-items: center !important; }
    #top-nav-line #quick-menu li > a {
        float: none !important; display: flex !important; align-items: center !important;
    }
}

/* =====================================================================
   CS-PATCH: mobile-empty-header-band-collapse  2026-08-18  (found on Energize)
   Two residues of the mobile-header rebuild, each painting a full-width band of
   header background with nothing inside it. On Energize they stacked into one
   203px dark box between the branded logo bar and the utility icon row - Tim:
   "there is a black box, but there's nothing occurring in it."

   (a) #menu-handler carrying a donor-authored fixed phone `height`. Donors size
       this band for the BS2 stacked phone layout (logo + nav + log-panel +
       search) that cs-mobile-generic.js and the block above dismantle. Energize's
       responsive-template.css sets `#menu-handler { height: 154px }` below 480px
       while the surviving content - the logo row - measures 84px, so 70px painted
       empty. height:auto lets the band follow whatever is actually left in it.

   (b) The 56px touch floor set a few rules above on `#top, #top > .row,
       #top > .row-fluid, #menu-handler` is there so the band HOLDING THE BRAND is
       finger-tall. Where #top holds no brand, every child it can hold has already
       been display:none'd higher up (#social, #top-quick-nav, #topmenu,
       #search-position, #quick-menu - all re-issued inside #cs-utility-row), so
       the floor was keeping an empty shell alive: Energize's #top measured 72px
       with one visible leaf and a 23px bottom margin. This uses the same
       :not(:has(#site-name-handler)) test the block above already applies to
       nav#menu, so a #top that still carries the brand keeps its floor. Collapsing
       rather than display:none means the band returns by itself if a donor ever
       puts content back.

   MEASURED @375, Energize, before -> after:
     #top-handler 340 -> 175 | #menu-handler 154 -> 84 | #top 72 -> 0
     largest empty band inside the header 203 -> 38 (38 = ordinary gutter)
     brand still painted, #cs-utility-row still painted, scrollWidth 360 unchanged.
   VERIFIED NO-OP @375 on the other nine - header height / largest empty band
   identical before and after: Acceler8 147/54, Creativa 162/40, Crystal 141/28,
   Encore 149/42, Oxygen 203/47, Progressive 197/84, Vision 150/27,
   MubleStore 139/42, Impulsa 141/38. All nine carry the brand INSIDE #top so (b)
   cannot fire; Acceler8/Creativa/Encore/Oxygen/Vision have #menu-handler inside an
   already-hidden nav#menu and MubleStore/Impulsa have no #menu-handler at all, so
   (a) cannot fire either.
   ===================================================================== */
@media (max-width: 767px) {
  #menu-handler { height: auto !important; }

  #top:not(:has(#site-name-handler)) {
    min-height: 0 !important; height: auto !important;
    padding-top: 0 !important; padding-bottom: 0 !important;
    margin-bottom: 0 !important;
  }
  #top:not(:has(#site-name-handler)) > .row,
  #top:not(:has(#site-name-handler)) > .row-fluid {
    min-height: 0 !important;
    padding-top: 0 !important; padding-bottom: 0 !important;
  }
}

/* =====================================================================
   CS-PATCH: bs2-row-zero-width-guard  2026-08-18  (found on Energize)
   Check 8 (horizontal overflow) failed on Energize interior and account pages
   at 1024 and 768 while home was clean at every width. The offender is the
   breadcrumb bar, and it is the ONLY thing overflowing on either page:

     #main-handler > #nav-line > div.row > div.col-md-12 > #brcr > .breadcrumbs
                                     ^ width 0

   MEASURED @1024 on the single-article page: #nav-line is a plain block at x=60,
   890px wide, with exactly ONE child - that .row. The row nevertheless lays out
   at x=975, its grandparent's right padding edge, with width 0. Its .col-md-12
   then resolves BS5's `width: 100%` against zero and is 0 as well; #brcr measures
   30px, which is only its own 15px padding twice; and the inline `a.pathway`
   breadcrumb links, with no width to wrap inside, run straight off the right edge
   - 58px past the viewport at 1024 and 66px at 768. Home carries no #nav-line
   row, which is exactly why home was clean at all four widths.

   This is ONE cause, not two. The earlier note that a second unidentified cause
   was at work at 1024 was an artefact of guarding the wrong box: `width: 100%` on
   the COLUMN does nothing, because the column already has `width: 100%` from BS5
   and the zero it is resolving against belongs to the ROW. Guarding the ROW takes
   1024 and 768 to zero together.

   `min-width` rather than `width` deliberately: on a row that already fills its
   parent this is a no-op - 100% is the width it already has - and it can only
   ever grow a row that has collapsed, never re-size or shrink a healthy one.
   Scoped to the BS2-shaped rows this file already special-cases a few rules above
   (`--bs-gutter-x: 0` plus zero side margins), so 100% resolves to exactly the
   parent's content width with no gutter left to overflow by.

   MEASURED scrollWidth - clientWidth, before -> after:
     Energize interior   1400:0   1024:58 -> 0   768:66 -> 0   375:0
     Energize account    1400:0   1024:58 -> 0   768:66 -> 0   375:0
   VERIFIED NO-OP - identical at all four widths on home, interior and account -
   on Acceler8, Creativa, Crystal, Encore, Oxygen, Progressive, Vision, MubleStore
   and Impulsa. On those nine the breadcrumb's row already measures its parent's
   full width (Acceler8 940/940, Vision 910/910), so there is nothing to raise.
   NOTE this does not touch the pre-existing, unrelated residuals that were there
   before and after: Acceler8 home 8px @375, Crystal and Encore 6px @768.
   ===================================================================== */
.row:has(> [class*="col-md-"]),
.row:has(> [class*="span"]) { min-width: 100%; }


/* =====================================================================
   CS-PATCH: bs5-btn-close-glyph  2026-08-19  (rev .68)

   THE MODAL CLOSE BUTTON RENDERED AS A SOLID BRAND-COLOURED BLOCK SITTING
   ON TOP OF THE "Login Form" TITLE. Reported by Tim on Encore at 375 and
   measured there as a 30x12 rectangle filled rgb(75,161,176), with
   background-image: none - i.e. no glyph at all, just a painted box.

   ROOT CAUSE, and it is not a colour bug. The BS2->BS5 markup conversion
   rewrote the login modal's dismiss control from BS2's

       button class="close" containing a literal times entity   <- a TEXT
                                                       glyph, which survives
                                                       with no CSS at all

   to BS5's

       button class="btn-close" containing NOTHING     <- the glyph is a CSS
                                                       background SVG and
                                                       there is nothing else

   ...but these templates do not ship BS5's stylesheet. Their CSS stack is the
   donor's own BS2 bootstrap.css plus this bridge, and NOTHING in it defines
   .btn-close. Verified on Encore by walking every loaded sheet: zero rules
   match .btn-close. So the control is an empty, completely unstyled button.

   An empty unstyled button then falls into the donor preset's

       .button, button, a.button, ..., .btn, .btn-primary {
           color: #fff !important; background-color: <brand> !important; }

   - a BARE ELEMENT selector carrying !important - and gets painted as a solid
   brand rectangle. That same single donor rule is also why the drawer chips
   come out white-on-brand (see cs-drawer-chip-ink below). One rule, several
   symptoms, which is why they are fixed together at this rev.

   WHY IT LOOKED FINE ON SOME TEMPLATES. Whether the glyph appears at all is
   pure accident: it depends on whether some OTHER extension on that page
   happened to pull in /media/vendor/bootstrap/css/bootstrap.min.css. Measured
   across the ten at 375:

     Crystal, MubleStore, Impulsa   BS5 present -> SVG glyph IS drawn, but the
                                    donor's background-color !important still
                                    shows through it as a brand block
     Acceler8, Creativa             BS5 present and the preset does not reach
                                    the control -> renders correctly
     Encore                         BS5 ABSENT -> no glyph whatsoever. The
                                    pure form of the defect, the one reported
     Progressive, Vision            a DIFFERENT control entirely - not a
                                    button.btn-close but
                                      <a class="close-lgform-button">x</a>
                                    a 7.4x20.8 text glyph at 13px in the brand
                                    colour, measured 1.88:1 on Progressive,
                                    jammed against the title rather than
                                    right-aligned. Same defect, third shape
     Energize, Oxygen               no close control in the header at all

   So this is ONE defect at three severities, not one broken template - and it
   was invisible on the templates that happened to load BS5 for other reasons.

   THE FIX: this file supplies the missing BS5 component itself. That is
   exactly its stated job - "BS2-to-BS5 visual aliasing" - and it loads last,
   so it is the correct place for it. !important here is required, not
   preferred: the donor rule is !important and specificity alone cannot beat
   it. Once both are important, .btn-close (0,1,0) outranks the donor's bare
   `button` (0,0,1), so this wins on every donor without per-template scoping.

   The `background` SHORTHAND is load-bearing. It resets background-color to
   transparent AS !important, which is the only way to defeat the donor's
   !important background-color while simultaneously restoring the image; a
   background-image longhand alone would leave the brand block behind it.

   A transparent colour hides any literal times character still in the markup
   - MubleStore carries one - which would otherwise double up with the SVG.
   Preferred to font-size:0 (the box is sized in em and would collapse) and
   to a text-indent clip, which was tried and leaked a visible smudge.

   ORDER: the markup emits the button BEFORE the title span, so without BS5's
   flex header it lands top-LEFT, on top of "Login Form" - exactly what was
   reported. order:99 + margin-left:auto puts it on the right on every
   template regardless of source order, and is a no-op where BS5 is loaded.

   opacity stays at BS5's own .5 rather than being darkened, so Acceler8 and
   Creativa - which Tim has already accepted - keep the resting appearance he
   signed off on.

   TWO MARKUP VARIANTS, ONE RULE. Surveying all ten for what the dismiss
   control actually IS - rather than assuming - found three shapes:

     button.btn-close.close-lgform-button   Creativa, Crystal, Encore,
                                            MubleStore, Impulsa
     button.btn-close                       Acceler8 (no second class)
     a.close-lgform-button containing "x"   Progressive, Vision
     nothing in the header                  Energize, Oxygen

   Neither class alone reaches all of them, so the rule carries BOTH. That is
   what takes this fix from six templates to eight rather than leaving
   Progressive and Vision with a 1.88:1 seven-pixel glyph nobody can hit.

   NOTE, reported but deliberately NOT changed here:

     * Energize and Oxygen have no header close control in the markup at all.
       Their modals still dismiss via the footer Close button, the backdrop
       and ESC, so nothing is unreachable - but they are the odd two out.
     * Progressive's and Vision's control is an <a> with NO href, so it is not
       keyboard focusable. CSS cannot fix that.

   Both are markup differences, not CSS ones, and editing four templates'
   markup is out of scope for a canonical CSS rev.
   ===================================================================== */
.btn-close,
.close-lgform-button {
    /* inline-block, because the second variant is an <a> and width/height
       do not apply to an inline box. */
    display: inline-block !important;
    box-sizing: content-box !important;
    /* 1rem, NOT the inherited size. The donors size buttons themselves -
       text.css sets 14px and responsive-template.css forces 11px !important
       between 768 and 979 - so an em-sized box measured 18.0 on Creativa and
       21.6 on Acceler8, both under the 24px minimum target. Pinning the
       font-size makes the control a consistent 24x24 on all ten: BS5's own
       resting size, and the WCAG 2.2 target-size floor. */
    font-size: 1rem !important;
    line-height: 1 !important;
    width: 1em !important;
    height: 1em !important;
    padding: .25em .25em !important;
    border: 0 !important;
    border-radius: .375rem !important;
    background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414'/%3e%3c/svg%3e") center/1em auto no-repeat !important;
    box-shadow: none !important;
    text-shadow: none !important;
    /* Kill any literal times character left in the markup - MubleStore
       still ships one - so it cannot double up with the SVG. The usual
       text-indent:100% clip was tried first and LEAKED: at a 24px box a
       single narrow glyph still showed as a smudge below-right of the
       SVG (caught on MubleStore at 375). Transparent ink removes it
       outright with no layout risk, and !important is needed because the
       donor preset sets color:#fff !important on bare `button`. */
    color: transparent !important;
    overflow: hidden;
    opacity: .5;
    cursor: pointer;
}
.btn-close:hover,
.close-lgform-button:hover { opacity: .75; }
.btn-close:focus,
.close-lgform-button:focus { opacity: 1; outline: 0; box-shadow: 0 0 0 .25rem rgba(13, 110, 253, .25); }

.modal .modal-header { display: flex; align-items: center; }
.modal .modal-header > .btn-close,
.modal .modal-header > .close-lgform-button { order: 99; margin: 0 0 0 auto !important; }


/* =====================================================================
   CS-PATCH: cs-drawer-chip-ink  2026-08-19  (rev .68)

   THE DRAWER ACCORDION CHIP, NOT THE UTILITY-ROW CHIP. Two concurrent
   measurement passes disagreed about "chip contrast" because they were
   looking at two different controls with similar names. Both were right:

     #cs-utility-row > button, #cs-utility-row > a   the 44x44 header chips.
       Floored at rev .65 (background at .64). Measured 12.63 on the plain
       three and 5.66-11.18 on the two accented ones. ALL TEN PASS - nothing
       to do here, and this patch does not touch them.

     .cs-chip  (inside #cs-drawer / #cs-secondary-panel)   the 34x32 "+"
       expander on a submenu row. Declared above with background:#fff and
       color:#666 and NO !important, so a donor overrides both. THIS is the
       one that fails.

   MEASURED, drawer open, 375, home, ink vs the chip's own composited bg:

     Progressive  #fff on #a1ca6b  1.88      Vision      #fff on #a13500  6.94 ok
     Impulsa      #fff on #f7930f  2.30      MubleStore  #fff on #bd001f  6.60 ok
     Crystal      #fff on #ed8a00  2.55      Creativa    #666 on #ffffff  5.74 ok
     Energize     #fff on #86a84b  2.72
     Encore       #fff on #4ba1b0  2.99
     Oxygen       #fff on #298bcc  3.71
     Acceler8     #808080 on #fff  3.95

   SAME ROOT CAUSE AS THE CLOSE BUTTON ABOVE. The chip is a button element, so
   the donor preset's `.button, button, ... { color:#fff !important;
   background-color:<brand> !important }` repaints BOTH its ink and its
   background. That one rule explains the whole column: eight templates get
   the brand background with white ink on top of it, and Acceler8 gets a donor
   grey. It is not eight separate bugs.

   THE FIX reuses the rev .65 mechanism rather than inventing a second one.
   cs-mobile-generic.js already derives --cs-accent-ink from the accent's OWN
   relative luminance, taking whichever of black or white contrasts further,
   so the ink is correct on a donor whose colour we do not get to choose. On
   eight of the ten the chip's background IS --cs-accent, exactly (verified by
   comparing the composited chip background against the resolved variable), so
   that derived ink is by construction the best available on it. On the other
   two the chip background is #fff and their derived ink is #000, which is
   also correct there.

   The background is deliberately LEFT ALONE. Flooring it to #fff would fix
   the contrast too, but it would restyle the chip from brand to white on
   eight templates including Acceler8 and Creativa, which Tim has accepted.
   Changing only the ink fixes all ten and restyles none.

   BLAST RADIUS, measured by applying this rule and then DELETING it at
   runtime and re-measuring, per template, at 375 with the drawer open:

     Acceler8      3.95 -> 21.00      Oxygen        3.71 -> 5.66
     Creativa      5.74 -> 21.00      Progressive   1.88 -> 11.18
     Crystal       2.55 -> 8.24       Vision        6.94 -> 6.94  (no change)
     Encore        2.99 -> 7.02       MubleStore    6.60 -> 6.60  (no change)
     Energize      2.72 -> 7.72       Impulsa       2.30 -> 9.14

   Ten of ten at or above 4.5. Chip GEOMETRY is untouched everywhere: still
   34x32 (Acceler8 40x32, its own donor width), and the background is the same
   colour it was before.

   RESIDUAL CONDITION, recorded so a future donor that trips it is caught
   rather than discovered: the derived ink is the best of black/white ON THE
   ACCENT. If some future donor leaves the chip background WHITE while also
   having an accent dark enough that --cs-accent-ink resolves to #ffffff, the
   chip would go white-on-white. No template in this cohort is in that state -
   the two with white chips (Acceler8, Creativa) both derive #000000, and all
   eight that derive from a dark accent have the accent as their background -
   but the pairing is empirical here, not structural, because the background
   belongs to the donor. If it ever bites, the answer is to take ownership of
   the chip background as well, exactly as rev .64 did for the utility row.
   ===================================================================== */
@media (max-width: 767px) {
    .cs-chip { color: var(--cs-accent-ink, #333) !important; }

    /* the glyph never gets its own opinion - it takes its chip's, the same
       rule the .65 utility-row floor relies on. The chip's content is the
       literal text "+" today, so this is belt-and-braces against a donor that
       wraps it, but it costs nothing and it is the pattern. */
    .cs-chip > i, .cs-chip > svg, .cs-chip > span {
        color: inherit !important;
        fill: currentColor !important;
    }
}


/* =====================================================================
   CS-PATCH: cloned-social-donor-leak + dead-bs2-split-button
   2026-08-19  (Tim review of Vision + Encore)

   (b) MOBILE SOCIAL PANEL OPENS EMPTY (Vision).
       `cs-mobile-generic.js` clones the donor's social <ul> into
       `#cs-social-panel`. The clone was fine - MEASURED 6 <li>, 3 of them real
       outbound links. What killed it is a DONOR HELPER CLASS that came along
       for the ride: every Vision social anchor carries `class="tipz"`, and
       Vision's nav.css says

           .tipz { margin-top: -64px; margin-left: -45px; }

       so each 44x44 anchor was dragged 64px above and 45px left of the panel it
       had just been put in. MEASURED @375 on Vision: panel 375x34 at y=157,
       anchors at y=110 - every one of them OUTSIDE its own panel, visible-in-
       panel count 0 of 6. The panel opened; it was just empty.
       A second leak from the same source: Vision's and Creativa's
       typography.css styles bare `ul li` with a bullet IMAGE and
       `padding: 3px 0 3px 24px`. Canonical reset the li's float/width/margin
       but not its padding or background, so the panel showed bullet graphics
       beside the icons and wrapped 5 icons onto two rows (Creativa: panel
       138px tall; 72px after this fix, one row, no bullets).
       Fix at the layer the bug is at: a node CLONED out of the donor's DOM
       must not keep the donor's positioning or list chrome. Neutralise both.
       VERIFIED a no-op on the other eight - all already measured margin 0,
       padding 0, no li background.

   (c) DEAD BS2 SPLIT-BUTTON BESIDE "Log in" (Tim: "there's a drop down arrow
       to the right of the login button. I don't know what that's for.")
       He is right not to know, and it is worse than decorative. The markup is

           <button class="button" type="submit">Log in</button>
           <button class="button dropdown-toggle" data-toggle="dropdown">
               <span class="caret"></span></button>
           <ul class="dropdown-menu"> ...forgot pw / forgot user / register... </ul>

       Two independent faults:
         1. `data-toggle` is the Bootstrap 2/3/4 attribute. These sites run BS5,
            which binds `data-bs-toggle`. Nothing is listening, so nothing calls
            preventDefault().
         2. The button has NO `type` attribute and lives inside `#login-form`,
            so its effective type is `submit`.
       Net effect, MEASURED on Encore @375: tapping the caret SUBMITS the login
       form with an empty password, the modal is destroyed, and the browser
       lands on /index.php/login showing "Warning - Empty password not allowed."
       The three links behind it are never reachable by any route.
       Present on 6 of the 10: Acceler8, Encore, Energize, Oxygen, MubleStore,
       Impulsa. The other 4 - Creativa, Crystal, Progressive, Vision - already
       render those three links PLAINLY in the modal body with no caret at all.
       So this is not a redesign, it is bringing six templates into line with
       the four that were already right: hide the dead control, and show the
       links it was hiding.
       Scoped on `[data-toggle="dropdown"]:not([data-bs-toggle])` so a genuine
       BS5 dropdown is never touched.
   ===================================================================== */

/* (b) cloned social nodes must not carry the donor's positioning or list chrome */
#cs-social-panel li,
#cs-social-panel .cs-social-list > li {
    padding: 0 !important;
    background: none !important;
    list-style: none !important;
    text-indent: 0 !important;
}
#cs-social-panel li a,
#cs-social-panel .cs-social-list a {
    margin: 0 !important;
    position: static !important;
    float: none !important;
    top: auto !important; right: auto !important; bottom: auto !important; left: auto !important;
    transform: none !important;
}

/* (c) retire the dead BS2 split-button and surface the links it hid */
.modal .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]),
.modal .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) > .caret {
    display: none !important;
}
.modal .btn-group:has(> .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle])) {
    display: block !important;
}
.modal .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) ~ ul.dropdown-menu {
    display: block !important;
    position: static !important;
    float: none !important;
    width: 100% !important;
    min-width: 0 !important;
    margin: 10px 0 0 !important;
    padding: 0 !important;
    background: none !important;
    border: 0 !important;
    box-shadow: none !important;
    list-style: none !important;
    transform: none !important;
    top: auto !important; left: auto !important; right: auto !important; bottom: auto !important;
    opacity: 1 !important;
    visibility: visible !important;
}
.modal .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) ~ ul.dropdown-menu > li {
    display: block !important;
    float: none !important;
    padding: 0 !important;
    margin: 0 !important;
    background: none !important;
    list-style: none !important;
    border: 0 !important;
}
.modal .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) ~ ul.dropdown-menu > li > a {
    display: inline-block !important;
    padding: 4px 0 !important;
    background: none !important;
    color: inherit !important;
    font-size: 13px !important;
    line-height: 1.5 !important;
    text-decoration: underline !important;
    white-space: normal !important;
}
/* (c-ii) ...and the donor preset must not paint them as a second button.
   MEASURED after (c): the three links came out as WHITE TEXT ON A SOLID BRAND
   PANEL sitting directly under the brand-coloured "Log in" button, so the modal
   read as two buttons stacked. Source is the donor preset, style1.css:

       #searchOpenButton, .closeMenu, #LoginForm .btn-group > .dropdown-menu,
       #LoginForm .btn-group > .dropdown-menu a, ...
           { color:#fff !important; background-color:<brand> !important }

   That selector is (1,2,1); the (c) rules above are (0,5,3). An id beats any
   number of classes, so `background: none !important` in (c) lost outright -
   the documented donor-preset trap, in its ID-selector form.
   Contrast MEASURED: white on Encore's #4BA1B0 is 3.0:1, under the 4.5:1 bar.
   Answer it at the same weight by adding an `#LoginForm`-scoped copy, (1,5,3).
   The generic `.modal` form is kept as well, for any donor whose login modal
   carries a different id. `color: inherit` takes the modal's own body colour
   (Encore rgb(65,65,65) on white = 8.5:1) rather than the brand hue, which on
   the lighter presets (Energize #86A84B = 2.7:1 on white) would fail the bar
   just as badly in the other direction.
   MEASURED AFTER on the six that carry the split button - Acceler8, Encore,
   Energize, Oxygen, MubleStore, Impulsa - transparent background, dark body
   text, underlined. */
#LoginForm .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) ~ ul.dropdown-menu,
.modal .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) ~ ul.dropdown-menu {
    background: transparent !important;
    background-color: transparent !important;
    color: inherit !important;
}
#LoginForm .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) ~ ul.dropdown-menu > li,
#LoginForm .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) ~ ul.dropdown-menu > li > a,
.modal .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) ~ ul.dropdown-menu > li,
.modal .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) ~ ul.dropdown-menu > li > a {
    background: transparent !important;
    background-color: transparent !important;
    color: inherit !important;
    text-decoration: underline !important;
}
/* /CS-PATCH: cloned-social-donor-leak + dead-bs2-split-button */

/* =====================================================================
   CS-PATCH: article-info-inline-flex-nowrap  2026-08-19  (found on Encore,
   Energize and Oxygen by Tim; measured on all ten)

   Tim @375: "Things are not wrapping. You've got written by, parent category,
   category, published, created, updated, but that drifts off to the side of
   the site."

   ROOT CAUSE - a DONOR sheet, not this file, and identical on every template:
   templates/<tpl>/css/blog.css (md5 38dd0576, byte-identical across all nine
   ET templates; the VMT donor ships the same two rules) contains

       .com-content-article dl.article-info        { display: inline-flex; width:100%; ... }
       .com-content-category-blog__item dl.article-info { display: inline-flex; width:100%; ... }

   `inline-flex` brings `flex-wrap: nowrap` with it. Joomla's article-info block
   is a `dl` of seven `dd` items (Written by / Parent Category / Category /
   Published / Created / Last Updated / Hits). On one non-wrapping line they are
   first squeezed to min-content and then simply run off the end of the box:
   MEASURED @375 on Encore the dl is 295px wide and its items end at x=448 -
   113px outside the content column and 73px outside the viewport, which is the
   whole of that page's horizontal overflow.

   The donor never sets `flex-wrap` at all, so this needs no `!important` and
   wins no specificity war - it supplies a property nobody else declares.
   `min-width: 0` on the items lets a single long item (Parent Category:
   Sample Data-Articles) wrap its own text instead of establishing a min-content
   floor wider than the column, which is what guarantees zero overflow rather
   than merely less of it.

   NOT scoped to a breakpoint: it is broken at 1400 too, just less visibly.
   MEASURED @1400 on Acceler8 BEFORE, every cell is squeezed and broken
   mid-phrase - "Written by: / Joomla!", "Published: 01 January / 2011",
   "Last Updated: 17 January / 2012" - in a 62.8px-tall block. AFTER, each label
   reads on one line and only "Hits" wraps, 72.8px. Reported to Tim as a
   deliberate desktop change with before/after screenshots.

   MEASURED @375, metadata spilling OUTSIDE THE CONTENT COLUMN, before -> after:
     Encore 113.3->0  Energize 62.2->0  MubleStore 31.5->0  Oxygen 24.6->0
     Impulsa 17.1->0  Creativa 10.5->0  Vision 4.5->0  Acceler8 49.6->0
     Crystal 0->0  Progressive 0->0   (the last two were already inside the
     column only because their content column is wide enough for the squeeze)
   Page horizontal overflow @375: Encore 73->0, Acceler8 30->0, Oxygen 17->0.
   No-op at 768/1024 on all ten except that cells stop being squeezed.

   column-gap 2026-08-19: once items share a row, donors that give `dd` no
   horizontal margin run them together - MEASURED @375 on Crystal and Impulsa,
   "Category: Joomla!" ends at x=137.9 and "Published:" starts at x=137.9, zero
   separation. Encore's donor gives dd `margin-left: 10px` and reads fine, so
   10px is the fleet's own spacing, not a new value. Applied to the flex
   container rather than the items so it only ever separates items that SHARE a
   row - the left inset of the first item on each row, which is what sets the
   block's desktop alignment, is untouched. VERIFIED at 375/768/1024/1400 on all
   ten: horizontal overflow still 0 everywhere and the row count at 1024 and
   1400 is unchanged (2), so no template gains a wrap from it.
   ===================================================================== */
dl.article-info {
  flex-wrap: wrap;
  row-gap: 2px;
  column-gap: 10px;
}
dl.article-info > dd,
dl.article-info > dt {
  min-width: 0;
}
/* /CS-PATCH: article-info-inline-flex-nowrap */

/* =====================================================================
   CS-PATCH: mobile-empty-search-band-collapse  2026-08-19  (found on
   Progressive)

   Tim @375: a large empty white band directly beneath the breadcrumb bar.
   MEASURED @375 on Progressive: 100px of nothing between the bottom of
   #nav-line1 (y=261.8) and the top of the next painted thing, #top-modules1
   (y=361.8).

   ROOT CAUSE - the same shape as mobile-empty-header-band-collapse above.
   The donor's responsive-template.css reserves a phone band UNDER the
   breadcrumb row for the search box:

       @media (max-width: 767px) { #nav-line { margin-bottom: 50px } }
       @media (max-width: 460px) { #nav-line { margin-bottom: 100px } }

   In the donor's BS2 phone layout `#search-position` (a .col-md-3 inside
   #nav-line's row) dropped out of the row as a 208x28 centred box and sat in
   that reserved space. The mobile-chrome block ABOVE re-issues search as the
   #cs-search-toggle chip in #cs-utility-row and hides `#search-position`
   outright at <=767 - so the reservation is now paid for nothing. Confirmed
   @375: #search-position display:none, height 0, still a child of #nav-line's
   row; @768 it is display:block, 42px tall, and the margin is already 0.

   Collapse the reservation rather than papering over the band, and key it to
   the element that STILL STRUCTURALLY HOLDS the search, so it is self-
   restoring: if a donor ever paints #search-position at phone width again it
   lays out in flow inside the row and the row grows on its own - no reserved
   margin is wanted in either case.

   Depth-bounded with child combinators (`> .row > #search-position`) for the
   reason recorded in desktop-search-row-margins above: an unbounded
   `:has(#search-position)` also selects every ANCESTOR that merely contains
   the search, which on ET Magnetic reached the main content row.

   MEASURED @375 before -> after: Progressive band 100 -> 0.
   VERIFIED NO-OP @375/768/1024/1400 on the other nine - band already 0 (or no
   breadcrumb module on the route: Creativa and Vision do not paint breadcrumbs
   at 375, Impulsa ships none) and none of them puts #search-position two
   levels inside a .container the donor gives a phone margin.
   ===================================================================== */
@media (max-width: 767px) {
  .container:has(> .row > #search-position),
  .container:has(> .row-fluid > #search-position),
  .container-fluid:has(> .row > #search-position),
  .container-fluid:has(> .row-fluid > #search-position) {
    margin-bottom: 0 !important;
  }
}
/* /CS-PATCH: mobile-empty-search-band-collapse */

/* =====================================================================
   CS-PATCH: inline-svg-icon-size-cap  2026-08-19  (found on Diversity,
   confirmed on Magnetic and Logic AFTER they had shipped)

   Joomla 6's WebAuthn plugin renders its passkey button as:

       <button ...><svg id="Passkey" viewBox="0 0 24 24">…</svg> Sign in with a passkey</button>

   The SVG carries a viewBox but NO width/height attributes and no intrinsic
   size. An SVG like that expands to fill its container, and every ET donor
   pre-dates inline SVG icons so not one of them sizes one.

   MEASURED on the login page, three templates:
       Diversity  svg#Passkey  269x269
       Magnetic   svg#Passkey  244x244   inside a 300x280 button
       Logic      svg#Passkey  276x276   inside a 312x324 button

   It paints as a large black passkey glyph across the log-in area. Tim saw it
   on Diversity as black shapes over the form; Magnetic 6.1.3 and Logic 6.1.3
   had already SHIPPED with it, and both needed a 6.1.3.1 point release.

   Cap inline SVGs used as button icons. Scoped to elements that have a viewBox
   AND no explicit dimensions, so deliberate inline artwork is untouched, with
   fill: currentColor so the glyph takes the button's own ink rather than raw
   black. Assume every J6 template with WebAuthn enabled needs this.
   ===================================================================== */
button svg[viewBox]:not([width]):not([height]),
a.btn svg[viewBox]:not([width]):not([height]),
.btn svg[viewBox]:not([width]):not([height]),
svg#Passkey {
    width: 1.25em !important;
    height: 1.25em !important;
    max-width: 1.25em !important;
    max-height: 1.25em !important;
    flex: 0 0 auto !important;
    vertical-align: -0.15em;
}
svg#Passkey,
button svg[viewBox]:not([width]):not([height]) {
    fill: currentColor !important;
}
/* /CS-PATCH: inline-svg-icon-size-cap */


/* =====================================================================
   CS-PATCH: camera-pagination-bottom-centre  2026-08-19  (Concern R1, Tim)
   CANONICAL CANDIDATE - any camera.js donor can hit this.

   Tim: "the pagination buttons are at the top in a vertical view instead of
   being along the bottom of the slideshow horizontally."

   MEASURED on Concern home @375: .camera_pag was 34x102 at x=40,y=194 - a
   VERTICAL column pinned to the TOP-LEFT inside a 320x500 .camera_wrap, dots
   stacked at y=194 and y=228. camera.js positions the pager from its own
   params (cam_paginationBot etc) and the donor's rules put it top-left.

   Put it back where a slideshow pager belongs: one horizontal centred row
   along the bottom edge. Scoped to .camera_wrap so no other pager is touched.
   ===================================================================== */
.camera_wrap .camera_pag {
    position: absolute !important;
    left: 0 !important;
    right: 0 !important;
    bottom: 12px !important;
    top: auto !important;
    width: auto !important;
    height: auto !important;
    text-align: center !important;
    z-index: 5;
}
.camera_wrap .camera_pag .camera_pag_ul {
    position: static !important;
    width: auto !important;
    height: auto !important;
    margin: 0 !important;
    padding: 0 !important;
    text-align: center !important;
    font-size: 0;
    line-height: 0;
}
.camera_wrap .camera_pag .camera_pag_ul li {
    display: inline-block !important;
    float: none !important;
    position: relative !important;
    top: auto !important;
    left: auto !important;
    margin: 0 6px !important;
    vertical-align: middle;
}
/* /CS-PATCH: camera-pagination-bottom-centre */

/* /CS-PATCH: camera-pagination-bottom-centre */

/* =====================================================================
   CS-PATCH: camera-nav-arrows-touch-visible  2026-08-19  (Concern R1, Tim)
   CANONICAL CANDIDATE.

   Tim: "there are no arrows for advancing or backing up."

   Two causes. The first is a PARAMETER, not CSS: the Home Page template style
   carried no `cam_navigation` key at all, so it fell through to the
   templateDetails default of "0" and camera.js never appended
   .camera_prev/.camera_next to the DOM. Fixed in the style params + default.

   The second is this rule. The donor pairs navigation with `navigationHover`,
   which reveals the arrows only while the pointer is over the stage - an event
   that never fires on a phone. Once the arrows exist, keep them reachable on
   touch and give them a >=44px target.
   ===================================================================== */
.camera_wrap .camera_prev,
.camera_wrap .camera_next {
    opacity: 1 !important;
    visibility: visible !important;
    display: block !important;
}
@media (max-width: 979px) {
    .camera_wrap .camera_prev,
    .camera_wrap .camera_next {
        width: 44px !important;
        height: 44px !important;
        margin-top: -22px !important;
        top: 50% !important;
    }
}
/* /CS-PATCH: camera-nav-arrows-touch-visible */

/* /CS-PATCH: camera-nav-arrows-touch-visible */

/* =====================================================================
   CS-PATCH: mobile-breadcrumb-single-line  2026-08-19  (Concern R1, Tim)
   CANONICAL CANDIDATE.

   Tim: "the breadcrumbs are quite large and there's an image behind them"
   (registration page, mobile).

   MEASURED @375 on the registration page: the trail is SEVEN items deep
   (Home / Using Joomla! / Using Extensions / Components / Users Component /
   Registration Form). Joomla's ol.mod-breadcrumbs is display:flex and WRAPS,
   so at 15.9px it broke onto five rows and stood 214px tall - pushing the form
   most of a screen down and letting the donor's decorative
   .background-parallax band read as "an image behind the breadcrumbs".

   There is no background image on the breadcrumb or any ancestor - checked to
   the root. The band is a separate absolutely-positioned element that was
   simply showing through a very tall breadcrumb block.

   Keep it to ONE line, horizontally scrollable, at a size that fits.
   ===================================================================== */
@media (max-width: 767px) {
    .mod-breadcrumbs__wrapper .mod-breadcrumbs.breadcrumb {
        flex-wrap: nowrap !important;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        font-size: 12.5px !important;
        padding: 0.35rem 0.6rem !important;
    }
    .mod-breadcrumbs__wrapper .mod-breadcrumbs.breadcrumb::-webkit-scrollbar { display: none; }
    .mod-breadcrumbs__wrapper .mod-breadcrumbs.breadcrumb > li {
        flex: 0 0 auto !important;
        white-space: nowrap !important;
        height: auto !important;
    }
    .mod-breadcrumbs__wrapper .mod-breadcrumbs.breadcrumb > li > a,
    .mod-breadcrumbs__wrapper .mod-breadcrumbs.breadcrumb > li > span {
        white-space: nowrap !important;
    }
}
/* /CS-PATCH: mobile-breadcrumb-single-line */

/* /CS-PATCH: mobile-breadcrumb-single-line */

/* CS-PATCH: mobile-breadcrumb-single-line (addendum)  2026-08-19
   The donor sizes the breadcrumb ANCHORS directly at 24px, which beat the 12.5px
   set on the <ol> - measured olFontSize 12.5px but linkFontSize 24px, so the trail
   still read oversized. Size the items themselves. */
@media (max-width: 767px) {
    .mod-breadcrumbs__wrapper .mod-breadcrumbs.breadcrumb > li,
    .mod-breadcrumbs__wrapper .mod-breadcrumbs.breadcrumb > li > a,
    .mod-breadcrumbs__wrapper .mod-breadcrumbs.breadcrumb > li > span {
        font-size: 13px !important;
        line-height: 1.5 !important;
    }
    .mod-breadcrumbs__wrapper .mod-breadcrumbs.breadcrumb > li > a { text-decoration: none; }
}
/* /CS-PATCH: mobile-breadcrumb-single-line (addendum) */

/* /CS-PATCH: mobile-breadcrumb-single-line (addendum) */

/* CS-PATCH: mobile-breadcrumb-single-line (addendum 2)  2026-08-19
   template.css carries `.breadcrumb > li *  { font-size:24px !important }` and the
   same on the `+ .breadcrumb-item::before` separator, so the <span> INSIDE each
   anchor stayed 24px even after the li and a were sized down. Match that selector. */
@media (max-width: 767px) {
    .mod-breadcrumbs__wrapper .breadcrumb > li *,
    .mod-breadcrumbs__wrapper .breadcrumb-item + .breadcrumb-item::before {
        font-size: 13px !important;
        line-height: 1.5 !important;
    }
}
/* /CS-PATCH: mobile-breadcrumb-single-line (addendum 2) */

/* /CS-PATCH: mobile-breadcrumb-single-line (addendum 2) */

/* =====================================================================
   CS-PATCH: drawer-single-submenu-affordance  2026-08-19  (Concern R1, Tim)
   CANONICAL CANDIDATE.

   Tim: "there's a plus to open a different level of submenus, but there's also
   a down arrow icon. Go with the plus. Get rid of the down arrow."

   MEASURED in the section panel: every parent <li> carries BOTH controls -
   our button.cs-chip (34x32, the "+") AND the donor's own span.btn-toggle
   (16x24, the chevron) - so each item showed two open/close affordances.

   Hide the donor's toggle ONLY where our chip is present, so donors that still
   rely on their own toggle keep it.
   ===================================================================== */
.cs-chip ~ .btn-toggle,
li:has(> .cs-chip) > .btn-toggle {
    display: none !important;
}
/* /CS-PATCH: drawer-single-submenu-affordance */

/* /CS-PATCH: drawer-single-submenu-affordance */

/* =====================================================================
   CS-PATCH: icon-link-no-bs5-underline  2026-08-19  (Concern R1b, Tim)
   CANONICAL CANDIDATE - almost certainly fleet-wide.

   Tim: "Why does the hamburger on the desktop have four lines? instead of the
   recognized three lines."

   It is not the glyph. MEASURED: the <i> renders U+F0C9 in "Font Awesome 6 Free"
   900 - `fa-bars`, which is three bars. The fourth "bar" is a TEXT-DECORATION
   UNDERLINE on the anchor, in rgb(77,77,77) - the same grey as the glyph - so it
   reads as part of the icon.

   Cause is a Bootstrap 2 -> 5 default change. Two rules match the anchor:

       template.css      a { text-decoration: none }        <- donor
       bootstrap.min.css a { text-decoration: underline }   <- BS5, loads LAST

   Bootstrap 5 underlines links by default; Bootstrap 2 did not. The donor never
   had to defend against it, so every BARE ICON LINK in the header picks up a
   stray underline. Under a hamburger it looks like a fourth bar; under the
   account / pencil / search glyphs it is a stray dash.

   Scoped to the donor's header control group - the same list `desktop-header-icons`
   sizes - so ordinary text links keep whatever underline the design intends.
   ===================================================================== */
.open-off-canvas,
.open-register-form,
.v_register,
#v_register,
#v_toggle,
#searchOpenButton,
#cartpanel.cart-button,
.open-off-canvas:hover,
.open-register-form:hover,
.v_register:hover,
#v_register:hover,
#v_toggle:hover,
#searchOpenButton:hover,
#cartpanel.cart-button:hover {
    text-decoration: none !important;
}

/* Any anchor whose entire content is an icon: an underline there is never
   intentional. Kept separate and last so it is easy to lift if a donor ever
   wants it. */
a:has(> i.fa):not(:has(> :not(i))) {
    text-decoration: none !important;
}
/* /CS-PATCH: icon-link-no-bs5-underline */

/* /CS-PATCH: icon-link-no-bs5-underline */

/* =====================================================================
   CS-PATCH: positioned-header-needs-explicit-width  2026-08-19  (AllBusiness)
   CANONICAL CANDIDATE - any donor whose header is absolutely/fixed positioned.

   MEASURED on AllBusiness @375 before this rule:

       header#top-handler   x=20  w=30    <- should be 320
       .container-fluid     x=20  w=30
       .inner-handler       x=20  w=30
       #top / .container-lr / .row        w=0
       #cs-menu-toggle      w=2  h=44     <- burger 2px wide
       .span3 (#site-name-handler) x=-152 w=375
       document.scrollWidth 392 vs 375    <- page draggable sideways

   Cause is OUR canonical rule, not the donor's. `nonresponsive-donor-fluidize`
   sets `width: auto !important` on `#top, #top-handler, #menu-handler, …` to
   unpin fixed-width donors. The donor's own `header#top-handler { width: 100% }`
   loses to it. That is harmless while the header is in normal flow - but this
   donor also sets `#top-handler.fixed { position: absolute }`, and an
   absolutely-positioned block with `width: auto` **shrink-to-fits its content**.
   The content had already collapsed, so the header resolved to 30px and every
   descendant collapsed with it.

   The 12px-off-screen `.span3` is the SAME defect, not a second one: that element
   is `#site-name-handler`, which canonical gives a full-bleed
   `margin-left: calc(50% - 50vw); width: 100vw`. The `50%` term resolves against
   its containing block - 30px instead of 320px - so the full-bleed maths pulled it
   to x=-152 and produced the sideways pan. Restore the header's width and the
   full-bleed resolves correctly; no separate rule is needed for it.

   Take back only what `width: auto` gave away, and only for the positioned case,
   so flow-positioned donors keep the unpinning behaviour.
   ===================================================================== */
header#top-handler.fixed,
#top-handler.fixed {
    left: 0 !important;
    right: 0 !important;
    width: 100% !important;
    box-sizing: border-box !important;
}
/* /CS-PATCH: positioned-header-needs-explicit-width */

/* =====================================================================
   CS-PATCH: positioned-header-needs-explicit-width (addendum: pin to top)
   2026-08-19  (AllBusiness R1, Tim)

   Tim: "there is a huge white space at the top… the top bar and the row of icons
   is appearing over the slideshow instead of at the top of it."

   Both symptoms are ONE bug. MEASURED @375:

       body                 padding-top: 175px     <- space reserved for the header
       header#top-handler   position: absolute, top: AUTO, y=175, z-index 99
       .camera_wrap         y=175                  <- slideshow starts here too

   `#top-handler.fixed { position: absolute }` (template.css) takes the element out
   of flow, and **nothing sets `top`**. An absolutely-positioned box with `top: auto`
   stays at its STATIC position - which is *after* the 175px padding our own
   `markFixedHeader()` adds to reserve room for it. So the reserved strip stayed
   empty (the white band) and the header landed exactly on top of the slideshow,
   winning on z-index 99.

   Pin it to the top of its containing block. The reserved padding then does its job:
   header occupies 0..175, content starts at 175, nothing overlaps.
   ===================================================================== */
header#top-handler.fixed,
#top-handler.fixed {
    top: 0 !important;
}
/* /CS-PATCH: positioned-header-needs-explicit-width (addendum: pin to top) */

/* =====================================================================
   CS-PATCH: transparent-overlay-header-reserves-nothing  2026-08-19
   (AllBusiness R1, Tim)   CANONICAL CANDIDATE.

   Tim: "there is a huge white space at the top… the top bar and the row of icons
   is appearing over the slideshow instead of at the top of it."

   The white band is OURS. Canonical reserves room for a fixed header with
   `body { padding-top: var(--cs-header-h, 0px) }`, and cs-mobile.js measures the
   header into that variable. That is right for a SOLID fixed header, which would
   otherwise cover the first 175px of content.

   This donor's header is not that. MEASURED:

       header#top-handler  background-color: rgba(0, 0, 0, 0)   <- transparent
       home page:      --cs-header-h 175px  ->  body padding-top 175px
       interior page:  --cs-header-h 0px    ->  body padding-top 0px, correct

   It is a TRANSPARENT OVERLAY header: white type designed to sit on the hero
   image, going solid-dark on scroll (which is also why Tim saw "a lot of the
   buttons are invisible" on desktop until the bar turns black). Reserving space
   for it pushes the hero down and leaves an empty white strip, and the header then
   lands on the middle of the slideshow rather than at the top of it.

   The donor's own template.css sets `body { padding-top: 0 }` - three times. This
   restores that. Interior pages are unaffected: they already compute 0.

   General rule worth promoting: reserve space for a fixed header only when it has
   an opaque background. A transparent overlay header must reserve nothing.
   ===================================================================== */
body {
    padding-top: 0 !important;
}
/* /CS-PATCH: transparent-overlay-header-reserves-nothing */

/* =====================================================================
   CS-PATCH: mobile-header-in-flow-not-overlay  2026-08-19  (AllBusiness R1, Tim)
   CANONICAL CANDIDATE.

   Tim: "the top bar and the row of icons is appearing over the slideshow instead
   of at the top of it. Slideshow showing up behind it."

   The donor's header is an absolutely-positioned TRANSPARENT overlay: white type
   on the hero, going solid on scroll. That works on desktop, where the header is
   only a logo row over a tall hero.

   On a phone it does not, because the mobile chrome we build adds an OPAQUE
   utility row (burger / account / search / social) inside that same header. An
   opaque strip inside an overlay header sits directly on the slideshow and covers
   its caption - MEASURED: header 0..175 with the icon row at 85..152, painting
   over the hero text.

   Two rows of chrome is not an overlay any more, it is a header. Put it back in
   normal flow at phone width so both rows stack ABOVE the slideshow, which is
   where Tim expects them and what the icons need to stay legible. Desktop keeps
   the donor's overlay-and-go-solid behaviour untouched.

   Supersedes the `top: 0` pin below 980px - a static element ignores `top`.
   ===================================================================== */
@media (max-width: 979px) {
    header#top-handler.fixed,
    #top-handler.fixed,
    header#top-handler {
        position: static !important;
        top: auto !important;
    }
}
/* /CS-PATCH: mobile-header-in-flow-not-overlay */

/* =====================================================================
   CS-PATCH: mobile-header-in-flow-not-overlay (addendum: donor's solid skin)
   2026-08-19  (AllBusiness R1, Tim)

   Once the header is in normal flow at phone width it is no longer sitting on the
   hero image, so the donor's WHITE brand type lands on white and disappears -
   MEASURED: `#site-name-handler a` colour rgb(255,255,255) on a transparent header
   over the page's white background. This is the same root cause as Tim's desktop
   note that "a lot of the buttons are invisible" until the bar turns black.

   The donor already ships the answer. It has two header skins:

       #top-handler.fixed                        background: transparent   (overlay)
       header#top-handler.static > .container-fluid   background: rgb(29,31,33)  (solid)

   Wear the donor's own solid skin at phone width - its own colour, not one we
   invent - so the white type is legible exactly as it is in the scrolled state.
   ===================================================================== */
@media (max-width: 979px) {
    header#top-handler > .container-fluid,
    header#top-handler.fixed > .container-fluid {
        background: rgb(29, 31, 33) !important;
    }
}
/* /CS-PATCH: mobile-header-in-flow-not-overlay (addendum: donor's solid skin) */

/* =====================================================================
   CS-PATCH: topnav-nonlink-items-match-siblings  2026-08-19  (AllBusiness R1, Tim)
   CANONICAL CANDIDATE - any donor whose top nav styles <a> only.

   Tim: "the template features menu item… does not have the same formatting as the
   other links on there. I recognize this as a goofy Joomla problem."

   Correct diagnosis. MEASURED in `.menu-nav > li` at 1280:

       Home / Blog / Using Joomla! / Contact Us   <a>    white  16px/400  h=36
       Template Features                         <span>  rgb(60,60,60) 12px/300  h=17

   "Template Features" (menu id 487) is `type = heading` - the ONLY non-component
   item among 85 in that menu - so Joomla emits
   `<span class="mod-menu__heading nav-header">` instead of an `<a>`.

   ⚠️ Tim's proposed fix - switch it to a System Separator - would NOT have been
   enough on its own, and it is worth knowing why before anyone tries it:

     * a separator renders as `<span class="mod-menu__separator separator">`, still
       a SPAN, and this donor styles `.menu-nav li a` - the ANCHOR - so a span picks
       up none of it either way;
     * the donor's only `.separator` rules are scoped to
       `.moduletable_menu ul.menu li.separator` (SIDEBAR menu modules) and set
       `font-size: 75%`, which would have made it smaller still.

   So fix the real cause: give non-link top-level items the same skin as their
   sibling links. This works whether the item is a heading OR a separator, so Tim
   can still change the type for semantic reasons without it regressing.
   ===================================================================== */
.menu-nav > li > span.mod-menu__heading,
.menu-nav > li > span.nav-header,
.menu-nav > li > span.mod-menu__separator,
.menu-nav > li > span.separator {
    display: block !important;
    color: inherit !important;
    font-size: inherit !important;
    font-weight: inherit !important;
    line-height: inherit !important;
    padding: 0 15px !important;
    margin: 0 !important;
    text-transform: inherit !important;
}
/* /CS-PATCH: topnav-nonlink-items-match-siblings */

/* =====================================================================
   CS-PATCH: topnav-nonlink-items-match-siblings (addendum: real values)
   2026-08-19  (AllBusiness R1, Tim)

   The first pass used `inherit`, which was wrong and MEASURED as wrong: only
   `padding` matched. `inherit` takes from the parent `<li>`, and the `<li>` never
   carries the menu skin - the donor puts it on `.menu-nav li a`, the ANCHOR. So
   the span kept rgb(60,60,60) / 12px / 300 while its siblings were white / 16px /
   400.

   Set the values the anchors actually compute to. MEASURED on the sibling links:

       color rgb(255,255,255)   font-size 16px   font-weight 400   height 36px

   ⚠️ These are this donor's numbers, which is acceptable in a per-template bridge
   but is NOT what a canonical version should do - canonical would need to derive
   them from the sibling anchor rather than hardcode a palette. Noted so nobody
   promotes this block verbatim.
   ===================================================================== */
.menu-nav > li > span.mod-menu__heading,
.menu-nav > li > span.nav-header,
.menu-nav > li > span.mod-menu__separator,
.menu-nav > li > span.separator {
    color: #fff !important;
    font-size: 16px !important;
    font-weight: 400 !important;
    line-height: 36px !important;
}
/* /CS-PATCH: topnav-nonlink-items-match-siblings (addendum: real values) */

/* =====================================================================
   CS-PATCH: header-solid-skin-all-widths  2026-08-20  (AllBusiness R1b, Tim)

   Tim: "on the home page and the desktop view, the header is still white… and the
   buttons are white on white."

   Ours again. MEASURED @1280 and @1600:

       #top-handler                       background rgb(255,255,255)
       .open-register-form / .v_register / #searchOpenButton   color rgb(255,255,255)

   The donor sets `#top-handler.fixed { background: transparent !important }` for its
   overlay state. Canonical then adds

       #top-handler.fixed, #top-handler.scrolled { background: rgb(255,255,255) }

   which paints the OVERLAY state white as well as the scrolled one - and the donor's
   control icons are white, so they vanish into it.

   The donor already ships the right colour for a solid header: its `.static` skin is
   `rgb(29,31,33)`. Use that at every width, matching what the mobile header already
   wears and what Tim described approvingly on scroll ("the top bar turns black and a
   lot of the links show").
   ===================================================================== */
#top-handler,
#top-handler.fixed,
#top-handler.scrolled,
header#top-handler.fixed > .container-fluid,
header#top-handler > .container-fluid {
    background: rgb(29, 31, 33) !important;
}

/* =====================================================================
   CS-PATCH: header-icon-label-needs-room-above-1450  2026-08-20  (AllBusiness R1b)
   CANONICAL CANDIDATE.

   Tim: "my account and register and search and icons there are all overlapping each
   other."

   Width-dependent, which is why it did not show at 1280. The donor hides the labels
   only below its own breakpoint:

       @media (max-width: 1450px) { .tl-label { display: none } }

   Above that the labels appear - but canonical's `desktop-header-icons` sends any
   control containing an `<i>` down its ICON-BUTTON branch, `width/height: 32px;
   padding: 0`. A visible label then has nowhere to go. MEASURED @1600:

       .open-register-form  box 32x32  content overflow 17px  label right edge 1338 vs box end 1321
       .v_register          box 32x32  content overflow 20px  label right edge 1391 vs box end 1371

   …so each label spills into the neighbour that starts ~1px later, which is the
   overlap Tim sees. Below 1450 the labels are hidden and the hard box is correct, so
   this is scoped to exactly the range where a label is actually rendered: size to
   content, keep the 32px floor and the shared midline.
   ===================================================================== */
@media (min-width: 1451px) {
    .open-register-form:has(> i),
    .v_register:has(> i),
    #v_register:has(> i),
    #v_toggle:has(> i),
    #searchOpenButton:has(> i) {
        width: auto !important;
        min-width: 32px !important;
        padding-left: 8px !important;
        padding-right: 8px !important;
    }
}

/* =====================================================================
   CS-PATCH: burger-bars-contrast-on-dark-header  2026-08-20  (AllBusiness R1b, Tim)

   Tim: "the top hamburger is a very light gray on black. It's hard to see. It should
   be white."

   MEASURED: the three `.cs-burger-bars > span` paint `rgb(51,51,51)` against the
   header's `rgb(29,31,33)` - about as close to invisible as two greys get. The
   button's own `color` is white, but the bars are drawn with background-color, so
   the white never reaches them.

   This is the symptom already recorded as outstanding bridge debt: "the second
   hamburger's bars rendering black (rgb(51,51,51)) instead of white - observed on
   Aeon and reported by Tim on Coga". Same defect, third template.
   ===================================================================== */
.cs-burger-bars > span,
.cs-burger-bars > i,
#cs-menu-toggle .cs-burger-bars > span {
    background-color: #fff !important;
}

/* =====================================================================
   CS-PATCH: drawer-nonlink-items-match-siblings  2026-08-20  (AllBusiness R1b, Tim)

   Tim: "after clicking the big hamburger… the template features menu item does have
   different formatting."

   Distinct from the desktop fix: `topnav-nonlink-items-match-siblings` is scoped to
   `.menu-nav`, and the phone drawer is a CLONE with class `.cs-drawer-menu`, so the
   rule never reached it.

   In the drawer it is closer than it looks - MEASURED colour rgb(43,110,168),
   17px/300 and padding 7px 14px all already match its sibling links. The only
   difference is the box:

       drawer link  h = 51
       drawer span  h = 37

   so the row sits short and reads as a different item. Give it the same box height
   and centre the text in it.

   ⚠️ 51px is this donor's row height, not a universal one. A canonical version must
   measure the sibling anchor rather than carry the number.
   ===================================================================== */
.cs-drawer-menu > li > span.mod-menu__heading,
.cs-drawer-menu > li > span.nav-header,
.cs-drawer-menu > li > span.mod-menu__separator,
.cs-drawer-menu > li > span.separator {
    display: flex !important;
    align-items: center !important;
    min-height: 51px !important;
}
/* /CS-PATCH: AllBusiness R1b */

/* =====================================================================
   CS-PATCH: topnav-nonlink-items-match-siblings (addendum 2: opacity)
   2026-08-20  (AllBusiness R1c, Tim)

   Tim: "the template features menu header still does not have the same CSS as the
   other top menu items there. I can see that it is whiter."

   Correct, and it is not the colour - every item computes rgb(255,255,255).
   MEASURED opacity on `.menu-nav > li > a|span`:

       Home (current)      1
       Blog                0.8
       Using Joomla!       0.8
       Template Features   1     <- ours, too bright
       Contact Us          0.8

   The donor dims non-current items with opacity, and my earlier pass matched
   colour/size/weight/height but not that. "Template Features" is not the current
   item, so it belongs at 0.8 with its siblings.
   ===================================================================== */
.menu-nav > li:not(.current):not(.active) > span.mod-menu__heading,
.menu-nav > li:not(.current):not(.active) > span.nav-header,
.menu-nav > li:not(.current):not(.active) > span.mod-menu__separator,
.menu-nav > li:not(.current):not(.active) > span.separator {
    opacity: 0.8 !important;
}

/* =====================================================================
   CS-PATCH: drawer-nonlink-items-match-siblings (addendum: fill the row)
   2026-08-20  (AllBusiness R1c, Tim)

   Tim: "that same menu header does not have the same formatting on the mobile menu.
   You can see that the plus sign, it that is off to the left and the plus sign is
   not at the far right."

   Caused by my own previous fix. Both parent rows are `display: flex`, and the
   `+` chip is a SIBLING of the label. MEASURED:

       Using Joomla!      <a>    width 332px   chip x -38   <- fills, chip at far right
       Template Features  <span> width 170px   chip x -200  <- shrank to content

   Giving the span `display: flex` (to centre it vertically) also made it a
   shrink-to-fit flex ITEM, so it stopped filling the row and the chip collapsed in
   next to the text instead of being pushed to the right edge.

   Make it fill the row the way the anchor does. The anchor is `display: block` with
   `width: 332px` for the same reason - it grows into the space the chip does not use.
   ===================================================================== */
.cs-drawer-menu > li > span.mod-menu__heading,
.cs-drawer-menu > li > span.nav-header,
.cs-drawer-menu > li > span.mod-menu__separator,
.cs-drawer-menu > li > span.separator {
    flex: 1 1 auto !important;
    width: auto !important;
    min-width: 0 !important;
}
/* /CS-PATCH: AllBusiness R1c */

/* =====================================================================
   CS-PATCH: drawer-nonlink-items-match-siblings (addendum 2: text indent)
   2026-08-20  (AllBusiness R1d, Tim)

   Tim: "the left alignment of that menu header is not in line with the other menus
   here on the mobile."

   The BOXES were already aligned - an earlier check compared element rects and
   reported 0 offset for every row, which is why this survived two passes. The
   difference is one level deeper: Joomla wraps a link's label in its own element,
   and a heading has no such wrapper.

   MEASURED, text edge relative to the drawer (via a Range over the text node, not
   the element box):

       Home / Blog / Using Joomla! / Contact Us
           <a> padding-left 14px  +  <span class="link-no-image"> padding-left 14px
           -> text at 28px
       Template Features
           <span> padding-left 14px  +  bare text node
           -> text at 14px

   Exactly the 14px Tim can see. Add the inner wrapper's indent to the heading so
   the TEXT lines up, not just the box.

   ⚠️ 28px is this donor's 14 + 14. A canonical version must read the sibling's
   inner label padding rather than carry the total - and must compare the TEXT
   rect, since the element boxes match either way.
   ===================================================================== */
.cs-drawer-menu > li > span.mod-menu__heading,
.cs-drawer-menu > li > span.nav-header,
.cs-drawer-menu > li > span.mod-menu__separator,
.cs-drawer-menu > li > span.separator {
    padding-left: 28px !important;
}
/* /CS-PATCH: drawer-nonlink-items-match-siblings (addendum 2: text indent) */

/* CS-PATCH: drawer-nonlink-items-match-siblings (addendum 3: beat the ID selector)
   2026-08-20  (AllBusiness R1d, Tim)

   The 28px indent from addendum 2 was present but LOSING. Canonical carries

       #cs-drawer .cs-drawer-menu a, #cs-drawer .cs-drawer-menu span { padding: 7px 14px !important }

   an ID-led selector, so `!important` alone did not help - my `.cs-drawer-menu > li >
   span.nav-header` is (0,3,1) against its (1,1,1) and the ID wins. Re-declare with the
   same `#cs-drawer` prefix so the heading actually gets the wrapper indent its sibling
   links have. */
#cs-drawer .cs-drawer-menu > li > span.mod-menu__heading,
#cs-drawer .cs-drawer-menu > li > span.nav-header,
#cs-drawer .cs-drawer-menu > li > span.mod-menu__separator,
#cs-drawer .cs-drawer-menu > li > span.separator {
    padding-left: 28px !important;
}
/* /CS-PATCH: drawer-nonlink-items-match-siblings (addendum 3) */

/* =====================================================================
   CS-PATCH: header-icon-label-gap  2026-08-20  (AllBusiness R1e, Tim)
   CANONICAL CANDIDATE.

   Tim: "I need just a little bit of space between the icon and the text that it
   goes with. my account and register… even two pixels would be fine."

   MEASURED @1600 before: gap exactly 0 on both - the icon's right edge and the
   label's left edge share a coordinate.

       .open-register-form   icon right 1205.6   label left 1205.6   gap 0
       .v_register           icon right 1316.3   label left 1316.3   gap 0

   `desktop-header-icons` makes these controls `inline-flex` but never sets `gap`,
   so glyph and label butt together the moment the donor reveals the label above
   1450px.

   4px - small enough to stay "teeny", enough to separate the glyph from the word.
   No media query needed: with the label hidden there is only one flex item, so the
   gap renders nothing below 1451px.
   ===================================================================== */
.open-register-form,
.v_register,
#v_register,
#v_toggle,
#searchOpenButton,
#cartpanel.cart-button {
    gap: 4px;
}
/* /CS-PATCH: header-icon-label-gap */
