/**
 * Copyright since 2026 GuruCoders
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Academic Free License 3.0 (AFL-3.0)
 * that is bundled with this package in the file LICENSE.md.
 * It is also available through the world-wide-web at this URL:
 * https://opensource.org/licenses/AFL-3.0
 * If you did not receive a copy of the license and are unable to obtain it
 * through the world-wide-web, please send an email to office@gurucoders.ro
 * and a copy will be sent immediately.
 *
 * @author    GuruCoders <office@gurucoders.ro>
 * @copyright Since 2026 GuruCoders
 * @license   https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
 */
/* modules/gcpromo/views/css/front.css
 *
 * Own classes, own layout. No Bootstrap, no theme grid: 1.6 themes carry
 * Bootstrap 3, 1.7+ carry Bootstrap 4, and purchased fleet themes carry
 * anything. CSS Grid needs nothing from the theme and works in every browser
 * that matters.
 *
 * Everything is prefixed .gcpromo-, so nothing here reaches the rest of the
 * page. The explicit resets on ul/img are there because a theme's own reset
 * can set list-style or max-width and the grid must not inherit it.
 *
 * NO rem, ANYWHERE. `rem` is a fraction of the THEME's root font size, and a
 * theme is free to pick any. PrestaShop 1.6's default theme sets
 * `html { font-size: 10px }` - so every size here used to come out 37.5%
 * smaller on 1.6 than on 1.7, 8 and 9, which are 16px. Measured on the
 * containers 2026-10-21: the campaign title 20px instead of 32, block titles
 * 14 instead of 22, the countdown 12 instead of 19, and the discount badge
 * 8.5px - a price reduction nobody could read, on seven fleet shops.
 *
 * Absolute units, and an explicit base on .gcpromo-page, so the page is the
 * same size on every theme. Same rule as the colours: set what you depend on.
 *
 * ---------------------------------------------------------------------------
 * THE LOOK, and why it is built the way it is (2026-10-21).
 *
 * The page is a guest. It sits inside a theme that owns the header, the
 * footer, the background and the body font, and it must read as designed
 * without pretending to be a site of its own. So the voice is in WEIGHT,
 * SCALE, RHYTHM and ONE ACCENT - not in a typeface the shop does not own.
 *
 * No webfont, deliberately. A campaign page is served on every visit of a
 * sale; loading a font from a third party would put a request to somebody
 * else's server on all of them, and that is not a decision a discount module
 * gets to make for a shop. The display weight comes from the theme's own
 * family at a heavier grade.
 *
 * Surfaces are derived from `currentColor` rather than hardcoded greys, so a
 * dark theme gets a light plinth instead of an invisible one. `color-mix` does
 * that where it exists; the rgba values above each rule are the fallback and
 * are not dead code.
 */

/* --- tokens ---------------------------------------------------------------
 *
 * TWO LOOKS, ONE FILE (2026-10-22). George: "a select has to be added in the
 * BO letting the client choose whether it will be a landing page or a
 * 'category page' like it was initially."
 *
 *   .gcpromo-layout-category  the original: quiet, inside the theme's palette,
 *                             for a permanent outlet section that should read
 *                             as part of the catalogue.
 *   .gcpromo-layout-landing   dark, lit, the area taken over - what a sale day
 *                             looks like everywhere it is run properly.
 *
 * The category look is the BASE and the landing look overrides it, rather than
 * two parallel blocks: a token added for one must not be forgotten by the
 * other, and this way it cannot be.
 *
 * On the page root rather than :root, so a shop can override the lot for this
 * module alone and nothing here leaks into the theme. */
.gcpromo-page {
    --gcpromo-accent: #e10600;
    --gcpromo-accent-ink: #ffffff;
    /* The accent at a strength a light page can carry. The landing block
     * redefines it four times louder, which is right there and would be a
     * stain here. */
    --gcpromo-accent-wash: rgba(225, 6, 0, 0.08);
    --gcpromo-rule: rgba(0, 0, 0, 0.14);
    --gcpromo-surface: rgba(0, 0, 0, 0.04);
    --gcpromo-surface-strong: rgba(0, 0, 0, 0.08);
    --gcpromo-muted: rgba(0, 0, 0, 0.55);
    /* A SOLID colour, never currentColor.
     *
     * This token is used as a BACKGROUND under white text, and `currentColor`
     * inside a declaration resolves to the colour of the element being
     * styled - which, on a rule that also sets `color: #fff`, is white. The
     * current navigation pill rendered white on white until that was
     * understood. */
    --gcpromo-ink: #111111;
    /* HEADINGS do not inherit the theme's body colour. PrestaShop 1.6's
     * default theme paints body text rgb(119,119,119) and its OWN headings
     * rgb(85,84,84) - it darkens them itself. A 44px masthead inheriting the
     * paragraph grey is weaker than anything the theme prints beside it, and
     * reads as unfinished rather than as restrained. */
    --gcpromo-heading: #111111;
    --gcpromo-panel: rgba(0, 0, 0, 0.04);
    --gcpromo-panel-soft: rgba(0, 0, 0, 0.08);
    --gcpromo-countdown-ink: #ffffff;
    --gcpromo-card-ring: transparent;
    --gcpromo-card-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
    --gcpromo-card-shadow-hover: 0 10px 24px rgba(0, 0, 0, 0.12);
    --gcpromo-badge-glow: none;
    --gcpromo-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
    --gcpromo-shadow-lift: 0 10px 24px rgba(0, 0, 0, 0.12);
    --gcpromo-radius: 10px;

    max-width: 1280px;
    margin: 0 auto;
    padding: 28px 16px 40px;
    box-sizing: border-box;
    font-size: 15px;
    line-height: 1.55;
}

/* Where it exists, the surfaces are built from the text colour the theme set,
 * so the quiet look is legible on a dark theme without a second stylesheet.
 * Scoped to the category look: the landing page sets its own background and
 * derives nothing from the shop. */
@supports (color: color-mix(in srgb, red 50%, transparent)) {
    .gcpromo-page.gcpromo-layout-category {
        --gcpromo-rule: color-mix(in srgb, currentColor 16%, transparent);
        --gcpromo-surface: color-mix(in srgb, currentColor 5%, transparent);
        --gcpromo-surface-strong: color-mix(in srgb, currentColor 10%, transparent);
        --gcpromo-muted: color-mix(in srgb, currentColor 60%, transparent);
        --gcpromo-panel: color-mix(in srgb, currentColor 5%, transparent);
        --gcpromo-panel-soft: color-mix(in srgb, currentColor 10%, transparent);
    }
}

/* --- the landing look -----------------------------------------------------
 * Everything the sale page changes, in one place. */
.gcpromo-page.gcpromo-layout-landing {
    /* Near-black with a blue cast rather than pure #000: a flat black swallows
     * the card edges and the glow has nothing to sit on. */
    --gcpromo-bg: #05070f;
    --gcpromo-panel: #0c1126;
    --gcpromo-panel-soft: #121a38;

    /* The two lights. Blue outlines the furniture, pink marks the money -
     * never the other way round, or the discount stops being the loudest
     * thing on the page. */
    --gcpromo-neon: #2f7bff;
    --gcpromo-neon-soft: rgba(47, 123, 255, 0.38);
    /* The same light at three more strengths, NAMED rather than written out
     * wherever they are used. A campaign can be themed from the back office
     * (GcpromoTheme), and a page whose accent was green while its badges still
     * glowed pink would read as half-themed. Every rgba() below that was once
     * a literal is one of these. */
    --gcpromo-neon-glow: rgba(47, 123, 255, 0.22);
    --gcpromo-neon-glow-strong: rgba(47, 123, 255, 0.35);
    --gcpromo-hero-glow: rgba(47, 123, 255, 0.28);
    --gcpromo-accent: #ff2e63;
    --gcpromo-accent-ink: #ffffff;
    --gcpromo-accent-glow: rgba(255, 46, 99, 0.5);
    --gcpromo-accent-wash: rgba(255, 46, 99, 0.22);

    --gcpromo-ink: #ffffff;
    --gcpromo-heading: #ffffff;
    --gcpromo-muted: rgba(255, 255, 255, 0.64);
    --gcpromo-rule: rgba(255, 255, 255, 0.12);
    --gcpromo-surface: rgba(255, 255, 255, 0.05);
    --gcpromo-surface-strong: rgba(255, 255, 255, 0.10);

    --gcpromo-countdown-cell: var(--gcpromo-panel-soft);
    --gcpromo-countdown-glow: 0 0 0 1px var(--gcpromo-neon-soft), 0 0 16px var(--gcpromo-neon-glow);
    --gcpromo-card-ring: var(--gcpromo-neon-soft);
    --gcpromo-card-shadow: 0 0 0 1px var(--gcpromo-neon-soft), 0 6px 20px rgba(0, 0, 0, 0.45);
    --gcpromo-card-shadow-hover: 0 0 0 1px var(--gcpromo-neon), 0 0 26px var(--gcpromo-neon-glow-strong), 0 14px 34px rgba(0, 0, 0, 0.55);
    --gcpromo-badge-glow: 0 0 18px var(--gcpromo-accent-glow);
    --gcpromo-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
    --gcpromo-shadow-lift: 0 14px 34px rgba(0, 0, 0, 0.55);
    --gcpromo-radius: 16px;

    /* FULL BLEED, with the content still in a container.
     *
     * The dark surface runs the whole width of the window while everything
     * inside it stays in the same 1280px column - which is what a sale page
     * looks like everywhere it is done properly, and what a rounded 1280px
     * card floating in the theme's white page did not.
     *
     * NOT with negative margins, and not with width:100vw - they are the same
     * thing and both are wrong. A viewport unit INCLUDES the vertical
     * scrollbar, so on a machine that reserves space for one the panel is
     * ~15px wider than the window and the whole page scrolls sideways. On
     * macOS, where scrollbars are overlays by default and measure 0px, it
     * looks perfect; George's screen showed the horizontal scrollbar at once.
     *
     * A spread box-shadow bleeds instead. It paints the same colour out past
     * the panel in every direction, and a box-shadow NEVER contributes to the
     * scrollable overflow area, so there is nothing to scroll to. It also
     * covers the strip of theme background above and below the panel, which
     * two absolutely positioned bands used to paint - and those DID add to the
     * scrollable area: the bottom one put 240px of empty black under the last
     * block, which George read, correctly, as dead space.
     *
     * The container is applied to the CHILDREN, so no wrapper element has to
     * be added to a template that both looks share. */
    max-width: none;
    box-shadow: 0 0 0 100vmax var(--gcpromo-bg);
    /* MEASURED, not expressed in viewport units. views/js/page.js sets this to
     * the real distance from the panel's box to the window's edge, so the
     * panel widens on a boxed layout (1.7 and 8 put the content-only page in a
     * 1110px container) without any vw - and vw is what brought the scrollbar
     * back the last two times. Falls back to 0px with no JavaScript, where the
     * spread shadow above still carries the colour to the edges. */
    margin-left: calc(-1 * var(--gcpromo-bleed-left, 0px));
    margin-right: calc(-1 * var(--gcpromo-bleed-right, 0px));
    padding: 34px 20px 54px;
    /* THE CAMPAIGN'S IMAGE, FRAMED RATHER THAN STRETCHED.
     *
     * `cover` sizes the image against the ELEMENT, and this element is the
     * whole page: measured on 9 on 2026-10-26, a 1600x900 photo was blown up
     * 1.91x to cover a 1722px-tall panel, and on a real desktop page three
     * times that. What you saw was a smeared crop, not the picture.
     *
     * `background-attachment: fixed` sizes it against the VIEWPORT instead, so
     * it is always exactly one screen, sharp, composed the way it was shot,
     * and the page scrolls over it.
     *
     * The scrim is a vertical fade rather than one flat veil: lightest at the
     * top where the image is meant to be seen, settling to a heavier veil
     * further down so white product cards have something quiet behind them.
     *
     * IT NEVER REACHES THE SOLID COLOUR. The first version faded to opaque by
     * 110vh, which turned the image into a header band - George, 2026-10-26:
     * "but the background should come across the whole page." It is a page
     * background, so it is behind the whole page; the veil only decides how
     * much of it shows.
     *
     * Expressed in vh so it follows the window rather than the length of the
     * page: a campaign with 8 products and one with 80 look the same.
     *
     * The gradient is the FIRST layer, so it sits over the image. */
    background-color: var(--gcpromo-bg);
    color: var(--gcpromo-ink);
    border-radius: 0;
}

/* THE IMAGE GOES ON A LAYER THE SIZE OF THE WINDOW, not on the panel.
 *
 * A background is painted inside the box that owns it. The panel is not the
 * window: on 1.7 and 8 the content-only layout is a 1110px container, and on
 * every generation the theme leaves padding above it. The spread shadow fills
 * that with the flat colour, which is invisible while the page is one colour
 * and became a black strip across the top the moment a campaign had an IMAGE -
 * George, 2026-10-26, with the same seam down the right-hand side.
 *
 * A fixed layer covers the window whatever the theme does with the panel. It
 * is behind the content (z-index -1) and in front of the page's own
 * background, which is why the panel itself must not paint an opaque one.
 *
 * The scrim rides on this layer too, so the veil and the image stay in step. */
.gcpromo-page.gcpromo-layout-landing::before {
    content: "";
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: -1;
    pointer-events: none;
    background-color: var(--gcpromo-bg);
    background-image:
        linear-gradient(
            to bottom,
            var(--gcpromo-bg-scrim-top, transparent) 0,
            var(--gcpromo-bg-scrim, transparent) 70vh
        ),
        var(--gcpromo-bg-image, none);
    background-size: auto, cover;
    background-position: center top, center center;
    background-repeat: no-repeat;
}
/* CENTRED BY ALIGNMENT, not by margins.
 *
 * The first version centred the children with `margin-left/right: auto` on
 * `.gcpromo-layout-landing > *`. Every component below sets its own vertical
 * rhythm with a `margin` SHORTHAND, which expands to all four longhands -
 * margin-left among them - and `.gcpromo-layout-landing .gcpromo-hero` has
 * exactly the same specificity as `.gcpromo-layout-landing > *`. Being later in
 * the file, it won, reset the centring to 0, and the whole column sat against
 * the left edge with 400px of empty black on the right. Qualifying the rule
 * with .gcpromo-page does not help: that is still two classes against two.
 *
 * Alignment is not in that fight at all. A flex column with align-items:center
 * places its items regardless of what their margin-left says, so no component
 * can un-centre itself by setting its own rhythm, now or later. The items keep
 * width:100% so they still fill the column up to its maximum.
 *
 * The spread box-shadow is painted, not laid out, so it is unaffected. */
.gcpromo-page.gcpromo-layout-landing {
    display: flex;
    flex-direction: column;
    align-items: center;
    /* TRANSPARENT, because the fixed layer is painted at z-index -1 and an
     * opaque background here would cover it. The colour is on that layer and
     * on the spread shadow, so nothing is lost.
     *
     * AND a STACKING CONTEXT, which is `position: relative` PLUS `z-index: 0`
     * together. Either one on its own does nothing here: z-index is ignored on
     * a static box, and relative alone leaves z-index auto. Without the pair,
     * -1 means "behind the nearest ancestor that paints a background" - which
     * on 1.7 is the theme's `section` (#f6f6f6), so the layer disappeared
     * under it and the page went light grey. With the pair, -1 means "behind
     * this panel's own content", which is exactly what a page background is.
     *
     * It also puts the spread shadow INSIDE this context, under the layer, so
     * the two no longer fight over the strip of theme padding above the panel
     * - the strip George saw as a black band is painted by the layer now. */
    background-color: transparent;
    position: relative;
    z-index: 0;
}
/* A FIXED BACKGROUND IS A TOUCH-DEVICE BUG.
 *
 * iOS Safari paints it at document size and Android Chrome repaints it on
 * every scroll frame; both end up worse than the stretching this avoids. A
 * coarse pointer is the standard way to ask "is this a touch screen", and
 * there the image scrolls with the page - upscaled, but phone viewports are
 * narrow so there is much less of it to upscale, and the fade still turns it
 * into a header rather than a full-page wash. */
/* On a touch screen the layer scrolls with the page instead of standing
 * still: a fixed background is repainted every frame on Android Chrome and
 * painted at document size by iOS Safari, and both are worse than the page
 * simply moving over a still image.
 *
 * `position: absolute` AND NOTHING ELSE. The base rule above already sets all
 * four insets to 0, and `.gcpromo-page.gcpromo-layout-landing` is a positioned
 * ancestor (`position: relative` + `z-index: 0`, see the block above), so the
 * layer stretches to the panel's OWN FULL HEIGHT.
 *
 * This block used to add `bottom: auto; height: 100vh`, on the reasoning that
 * the layer is "window-sized either way". That reasoning is false the moment
 * the page is taller than one screen, which any real product grid is: the dark
 * background stopped one viewport down and the theme's light background showed
 * through under it, in the middle of the product cards. Reported from a phone
 * on 2026-10-27 and again on 2026-10-30; `height` is what has to go, because a
 * fixed layer never needed one (the viewport gave it one) and an absolute layer
 * with four zero insets does not want one. */
@media (pointer: coarse) {
    .gcpromo-page.gcpromo-layout-landing::before {
        position: absolute;
    }
}
.gcpromo-layout-landing > * {
    width: 100%;
    max-width: 1280px;
}

/* The theme's own link colour inside our panel would still be the theme's blue
 * on black. Ours, and only there. */
.gcpromo-page.gcpromo-layout-landing a { color: inherit; }

/* --- the masthead, both looks ---------------------------------------------- */
/* THE QUIET PAGE'S HEADER, as a band rather than a stray heading.
 *
 * It used to be a title, a short accent stroke and a line of small type, with
 * 8px under the lot - so the countdown that follows read as part of the
 * heading instead of as the next thing. A hairline across the full width and
 * room under it is what separates a header from the page it heads. */
.gcpromo-hero {
    margin: 0 0 30px;
    padding: 0 0 22px;
    border-bottom: 1px solid var(--gcpromo-rule);
}
/* The window pill. On the quiet page it is a line of small type under the
 * heading; on the sale page the rule below turns it into a lit badge. */
.gcpromo-hero-dates {
    margin: 0;
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--gcpromo-muted);
}

/* --- THE LANDING PAGE -----------------------------------------------------
 *
 * George, 2026-10-22: "it needs to be thought of more as a landing page."
 * Right - what came before was a dark product grid, and a landing page is a
 * sequence of acts: an arrival, a reason to stay, then the goods.
 *
 * All of it scoped to the landing look, so the shop that picked the quiet
 * category page gets exactly what it had.
 *
 * The scale steps up in two media queries rather than with clamp(): clamp is
 * fine on 1.7+ and absent on browsers this fleet still serves, and a hero that
 * silently falls back to nothing is worse than one that steps. */
.gcpromo-layout-landing .gcpromo-hero {
    position: relative;
    margin: 0 0 34px;
    padding: 38px 24px 34px;
    text-align: center;
    border-radius: 20px;
    /* A lit band: the page's own panel, lifted, with the blue sitting behind
     * the type rather than around it. */
    background:
        radial-gradient(120% 140% at 50% 0%, var(--gcpromo-hero-glow) 0%, transparent 62%),
        var(--gcpromo-panel);
    box-shadow: 0 0 0 1px var(--gcpromo-neon-soft), 0 0 40px var(--gcpromo-neon-glow);
}
.gcpromo-layout-landing .gcpromo-title {
    font-size: 54px;
    line-height: 1.02;
    letter-spacing: -0.03em;
    margin: 0 0 16px;
}
/* The rule under the title is the quiet page's signature and the hero does
 * not need it - the band is the frame. */
.gcpromo-layout-landing .gcpromo-title::after { display: none; }
.gcpromo-layout-landing .gcpromo-hero-dates {
    display: inline-block;
    margin: 0;
    padding: 8px 18px;
    border-radius: 999px;
    /* The derived surfaces, not literal white. A campaign can be themed from
     * the back office, and on the light preset this pill was white type on a
     * white film - the dates were served, present and invisible. */
    background: var(--gcpromo-surface-strong);
    box-shadow: 0 0 0 1px var(--gcpromo-rule);
    color: var(--gcpromo-ink);
    font-size: 14px;
    letter-spacing: 0.1em;
}

@media (max-width: 720px) {
    .gcpromo-layout-landing .gcpromo-hero { padding: 26px 16px 24px; }
    .gcpromo-layout-landing .gcpromo-title { font-size: 34px; }
}

/* THE RHYTHM. A landing page breathes: each block is an act, and the air
 * between them is what tells a visitor one has ended. */
.gcpromo-layout-landing .gcpromo-block { margin: 0 0 56px; }
.gcpromo-layout-landing .gcpromo-block-title {
    font-size: 26px;
    /* justify-content, NOT text-align: this element is a flex container - it
     * carries the accent square as a flex child on the quiet page - and
     * text-align does nothing to flex children. Measured: the computed style
     * read `center` while the title sat hard left. */
    justify-content: center;
    text-align: center;
}
/* The square before a section title reads as a bullet in a centred line, so
 * the centred look gets a rule under the title instead. */
.gcpromo-layout-landing .gcpromo-block-title::before { display: none; }
.gcpromo-layout-landing .gcpromo-block-subtitle {
    text-align: center;
    max-width: 680px;
    margin-left: auto;
    margin-right: auto;
}

/* THE COUNTDOWN sits under the hero and belongs to it. */
.gcpromo-layout-landing .gcpromo-block-countdown { margin-top: -14px; }
.gcpromo-layout-landing .gcpromo-countdown { align-items: center; width: 100%; }
.gcpromo-layout-landing .gcpromo-countdown-label { text-align: center; width: 100%; }
.gcpromo-layout-landing .gcpromo-countdown-value { justify-content: center; }

/* THE COPY. An html block on a sale page is the sentence that explains the
 * day - eMAG's sits centred under the banner at a readable measure, and a
 * paragraph running the full 1280px would not be read at all. */
.gcpromo-layout-landing .gcpromo-block-html {
    max-width: 760px;
    margin-left: auto;
    margin-right: auto;
    text-align: center;
    font-size: 16px;
    color: var(--gcpromo-muted);
}

/* THE NAVIGATION, as chips. */
.gcpromo-layout-landing .gcpromo-nav { justify-content: center; }
/* ON THE LINK, not on the list item.
 *
 * The <li> is invisible: it has no background and no border, so padding on it
 * does not make the pill bigger - it pushes the pills apart, 18px a side on
 * top of the 8px flex gap, which is the 44px George asked to lose. */
.gcpromo-layout-landing .gcpromo-nav-item { padding: 0; }
.gcpromo-layout-landing .gcpromo-nav-item a {
    border-radius: 999px;
    padding: 9px 18px;
}

/* --- the page's own header ------------------------------------------------
 *
 * Only on the landing look, where the shop's header has been taken off. Three
 * things and no more: whose shop this is, a search box, and a way back. A
 * fourth would be the beginning of rebuilding the navigation that was removed
 * on purpose. */
.gcpromo-chrome {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    margin: 0 0 26px;
    padding-bottom: 18px;
    border-bottom: 1px solid var(--gcpromo-rule);

    /* STICKY, because the things in it are needed while scrolling.
     *
     * George, 2026-10-28: "on the landing page you had better make the header
     * sticky." A landing page is one long scroll of products, and the three
     * controls it carries are exactly the ones somebody reaches for halfway
     * down: the basket that has been counting up as they add things, the way
     * back into the shop, and the search.
     *
     * The padding at the top is NEW. Unstuck the chrome sits against the
     * panel's own 34px of padding; stuck it would sit against the window with
     * its logo touching the edge, so the room has to come from the element
     * itself.
     */
    position: sticky;
    top: 0;
    /* ABOVE THE CARDS, BELOW THE POPUP. A card lifts on hover and would
     * otherwise pass in front of the header it is scrolling under. */
    z-index: 5;
    padding-top: 14px;

    /* A BACKDROP, or the products scroll through it.
     *
     * The page's colour is on a fixed layer behind everything (see the
     * ::before on the landing panel), so the chrome itself paints nothing and
     * a sticky element that paints nothing is a transparent strip with text
     * in it.
     *
     * NO backdrop-filter, and that is deliberate. It was here as a
     * progressive extra until 2026-10-28, when the language panel turned out
     * to be CUT OFF at this element's bottom edge: Chrome clips the
     * descendants of a backdrop-filtered element to its own box, so the one
     * child of the header that is meant to hang below it could not. The blur
     * bought nothing anyway - this background is fully opaque, so there is
     * nothing showing through for it to soften.
     */
    background: var(--gcpromo-bg);
}
/* THE COLOUR IS REACHED TO THE EDGES, like the panel's own.
 *
 * The panel is full-bleed by a spread shadow, and the chrome is inside its
 * padding - so a backdrop that stopped at the chrome's box would leave two
 * vertical slivers of moving product beside it. The same trick, at the
 * chrome's own width.
 *
 * ON A LAYER OF ITS OWN, since 2026-10-28, and this is the whole reason the
 * pseudo-element exists. A spread shadow grows in all four directions, so it
 * was cut back to the element's own top and bottom with
 * `clip-path: inset(0 -100vmax)` - and a clip-path clips DESCENDANTS too. The
 * language panel, the one child of this header meant to hang below it, was
 * sliced off at the header's bottom edge. Putting the shadow and its clip on
 * a child leaves the header itself unclipped. */
.gcpromo-layout-landing .gcpromo-chrome {
    /* The pseudo is absolutely placed against this box. */
    position: sticky;
}
.gcpromo-layout-landing .gcpromo-chrome::after {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    /* BEHIND the header's own content, which is all this layer has to stay
     * under: it is inside the header's stacking context, so -1 cannot escape
     * to behind the page the way it once did on the panel. */
    z-index: -1;
    box-shadow: 0 0 0 100vmax var(--gcpromo-bg);
    clip-path: inset(0 -100vmax);
    pointer-events: none;
}
.gcpromo-chrome-brand {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
}
.gcpromo-chrome-brand img {
    display: block;
    max-height: 44px;
    width: auto;
    max-width: 200px;
}
/* WHITE - but only where white is possible.
 *
 * A colour logo drawn for a white header disappears on this background -
 * George, 2026-10-23: "the logo should be white, a colour one is hard to see."
 * brightness(0) crushes it to black and invert(1) lifts that to white, so a
 * wordmark on a TRANSPARENT background becomes a clean silhouette. It loses the
 * brand's colours, which on a black page is the point: a legible mark beats an
 * accurate one nobody can make out.
 *
 * It also whitens every pixel of an OPAQUE image, which is a solid white
 * rectangle where the logo was. 1.6's default theme ships logo.jpg and showed
 * exactly that; 1.7, 8 and 9 ship logo.png and looked right, so the rule passed
 * on three generations out of four. The controller reads the file's alpha
 * channel (GcpromoLogoTone) and adds --opaque when there is none. */
/* :not(--opaque) is load-bearing, not tidiness.
 *
 * The rule below for an opaque logo has the SAME specificity as these two -
 * three classes and a type - and these come later in the file, so they won.
 * On 1.6, whose default theme ships logo.jpg, a light palette therefore
 * crushed the whole rectangle to solid black with brightness(0): George,
 * 2026-10-26, "the logo doesn't look right on 1.6."
 *
 * Excluding the opaque case here is better than out-specifying it there,
 * because the two rules then cannot both match - there is nothing left to win. */
.gcpromo-tone-dark.gcpromo-layout-landing
    .gcpromo-chrome-brand:not(.gcpromo-chrome-brand--opaque) img {
    filter: brightness(0) invert(1);
}
/* ... and BLACK on a light one. brightness(0) alone crushes the logo to a
 * silhouette without lifting it, which is the same trick upside down. The
 * light preset showed why this cannot be one rule: a white logo on a white
 * page is not a faint logo, it is no logo at all. */
.gcpromo-tone-light.gcpromo-layout-landing
    .gcpromo-chrome-brand:not(.gcpromo-chrome-brand--opaque) img {
    filter: brightness(0);
}
/* No filter, so the logo keeps its own colours; the chip under it supplies the
 * light background those colours were drawn for. */
.gcpromo-layout-landing .gcpromo-chrome-brand--opaque img {
    filter: none;
    border-radius: 6px;
}
.gcpromo-layout-landing .gcpromo-chrome-brand--opaque {
    padding: 6px 10px;
    background: #fff;
    border-radius: 10px;
}
.gcpromo-chrome-name {
    font-size: 20px;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--gcpromo-heading);
}
/* The search box takes the space the other two do not. */
.gcpromo-chrome-search {
    display: flex;
    flex: 1 1 260px;
    min-width: 200px;
    gap: 0;
}
.gcpromo-chrome-search input {
    flex: 1 1 auto;
    min-width: 0;
    /* 9px, THE SAME AS THE PILLS BESIDE IT, and that is the whole of this
     * number. At 10px the bar stood 41.7px against their 39.7 and sat a pixel
     * higher in the row - measured at 1280px on 2026-10-30. Two pixels is
     * exactly enough to read as "not quite lined up" without being obvious
     * enough to point at. */
    padding: 9px 14px;
    border: 0;
    border-radius: 999px 0 0 999px;
    background: var(--gcpromo-surface-strong);
    color: var(--gcpromo-ink);
    font-size: 14px;
    /* The theme's own input styling is not here to inherit from, so the box
     * states what it needs - including the outline, which a theme would
     * normally provide and which a search field without one loses entirely
     * for anybody using a keyboard.
     *
     * NEGATIVE OFFSET, so the ring is drawn just INSIDE the field. At +2px it
     * sat outside, and a focused search bar grew by four pixels in every
     * direction while the pills beside it stood still - which is what it was
     * reported as: "when it is focused the bar looks taller".
     *
     * KEPT, but it is not what does the work - see the :focus-visible rule
     * below. Firefox honours this offset against its own default ring; Blink
     * does not, which is why the offset alone did not fix the report.
     *
     * An outline and not a box-shadow: Windows' forced-colors mode throws
     * shadows away and keeps outlines, so a shadow ring is a focus indicator
     * that vanishes for the people most likely to need it. */
    outline-offset: -2px;
}
/* THE RING IS DECLARED, NOT LEFT TO THE BROWSER.
 *
 * `outline-offset: -2px` above was meant to pull the ring inside the field, and
 * on its own it did nothing in Chrome: with no `outline` of our own the field
 * gets the UA's `outline: auto`, and Blink draws that ring hugging the border
 * box and ignores `outline-offset` entirely. So the bar still grew when focused
 * and still stood taller than the button beside it - reported again 2026-10-30
 * after the offset had already been added.
 *
 * An explicit `2px solid` with a negative offset is painted strictly inside the
 * box, so the field cannot change size at all. Every other control in this
 * stylesheet already declares its own ring this way (.gcpromo-card-add,
 * .gcpromo-card:focus-within) - the search field was the one that did not.
 *
 * `--gcpromo-ink`, the text colour, rather than the blue: it is derived from
 * the preset, so the ring keeps its contrast on a light preset as well as on a
 * dark one. That is also why .gcpromo-card-add:focus-visible uses it. */
.gcpromo-chrome-search input:focus-visible {
    outline: 2px solid var(--gcpromo-ink);
    outline-offset: -2px;
}
.gcpromo-chrome-search input::placeholder { color: var(--gcpromo-muted); }
.gcpromo-chrome-search button {
    /* 9px, with the field: the two halves of one pill cannot disagree about
     * how tall the pill is. */
    padding: 9px 18px;
    border: 0;
    border-radius: 0 999px 999px 0;
    background: var(--gcpromo-accent);
    color: var(--gcpromo-accent-ink);
    font-size: 14px;
    font-weight: 700;
    cursor: pointer;
}
/* The arrow and the label on one line, and the same height whichever of them
 * is taller - inline-flex rather than inline-block, which would sit the SVG on
 * the text baseline and leave a gap under it. */
.gcpromo-chrome-back {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 9px 16px;
    border-radius: 999px;
    background: var(--gcpromo-surface-strong);
    box-shadow: 0 0 0 1px var(--gcpromo-rule);
    color: var(--gcpromo-ink);
    font-size: 14px;
    font-weight: 700;
    text-decoration: none;
    white-space: nowrap;
    /* PUSHED TO THE RIGHT EDGE BY ITSELF, at every width.
     *
     * It used to land there only because the search box sits between it and
     * the logo and takes all the free space. A campaign configured WITHOUT the
     * search has nothing in the middle, so the button came to rest right
     * beside the logo with the rest of the row empty - George, 2026-10-26.
     * With the search present this margin resolves to 0 and nothing moves. */
    margin-left: auto;
}
.gcpromo-chrome-back:hover { background: var(--gcpromo-surface); }

/* THE LANGUAGE SWITCH.
 *
 * George, 2026-10-28, in three notes: the landing page needs one at all; it
 * should be a dropdown rather than a row of codes; and "the dropdown should
 * not overlap the select."
 *
 * The last one is why this is a <details> and not a <select>. A native
 * select's option list is drawn by the OPERATING SYSTEM - on macOS it opens
 * over the control, aligning the chosen item with it - and no stylesheet can
 * move it. A panel this page owns opens where this page says.
 *
 * It also works with JavaScript off, which the select did not: those are real
 * links, and the browser opens and closes a <details> on its own.
 */
.gcpromo-langs {
    position: relative;
    margin-left: auto;
}
/* When the languages are there, THEY take the auto margin and the cart stops
 * claiming one - two elements both pushing would put all the free space
 * between them instead of keeping the controls together. */
.gcpromo-langs ~ .gcpromo-chrome-cart,
.gcpromo-langs ~ .gcpromo-chrome-back { margin-left: 0; }

/* THE CLOSED CONTROL, which is the same pill as the way back - padding and
 * type copied rather than invented, and NO line-height, because the pills
 * inherit the page's and setting one here made this 35px against their 40. */
.gcpromo-langs-current {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 9px 16px;
    border-radius: 999px;
    background: var(--gcpromo-surface-strong);
    box-shadow: 0 0 0 1px var(--gcpromo-rule);
    color: var(--gcpromo-ink);
    font-size: 14px;
    font-weight: 700;
    letter-spacing: 0.04em;
    cursor: pointer;
    user-select: none;
    /* The browser's own triangle, removed so the drawn one is the only mark.
     * Three selectors because three engines spell it differently and a
     * stylesheet that knows only one leaves a marker on the others. */
    list-style: none;
}
.gcpromo-langs-current::-webkit-details-marker { display: none; }
.gcpromo-langs-current::marker { content: ""; }
.gcpromo-langs-current:hover { background: var(--gcpromo-surface); }
.gcpromo-langs[open] .gcpromo-langs-current { background: var(--gcpromo-surface); }

/* The caret turns over when the panel is open, which is the one piece of
 * feedback a dropdown owes its user. */
.gcpromo-langs-caret {
    flex: 0 0 auto;
    transition: transform 140ms ease;
}
.gcpromo-langs[open] .gcpromo-langs-caret { transform: rotate(180deg); }

/* THE PANEL, BELOW THE CONTROL AND NEVER OVER IT.
 *
 * Absolutely positioned from the control's own box - `top: calc(100% + 6px)`
 * is the whole of "does not overlap" - and right-aligned, because this sits
 * near the right edge of the header and a left-aligned panel would hang off
 * the window on a phone. */
.gcpromo-langs-list {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 20;
    min-width: 100%;
    margin: 0;
    padding: 6px;
    list-style: none;
    border-radius: 14px;
    background: var(--gcpromo-panel);
    box-shadow:
        0 0 0 1px var(--gcpromo-rule),
        0 18px 40px rgba(0, 0, 0, 0.45);
}
.gcpromo-langs-list li { list-style: none; }

.gcpromo-langs-item {
    display: block;
    padding: 8px 14px;
    border-radius: 999px;
    color: var(--gcpromo-muted);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-align: center;
    text-decoration: none;
    white-space: nowrap;
}
.gcpromo-langs-item:hover { color: var(--gcpromo-ink); background: var(--gcpromo-surface); }
.gcpromo-langs-item--current {
    color: var(--gcpromo-ink);
    background: var(--gcpromo-surface-strong);
}

/* THE CART, in the page's own chrome.
 *
 * George, 2026-10-28: "a cart integrated in the skin." The landing page is
 * served without the theme's header, which is where every shop keeps its
 * basket - so a visitor who adds something and comes back had no way of
 * knowing it was still there.
 *
 * Built on the same pill as the way back, because they are the same KIND of
 * control and a header with two different pill shapes in it reads as two
 * headers. It is the only one that carries the accent, which is this module's
 * colour for the money. */
.gcpromo-chrome-cart {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 9px 16px;
    border-radius: 999px;
    background: var(--gcpromo-surface-strong);
    box-shadow: 0 0 0 1px var(--gcpromo-rule);
    color: var(--gcpromo-ink);
    font-size: 14px;
    font-weight: 700;
    text-decoration: none;
    white-space: nowrap;
    /* Pushed to the right with the way back rather than left of the search:
     * a basket belongs beside the way out, which is where every shop puts it
     * and where a visitor's eye already goes. */
    margin-left: auto;
}
.gcpromo-chrome-cart:hover { background: var(--gcpromo-surface); }
.gcpromo-chrome-cart .gcpromo-icon { flex: 0 0 auto; }

/* ONLY ONE auto margin does any pushing. With both the cart and the way back
 * claiming it, the two would be separated by all the free space rather than
 * sitting together - so the way back keeps its own only when it is alone. */
.gcpromo-chrome-cart ~ .gcpromo-chrome-back { margin-left: 0; }

.gcpromo-cart-total {
    font-weight: 600;
    color: var(--gcpromo-muted);
}

/* The count is a disc, and the one piece of the header painted in the accent:
 * it is the number that says there is money in play. */
.gcpromo-cart-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 22px;
    padding: 0 6px;
    box-sizing: border-box;
    border-radius: 999px;
    background: var(--gcpromo-accent);
    color: var(--gcpromo-accent-ink);
    font-size: 12px;
    line-height: 1;
}
/* `hidden` is an attribute the stylesheet has to honour: `display: inline-flex`
 * above beats the browser's own `[hidden] { display: none }`, which is how an
 * empty cart ended up wearing an empty disc. */
.gcpromo-cart-count[hidden],
.gcpromo-cart-total[hidden] { display: none; }

/* A cart with something in it is lit, so the change is visible to somebody who
 * was already looking at the page when the request came back. */
.gcpromo-chrome-cart--filled {
    box-shadow: 0 0 0 1px var(--gcpromo-accent-wash);
}

/* "ADD TO CART" ON A CARD.
 *
 * George, 2026-10-28: "it would have been good if you had included an add to
 * cart button and the cart updated."
 *
 * A SIBLING of the card's link, not a child: a button inside an anchor is
 * invalid markup and a click would navigate before the handler ran. It sits
 * flush at the foot of the tile, full width, so a row of cards ends on one
 * line whether or not every card has one. */
.gcpromo-card-add {
    display: block;
    width: calc(100% - 28px);
    margin: 12px 14px 0;
    padding: 10px 12px;
    border: 0;
    border-radius: 999px;
    background: var(--gcpromo-accent);
    color: var(--gcpromo-accent-ink);
    font: inherit;
    font-weight: 700;
    font-size: 14px;
    line-height: 1.2;
    cursor: pointer;
    transition: transform 120ms ease, opacity 120ms ease, background 120ms ease;
}
.gcpromo-card-add:hover { transform: translateY(-1px); }
.gcpromo-card-add:focus-visible {
    outline: 2px solid var(--gcpromo-ink);
    outline-offset: 2px;
}

/* THE SAME SLOT WHEN THE PRODUCT CANNOT BE ADDED.
 *
 * A ghost of the button rather than the button: it has to fill the space so
 * the card stops looking unfinished, and it must not look like a second way
 * to buy - it leads to the product page, where the size is chosen or the text
 * is typed. Outlined in the accent, which says "this is about this product"
 * without claiming the solid pill's job. */
.gcpromo-card-ask {
    display: block;
    width: calc(100% - 28px);
    margin: 2px 14px 14px;
    padding: 10px 12px;
    border-radius: 999px;
    box-shadow: inset 0 0 0 1px var(--gcpromo-accent-wash);
    background: transparent;
    color: var(--gcpromo-ink);
    font-size: 14px;
    font-weight: 700;
    line-height: 1.2;
    text-align: center;
    text-decoration: none;
    transition: box-shadow 120ms ease, background 120ms ease;
}
.gcpromo-card-ask:hover {
    background: var(--gcpromo-surface);
    box-shadow: inset 0 0 0 1px var(--gcpromo-accent);
}
/* OUT OF STOCK IS NOT AN INVITATION. The card already carries its own "out of
 * stock" flag, so this one stays quiet - present, to hold the space, but
 * without the accent that reads as "do this". */
.gcpromo-card-ask--stock {
    box-shadow: inset 0 0 0 1px var(--gcpromo-rule);
    color: var(--gcpromo-muted);
}
.gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-ask {
    width: calc(100% - 40px);
    margin: 2px 20px 20px;
    padding: 13px 16px;
    font-size: 16px;
}

/* IN FLIGHT. The request takes as long as the shop takes, and a button that
 * looks unchanged invites a second click - which is a second item nobody
 * asked for. */
.gcpromo-card-add--working {
    opacity: 0.6;
    cursor: progress;
}

/* AND AFTERWARDS, briefly. A confirmation rather than a label: it clears, or
 * the next person at that screen believes it is already in their basket. */
.gcpromo-card-add--done {
    background: var(--gcpromo-neon);
}
.gcpromo-card-add--done::after {
    content: " \2713";
}

/* NO :has() HERE. It would let a card with a button drop the link's own bottom
 * padding, which is tidier by 14px - and it is unsupported on the browsers a
 * shop on 1.6 still serves, so the tidying would happen on some visitors'
 * screens and not others. A margin that is right everywhere beats a margin
 * that is perfect in half the places. */
.gcpromo-card-add { margin-top: 2px; margin-bottom: 14px; }

/* The hero of a spotlight block gets the bigger version, like everything else
 * in that tile. */
/* THE SENTENCE UNDER THE HERO'S NAME.
 *
 * Quiet, and clamped: the column is about 463px wide and the text is cut to
 * 300 characters in PHP, which is a little over seven lines at this size.
 * The clamp is the second belt - six lines, so a description longer than
 * its share is cut by the browser rather than by a guess about how many
 * characters fit a line on somebody else's screen.
 *
 * `display: -webkit-box` with a line clamp is the only form of this that
 * works in every browser this fleet meets, Safari included; the standard
 * `line-clamp` is set beside it so the day it lands nothing has to change. */
.gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-teaser {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 40;
    line-clamp: 40;
    overflow: hidden;
    /* THE SAME COLUMN AS EVERYTHING ELSE ON THE CARD. The name, the prices
     * and the button are all inset 20px from the card's edge; the first
     * version of this had no side margin at all, so the one piece of text
     * that is not a heading or a number sat 20px wider than the rest on
     * both sides. Measured on 1.7: 48/479 against everything else's
     * 68/459. */
    margin: 0 20px 14px;
    color: var(--gcpromo-muted);
    font-size: 14px;
    line-height: 1.45;
    font-weight: 400;
    /* THE WORDS TAKE THE LEFTOVER HEIGHT, 2026-11-04.
     *
     * The hero spans two rows and its content is shorter than they are. That
     * slack used to collect above the button, which `margin-top: auto` pins to
     * the floor - "tot ar ramane mult spatiu gol". Growing the PICTURE instead
     * was tried twice and is settled: see the rule above, it crops or it
     * leaves the product adrift in white.
     *
     * So the description takes it. `flex: 1 1 auto` lets the box grow into
     * whatever is left and the clamp - raised from six lines to sixteen -
     * decides how much text may use it; `min-height: 0` because a flex item's
     * default `min-height: auto` refuses to shrink below its content and
     * would push the button off the tile on a long description.
     *
     * On a product with two lines of description the gap comes back, and
     * nothing here can prevent that: there is no content to fill it with. */
    /* BASIS ZERO, and this is the whole of it.
     *
     * `flex: 1 1 auto` let the description ADD height: the box grew to fit the
     * words, the words filled it, and with equal rows the hero then set the row
     * height for the entire grid - 1188px against 428px cards at 1280px with a
     * 1,400-character description, which opened a band of empty grid between
     * every row.
     *
     * With a basis of 0 the description contributes NOTHING to the card's
     * height and only absorbs what is left over. The rows are then sized by the
     * small cards, the hero is exactly twice one plus the gap, and the text
     * clips to fit. It fills space; it no longer makes it. */
    flex: 1 1 0%;
    min-height: 0;
}

.gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-add {
    width: calc(100% - 40px);
    margin: 14px 20px 20px;
    padding: 13px 16px;
    font-size: 16px;
}
/* flex-shrink so a long translated label never squashes the arrow. */
.gcpromo-chrome-back .gcpromo-icon { flex: 0 0 auto; }

@media (max-width: 560px) {
    /* The brand and the way back on one line, the search under them: three
     * things side by side on a phone makes all three too small to use. */
    /* ROW TWO HOLDS THE LANGUAGES AND THE SEARCH, side by side.
     *
     * The language switch arrived on row one, between the brand and the cart,
     * and pushed the way back onto a third row: 133px of a 760px window, where
     * the sticky budget is 95. Two codes are narrow and a search box stretches,
     * so they share a line and the header stays two rows deep. */
    .gcpromo-chrome-search { order: 3; flex: 1 1 auto; min-width: 0; }
    .gcpromo-langs {
        order: 2;
        /* NO auto margin here. On the wide layout it is what pushes the
         * controls to the right; on this one it would push the search away
         * from it and leave a gap down the middle of the row. */
        margin-left: 0;
    }
    /* Which means the way back takes the push back, as it does when there are
     * no languages at all. */
    .gcpromo-langs ~ .gcpromo-chrome-back { margin-left: auto; }
    /* On a phone it shrinks with the pills, and by the same amounts. */
    .gcpromo-langs-current { padding: 8px 12px; font-size: 13px; }

    /* EQUAL HEIGHTS on that first line.
     *
     * Measured on 9 at 375px: the brand was 28px tall and the button 40,
     * centred against each other - two pills of different sizes on one row,
     * which is what made the header look unfinished. `stretch` makes the
     * shorter one match the taller, whichever that is: a shop with a tall logo
     * and one with a short one both get a straight row. */
    /* STICKY HERE TOO, by making it fit.
     *
     * George, 2026-10-28: "but do it on mobile as well." The first answer was
     * to leave the phone alone, because the chrome is two rows there - the
     * brand, the cart and the way back on one, the search on the next - and it
     * measured 117px on a 375x820 screen. A band keeping 14% of a phone all
     * the way down a long page costs more than it gives.
     *
     * The answer was not to drop the search, which trades a feature for a
     * convenience, nor to hide it on scroll, which is JavaScript for a
     * stylesheet's problem. It was to spend less on everything: the two rows
     * still hold what they held, in about three quarters of the height.
     *
     * Below the threshold the gate enforces - an eighth of the window - and
     * measured rather than guessed. */
    .gcpromo-chrome { align-items: stretch; }
    .gcpromo-chrome-search input { padding: 8px 12px; font-size: 13px; }
    .gcpromo-chrome-search button { padding: 8px 14px; font-size: 13px; }

    /* THE WAY BACK GOES TO THE RIGHT EDGE. George, 2026-10-26: "it would be
     * ok for the back to the shop button to be on the right on mobile."
     *
     * `margin-left: auto` rather than space-between on the row: the row also
     * holds the search box, which is a full-width flex item on its own line,
     * and space-between would stretch that line's single item to nothing in
     * particular. An auto margin pushes just this one item and leaves every
     * other line alone.
     *
     * The label still reads, but it is a long one - "Back to the shop" is half
     * the row at this width - so it gives back what it can. */
    /* margin-left: auto is on the base rule now, so it is not repeated here. */
    .gcpromo-chrome-back { padding: 8px 14px; font-size: 13px; gap: 6px; }
    /* The cart keeps its number and loses its word: on a phone the row holds a
     * logo, a basket and a way out, and "Cart" beside a basket icon is the
     * only one of the three that says nothing the picture has not. */
    .gcpromo-chrome-cart { padding: 8px 12px; font-size: 13px; gap: 6px; }
    .gcpromo-chrome-cart .gcpromo-cart-label,
    .gcpromo-chrome-cart .gcpromo-cart-total { display: none; }
    .gcpromo-chrome-brand img { max-height: 26px; }
    .gcpromo-chrome-brand--opaque { padding: 5px 9px; }

    /* ONE PLACE FOR THE HEIGHT, because there are two .gcpromo-chrome rules
     * inside this same media block and the later one wins - the first attempt
     * at the sticky phone header set its padding above and watched this line
     * put it back. The same-specificity collision this stylesheet keeps
     * producing.
     *
     * MEASURED, not guessed, and on every generation: 95px on 9 at 375x820
     * and 97px on 1.6, whose theme gives the pills two pixels more. The gate's
     * ceiling is an eighth of the window - 95px at that size - so the first
     * attempt passed on three shops and failed on the fourth by two pixels.
     * Shaving to the exact edge is how a threshold starts failing for reasons
     * nobody can see, so there is real room under it now. */
    .gcpromo-chrome {
        margin-bottom: 18px;
        padding-top: 5px;
        padding-bottom: 5px;
        gap: 6px;
    }
}

/* THE BANNER, as a band rather than a slab.
 *
 * The quiet page draws it as a panel with an accent stroke down its left
 * edge - right for a notice inside a catalogue. On a sale page the same block
 * is the second act after the hero, so it is centred, large, and lit from
 * below. */
.gcpromo-layout-landing .gcpromo-block-banner {
    padding: 40px 28px;
    text-align: center;
    border-left: 0;
    border-radius: 20px;
    background:
        radial-gradient(110% 160% at 50% 100%, var(--gcpromo-accent-wash) 0%, transparent 60%),
        var(--gcpromo-panel);
    box-shadow: 0 0 0 1px var(--gcpromo-surface-strong);
}
.gcpromo-layout-landing .gcpromo-banner-title {
    font-size: 34px;
    line-height: 1.1;
    letter-spacing: -0.02em;
}
.gcpromo-layout-landing .gcpromo-banner-subtitle {
    max-width: 640px;
    margin-left: auto;
    margin-right: auto;
    font-size: 17px;
}
@media (max-width: 720px) {
    .gcpromo-layout-landing .gcpromo-block-banner { padding: 26px 18px; }
    .gcpromo-layout-landing .gcpromo-banner-title { font-size: 24px; }
}

/* --- masthead -------------------------------------------------------------- */
.gcpromo-title {
    font-size: 44px;
    line-height: 1.05;
    font-weight: 800;
    letter-spacing: -0.022em;
    margin: 0 0 12px;
    color: var(--gcpromo-heading);
    text-wrap: balance;
}
/* The rule under the title is the page's signature: one short, heavy accent
 * stroke, the only place the accent appears at full strength besides the
 * badges. */
.gcpromo-title::after {
    content: "";
    display: block;
    width: 56px;
    height: 4px;
    border-radius: 2px;
    margin-top: 12px;
    background: var(--gcpromo-accent);
}

.gcpromo-teaser-note,
.gcpromo-ended {
    font-size: 17px;
    color: var(--gcpromo-muted);
    margin: 0 0 24px;
    padding: 14px 16px;
    border-left: 3px solid var(--gcpromo-rule);
    background: var(--gcpromo-surface);
}
.gcpromo-ended > :first-child { margin-top: 0; }
.gcpromo-ended > :last-child { margin-bottom: 0; }

/* --- blocks ---------------------------------------------------------------- */
.gcpromo-block { margin: 0 0 44px; }
.gcpromo-block:last-child { margin-bottom: 0; }

/* A heading with a rule that runs to the end of the line. The hairline is
 * drawn by the heading itself rather than by a wrapper, because the markup is
 * a bare <h2> and the block templates are not changing for a stylesheet. */
.gcpromo-block-title {
    font-size: 22px;
    line-height: 1.25;
    font-weight: 700;
    letter-spacing: -0.01em;
    margin: 0 0 8px;
    padding: 0 0 12px;
    color: var(--gcpromo-heading);
    border-bottom: 1px solid var(--gcpromo-rule);
    display: flex;
    align-items: center;
    gap: 10px;
}
/* A vertical accent bar beside the heading, not a square floating next to it.
 *
 * The square was 10px and sat on the text's centre line, which at 22px type
 * reads as a bullet somebody forgot to remove. A bar the height of the type
 * belongs to the heading instead of interrupting it. */
.gcpromo-block-title::before {
    content: "";
    flex: 0 0 auto;
    width: 4px;
    align-self: stretch;
    min-height: 1.1em;
    border-radius: 2px;
    background: var(--gcpromo-accent);
}
.gcpromo-block-subtitle {
    margin: 10px 0 18px;
    color: var(--gcpromo-muted);
}
.gcpromo-block-title + .gcpromo-grid,
.gcpromo-block-title + .gcpromo-empty { margin-top: 18px; }

/* The first block after the header, and the one that most often is the
 * countdown, needs to start rather than continue. */
.gcpromo-hero + .gcpromo-block { margin-top: 4px; }

/* --- countdown ------------------------------------------------------------
 *
 * BOXED NUMBERS with a word under each, the way a sale countdown is read at a
 * glance - George, 2026-10-22: "it still needs a bit of work here, let's do it
 * in the eMAG style." It used to be a small grey pill reading "STARTS IN
 * 22:54:23", which is a sentence rather than a clock.
 *
 * No `rem` anywhere. A rem is a fraction of the THEME's root font size, and
 * 1.6's default theme sets html { font-size: 10px } - every rem measured here
 * came out 37.5% smaller there. Pixels say the same thing on all four. */
.gcpromo-countdown {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    margin: 0 0 18px;
}
.gcpromo-countdown-label {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--gcpromo-muted);
}
.gcpromo-countdown-value {
    display: flex;
    align-items: stretch;
    gap: 8px;
}
.gcpromo-countdown-cell {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-width: 64px;
    padding: 10px 8px 8px;
    border-radius: 12px;
    /* Dark boxes with white figures on BOTH looks: a countdown is the one
     * element that has to read as an instrument rather than as text, and the
     * quiet page can afford four small dark tiles. The sale page lights them
     * further through the token below. */
    background: var(--gcpromo-countdown-cell, var(--gcpromo-ink));
    box-shadow: var(--gcpromo-countdown-glow, none);
    /* Contrast against THE CELL, which is not the page: on a light palette the
     * cell is pale and white figures are a countdown nobody can read. */
    color: var(--gcpromo-countdown-ink, #ffffff);
}
.gcpromo-countdown-number {
    font-size: 30px;
    line-height: 1;
    font-weight: 800;
    /* Tabular figures so the boxes do not jiggle as the digits change - the one
     * place on this page where the text rewrites itself every second. */
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    letter-spacing: -0.01em;
}
.gcpromo-countdown-unit {
    margin-top: 4px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    opacity: 0.72;
}

/* Narrow phones: four boxes of 64px plus gaps do not fit beside a margin, and
 * a countdown that wraps mid-clock reads as two broken rows. */
@media (max-width: 420px) {
    .gcpromo-countdown-cell { min-width: 54px; padding: 8px 6px 6px; }
    .gcpromo-countdown-number { font-size: 24px; }
}

/* --- the product grid ------------------------------------------------- */
.gcpromo-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 18px;
    list-style: none;
    margin: 0;
    padding: 0;
}
.gcpromo-grid > li { list-style: none; }

/* THE SPOTLIGHT IS A ZONE, NOT A BIGGER CARD.
 *
 * George, 2026-10-28, after the first attempt: "it should be a more EXCLUSIVE
 * area, a more attractive product block, something that catches your eye."
 *
 * The first version enlarged one card and left the block itself identical to
 * every other one - same page behind it, same heading, same edges. So the eye
 * had a bigger tile to land on and no reason to believe it had arrived
 * anywhere. What makes a zone read as exclusive is not a louder colour, it is
 * a BOUNDARY: its own surface, its own light, and more air inside it than
 * anything around it has.
 *
 * Four things, and nothing is brighter than the page already was:
 *   a lifted surface, so the section sits ON the page rather than in it;
 *   a hairline in the accent at the top, which is the one place the colour of
 *     the money is allowed to outline something;
 *   a wash falling from the top edge, so the surface has a direction;
 *   room - 34px of it - because space is what cheap pages never spend.
 */
.gcpromo-products--spotlight {
    position: relative;
    padding: 34px 28px 30px;
    border-radius: calc(var(--gcpromo-radius) + 8px);
    background:
        radial-gradient(130% 80% at 50% 0, var(--gcpromo-accent-wash) 0, transparent 62%),
        var(--gcpromo-panel);
    box-shadow:
        0 0 0 1px var(--gcpromo-rule),
        0 24px 60px rgba(0, 0, 0, 0.35);
}

/* The accent hairline, drawn rather than bordered: a border on the element
 * would square the top corners off against the radius above it. */
.gcpromo-products--spotlight::before {
    content: "";
    position: absolute;
    top: 0;
    left: 12%;
    right: 12%;
    height: 2px;
    border-radius: 0 0 2px 2px;
    background: linear-gradient(
        to right,
        transparent 0,
        var(--gcpromo-accent) 30%,
        var(--gcpromo-accent) 70%,
        transparent 100%
    );
}

/* THE HEADING, CENTRED AND WITHOUT ITS BAR.
 *
 * The bar beside a left-aligned title is the module's ordinary block heading,
 * which is exactly what this section must not look like. A zone announces
 * itself from the middle. */
.gcpromo-products--spotlight .gcpromo-block-title {
    display: block;
    text-align: center;
    font-size: 28px;
    border-bottom: 0;
    padding: 0;
    margin: 0;
}
.gcpromo-products--spotlight .gcpromo-block-title::before { display: none; }

/* AND THE SUBTITLE BECOMES A KICKER, above the title rather than under it.
 *
 * Small, letterspaced and in the accent: the two lines then read as one
 * announcement - "selected" over "Featured products" - instead of a heading
 * with a caption, which is what every other block on the page is. `order` can
 * do this without the template knowing, because the section is a flex column
 * here and nowhere else. */
.gcpromo-products--spotlight {
    display: flex;
    flex-direction: column;
}
.gcpromo-products--spotlight .gcpromo-block-subtitle {
    order: -1;
    /* `0 auto`, not `0 0`. The landing look centres a subtitle with auto side
     * margins under a 680px cap, and that rule has the SAME specificity as
     * this one - so writing `margin: 0 0 10px` here did not "leave the
     * horizontal alignment alone", it replaced it, and the kicker sat 272px
     * left of the title it belongs to. The same-specificity collision this
     * stylesheet keeps producing. */
    margin: 0 auto 10px;
    text-align: center;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--gcpromo-accent);
}
.gcpromo-products--spotlight .gcpromo-grid { margin-top: 26px; }

@media (max-width: 560px) {
    /* The frame costs width a phone does not have to spare, so it keeps the
     * surface and gives the padding back. */
    .gcpromo-products--spotlight { padding: 24px 14px 20px; }
    .gcpromo-products--spotlight .gcpromo-block-title { font-size: 22px; }
}

/* THE SPOTLIGHT LAYOUT.
 *
 * George, 2026-10-27, looking at a featured-products block that rendered as
 * the same uniform grid as every other one: "it has to be a more ATTRACTIVE
 * product block, something that catches your eye."
 *
 * A grid of identical tiles has no focal point - the eye has nowhere to land,
 * so it lands nowhere. The first product gets four times the room, a bigger
 * picture, a bigger price and a discount flag you can read across the room;
 * the rest keep their normal size around it. That difference IS the attention:
 * nothing here is louder in colour than the plain grid already is, it is only
 * arranged so one thing is obviously first.
 *
 * The columns are narrower than the plain grid's 220px, so that a hero
 * spanning two of them is a sensible width rather than half the page, and so
 * that a row still fits four small cards beside it on a desktop.
 */
.gcpromo-products--spotlight .gcpromo-grid {
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    /* A CARD IS AS TALL AS ITS OWN CONTENT.
     *
     * `grid-auto-rows: 1fr` below keeps the rows equal so the hero's two-row
     * span is exactly twice a card. A grid item then STRETCHES to its row by
     * default, and the hero is the only card carrying a description - so every
     * small card was inflated to the hero's height and
     * `.gcpromo-card-prices { margin-top: auto }` pooled the difference above
     * its price. Measured at 1280px on 2026-11-04: 20px of air in each small
     * card, 71px when the hero's clamp was briefly raised. With `start` they
     * hug their text: 2px.
     *
     * The cards in one row no longer end level. That is the price, and it is
     * far smaller than a hole in eight cards. */
    align-items: start;
    /* EACH ROW AS TALL AS ITS OWN CARDS, not as the tallest card in the grid.
     *
     * This was `1fr` - equal rows - so that a hero spanning two of them was
     * exactly twice a card. `contain: size` on the hero does that job now and
     * does it better: the hero takes whatever its span gives it and contributes
     * nothing back, so the rows no longer have to be equal for it to line up.
     *
     * What `1fr` cost, measured from the live shop's own numbers on
     * 2026-11-04: every row becomes as tall as the TALLEST card ANYWHERE in the
     * grid, including rows far below the hero. That shop's visible cards were
     * 436px and its rows 473px, because something lower down needed 473 - so
     * the hero was 964 where two rows of its neighbours are 890, and the 74px
     * difference read as a band under the cards beside it. The container never
     * showed it: every card there is the same height, so `1fr` and `auto` agree
     * and the fault is invisible on demo data.
     *
     * With `auto` a tall card in row five makes row five tall and nothing
     * else. */
    grid-auto-rows: auto;
}

.gcpromo-products--spotlight .gcpromo-card--hero {
    grid-column: span 2;
    grid-row: span 2;
    /* AND IT TAKES ITS HEIGHT FROM THE GRID, NOT FROM ITS OWN TEXT.
     *
     * Without this the hero's description decides everything: the box grows to
     * fit the words, the words fill the box, and with equal rows the hero then
     * sets the row height for the whole grid. Measured at 1280px with a
     * 1,400-character description: hero 1188px against 428px cards, so a band
     * of empty grid opened between every row - reported 2026-11-04.
     *
     * `min-height: 0` lets the card be SHORTER than its content, which is what
     * allows the rows to be sized by the small cards instead. The teaser and
     * the link both already carry `min-height: 0` and `overflow: hidden`, so
     * the text clips to whatever two rows leave rather than pushing them open.
     * The hero is then exactly twice a card plus the gap, by construction. */
    min-height: 0;
    /* ITS HEIGHT COMES FROM THE GRID, NOT FROM ITS OWN TEXT.
     *
     * `contain: size` tells the browser to size this box without consulting
     * its contents, which is exactly the question that kept going wrong: with
     * equal rows, a hero whose description decided its own height decided every
     * row's height too, and a band of empty grid opened between the rows.
     * Measured at 1280px with a 1,400-character description and a 40-line
     * clamp: **1188px without containment, 875px with it** - and 875 is
     * 2x428 + the 18px gap, the hero's span exactly.
     *
     * It also makes the clamp irrelevant to the geometry, which is why the
     * clamp is back at 40: the box is whatever two rows leave, `overflow:
     * hidden` cuts the rest, and the description fills it without being able
     * to push it open. Before this, the clamp had to be tuned to 7 against one
     * shop's card heights - a number that would have been wrong on the next
     * shop, which is what this replaces.
     *
     * TURNED OFF BELOW 560px, in the phone block: there the hero spans nothing,
     * so there is no outside height to take and the card collapsed onto the row
     * the small cards had set - measured at 540px, hero 412px with its own
     * prices clipped off the bottom. Containment only makes sense where the
     * grid has a size to give. */
    contain: size;
    /* ...and the HERO still fills the two rows it spans. `align-items: start`
     * on the grid would otherwise make it hug its content too, and a hero that
     * does not reach the bottom of its own span is the gap this is all about,
     * one level up. */
    align-self: stretch;
    /* Lit harder than its neighbours, in the accent rather than in the blue
     * the other cards use: the accent is the colour this module reserves for
     * the money, and the hero is the offer. */
    box-shadow: 0 0 0 1px var(--gcpromo-accent), 0 0 46px var(--gcpromo-accent-wash);
}
.gcpromo-products--spotlight .gcpromo-card--hero:hover {
    box-shadow: 0 0 0 1px var(--gcpromo-accent), 0 0 64px var(--gcpromo-accent-glow);
}

/* The picture fills the tile rather than sitting in a square plinth: a hero
 * with the same contained thumbnail as its neighbours is just a bigger white
 * box. `cover` crops, which is the right trade for one product. */
/* THE HERO'S PICTURE IS A SQUARE, LIKE EVERY OTHER CARD'S.
 *
 * Three attempts, and the first two each fixed the previous one's damage:
 *
 *   68% of the tile    left a slab of empty panel under the photograph when
 *                      the zone grew taller - 123px at 1440px.
 *   flex: 1 + cover    filled the tile by CROPPING, and a product photograph
 *                      cannot be cropped: a grill on a live Black Friday page
 *                      was sliced off top and bottom while the small cards
 *                      beside it showed their products whole.
 *   flex: 1 + contain  stopped the cropping and turned the void into a frame
 *                      INSIDE the picture: a 1:1 photograph in a 463x572 box
 *                      is a product adrift in white, measured on 1.6 on
 *                      2026-11-02 and reported as the framing being wrong.
 *
 * The box was the problem all along, not the fit. `flex: 1` let the picture
 * take whatever height was left, and a square photograph cannot use height
 * that is not square. A 1:1 box - the same shape the small cards have always
 * used - has no leftover to mishandle, and the hero is then simply a bigger
 * version of its neighbours rather than a different idea.
 *
 * The height that is left goes to the words, which on a hero are bigger
 * anyway, and the button is pushed to the floor of the tile so the leftover
 * reads as breathing room rather than as a gap.
 */
.gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-image {
    aspect-ratio: 1 / 1;
    flex: 0 0 auto;
    height: auto;
    min-height: 0;
    object-fit: contain;
    padding: 20px;
}
.gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-add {
    margin-top: auto;
}

/* THE NAME AND THE PRICE TRAVEL TOGETHER, to the bottom.
 *
 * A card's price row carries `margin-top: auto` so that prices line up across
 * a row of equal cards. On a hero twice as tall that pushes the price to the
 * floor and leaves the name stranded at the top with a hand's width of nothing
 * between them. Moving the `auto` onto the NAME sends the pair down as one
 * block, which is what the eye expects under a big picture. */
.gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-name {
    /* No `auto` here any more: the image above grows instead, so the name sits
     * directly under the photograph wherever the photograph ends. */
    margin: 16px 20px 10px;
    font-size: 22px;
    line-height: 1.25;
    min-height: 0;
}
.gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-prices {
    margin: 0 20px 20px;
    padding-right: 96px;
}
.gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-now {
    font-size: 30px;
}
.gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-was {
    font-size: 16px;
}
/* WHAT THE DISCOUNT IS WORTH IN MONEY, under the price on the big card. */
.gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-saving {
    display: block;
    margin: -8px 20px 16px;
    color: var(--gcpromo-accent);
    font-size: 16px;
    font-weight: 700;
}
.gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-saving-label {
    color: var(--gcpromo-muted);
    font-weight: 600;
    margin-right: 4px;
}

/* THE LINK HUGS ITS CONTENT ON THE BIG CARD.
 *
 * Everywhere else `.gcpromo-card-link` stretches, which is right: on a
 * small card it fills what the button does not take and the badge, anchored
 * to the link's bottom right, lands beside the price. On the HERO the same
 * stretch made the link 300px taller than its text, so the badge floated
 * alone in the middle of an empty navy field - visible in the screenshot
 * that reported this card as "still looking empty" on 2026-11-04.
 *
 * Hugging puts the badge back beside the price, which is where the prices
 * row has been reserving 96px for it all along, and moves the leftover
 * space below the text where the button's `margin-top: auto` already
 * accounts for it. */
/* AND THE LINK TAKES THE HEIGHT, so the words inside it can.
 *
 * This hugged its content until 2026-11-04, because stretching it had left the
 * badge floating alone in the middle of an empty navy field. That was measured
 * before the card had a description: there was nothing inside the link that
 * could absorb the extra height, so it pooled around the badge.
 *
 * There is now. The teaser carries `flex: 1 1 auto`, so a stretched link hands
 * the height to the DESCRIPTION rather than to the badge. Measured at 1280px
 * the same day: the air between the link and the button went from 36px to 0,
 * the teaser grew from 41px to 77px, and the badge stayed 12px off the link's
 * bottom edge - exactly where hugging had put it. */
.gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-link {
    flex: 1 1 auto;
    min-height: 0;
}

.gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-badge {
    font-size: 20px;
    padding: 8px 16px;
}

/* ONE COLUMN, NO SPOTLIGHT. A hero spanning two columns of a one-column grid
 * is a card the same width as every other one carrying twice the height for
 * nothing. Below this width the layout is the plain grid again, which is what
 * a phone wants anyway. */
@media (max-width: 560px) {
    .gcpromo-products--spotlight .gcpromo-card--hero {
        /* See the note on `contain: size` above: with no span there is no
         * outside height to take, and a contained card collapses onto the row
         * the small cards set, clipping its own price off the bottom. */
        contain: none;
        /* AND IT HAS TO LOOK DIFFERENT WITHOUT A NEIGHBOUR TO COMPARE TO.
         *
         * Reported from a phone on 2026-11-04: "nu vezi diferenta in skin
         * intre dala mare si restul in afara de short desc". Measured at
         * 390px and the report is exact - the spotlight grid is ONE column
         * there, so the hero is 322px wide and so is every other card. On a
         * desktop the whole distinction is that it is four times the size;
         * at one column "bigger" has nothing to be bigger than.
         *
         * So the mark has to be intrinsic rather than comparative: a ring
         * in the accent, thick enough to read as deliberate, and a panel a
         * shade lifted off the ones around it. The flag over the picture is
         * the other half of this and is a block setting, not a style. */
        box-shadow: 0 0 0 2px var(--gcpromo-accent), 0 0 34px var(--gcpromo-accent-wash);
        background: var(--gcpromo-surface-strong);
        grid-column: auto;
        grid-row: auto;
    }
    .gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-image {
        aspect-ratio: 1 / 1;
        height: auto;
        min-height: 0;
        object-fit: contain;
        padding: 14px;
    }
    /* ROWS SIZE TO THEIR OWN CONTENT AGAIN.
     *
     * `grid-auto-rows: 1fr` exists so a hero spanning two rows is exactly twice
     * a card and the bottom edges line up. Below this width the hero spans
     * nothing - measured, `grid-row: auto / auto` - so the equal rows buy
     * nothing and cost a great deal: every row becomes as tall as the TALLEST
     * card anywhere in the grid, and the hero is taller than the rest because
     * it is the only card that carries a description.
     *
     * Measured on the 1.7 container at 540px with a 600-character description,
     * which is why this was not visible on demo data: five rows all
     * 607.266px, the hero using 101px of that for its teaser, and every other
     * card left with a 226-246px hole between its name and its price, because
     * `.gcpromo-card-prices` pins itself to the bottom with `margin-top: auto`.
     * Reported from a phone on 2026-11-04: "la restul ramane un spatiu destul
     * de mare".
     *
     * `auto` lets each row be as tall as its own content. The cards in one row
     * no longer end level, which on a two-column phone grid is a far smaller
     * price than a hand's depth of nothing in every card. */
    .gcpromo-products--spotlight .gcpromo-grid {
        grid-auto-rows: auto;
        /* AND A CARD DOES NOT STRETCH TO ITS ROW-MATE EITHER.
         *
         * `auto` rows fixed every row but the hero's own: a grid item defaults
         * to stretching to its row's height, so the card BESIDE the hero still
         * matched it - 607px against its own 387px, measured, carrying the same
         * 226px hole while the other eight had come down to 6. `start` lets
         * each card be its own height. Safe here and not above this width,
         * where the hero spans two rows and needs to fill them. */
        align-items: start;
    }

    /* AND THE DESCRIPTION STOPS TAKING THE LEFTOVER HEIGHT.
     *
     * Above this width the hero spans two rows and the words fill what the
     * picture no longer does - see the rule outside this block. Below it the
     * hero is a plain card again, there is no span and so no leftover, and
     * `flex: 1 1 auto` with a sixteen-line clamp simply made one card much
     * taller than the rest: the grid is `minmax(220px, 1fr)`, a row is as tall
     * as its tallest card, and the neighbour then showed a hand's depth of
     * nothing between its name and its price - which is how this arrived,
     * reported from a phone on 2026-11-04, the same day the clamp was raised.
     *
     * Back to hugging its text, and to a clamp a narrow column can carry. */
    .gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-teaser {
        flex: 0 0 auto;
        -webkit-line-clamp: 5;
        line-clamp: 5;
    }
    .gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-name { font-size: 16px; }
    .gcpromo-products--spotlight .gcpromo-card--hero .gcpromo-card-now { font-size: 20px; }
}

/* A card is a lit tile: dark body, white picture, one blue hairline and a
 * halo. The halo is what makes a grid of them read as a row of lights rather
 * than as a table. */
.gcpromo-card {
    position: relative;
    /* A COLUMN, so the card can hold more than its link.
     *
     * The link carries `height: 100%`, which fills the whole tile - so an
     * "add to cart" button placed after it was laid out BELOW the card and
     * clipped away by the `overflow: hidden` two lines down. It was in the
     * DOM, it had a width, and nobody could see or click it: George, looking
     * at the page, "I don't see the button on the product cards."
     *
     * As a flex column the link takes the room that is left and the button
     * sits under it, inside the tile. A card with no button is unchanged -
     * the link is then the only item and still fills the column. */
    display: flex;
    flex-direction: column;
    border-radius: var(--gcpromo-radius);
    background: var(--gcpromo-panel);
    /* Both looks come out of one rule, through tokens: a hairline and a halo
     * on the sale page, a plain plinth on the quiet one. */
    box-shadow: var(--gcpromo-card-shadow);
    overflow: hidden;
    transition: transform 160ms ease, box-shadow 160ms ease;
}
.gcpromo-card:hover {
    transform: translateY(-3px);
    box-shadow: var(--gcpromo-card-shadow-hover);
}
.gcpromo-card:focus-within { outline: 2px solid var(--gcpromo-accent); outline-offset: 2px; }

.gcpromo-card-link {
    display: flex;
    flex-direction: column;
    /* `flex: 1 1 auto` rather than `height: 100%`: inside the flex column
     * above, 100% of the card is the WHOLE card, which leaves a button with
     * nowhere to be. This fills whatever the button does not take, which is
     * all of it when there is no button. */
    flex: 1 1 auto;
    min-height: 0;
    /* What the discount badge is anchored to - see .gcpromo-badge. */
    position: relative;
    padding: 0 0 14px;
    text-decoration: none;
    color: inherit;
}
.gcpromo-card-link:focus { outline: none; }

/* A plinth, drawn by the image itself: there is no wrapper element to put it
 * on, and the markup is not changing for a stylesheet. A fixed square with
 * `contain` is what makes a row of cards line up when the shop's photos are
 * all different shapes - which is most of why the grid read as untidy. */
.gcpromo-card-image {
    display: block;
    width: 100%;
    max-width: 100%;
    aspect-ratio: 1 / 1;
    height: auto;
    object-fit: contain;
    padding: 14px;
    box-sizing: border-box;
    background: #ffffff;
    transition: transform 240ms ease;
}
/* WHITE, and not derived from the text colour any more. The derivation used
 * to lighten the plinth with the theme's ink; on this page the ink is white,
 * so it resolved to white-on-white and said nothing. A product photo shot on
 * white needs white behind it whatever the page does. */
.gcpromo-card:hover .gcpromo-card-image { transform: scale(1.035); }

/* THE FLAG OVER THE BIG TILE.
 *
 * Asked for 2026-11-04: a sticker on the hero - either a few words ("oferta
 * bomba") or an uploaded PNG with transparency.
 *
 * ABSOLUTE, in the card's own corner. The card is `position: relative` and
 * `overflow: hidden` already, so the flag cannot escape it and needs no
 * z-index war: it is the last positioned child of the card and sits over the
 * picture, which is a plain `img` in the flow.
 *
 * `pointer-events: none` because the whole card is a link - a sticker that
 * swallowed the click would make the hero the one product you cannot open.
 *
 * Top LEFT, which is where the screenshot drew it and the opposite corner
 * from the discount badge, so a card can carry both without them meeting. */
.gcpromo-card-flag {
    position: absolute;
    top: 14px;
    left: 14px;
    z-index: 2;
    pointer-events: none;
}

/* The uploaded sticker, never cropped and never wider than a third of the
 * tile - a PNG exported at 1000px should not cover the product it is
 * advertising. `height: auto` keeps whatever proportion it was drawn in. */
.gcpromo-card-flag--image {
    width: 33%;
    max-width: 160px;
    height: auto;
}

/* The words, when there is no sticker. The accent is the campaign's own, so a
 * themed campaign flags in its own colour rather than in a hard-coded red. */
.gcpromo-card-flag--text {
    display: inline-block;
    max-width: 70%;
    padding: 7px 14px;
    border-radius: 999px;
    background: var(--gcpromo-accent);
    color: var(--gcpromo-accent-ink);
    font-size: 14px;
    font-weight: 800;
    line-height: 1.2;
    letter-spacing: 0.01em;
    text-transform: uppercase;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.28);
}

/* A phone's hero is a plain card: the sticker keeps its share of a narrower
 * picture, and the words step down with it. */
@media (max-width: 560px) {
    .gcpromo-card-flag {
        top: 10px;
        left: 10px;
    }
    .gcpromo-card-flag--image {
        max-width: 110px;
    }
    .gcpromo-card-flag--text {
        padding: 5px 11px;
        font-size: 12px;
    }
}

.gcpromo-card-name {
    /* EXACTLY THREE LINES, reserved and not exceeded.
     *
     * A grid of cards is read as a row of equal things, and the heights only
     * differed because the names did - measured at 1280px with this shop's own
     * naming ("Gratar pe gaz si carbuni hibrid Char-Broil Gas2Coal 2.0 330 cu
     * 3 arzatoare 140925/140723"): 424px against 384px, a 40px step down the
     * row. Reserving three lines alone still left 424 against 404, because the
     * longest names take four.
     *
     * So the box is three lines whatever the name does: `min-height` reserves
     * them and the clamp stops a fourth. The full name is one click away on
     * the product page, and a ragged row of cards costs more than the tail of
     * a reference number.
     *
     * The HERO overrides this with `min-height: 0` and no clamp - it has the
     * width to show a long name in two lines and no neighbour to match. */
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    overflow: hidden;
    margin: 12px 14px 8px;
    font-weight: 600;
    line-height: 1.35;
    /* Two lines for every card, so the price row of every card in a row sits
     * on the same line. */
    min-height: 4.05em;
}
.gcpromo-card-prices {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 8px;
    margin: auto 14px 0;
    /* ROOM FOR THE FLAG, always. The discount sits at the end of this row and
     * is positioned rather than laid out - the markup puts it after the row,
     * not inside it, and a stylesheet does not get to rearrange a template.
     * Reserving the space unconditionally keeps the prices of a full row on
     * one line whether or not each card happens to be discounted. */
    padding-right: 62px;
    /* TWO LINES, RESERVED. A discounted card carries the old price and the
     * badge under the current one; an undiscounted card does not, and the two
     * sat 25px apart in the same row - the step still visible after the names
     * were equalised, reported on 2026-11-04. Reserving the taller case costs
     * an undiscounted card some empty space inside its own price block, which
     * reads as spacing, where the difference read as a mistake. */
    min-height: 3.9em;
}
.gcpromo-card-was {
    order: 2;
    font-size: 14px;
    color: var(--gcpromo-muted);
    text-decoration-thickness: 1px;
}
.gcpromo-card-now {
    order: 1;
    font-size: 20px;
    font-weight: 800;
    color: var(--gcpromo-heading);
    letter-spacing: -0.015em;
    font-variant-numeric: tabular-nums;
}

/* The discount flag: a pink pill, the loudest thing on the card.
 *
 * Top left rather than top right - it reads before the photo instead of after
 * it - and rounded on all four corners now, because on a tile that is itself
 * heavily rounded a square-cornered flag reads as a sticker somebody forgot to
 * remove. */
.gcpromo-badge {
    position: absolute;
    /* BESIDE THE PRICE, which is where a shopper is already looking - the
     * reference George sent puts the percentage in a block to the right of
     * the figure rather than as a corner sticker. Anchored to the bottom
     * right of the LINK so it lines up with the price row whatever the name
     * above it wrapped to.
     *
     * The LINK, and no longer the card: once a card could carry an "add to
     * cart" button under the link, the card's bottom right was the button -
     * and the badge landed on top of it, over the word "cart". The link ends
     * where the prices end, with or without a button. */
    right: 12px;
    bottom: 12px;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 52px;
    padding: 7px 10px;
    border-radius: 12px;
    background: var(--gcpromo-accent);
    color: var(--gcpromo-accent-ink);
    box-shadow: var(--gcpromo-badge-glow);
    font-weight: 800;
    font-size: 15px;
    letter-spacing: 0.01em;
    line-height: 1.1;
}

.gcpromo-card-oos { opacity: 0.62; }
.gcpromo-oos {
    display: inline-block;
    margin: 8px 14px 0;
    padding: 2px 8px;
    border: 1px solid var(--gcpromo-rule);
    border-radius: 999px;
    font-size: 13px;
    color: var(--gcpromo-muted);
}

/* --- empty state ----------------------------------------------------------- */
.gcpromo-empty {
    margin: 0;
    padding: 36px 20px;
    border: 1px dashed var(--gcpromo-rule);
    border-radius: var(--gcpromo-radius);
    background: var(--gcpromo-surface);
    color: var(--gcpromo-muted);
    text-align: center;
}

/* --- calls to action ------------------------------------------------------- */
/* QUALIFIED WITH .gcpromo-page AND WRITTEN ON THE ANCHOR, which is not
 * decoration - it is the same specificity contest the current nav tab lost
 * on 2026-10-23, and this rule lost it too.
 *
 * The landing look neutralises the theme's link colour with
 * `.gcpromo-page.gcpromo-layout-landing a { color: inherit }`: two classes
 * and a type. A bare `.gcpromo-cta` is one class, so `inherit` won and the
 * button took the page's ink - white text on a white pill, invisible.
 *
 * Latent until 2026-11-03 for the reason the comment below already guessed
 * at: no demo banner had a button, so nothing ever drew one on the landing
 * look. The demo has a whole one now, and this is what it showed. */
.gcpromo-page a.gcpromo-cta,
.gcpromo-cta {
    display: inline-block;
    padding: 12px 22px;
    border-radius: 999px;
    background: var(--gcpromo-ink);
    /* The page's own background, which is the one colour guaranteed to
     * contrast with its ink. Literal white here was white on white on every
     * dark palette. */
    color: var(--gcpromo-bg);
    text-decoration: none;
    font-weight: 700;
    letter-spacing: 0.01em;
    transition: transform 140ms ease, opacity 140ms ease;
}
.gcpromo-page a.gcpromo-cta:hover,
.gcpromo-cta:hover { transform: translateY(-1px); opacity: 0.92; color: var(--gcpromo-bg); }

/* --- campaign navigation --------------------------------------------------- */
/* QUICK LINKS TO THE SECTIONS OF THIS PAGE.
 *
 * Asked for on 2026-11-02 as a toggle. They are the same pill as the page
 * navigation above them - a visitor should not have to learn two shapes for
 * "jump somewhere on this site" - but WITHOUT the rule underneath: two
 * stacked rows each closed by a hairline reads as two separate headers
 * rather than as navigation and a shortcut to it.
 *
 * Centred, because the row they point into is centred and a left-aligned
 * strip under a centred title looks like it belongs to something else.
 */
.gcpromo-sections {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    list-style: none;
    margin: 0 0 18px;
    padding: 0;
}
.gcpromo-sections > li { list-style: none; }
.gcpromo-sections-link {
    display: inline-block;
    padding: 8px 16px;
    border: 1px solid var(--gcpromo-rule);
    border-radius: 999px;
    text-decoration: none;
    color: inherit;
    font-weight: 600;
    transition: background-color 140ms ease, border-color 140ms ease;
}
.gcpromo-sections-link:hover {
    background: var(--gcpromo-surface-strong);
    border-color: var(--gcpromo-accent);
}
/* A THEME THAT CLIPS ITS MAIN ELEMENT TURNS OFF EVERY STICKY ON THE PAGE.
 *
 * `overflow: hidden` makes an element a SCROLL CONTAINER, and a sticky
 * descendant sticks to that container rather than to the window. The
 * container never scrolls, so the sticky element simply travels away with the
 * page and nothing appears to happen.
 *
 * Measured on a live 1.7.8.3 shop on 2026-11-05, where `main { overflow:
 * hidden !important }` comes from the theme: at 2500px of scroll the header
 * that has been `sticky; top: 0` since 2026-10-23 was at -2420. It had NEVER
 * worked there, on any campaign page, and nobody had noticed because nobody
 * had asked for a second sticky row and gone looking.
 *
 * `overflow: clip` is the same clipping with none of that: it does not create
 * a scroll container, so sticky descendants go back to the window. Verified
 * on that shop with the rule injected - header at 0, links at 77, and the
 * horizontal overflow the theme was clipping stayed at 0px, which is the
 * thing a theme author uses `hidden` to prevent.
 *
 * SCOPED BY `:has()` TO OUR OWN PAGES, so a shop's every other page keeps
 * whatever its theme decided. `!important` because the theme's own rule
 * carries one and specificity alone loses - checked rather than assumed.
 *
 * A browser without `:has()` or without `overflow: clip` ignores this and the
 * page behaves as it did before: not sticky, not broken.
 */
main:has(> .gcpromo-page),
main:has(.gcpromo-page) {
    overflow: clip !important;
}

/* THE SECTION LINKS FOLLOW THE SCROLL, and stop under the header.
 *
 * Asked for on 2026-11-05: on a campaign page that is several screens of
 * products, the row that lets you jump between them is only reachable by
 * scrolling back to the top - which is the one thing it exists to save.
 *
 * `top` IS MEASURED, by page.js, into --gcpromo-chrome-h. The header above
 * this is itself `sticky; top: 0`, and its height depends on the shop's logo,
 * on whether it carries a language switch, and on which of three layouts the
 * width has chosen - 73px, 128px and 89px on the test shop, and a different
 * logo moves all three. The fallback is the desktop figure, so a visitor with
 * no JavaScript still gets sticky links, just not perfectly placed.
 *
 * z-index 4: over the cards, UNDER the header's 5. The two are stacked, not
 * overlapping, but a card lifts on hover and would otherwise pass in front of
 * both.
 */
.gcpromo-layout-landing .gcpromo-block-sections {
    position: sticky;
    top: var(--gcpromo-chrome-h, 73px);
    z-index: 4;
    /* ROOM ABOVE AND BELOW, and the one below is PADDING on purpose.
     *
     * Unstuck these sit in the flow with the page's own spacing; stuck they
     * would touch the header above and the products below. The <ul> used to
     * carry 18px of bottom MARGIN for that gap and this rule took it away,
     * which left the first product butted against the pills - reported
     * 2026-11-05: "era ok un mic spatiu".
     *
     * Putting it back as margin would not do: a margin is outside the box, so
     * the background does not reach it and the products would scroll up
     * THROUGH the gap. Padding is inside, so the bar paints it and the
     * content slides under a clean edge. */
    padding: 10px 0 14px;

    /* THE ROW PAINTS ITSELF, and this line is the whole of what was missing.
     *
     * The ::after below carries `box-shadow: 0 0 0 100vmax` to reach the
     * window's edges - and a box-shadow is painted AROUND a box, never
     * inside it. The header this was copied from has its own `background`
     * and the shadow only extends that colour sideways; this row had the
     * shadow and no background, so the strip between the pills stayed
     * transparent and the products scrolled straight through it. Reported
     * 2026-11-05 with a screenshot, and the gate had passed: it asked
     * `elementFromPoint`, which answers about hit-testing and says nothing
     * at all about paint. */
    background: var(--gcpromo-bg);
}
/* AND IT PAINTS, or the products scroll through it.
 *
 * The same full-bleed backdrop the header uses, and for the same reason: the
 * page's colour is on a fixed layer behind everything, so a sticky element
 * that paints nothing is a transparent strip with pills in it. The shadow
 * reaches the window's edges and the clip keeps it off the rows above and
 * below; both live on a pseudo-element so nothing clips the pills themselves.
 */
.gcpromo-layout-landing .gcpromo-block-sections::after {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: -1;
    box-shadow: 0 0 0 100vmax var(--gcpromo-bg);
    clip-path: inset(0 -100vmax);
    pointer-events: none;
}
/* The <ul>'s own bottom margin is the gap to the next block, and a sticky
 * strip carrying it would hold that empty space against the header. */
.gcpromo-layout-landing .gcpromo-block-sections .gcpromo-sections { margin-bottom: 0; }

/* ON A NARROW SCREEN THE ROW BECOMES ONE LINE YOU SWIPE.
 *
 * Measured at 844px tall on 2026-11-05, with the row wrapping as it does on
 * the wide layout: the header and the links together took 231px at 720 and
 * 241px at 390 - 27% and 29% of the window, held there for the whole scroll.
 * A navigation aid that keeps a third of a phone screen has stopped being an
 * aid. Wide, the same two bars are 126px of 844 - 15% - which is why this
 * only applies below 720.
 *
 * So the pills stop wrapping and the row scrolls sideways instead: one line,
 * about 45px, with the rest a swipe away. It is the pattern a phone already
 * uses for category chips, so there is nothing to learn.
 *
 * `justify-content: flex-start` because centring a row that overflows pushes
 * its first item off the left edge, where it cannot be scrolled back to.
 */
/* THE SAME ROW, ONE LINE YOU SWIPE, decided by MEASUREMENT rather than by a
 * width.
 *
 * page.js adds this class when the names would need three lines or more, at
 * whatever width that happens. The media query below it is the
 * no-JavaScript floor and a guess at "a phone"; this is the answer for the
 * tablet held upright, where thirteen long names still stacked four deep and
 * the sticky bar took a third of the window. */
.gcpromo-layout-landing .gcpromo-block-sections.gcpromo-sections--swipe .gcpromo-sections {
    flex-wrap: nowrap;
    justify-content: flex-start;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding: 2px 2px 6px;
}
.gcpromo-layout-landing .gcpromo-block-sections.gcpromo-sections--swipe .gcpromo-sections::-webkit-scrollbar {
    display: none;
}
.gcpromo-layout-landing .gcpromo-block-sections.gcpromo-sections--swipe .gcpromo-sections > li {
    flex: 0 0 auto;
}

@media (max-width: 720px) {
    .gcpromo-layout-landing .gcpromo-block-sections .gcpromo-sections {
        flex-wrap: nowrap;
        justify-content: flex-start;
        overflow-x: auto;
        /* Momentum on iOS, and a scrollbar that does not sit over the pills
         * on the desktop browsers that draw one. */
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        /* The pills are the full height of the line; without this the first
         * and last touch the window's edges. */
        padding: 2px 2px 6px;
    }
    .gcpromo-layout-landing .gcpromo-block-sections .gcpromo-sections::-webkit-scrollbar {
        display: none;
    }
    /* A pill must not be squeezed to fit - the row scrolls instead. */
    .gcpromo-layout-landing .gcpromo-block-sections .gcpromo-sections > li {
        flex: 0 0 auto;
    }
}

/* A SECTION ARRIVED AT MUST NOT SIT UNDER THE STICKY HEADER.
 *
 * The landing page's chrome is `position: sticky; top: 0`, so an anchor
 * scrolled to the top of the window lands BEHIND it and the visitor sees the
 * middle of the section they asked for. `scroll-margin-top` is what the
 * browser subtracts when it scrolls to an anchor, and it costs nothing when
 * the header is not sticky.
 */
.gcpromo-block[id^="gcpromo-block-"] {
    /* TWO BARS TO CLEAR SINCE 2026-11-05, not one: the section links are
     * sticky too, and they are the very thing that scrolls you here. Landing
     * a section behind the row of links you just used is the worst version of
     * this bug.
     *
     * BOTH HEIGHTS ARE MEASURED, by page.js. The links row was an ESTIMATE
     * first - one row plus room - and it was 17px short on the first real
     * page it met, because that page's row runs to two lines. It depends on
     * how many sections a page has and how long their names are, which is not
     * something a stylesheet can know: 53px on one line, 103px on two, 152px
     * on three.
     *
     * The 12px is breathing room, so a section does not arrive flush against
     * the bar above it. The fallbacks cover a visitor with no JavaScript. */
    scroll-margin-top: calc(
        var(--gcpromo-chrome-h, 73px) + var(--gcpromo-sections-h, 103px) + 12px
    );
}

.gcpromo-nav {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    list-style: none;
    margin: 0;
    padding: 0 0 14px;
    border-bottom: 1px solid var(--gcpromo-rule);
}
.gcpromo-nav > li { list-style: none; }
.gcpromo-nav-item a {
    display: inline-block;
    padding: 8px 16px;
    border: 1px solid var(--gcpromo-rule);
    border-radius: 999px;
    text-decoration: none;
    color: inherit;
    font-weight: 600;
    transition: background-color 140ms ease, border-color 140ms ease;
}
.gcpromo-nav-item a:hover { background: var(--gcpromo-surface-strong); }
/* The accent rather than the ink: a red pill with white text reads on a light
 * theme and on a dark one, and it is the same mark the badges use, so the page
 * says "you are here" in its own voice. */
/* QUALIFIED WITH .gcpromo-page, and that is not decoration.
 *
 * The landing look neutralises the theme's link colour with
 * `.gcpromo-page.gcpromo-layout-landing a { color: inherit }` - two classes and
 * a type. `.gcpromo-nav-current a` is one class and a type, so `inherit` won
 * and the current tab took the page's ink instead of the accent's.
 *
 * It was invisible for as long as there was one palette: on a dark page the
 * ink is white and the accent ink is white, so the wrong rule produced the
 * right colour. The light preset put #111111 on a red pill. */
.gcpromo-page .gcpromo-nav-current a,
.gcpromo-page .gcpromo-nav-current a:hover {
    background: var(--gcpromo-accent);
    color: var(--gcpromo-accent-ink);
    border-color: var(--gcpromo-accent);
}

/* THE WAY OUT OF A SLICE.
 *
 * A campaign page shows part of a category and sends the visitor to the
 * shop's own page for the rest (2026-11-04). The button is quiet on
 * purpose: it is the exit, not the offer, and a second accent-coloured
 * button under every grid would compete with the one on the cards.
 *
 * Centred and full-width on a phone, where a 200px target in the middle of
 * a column is a thing people miss. */
.gcpromo-see-all {
    display: block;
    width: fit-content;
    margin: 22px auto 0;
    padding: 12px 26px;
    border: 1px solid var(--gcpromo-rule);
    border-radius: 999px;
    color: inherit;
    text-decoration: none;
    font-weight: 700;
    font-size: 15px;
    transition: background-color 140ms ease, border-color 140ms ease;
}
.gcpromo-page a.gcpromo-see-all:hover,
.gcpromo-see-all:hover {
    background: var(--gcpromo-surface-strong);
    border-color: var(--gcpromo-accent);
}
@media (max-width: 560px) {
    .gcpromo-see-all { width: 100%; text-align: center; }
}

/* A BANNER THAT IS ITSELF A LINK (2026-11-05).
 *
 * The anchor wraps the panel's contents rather than replacing the panel,
 * so the section keeps its padding and its background and this only has to
 * carry the colour and the layout the words had before.
 *
 * `display: block` because an inline anchor around a picture and two
 * headings collapses to the line box of the first one, and `color:
 * inherit` because the theme's link colour on a whole panel of text is the
 * thing that makes a banner look broken. */
.gcpromo-banner-link {
    display: block;
    color: inherit;
    text-decoration: none;
}
.gcpromo-page a.gcpromo-banner-link:hover { text-decoration: none; }

/* AND THE BUTTON INSIDE IT IS A SPAN. It cannot be an anchor - an <a> in an
 * <a> is invalid - so it is styled from the same rule and simply does not
 * need its own href; the panel's click covers it. */
.gcpromo-block-banner--linked .gcpromo-cta { cursor: pointer; }

/* The whole panel lifts on hover, so it reads as one target rather than as
 * a button sitting on a picture. */
.gcpromo-block-banner--linked { transition: box-shadow 140ms ease, transform 140ms ease; }
.gcpromo-block-banner--linked:hover { transform: translateY(-1px); }

/* --- the banner block ------------------------------------------------------ */
/* A BANNER, not a notice.
 *
 * Flat grey with a 5px red bar down the side is the shape every CMS uses for
 * "warning", and a Black Friday banner wearing it reads as an error message.
 * The accent stays, as a wash across the panel and a thinner edge. */
.gcpromo-block-banner {
    padding: 34px 30px;
    border-radius: var(--gcpromo-radius);
    background:
        linear-gradient(90deg, var(--gcpromo-accent-wash, transparent) 0%, transparent 70%),
        var(--gcpromo-surface);
    border-left: 3px solid var(--gcpromo-accent);
}
/* THE PICTURE A BANNER CARRIES.
 *
 * Never cropped: a banner is usually artwork with words in it, and a crop
 * takes the words off. It keeps its own proportions and takes the width it
 * is given - inside the panel's padding rather than bleeding to its edges,
 * because that padding is three different values across the two looks and
 * the mobile width, and a full-bleed picture would have to know all three. */
.gcpromo-banner-image {
    display: block;
    width: 100%;
    height: auto;
    margin: 0 auto;
    border-radius: calc(var(--gcpromo-radius) - 6px);
}
/* THE SPACE UNDER THE PICTURE BELONGS TO WHAT FOLLOWS IT, and is 0 when
 * nothing does.
 *
 * This was `margin: 0 auto 18px` with
 * `.gcpromo-banner-image:only-child { margin-bottom: 0 }` to take it back on
 * an image-only banner - and 1.3.4 broke that silently, because the image
 * stopped being an only child. It is now wrapped in a `<picture>` beside a
 * `<source>` when a mobile image is set, and in an `<a>` when the whole panel
 * is a link. Either wrapper is enough to lose the rule.
 *
 * Reported 2026-11-05 as "bannerul are un mic margin bottom" and measured on
 * the page: 58px under the picture where the panel's own padding is 40px -
 * exactly the 18px that should have been cancelled.
 *
 * `:not(:last-child)` rather than `:only-child` because it asks the question
 * that actually matters - is there anything below me - and it keeps asking it
 * correctly through any wrapper a future template adds. Both forms are
 * listed: the bare image, and the image inside a `<picture>`. */
.gcpromo-block-banner .gcpromo-banner-image:not(:last-child),
.gcpromo-block-banner picture:not(:last-child) .gcpromo-banner-image {
    margin-bottom: 18px;
}

.gcpromo-banner-title {
    margin: 0;
    color: var(--gcpromo-heading);
    font-size: 26px;
    line-height: 1.2;
    font-weight: 800;
    letter-spacing: -0.015em;
}
.gcpromo-banner-subtitle {
    margin: 8px 0 0;
    color: var(--gcpromo-muted);
    font-size: 17px;
}
.gcpromo-block-banner .gcpromo-cta { margin-top: 18px; }

/* --- free HTML ------------------------------------------------------------- */
/* A READABLE MEASURE. The landing look already caps this at 760px and
 * centres it; the quiet page had the terms running the full width of a 1200px
 * grid, which is about 150 characters a line. */
.gcpromo-block-html { max-width: 70ch; }
.gcpromo-block-html > :first-child { margin-top: 0; }
.gcpromo-block-html > :last-child { margin-bottom: 0; }

/* --- arrival --------------------------------------------------------------- */
/* One orchestrated reveal rather than scattered micro-animations: the blocks
 * come up in order, once, and only for a visitor who has not asked for less
 * motion. */
@media (prefers-reduced-motion: no-preference) {
    .gcpromo-page > .gcpromo-block,
    .gcpromo-page > .gcpromo-title {
        animation: gcpromo-rise 420ms ease-out both;
    }
    .gcpromo-page > .gcpromo-block:nth-of-type(1) { animation-delay: 60ms; }
    .gcpromo-page > .gcpromo-block:nth-of-type(2) { animation-delay: 120ms; }
    .gcpromo-page > .gcpromo-block:nth-of-type(3) { animation-delay: 180ms; }
    .gcpromo-page > .gcpromo-block:nth-of-type(4) { animation-delay: 240ms; }
    .gcpromo-page > .gcpromo-block:nth-of-type(n+5) { animation-delay: 300ms; }
}
@keyframes gcpromo-rise {
    from { opacity: 0; transform: translateY(10px); }
    to { opacity: 1; transform: none; }
}

/* --- narrow ---------------------------------------------------------------- */
@media (max-width: 860px) {
    .gcpromo-title { font-size: 34px; }
    .gcpromo-block { margin-bottom: 34px; }
}
@media (max-width: 560px) {
    /* THE CARDS A BLOCK SAYS A PHONE DOES NOT SHOW.
     *
     * Asked for on 2026-11-05: a block set to 50 products is 25 rows of
     * scrolling here. The number is per block, PHP marks the surplus (see
     * GcpromoMobileLimit) and this is the whole of the hiding - one rule, in
     * the phone block that already redraws this grid, so there is only ever
     * one width at which the layout changes.
     *
     * `display: none` and not `visibility`/`height: 0`: it must leave the
     * grid entirely, or the auto-fill columns keep counting it and leave
     * holes. It is also what stops the browser fetching the picture. */
    .gcpromo-card--mobile-hidden { display: none; }

    .gcpromo-page { padding: 20px 14px 32px; }
    .gcpromo-title { font-size: 28px; }
    .gcpromo-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
    .gcpromo-card-name { margin: 10px 10px 6px; min-height: 0; }
    .gcpromo-card-prices { margin: auto 10px 0; }
    .gcpromo-card-now { font-size: 17px; }
}
