/* ==========================================================================
   GBZ420 - listing QA layer (categories + search) - v1, 2026-09-23
   --------------------------------------------------------------------------
   Surface: the two listing families only.
     - category listings, `body#category`, mockups 02 / 03 / 04
       (THCX, 10-OH-HHC, GBZ & GBZ+) and the categories without a mockup,
       which share the same listing document;
     - the search listing, `body#search`, mockup-free (charter level B).
   The GBZ Prime landing is category 45 and is excluded everywhere: it keeps
   its own pack grid, hero and badges (V-08 / Prime non-regression).

   Eyeballed deviations it closes, all measured on the local copy on
   2026-09-23 (before -> after, see docs/sitewide-maquettes-20260921/
   qa-listings-css-20260923):
     V-03  full-bleed category hero band overflowed the client area by 8 px
           at 1440 (documentElement.scrollWidth - clientWidth).
     V-02  product grid: card 23.54 % of the row, gutter 24 px (1.92 %);
           charter 5 wants 24.0-24.1 % and 1.3-1.5 %.
     V-05  card "add to cart" pill 36 px tall at 1440 and 31 px at 390;
           charter 11 wants >= 40 px on desktop and >= 44 px touch target.
     V-09  hero CTA hover/active paint was a dark olive (rgb(37, 44, 2) at
           hover today, rgb(21, 25, 1) when the pass was written).
     V-10  stock badge `.product-unavailable` painted theme brown #a17738.
     V-14  search grid: 5 columns of 20 % at >= 1200 px; charter 5 wants 4.

   Untouched on purpose: copy, prices, stock data, badges' text, card markup,
   sorting, facets, pagination, the view switcher, the focus rings already
   asserted by the shell, and every non-listing family.

   Load order: the theme's combined stylesheet is printed after this file
   (same situation as gbz-listing-bar-v1.css), so each rule is id-qualified:
   `body#category` / `body#search` outrank Elementor's and Bootstrap's
   class-only defaults whatever the order is.
   ========================================================================== */

/* Inert marker: lets a QA harness prove this layer is applied. It paints
   nothing and is consumed by no other rule. */
body#category:not(.category-id-45),
body#search {
    --gbz-qa-listings-v1: 1;
}

/* ------------------------------------------------------------------ V-03 --
   The category hero band is a full-bleed strip built with
   `width: 100vw; margin-inline: calc(50% - 50vw)`. On a viewport that shows a
   classic scrollbar, 100vw is 15 px wider than the document client area, so
   the band ran from -7.5 px to 1432.5 px on a 1425 px client width: 8 px of
   horizontal overflow at 1440 (0 px at 390, overlay scrollbars).

   `overflow-x: clip` on the root removes that ink from the scrollable overflow
   region (measured: scrollWidth - clientWidth = 0) while leaving the band box
   untouched (still 1440 px wide, same left/right edge). `clip` and not
   `hidden`: clip creates no scroll container, so `position: sticky` and
   programmatic scrolling keep working. Scoped with `:has()` so the root is
   only clipped on the listing categories; browsers without `:has()` simply
   keep the current rendering. */
html:has(body#category:not(.category-id-45)) {
    overflow-x: clip;
}

/* ------------------------------------------------------------------ V-02 --
   Cards were 294 px wide with 24 px gutters at 1440: 23.54 % of the card row
   for a 1.92 % gutter. The card width is the grid track, so only the gutter
   has to move to land inside both charter bounds at once
   (card 24.0-24.1 %, gutter 1.3-1.5 %): a 1.32 % gutter gives
   (100 % - 3 x 1.32 %) / 4 = 24.01 % cards, i.e. 299.9 px / 16.5 px at 1440.
   The 4/2/1 track pattern of the widget (`elementor-grid-4`,
   `elementor-grid-tablet-2`, `elementor-grid-mobile-1`) is left alone, so
   tablet and mobile keep the columns the listing document chose. */
@media (min-width: 1025px) {
    body#category:not(.category-id-45) .ce-products.ce-product-grid {
        column-gap: 1.32%;
    }
}

/* ------------------------------------------------------------------ V-05 --
   The card pill is padded by `.btn-product-list` (0.535714rem top and bottom)
   and lands 36 px high on desktop, 31 px on mobile. The seam is the height, so
   only a floor is added: the label, the icon, the colours, the hover and the
   form submit stay exactly as they are. Charter 11: >= 40 px pill on desktop,
   >= 44 x 44 px touch target on mobile. */
@media (min-width: 768px) {
    body#category:not(.category-id-45) .product-miniature .btn-product-list.add-to-cart,
    body#category:not(.category-id-45) .product-miniature .btn-add-to-cart-small {
        min-height: 40px;
    }

    /* The unavailable sibling - a plain `<a class="btn btn-product-list">`
       ("Voir le produit" / "View" / "Produkt ansehen") - was left behind by the
       floor above: 38 px at 1440 and 33 px at 390, i.e. line-height 21 px
       (18 px at 390) + padding 7.5 px (6.43 px) + the 1 px borders, all below
       the charter's 40 px desktop / 44 px mobile bound for a CTA.

       Only the box is touched: the label keeps its text, the href keeps its URL,
       availability, availability badges and markup are untouched, and no
       purchase path is added or removed (this link carries no form and no cart
       request). The theme centres the label with `text-align: center` and an
       inline `::after` arrow; `display: flex` with centred alignment keeps that
       same single-line, centred label-and-arrow group and centres it vertically
       in the taller box, which a bare `min-height` would not do (the extra space
       would fall below the text). */
    body#category:not(.category-id-45) .product-miniature .product-add-cart > a.btn.btn-product-list {
        align-items: center;
        display: flex;
        justify-content: center;
        min-height: 40px;
    }
}

@media (max-width: 767.98px) {
    body#category:not(.category-id-45) .product-miniature .btn-product-list.add-to-cart,
    body#category:not(.category-id-45) .product-miniature .btn-add-to-cart-small {
        min-height: 44px;
    }

    body#category:not(.category-id-45) .product-miniature .product-add-cart > a.btn.btn-product-list {
        align-items: center;
        display: flex;
        justify-content: center;
        min-height: 44px;
    }
}

/* --------------------------------------------------------------- V-05b ----
   Card CTA width. Mockup 02 draws the card pill across the card's content box
   (198 of 225.5 image px = 87.8 % of the card). In the live grid the in-stock
   pill is the only card CTA that does not fill that box: it sits inside
   `div.input-group-add-cart`, a flex item of the card form that shrinks to its
   content - 194 px of a 275.9 px content box, i.e. 64.7 % of the 300 px card at
   1440 and 166 of 348 px, 44.6 %, at 390. Its unavailable sibling
   (`a.btn.btn-product-list`, "Voir le produit") is a direct child of
   `.product-add-cart` and already fills the box (274 px = 91.3 % at 1440,
   346 px = 93.0 % at 390). Stretching the wrapper therefore aligns the two
   variants instead of inventing a third width; the 3.5 pt gap to the mockup is
   the card padding itself (12 px live against ~19 px in the reference), which
   this layer does not touch.

   The wrapper's only other child is `input.form-control.input-qty`, computed
   `display: none` in this grid, so nothing else moves. Measured on 2026-09-23 on
   `/fr/thcx`, `/fr/10-oh-hhc`, `/fr/gbz`, `/fr/fleurs-cbd`, `/fr/e-liquide` and
   `/fr/exclus-gbz-prime` at 1440 and 390: the only visible child of the wrapper is
   the pill, `input[name="qty"].input-qty` is `display: none` / 0 x 0 in every
   card, card heights are identical with and without the rule and the document
   overflow stays 0.

   The rule is deliberately tied to `.ce-products-grid`, the widget class that
   carries the CE rule hiding the quantity field (`.ce-products-grid
   .product-add-cart .input-qty { display: none }`). A category whose listing is
   rendered by the theme's own template does not carry that class, would keep a
   visible quantity input, and is therefore left untouched by this rule. The
   button keeps `width: 100 %` from the existing `.ce-products-grid
   .product-add-cart form .btn-product-list` rule, and the form, its action, its
   hidden fields and its jQuery click binding are untouched. */
body#category:not(.category-id-45) .ce-products-grid .product-miniature .product-add-cart form .input-group-add-cart {
    flex: 1 1 auto;
    width: 100%;
}

/* ------------------------------------------------------------------ V-09 --
   The hero CTA rests on its band's own colours, and those are right: black
   pill with white text on the white THCX band (02), black pill with white text
   on the lime 10-OH-HHC band (03), accent pill with black text on the black
   GBZ band (04). The rest state is therefore left alone - pinning it to ink
   would have turned the GBZ pill black on its black band.

   What is pinned is the interaction paint. Two things happen there today:
   the settled hover paints white text on the accent pill (1.20:1, the pair the
   charter forbids), and a 0.5 s `transition: all` on `.elementor-button` makes
   any mid-flight sample read as the dark olive the audit reported
   (rgb(37, 44, 2) at 180 ms, rgb(21, 25, 1) when the pass was written). With
   this rule the settled hover/focus/active state is the accent pill with ink
   text on all three heroes, and no sample of the state can leave the palette.
   The focus ring declared by the shell is not touched. */
body#category:not(.category-id-45) .gbz-hero .gbz-hero-cta .elementor-button:hover,
body#category:not(.category-id-45) .gbz-hero .gbz-hero-cta .elementor-button:focus,
body#category:not(.category-id-45) .gbz-hero .gbz-hero-cta .elementor-button:active {
    background-color: var(--gbz-accent, #d4fa0a);
    border-color: var(--gbz-accent, #d4fa0a);
    color: var(--gbz-accent-ink, #000);
}

/* ------------------------------------------------------------------ V-10 --
   `.product-unavailable` ("Rupture de stock", "Produit disponible avec
   d'autres options") is painted #a17738 with white-pink text by the theme
   bundle: a brown that exists in none of the six mockups. A stock state is a
   status, not a promotion, so it takes the neutral surface token with ink text
   instead of the lime accent, which already signals promotions on the same
   card (`.product-flag.on-sale`). No border or shadow is added: the badge keeps
   its box, its padding and its square silhouette - the measured badge boxes are
   identical before and after (230x20 and 113x20 at 1440, 197x16 and 97x16 at
   390), only the fill and the text colour change. */
body#category:not(.category-id-45) .product-availability .badge.product-unavailable,
body#search .product-availability .badge.product-unavailable {
    background-color: var(--gbz-surface-alt, #f6f6f6);
    color: var(--gbz-ink, #101010);
}

/* ------------------------------------------------------------------ V-14 --
   The search results are the theme's flex grid, and its five columns come from
   `.col-xl-15 { flex: 0 0 20 % }`. At >= 1200 px the wrapper becomes a 24 %
   column with a 1.32 % flex gutter, so four 299.9 px cards with 16.5 px
   gutters fill the same 1249 px row - the same geometry as the category grid
   above, and the same charter 5 bounds. The sum stays 0.5 px short of 100 %
   on purpose, so LayoutUnit rounding cannot push the fourth card onto the next
   line. Below 1200 px nothing is declared: the theme keeps its own column
   count (2 per row on mobile). Sorting, facets, the count line and the
   grid/list view switcher are not addressed by this rule. */
@media (min-width: 1200px) {
    body#search .products.products-grid {
        column-gap: 1.32%;
        row-gap: 24px;
    }

    body#search .products.products-grid > .js-product-miniature-wrapper {
        flex: 0 0 24%;
        max-width: 24%;
        width: 24%;
    }
}

/* ------------------------------------------------------------------ F-345-A2 --
   Frame of maquettes 03 and 04. On these two families the category hero renders its own
   Elementor container - the three zones measured 3,68 % .. 96,31 % of the 1425 px layout
   at 1440, a 1320 px frame - while the sort bar and the product grid below stay inside
   the theme container (88 .. 1337 px, 1249 px, 87,65 %). The gap is 4,98 points per side.
   The reference PNGs show both frames sharing the same edges (ref 03 hairlines at 40 and
   928 of 969 = 91,6 %; ref 04 at 36 and 885 of 922), and charter section 9.5 names the
   same figure, "le conteneur de 1320 px a 1440". `--gbz-container-max` already carries
   1320 px, so the frame is set to that token instead of to a new arbitrary width.

   Scoped twice, body id and viewport: only categories 3 and 7, only from 1400 px up -
   the range where the theme container is capped at 1270 px and the hero frame is already
   1320 px wide. Every other category keeps its frame, tablet and mobile keep theirs, and
   the container itself is not touched: only these two bands move, so the header, the
   footer, the breadcrumb and the pagination column keep their current edges. The 4-up
   track pattern, the 1,32 % gutter and the card ratio are declared elsewhere and are not
   addressed here.

   Consequence on geometry, measured: the listing row goes 1249 -> 1320 px and each card
   299,9 -> 316,8 px, still 24,01 % of the row. The V-02 (above, line 62) and V-14 (above,
   line 199) notes that quote a 1249 px row still describe every category except 3, 7 and 39.

   2026-09-24, catalogue fixes: category 39 (THCX) is added to this list, and only it.
   The catalogue audit of 2026-09-24 measured the same defect there as on 3 and 7 - the
   maquette hero of `/fr/thcx` puts its heading, its lead and its price column on the
   1320 px frame (measured: heading box x 52,5, right edge 1372,5) while its listing bar
   and its grid stayed on the theme's 1249 px column at x 88 (finding 3: "gouttière" 6,1 %
   against 3,6 % on `/fr/10-oh-hhc` and `/fr/gbz`, maquettes 02/03/04 at about 4,0 %).
   Reproducing the frame of the two accepted reference pages is therefore a correction of
   the deviation, not a new layout: same token, same breakpoint, same two boxes.

   The 33 generic categories are deliberately NOT added: their shared band
   (`.gbz-cat-hero__inner`, gbz-category-hero-v1.css) measures 1249 px at x 88, i.e. the
   same frame as their bar and their grid, so those pages are internally consistent and
   widening their listing alone would break that agreement. Category 45 (Prime) is out of
   scope everywhere, as its listing chrome is removed on purpose. */
@media (min-width: 1400px) {
    body#category.category-id-3 .gbz-listing-bar,
    body#category.category-id-7 .gbz-listing-bar,
    body#category.category-id-39 .gbz-listing-bar,
    body#category.category-id-3 #js-product-list,
    body#category.category-id-7 #js-product-list,
    body#category.category-id-39 #js-product-list {
        margin-left: calc((100% - var(--gbz-container-max, 1320px)) / 2);
        margin-right: calc((100% - var(--gbz-container-max, 1320px)) / 2);
    }
}

/* ------------------------------------------------------------------ F-345-A3 --
   On category 7 the hero CTA rests on the accent pill, because the band it sits on is
   black (charter 6.3). The V-09 rule above (line 174) pins hover, focus and active to
   that same accent pill on every category except 45, so on this one band the settled
   hover paints exactly what the rest state already paints - the "etat survol sans
   changement perceptible" that charter 6.5 forbids. On category 3 the rule does show,
   because the rest state there is the black pill.

   This gives the black band its own hover and active paint: the pill turns black with
   white ink, the pair the rest state of categories 02 and 03 already uses and a 21:1
   contrast, plus a 2 px accent inset ring so the pill's silhouette stays legible against
   the black band. `box-shadow` is used rather than `outline` on purpose: the focus ring
   measured on this button (solid 2 px white, offset 2 px, declared by the shell) is left
   exactly as it is, and only hover and active are addressed.

   Two guards here are load-bearing, and they were both measured rather than assumed.

   The `:not(#gbz-ce)` guard puts this selector at 2-5-0. The CCC bundle carries
   `body#category.category-id-7 .gbz-hero .gbz-hero-cta .elementor-button:not(#gbz-ce):hover`
   - the same 2-5-0 - and the bundle is emitted after this sheet (`_partials/stylesheets.tpl`
   is included at the top of head.tpl, the GBZ layers follow it, and the bundle link comes
   last in the served order), so at equal specificity the bundle's ink wins on source order.
   Without the guard the first cut of this rule won the background and lost the colour: the
   hover state measured rgb(0, 0, 0) ink on an rgb(0, 0, 0) pill, a black label on a black
   pill. The guard closes the id gap but not the order gap.

   `!important` on the ink is therefore the second guard, and it is deliberate: the ink here
   is a state paint that has to beat an auto-generated bundle whose selector is regenerated
   whenever the CE document is saved. Specificity arithmetic against a generated selector is
   not durable, and only `color` needs it - the background already wins on specificity.
   `object-fit`/`box-shadow` are unaffected, and the focus ring keeps its own outline. */
body#category.category-id-7 .gbz-hero .gbz-hero-cta .elementor-button:hover:not(#gbz-ce),
body#category.category-id-7 .gbz-hero .gbz-hero-cta .elementor-button:active:not(#gbz-ce) {
    background-color: var(--gbz-black, #000);
    border-color: var(--gbz-accent, #d4fa0a);
    box-shadow: inset 0 0 0 2px var(--gbz-accent, #d4fa0a);
    color: var(--gbz-white, #fff) !important;
}

/* ------------------------------------------------------------------ F-345-A4 --
   Card image box of maquettes 03 and 04. The box is shrink-wrapped to the artwork's
   intrinsic width - 236 px on a 300 px card at 1440 and 236 px again on a 372 px card at
   390 - because the card centres its children while the img only carries `width: 100 %`,
   so the percentage resolves against an indefinite width and the box never follows the
   card. With `object-fit: contain` on a 236x305 source, the painted silhouette is
   182,6 px: 60,9 % of the card at 1440, 49,1 % at 390, against 83,1 % (ref 03) and
   71,9 % (ref 04) measured on the reference cards.

   The box takes the card content width plus half of the card's 12 px inline padding on
   each side, i.e. exactly 96 % of the card at any width, so it scales with the card
   instead of staying at 236 px. The img keeps its `aspect-ratio: 1 / 1` and
   `object-fit: contain`: nothing is cropped, nothing is stretched, and the catalogue's
   0,774-ratio artwork paints 0,774 x 96 % = 74,3 % of the card at 1440 as well as at
   390, inside the 72-83 % band the references measure. Only this box is addressed -
   prices, rating, add-to-cart pill and every source image are untouched, and the
   `margin-inline: auto !important` already declared by the grid keeps the widened box
   centred in the card. */
body#category.category-id-3 .ce-products.ce-product-grid .product-miniature .thumbnail-container,
body#category.category-id-7 .ce-products.ce-product-grid .product-miniature .thumbnail-container {
    width: calc(100% + var(--gbz-space-3, 12px));
}

/* ------------------------------------------------------------------ F-345-A5 --
   Hero chip of maquette 03 only. The generic charter line (section 6.3, "bloc central
   (« CURRENT SELECTION » en chip lime)") describes the maquette 04 hero, where the band is
   black and the chip is lime. On maquette 03 the band is lime and the reference chip is a
   light flat with black ink: `tools/maquette-detail.ps1` reads the chip interior at 72,3 %
   of pixels above 235 luminance and 0 % of lime pixels, and the live page paints it black
   with white ink, which no source asks for.

   Decision taken with the parent: on this page the specific maquette wins over the generic
   charter line. The flat is set to `--gbz-white` rather than to a tint sampled off the
   render, because a compressed ImageGen chip cannot settle the exact value: the measured
   strips are near-white with no lime, so white is the documented inference and no invented
   colour enters the palette. Category 7 keeps its lime chip, every other heading on the
   page keeps its own paint, and no CE document or content field is touched - only these two
   declarations on this one heading. Contrast after the change: #000 on #FFF = 21:1, the
   pair the chip already carried, against the 17,48:1 the lime chip measures on category 7. */
body#category.category-id-3 .gbz-hero .gbz-hero-chip .elementor-heading-title:not(#gbz-ce) {
    background-color: var(--gbz-white, #fff);
    color: var(--gbz-black, #000);
}

/* ------------------------------------------------------------------ F-345-A7 --
   Two of the three targets charter 9.11 puts under its 44 x 44 floor at 390 px, on the
   two families of this pass. The third - the 42 x 45 px header search button - belongs to
   the shared shell and is deliberately left alone by this mission.

   The hero mini "VOIR" link measures 34 x 19 px at 390 and 39 x 22 px at 1440. Raising its
   own box would move the flex row it shares with the price, so the hit area is extended
   instead: an empty, invisible ::after band 44 px tall, centred on the link and exactly as
   wide as the link, with no paint of its own. Nothing moves, the label keeps its size, the
   accent underline keeps its position, and the band stays inside the row's own bottom
   area. The link keeps its own position value unless it was static.

   The listing-bar sort select measures 319 x 40 px at 390. A native select cannot take a
   pseudo-element hit area, so its own box gets the floor: min-height 44 px, mobile only,
   which moves the bar 4 px down and nothing else. Applied at <= 991 px, the boundary the
   shell already uses for its mobile fallback. */
body#category.category-id-3 .gbz-hero-mini .product-bottom > a.product-view,
body#category.category-id-7 .gbz-hero-mini .product-bottom > a.product-view {
    position: relative;
}

body#category.category-id-3 .gbz-hero-mini .product-bottom > a.product-view::after,
body#category.category-id-7 .gbz-hero-mini .product-bottom > a.product-view::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    right: 0;
    height: 44px;
    transform: translateY(-50%);
}

@media (max-width: 991px) {
    body#category.category-id-3 .gbz-listing-bar select,
    body#category.category-id-7 .gbz-listing-bar select {
        min-height: 44px;
    }
}

/* ------------------------------------------------------------------ F-345-A8 --
   `div.gbz420-gbz .gbz-hero` box reset. The category 3 editorial block names its
   first element `<header class="gbz-hero">`, the plain class of the category hero
   band, and the band rule therefore matched a card inside the 1060 px panel:

     1170{1,3,4}01.css (module creativeelements, the CE document of each language)
       .gbz-hero { margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw);
                   width: 100vw }

   That rule's own comment reads "the hero band breaks out of the 1320 px content
   column to the viewport edge": it is written for `<section class="gbz-hero">`
   inside `.gbz-hero-slot`, and it is reaching the block only through the shared
   class name. Measured before this rule, with the band's own <style> asking for a
   bordered 18 px-radius card (`padding: 20px`), the element painted 100 vw wide
   and centred on the panel instead of on the panel's content box:

     viewport  panel   band    overhang per side
     1440      1060    1440    190 px
     1266      1060    1266    103 px
     1024       948    1024     38 px
      390       332     390     29 px

   None of the block's other children is affected: at 1440 every other direct child
   of the host measures 1028 px, i.e. the host's content box, and all 26 elements
   that leave that box are inside this one band. Only the three declarations the
   band rule imposes on the block are therefore reset, and only under the widget.
   `auto` restores the block's normal flow, which is what the block's own style
   sheet describes; its border, its padding, its grid and its paint are untouched,
   and the hero band itself is not inside `div.gbz420-gbz`, so it keeps its
   full-bleed geometry.

   The V-03 `overflow-x: clip` above stays: it is written for the hero band, which
   still paints 100 vw. The category 7 block carries no `.gbz-hero`, so this rule
   matches nothing there. No `!important`: the selector is 2-3-2 against the
   bundle's 0-1-0, so specificity decides whatever the load order is. */
body#category:not(.category-id-45) #content div.gbz420-gbz .gbz-hero {
    margin-left: 0;
    margin-right: 0;
    width: auto;
}

/* ==========================================================================
   GBZ420 - catalogue fixes, 2026-09-24
   --------------------------------------------------------------------------
   Local copy only: C:\Codex\GBZ420\site (gbz420.test). Production is out of scope.
   Owner: catalogue fixes (2026-09-24). Three corrections taken from the catalogue
   audit of 2026-09-24
   (docs/sitewide-maquettes-20260921/qa-full-audit-20260924/catalogue/REPORT.md,
   findings 2, 4 and 6). The counter of the same bar (finding 1) and the listing
   order (finding 8) are server-side values, so they are handled by
   assets/js/gbz-category-hero-v1.js and by the band partial, not here: this file
   never invents a price, a badge or a count.
   ========================================================================== */

/* ------------------------------------------------------------------ C-04 -- 
   "PROMO !" only while a reduction is actually displayed.

   The flag comes from PrestaShop's stored `on_sale` column through the theme's own
   `catalog/_partials/product-flags.tpl`; it is not a test of a live reduction, so 44
   of the 65 active products flagged `on_sale` carry no valid reduction row today
   (audit finding 2: the reference cards show "PROMO !" with a single price and no
   struck-through price). Nothing in the database is touched: the flag is only
   withdrawn from the paint when the page prints no reduction next to it.

   Card: the theme prints `<span class="regular-price text-muted">` inside
   `.product-price-and-shipping` as soon as the product really has a reduction
   (measured: `/fr/space-cake`, card of product 54 -> 8,49 € + 9,99 € struck),
   and prints nothing when it does not (measured: `/fr/thcx`, 8 cards, 0
   `.regular-price`). The test is made on the card itself, so a reduced card of the
   same grid keeps its flag.

   Product page: the cover carries the same flag list, and the price block grows
   `div.has-discount` with the struck-through `del.regular-price` when the reduction
   is live (measured: product 150 -> no `has-discount`, flag present; products 58 and
   171 -> `has-discount`, flag kept). The test is scoped to `#main-product-wrapper`,
   the product's own container, so the flags of the related-product cards further
   down the page are not affected.

   `:has()` is already a dependency of this catalogue layer (see
   gbz-category-hero-v1.css and the V-03 rule above): on an engine without it the
   selector is invalid, no badge is withdrawn, and the page keeps its current
   rendering. */
.product-miniature:not(:has(.regular-price)) .product-flags .product-flag.on-sale,
body#product #main-product-wrapper:not(:has(.product-prices .has-discount)) .product-cover .product-flags .product-flag.on-sale {
    display: none;
}

/* ------------------------------------------------------------------ C-06 -- 
   The featured price of the maquette hero wears the same display face as the two
   side prices. Measured on `/fr/thcx` at 1440 before this rule (audit finding 6):
   featured "49,99 €" in Karla 24,5 px / 700, side prices "44,99 €" and "39,99 €" in
   GBZ Display 25,2 px / 700. The bundle of the Creative Elements document gives the
   family to the mini prices only
   (`modules/creativeelements/views/css/ce/1170401.css`: `.gbz-hero .gbz-hero-mini
   .product-card .price`), which is why the same band mixed the two faces.

   Only the family is asserted: size, weight, colour and the two-price layout are
   untouched, and the declaration is repeated literally - exactly like
   gbz-category-hero-v1.css does for "GBZ Anton" - because `--gbz-font-display` is
   declared by that same bundle rather than by gbz-tokens.css. `body#category`
   outranks the bundle's class-only selectors whatever the load order is, and the
   generic `.gbz-hero-slot` categories have no `.gbz-hero-feature`, so they are not
   concerned. */
body#category .gbz-hero .gbz-hero-feature .product-card .price {
    font-family: 'GBZ Display', Anton, Impact, "Bahnschrift Condensed", "Arial Narrow", "Arial Black", Arial, sans-serif;
}

/* ------------------------------------------------------------------ C-04b --
   Pagination of the listing reaches the 44 x 44 floor at every width.
   Measured on `/fr/thcx` (audit finding 4): the Creative Elements pagination links
   of `nav.ce-pagination` are 48,6-57 px wide but 37 px tall at 1440, and already
   44 px at 390. The floor was set for the secondary listings on 2026-09-24
   (CHARTE-GRAPHIQUE.md section 2.2); the listing bar's own controls take it below
   992 px in gbz-qa-listings-v1 (V-08 block above). Only `min-height` and the
   vertical centring are added, so the pill's width, padding, label and focus ring
   keep their geometry; the arrows and the page links share the rule. */
@media (min-width: 992px) {
    body#category .ce-pagination a,
    body#search .ce-pagination a {
        align-items: center;
        min-height: 44px;
    }
}

/* ------------------------------------------------------------------ C-07 --
   Same-row card boxes. On a category listing the Creative Elements grid
   (`.ce-products.ce-product-grid`) sizes each row to its tallest card, but the
   items do not take that height: `.js-product-miniature-wrapper` resolves
   `align-self: flex-start` (measured on `/en/gbz` at 1440: grid rows
   476,875 px and 494,375 px, and the first item of the second row reports
   `align-self: flex-start`), so that one card stays 476,859 px while its two
   neighbours are 494,375 px. The 17,5 px is the card's own price block:
   `.product-price-and-shipping` measures 17,5 px on a single price and 35 px
   when the card also prints its struck-through regular price. The short card
   therefore ends 17,5 px higher, and its CTA starts 17,5 px higher with it
   (measured before this rule, `/en/gbz` row 2: card bottoms
   1846,8 / 1864,3 / 1864,3 and CTA tops 1793,8 / 1811,3 / 1811,3).

   Three declarations, and no display change inside the card:
     - the grid item stretches to its row. The row is already sized by the
       tallest card, so this only lifts the shorter box onto the baseline the
       others already sit on;
     - the card takes the item's height, which it could not before because the
       item was shrink-wrapped around it;
     - the add-to-cart block is pushed to the bottom of the description. That
       box already computes to `display: flex` on this page, and its children
       already stack vertically in it: the measured child boxes
       (13,7 + 8 + 17,5 + 8 + 13,4 + 17,8 + 17,5 + 40) sum to exactly the
       measured parent height of 135,9 px. So the slack of a shorter card
       lands above the CTA instead of below it.

   Nothing else is addressed: prices, the rating row, the badges, the images,
   the links and the form keep their markup and their paint, and the quantity
   field keeps the `display: none` the `.ce-products-grid` rule declares. A row
   whose cards already measure equal heights keeps its geometry, because the
   item then takes the height it already had and the description has no slack
   to give. Category 45 (GBZ Prime) is excluded as everywhere in this file, the
   search listing is not matched, and the single-column mobile track holds one
   card per row, so nothing moves there. */
body#category:not(.category-id-45) .ce-products.ce-product-grid > .js-product-miniature-wrapper {
    align-self: stretch;
}

body#category:not(.category-id-45) .ce-products.ce-product-grid > .js-product-miniature-wrapper > .product-miniature {
    height: 100%;
}

body#category:not(.category-id-45) .ce-products.ce-product-grid .product-miniature .product-description > .product-add-cart {
    margin-top: auto;
}

/* ------------------------------------------------------------------ V-20 --
   A long availability label on a listing card ("Artikel nur noch in anderer
   Variante erhaeltlich", live audit 2026-09-26) renders as a single nowrap
   badge inside an absolutely positioned shrink-to-fit box: it measured 220 px
   wide and pushed documentElement.scrollWidth 12 px past clientWidth at 390 on
   /de/suche?controller=search&s=thcx. Capping the box to its card and letting
   the label wrap keeps the badge inside the card instead of the viewport.
   Scoped to mobile and to the two listing bodies that already carry the V-10
   stock-state rule; desktop geometry and every other badge are untouched. */
@media (max-width: 767.98px) {
    body#search .product-availability,
    body#category:not(.category-id-45) .product-availability {
        max-width: 100%;
    }

    body#search .product-availability .badge,
    body#category:not(.category-id-45) .product-availability .badge {
        white-space: normal;
    }
}

/* ====================================================== GBZ-COMBO-01 BEGIN ==
   Category variant controls: one pill per combination, rendered by
   `catalog/_partials/miniatures/_partials/product-miniature-btn.tpl` inside
   `.product-add-cart-combos`. Each pill carries the basket icon and the weight
   label only - no price.

   The card content box measures ~275,9 px at 1440 (4-up desktop grid) and
   ~324 px at 390 (1-up mobile track). With a 4 px gap, `minmax(70px, 1fr)`
   gives 3 x 70 + 2 x 4 = 218 px, so three weights sit side by side on both
   widths; two combinations collapse to two half-width pills (auto-fit drops
   the empty tracks) and a fourth wraps under the row instead of shrinking
   below 70 px, so 2, 3 and 4+ options never overflow the card.

   The 40 px desktop / 44 px mobile touch floors are repeated here so the block
   remains self-sufficient when ported on its own. Only the wrapper grid, the
   per-control wrapper and the pill box are touched: the form, its action, the
   hidden `id_product_attribute`/`qty`, the submit handler, the `disabled`
   attribute, the label text and the spinner stay as the template renders them.
   `.product-description > .product-add-cart` keeps its `margin-top: auto`
   (block above) through the matching wrapper rule below. */
body#category:not(.category-id-45) .product-miniature .product-description > .product-add-cart-combos {
    display: grid;
    gap: 4px;
    grid-template-columns: repeat(auto-fit, minmax(70px, 1fr));
    margin-top: auto;
}

body#category:not(.category-id-45) .product-miniature .product-add-cart-combos > .product-add-cart {
    margin: 0;
    min-width: 0;
}

body#category:not(.category-id-45) .product-miniature .product-add-cart-combos form {
    margin: 0;
}

body#category:not(.category-id-45) .product-miniature .product-add-cart-combos .btn-product-list.add-to-cart {
    align-items: center;
    display: flex;
    font-size: 0.75rem;
    gap: 3px;
    justify-content: center;
    min-width: 0;
    padding-left: 4px;
    padding-right: 4px;
    text-transform: uppercase;
    width: 100%;
}

body#category:not(.category-id-45) .product-miniature .product-add-cart-combos .gbz-combo-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

@media (min-width: 768px) {
    body#category:not(.category-id-45) .product-miniature .product-add-cart-combos .btn-product-list.add-to-cart {
        min-height: 40px;
    }
}

@media (max-width: 767.98px) {
    body#category:not(.category-id-45) .product-miniature .product-add-cart-combos .btn-product-list.add-to-cart {
        min-height: 44px;
    }
}
/* ======================================================== GBZ-COMBO-01 END == */
