/* CANONICAL-REV: 2026-08-11.31
   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"]: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; }
#ph-camera-slideshow-handler { min-height: inherit; } /* CS 2026-09-07 swiper handler height (petoutside/Intense) */

/* =====================================================================
   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: 100% !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; }
  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:not(ul[class*="product"] > *), .row .span1:not(ul[class*="product"] > *) { width: 5.982905983%; }
    .row-fluid .span2:not(ul[class*="product"] > *), .row .span2:not(ul[class*="product"] > *) { width: 14.52991453%; }
    .row-fluid .span3:not(ul[class*="product"] > *), .row .span3:not(ul[class*="product"] > *) { width: 23.07692308%; }
    .row-fluid .span4:not(ul[class*="product"] > *), .row .span4:not(ul[class*="product"] > *) { width: 31.62393162%; }
    .row-fluid .span5:not(ul[class*="product"] > *), .row .span5:not(ul[class*="product"] > *) { width: 40.17094017%; }
    .row-fluid .span6:not(ul[class*="product"] > *), .row .span6:not(ul[class*="product"] > *) { width: 48.71794872%; }
    .row-fluid .span7:not(ul[class*="product"] > *), .row .span7:not(ul[class*="product"] > *) { width: 57.26495726%; }
    .row-fluid .span8:not(ul[class*="product"] > *), .row .span8:not(ul[class*="product"] > *) { width: 65.81196581%; }
    .row-fluid .span9:not(ul[class*="product"] > *), .row .span9:not(ul[class*="product"] > *) { width: 74.35897436%; }
    .row-fluid .span10:not(ul[class*="product"] > *), .row .span10:not(ul[class*="product"] > *) { width: 82.90598291%; }
    .row-fluid .span11:not(ul[class*="product"] > *), .row .span11:not(ul[class*="product"] > *) { width: 91.45299145%; }
    .row-fluid .span12:not(ul[class*="product"] > *), .row .span12: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,
.camera_pag li, .camera_loader {
    box-sizing: content-box;
}

/* =====================================================================
   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.
   ===================================================================== */
@media (max-width: 767px) {
    #cs-menu-toggle {
        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; }

/* gradfix-desc 2026-10-14 */
/* Category description: no side padding (template-managed space; top/bottom kept). */
.category_description { padding-left: 0 !important; padding-right: 0 !important; }
