/* CANONICAL-REV: 2026-10-19.131 (HyperShop: .130 + the .131 addtocart-bar-qty-own-height block)
   .130 (2026-10-16): adopted-cart-panel-ink on the outermost panel only, zero specificity (FlatStore's dark panel went #333 on #363636).
   .129 (2026-10-16): phone cart panel centring raised to (1,1,2) - Flexo's donor `div.panel2 {left:auto !important}` beat it (panel half off-screen).
   .128 (2026-10-16): article-pager-ends (article Prev/Next bunched at the left; Next now at the far right).
   .127 (2026-10-16): adopted-cart-panel-ink (phone cart panel inherited the chip's white ink: an empty-looking cart).
   .126 (2026-10-16): password-toggle-inline (Joomla's show-password eye wrapped onto its own line under the password field on phones).
   .125 (2026-10-16): cart-count-badge-desktop (a count badge on the desktop cart icon whenever the cart holds items; Tim: "that number should always show ... a thing for all templates with carts").
   .124 (2026-10-15): cart-chip-same-box (the adopted cart chip lost its 1px ring: it made the chip 46px, grey-ringed and out of line with its 44px neighbours; Tim on Serenity: "the basket icon is not centered in its square").
   .123 (2026-10-13): phone-search-input-submit (input[type=submit] search button was blank white on phones) + cart-count-badge (number on the phone cart chip) - SparkStore, Tim.
   .122 (2026-10-10): vmmenu-sublists-phone (VirtueMart Category module subcategories open on phones again; the menu-module hide caught their ul.menu).
   .121 (2026-09-30): phone-pagination-wraps (Joomla pagination fits phones; 15 ET stages had cut-off buttons).
   .120 (2026-09-30): phone-vm-cart-page-fits (Tim: Captiva cart didn't fit; one block for all VM cart markups; the icon-only remove button applies at all widths).
   .119 (2026-09-30): phone-account-content-first rebuilt (component-level markers only; cheaper selectors; .118 slowed slick/Owl init on VMT Infinity).
   .118 (2026-09-29): + phone-account-content-first (Tim's rule: no modules above account pages on phones except a store category menu; breadcrumbs stay). ET rollout only - VMT stays pinned at .116.
   .117 (2026-09-28, agU): ET-only rollout - VMT stays pinned at .116 pending a 43-stage VMT A/B of the .117 edits
   (desktop-header-icons text-link exclusion C1, 980-1199 cap !important C4, megamenu-link-bs2-content-box C6m).
   The single source for every ET template's bridge. Fixes go HERE, never
   into a template. A template records the rev it was last checked at; when
   this rev moves, every template below it is due a re-check. Per-template
   blocks are debt - see 33-VMT-ET-Template-Modernization-Pipeline.md. */
/*
 * Serenity template — bs2-bs5-bridge.css
 *
 * BS2-to-BS5 visual aliasing for BS2 grid + utility classes that survive in:
 *   (1) custom-HTML module content stored in jos_modules.content
 *   (2) VirtueMart's older view templates that still emit .row-fluid / .span* markup
 *
 * The Skill 26 modernize-stack.py helper rewrites BS2 classes in index.php, but
 * MODULE CONTENT is data not code — those BS2 classes survive intact. Without
 * this bridge, BS5 doesn't know what .row-fluid / .spanN mean, so module rows
 * collapse to stacked-vertically block elements.
 *
 * This file is loaded LAST in the <head> CSS stack so its rules win the cascade.
 *
 * Reference: Avant's avant-fixups.css (early Phase 3b version) — same pattern
 * applied to module content that contains legacy markup.
 */

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

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

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

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

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

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

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

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

/* CS-PATCH: span12-never-gutters (2026-09-14, Quadra)
   A .span12 is full-width by definition, so it can NEVER carry a grid gutter --
   any margin-left pushes its right edge past the container. The group rule above
   is :where()-wrapped (zero specificity) and only zeroes :first-child, so a donor's
   own BS2 `[class*="span"] { margin-left: 30px }` (0,1,0, and at >=1200px in
   responsive.css) wins on every span12 that is NOT first-child. Measured on Quadra:
   a standalone `.tf-module.span12` stack rendered x=138 right=1308 against a
   container right edge of 1278 -- a 30px overflow -- while its first-child twin sat
   correctly at x=108 right=1278. Real specificity is required here; the bridge loads
   last, so 0,1,0 is enough to win the tie without !important. */
.span12 { margin-left: 0; }
/* ...and at 0,2,0 for the in-row case: a `.row`/`.row-fluid` ANCESTOR (not just a
   parent) brings `.row [class*="span"] { margin-left: 2.5641% }` into play, which at
   the 1170 grid is exactly the same 30px and outranks a bare `.span12`. Measured on
   Quadra: the offending block sat in a footer `.row`. Mirrors the width idiom already
   used below for `.row-fluid .span12, .row .span12`. */
.row-fluid .span12, .row .span12 { margin-left: 0; }

/* CS-PATCH: wrapped-span-first-gutter  2026-09-27  (Essential grad prep)
   Spans inside a PLAIN wrapper that is itself a .row child (`#footer-line.row > #foo-left-right >
   .span6 + .span6`). BS2 widened the .row by its -30px margin, so both spans could carry the donor's
   `[class*="span"] { margin-left: 30px }` and still fit: live parent 1200 wide, spans at x115/x715.
   Under BS5 the wrapper is only the container's 1170, the :where() first-child zero loses to the donor's
   0,1,0 gutter, and 30+570+30+570 = 1200 > 1170 - the copyright half WRAPPED below the links
   (MEASURED Essential @1400: both halves at x145, second at y+31). Zeroing the first child's gutter at
   real specificity lands them at x115/x715 = live. Fleet A/B on all 37 stage homes: changes ONLY
   Essential (fixed) and Engage (footer span12 185 -> 155 = live's 155). */
.row > div:not([class*="span"]):not([class*="col-"]) > [class*="span"]:first-child { margin-left: 0; }

/* ---------- 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. */

/* CS-PATCH: sticky-header-scrolled-only
   CORRECTED 2026-09-01. The 2026-06-15 note above states the intent exactly -- "when
   SCROLLING over the slideshow / content images the menu blended into the page" -- but
   the selector also matched `.fixed`, which describes POSITIONING, not scroll state.
   Six templates (Armada, Infinity, Nevo, Studio, Theon, XStore) carry `.fixed` from
   page load, so their header was painted solid white while sitting on top of a dark
   hero -- with white logo and white menu text on it. Measured on Studio: #top-handler
   background rgb(255,255,255) on the stage vs rgba(0,0,0,0) on the live donor, and a
   header that read as completely empty in a screenshot.

   None of those donors ever sets `.scrolled`; on scroll they add their OWN classes
   (`dark`, `tiny`) with their own colours -- Armada rgba(15,15,15,.98), Nevo
   rgb(47,49,51), XStore rgba(255,255,255,.99). So the fix is two-part:

     1. drop `.fixed`, so at rest the donor's transparent header is restored;
     2. key the background off `cs-scrolled`, which the canonical chrome JS adds on
        scroll, and wrap it in :where() so it contributes ZERO specificity. Any donor
        rule -- even a single class -- then wins, and templates that already style
        their own scrolled header keep doing so. Templates with no scrolled styling
        at all (Studio, Theon, Infinity) get the readable background the 2026-06-15
        note wanted, and only once scrolled. */

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

#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. */

/* CS-PATCH: sticky-header-scrolled-only
   CORRECTED 2026-09-01. The 2026-06-15 note above states the intent exactly -- "when
   SCROLLING over the slideshow / content images the menu blended into the page" -- but
   the selector also matched `.fixed`, which describes POSITIONING, not scroll state.
   Six templates (Armada, Infinity, Nevo, Studio, Theon, XStore) carry `.fixed` from
   page load, so their header was painted solid white while sitting on top of a dark
   hero -- with white logo and white menu text on it. Measured on Studio: #top-handler
   background rgb(255,255,255) on the stage vs rgba(0,0,0,0) on the live donor, and a
   header that read as completely empty in a screenshot.

   None of those donors ever sets `.scrolled`; on scroll they add their OWN classes
   (`dark`, `tiny`) with their own colours -- Armada rgba(15,15,15,.98), Nevo
   rgb(47,49,51), XStore rgba(255,255,255,.99). So the fix is two-part:

     1. drop `.fixed`, so at rest the donor's transparent header is restored;
     2. key the background off `cs-scrolled`, which the canonical chrome JS adds on
        scroll, and wrap it in :where() so it contributes ZERO specificity. Any donor
        rule -- even a single class -- then wins, and templates that already style
        their own scrolled header keep doing so. Templates with no scrolled styling
        at all (Studio, Theon, Infinity) get the readable background the 2026-06-15
        note wanted, and only once scrolled. */

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

#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;
    height: auto !important;
    object-fit: contain;
    /* 2026-09-28 (.116, C1b): `width:100% !important` + `aspect-ratio:1/1` removed - they upscaled 150px VirtueMart
       thumbs to 210-252px and squared portrait photos (live: natural size). max-width still contains the 585px
       category images Round 9 was written for. A/B 43 VMT stages: 16 improved, 0 regressed. */
}

/* 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;
    height: auto !important;
    object-fit: contain;
    /* 2026-09-28 (.116, C1b): `width:100% !important` + `aspect-ratio:1/1` removed - they upscaled 150px VirtueMart
       thumbs to 210-252px and squared portrait photos (live: natural size). max-width still contains the 585px
       category images Round 9 was written for. A/B 43 VMT stages: 16 improved, 0 regressed. */
}

/* 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 !important; } }
@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) {
    /* 2026-09-26 (Delta release audit): `!important` added. The block's own trap #1
       says these MUST be neutralised, but on donors that emit their stylesheets as raw
       <link>s AFTER Joomla's head include, the donor's equal-specificity
       `#search-position { position:relative }` (Delta layout.css) loads later and wins,
       pinning the "full-width" panel inside the 50px search cell. MEASURED @900:
       panel 60px wide, field 22px, "Search" label spilling out; @1024/1400 fine. */
    #search-position,
    #search-position-handler {
        position: static !important;
    }
    /* ...and one level further up on donors whose header row is `#top > #search-position`
       (Delta, FlatGear). `#top` then becomes the panel's containing block, but it holds only
       floats and computes 0px tall, so `top:100%` resolved to y=0 and the panel sat ON the
       header row, hiding the logo. Static hands the panel to the 90px `.container`.
       MEASURED @768/900/979 with the rule injected: every header element unmoved, page height
       unchanged, panel y 0 -> 90 (directly under the header). `display:flow-root` was the
       alternative and was rejected - it moves #top. */
    /* .102: TABLET ONLY - see the @media (min-width:768px) and (max-width:979px) block below. */
    #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;
    }
}
/* .102 (2026-09-26, Delta release): `#top` static is needed only where the DESKTOP search panel
   is shown at tablet width. At <=767 the phone chrome replaces it, and `#top` is the phone
   burger's positioning context: applied there, .100 dropped the burger from y=14 to y=48
   (centre 70 vs the logo's 36) - caught by the mobile gate. */
@media (min-width: 768px) and (max-width: 979px) {
    #top:has(> #search-position) {
        position: static !important;
    }
    /* .104 (2026-09-26, AllBusiness 6.1.3.2): a third wrapper. The "cl" (cart/login) family wraps
       the search cell in `#cl-handler { position:relative }` (template.css), so the panel's
       left:0/right:0 resolved against that 130px cell. MEASURED @768/900/979: panel 130/138/138
       wide, field 106-114px -> with this rule 564/670/733 (the header row), field 540-709px, panel
       y unchanged, #LoginForm unmoved, closed header pixel-identical beyond slideshow noise.
       Trento and Xeno (same wrapper, not relative) measured no-op. 20 ET donors carry #cl-handler. */
    #cl-handler:has(#searchpanel) {
        position: static !important;
    }
}

@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; }
/* .107 (2026-09-27, Essential grad prep): donors that ship Bootstrap 2.0's own css/bootstrap.css centre
   the modal with `.modal { top: 50%; margin-top: -250px }`. This rule moves top to 10% but left the
   negative margin, so the dialog opened 250px higher: MEASURED Essential @1400 #LoginForm y=-160,
   username field at y=-71 (off screen) vs live y=200. A negative margin never makes sense beside a
   10% top, so zero it with the same weight. */
div.modal.fade.show, .modal.fade.show { top: 10%; margin-top: 0; }
.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: phone-absolute-header-stretch  2026-09-28 (.114, Q21 A/B by agM)
     width:auto on a position:absolute #top-handler is shrink-to-fit: HighLevel's header
     measured 30px wide and Render's 60px @375 (logo x=-85 / x=-72, burger 2px / 30px wide,
     chip row at x=-172 / x=-157). Pinning both edges makes auto = the full row again; on
     a static or relative header left/right 0 are inert.
     A/B, 37 ET stages x 375/320 (+ scrolled state @375): 0 changed. Alone (per-template
     header-inflow blocks removed): HighLevel header 30 -> 375 wide, burger 44x44 at x=308,
     chip row x=0; Render 60 -> 375, logo x=86, burger 44x44 (its header still overlays the
     slide card by design - Render keeps its own in-flow block for that). */
  #top-handler { left: 0 !important; right: 0 !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):not(:where(.cs-hdr-donor-row *)),
  #top-handler .row:has(> #search-position):not(:where(.cs-hdr-donor-row *)),
  .row:has(> #search-position):not(:where(.cs-hdr-donor-row *)) {
    display: flex !important;
    align-items: center !important;
  }
  .row:has(> #search-position):not(:where(.cs-hdr-donor-row *)) > #search-position { float: none !important; }
  /* .109 (2026-09-27) OPT-OUT `cs-hdr-donor-row` (et-body-tag.py): donors that lay the header row out
     with FLOATS / inline-blocks. Flex ignores float:right and text-align:center, so this block moved
     their login + search: MEASURED @1400 FlatGear log-panel 1277 -> 806, search 1360 -> 774; NewsCast
     search 1219 -> 569; Gravity whole row left-packed (menu 387 -> 0). With the opt-out they sit within
     ~15-30px of live (icon widths). Released templates using this row (Encore, Acceler8, Oxygen,
     Energize, Progressive, Nexxy, Clarity) are untouched - the class is only added where measured.
     canonical desktop-header-icons pins #top-quick-nav display:flex (block-level); in a donor row it
     must stay in the line, so inline-flex. */
  .cs-hdr-donor-row #top-handler #top #top-quick-nav { display: inline-flex !important; vertical-align: middle; }
}

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

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

@media (max-width: 767px) {

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

  /* The donor's menu MODULES also render in flow on inner pages - a 6-item,
     210px-tall ul.menu sat above the article on Encore's single-article page, and
     the footer menu wrapped "Contact Us" onto two lines. Navigation on mobile is
     the two hamburgers; these are the source the drawer and secondary panel are
     built from, so once cloned they have no reason to paint. */
  /* CS-PATCH: vmmenu-sublists-phone  2026-10-10  (Flexi pre-sales report). VirtueMart's
     Category module renders its SUBcategory lists as ul.menu inside ul.VMmenu. The hide
     below caught them, so on phones the arrow flipped VmOpen but the list never opened
     (and VMmenu is not cloned into the drawer, so subcategories were unreachable).
     VirtueMart's own inline display + slideDown control those lists again. */
  .moduletable ul.menu:not(.VMmenu ul), .moduletable_menu ul.menu:not(.VMmenu ul), .module-content ul.menu:not(.VMmenu ul),
  #footer-handler ul.menu:not(.VMmenu ul), #footer ul.menu:not(.VMmenu ul), footer ul.menu:not(.VMmenu ul),
  #sidebar ul.menu:not(.VMmenu ul), .sidebar ul.menu:not(.VMmenu ul) { 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; }
  /* CS-PATCH: menu-module-orphan-title  2026-09-26  (Delta R1). Hiding the list
     above left the MODULE painting its title - Delta's sidebar showed a bare
     "Main Menu" h3 flush at x=0 in a 48px band above the article, on every inner
     page. A module whose only content is headings + the (now hidden) menu goes
     entirely; one with any other content keeps painting. Our own clones in the
     drawer / secondary panel are excluded. */
  :is(.moduletable, .moduletable_menu):not(#cs-drawer *):not(#cs-secondary-panel *):has(ul.menu):not(:has(> :not(h1, h2, h3, h4, h5, h6, ul.menu, .module-content))):not(:has(> .module-content > :not(ul.menu))) { display: none !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;
    /* CS 2026-09-12 (Tim, Flexi walkthrough R1): several donors put a hairline on
       #sn-position -- Flexi `1px solid rgba(255,255,255,.15)`, also HyperShop,
       LightCart, ModernaStore. On DESKTOP that box is inset 1px inside the coloured
       #site-name-handler, so the line hugs the block edge and reads as intentional.
       On MOBILE this block resizes #sn-position to its content (width:auto) while the
       chrome stretches #site-name-handler to the full screen -- so the hairline stops
       tracking the block and floats in the middle of the coloured bar, with its right
       edge landing a few px from the burger's own border. MEASURED on Flexi @375:
       #site-name-handler -7..368, #sn-position 24..297, #cs-menu-toggle 271..315 --
       two mis-aligned rectangles, which reads as a broken background image.
       The bar is a flat colour; let it be one. Desktop is untouched. */
    border: 0 !important;
  }
  #site-name-handler > *, #sn-position > * { align-self: center !important; }
  #top-handler #sn-position > #h1,
  #top-handler #h1,
  #sn-position > #h1 { position: static !important; top: auto !important; left: auto !important; }
  #h1 { float: none !important; margin: 0 !important; width: auto !important; }
  #top-handler #h1 a, #h1 a { display: inline-block !important; line-height: 0; }
  #top-handler #h1, #h1 { width: auto !important; min-width: 0 !important; overflow: visible !important; }
  #top-handler #h1 img, #top-handler #site-name-handler img, #h1 img {
    /* CS-PATCH 2026-09-15 (WCW/AllBusiness): max-width:100% is relative to #h1,
       which is width:auto -- so it never actually constrains anything, and a
       logo wider than the space left by the burger + header CTA overflows and
       clips. Cap on the WIDTH axis against the viewport instead. Deliberately
       NOT a max-height: Tim's 2026-08-10 note below records that forcing a
       height on this image is what scaled the slogan graphic to 495px wide.
       Width-capping preserves the natural aspect ratio, so that regression
       cannot come back. Templates needing a different cap can set
       --cs-logo-mobile-maxw. */
    max-width: min(100%, var(--cs-logo-mobile-maxw, 58vw)) !important;
    max-height: none !important;
    width: auto !important;
    /* height: 34px !important;  <- Tim, 2026-08-10: forcing a height on this image
       is what scaled the slogan graphic up to 495px wide. Let it size naturally and
       the slogan renders as smaller text under the logo. */
    display: block !important;
  }
  /* #h2 is no longer hidden - it is stacked under the logo instead (see above) */

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

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

  /* ---- 1. primary hamburger: 56x56 box, 34x26 glyph, top-right, logo row ---- */
  /* 2026-08-18 (Energize/Progressive, Tim - REPEAT of the Intense round-4 report
     "the X is offset well to the left"). This group used to list
     `body.cs-panel-open #cs-menu-toggle` alongside `#cs-menu-toggle`, meaning to
     make open and closed share one geometry. It did the exact opposite: the
     compound selector is (0,2,0) while every later override is (0,1,0), so the
     OPEN state silently reverted every correction the closed state received.
     MEASURED @375 before - Energize closed 303,14 44x44 -> open 291,8 56x56 (the
     56px here beat the 44px of mobile-ui-round5); Progressive additionally
     reverted `top` from --cs-burger-top 33px to this rule's 50% = 61.125px, so
     its X fell 28px as well. `#cs-menu-toggle` alone already matches in BOTH
     states - the body class never stops it matching - so the compound selector
     bought nothing but the bug. AFTER: centre drift 0.0px open AND on reclose,
     on Energize / Progressive / Crystal / Oxygen / MubleStore / Impulsa, X still
     hit-testable above the scrim. */
  #cs-menu-toggle {
    display: flex; position: absolute !important; top: 50% !important;
    right: 8px !important; left: auto !important; transform: translateY(-50%) !important;
    width: 56px !important; height: 56px !important;
    align-items: center; justify-content: center;
    background: transparent !important; box-shadow: none !important;
    /* a hairline square so the control reads as a button rather than three loose
       bars on white; colour follows the same ink the bars use */
    border: 1px solid color-mix(in srgb, var(--cs-burger-ink, #fff) 45%, transparent) !important;
    border-radius: 6px !important;
    padding: 0; cursor: pointer; z-index: 1055 !important;
  }
  /* CS 2026-09-12 (Tim, Flexi walkthrough R1): this box was 26px here while a LATER
     block ("primary burger sized to match the row", further down) set height:20px at
     the SAME specificity - so 20px won, but the span offsets below were never moved off
     the 26px rhythm (0/11/22 + 4px bar = 26px of content). The bars therefore overflowed
     the 20px box by 6px and their visual centre sat 3px BELOW the button's.
     MEASURED on Flexi @375: button 29..73 (cy 51), bars 41/52/63 spanning 41..67 (cy 54).
     One geometry now: a 20px box with bars at 0/8/16, which fills it exactly and centres
     in the 44px button -> bars 41..61, cy 51 = button cy 51. Gaps tighten 7px -> 4px,
     which is the "too far apart" half of the same report.
     That later block's `width: 26px` was ALWAYS a no-op - the min/max-width pins right
     here hold the width at 34px - so the bars have always been 34 wide. Left at 34. */
  #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: 20px !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: 8px !important; }
  #cs-menu-toggle .cs-burger-bars span:nth-child(3) { top: 16px !important; }
  /* the X must fold onto the MIDDLE bar, so these track the offsets above (was 11px) */
  body.cs-menu-open #cs-menu-toggle .cs-burger-bars span:nth-child(1) { transform: translateY(8px) 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(-8px) rotate(-45deg); }

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

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

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

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

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

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

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

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

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

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

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

  /* CS-PATCH: secondary-matches-drawer  2026-09-26  (Delta R2, Tim: "the second
     hamburger menu should have the same formatting as the main hamburger menu, but
     be a different menu"). MEASURED @375 before: drawer items 17px / padding 14px /
     rows full-bleed; secondary items 14.4px / padding 4px inside a 14px panel inset,
     a module title on top, and the donors' empty `li.clear` rows painted as blank
     bordered lines. Same list, same rhythm, same chips - only the CONTENT differs.
     (The drawer's own values: #cs-drawer .cs-drawer-menu rules above.) */
  body.cs-secondary-open #cs-secondary-panel:has(.moduletable_menu) { padding-left: 0 !important; padding-right: 0 !important; }
  #cs-secondary-panel .moduletable_menu > h3, #cs-secondary-panel .moduletable_menu > .module-title { display: none !important; }
  #cs-secondary-panel .moduletable_menu ul, #cs-secondary-panel .moduletable_menu li,
  #cs-secondary-panel .moduletable_menu a, #cs-secondary-panel .moduletable_menu span {
    float: none !important; width: auto !important; height: auto !important;
    background: none !important; text-indent: 0 !important; line-height: normal !important;
  }
  #cs-secondary-panel .moduletable_menu ul { list-style: none; padding: 0 !important; margin: 0 !important; }
  #cs-secondary-panel .moduletable_menu li { position: relative !important; padding: 0 !important; border-bottom: 1px solid rgba(0,0,0,.07); }
  #cs-secondary-panel .moduletable_menu li.clear { display: none !important; }
  #cs-secondary-panel .moduletable_menu a, #cs-secondary-panel .moduletable_menu span {
    display: block; padding: 16px 14px !important; font-size: 17px; color: #2b6ea8; text-decoration: none;
  }
  #cs-secondary-panel .moduletable_menu ul ul a { padding-left: 34px !important; font-size: 15px; }
  #cs-secondary-panel .moduletable_menu ul ul ul a { padding-left: 50px !important; }
  /* nested levels: the drawer keeps BS5's 2rem list indent and a divider under every
     sub-item; donor nav.css zeroes both for .moduletable_menu (li ul li border 0).
     MEASURED: drawer sub-item rule starts x=32, text x=66; secondary x=0 / 34, no rules. */
  #cs-secondary-panel .moduletable_menu ul ul { padding-left: 2rem !important; }
  #cs-secondary-panel .moduletable_menu ul ul li { border-bottom: 1px solid rgba(0,0,0,.07) !important; }
}

@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. */
  /* 2026-09-28 (.116, C4): not the donor mega panels - they set their own top (DiverseShop/HyperShop/ModernaStore/
     MultimShop/SquareShop/StilioShop/Xenon opened 10-20px off live). :where() keeps the specificity unchanged. */
  li:not(:where(#mega-menu > li)):hover > ul,
  li:not(:where(#mega-menu > li)).sfHover > ul { top: 100% !important; }
  li li:hover > ul,
  li li.sfHover > ul { top: 0 !important; left: 100% !important; margin-left: 0 !important; }
}

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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


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

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

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

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

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


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

   Let the inner container follow its parent instead of the retired grid. Its
   own `margin: auto !important` still centres it, and the <=1199px media
   blocks above narrow the outer container as before.
   ===================================================================== */
/* CS-PATCH: container1-follows-outer-container  2026-09-28 (.114, Q21 A/B by agM)
   The width:auto above assumed .container1 always sits inside an outer .container. Where
   it is a direct child of a full-width band (Fluency #bm/#footer, Extend #padding, Venda's
   header/module rows) auto = the viewport, so the band ran 0..1400 instead of the donor's
   own fixed width. Scope the auto to the nested case (:where keeps the old specificity so
   per-template tablet widths such as Lucid's still win); keep the phone behaviour and the
   max-width clamp everywhere.
   A/B, 37 ET stages x 1400/1024/800/375: changed only Extend (#padding 0+1400 -> 200+1000
   @1400, 12+1000 @1024 = live exactly), Fluency (#bm/#footer 115+1170 -> 230+940 @1400 =
   live) and Venda (four rows -> live's 1210/970/748, nothing painted differently); 0
   regressions. Alone (per-template footer block removed): Fluency 0+1400 -> 230+940 @1400
   and 0+1024 -> 42+940 @1024 = live. */
.container1 { max-width: 100% !important; }
:where(.container) .container1 { width: auto !important; }
@media (max-width: 767px) { .container1 { width: auto !important; } }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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


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


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


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

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

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

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

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

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


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

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

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

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

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

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

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

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


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

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

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


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

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


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


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


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

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

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

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

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

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

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

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

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


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


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

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

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

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

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

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

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

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

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


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


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

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

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

    /* `position: absolute` + `left: 50%` centres against the CONTAINING BLOCK,
       which is the 28px cart button - the panel landed at x=130 and ran 94px
       off the right edge. To centre on the SCREEN it has to be viewport-
       positioned. Anchor the top to the same `--cs-drawer-top` the mobile
       drawer uses, so it opens just below the header like every other panel.
       2026-10-16 (.129, Tim's Flexo grad test: "opens up half off to the left"): Flexo's responsive-template.css has
       `#vmCartModule div.panel2 { left: auto !important }` at (1,1,1), which beat this rule's (1,1,0). left:auto put the
       fixed panel at its static spot (x=35) and translateX(-50%) then dragged it to x=-135. `html body` lifts this to
       (1,1,2) so the donor rule can no longer win. */
    html body #vmCartModule .panel2 {
        position: fixed !important;
        top: var(--cs-drawer-top, 120px) !important;
        left: 50% !important;
        right: auto !important;
        bottom: auto !important;
        margin-left: 0 !important;
        transform: translateX(-50%) !important;
        width: calc(100vw - 24px) !important;
        max-width: 340px !important;
        z-index: 1060 !important;
    }
    /* the little pointer no longer lines up with a centred panel */
    #vmCartModule .panel2 > .arrow { display: none !important; }
}


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


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


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

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

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

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


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

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

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

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


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

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

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


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

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

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


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

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

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

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

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

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

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

    /* (b) primary burger sized to match the row */
    #cs-menu-toggle { width: 44px !important; height: 44px !important; }
    /* geometry (34x20, bars at 0/8/16) is declared once, up in the burger block.
       This line used to restate it wrongly - width:26px never applied (min/max-width
       pins it to 34) and height:20px silently contradicted the 26px up there. */

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

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


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

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

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

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

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

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

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


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


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

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

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

   One mechanism only: zero the margins and let `gap` own the spacing. 10px
   gives 264 + 50 = 314px inside a 359px content box, so the row centres with
   ~22px clear at each end.
   ===================================================================== */
@media (max-width: 767px) {
    #cs-utility-row {
        gap: 10px !important;
        justify-content: center !important;
        flex-wrap: nowrap !important;
    }
    #cs-utility-row > * { margin: 0 !important; }
    /* CS-PATCH: burger-no-donor-margin  2026-09-26  (Delta R1, Tim: "the top
       hamburger is not centered vertically"). #cs-menu-toggle is a <button>, so a
       donor's blanket `button { margin-top }` lands on it - Delta text.css
       `.button, button { margin-top: 10px }`. The top:50%/translateY(-50%) centring
       is then 10px low. MEASURED @375: logo centre y=36, burger centre y=46.
       The utility row already zeroes its children (line above); the burger never did. */
    #cs-menu-toggle { margin: 0 !important; }
}


/* CS-PATCH: donor-hdr-cells  2026-09-26  (Delta grad prep)

   desktop-header-icons above exists because most donors size their top-right
   controls independently (Coga: heights 16-28px, icons 10-18px, midlines 6px
   apart). A donor that sizes them AS ONE CELL SIZE from a template param is
   already uniform by construction - and the block then SHRINKS it.
   Delta:  index.php  #log-panel li, #log-panel li a, #search-position
           #searchOpenButton { width/height/line-height: <topheight>px }
   MEASURED @1400 before: live 90x90 cells, 24px glyphs, one midline;
   stage 32x32 boxes, 16px glyphs, and the search glyph rode to y=38 (top of
   its cell) instead of the cell's midline - three controls on two lines,
   i.e. the opposite of what the block is for. FlatGear has the same rule.

   The discriminator is a BUILD-TIME fact (does index.php size these controls
   from `topheight`?), so it is a <body> class set by et-hdr-cells-optout.py,
   exactly like cs-no-bs2-gutter. Every selector in the five groups of
   desktop-header-icons carries :not(:where(.cs-donor-hdr-cells *)):
   :where() is zero-specificity, so every template WITHOUT the class resolves
   byte-for-byte as it did at .86. */
/* =====================================================================
   CS-PATCH: desktop-header-icons  2026-08-13  (Tim review of Coga)

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  /* ---- the template's own spacing is enough on phones ---- */
  /* 2026-09-26 (Delta R1): NOT when #content-handler IS the template's own phone
     spacing. ET donors of the Delta shape nest it inside `#story-content` and pair
     `#story-content { margin: 0 -20px }` (full-bleed bands) with `#content-handler
     { padding: 0 20px }` (text back in) in responsive-template.css. Zeroing only the
     padding half put article text and headings 9px off both edges (live: 20px, the
     centred "Over 40 modules" title 31px). `:where()` keeps specificity unchanged,
     so the VMT donors this was written for (Intense/Impulsa) are unaffected.
     2026-09-26 (AllBusiness 6.1.3.2, .103): the exemption was keyed on the NESTING, but 30 ET
     donors nest #content-handler in #story-content and only Delta has the bleed. AllBusiness pads
     #content-handler 0 20px inside a 20px body gutter with NO bleed -> text moved x20 -> x40 @375.
     Now opt-in: et-story-bleed-optout.py tags <body> cs-story-bleed when the donor phone css gives
     #story-content a negative side margin (fleet scan 2026-09-26: Delta only). */
  /* CS-PATCH: no-gutter-keeps-content-inset  2026-09-28 (.114, Q21 A/B by agM)
     The strip assumes the BS2 body gutter supplies the phone inset. Under
     body.cs-no-bs2-gutter (et-gutter-optout.py) there is no body gutter, so stripping the
     donor's own #content-handler padding ran content flush to both screen edges. Skip the
     strip there; the donor's value (20px on every opted-out donor that has one) returns.
     :where() keeps the specificity unchanged.
     A/B, 37 ET stages x 375/320, home + com_users login: changed only Empressa, HighLevel,
     Render, Squado (+ Simplex/XeonShop, handler hidden on the login view): padding 0/0 ->
     20/20 = live's 20/20, content moves toward live on all four. A hard 20px rule was
     rejected (it inset Synchronize/Venda, whose live padding is 0). Alone (per-template
     inset blocks removed on Restaurant, MagnetaShop, UltimateShop, HyperonShop, LightShop):
     0/0 -> 20/20 on all five. */
  :where(body:not(.cs-no-bs2-gutter)) #content-handler:not(:where(.cs-story-bleed #story-content *)) { padding: 0 !important; }

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

  /* ---- drawer rhythm + a single search submit in the chrome panel ---- */
  #cs-drawer .cs-drawer-menu a, #cs-drawer .cs-drawer-menu span { padding: 7px 14px !important; }
  /* 2026-09-26 (Delta R2): the second hamburger keeps the drawer's rhythm too
     (secondary-matches-drawer). Nested levels keep their 34/50px indent. */
  #cs-secondary-panel .moduletable_menu a, #cs-secondary-panel .moduletable_menu span { padding-top: 7px !important; padding-bottom: 7px !important; }
  #cs-search-panel input[type="image"] { display: none !important; }

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

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


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

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

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

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

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

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

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


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

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

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

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


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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

/* CS-PATCH: phone-login-panel-nonmodal  2026-09-26  (Delta grad prep)
   Some donors ship #LoginForm as their OWN slide panel, not a Bootstrap modal
   (Delta: .LoginForm-header / .LoginForm-body, toggled by .open-register-form).
   It lives in the header cell the phone chrome hides, so at <=767px the mobile
   script moves it to <body>. Once there it has no position of its own and lays
   out at the END of the page - MEASURED @375: panel at y=5916 on a 6200px page,
   username field not hittable from the viewport. Pin it under the header like
   the other phone panels (--cs-drawer-top is set by the chrome script).
   `body > #LoginForm:not(.modal)` only matches a relocated NON-modal panel, so
   BS5 modals and every template that never moves one are untouched.
   Restores the behaviour Delta's own 2026-08-04 bridge block had before it was
   lost at a resync. */
@media (max-width: 767px) {
  body > #LoginForm:not(.modal) {
    position: fixed !important; top: var(--cs-drawer-top, 150px) !important;
    left: 0 !important; right: 0 !important; width: auto !important; min-width: 0 !important;
    max-height: calc(100vh - var(--cs-drawer-top, 150px)); overflow-y: auto;
    z-index: 1052 !important; background: #fff !important;
    box-shadow: 0 6px 14px rgba(0,0,0,.2); padding: 14px !important; box-sizing: border-box !important;
  }
}

/* CS-PATCH: swiper-forced-arrows-desktop  2026-09-26  (Delta grad prep)
   The phone standard (Skill 33 item 6) wants slideshow arrows at <=767px even
   when the donor shipped cam_navigation OFF, so the Swiper conversion emits them
   anyway and marks them `.cs-forced`. That class means "the donor turned these
   off" - so at desktop they must not render, or desktop stops matching live.
   The hide rule lived only in Delta's own bridge block and was lost when that
   block was dropped at a resync: MEASURED @1400 before - two 44x44 arrows at
   x=12 / x=1344 on the stage, none on live. Canonical now owns the contract. */
@media (min-width: 768px) {
  .ph-swiper-slideshow .swiper-button-prev.cs-forced,
  .ph-swiper-slideshow .swiper-button-next.cs-forced { display: none !important; }
}
.ph-swiper-slideshow .ph-slide-caption,
.ph-swiper-slideshow .camera_caption {
  position: absolute !important; left: 5% !important; right: 5% !important; top: 20% !important;
  max-width: 70%; z-index: 4; color: #ffffff;
}
@media (max-width: 767px) {
  .ph-swiper-slideshow { aspect-ratio: 4 / 3; min-height: 240px; }
  .ph-swiper-slideshow .swiper-button-prev,
  .ph-swiper-slideshow .swiper-button-next { background: rgba(0,0,0,0.5); }
}

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

/* =====================================================================
   CS-PATCH: phone-floated-cta-stacks  2026-09-26  (AllBusiness 6.1.3.2)
   Demo module content puts a call-to-action in a floated paragraph BEFORE the heading:
       <div class="custom_style4"><p style="float: right"><a class="button">Get Started Now</a></p>
       <h2>We provide powerful solutions for business</h2></div>
   Right on desktop (button beside a one-line heading). On a phone the 156px float eats the
   heading's first line: MEASURED @375 "We" alone beside the button, the rest wrapping below.
   The donor hid the sibling "View Our Portfolio" copy on phones (hidden-phone) and missed this one.
   At <=767 the module becomes a column and the CTA paragraph unfloats and moves AFTER the heading.
   Scoped to a floated <p> whose ONLY child is a.button, directly in a custom module - fleet scan of
   73 ET demo home pages 2026-09-26: AllBusiness only. MEASURED @375: heading 773..889, button y921;
   @768 unchanged (btn beside the heading, y731). ===================================================================== */
@media (max-width: 767px) {
  :is(.custom, [class^="custom_"], [class*=" custom_"]):has(> p[style*="float"] > a.button:only-child) {
    display: flex;
    flex-direction: column;
  }
  :is(.custom, [class^="custom_"], [class*=" custom_"]) > p[style*="float"]:has(> a.button:only-child) {
    float: none !important;
    order: 2;
    margin: 12px 0 0 !important;
  }
}

/* ---- phone text inset ----
   PROMOTED FROM Progressive (`cs-mobile-text-inset-20260811`). Tim: "a little bit
   of padding inside of left and right for text, such as the title of the module."
   Donors inset body copy but let HEADINGS run full-bleed, so at 390px the
   page-header H1 and custom-module headings sat 2px off both edges while the
   paragraphs beside them sat at 22px. This file had ZERO `.page-header` references.
   The inset goes on the headings and article wrappers, never on the module box.
   `box-sizing` is load-bearing: on a content-box donor a 12px inset would push the
   block 24px past the viewport and bring back horizontal scroll. */
@media (max-width: 767px) {
  .page-header, .category-desc, .item-content,
  .mod-custom > h1, .mod-custom > h2, .mod-custom > h3, .mod-custom > h4, .mod-custom > h5,
  .custom > h1, .custom > h2, .custom > h3, .custom > h4,
  /* 2026-09-26 (Delta R1): a module CLASS SUFFIX renames the wrapper - Joomla
     emits `custom<suffix>` (Delta: `.custom_empty`), so `.custom > h3` never
     matched and "Welcome to the Delta fully responsive Joomla template" sat 2px
     off both edges @375. Match the suffixed wrappers too. */
  :is([class^="custom_"], [class*=" custom_"]) > :is(h1, h2, h3, h4) {
    padding-left: 12px !important;
    padding-right: 12px !important;
    box-sizing: border-box !important;
  }
  /* CS-PATCH: page-header-no-bleed  2026-09-26  (Delta R1, Tim: "some headings
     are right on or beyond the left border - the right side might be the same").
     Donors bleed the header band with a PAIR: `.page-header { margin: 0 -30px;
     padding: 0 30px }` (Delta template.css). The inset above replaces the padding
     half of the pair and left the negative margin, so the title slid off BOTH
     edges. MEASURED @375 / @440 on /sample-sites and /using-joomla: box -22 /
     -21, text -10 / -9 left and right. Zero the margin where the padding is ours. */
  .page-header { margin-left: 0 !important; margin-right: 0 !important; }
}

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

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

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

     SUBMIT SELECTOR IS DELIBERATELY SCOPED to `form.form-horizontal` and
     `form.form-validate` rather than a bare `form button[type=submit]`.
     MEASURED REASON: MubleStore's VirtueMart cart carries
     `button[name=checkout]` (147x36) inside a CLASSLESS form, beside
     `input[type=submit]` coupon/quantity buttons at 76-81px that these rules
     cannot reach. The unscoped selector blew checkout to 320x46 and left the
     coupon buttons small - a full-width primary next to two tiny secondaries.
     Scoping to the two form classes this block already stacks keeps every
     Joomla component form (contact, registration, login, profile) and leaves
     the VirtueMart commerce surface bit-for-bit unchanged. VERIFIED no-op on
     MubleStore cart (checkout / Submit / setcoupon all unchanged) and on
     Impulsa (`input.addtocart-button` 99x30 unchanged on the product grid). */
  form .controls input[type="text"],
  form .controls input[type="email"],
  form .controls input[type="url"],
  form .controls input[type="tel"],
  form .controls input[type="number"],
  form .controls input[type="password"],
  form .controls textarea,
  form .controls select {
    min-height: 44px !important;
    padding: 8px 10px !important;
    /* ⚠️ 16px, NOT 15px. Corrected 2026-08-23 (found on ElectraShop).
       The rev .64 note above gives the right reason - "small enough that iOS zooms
       the viewport on focus" - and then picked a value that still trips it. iOS
       Safari's zoom threshold is EXACTLY 16px: 15px zooms, and it does not zoom back
       out on blur, leaving the user on a page wider than the screen. The rationale was
       correct and the number was one pixel short of satisfying it.
       min-height raised 40 -> 44 at the same time, to match the 44px target the rest
       of the phone chrome uses. */
    font-size: 16px !important;
  }
  form .controls textarea { min-height: 120px !important; }
  form.form-horizontal button[type="submit"],
  form.form-validate button[type="submit"] {
    width: 100% !important;
    min-height: 46px !important;
    font-size: 16px !important;
  }
}

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

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


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

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

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

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

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

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


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

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

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

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

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

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

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

   CONTRAST NOTE, recorded not fixed: white ink on the donor accent clears 4.5:1
   on Vision (6.94) but NOT on Creativa (3.39) - nor on Acceler8 itself (3.27),
   which has looked correct all day. That is a property of this file's
   white-on-accent chip treatment fleet-wide, not of this patch. Both clear the
   3:1 that WCAG 2.1 SC 1.4.11 asks of a non-text UI component, which is the
   applicable bar for a 20px glyph. Raising it further means either an ink that
   reacts to accent luminance or dropping the accent fill - a design decision,
   not a bug fix, and out of scope here.
   ===================================================================== */
@media (max-width: 767px) {
    /* ⭐ CS 2026-09-12 — TIM'S STANDARD, stated twice: every chip in the mobile
       utility row is ACCENT with a WHITE glyph. Asked for on Cosmo (graduation
       walkthrough 2026-09-11, fix #4, applied there as a template-local override)
       and again on Flexi (walkthrough R1): "some icons are red with white in them,
       some are white with black in them. They should all be red with white."
       Two templates asking for the same block makes it promotable by this runbook's
       own test, so the floor itself flips rather than each template overriding it.

       This also closes the separate "the adopted cart chip is bare" gap: the cart is
       a <div>, so the button/a floor never reached it and the sixth control shipped
       with no fill on EVERY template, the released Cosmo included. It is listed
       explicitly below.

       ⚠ CONTRAST: this puts white ink on the donor accent for all six chips instead
       of two. The .64/.65 notes above already measured that combination fleet-wide —
       6.94 on Vision, 3.39 on Creativa, 3.27 on Acceler8 — i.e. it clears the 3:1
       that WCAG 2.1 SC 1.4.11 asks of a non-text UI component everywhere, and 4.5:1
       on most. MEASURED on Flexi (accent #a13500): white on accent = 6.95:1. */
    /* the floor: no donor TYPE selector reaches the chip set */
    #cs-utility-row > button,
    #cs-utility-row > a,
    #cs-utility-row > .cs-adopted-cart { background: var(--cs-accent, #992946) !important; }

    /* kept at (0,2,0) so a donor rule that outranks the floor still cannot
       repaint these two; same value as the floor now. */
    #cs-utility-row > #cs-secondary-toggle,
    #cs-utility-row > #cs-register { background: var(--cs-accent, #992946) !important; }

    /* the adopted cart is a <div>: give it the chip's own box too, which the
       button/a geometry rule never reached. */
    #cs-utility-row > .cs-adopted-cart {
        border-radius: 6px !important;
        border: 1px solid rgba(0, 0, 0, 0.18) !important;
    }
}


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

   THE SYMMETRIC PARTNER OF THE .64 BACKGROUND FLOOR.

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

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

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

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

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

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

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

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

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

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

   THE GLYPH `inherit` LINE is load-bearing. Donors paint the <i> directly as
   often as they paint the control, so flooring the chip alone leaves the glyph
   free. Forcing the glyph to inherit means one declaration - the chip's -
   decides, which is the whole point of a floor.
   ===================================================================== */
@media (max-width: 767px) {
    /* every chip: white ink, to match the accent floor (see the 2026-09-12 note
       on the background floor above). Same (0,1,1) reach as that floor. */
    #cs-utility-row > button,
    #cs-utility-row > a,
    #cs-utility-row > .cs-adopted-cart { color: var(--cs-accent-ink, #fff) !important; }

    /* the glyph never gets its own opinion - it takes its chip's */
    #cs-utility-row > button i,   #cs-utility-row > a i,
    #cs-utility-row > button svg, #cs-utility-row > a svg,
    #cs-utility-row > button > span:not(.cs-burger-bars),
    #cs-utility-row > a > span:not(.cs-burger-bars),
    /* DIRECT children only. The adopted cart is not just a glyph - it owns a
       slide-out basket panel nested inside it, and a descendant selector here
       forced that whole panel's text to the chip's accent ink. MEASURED on Flexi
       2026-09-12: every row in the open cart went #fff on the panel's #fff
       surface, contrast 1.00:1 - an invisible cart. The chip's own affordance is
       always a direct child; its flyout never is. */
    #cs-utility-row > .cs-adopted-cart > i,
    #cs-utility-row > .cs-adopted-cart > svg,
    #cs-utility-row > .cs-adopted-cart > a {
        color: inherit !important;
        fill: currentColor !important;
    }

    /* the primary burger's own bars ride the same standard */
    #cs-utility-row > #cs-secondary-toggle .cs-burger-bars > span {
        background: var(--cs-accent-ink, #fff) !important;
    }

    /* kept at (0,2,0) so a donor rule cannot repaint these two; same value now. */
    #cs-utility-row > #cs-secondary-toggle,
    #cs-utility-row > #cs-register { color: var(--cs-accent-ink, #fff) !important; }
}


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

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

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

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

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

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

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

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

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


/* =====================================================================
   CS-PATCH: submenu-close-delay  2026-09-26  (Delta R1, Tim: "the gap is too
   big underneath to go down and click another link")

   Phase 4a/7 retired superfish, and with it superfish's hover DELAY. The
   dropdown is now CSS :hover only, so it closes the instant the pointer
   leaves the parent <li>. Every ET donor in this family drops the panel from
   the HEADER'S bottom edge, not the link's, so there is a dead band to cross:
     Delta @1400: link bottom y=92, panel top y=120 - a 28px gap.
   MEASURED with a hand-speed pointer walk (40 steps / ~1s) from the parent
   link to its first two items at 1280/1400/1920: live OK on all 9 walks
   (superfish holds ~800ms), stage LOST on all 9 at y=94 - two pixels below
   the link.

   THE FIX restores the grace period in CSS: a closed panel waits 0.6s before
   jumping to its parked position (left/top are switched by a 0s transition
   with a delay - transitions outrank !important in the cascade), while
   OPENING stays instant. Once the pointer reaches the panel, the parent is
   :hover again (the panel is its descendant) and it stays open.
   `.menu-nav:hover` cancels the delay for siblings, so moving across to
   another top item closes the previous panel at once - no two open together.
   Scoped >=768 like parked-submenu-scrollwidth: the phone drawer opens by
   class, not hover. */
@media (min-width: 768px) {
    .menu-nav li > ul {
        transition: left 0s linear 0s, top 0s linear 0s;
    }
    .menu-nav li:not(:hover):not(:focus-within):not(.sfHover):not(.open) > ul {
        transition: left 0s linear .6s, top 0s linear .6s;
    }
    .menu-nav:hover li:not(:hover):not(:focus-within):not(.sfHover):not(.open) > ul {
        transition-delay: 0s, 0s;
    }
}


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

   CLOSES GAP 2, recorded at .63.

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

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

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

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


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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

   to BS5's

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

   NOTE, reported but deliberately NOT changed here:

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

/* 2026-09-26 (Delta release audit): every (c) rule now also scoped to `#LoginForm`.
   The block was written for login pop-ups built as Bootstrap MODALS; Delta's #LoginForm
   is the donor's own slide panel, so only the (c-ii) colour copy reached it - the dead
   caret beside LOG IN still showed and the forgot-password / forgot-username / create-
   account links stayed unreachable. Same structure, same fault, now the same fix. */
/* (c) retire the dead BS2 split-button and surface the links it hid */
#LoginForm .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]),
#LoginForm .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) > .caret,
.modal .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]),
.modal .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) > .caret {
    display: none !important;
}
#LoginForm .btn-group:has(> .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle])),
.modal .btn-group:has(> .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle])) {
    display: block !important;
}
#LoginForm .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) ~ ul.dropdown-menu,
.modal .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) ~ ul.dropdown-menu {
    display: block !important;
    position: static !important;
    float: none !important;
    width: 100% !important;
    min-width: 0 !important;
    margin: 10px 0 0 !important;
    padding: 0 !important;
    background: none !important;
    border: 0 !important;
    box-shadow: none !important;
    list-style: none !important;
    transform: none !important;
    top: auto !important; left: auto !important; right: auto !important; bottom: auto !important;
    opacity: 1 !important;
    visibility: visible !important;
}
#LoginForm .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) ~ ul.dropdown-menu > li,
.modal .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) ~ ul.dropdown-menu > li {
    display: block !important;
    float: none !important;
    padding: 0 !important;
    margin: 0 !important;
    background: none !important;
    list-style: none !important;
    border: 0 !important;
}
#LoginForm .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) ~ ul.dropdown-menu > li > a,
.modal .btn-group > .dropdown-toggle[data-toggle="dropdown"]:not([data-bs-toggle]) ~ ul.dropdown-menu > li > a {
    display: inline-block !important;
    padding: 4px 0 !important;
    background: none !important;
    color: inherit !important;
    font-size: 13px !important;
    line-height: 1.5 !important;
    text-decoration: underline !important;
    white-space: normal !important;
}
/* (c-ii) ...and the donor preset must not paint them as a second button.
   MEASURED after (c): the three links came out as WHITE TEXT ON A SOLID BRAND
   PANEL sitting directly under the brand-coloured "Log in" button, so the modal
   read as two buttons stacked. Source is the donor preset, style1.css:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/* =====================================================================
   CS-PATCH: row-passthrough-wrapper  2026-09-11  (found on Flexi, VMT grad-prep)

   Bootstrap 5's own grid sets  .row > * { width: 100% }.  That is fine when
   every child of a .row is a real column, which is what Phase 2/3a produces
   from well-formed donor markup.

   But these donors also use a CLASSLESS wrapper div that exists only to hold
   a floated BS2 column - e.g.

       <div id="nav-line" class="row">            (was .row-fluid)
         <div id="breadcrumb-handler">            <-- no class
           <div id="breadcrumb-bg" class="col-md-8">...</div>
         </div>
         <div class="col-md-4"> ...cart... </div>
       </div>

   Under BS2 the wrapper collapsed (its child was floated, so the float
   escaped) and span8 + span4 shared one line.  Under BS5 the wrapper is a
   flex item that BS5 hands width:100% - so it eats the whole line and every
   real column sibling WRAPS onto a second row.  The page then grows by the
   height of that spurious row, and everything below it shifts down.

   MEASURED on Flexi @1400 (live donor in brackets):
       #nav-line height        58   ->  29    [35]
       #breadcrumb-bg  x/w    238/910 -> 238/599   [238/600]
       cart column     x/w    261/288 -> 860/288   [857/290]
       slideshow y            191  ->  162   [168]
       document height       1363  -> 1334   [1340]

   Fix: dissolve the wrapper so its columns become direct flex items of the
   row - which is exactly what the donor's float behaviour produced.

   Deliberately narrow.  It only fires when the row ALREADY has a real column
   child of its own, because that is the only case that wraps.  A classless
   wrapper that is a row's only child is still correct at width:100% and is
   left alone.  Verified inert on Metro and Nextor (the only other two stages
   whose rendered DOM carries this shape) - document height unchanged on both.

   NOTE: the wrapper's own box is dissolved, so any padding/background on the
   wrapper itself goes with it. On Flexi that is #breadcrumb-handler's
   padding:3px 2px, which is why the stage lands ~6px above the donor rather
   than exactly on it. Accepted: 6px on a 1340px page, and far better than the
   23px the bug cost. Do NOT extend this to wrappers that carry a visible
   background or border.
   ===================================================================== */
.row:has(> [class*="col-"]) > :not([class]):has(> [class*="col-"]) {
    display: contents !important;
}


/* =====================================================================
   CS-PATCH: vm-cart-button-dead-sprite  2026-09-11  (found on Flexi, VMT grad-prep)

   The donors style VirtueMart's two cart-page controls off the Bootstrap 2
   glyphicons SPRITE that shipped in Joomla 3's media/jui:

       .vm2-add_quantity_cart, .vm2-remove_from_cart {
           background-image: url("../../../media/jui/img/glyphicons-halflings.png") !important;
       }

   `media/jui/img/` DOES NOT EXIST on Joomla 4/5. So on the cart page:
     - a 404 fires for glyphicons-halflings.png, and
     - `button.vmicon.vm2-add_quantity_cart` ("Update Quantity In Cart") and
       `button.vmicon.vm2-remove_from_cart` ("Delete Product From Cart")
       render as BLANK 16x16 boxes. They still work - they just have no glyph,
       on the one page where a customer is spending money.

   The existing `kill-bs2-glyphicons-sprite` block does not reach these: it is
   scoped to `[class^="icon-"]`, and these carry `vmicon vm2-*`.

   Why this block and not a per-template edit: MEASURED across the 45 unreleased
   VMT stages, 15 carry this exact rule - Aeon, FlatStore, Flexi, Flexo, Inspire,
   LightCart, Liquid, Metro, Nano, Prime, SparkStore, Swift, VMBlend, VMShopBlue,
   VMShopGreen. Promotable by the runbook's two-or-more test.

   Glyphs follow each control's own title attribute rather than trying to
   reproduce the sprite offset: arrows-rotate for "update quantity",
   trash-can for "delete from cart".

   ⚠ Only kills the DEAD jui sprite. The donors' own `images/vm2-sprite.png`
   icons (vm2-billto-icon, vm2-shipto-icon, ...) are untouched - that file
   exists and those render correctly.
   ===================================================================== */
.vm2-add_quantity_cart,
.vm2-remove_from_cart {
    background-image: none !important;
    /* The donor sprite was WHITE-on-transparent, meant to sit on a coloured
       button, so these controls carry color:#fff. Painted on VirtueMart's own
       white .cart-summary table that is a 1.00:1 contrast ratio - the glyph is
       invisible. `inherit` takes the table cell's own text colour, which is by
       definition readable against whatever that donor paints behind it, so this
       stays correct on a dark-themed cart too.
       MEASURED on Flexi: #fff on #fff = 1.00  ->  #2f2f2f on #fff = 13.39 (AA+). */
    color: inherit !important;
}

.vm2-add_quantity_cart::before,
.vm2-remove_from_cart::before {
    font-family: "Font Awesome 6 Free";
    font-weight: 900;
    font-style: normal;
    font-variant: normal;
    display: inline-block;
    line-height: 1;
    font-size: 14px;
    text-rendering: auto;
    -webkit-font-smoothing: antialiased;
}
.vm2-add_quantity_cart::before { content: "\f021"; }   /* arrows-rotate */
.vm2-remove_from_cart::before  { content: "\f2ed"; }   /* trash-can     */

/* =====================================================================
   CS-PATCH: phone-overlay-slideshow-inflow  2026-09-27  (Energy grad prep)
   Energy's desktop design lays the header OVER a full-bleed slideshow: #slideshow-handler is
   position:absolute at top:0 and #theme-width.oklideshow reserves the space with margin-top:308px
   (#menu-m pulls up -60px). On phones the canonical mobile-fixed-width-wrapper-cap sets
   width:auto on the handler - on an ABSOLUTE box that is shrink-to-fit, and a Swiper has no
   intrinsic width, so the slideshow rendered 0px wide: MEASURED @375 handler 20,0,0x820, camera 0x0,
   a 250px blank band under the chip row, and the logo <h1> (donor #sn-position h1 {position:absolute;
   top:29px}) sat UNDER the chip row. Live @375: slideshow 375x188.
   Phone fix: slideshow back in flow under the header, reservation and pull-up removed, logo static
   and capped so logo + slogan fit the 64px row. MEASURED after: logo 90x34 y14, slogan y53, slideshow
   335x168 at y141 (directly under the chips), content y309 (was 389), no sideways scroll.
   Scoped by :has(#theme-width.oklideshow) - the structure is unique to Energy in the ET fleet
   (fleet grep 2026-09-27), so every other template is untouched. ===================================================================== */
@media (max-width: 767px) {
  body:has(#theme-width.oklideshow) #slideshow-handler { position: relative !important; height: auto !important; min-height: 0 !important; left: auto; right: auto; }
  body:has(#theme-width.oklideshow) #menu-m { margin-top: 0 !important; }
  #theme-width.oklideshow { margin-top: 0 !important; }
  body:has(#theme-width.oklideshow) #sn-position > h1 { position: static !important; top: auto !important; margin: 0 0 4px !important; }
  body:has(#theme-width.oklideshow) #sn-position > h1 img { max-height: 34px !important; height: auto !important; width: auto !important; }
}

/* ==========================================================================
   CS-PATCH: et-utility-textbutton-restore   PROMOTED TO CANONICAL 2026-09-27 (.110) - was carried per-template on ~20 ET stages
   ET-ONLY carve-out from the canonical icon-button block.

   Canonical gives .open-register-form / .v_register / #searchOpenButton /
   #cartpanel.cart-button a uniform 32x32 inline-flex box. Correct on VMT, where
   all five are ICONS. On ET several are TEXT buttons ("Login Form", "Register"),
   and a 32px box collapses them so the neighbouring control overlaps.

   Measured on Brilliance @1400px:  live 93x27 block   ->  stage 32x32 flex.
   Fleet: 21 controls across 20 ET stages.

   Restores natural sizing ONLY where the control has no icon child, so a real
   icon button still gets the canonical treatment. Padding is intentionally left
   at canonical's 0 -- the donors' own padding differs per template.
   ========================================================================== */
@media (min-width: 768px) {
    /* .117 C1b: header TEXT links are no longer touched by desktop-header-icons - nothing to restore */
}

/* =====================================================================
   CS-PATCH: phone-retires-the-second-donor-menu-toggle  [PROMOTED TO CANONICAL 2026-09-27 (.111) from VevaShop R1 - same symptoms measured on LightShop/HyperonShop/UniversalShop phones]  2026-08-28  (VevaShop)

   The canonical "retire the donor's own phone chrome" group hides `a#menupanel`.
   This donor ships a SECOND toggle, `a#menupanel2`, which that selector misses.

   MEASURED @375 before: visible at x=290 y=83, 30x50, sitting between the brand
   and the utility row - and DEAD. Its inline handler is
   `onclick="toggle_visibility('mega-menu')"`, and clicking it throws
   `ReferenceError: toggle_visibility is not defined`: the donor script that
   defined that function came in behind the jQuery loader this template no longer
   ships. So a customer saw two hamburgers on the phone, and the more prominent
   one did nothing at all.

   Matched by CLASS, which both toggles carry, rather than by id.
   ===================================================================== */
@media (max-width: 767px) {
    a.menupanel,
    a#menupanel2 { display: none !important; }
}
/* /CS-PATCH: VevaShop second donor menu toggle */

/* =====================================================================
   CS-PATCH: phone-hides-the-duplicate-account-strip  [PROMOTED TO CANONICAL 2026-09-27 (.111) from VevaShop R1 - same symptoms measured on LightShop/HyperonShop/UniversalShop phones]  2026-08-28  (VevaShop)

   `ul.log-panel` - the desktop "My Account | Register" strip - rendered at the very top
   of the phone page, directly above the utility row that already carries both as chips.
   MEASURED at 375: both anchors visible at y=5, and the row below carrying #cs-account
   and #cs-register.

   ⛔ CHECKED BEFORE HIDING, because Elegant is on record where I hid a menu after
   searching only ONE of two panels and lost links. Both destinations are covered:
       Register   - #cs-register carries the IDENTICAL href
       My Account - #cs-account is a <button> with no href, so an href comparison
                    reports it missing. Verified FUNCTIONALLY instead: clicking it
                    opens #LoginForm (display block, 330x800, backdrop present),
                    which is exactly what the "My Account" link does.

   ⚠️ That functional check also failed the first time, because I read the modal's state
   in the SAME TICK as the click - Bootstrap animates, so it was still `display:none`.
   Re-read after the animation. A synchronous assertion about an animated component
   will report a working control as broken.
   ===================================================================== */
@media (max-width: 767px) {
    ul.log-panel { display: none !important; }
}
/* /CS-PATCH: VevaShop duplicate account strip */

/* =====================================================================
   CS-PATCH: modals-fit-the-phone  [PROMOTED TO CANONICAL 2026-09-27 (.111) from VevaShop R1 - same symptoms measured on LightShop/HyperonShop/UniversalShop phones]  2026-08-28  (VevaShop, Tim)

   Tim: "in mobile when clicking on the login, the pop up is way off to the left."

   MEASURED at 375, layout width 360:
       #LoginForm   margin-left -175px, left 14.39px, width 330  ->  x = -161
       only the right-hand sliver of the dialog was on screen

   The canonical bs5modal-on-bs2 block already carries a phone rule:
       @media (max-width: 620px) { .modal { width:auto; left:4%; right:4%; margin-left:0 } }
   but `layout.css:274` sets `#LoginForm.modal { width:330px; margin-left:-175px }`.

       donor      #LoginForm.modal   (1,1,0)
       canonical  .modal             (0,1,0)

   and a media query contributes NOTHING to specificity, so the donor's BS2 centring
   idiom (`left:50%` + negative half-width) won at every width. On BS5 that idiom is
   not needed at all - the dialog centres itself - and here it just pushed the modal
   off the screen.

   Declared !important rather than out-specified: this donor styles several modals by
   id (#LoginForm, #socialModal), and !important beats a non-important donor rule at
   any specificity, so one rule covers the whole family without listing them.

   ⚠️ CARRY BACK TO CANONICAL: the canonical phone rule is authored at (0,1,0) and is
   silently defeated by ANY donor that sizes a modal by id. Either author it to win,
   or have the graduation script open every modal at 375 and assert x >= 0.
   ===================================================================== */
@media (max-width: 767px) {
    .modal,
    .modal.fade,
    .modal.show {
        left: 8px !important;
        right: 8px !important;
        width: auto !important;
        max-width: none !important;
        margin-left: 0 !important;
        margin-right: 0 !important;
        padding: 0 !important;
        box-sizing: border-box !important;
    }
    .modal .modal-dialog {
        width: auto !important;
        max-width: none !important;
        margin: 10px 0 !important;
    }
    .modal .modal-content { width: auto !important; max-width: none !important; }
    /* the donor's own inner padding was on the .modal box we just reset */
    .modal .modal-body, .modal .modal-header { padding-left: 16px !important; padding-right: 16px !important; }
}
/* /CS-PATCH: VevaShop modals fit the phone */

/* =====================================================================
   CS-PATCH: password-reveal-control  [PROMOTED TO CANONICAL 2026-09-27 (.111) from VevaShop R1 - same symptoms measured on LightShop/HyperonShop/UniversalShop phones]  2026-08-27  (VevaShop, ported from EvoStore)
   Tim: "in mobile, the eyeball for revealing what's in the password is underneath it instead of
   to the right."

   ⚠️ MY OMISSION, and worth naming: `cs-password-reveal.js` was copied over from Elegant but its
   STYLESHEET was not. Elegant's bridge carries 17 `.cs-pw-wrap` rules; this one carried zero. The
   script builds the wrapper and the button correctly - with no CSS the button is simply a
   block-level element following the input, so it lands on the next line. The behaviour was right
   and only the styling was missing. A helper script and its rules are one unit; shipping half of
   it looks exactly like a broken script.

   Layout follows the same split Elegant uses, and for the same measured reasons:
     phones  - the field is full width, so the control is OVERLAID inside it and the input gets
               right padding to keep typed text clear. Costs no width.
     desktop - the control sits BESIDE the field, which is what Joomla core does on its own
               login view, so the two surfaces agree.

   The wrapper deliberately sets NO width, flex factor or box-sizing on the input: a percentage
   width on a field resolves against its PARENT, so a wrapper that changes the parent silently
   resizes the field. That cost several attempts on Elegant.
   ===================================================================== */
.cs-pw-wrap {
    position: relative;
    display: flex;
    align-items: stretch;
    width: 100%;
    margin-top: 2px;
    margin-bottom: 2px;
}
.cs-pw-wrap > input {
    margin-top: 0 !important;
    margin-bottom: 0 !important;
}
.cs-pw-wrap > button.cs-pw-toggle {
    display: flex !important;
    align-items: center;
    justify-content: center;
    background: transparent !important;
    background-color: transparent !important;
    background-image: none !important;
    border: 0 !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    padding: 0 !important;
    margin: 0 !important;
    color: #555 !important;
    cursor: pointer;
    line-height: 1 !important;
    min-width: 0 !important;
}
.cs-pw-wrap > button.cs-pw-toggle > i {
    line-height: 1;
    color: inherit !important;
}
.cs-pw-wrap > button.cs-pw-toggle:hover,
.cs-pw-wrap > button.cs-pw-toggle:focus-visible { color: #111 !important; }
.cs-pw-wrap > button.cs-pw-toggle:focus-visible {
    outline: 2px solid #62aae8;
    outline-offset: -2px;
}

/* phones: overlaid inside the field */
@media (max-width: 767px) {
    .cs-pw-wrap > input {
        padding-right: 42px !important;
        box-sizing: border-box !important;
    }
    .cs-pw-wrap > button.cs-pw-toggle {
        position: absolute !important;
        top: 1px;
        bottom: 1px;
        right: 1px;
        width: 40px;
        min-width: 40px;
    }
    .cs-pw-wrap > button.cs-pw-toggle > i { font-size: 15px; }
}

/* desktop: beside the field, box model untouched */
@media (min-width: 768px) {
    .cs-pw-wrap { align-items: center; gap: 4px; }
    .cs-pw-wrap > button.cs-pw-toggle {
        position: static !important;
        flex: 0 0 22px;
        width: 22px;
        min-width: 22px;
        height: 18px;
    }
    .cs-pw-wrap > button.cs-pw-toggle > i { font-size: 12px; }
}
/* /CS-PATCH: VevaShop password reveal */

/* =====================================================================
   CS-PATCH: cart-and-login-fields-reach-the-ios-threshold  [PROMOTED TO CANONICAL 2026-09-27 (.111) from VevaShop R1 - same symptoms measured on LightShop/HyperonShop/UniversalShop phones]  2026-08-28  (VevaShop)

   iOS Safari's zoom threshold is EXACTLY 16px: below it the page zooms on focus and
   does NOT zoom back on blur. On a checkout form that leaves the customer part-way
   through an order on a page wider than their screen.

   MEASURED at 375 with one product in the cart (these fields do not exist on an
   empty cart, so an empty-cart sweep cannot see them):

       input[name=username]     .inputbox        12px  h20
           markup: input < div.width30.floatleft < fieldset.userdata < form
       input[name=password]     .inputbox        12px  h20
           markup: input < span.cs-pw-wrap < div.width30.floatleft < fieldset.userdata
       input[name=coupon_code]  .coupon          12px  h20
           markup: input < td < tr < tbody < table.cart-summary
       input[name^=quantity]    .quantity-input  14px  h44

   ⚠️ FashionShop's equivalent blocks do NOT work here and were removed rather than
   left in place as dead selectors. That template ships a newer VirtueMart layout
   (`.mb-3`, `.form-control`, `.vm-coupon-container`, `.vm-form-row`); this donor
   uses the legacy `.inputbox` class and lays the coupon and quantity out inside
   `table.cart-summary`. Porting a block between templates requires checking the
   markup shape, not just the template family.

   ⚠️ The password sits inside `span.cs-pw-wrap`, the wrapper our own password-reveal
   helper builds. Any selector written with a `>` combinator from the field's
   original parent stops matching once that helper runs.

   Type-qualified so the terms and newsletter checkboxes keep their own size.
   Buttons are excluded on purpose: iOS only zooms for text entry, so the submit
   controls at 13-15px are not a zoom problem.
   ===================================================================== */
@media (max-width: 767px) {
    fieldset.userdata input.inputbox[type="text"],
    fieldset.userdata input.inputbox[type="email"],
    fieldset.userdata input.inputbox[type="password"],
    fieldset.userdata .cs-pw-wrap > input[type="password"],
    .cart-view fieldset.userdata input[type="text"],
    .cart-view fieldset.userdata input[type="password"],
    table.cart-summary input[type="text"].coupon,
    table.cart-summary input.quantity-input,
    /* the donor sets 14px !important at (0,3,0) in vm-squareshop.css:1505
       (`.cart-summary .quantity-input.js-recalculate`). Both declarations are
       !important, so specificity decides and (0,2,2) loses. Naming the third
       class takes this to (0,3,2). */
    table.cart-summary input.quantity-input.js-recalculate,
    .cart-summary .quantity-input.js-recalculate,
    input[type="text"].coupon,
    input[type="text"].quantity-input:not(.addtocart-bar input) {
        font-size: 16px !important;
        min-height: 44px !important;
        box-sizing: border-box !important;
    }
    /* .131 addtocart-bar-qty-own-height */
    /* 2026-10-19 (Tim, HyperShop grad: on phone product cards the quantity number overlapped Add to Cart; it should be the
       height of the -/+ buttons). The bare selector above also caught the product add-to-cart bar, where the donor sizes
       the box to its 34px -/+ controls and puts the button right under them: 44px pushed the box 8-10px into the button.
       The bar keeps the donor's own height and its BS2 content-box model (BS5's global border-box made the product-page box
       30px against 34px -/+: the donor height excludes its 1px border). Only the 16px no-zoom font is added. */
    .addtocart-bar input[type="text"].quantity-input { font-size: 16px !important; box-sizing: content-box; }
    /* the quantity box is deliberately narrow; it still has to clear the threshold */
    table.cart-summary input.quantity-input.js-recalculate,
    table.cart-summary input.quantity-input {
        width: 3.2em !important;
        text-align: center !important;
    }
}
/* /CS-PATCH: VevaShop cart and login fields reach the iOS threshold */

/* =====================================================================
   CS-PATCH: utility-row-fits-at-320  [PROMOTED TO CANONICAL 2026-09-27 (.111) from VevaShop R1 - same symptoms measured on LightShop/HyperonShop/UniversalShop phones]  2026-08-28  (VevaShop)

   MEASURED at 320 before: the row needs 266px of chips + 50px of gaps = 316px,
   but has 304px of usable width (320 less its 8px side padding). Centred, the
   12px of overflow split evenly and shaved both end chips - burger at x=-6, cart
   right edge at 311 against a 305px scrollWidth.

   ⚠️ The page did not scroll sideways, because the row is `cs-full-bleed` and the
   overflow happens inside an element the root clips. `scrollWidth == clientWidth`
   would have reported this clean. Screenshot, don't just measure.

   The gap gives way, not the chip: 44px is the tap-target floor. At 6px the row
   needs 296px and fits with room to spare. Applied below 360 only, so the 375
   layout Tim signed off on is untouched.
   ===================================================================== */
@media (max-width: 359px) {
    #cs-utility-row {
        gap: 6px !important;
        padding-left: 4px !important;
        padding-right: 4px !important;
    }
}
/* /CS-PATCH: VevaShop utility row fits at 320 */

/* =====================================================================
   CS-PATCH: cart-confirmation-fits-the-phone  [PROMOTED TO CANONICAL 2026-09-27 (.111) from VevaShop R1 - same symptoms measured on LightShop/HyperonShop/UniversalShop phones]  2026-08-28  (VevaShop)
   v2 - v1 targeted fancybox 2.x CLASS names; this donor ships fancybox 1.3.4,
   which uses IDs, so v1 matched nothing.

   Adding an item on a phone opened VirtueMart's confirmation wider than the
   screen: "Show Cart" was cut off mid-word and the page gained a sideways scroll.

   MEASURED @375 with the box open:
       #fancybox-wrap  inline width 340px, left 10px, class="" (ID only)
                       computed width 380px -> right 390 vs viewport 375
       the extra 40px is fancybox's own side padding on a content-box element

   Hence border-box (so the declared width is the whole width) plus a centring
   left/transform pair. Width and left are written inline by fancybox's JS, so
   these need !important to reach them.

   ⚠️ Unreachable until today: with the cart's CSRF token missing the request
   303'd and this box never rendered, which is why no graduation test caught it.
   ===================================================================== */
@media (max-width: 767px) {
    #fancybox-wrap {
        left: 50% !important;
        transform: translateX(-50%) !important;
        width: calc(100vw - 20px) !important;
        max-width: calc(100vw - 20px) !important;
        box-sizing: border-box !important;
        padding: 0 !important;
    }
    #fancybox-outer,
    #fancybox-content,
    #fancybox-content > div {
        width: 100% !important;
        max-width: 100% !important;
        box-sizing: border-box !important;
    }
    /* the two actions share one line and were the part being clipped */
    #fancybox-content .continue_link,
    #fancybox-content .showcart_link { white-space: normal !important; }
}
/* /CS-PATCH: VevaShop cart confirmation fits the phone */

/* =====================================================================
   CS-PATCH: bs5css-modal-shell  2026-09-27 (.112)  (found on FlatGear by agJ; fleet A/B by the orchestrator)

   bs5modal-on-bs2 (07-14) was written for the VMT cohort, which loads BS5's JS
   but NO Bootstrap CSS, and says the Phase 6 wrappers "stay transparent blocks".
   ET stages ALSO load Joomla's BS5 CSS (Phase 3b.1), whose `.modal{height:100%}`,
   `.modal-dialog{max-width:500px;margin:1.75rem auto}` and white `.modal-content`
   card beat that intent: the login dialog opened as a 900px-tall white sheet
   (dark donor boxes turned white; white buttons/titles vanished).

   MEASURED @1400, #LoginForm opened, 37 ET stages: 29 stages 560-700 x 900 white
   (live 347-544 tall). With this block every one comes back to within ~+-50px of
   live's height and the donor's own box colour; the 4 stages already tuned
   per-template (Essential, FlatGear, LightShop, UniversalShop) are unchanged.
   No !important and modest specificity, so a template's own `#LoginForm` rules
   still win. Inert on VMT (no BS5 CSS there = nothing to undo).
   ===================================================================== */
.modal:has(> .modal-dialog) { height: auto; overflow: visible; }
.modal > .modal-dialog { width: auto; max-width: none; min-height: 0; margin: 0; pointer-events: auto; }
.modal > .modal-dialog > .modal-content { background: transparent; border: 0; border-radius: 0; box-shadow: none; color: inherit; }
/* /CS-PATCH: bs5css-modal-shell */

/* CS-PATCH: cart-text-label-grid  2026-09-28 (VMT grad prep, rev .113)
   The (a-i) rule sizes every header control with a direct <i> child as a 32x32 ICON button. A text-label
   cart (`<a id="cartpanel"><i/><span class="cart-module-title">My Cart</span><span class="products-number">
   Items: 0</span></a>` - Studio, XStore, Armada) also has a direct <i>, so its two-line label was crushed
   into 32px and wrapped over itself ("My / Cart0"). MEASURED @1400: live 83x36 / 87x36, stage 32x32.
   Keyed on the label span (count-badge carts keep the 32px box): icon left spanning both rows, the two
   spans stacked beside it, width to content. AFTER: Studio 85x32, XStore 89x32 - same layout as live. */
@media (min-width: 768px) {
  #cartpanel.cart-button:has(> .cart-module-title):not(:where(.cs-donor-hdr-cells *)) {
    display: inline-grid !important; grid-template-columns: auto auto; grid-template-rows: auto auto;
    column-gap: 6px; align-items: center; justify-items: start;
    width: auto !important; min-width: 32px !important; height: auto !important; min-height: 32px !important;
    padding: 0 8px !important; line-height: 1.15 !important;
  }
  #cartpanel.cart-button:has(> .cart-module-title) > i { grid-row: 1 / span 2; }
  #cartpanel.cart-button:has(> .cart-module-title) > span { display: block !important; white-space: nowrap; }
}


/* =====================================================================
   Q21 promotions  2026-09-28 (.114) - generalised from the 2026-09-27 overnight
   per-template blocks. Protocol per block: the block was served in place of the
   stage bridge on ALL 37 ET grad-prep stages (page.route, frozen clock, same-page
   stylesheet swap; separate loads where JS measures at init) and pixel-diffed +
   box-diffed against the unchanged bridge; then the matching per-template block
   was removed to prove this rule alone fixes the original defect.
   ===================================================================== */

/* CS-PATCH: phone-logo-bare-h1-unpin  2026-09-28 (.114, Q21 A/B by agM)
   The phone header un-absolutes #h1 only. ET donors pin a BARE <h1>, or .h1/.h2
   classes, absolutely inside #sn-position, which then measures 0 wide: slogan printed
   over the logo (Engage, Ignite, Lucid), logo under the chip row (Fluency), logo under
   the burger (LightShop), logo 0x0 (Extend). Headers that cs-mobile-generic.js moved
   (.cs-mob-logo-branch) are left to mobile-logo-unpin: Ngine positions its absolute h1
   there on purpose and moved x=20 -> 77 without the exclusion.
   A/B, 37 stages x 375/320: 0 pixels changed (LightShop: an empty 0x0 .h2 moved).
   Alone (per-template blocks removed) @375: Engage logo 93,70 -> 30,46 with the slogan
   under it (y=78, no overlap); Ignite, Lucid same; Fluency logo off the chip row
   (y 93 -> 4); LightShop logo off the burger (168..324 -> 89..245); Extend logo 0x0 ->
   visible (its own 170px cap still needed). Empressa's .h1 sits in .snc-handler and is
   not reached - its block stays. */
@media (max-width: 767px) {
  #sn-position:not(.cs-mob-logo-branch *) > h1,
  #sn-position:not(.cs-mob-logo-branch *) > .h1,
  #sn-position:not(.cs-mob-logo-branch *) > .h2 {
    position: static !important;
    top: auto !important; left: auto !important; right: auto !important;
  }
}
/* /CS-PATCH: phone-logo-bare-h1-unpin */

/* CS-PATCH: drawer-megapanel-module-menus  2026-09-28 (.114, Q21 A/B by agM)
   The drawer clones a mega panel whose content is menu MODULES; the accordion only
   re-opens li.cs-open > ul, but these ul.menu-nav sit inside .module-content, so the
   panel opened onto bare module headings and its links stayed 0x0.
   A/B, 37 stages x 375/320 with the drawer open and every accordion expanded: 0 changed.
   Alone: HighPulse visible drawer links 9 -> 15, Various 7 -> 22. */
@media (max-width: 767px) {
  #cs-drawer .cs-drawer-menu li.cs-open .module-content > ul.menu-nav { display: block !important; }
}
/* /CS-PATCH: drawer-megapanel-module-menus */

/* CS-PATCH: phone-slideshow-flex-item-width  2026-09-28 (.114, Q21 A/B by agM)
   mobile-fixed-width-wrapper-cap gives #slideshow-handler width:auto. Where the handler
   is a FLEX ITEM of a .row, auto = content width, so Camera initialised in a 38px box
   (MagnetaShop, NewsCast: spinner and caption squeezed into a column).
   A/B, separate page loads (Camera measures at init), 37 stages x 375/320: handler and
   camera boxes identical on all 37. Alone: MagnetaShop 38 -> 375 (320 -> 320) wide,
   NewsCast handler 78 -> 335, camera 38 -> 295. */
@media (max-width: 767px) {
  .row > #slideshow-handler,
  .row-fluid > #slideshow-handler { flex: 0 0 100% !important; width: 100% !important; max-width: 100% !important; }
}
/* /CS-PATCH: phone-slideshow-flex-item-width */

/* CS-PATCH: camera-pag-span-dots  2026-09-28 (.114, Q21 A/B by agM)
   camera-pag-dots-mobile hides li > span, but most ET donors mark the CURRENT slide (and
   several paint every dot) on that span, not on the li. MEASURED live @375 on 27 of the
   37 stages: span display block with the donor colour; stage: span hidden, so the current
   slide indicator was gone everywhere and on the transparent-li donors (Empressa,
   HighLevel, HyperonShop, LightShop, Render, Squado) no dots showed at all. Show the span
   as a dot inset 2px in the 14px li (ring donors keep their ring, as live does); the
   camera number text inside it stays hidden. Where the donor leaves the span transparent
   (li paints the dot, e.g. HighPulse) nothing changes.
   A/B, 37 stages x 375/320: 10 unchanged, 27 changed - on every one the dots now carry
   exactly the span colours live shows (e.g. Captiva current rgb(171,14,14), Synchronize
   rgb(0,152,230), HighLevel rgb(230,134,0)). Alone (per-template span-dot blocks removed
   on Restaurant, UltimateShop, ZeusShop, MagnetaShop): pager restored in live colours.
   Those blocks fill the whole li; they may stay if that exact look is wanted. */
@media (max-width: 767px) {
  .camera_wrap .camera_pag .camera_pag_ul li { overflow: visible !important; }
  .camera_wrap .camera_pag .camera_pag_ul li > span,
  .camera_wrap .camera_pag .camera_pag_ul li.cameracurrent > span {
    display: block !important; position: absolute !important;
    top: 2px !important; left: 2px !important; right: 2px !important; bottom: 2px !important;
    width: auto !important; height: auto !important; margin: 0 !important;
    border-radius: 50% !important;
    font-size: 0 !important; line-height: 0 !important; color: transparent !important;
    text-indent: -9999px !important; overflow: hidden !important;
  }
  .camera_wrap .camera_pag .camera_pag_ul li > span > span { display: none !important; }
}
/* /CS-PATCH: camera-pag-span-dots */

/* CS-PATCH: home-section-tabs-not-flex  2026-09-28 (.114, Q21 A/B by agM)
   VirtueMart 4 vm-bs5-common.css sets #tabs { display:flex } for ITS product tabs
   (a ul#tabs). ET shop donors use section#tabs for the home product carousel; as a flex
   container its pane shrink-wraps before Owl measures, so Owl sized its items from a
   narrow box and never re-measured (MagnetaShop 66px items, UniversalShop 102px).
   Scoped to section#tabs so VM's ul#tabs keeps its flex.
   A/B, separate loads, 37 stages x 1400/375: section#tabs on 11 stages, flex -> block on
   LightShop/UniversalShop/ZeusShop; tab box and Owl item widths identical on all 37.
   Alone: MagnetaShop Owl items 66 -> 222 @1400 (live 222), 155 -> 335 @375;
   UniversalShop 102 -> 297 @1400 (live 297), 159 -> 370 @375. */
section#tabs { display: block; }
/* /CS-PATCH: home-section-tabs-not-flex */

/* CS-PATCH: a-modal-not-a-dialog  2026-09-28 (VMT grad prep, rev .115)
   The BS2 dialog shell above (`.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px}` + the phone
   `left/right` rules) and BS5's own `.modal{position:fixed}` also match VirtueMart's SqueezeBox lightbox, print and
   e-mail LINKS - `<a class="modal">`. They became invisible fixed boxes: @1400 at ~700,80 over the header (a grey
   square on Illusion/Impress), @375 359x32 at y=80 over the phone chip row (hitscan: chips "covered by A.modal" on
   Vivid/Quassar). Found independently by 5 agents; 36 of 43 VMT stages carried the same per-template block.
   Dialogs are always a div, so only anchors are reset - div.modal is untouched. */
a.modal {
  position: static !important; top: auto !important; left: auto !important; right: auto !important; bottom: auto !important;
  z-index: auto !important; margin: 0 !important; overflow: visible !important;
  background-color: transparent; border: 0; border-radius: 0; box-shadow: none;
}

/* CS-PATCH: module-menu-bs5-reboot-indent  2026-09-28 (.116, C2)
   BS5's reboot `ul{padding-left:2rem}` pushed footer/sidebar menu modules 32px right of live (the donors are BS2, no
   indent). Specificity 0,0,3: beats the reboot, loses to any donor class/id rule. `body.cs-bs5-donor` (a donor that is
   itself Bootstrap 5 - Theon) keeps its own 32px. A/B 43 VMT: Quassar + SparkStore = live; 7 per-template blocks superseded. */
@media (min-width: 768px) {
  html body:where(:not(.cs-bs5-donor)) :where(.module-content, .moduletable) > ul:where(.menu) { padding-left: 0; }
}

/* CS-PATCH: bs2-well-page-header  2026-09-28 (.116, C3)
   BS2 `.well` (Joomla login form, VirtueMart empty cart) and the donors' bootstrap-extended `.page-header` had no shim
   under BS5 - grey boxes and heading rules gone. 0,0,1+ so it beats donor element resets (Metro's `form{background:
   transparent}`) and loses to donor classes. A/B 43 VMT: 31 improved, 0 regressed (Theon opts out via cs-bs5-donor). */
html body:where(:not(.cs-bs5-donor)) :where(.well) { min-height: 20px; padding: 19px; margin-bottom: 20px; background-color: #f5f5f5; border: 1px solid #e3e3e3; border-radius: 4px; box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); }
html body:where(:not(.cs-bs5-donor)) :where(.well-large) { padding: 24px; border-radius: 6px; }
html body:where(:not(.cs-bs5-donor)) :where(.well-small) { padding: 9px; border-radius: 3px; }
html body:where(:not(.cs-bs5-donor)) :where(.page-header) { padding-bottom: 5px; margin: 2px 0 10px; border-bottom: 1px solid #eee; }

/* CS-PATCH: phone-logo-left-in-column  2026-09-28 (.116, C5)
   The phone rule stacks #site-name-handler as a column with align-items:flex-start, but two later rules set
   `align-items:center !important` and `#site-name-handler > *{align-self:center !important}` - in a column that
   CENTRES the logo (Studio x=104, Impress x=95; the standard is logo left). A/B 43 VMT: 18 improved, no burger overlap. */
@media (max-width: 767px) {
  #top-handler #site-name-handler, #site-name-handler { align-items: flex-start !important; }
  #site-name-handler > * { align-self: flex-start !important; }
}

/* CS-PATCH: megamenu-link-bs2-content-box  2026-09-28 (.117 C6m)
   BS2 had no global border-box: a donor mega-menu link sized `height:40px; padding:10px 0` (or 5px) is a 60/50px cell
   on live; BS5's reboot `*{box-sizing:border-box}` shrinks it to 40 and the whole menu bar with it. No !important,
   specificity (1,0,3): beats the reboot and plain donor link rules; a template rule with an id + class still wins.
   A/B vs live: TEST2 (all blocks kept) changes exactly 2 of 37 stages, both to live: UltimateShop #megamenu-handler
   40 -> 50 (live 50) and the li 20 -> 30 tall (live 30); UniversalShop #mega-menu 40 -> 46, #st-navigation 63 -> 69
   (live 46/69), mega panel top 122 = live; hover walk into the panels OK. Nothing <980 changes.
   TEST1: reproduces the `ul#mega-menu > li > a { box-sizing: content-box }` line of zeus-desktop-megamenu-bar-live
   (#mega-menu 40 -> 60 @1400, 34 -> 44 @1024 = live) and veva-desktop-menu-bar-live (40 -> 50 / 34 -> 54 = live).
   The rest of those two blocks (cart cell) stays. */
@media (min-width: 980px) { ul#mega-menu > li > a { box-sizing: content-box; } }

/* CS-PATCH: phone-account-content-first  2026-09-30 (canonical candidate for .119, agX)
   .119 = .118 with a hardened detector and a much cheaper selector structure. Same rule, same result on every ET account page.
   Tim (verbatim): "on mobile no modules should show above the account info page on any template, unless it is a
   welcome to the store menu". Breadcrumbs may stay above; only a STORE CATEGORY menu may stay above (any title).

   WHY .119 (VMT Infinity regression under .118): Infinity's home, 375px, initialised a visible Owl carousel at width 0
   (6/6, 8/8, 4/4, 3/3, 2/8, 2/12 loads across idle runs; .117 0 in every run). It is NOT a detector match: a
   MutationObserver sampler saw no account marker at any moment of any load, and .118 with the `.login > form` marker
   removed still broke 4/4. The cause is COST. .118's :has() rules have broad subjects and whole-subtree arguments, so
   every forced style recalc during load got dearer. Slick's setDimensions loop (per-slide width write + read) went
   from about 0.7 s to 1.4 s of jQuery getComputedStyle time, and that shifted Owl-v1-at-ready against the donor's
   tab-pane activation. The race is in the donor, but the canonical must not add enough cost to trigger it:
   - account markers are COMPONENT-level only (com_users view classes, VM form[name=userForm] / .vm-orders-list, J3
     override ids); the J3 login form counts only inside a content wrapper and outside any module / #LoginForm / modal;
   - every rule subject is an exact layout token (no universal/attribute-substring subjects carrying :has); in P the
     cheap child/sibling :has() runs before the account scan;
   - no :has(~ * ...) sibling-subtree scan remains; order after the branch comes from rule (4).
   Measured on Infinity home 375: slick-style loop (360 inline-width writes + reads) base 2.3-2.6 s, .118 +2.3 s,
   .119 +1.3 s; parse-to-DOMContentLoaded base 591 ms, .118 +71 ms, .119 +47 ms; zero-width Owl: .118 25/41 idle
   loads, .119-draft 0/20 and .119 0/10 (the last window ran during the ET core updates and .118 also read 0/10).
   Still not free - donors that init carousels while their pane is hidden stay fragile, which is VMT per-template work.

   Scope: <=767px, account page = body holds .com-users-login|-logout|-registration|-remind|-reset|-profile|-profile__edit,
   #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list,
   or [id*=content] > .login > form[action*="user.login"] outside a module/#LoginForm/.modal (J3 override: CorporateImpact).
   (#com-form-login is NOT a marker: VirtueMart prints it on the cart too.)
   Logic: (1) P = exact layout wrapper (body .row .row-fluid .global-container .span8/9 .col-md-8/9 #main-cols
   #site_wrapper #tmp-container #padding #site-handler #content-handler #content-cols) with a module-region child
   BEFORE a branch child, holding account markup -> wrapping flex row of full-width items. (2) module-region children
   of P (col-md-2/3/4 col-sm/lg-* span2/3/4 #col-left #tab-modules #top-modules #top-content-modules
   #slideshow-handler-bg #sl-top #deep-box aside moduletable* .module .mod-custom; bare div holding #top-modules) that
   hold no header chrome/breadcrumbs -> order 1. (3) a region holding a VirtueMart category module (ul.VMmenu, or
   ul.menu > li > div > a) is dissolved so that module stays above. (4) siblings after the account branch -> order 2.
   Brand-neutral token lists; read-only check on VMT Nano, Armada, Illusion, VMShopBlue, SparkStore, Flexo: store menu
   kept, every other module moved.

   A/B on the served ET stages (A = served bridge minus .118 and its blocks, B = .119 in place of .118, 2026-09-30):
   - account pages 375/320, guest + logged in, all 37: 564 page-widths; 308 had modules above (27 templates, 954
     modules) -> 0; store menu kept above on Captiva, UltimateShop, UniversalShop, XeonShop; 4,132 fields, 0
     unreachable; login submits 37/37; h-scroll/overlap/new hitscan/drawer faults 0. Box geometry = .118 on 542/564:
     Eclipse/Various 1px (.119 = pre-.118), CorporateImpact moved menu column now sits before the right column,
     4 random-product pages.
   - load time, non-account pages (home + category + product|article), 6 loads x 92 page series, 375: zero-width
     carousels .117 0/552, .119 0/552; loads where ANY .119 selector matched at ANY moment (MutationObserver + rAF
     sampler; positive control on account pages matches) 0/552.
   - leak: .119 selectors match 0 elements on 110/110 non-account pages incl. cart; box geometry at 375/768/1400:
     321/330 identical, 9 = random modules/1px; account pages at 768/1400: 360/474 identical, 103 random ids, 11 random
     sidebars (same pages as the base-vs-base noise). */
@media (max-width: 767px) {
  /* (1) P: a layout wrapper with a module region child BEFORE a branch child, and account markup inside -> wrapping
     flex row of full-width items (cheap child/sibling :has first, the account scan last) */
  :is(body, .row, .row-fluid, .global-container, .span8, .span9, .col-md-8, .col-md-9, #main-cols, #site_wrapper, #tmp-container, #padding, #site-handler, #content-handler, #content-cols):has(> :is(.col-md-2, .col-md-3, .col-md-4, .col-sm-3, .col-sm-4, .col-lg-2, .col-lg-3, .col-lg-4, .span2, .span3, .span4, #col-left, #tab-modules, #top-modules, #top-content-modules, #slideshow-handler-bg, #sl-top, #deep-box, aside, [class^="moduletable"], [class*=" moduletable"], .module, .mod-custom) ~ :is(.span8, .span9, .col-md-6, .col-md-8, .col-md-9, #main-cols, #column-right, #maincontent, #tmp-container, #box2, .container, .container-fluid, .main-bg, #main-handler, main, .tmp-content-area, #middle-content-handler, div[id=""], .com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *)), > * > #top-modules):has(.com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *)) {
    display: flex !important; flex-flow: row wrap !important; align-items: flex-start !important; align-content: flex-start !important;
  }
  :is(body, .row, .row-fluid, .global-container, .span8, .span9, .col-md-8, .col-md-9, #main-cols, #site_wrapper, #tmp-container, #padding, #site-handler, #content-handler, #content-cols):has(> :is(.col-md-2, .col-md-3, .col-md-4, .col-sm-3, .col-sm-4, .col-lg-2, .col-lg-3, .col-lg-4, .span2, .span3, .span4, #col-left, #tab-modules, #top-modules, #top-content-modules, #slideshow-handler-bg, #sl-top, #deep-box, aside, [class^="moduletable"], [class*=" moduletable"], .module, .mod-custom) ~ :is(.span8, .span9, .col-md-6, .col-md-8, .col-md-9, #main-cols, #column-right, #maincontent, #tmp-container, #box2, .container, .container-fluid, .main-bg, #main-handler, main, .tmp-content-area, #middle-content-handler, div[id=""], .com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *)), > * > #top-modules):has(.com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *)) > :not([id^="cs-"]) { flex: 1 1 100% !important; min-width: 0; }
  /* (2) Q: module regions that are children of P (no header chrome / breadcrumbs) -> after the content */
  :is(body, .row, .row-fluid, .global-container, .span8, .span9, .col-md-8, .col-md-9, #main-cols, #site_wrapper, #tmp-container, #padding, #site-handler, #content-handler, #content-cols):has(> :is(.col-md-2, .col-md-3, .col-md-4, .col-sm-3, .col-sm-4, .col-lg-2, .col-lg-3, .col-lg-4, .span2, .span3, .span4, #col-left, #tab-modules, #top-modules, #top-content-modules, #slideshow-handler-bg, #sl-top, #deep-box, aside, [class^="moduletable"], [class*=" moduletable"], .module, .mod-custom) ~ :is(.span8, .span9, .col-md-6, .col-md-8, .col-md-9, #main-cols, #column-right, #maincontent, #tmp-container, #box2, .container, .container-fluid, .main-bg, #main-handler, main, .tmp-content-area, #middle-content-handler, div[id=""], .com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *)), > * > #top-modules):has(.com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *)) > :is(.col-md-2, .col-md-3, .col-md-4, .col-sm-3, .col-sm-4, .col-lg-2, .col-lg-3, .col-lg-4, .span2, .span3, .span4, #col-left, #tab-modules, #top-modules, #top-content-modules, #slideshow-handler-bg, #sl-top, #deep-box, aside, [class^="moduletable"], [class*=" moduletable"], .module, .mod-custom):not(header, #top-handler, .header-spacing, [id^="cs-"], .breadcrumb, .breadcrumbs, #breadcrumb-line, .pathway, .modal, :has(header, #top-handler, .header-spacing, #cs-utility-row, #cs-menu-toggle, .breadcrumb, .breadcrumbs, .breadcrumb1, #breadcrumb-line, #breadcrumb-handler, .mod-breadcrumbs__wrapper, .pathway)), :is(body, .row, .row-fluid, .global-container, .span8, .span9, .col-md-8, .col-md-9, #main-cols, #site_wrapper, #tmp-container, #padding, #site-handler, #content-handler, #content-cols):has(> :is(.col-md-2, .col-md-3, .col-md-4, .col-sm-3, .col-sm-4, .col-lg-2, .col-lg-3, .col-lg-4, .span2, .span3, .span4, #col-left, #tab-modules, #top-modules, #top-content-modules, #slideshow-handler-bg, #sl-top, #deep-box, aside, [class^="moduletable"], [class*=" moduletable"], .module, .mod-custom) ~ :is(.span8, .span9, .col-md-6, .col-md-8, .col-md-9, #main-cols, #column-right, #maincontent, #tmp-container, #box2, .container, .container-fluid, .main-bg, #main-handler, main, .tmp-content-area, #middle-content-handler, div[id=""], .com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *)), > * > #top-modules):has(.com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *)) > div:is(:not([class]), [class=""]):is(:not([id]), [id=""]):has(> #top-modules) { order: 1; }
  /* (3) a region holding a store CATEGORY menu is dissolved; the menu module keeps its place above */
  :is(body, .row, .row-fluid, .global-container, .span8, .span9, .col-md-8, .col-md-9, #main-cols, #site_wrapper, #tmp-container, #padding, #site-handler, #content-handler, #content-cols):has(> :is(.col-md-2, .col-md-3, .col-md-4, .col-sm-3, .col-sm-4, .col-lg-2, .col-lg-3, .col-lg-4, .span2, .span3, .span4, #col-left, #tab-modules, #top-modules, #top-content-modules, #slideshow-handler-bg, #sl-top, #deep-box, aside, [class^="moduletable"], [class*=" moduletable"], .module, .mod-custom) ~ :is(.span8, .span9, .col-md-6, .col-md-8, .col-md-9, #main-cols, #column-right, #maincontent, #tmp-container, #box2, .container, .container-fluid, .main-bg, #main-handler, main, .tmp-content-area, #middle-content-handler, div[id=""], .com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *)), > * > #top-modules):has(.com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *)) > :is(.col-md-2, .col-md-3, .col-md-4, .col-sm-3, .col-sm-4, .col-lg-2, .col-lg-3, .col-lg-4, .span2, .span3, .span4, #col-left, #tab-modules, #top-modules, #top-content-modules, #slideshow-handler-bg, #sl-top, #deep-box, aside, [class^="moduletable"], [class*=" moduletable"], .module, .mod-custom):not([class*="moduletable"], .module, [class^="mod-"], [class*=" mod-"]):has(ul.VMmenu, ul.menu > li > div > a):not(header, #top-handler, .header-spacing, [id^="cs-"], .breadcrumb, .breadcrumbs, #breadcrumb-line, .pathway, .modal, :has(header, #top-handler, .header-spacing, #cs-utility-row, #cs-menu-toggle, .breadcrumb, .breadcrumbs, .breadcrumb1, #breadcrumb-line, #breadcrumb-handler, .mod-breadcrumbs__wrapper, .pathway)) { display: contents !important; }
  :is(body, .row, .row-fluid, .global-container, .span8, .span9, .col-md-8, .col-md-9, #main-cols, #site_wrapper, #tmp-container, #padding, #site-handler, #content-handler, #content-cols):has(> :is(.col-md-2, .col-md-3, .col-md-4, .col-sm-3, .col-sm-4, .col-lg-2, .col-lg-3, .col-lg-4, .span2, .span3, .span4, #col-left, #tab-modules, #top-modules, #top-content-modules, #slideshow-handler-bg, #sl-top, #deep-box, aside, [class^="moduletable"], [class*=" moduletable"], .module, .mod-custom) ~ :is(.span8, .span9, .col-md-6, .col-md-8, .col-md-9, #main-cols, #column-right, #maincontent, #tmp-container, #box2, .container, .container-fluid, .main-bg, #main-handler, main, .tmp-content-area, #middle-content-handler, div[id=""], .com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *)), > * > #top-modules):has(.com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *)) > :is(.col-md-2, .col-md-3, .col-md-4, .col-sm-3, .col-sm-4, .col-lg-2, .col-lg-3, .col-lg-4, .span2, .span3, .span4, #col-left, #tab-modules, #top-modules, #top-content-modules, #slideshow-handler-bg, #sl-top, #deep-box, aside, [class^="moduletable"], [class*=" moduletable"], .module, .mod-custom):not([class*="moduletable"], .module, [class^="mod-"], [class*=" mod-"]):has(ul.VMmenu, ul.menu > li > div > a):not(header, #top-handler, .header-spacing, [id^="cs-"], .breadcrumb, .breadcrumbs, #breadcrumb-line, .pathway, .modal, :has(header, #top-handler, .header-spacing, #cs-utility-row, #cs-menu-toggle, .breadcrumb, .breadcrumbs, .breadcrumb1, #breadcrumb-line, #breadcrumb-handler, .mod-breadcrumbs__wrapper, .pathway)) > * { order: 1; flex: 1 1 100% !important; min-width: 0; }
  :is(body, .row, .row-fluid, .global-container, .span8, .span9, .col-md-8, .col-md-9, #main-cols, #site_wrapper, #tmp-container, #padding, #site-handler, #content-handler, #content-cols):has(> :is(.col-md-2, .col-md-3, .col-md-4, .col-sm-3, .col-sm-4, .col-lg-2, .col-lg-3, .col-lg-4, .span2, .span3, .span4, #col-left, #tab-modules, #top-modules, #top-content-modules, #slideshow-handler-bg, #sl-top, #deep-box, aside, [class^="moduletable"], [class*=" moduletable"], .module, .mod-custom) ~ :is(.span8, .span9, .col-md-6, .col-md-8, .col-md-9, #main-cols, #column-right, #maincontent, #tmp-container, #box2, .container, .container-fluid, .main-bg, #main-handler, main, .tmp-content-area, #middle-content-handler, div[id=""], .com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *)), > * > #top-modules):has(.com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *)) > :is(.col-md-2, .col-md-3, .col-md-4, .col-sm-3, .col-sm-4, .col-lg-2, .col-lg-3, .col-lg-4, .span2, .span3, .span4, #col-left, #tab-modules, #top-modules, #top-content-modules, #slideshow-handler-bg, #sl-top, #deep-box, aside, [class^="moduletable"], [class*=" moduletable"], .module, .mod-custom):not([class*="moduletable"], .module, [class^="mod-"], [class*=" mod-"]):has(ul.VMmenu, ul.menu > li > div > a):not(header, #top-handler, .header-spacing, [id^="cs-"], .breadcrumb, .breadcrumbs, #breadcrumb-line, .pathway, .modal, :has(header, #top-handler, .header-spacing, #cs-utility-row, #cs-menu-toggle, .breadcrumb, .breadcrumbs, .breadcrumb1, #breadcrumb-line, #breadcrumb-handler, .mod-breadcrumbs__wrapper, .pathway)) > :is([class*="moduletable"], .module, [class^="mod-"], [class*=" mod-"]):has(ul.VMmenu, ul.menu > li > div > a) { order: 0; }
  /* (4) everything after the account branch (footer, right column, bottom bands) stays after the moved regions;
     regions that sit after the branch keep their place too */
  :is(body, .row, .row-fluid, .global-container, .span8, .span9, .col-md-8, .col-md-9, #main-cols, #site_wrapper, #tmp-container, #padding, #site-handler, #content-handler, #content-cols) > :is(.span8, .span9, .col-md-6, .col-md-8, .col-md-9, #main-cols, #column-right, #maincontent, #tmp-container, #box2, .container, .container-fluid, .main-bg, #main-handler, main, .tmp-content-area, #middle-content-handler, div[id=""], .com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *)):is(.com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *), :has(.com-users-login, .com-users-logout, .com-users-registration, .com-users-remind, .com-users-reset, .com-users-profile, .com-users-profile__edit, #member-registration, #member-profile, #user-registration, #users-profile-core, form[name="userForm"], .vm-orders-list, [id*="content"] > .login > form[action*="user.login"]:not(:is(.moduletable, .module, [class*="moduletable"], #LoginForm, .modal) *))) ~ * { order: 2 !important; }
}


/* =====================================================================
   CS-PATCH: phone-vm-cart-page-fits  2026-09-30 (candidate .120, agY/agY2)  [ET canonical candidate]

   TIM (Captiva, phone): the VirtueMart cart page does not fit. table.cart-summary
   (Name/SKU/Price/Quantity/Tax/Discount/Total) is 445-455px wide at 375 and the Total
   column is cut off; the ADD/EDIT BILLING DETAILS / EDIT CURRENT SHIPPING ADDRESS
   buttons sit side by side at uneven heights (147x85 vs 147x108). The page does NOT
   scroll sideways - html/body clip horizontally - so every scrollWidth gate passed.
   `agent-agY/clipscan.py` is the gate for that blind spot.

   SURVEY, all 10 ET VM shops (VM 4.8.4), a real product in the cart (verified via
   viewJS count), SEF cart menu item, 375 + 320. Two markups:
     A. legacy VM3 `table.cart-summary` (Captiva, HyperonShop, Restaurant, UltimateShop,
        VevaShop, XeonShop). Captiva's donor has no responsive cart CSS: table 455 wide,
        Total/Tax/Discount clipped (320: Tax+Discount+Total). The other five already
        stack the cells but print BARE values - "20,00 EUR / 120,00 EUR" with no label,
        so Tax and Total are indistinguishable. No data-labels in the markup.
     B. VM4 bootstrap layout (LightShop, MagnetaShop, UniversalShop, ZeusShop):
        "Delete Product From Cart" is a 26x32 sprite box with its text spilling over the
        icon (unreadable, and on Magneta the spill covers the tap point); the register
        password pair is 360px in a 335px column (right border clipped, R5-R25);
        subtotal lines print label and amount on separate lines (canonical orphan-.row
        rule makes .row block).

   FIX (<=767, except the VM4 remove button which is ALL widths per Tim; every selector is under
   #cart-view, so no other page is reachable):
     A. each product row becomes a card - image + name as the heading, then labelled
        lines SKU / Price / Quantity (input + update + remove, 44px targets) / Tax /
        Discount / Total. Labels come from `content: var(--cs-cart-lbl-*, "<English>")`
        so a non-English site can set the seven custom properties once. Cells that print
        no amount (Discount, unset Price) are hidden instead of showing an empty label.
        Columns are addressed with :nth-of-type because Restaurant/Ultimate/Veva/Xeon put
        an <input type=hidden> directly inside the <tr> (nth-child is off by one there).
        Totals rows are key/value lines (label left, amount right, Tax/Discount as a
        small labelled line under it); spacer <hr> rows and the header row go.
        Coupon field + Save share a line and wrap below 360.
     billing/shipping: stacked, full width, 44px, centred (A: .billto-shipto; B: a.details).
     legacy guest login: fields full width; Login / checkbox / "Remember me" on one line.
     B. remove button = icon only (text kept as the accessible name) at ALL widths - Tim
        2026-09-30; 44x44 on phones, the donor's 26x32 box on desktop/tablet;
        register/login fields width 100%; subtotal rows back to one line.
     Checkout/Confirm button: full width, 44px.
   Keeps the donor's colours and fonts (only borders use a neutral rgba grey).

   A/B (page.route: stage bridge vs bridge + this block; product in cart):
     375/320, 10 shops: elements past the viewport or cut by a clipping ancestor
       Captiva 12/21 -> 0/0, LightShop/UniversalShop 2 -> 0, ZeusShop 3 -> 1 (the donor's
       BS5 .row negative margin on form#checkoutForm with Zeus's 0-3px content gutter:
       box only, no content lost; site-wide on Zeus, same on live). Others 0 -> 0.
       Captiva table 455 -> 293 (375) / 238 (320). Billing/shipping buttons 147x85 +
       147x108 side by side -> 293x44 + 293x44 stacked (every legacy shop: full width 44).
       Checkout 126-167px -> full width x44 on all 10.
     function, 375 AND 320, every shop, real pointer clicks: qty -> 2 (update button, or
       the +/- stepper on B) -> cart count 2 and total doubled; coupon Save submits
       ("Coupon code not found"); Checkout submits (Captiva/VevaShop show VM's own
       "Error updating cart" dialog - identical WITHOUT the block, pre-existing); remove
       -> count 0. Same results as baseline; control centres hit-test to themselves.
     LEAK: home, a product page and a category page at 375 + the cart at 768/1024/1400,
       all elements' box/display/colour/font keyed by DOM path, A vs B with an A vs A
       noise run: 0 differences on 58 of 60 page-widths; the 2 non-zero are
       random-product modules (LightShop cart@1024 14 = noise 14, MagnetaShop home 7 vs
       noise 4) - neither page has #cart-view or is <=767, so the block cannot reach them.
   .120 re-run (with the all-widths remove button): 375/320 same numbers as above, remove button
     44x44 on all 10; function test identical (qty/coupon/checkout/remove on all 10 at 375 and 320).
     >=768: the ONLY changed element is button.vm2-remove_from_cart on LightShop/MagnetaShop/
     UniversalShop/ZeusShop (same 26x32 box, text hidden, glyph centred); 0 elsewhere. Glyph ink is
     restored per template (<slug>-cart-remove-icon-ink, white / Zeus #00adcc = live).
   ET-only until the VMT session A/Bs it (VMT stages are pinned at .116; no VMT stage
   carries a phone cart-page block today).
   ===================================================================== */
@media (max-width: 767px) {
  /* ---- (1) legacy VM3 table.cart-summary -> product cards + key/value totals ---- */
  #cart-view table.cart-summary,
  #cart-view table.cart-summary > tbody {
    display: block !important; width: 100% !important; min-width: 0 !important; max-width: 100% !important;
    box-sizing: border-box !important;
  }
  #cart-view table.cart-summary > tbody > tr {
    display: flex !important; flex-wrap: wrap !important; align-items: baseline !important;
    width: auto !important; min-width: 0 !important; box-sizing: border-box !important;
    margin: 0 !important; padding: 8px 0 !important; background-image: none !important;
    border-top: 1px solid rgba(128, 128, 128, .25) !important;
  }
  #cart-view table.cart-summary > tbody > tr > td {
    display: block !important; flex: 0 0 auto; width: auto !important; min-width: 0 !important; max-width: 100% !important;
    height: auto !important; box-sizing: border-box !important; padding: 2px 0 !important; border: 0 !important;
    text-align: left !important; overflow-wrap: anywhere;
  }
  /* the header row (labels move onto the values) and the <hr> spacer rows */
  #cart-view table.cart-summary > tbody > tr:has(> th),
  #cart-view table.cart-summary > tbody > tr:has(> td > hr) { display: none !important; }
  /* a value cell that prints no price (Discount, an unused Tax, a free shipment) */
  #cart-view table.cart-summary > tbody > tr > td:not(:first-of-type):not(:has(.vm-display, input, button, strong, img)):not(:nth-of-type(2):has(~ td input.quantity-input)) { display: none !important; }

  /* product row = a card: name as heading, then labelled lines */
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) {
    display: block !important; padding: 12px !important; margin: 0 0 12px !important;
    border: 1px solid rgba(128, 128, 128, .3) !important; border-radius: 4px;
  }
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) > td {
    display: flex !important; justify-content: space-between !important; align-items: center !important;
    gap: 10px; width: 100% !important; padding: 7px 0 !important; text-align: right !important;
    border-top: 1px solid rgba(128, 128, 128, .18) !important;
  }
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) > td::before {
    flex: 0 0 auto; margin-right: auto; text-align: left; font-weight: 600;
  }
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) > td:first-of-type {
    justify-content: flex-start !important; flex-wrap: wrap !important; text-align: left !important;
    border-top: 0 !important; padding-top: 0 !important; font-weight: 600;
  }
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) > td:first-of-type::before { content: none; }
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) > td:first-of-type .cart-images { float: none !important; margin: 0 !important; flex: 0 0 auto; }
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) > td:first-of-type .cart-images img { width: 60px !important; height: auto !important; max-width: 60px !important; }
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) > td:first-of-type .vm-customfield-cart { flex: 1 1 100%; font-weight: normal; }
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) > td:first-of-type .vm-customfield-cart * { position: static !important; float: none !important; }
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) > td:nth-of-type(2)::before { content: var(--cs-cart-lbl-sku, "SKU"); }
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) > td:nth-of-type(3)::before { content: var(--cs-cart-lbl-price, "Price"); }
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) > td:has(input.quantity-input)::before { content: var(--cs-cart-lbl-qty, "Quantity"); }
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) > td:nth-of-type(5):nth-last-of-type(3)::before { content: var(--cs-cart-lbl-tax, "Tax"); }
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) > td:nth-of-type(6):nth-last-of-type(2)::before { content: var(--cs-cart-lbl-discount, "Discount"); }
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) > td:last-of-type::before { content: var(--cs-cart-lbl-total, "Total"); }
  #cart-view table.cart-summary > tbody > tr:has(input.quantity-input) > td:last-of-type { font-weight: 600; }
  /* quantity + update + remove on one line, 44px targets */
  #cart-view table.cart-summary td:has(> input.quantity-input) { gap: 6px !important; }
  #cart-view table.cart-summary td > input.quantity-input { flex: 0 0 auto; margin: 0 !important; float: none !important; }
  #cart-view table.cart-summary td > button.vm2-add_quantity_cart,
  #cart-view table.cart-summary td > button.vm2-remove_from_cart {
    display: inline-flex !important; align-items: center !important; justify-content: center !important;
    flex: 0 0 auto; width: 44px !important; min-width: 44px !important; height: 44px !important; margin: 0 !important;
    padding: 0 !important; float: none !important; position: static !important;
  }

  /* totals rows: label left, amount right, tax/discount as small labelled lines under it */
  #cart-view table.cart-summary > tbody > tr:not(:has(input.quantity-input)) > td:first-of-type { flex: 1 1 55% !important; padding-right: 8px !important; }
  #cart-view table.cart-summary > tbody > tr:not(:has(input.quantity-input)) > td:last-of-type:not(:first-of-type) { margin-left: auto !important; text-align: right !important; }
  #cart-view table.cart-summary > tbody > tr:not(:has(input.quantity-input)) > td:nth-of-type(2):nth-last-of-type(3),
  #cart-view table.cart-summary > tbody > tr:not(:has(input.quantity-input)) > td:nth-of-type(3):nth-last-of-type(2) {
    order: 3; flex: 1 1 100% !important; display: flex !important; justify-content: space-between !important;
    font-size: .9em; padding: 0 !important;
  }
  #cart-view table.cart-summary > tbody > tr:not(:has(input.quantity-input)) > td:nth-of-type(2):nth-last-of-type(3)::before { content: var(--cs-cart-lbl-tax, "Tax"); }
  #cart-view table.cart-summary > tbody > tr:not(:has(input.quantity-input)) > td:nth-of-type(3):nth-last-of-type(2)::before { content: var(--cs-cart-lbl-discount, "Discount"); }
  #cart-view table.cart-summary > tbody > tr:not(:has(input.quantity-input)) > td[colspan] .floatright { float: none !important; display: block; }
  /* coupon: field and button share the line and fit */
  #cart-view table.cart-summary > tbody > tr:has(input[name="coupon_code"]) > td:first-of-type {
    flex: 1 1 100% !important; display: flex !important; flex-wrap: wrap !important; align-items: center !important; gap: 8px; padding-right: 0 !important;
  }
  #cart-view table.cart-summary > tbody > tr:has(input[name="coupon_code"]) > td:not(:first-of-type) { display: none !important; }
  #cart-view table.cart-summary input[name="coupon_code"] { flex: 1 1 180px; width: auto !important; min-width: 0 !important; max-width: none !important; margin: 0 !important; }
  #cart-view table.cart-summary td:has(> input[name="coupon_code"]) > .details-button { flex: 0 0 auto; margin: 0 !important; float: none !important; }
  #cart-view table.cart-summary td:has(> input[name="coupon_code"]) > .details-button > input { margin: 0 !important; min-height: 44px; }
  /* shipment / payment choosers (Captiva prints the full radio list in the row) */
  #cart-view table.cart-summary fieldset.vm-payment-shipment-select { margin: 6px 0 0 !important; padding: 0 !important; min-width: 0 !important; }

  /* ---- (2) billing / shipping (legacy .billto-shipto): stacked, even, full-width buttons ---- */
  #cart-view .billto-shipto > div:not(.clear) {
    float: none !important; width: 100% !important; max-width: 100% !important; margin: 0 0 16px !important; box-sizing: border-box !important;
  }
  #cart-view .billto-shipto a.details {
    display: flex !important; align-items: center !important; justify-content: center !important;
    width: 100% !important; min-height: 44px; box-sizing: border-box !important; margin: 8px 0 0 !important;
    float: none !important; text-align: center !important; white-space: normal !important;
  }

  /* ---- (3) legacy guest login on the cart page ---- */
  #cart-view #com-form-login .width30 { float: none !important; width: 100% !important; margin: 0 0 8px !important; }
  #cart-view #com-form-login #com-form-login-remember { display: flex !important; flex-wrap: wrap; align-items: center; gap: 8px; }
  #cart-view #com-form-login #com-form-login-remember > input[type="submit"] { order: 0; min-height: 44px; margin: 0 8px 0 0 !important; }
  #cart-view #com-form-login #com-form-login-remember > input[type="checkbox"] { order: 1; flex: 0 0 auto; width: auto !important; min-width: 0 !important; height: auto !important; margin: 0 !important; float: none !important; position: static !important; }
  #cart-view #com-form-login #com-form-login-remember > label { order: 2; margin: 0 !important; float: none !important; }

  /* ---- (4) VM4 bootstrap layout (LightShop family) ---- */
  /* register / login fields: the password pair was 360px in a 335px column (clipped) */
  #cart-view .vm-checkout-register-userfields .form-control,
  #cart-view .vm-login .form-control { width: 100% !important; max-width: 100% !important; box-sizing: border-box !important; }
  /* shipping-address button: full width like the legacy family */
  #cart-view .vm-checkout-shipping-userfields a.details,
  #cart-view .vm-checkout-billing-userfields a.details {
    display: flex !important; align-items: center !important; justify-content: center !important;
    width: 100% !important; min-height: 44px; box-sizing: border-box !important; text-align: center !important; white-space: normal !important;
  }
  /* subtotal / shipment / payment / total / tax: label and amount on one line
     (the phone column stacking put every amount on a line of its own) */
  #cart-view .vm-checkout-subtotals > .row { display: flex !important; flex-wrap: nowrap !important; align-items: baseline !important; }
  #cart-view .vm-checkout-subtotals > .row > .col { flex: 1 1 0% !important; width: auto !important; max-width: none !important; min-width: 0 !important; }
  /* remove-from-cart (VM4 layout): the icon-only box from the all-widths block below, grown to a 44px target */
  #cart-view .vm-checkout-products button.vm2-remove_from_cart { width: 44px !important; min-width: 44px !important; height: 44px !important; }
  #cart-view .vm-checkout-products button.vm2-remove_from_cart::before { font-size: 16px !important; }

  /* ---- (5) the order button is the page's primary action: full width ---- */
  #cart-view .checkout-button-top { float: none !important; width: 100% !important; text-align: center !important; }
  #cart-view button[name="checkout"] { display: block !important; width: 100% !important; min-height: 44px; margin-left: 0 !important; margin-right: 0 !important; float: none !important; box-sizing: border-box !important; }
}
/* ALL WIDTHS (Tim 2026-09-30): VM4 layout remove-from-cart = icon only.
   The donor styles `.vm2-remove_from_cart` as a 26x32 sprite box, but the VM4 layout prints
   "Delete Product From Cart" as the button TEXT: live shows it spilling out of the box ("Delet /
   Prod", white on the accent box, cut off), the stage the same plus the canonical trash glyph.
   Text kept as the accessible name (font-size 0 keeps it in the accessibility tree), the canonical
   dead-sprite trash glyph centred in the donor's own box. Desktop/tablet keep the donor's 26x32
   box; <=767 grows it to 44x44 (phone block above). The glyph INK is per template (the canonical
   dead-sprite block forces color:inherit; the donors' ink was white / their accent) - see the
   <slug>-cart-remove-icon-ink blocks. Legacy table carts (button.vmicon, no text) are not matched. */
#cart-view .vm-checkout-products button.vm2-remove_from_cart {
  display: inline-flex !important; align-items: center !important; justify-content: center !important;
  padding: 0 !important; font-size: 0 !important; line-height: 0 !important; overflow: hidden !important;
  white-space: nowrap !important; text-indent: 0 !important; vertical-align: middle;
}
#cart-view .vm-checkout-products button.vm2-remove_from_cart::before { font-size: 14px; line-height: 1; }
/* /CS-PATCH: phone-vm-cart-page-fits */

/* =====================================================================
   CS-PATCH: phone-pagination-wraps  2026-09-30 (candidate, agY2)  [ET canonical candidate]

   Joomla 5 prints the blog/category pagination as BS5 `ul.pagination` (flex, nowrap, 40-44px
   buttons) next to `<p class="...__counter counter float-end">Page 1 of N</p>`. ET donors never
   loaded BS5 CSS, so on live `float-end` did nothing and the counter sat above the buttons; the
   stages load Joomla's BS5 CSS (Phase 3b.1), so the counter floats right and the non-wrapping
   button row runs under it and past the column - the last button is cut off by the html/body
   clip (so scrollWidth gates pass) and "Page 1 of N" is printed under the buttons.
   Reported on FlatGear /our-blog and HighLevel /blog; the fleet scan found it on 15 stages.

   <=767 only: counter back in flow (above the buttons, as on live), the button row wraps.
   `float-md-end` counters (HighLevel) are already in flow below 768 and are not touched.

   A/B (page.route; pagination pages found by crawling each stage's menu (5 stages had none);
   375 + 320; checks: every button inside the viewport, none over the counter, clipscan):
     before: 15 stages FAIL - AlphardPremium, CorporateImpact@320, Eclipse, Empressa@320, Engage,
             FlatGear, Gravity, HighLevel (its own A/B, abpag.py), Motion, NewsCast, Simplex,
             Squado, Synchronize, Venda, XeonShop - buttons off-screen or over the counter
             (blog and category-list layouts both)
     after:  0 FAIL (84/84 page-widths OK); screenshots checked. 768/1400 unchanged (media query).
   Per-template copies on FlatGear/HighLevel (<slug>-phone-pagination-wrap) become redundant
   once this is promoted.
   ===================================================================== */
@media (max-width: 767px) {
  .com-content-category-blog__navigation > .counter,
  .com-content-category__navigation > .counter { float: none !important; }
  .pagination__wrapper > ul.pagination { flex-wrap: wrap !important; row-gap: 6px; max-width: 100% !important; }
}
/* /CS-PATCH: phone-pagination-wraps */

/* =====================================================================
   CS-PATCH: phone-search-input-submit  2026-10-13 (rev .123, SparkStore, Tim: "the button for search is just blank")
   The 2026-08 fix for the white-on-white phone search submit only covered `button[type=submit]`. Donors whose
   search form posts with `<input type="submit" class="button">` (SparkStore) got the white SVG magnifier from
   `phone-search-submit-icon` but no background, so the 44px control was still a blank white square. Accent
   background only (no shorthand, so the SVG survives), matching the utility-row chips.
   ===================================================================== */
@media (max-width: 767px) {
    #cs-search-panel input[type="submit"] {
        background-color: var(--cs-accent, #2b9fb3) !important;
        border-color: var(--cs-accent, #2b9fb3) !important;
        border-radius: 4px !important;
    }
}
/* /CS-PATCH: phone-search-input-submit */

/* =====================================================================
   CS-PATCH: cart-count-badge  2026-10-13 (rev .123, Tim on SparkStore)
   Number bubble on the adopted phone cart chip (cs-mobile-generic.js adds span.cs-cart-badge inside the
   adopted module when the cart holds items). Desktop keeps the donor's own text cart, so it is hidden >=768.
   ===================================================================== */
.cs-cart-badge { display: none; }
@media (max-width: 767px) {
    #cs-utility-row > .cs-adopted-cart { position: relative !important; overflow: visible !important; }
    #cs-utility-row > .cs-adopted-cart > .cs-cart-badge {
        display: flex; position: absolute; top: -7px; right: -7px; z-index: 3;
        min-width: 20px; height: 20px; padding: 0 5px; box-sizing: border-box;
        align-items: center; justify-content: center;
        border-radius: 10px; background: #d32f2f; color: #fff;
        font: 700 12px/1 Arial, Helvetica, sans-serif; text-indent: 0; letter-spacing: 0;
        box-shadow: 0 0 0 2px #fff; pointer-events: none;
    }
}
/* /CS-PATCH: cart-count-badge */

/* =====================================================================
   CS-PATCH: cart-chip-same-box  2026-10-15 (rev .124, Tim's Serenity grad test: "On mobile the basket icon is not
   centered in its square")
   utility-chip-specificity-floor gave the adopted VirtueMart cart a 1px rgba(0,0,0,.18) border ("give it the chip's
   own box too"). Where the cart's inner link already fills the 44px chip (GliseStore, Furniture, HyperShop, Serenity),
   that border made the cart chip 46px with a grey ring, 1px out of line with its neighbours, so the basket read as
   off-centre. Same box as every other chip: no border.
   ===================================================================== */
@media (max-width: 767px) {
    #cs-utility-row > .cs-adopted-cart { border: 0 !important; }
    /* the count on the phone chip is the canonical .cs-cart-badge (cart-count-badge, .123). A donor that styles its own
       "N products" as a bubble (HyperShop) showed BOTH with an item in the cart, and a "0" bubble when empty. */
    #cs-utility-row > .cs-adopted-cart .products-number { display: none !important; }
}
/* /CS-PATCH: cart-chip-same-box */

/* =====================================================================
   CS-PATCH: cart-count-badge-desktop  2026-10-16 (rev .125, Tim on GliseStore/Furniture: the desktop basket showed a
   number only on mouse-over; "that number should always show ... make that a thing for all templates with carts")
   cs-mobile-generic.js puts span.cs-cart-badge-d inside the cart's icon link (a.cs-badge-host) whenever the cart holds
   1 or more. Shown from 768 up on the icon's top-right corner; the donor's own hover-only count bubble inside the same
   link is hidden while the badge shows (no double count). Phones keep the chip badge (cart-count-badge).
   ===================================================================== */
.cs-cart-badge-d { display: none; }
@media (min-width: 768px) {
    .vmCartModule a.cs-badge-host { position: relative !important; overflow: visible !important; }
    .vmCartModule a.cs-badge-host > .cs-cart-badge-d {
        display: flex; position: absolute; top: -8px; right: -10px; z-index: 5;
        min-width: 20px; height: 20px; padding: 0 5px; box-sizing: border-box;
        align-items: center; justify-content: center;
        border-radius: 10px; background: #d32f2f; color: #fff;
        font: 700 12px/1 Arial, Helvetica, sans-serif; text-indent: 0; letter-spacing: 0; text-transform: none;
        box-shadow: 0 0 0 2px #fff; pointer-events: none;
    }
    .vmCartModule a.cs-badge-host.cs-has-n .products-number { display: none !important; }
}
/* /CS-PATCH: cart-count-badge-desktop */

/* =====================================================================
   CS-PATCH: password-toggle-inline  2026-10-16 (rev .126; found on GliseStore at Tim's grad test, then measured on
   Furniture, HyperShop, FlatStore, Flexo, Serenity, SparkStore: all wrapped)
   Joomla's login/registration password field is a BS5 .input-group (input + .input-password-toggle eye). Donor input
   rules (width:100%) made the group wrap, so the eye dropped onto its own line under the field. Keep them on one line.
   ===================================================================== */
.input-group:has(> .input-password-toggle) { flex-wrap: nowrap !important; }
.input-group:has(> .input-password-toggle) > input.form-control,
.input-group:has(> .input-password-toggle) > input { flex: 1 1 auto !important; width: 1% !important; min-width: 0 !important; max-width: none !important; }
.input-group:has(> .input-password-toggle) > .input-password-toggle { flex: 0 0 auto !important; width: auto !important; }
/* /CS-PATCH: password-toggle-inline */

/* =====================================================================
   CS-PATCH: adopted-cart-panel-ink  2026-10-16 (rev .127; Tim's HyperShop grad test: "clicking on the cart button brings
   up an empty box ... when something is in the cart it just has the name of the product")
   The phone utility row paints every chip `color: var(--cs-accent-ink)` (white), the adopted cart included, and the
   cart module's own drop-down panel lives INSIDE that chip. A donor panel with no colour of its own inherited the white:
   quantity, price, "1 product", "Total" and "0 products" at 1.00:1 on the white panel - only the product-name link
   (its own colour) showed. The 2026-09-12 Flexi fix stopped the glyph rule reaching in, but not plain inheritance.
   MEASURED with cartink.py @375: HyperShop stage, Furniture, Avant - invisible; 25 other VMT demos clean.
   :where() keeps this at (0,1,0) and it carries no !important, so any donor rule that colours its panel still wins;
   it only replaces the inherited chip ink.
   ===================================================================== */
@media (max-width: 767px) {
    /* .130: OUTERMOST panel only, at zero specificity. .127 also matched the .cartpanel INSIDE FlatStore's dark .panel2,
       whose donor colour (rgb 147 on rgb 54) it then overrode with #333 - "Cart empty" at 1.05:1. Any donor rule now wins,
       and an inner panel inherits from its outer one as the donor intended. */
    :where(#cs-utility-row > .cs-adopted-cart) :where(.panel1, .panel2, .cartpanel:not(:is(.panel1, .panel2) *)) { color: var(--cs-panel-ink, #333); }
}
/* /CS-PATCH: adopted-cart-panel-ink */

/* =====================================================================
   CS-PATCH: article-pager-ends  2026-10-16 (rev .128; Tim's FlatStore grad test: "the previous and next buttons on
   articles are squished together ... next should be to the far right")
   Joomla 5's core pager is `nav.pagenavigation > span.pagination > a.previous + a.next`. Cassiopeia spreads it; the
   donors never had that CSS, so both buttons sat at the left edge 4px apart. MEASURED with pager.py @1400: FlatStore
   live + stage Next 59-73px from the left of an 848-870px column; Serenity the same. Same on the phone.
   Next takes margin-left:auto so it also goes right when it is the only button (first article).
   ===================================================================== */
.pagenavigation .pagination { display: flex !important; flex-wrap: wrap; justify-content: space-between; align-items: center; width: 100%; gap: 8px; }
.pagenavigation .pagination > .next { margin-left: auto !important; }
/* /CS-PATCH: article-pager-ends */

/* CS-PATCH: hypershop-span-selector-mirror (2026-09-28) - product boxes: the action icons under Add to Cart lost their grey tiles/colours (style1 preset rules were .row-fluid-scoped)
   The donor's own CSS (template/layout/nav/typography/responsive-template/preset/vm css) addresses grid cells as
   .spanN / [class*="span"] / .row-fluid; the stage markup is .col-md-N / .row, so those rules were dead. Mechanical
   mirror of those rules only (phone-only media skipped - the phone chrome is canonical). Canonical candidate. */
/* from template.css */
.row .ex-span2 { width:17.92%; }
.row .ex-span1_5 { width:14.46%; }
.col-md-3 .moduletable > h3,
.col-md-3 .moduletable_menu > h3,
.col-md-3 .moduletable_products > h3 { display:block;margin-bottom:10px; }
.col-md-2.sp20 { width:19.2%;margin-left:1%; }
ul.vmproduct.product-details li.col-md-12 { margin:0px;padding:0px;background:none; }
.row .product { margin-bottom:15px; }
.col-md-6.floatleft span.vote { display:block;text-align:center;height:24px;line-height:24px;vertical-align:top; }
.billto-shipto.row .col-md-6 { padding-bottom:10px; }
.category-view .row .category img,
.row .spacer img { -webkit-backface-visibility:hidden; }
.category-view .row .category,
.row .spacer { opacity:1;-moz-transition-duration:0.3s;-webkit-transition-duration:0.3s;-o-transition-duration:0.3s;-ms-transition-duration:0.3s;transition-duration:0.3s;-webkit-transition-property:opacity,-webkit-filter,border-color;-ms-transition-property:opacity,border-color;-o-transition-property:opacity,border-color;-moz-transition-property:opacity,border-color;transition-property:opacity,-webkit-filter,border-color; }
.category-view .row .category .spacer h2 a .cat-title { -webkit-backface-visibility:hidden;text-align:center;display:block;vertical-align:middle;-moz-transition-duration:0.2s;-webkit-transition-duration:0.2s;-o-transition-duration:0.2s;-ms-transition-duration:0.2s;transition-duration:0.2s;-webkit-transition-property:position,bottom;-ms-transition-property:position,bottom;-o-transition-property:position,bottom;-moz-transition-property:position,bottom;transition-property:position,bottom;bottom:0px; }
.category-view .row:hover .category:hover .spacer h2 a .cat-title { position:absolute;bottom:32%;text-align:center;width:100%; }
.category-view .row:hover .category:hover .spacer { padding-bottom:31px; }
.moduletable_products.quick .row .product { margin-bottom:0px!important; }
@media (min-width:980px) and (max-width:1199px) {
  #info-line [class*="col-md-"] { margin:0px;padding:5px 0px 5px 0px; }
  .col-md-2 .spacer input.addtocart-button { padding:6px 8px; }
  .col-md-9 .col-md-3 .spacer input.addtocart-button { padding:6px 13px; }
}
@media (min-width:768px) and (max-width:979px) {
  #top-navigation-bar [class*="col-md-"] { float:none;margin:0px;width:auto!important;padding:2px 0px 2px 0px; }
  .col-md-2 .spacer .quantity-input.js-recalculate,
  .col-md-2 .spacer input.quantity-input { margin-top:1px; }
  .category-view .row .col-md-3,
  .latest-view .row .col-md-3,
  .recent-view .row .col-md-3,
  .vmproduct .col-md-2,
  .vmproduct .col-md-3 { width:49%;margin:0px 0.5% 5px 0.5%!important;float:left;min-height:300px; }
  #tab-modules [class*="col-md-"] .text-area { text-align:center; }
}
/* from style1.css */
.row .spacer .action-handler .popout-price .show-pop-up-image a:hover:after,
.row .spacer .action-handler .popout-price .product-details:hover:after,
.row .spacer .action-handler .popout-price .show-advanced-fields:hover:after { color: #d93616 !important; }
.row .spacer .action-handler .popout-price .product-details,
.row .spacer .action-handler .popout-price .show-pop-up-image,
.row .spacer .popout-price-buttons-handler .show-advanced-fields { background-color: #f5f5f5 !important; }
.row .spacer .action-handler .popout-price .show-pop-up-image a:after,
.row .spacer .action-handler .popout-price .product-details:after,
.row .spacer .action-handler .popout-price .show-advanced-fields:after { color: #4277b3; }
.category-view .row .category .spacer h2 a .cat-title { color: #494949; }
.category-view .row .category .spacer:hover h2 a .cat-title { color: #ffffff; }
/* end hypershop-span-selector-mirror */

/* CS-PATCH: hypershop-hdr-live-geometry (2026-09-28) - @1400 the My Account / Register text BUTTONS were forced into the
   canonical 32x32 icon box (labels wrapped inside tiny squares: "My Accou"/"egiste"); the currency select shrank to 139x20
   (live 220x30); search/cart sat at y 80 vs live midline 105; Register's icon is fas fa-pencil-square-o, an FA4 name with
   no FA6 glyph (v4-shims only map .fa.*), so it rendered blank. Values = live computed boxes; icon boxes stay 32px. */
@media (min-width: 768px) {
  #cl-handler #cartpanel { margin: 0 4px 0 0 !important; }
  #cl-handler #vmCartModule .show-cart { display: flex; align-items: center; height: 50px; padding: 0 0 0 5px; }  /* 2026-10-16 (Tim: cart sat at the bottom of the bar): the 15px top margin + inline baseline put it at y 97 vs search 89; centred in the 50px bar instead */
  #cl-handler #searchOpenButton { margin: 9px 0 0 0 !important; }
  #cl-handler #search-position { margin-right: 13px; }
  #cl-handler .quick-menu select.inputbox { width: 220px; height: 30px; padding: 4px 6px; margin: 2px 0; }
  a.button.open-register-form, a.button.v_register { display: inline-block !important; width: auto !important; height: auto !important; min-width: 0 !important; min-height: 0 !important; padding: 9px 15px !important; line-height: 16px !important; }
  a.button.open-register-form > i, a.button.v_register > i { display: inline-block !important; font-size: inherit !important; width: auto !important; }
}
i.fa-pencil-square-o:before { content: "\f044"; font-family: "Font Awesome 6 Free"; font-weight: 400; }

/* CS-PATCH: hypershop-popout-modal-link (2026-09-28) - VM's quick-view link in each product box is a.modal, so the BS5
   .modal dialog rules (position:fixed, 560px margin offset, white card) pulled the search icon out of its grey tile and
   onto the product photo. Live: a plain 24px link inside the tile. */
.popout-price a.modal { position: static !important; top: auto; left: auto; right: auto; bottom: auto; margin: 0 !important; background-color: transparent; border: 0; border-radius: 0; box-shadow: none; z-index: auto; overflow: visible; width: auto; height: auto; }

/* CS-PATCH: hypershop-social-sidebar-tiles (2026-09-28) - the fixed left social strip lost its brand tiles when Phase 10
   re-keyed the links (si-twitter... -> sc-N): glyphs showed as bare red icons on the page. Live tile colours per network. */
#social-links a[class^="sc-"] { color: #fff !important; }
#social-links a[class^="sc-"] .icon, #social-links a[class^="sc-"] i { color: #fff !important; }
#social-links a.sc-1 { background-color: rgb(45, 170, 225); }
#social-links a.sc-2 { background-color: rgb(60, 91, 155); }
#social-links a.sc-3 { background-color: rgb(68, 68, 68); }
#social-links a.sc-14 { background-color: rgb(51, 51, 51); }

/* CS-PATCH: hypershop-module-box-bs2 (2026-09-28) - @1400 the Featured Products / Testimonials boxes lost live's 20px inner
   side padding (canonical .row:not(:has(> [class*=col-])) > * {padding:0} hits a module sitting straight in the rewritten
   .row) and BS2's li{line-height:20px} is gone (product cells 22.4px lines): Featured box 504 vs 489 tall, cards 222 vs 212. */
.row > .moduletable_products { padding-left: 20px !important; padding-right: 20px !important; }
:where(li) { line-height: 20px; }

/* CS-PATCH: hypershop-heading-top-margin (2026-09-28) - BS5 reboot (h1-h6 margin-top:0) beats the bridge's :where() BS2
   heading margin; live keeps BS2's 10px: Mobile Ready banner + footer columns each 10px short @1400 (page 20px short). */
h1, h2, h3, h4, h5, h6 { margin-top: 10px; }

/* CS-PATCH: hypershop-hdr-buttons-768 (2026-09-28) - live narrows the My Account/Register buttons to 9px 11px at 768-979;
   with the 1400 padding Register wrapped under My Account and pushed the menu bar 13px down @768. */
@media (min-width: 768px) and (max-width: 979px) {
  a.button.open-register-form, a.button.v_register { padding: 9px 11px !important; }
}

/* CS-PATCH: hypershop-no-gutter-content-inset (2026-09-28) - with body.cs-no-bs2-gutter the phone content ran edge to edge
   (tabs/banner/featured/testimonial boxes x=3 w=370) because canonical stripped #content-handler's 20px padding; live boxes
   sit at x=20 w=335 inside it, module boxes with 10px (not the desktop 20px) side padding. Footer columns: flex rows stop
   margin collapse (bot-modules 968 vs 938). */
@media (max-width: 767px) {
  body.cs-no-bs2-gutter #content-handler { padding-left: 20px !important; padding-right: 20px !important; }
  .row > .moduletable_products { padding-left: 10px !important; padding-right: 10px !important; }
  #bot-modules > .row, #bot-modules > .container > .row, #bot-modules > .container-fluid > .row { display: block; }
}

/* CS-PATCH: hypershop-phone-hero-inset (2026-09-28) - live keeps the hero inside 20px side margins on phone
   (#slideshow-handler margin 0 20px, camera 335 wide at x=20); the canonical phone full-width rule made it 375 at x=0, so
   the caption text started at x=0 against the screen edge (live x=20). */
@media (max-width: 767px) { body #slideshow-handler { margin-left: 20px !important; margin-right: 20px !important; width: auto !important; } }

/* CS-PATCH: hypershop-phone-cart-chip (2026-09-28) - @375 the adopted cart chip was oversized: the donor .show-cart padding
   (5px 0 5px 5px) + the module's own fill made a 51x56 coloured block (other chips 44x44; chip row 79 vs 69 tall), and the
   count badge (absolute right:-3px) anchored to the row, so it hung past the screen edge (x 358..378). */
@media (max-width: 767px) {
  #cs-utility-row .vmCartModule { background-color: transparent !important; }
  #cs-utility-row .show-cart { padding: 0 !important; }
  #cs-utility-row .cart-button { position: relative; }
}

/* CS-PATCH: hypershop-login-dropdown-box (2026-09-28) - @1400 the header login dropdown was 225 wide at x+40 (live 265: donor
   width is content-box + 20px padding; BS5 border-box shrank it) with 139x25 inset-bordered inputs (live 222x52, 2px #ccc).
   The canonical flat link list under Log in (instead of the BS2 caret menu) is kept. */
@media (min-width: 768px) {
  #LoginForm { box-sizing: content-box; }
  #LoginForm .lg-row input.inputbox { box-sizing: border-box; width: 222px; max-width: 100%; height: 52px; padding: 4px 6px; border: 2px solid #ccc; }
  #LoginForm .lg-row .cs-eye-wrap input.inputbox { padding-right: 30px; }
}

/* CS-PATCH: hypershop-module-box-padding-tiers (2026-09-28) - live's module-box side padding is tiered: 20px @>=980, 10px at
   768-979 and on phone (measured on live at 1400/1100/768/375). Corrects the single-value restore above. */
@media (min-width: 980px) { .row > .moduletable_products { padding-left: 20px !important; padding-right: 20px !important; } }
@media (max-width: 979px) { .row > .moduletable_products { padding-left: 10px !important; padding-right: 10px !important; } }

/* CS-PATCH: hypershop-phone-row-block (2026-09-28) - @375 stacked module columns sit in flex rows, so their bottom margins stop
   collapsing like live's block spans (footer columns ~30px taller than live, feature strip +20). */
@media (max-width: 767px) {
  #tab-modules-handler.row, #tab-modules-handler > .row, #bot-modules > .row, #bot-modules > .container > .row, #bot-modules > .container-fluid > .row { display: block; }
}

/* CS-PATCH: hypershop-a-modal-reset (2026-09-28) - VirtueMart marks popup LINKS a.modal (product-page Print icon, quick-view);
   the BS5/BS2 .modal dialog rules made them position:fixed 560px cards at top:10%/left:50% (Print icon floated at 708,90 over
   the header instead of beside the product title). Zero-specificity reset: beats .modal, loses to every donor a.modal rule.
   Canonical candidate. */
:where(a).modal { position: static; top: auto; left: auto; right: auto; bottom: auto; width: auto; height: auto; margin: 0; background-color: transparent; border: 0; border-radius: 0; box-shadow: none; z-index: auto; overflow: visible; outline: 0; }

/* CS-PATCH: hypershop-bs2-form-controls (2026-09-28) - live loads BS2's form-control base (text inputs/selects 40px tall, 4px 6px
   padding, 1px #ccc border, 206px wide); the stage has none, so e.g. the cart login fields were 127x25 UA boxes with inset
   black borders (live 222x52 / 220x50). Zero-specificity so every donor/canonical input rule still wins. */
:where(input[type="text"], input[type="password"], input[type="email"], input[type="search"], input[type="tel"], input[type="url"], input[type="number"], input:not([type]), textarea, select) { height: 40px; padding: 4px 6px; border: 1px solid #ccc; border-radius: 4px; box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); vertical-align: middle; }
:where(input[type="text"], input[type="password"], input[type="email"], input[type="search"], input[type="tel"], input[type="url"], input[type="number"], input:not([type]), textarea) { width: 206px; }
:where(textarea) { height: auto; }
:where(select) { width: 220px; }

/* CS-PATCH: hypershop-footer-inset-2026-10-15 */
/* 2026-10-15 grad prep (gradscan edge): the footer module band (#bottom-bg > .container) sat 3px from the phone screen
   edge, so "INFORMATION" / "About Us" etc. touched it while the content column is 20px in. Inset that band only. */
@media (max-width: 767px) {
  #bottom-bg > .container { margin-left: 0 !important; margin-right: 0 !important; width: auto !important; padding-left: 20px !important; padding-right: 20px !important; box-sizing: border-box !important; }
}
/* /CS-PATCH: hypershop-footer-inset-2026-10-15 */

/* CS-PATCH: hypershop-social-sidebar-phone-2026-10-15 */
/* 2026-10-15 grad prep: on phones the donor's fixed social sidebar (#socialModal: X / Facebook / Instagram / GitHub tabs
   pinned to the left edge) covered the start of the content - the cart and login fields were hidden under it (live has
   the same). Phones reach social through the utility-row share chip (#cs-social-panel), so the sidebar goes. */
@media (max-width: 767px) {
  body > #socialModal, #socialModal:not(#cs-social-panel #socialModal) { display: none !important; }
}
/* /CS-PATCH: hypershop-social-sidebar-phone-2026-10-15 */

/* CS-PATCH: hypershop-camera-disc-centre-2026-10-16 */
/* Slideshow prev/next/stop discs: the BS5 reboot made them border-box, so the 4px white ring ate into the 40px box
   and the 40px sprite span hung 4px right and low. Back to content-box = 48px disc, glyph centred, like live. */
.camera_wrap .camera_prev, .camera_wrap .camera_next, .camera_wrap .camera_commands { box-sizing: content-box; }
/* /CS-PATCH: hypershop-camera-disc-centre-2026-10-16 */

/* CS-PATCH: hypershop-phone-empty-nav-and-arrow-ring-2026-10-16 */
/* Tim grad test 2026-10-16. Phone: #st-navigation kept the donor's 20px/30px padding around a #tpr-nav whose only payload
   (main menu + My Account/Register) now lives in the drawer and the utility icon row - a 375x65 empty white band under
   the logo. CSS-only hide: the node stays in the DOM, so the drawer's clone source is untouched.
   The canonical phone slideshow arrows are a 44px translucent square; HyperShop's donor 4px white ring stayed on them. */
@media (max-width: 767px) {
  #st-navigation { display: none !important; }
  .camera_wrap .camera_prev, .camera_wrap .camera_next { border-width: 0 !important; }
}
/* /CS-PATCH: hypershop-phone-empty-nav-and-arrow-ring-2026-10-16 */

/* CS-PATCH: hypershop-empty-cart-panel-2026-10-16 */
/* Empty cart (Tim 2026-10-16: "brings up an empty box"): with no products the module still renders its .show-cart-link
   row - 82px of padding around nothing - above "0 products". Collapse it so the panel just says "0 products". */
#vmCartModule .cartpanel:not(:has(.vm_cart_products .product_row)) .show-cart-link { display: none; }
/* /CS-PATCH: hypershop-empty-cart-panel-2026-10-16 */

/* CS-PATCH: hypershop-phone-slideshow-picture-then-caption-2026-10-16 */
/* Tim 2026-10-16 (HyperShop grad test, phone): "the slideshow is text on top of images ... it isn't great". The slides are
   1141x400 desktop banners (product on the right on two slides, on the left on the third). On a phone the caption sat on
   the product. Phones: the WHOLE banner shows at full width (375x131 - every product visible, nothing cropped); title,
   price and the two buttons sit below it on white; the long lorem paragraph is dropped; the slideshow is 330px tall. Also: #slideshow-handler kept 20px side margins
   on a forced 100% width, so the slideshow ran 20px past the screen's right edge (Rule 3: no phone gutter). */
@media (max-width: 767px) {
  html body #slideshow-handler { margin-left: 0 !important; margin-right: 0 !important; }   /* beats the 2026-09-28 hero-inset block */
  .camera_wrap .cameraSlide { background: #fff; }
  .camera_wrap .cameraSlide img.imgLoaded {
    position: absolute !important; top: 0 !important; left: 0 !important; right: auto !important;
    width: 100% !important; height: auto !important; min-height: 0 !important; max-width: none !important; margin: 0 !important;
  }
  .camera_wrap .camera_caption > div > div {
    top: 131px !important; bottom: 0; left: 0 !important; right: 0; padding: 12px 16px 0; background: #fff;
  }
  .camera_wrap .camera_caption h1 { margin: 0 0 8px !important; font-size: 20px !important; line-height: 1.2 !important; }
  .camera_wrap .camera_caption p:not(:has(*)) { display: none !important; }
  .camera_wrap .camera_prev, .camera_wrap .camera_next { top: 65px !important; }
  .camera_wrap .camera_commands { top: 252px !important; }   /* pause sits beside the two buttons, not on the product */
  .camera_wrap, .camera_wrap .camera_fakehover, .camera_wrap .camera_target, .camera_wrap .camera_target_content { height: 330px !important; }
  .camera_wrap .camera_pie { display: none !important; }
}
/* /CS-PATCH: hypershop-phone-slideshow-picture-then-caption-2026-10-16 */
