/* CANONICAL-REV: 2026-08-18.62
   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, nav#menu, #menu,
  .social-links-handler, div.menupanel, a#menupanel, #quick-menu { 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 {
    max-width: none !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 ---- */
  #cs-menu-toggle,
  body.cs-panel-open #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; }
  #cs-utility-row > button[aria-expanded="true"] .cs-burger-bars span { background: #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: #fff !important; }
    #cs-secondary-toggle > i { color: #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: #fff !important;
    }
    #cs-register > i { color: #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: #fff !important;
    }
    #vmCartModule .show-cart a i,
    #cart-position a.cart-button i { color: #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) */
    .open-off-canvas,
    .open-register-form,
    .v_register,
    #searchOpenButton,
    #cartpanel.cart-button {
        display: inline-flex !important;
        align-items: center !important;
        justify-content: center !important;
        width: 32px !important;
        height: 32px !important;
        line-height: 1 !important;
        vertical-align: middle !important;
        padding: 0 !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;
    }
    /* 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,
    #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; }
    /* 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.) */
  #top .log-panel, .top .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. The container insets 20px per side. */
  #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: calc(100% + 40px) !important; max-width: none !important;
    margin-left: -20px !important; margin-right: -20px !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: mobile-burger-margin-zero  2026-08-18  (Magnetic grad walkthrough, Tim)
   *** CANONICAL CANDIDATE — this is a gap in the canonical rule, not a
   Magnetic quirk. ***

   The canonical block sets `padding: 0` on #cs-menu-toggle but never `margin`.
   For an ABSOLUTELY POSITIONED element the used `top` places the MARGIN edge,
   so any inherited margin pushes the border box down by exactly that much and
   --cs-burger-top is silently off by the margin.

   MEASURED on Magnetic @375: burger computed `margin: 10px`, `top: 36px`
   (correctly published by burgerMidline()), yet offsetTop resolved to 46 and
   the burger's midline sat 10px below the logo's. Zeroing the margin makes
   the published midline land exactly.
   ===================================================================== */
@media (max-width: 767px) {
    #cs-menu-toggle { margin: 0 !important; }
}

/* =====================================================================
   CS-PATCH: mobile-logo-burger-clearance  2026-08-18  (Magnetic grad walkthrough, Tim)
   Companion to the burgerMidline() port in js/cs-mobile.js.

   Tim, R1: the primary burger "is well down. It's not to the right of the
   template name."

   burgerMidline() puts the burger back on the logo's midline, but the burger is
   `position:absolute; right:8px` (canonical), so it is OUT OF FLOW and the logo
   row has no idea it is there. Two things then have to be true for Skill 33
   grad-walkthrough item 4 ("logo row is ONE row: logo left, primary burger
   right, on the same line"):

     1. the logo must be LEFT aligned - the donor centres it
        (#site-name-handler is `display:flex; justify-content:center`), which on
        a phone walks it right, straight under the burger;
     2. it must be narrow enough to clear the burger's 66px footprint
        (56 wide + 8 inset + gap) out of #top's 236px padding box at 375.

   MEASURED @375 before: logo x 62..258 vs burger x 242..300 -> overlap.
   After (capped 140): logo 90..230, burger 242..300, a 12px gap.

   ⚠️ Do NOT "fix" this by left-aligning the logo. Canonical deliberately
   centres it on a phone -
       @media (max-width:767px) { #site-name-handler,
                                  #top-handler #site-name-handler
                                  { justify-content: center !important } }
   - and that selector list carries a (0,2,0) branch, so a plain
   `#site-name-handler` override loses on specificity no matter how late it is
   appended. Capping the width keeps canonical's centring and still clears the
   burger, which is the outcome both rules actually want.

   The images are sized in the markup, so the cap has to reach them, not just
   the wrapper; height:auto keeps the aspect ratio, and burgerMidline()
   re-measures through its ResizeObserver so the burger follows the new height.
   ===================================================================== */
@media (max-width: 767px) {
    #top #sn-position,
    #site-name-handler #sn-position {
        max-width: 140px !important;
    }
    #top #sn-position img,
    #site-name-handler #sn-position img {
        max-width: 100% !important;
        height: auto !important;
    }
}

/* =====================================================================
   CS-PATCH: mobile-donor-social-rail-retire  2026-08-18  (Magnetic grad walkthrough, Tim)
   *** CANONICAL CANDIDATE *** — same family as the existing
   `.social-links-handler ... { display:none }` mobile rule; this donor just
   wraps its rail differently, so that rule never reached it.

   Tim, R1: "the social media icons are stacked off on the left hand side."

   MEASURED on Magnetic @375 before: ul#social-links computes
       position: fixed; top: 80px; left: 0; z-index: 9999
   Its parent div#socialModal is a CLOSED donor modal (height 0), but because
   the ul is `fixed` it is out of that flow entirely - so it ignores the modal
   being closed and paints as a 40x120 vertical rail hard against the left edge
   of the viewport, on top of the body copy. Three stacked chips, permanently.

   It is also REDUNDANT on a phone: cs-mobile.js already clones the same anchors
   into #cs-social-panel, which canonical styles and opens from the utility
   row's share control (`body.cs-social-open #cs-social-panel { display:block }`)
   as a centred row. Retiring the stray rail leaves exactly one copy, in the bar.

   Scoped to <=767 only - the fixed rail is a deliberate DESKTOP design feature
   of this donor and is left completely untouched there.
   ===================================================================== */
@media (max-width: 767px) {
    #socialModal,
    #socialModal #social-links,
    body > ul#social-links {
        display: none !important;
    }
    /* the clone in the panel must never inherit the rail's fixed positioning */
    #cs-social-panel #social-links,
    #cs-social-panel .cs-social-list {
        position: static !important;
        top: auto !important; left: auto !important;
        right: auto !important; bottom: auto !important;
        z-index: auto !important;
        float: none !important;
    }
}

/* =====================================================================
   CS-PATCH: inline-svg-icon-size-cap  2026-08-19  (Diversity R1, Tim)
   *** CANONICAL CANDIDATE — CONFIRMED ON MAGNETIC AND LOGIC, BOTH RELEASED. ***

   Joomla 6's WebAuthn plugin renders its "Log in with a passkey" button as:

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

   The SVG carries a viewBox but **no width/height attributes and no intrinsic
   size**, so with no author rule it expands to fill its container. Every ET donor
   predates inline SVG icons and none of them size one.

   MEASURED on the login page:
       Diversity  svg#Passkey  269x269  inside a 282-wide form
       Magnetic   svg#Passkey  244x244  inside a 300x280 button   (RELEASED)
       Logic      svg#Passkey  276x276  inside a 312x324 button   (RELEASED)

   It paints as a large black passkey glyph overlapping the surrounding text -
   which is what Tim saw as black shapes over the log-in area.

   Cap inline SVGs that are used as button icons. Scoped to elements with a
   viewBox and no explicit dimensions so real inline artwork is untouched, and
   `fill: currentColor` so the glyph takes the button's ink instead of raw black.
   ===================================================================== */
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;
}
