/**
 * Estilos del slot de Criteo Retail Media. Se inyectan por layout desde
 * Block\Slot, que los registra solo en las paginas donde el slot pinta algo.
 *
 * Aqui y no en el CSS del tema porque ese styles.css es un artefacto que genera
 * `yarn build`: si el build no corre, las reglas no existen y el fallo es
 * silencioso. Los templates de este modulo tampoco los escanea Tailwind, asi que
 * es CSS a mano, sin utilidades ni variantes entre corchetes.
 *
 * Los tiempos del apagado/encendido de la rejilla NO estan aqui a proposito: de
 * ellos depende que el reflujo no puntue como CLS, y van en linea desde
 * slot.phtml (settleAfterRemoval).
 */

    /* Border like the product cards (1px #e5e7eb, 6px radius) BUT no left/right
       padding (the theme .container adds an 18px gutter) so the banner/card sit
       flush to the border, as in Figma. overflow-hidden clips to the radius. */
    .criteo-slot__inner { padding-left: 0 !important;
        padding-right: 0 !important; border: 1px solid rgb(229 231 235);
        border-radius: 6px; overflow: hidden; }
    /* Menos en la ficha, donde el bloque es solo el carrusel y cada tarjeta ya
       dibuja ese mismo borde. Donde hay creatividad de Display el marco si
       encuadra algo, asi que se queda. */
    .catalog-product-view .criteo-slot__inner { border: 0; }
    /* Fade the block in when it renders (deferred load) so it doesn't pop in
       abruptly. */
    /* Same timing and curve as the in-grid cards' entry (the --criteo-enter-*
       tokens live in the theme's CSS), so band and cards move alike.
       Not used on the LANDING: this animation starts when the band is INSERTED,
       which on that surface is several frames before the box opens, so the fade
       was over by the time anything was visible and the band popped in. There
       revealBand() turns it off inline and drives the entry itself, in step with
       the movement of the page below. */
    .criteo-slot__inner {
        animation: criteoFadeIn var(--criteo-enter-duration, 260ms)
                   var(--criteo-enter-ease, ease-out) both;
    }
    @keyframes criteoFadeIn { from { opacity: 0; } to { opacity: 1; } }
    @media (prefers-reduced-motion: reduce) { .criteo-slot__inner { animation: none; } }
    /* Responsive layout is driven by JS-toggled classes (applyResponsive() +
       a ResizeObserver), NOT container queries. Reason: the theme carousel
       animates the track with the CSS `left` property, which relayouts every
       frame; with `container-type` on the slot the browser RE-EVALUATED the
       container queries on every one of those frames → ~24 micro layout-shifts
       per slide = the "flicker/jump" when moving product to product. A class
       toggled only on real resize has zero per-frame cost. Defaults are the
       WIDE layout (matches the first paint / server reserve); `.is-narrow` /
       `.is-wide` are set from measured widths (thresholds 1040 / 380). */
    .criteo-slot__row { display: flex; flex-direction: row; align-items: stretch; gap: 0; }
    /* Side-by-side: banner (creative) and carousel share the row 50/50 — both
       flex:1 1 0 so each column is exactly half the inner width (the creative used
       to be capped at 576px, leaving the carousel wider and the split uneven). */
    .criteo-slot__creative { width: 100%; min-width: 0; flex: 1 1 0; }
    /* Branding creative background (Criteo Commerce Display Spotlight): the
       1400x450 asset centred + cropped from all sides so the outer blueprint
       margins/guides drop off and the 576x200 safety zone stays prominent
       (~75% of the branding-zone height), matching the Criteo mockups instead
       of scaling the whole asset to fit (design QA 2026-07-31). `auto 168%`
       keeps that proportion independent of the strip height; the image URLs
       come from DisplayCreativeRenderer as CSS custom properties. */
    .criteo-slot__branding {
        background-image: var(--criteo-branding-bg);
        background-position: center;
        background-repeat: no-repeat;
        background-size: auto 168%;
    }
    @media (max-width: 640px) {
        /* Mobile: show the FULL mobile asset (delivered pre-sized ~16:5) with no
           side crop — the design team flagged that cover was cropping the sides
           (Criteo design QA 2026-07-31). Match the branding-zone ratio to the
           asset (16:5) and use contain so it fills the stacked strip without
           cropping or letterboxing; height:auto/min-height:0 (important, to beat
           the desktop reserve set inline) lets the zone take the asset's height. */
        .criteo-slot__branding {
            background-image: var(--criteo-branding-bg-m, var(--criteo-branding-bg));
            background-size: contain;
            aspect-ratio: 16 / 5;
            height: auto !important;
            min-height: 0 !important;
        }
    }
    .criteo-slot__inner.is-narrow .criteo-slot__row { flex-direction: column; }
    .criteo-slot__inner.is-narrow .criteo-slot__creative { flex: 1 1 auto; }
    .criteo-hcard__bottom { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; }
    .criteo-slot__hcard.is-wide .criteo-hcard__bottom {
        flex-direction: row; align-items: flex-end; justify-content: space-between; gap: 12px;
    }
    /* Isolate each card's internal layout: the theme carousel animates the track
       with the `left` property (a layout property → reflow every frame). Without
       this, that per-frame reflow recomputes the inside of every card (image,
       rating, price, ATC, wishlist) → jank/flicker when moving product to product
       on slower devices. `contain: layout` scopes the reflow to the card box (no
       size containment, so the flex slide still sizes it). Replaces the layout
       containment that `container-type` used to give — without re-evaluating a
       container query per frame. */
    .criteo-slot__hcard { contain: layout; }
    /* Carousel arrows — same look & behaviour as the homepage Splide slider:
       42px white circle, soft shadow, no border, black icon, .5 opacity (1 on
       hover), .35 when disabled. Disabled at the ends (via :disabled) so the
       click is captured and never falls through to the product. */
    .criteo-slot__arrow { width: 42px; height: 42px; border: 0; border-radius: 9999px;
        background: #fff; color: #000; opacity: .5; cursor: pointer;
        box-shadow: 0 6px 11px rgba(0,0,0,.09); transition: opacity .15s ease; }
    .criteo-slot__arrow:hover { opacity: 1; }
    .criteo-slot__arrow--disabled, .criteo-slot__arrow:disabled { opacity: .35; cursor: default; }
    /* Pagination bullets in Druni's pink tone (the theme .slider-dots-line only
       ships black/white variants); thin bar, active solid / inactive faded via
       the theme's --slider-dots-line-opacity var. */
    .criteo-slot__inner .slider-dots-line { background-color: rgb(211 57 113 / var(--slider-dots-line-opacity, 1)); }
    /* Pin the dots row height. The active dot animates its height (2px→4px, .15s)
       when the slide changes; the row is items-center, so that animation made the
       row height oscillate ~1px, and in side-by-side the banner (align-items:
       stretch) followed it → the "Tu marca" text jittered on every slide. Fixing
       the row to the tallest dot (4px) decouples it: the dots still animate inside,
       the row height (and the banner) stay put. */
    .criteo-slot__dots { height: 4px; box-sizing: content-box; }
    /* Price: show it EXACTLY like the grid card — the "Precio especial/habitual"
       labels are screen-reader only there (a scoped rule that doesn't reach our
       cloned price-box), so replicate that sr-only hiding → only the numbers show. */
    .criteo-slot__inner .price-box .price-label {
        position: absolute !important; width: 1px; height: 1px; padding: 0;
        margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
    }
    /* Price emphasis: match the organic grid card so it is clear which number is
       the actual price. The theme makes the current (final) price bold + larger
       and the old price smaller, but that rule is scoped to .product-item and
       doesn't reach our cloned .price-box, so both rendered the same size here
       (Criteo QA 2026-07-30: confusing on the CDS). Keyed on data-price-type so it
       also covers a product with no discount (final price only). */
    .criteo-slot__inner .price-box [data-price-type="finalPrice"] .price {
        font-weight: 700; font-size: 18px;
    }
    .criteo-slot__inner .price-box [data-price-type="oldPrice"] .price {
        font-weight: 400; font-size: 14px;
    }
    /* CLS: reserve the slot's footprint on PDP/cart (where it is placed
       server-side and NOT moved by JS) so the deferred content fills reserved
       space instead of growing from 0 and pushing the footer down. Collapsed to 0
       by JS if no ad is returned. (Home/category are JS-repositioned, so reserving
       there would just shift the gap around — handled separately.) */
    .criteo-slot--reserve { min-height: 470px; }
    @media (min-width: 768px) { .criteo-slot--reserve { min-height: 545px; } }
    /* The reserved box MUST keep its height even before Alpine strips x-cloak.
       Hyvä's global `[x-cloak]{display:none}` otherwise collapses the slot to 0px
       until hydration and then it pops to its reserve height, pushing everything
       below down — that was the whole landing CLS (category-cms jumped +232px at
       hydration, ~t=1.1s). The inner ad content still fades in via x-html on load;
       until then this is just an empty reserved box. */
    .criteo-slot--reserve[x-cloak],
    .criteo-slot--reserve-listing[x-cloak],
    .criteo-slot--reserve-ingrid[x-cloak],
    .criteo-slot--loading[x-cloak] { display: block !important; }
    /* Same exception for the landing's reveal box, but keeping it a GRID: it has
       zero height while closed, so there is nothing to hold — what it must not
       lose is the collapsed track the transition animates from. */
    .criteo-slot--reveal[x-cloak] { display: grid !important; }
    /* Loading skeleton shared by ALL spotlights (home, category landing and the
       in-grid sub-category band), so every spotlight surface shows the SAME
       "loading" hint. Its LOOK (grey, radius, pulse) esta mas abajo en esta misma
       hoja, en la misma regla que la celda in-grid, para que los dos skeletons no
       puedan separarse; only min-height is set INLINE per surface (see
       bandReserveHeight) and the class is removed by JS once the real band renders
       (the inner has margins and would let the grey peek otherwise). */
    /* Sub-category LISTING spotlight reserve: the band is woven into the product
       grid as a FULL-WIDTH row by JS (see earlyPlaceListingBand). Reserve that
       full-row footprint at first paint — grid-column 1/-1 + the band's ~480px
       height — so the deferred ad FILLS reserved space instead of being inserted
       at load time and pushing the rows below down (~500px CLS). Without this the
       band's only visible loading hint was the single in-grid product skeleton
       cell, and the full row popped in on load. Collapsed by JS if no ad. */
    /* In-grid band spans the full row; grey skeleton + height come from
       criteo-slot--loading + the inline min-height (see bandReserveHeight). */
    /* CATEGORY LANDING — no reserve at all, the band REVEALS itself.
       (Landing only. Home, PDP/cart and the sub-category in-grid band keep their
       reserves: this surface is the one where the reserve was a net loss.)

       Why the reserve went away: it was a bet on an ad arriving, and the bet
       loses most of the time. Reserving 280-480px and giving them back when
       Criteo returns nothing cost CLS 0,125 desktop / 0,209 mobile on EVERY
       landing load — no-fill included, i.e. the majority. Starting at zero and
       growing only when there IS an ad costs 0 on those loads and 0,094/0,151 on
       the ones that fill (measured 2026-08-28, banco in
       ATMOSUP-15317/TODOS/01-criteo-sin-reserva-transicion/banco-cls).

       Mechanics: a collapsed grid track (0fr) opened to 1fr. That is the only
       way to reach "however tall the content turns out to be" without naming a
       number — and naming a number is exactly what the reserve did.

       The box is a closed grid from the FIRST PAINT, with no dependency on Alpine
       having hydrated (see the [x-cloak] rule above). JS flips `is-open` when the
       ad has actually rendered.

       The `transition` below is NOT how the band opens on arrival. Animating it
       bought nothing: `grid-template-rows` is a LAYOUT property, so the 400ms were
       painted as 17-18 consecutive layout-shift entries which, being less than 1s
       apart, all SUM into the same session window — the total is the band's height,
       exactly what a hard pop costs (measured 0,1126 at 1440 and 0,3184 at 390 with
       the real Criteo, ATMOSUP-15317/LANDING-FLIP.md). So revealBand() now opens
       the box in ONE frame with `transition-property: none` inline and compensates
       what is below with `transform`, which is exempt (FLIP). What this rule still
       governs is every OTHER height change of the box — the is-narrow/is-wide
       toggling on resize — for which revealBand() hands the transition back.

       TWO INVARIANTS, do not break them:
       1. The animated child must be CONTENT-SIZED. A child with its own `height`
          wins over the 0fr track and the box never closes — that silently
          reinstates the reserve. `.criteo-slot__inner` is content-sized; keep it
          that way.
       2. Nothing outside the animated track collapses to zero. The inner's my-6
          does NOT: measured, the CLOSED box with an ad inside is 50px tall, not
          0. `min-height: 0` only removes the item's automatic minimum size; the
          24+24 of margin still count in what the 0fr track resolves to. Moving
          the air to `padding` was tried and gives the SAME 50px, because padding
          counts too — so there is no CSS spelling of "air outside the content"
          that the closed box does not hold.
          That is what the max-height clamp below is for, and why it is load-
          bearing rather than belt-and-braces: with the closed box pinned to zero,
          the insertion cannot give it those 50px, and revealBand() can measure
          the page right before opening and know that measure is the PAINTED one.
          Take the clamp away and the 50px come back as an uncompensated shift
          (0,0212 of the 0,0290 that survived the first version of this).

       The 15px padding is the same alignment fix as before: the category content
       (title, tiles) sits inside a 15px grid gutter, so without it the box looks
       15px wider on each side. Horizontal only — it costs no height. */
    .criteo-slot--reveal {
        display: grid;
        grid-template-rows: 0fr;
        padding-left: 15px;
        padding-right: 15px;
        transition: grid-template-rows 400ms ease-in-out;
    }
    /* The closed box is pinned to zero, not merely asked to be zero. Invariant 2
       says a 0fr track still resolves to the child's margins/padding — 50px here —
       and those 50px appear the instant the ad is inserted. Normally no frame is
       painted between the insert and revealBand(), so FLIP absorbs them; but on a
       slow frame one does get painted (measured: 1 run in 3, a +50 shift plus the
       correction). max-height settles it at the source: with the box clamped there
       are no 50px to paint, whatever the frame timing. It has to be dropped on
       is-open or the band could never grow past zero. */
    .criteo-slot--reveal:not(.is-open) {
        max-height: 0;
        overflow: hidden;
    }
    .criteo-slot--reveal > * {
        min-height: 0;
        overflow: hidden;
    }
    .criteo-slot--reveal.is-open {
        grid-template-rows: 1fr;
    }
    @media (prefers-reduced-motion: reduce) {
        .criteo-slot--reveal { transition: none; }
    }
    /* Sub-category LISTING (non-landing) spotlight band. The band is a GRID
       item, and it is the grid — not this element's server-side position —
       that reserves its footprint: ReserveInGridCells writes a full-width hole
       into the product grid HTML, and earlyPlaceListingBand() swaps the band
       into it on hydration.

       So this class reserves NOTHING. Holding the height here was the page's
       single biggest layout shift: the block is declared at the top of
       `column main` (the LANDING needs it there), so the reserve sat ABOVE the
       toolbar and vanished from there the moment the band was moved into the
       grid — the content column jumped up 480px, CLS 0.1607, toolbar from
       top=750 to top=270 (measured 2026-08-28,
       /cabello/peluqueria-profesional/tratamientos, RD3 local desktop 1440).
       It reproduced identically with consent DENIED, i.e. with no Criteo call
       at all, which is what ruled out the ad itself: reserving in one place and
       using the box in another is the whole bug.

       Reserving here again would reinstate it. The height now lives on
       --reserve-ingrid, which is only in play once the band is where it is
       used. Collapsed by JS (collapseReserve) if no ad is returned. */
    .criteo-slot--reserve-listing {
        min-height: 0;
        grid-column: 1 / -1;
    }
    /* Height of the band's row, applied only ONCE THE BAND IS IN THE GRID (the
       class is added by earlyPlaceListingBand as it swaps into the server-side
       hole). Shared by the BAND and by the HOLE the server leaves for it
       (.criteo-band-reserve, ReserveInGridCells) so the swap is between two
       identical boxes at any width — a mismatch would move the grid, which is
       what the hole exists to prevent.

       As tall as the BAND, not as tall as a product row: the row's height was
       borrowed rhythm, and whatever the band did not need became visible white
       space above and below it (--reserve-ingrid centres the band in its row).
       Client-reported 2026-09-02 on /cabello/peluqueria-profesional/tratamientos.

       Only TWO steps on desktop, because the band only has two layouts. What
       decides which is the INNER's width against applyResponsive's threshold
       (ANCHO_LADO_A_LADO in slot.phtml, 920), and the inner takes exactly three
       widths in the listing (the theme container minus the filter sidebar), so
       in CSS the crossover is a viewport breakpoint: the inner is 695 up to
       vp 1200, 951 from 1280 to 1500, and 1207 from 1560. With the threshold at
       920 that makes 1280 the line.

       Measured 2026-09-02, RD3 local, demo fill. Band broken down:

         vp    inner  layout   banner  card+dots  band
         390    370   narrow      ~115       ~230   444
         768    493   narrow       200        230   430
        1024    695   narrow       200        230   430
        1280    951   WIDE         228   (shares)   230
        1920   1207   WIDE         228   (shares)   230

       Side by side the banner IS the band (228 vs 230): the card shares the row
       instead of adding to it, which is why this is also the layout where the
       reserve equals the banner's own height. Stacked, the band is the banner
       plus the card, so 430.

       The old steps (400/480/530/480/530) tracked a product row and cost 25+25
       at 1440, 50+50 at 1024 and 150+150 at 1920.

       MOBILE (<640) is left at 400 on purpose and is the one estimate here: the
       band measured 352 on production (mobile creative at aspect-ratio 16/5 =
       115px, card 211, dots) but 444 on the demo provider, which emits no
       .criteo-slot__branding and so never gets that mobile ratio. 400 sits
       between the two; it needs a production measurement to be pinned, and until
       then it is a floor the band can still overflow. */
    .criteo-band-reserve,
    .criteo-slot--reserve-ingrid {
        grid-column: 1 / -1;
        min-height: 400px;
    }
    @media (min-width: 640px) {
        .criteo-band-reserve, .criteo-slot--reserve-ingrid { min-height: 430px; }
    }
    @media (min-width: 1280px) {
        .criteo-band-reserve, .criteo-slot--reserve-ingrid { min-height: 230px; }
    }
    .criteo-slot--reserve-ingrid {
        /* The row's height belongs to the GRID, not to the creative, so the band
           is centred in it instead of setting it. Column flow keeps the inner at
           full width (align-items defaults to stretch), justify-content centres it
           on the vertical axis, and nothing scales — the creative renders at the
           size it would have had anyway, so its proportions are untouched.
           Whatever is left over becomes equal air above and below: 25px at 1440,
           125px at 1920, where the banner sits side-by-side and is much shorter. */
        display: flex;
        flex-direction: column;
        justify-content: center;
    }
    /* The inner's my-6 is vertical rhythm for a band that SETS its own height. In
       the grid row it would only eat into the space the centring is distributing
       (and on this surface those margins do not collapse out — a grid item is a
       block-formatting-context root), so the box would need 48px more than the
       creative to show it. The row already provides the air. */
    .criteo-slot--reserve-ingrid > .criteo-slot__inner {
        margin-top: 0;
        margin-bottom: 0;
    }


/* Criteo placeholder while the deferred ad is fetched: same values as the theme's
   .skeleton-text-md, so it is indistinguishable from any other skeleton.
   La celda in-grid la escribe un plugin de PHP (ReserveInGridCells::placeholderCell)
   que pinta haya hidratado el slot o no, asi que su aspecto no puede vivir dentro de
   slot.phtml: comparte regla con el skeleton del spotlight, aqui, y asi los dos no
   pueden separarse. */
.criteo-slot--loading,
.criteo-ingrid-slot {
    background-color: #e2e8f0;
    border-radius: .375rem;
    animation: pulse 1.5s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
    .criteo-slot--loading,
    .criteo-ingrid-slot {
        animation: none;
    }
}

/* Entry of the Criteo content: the skeleton stays as a layer ABOVE the card
   (::before) and fades out over it, a real crossfade rather than a swap. Only
   opacity and transform are animated, so the entry cannot add CLS. --criteo-i is
   the card index, written by injectInGrid, and staggers the cascade. */
:root {
    --criteo-enter-duration: 260ms;
    --criteo-enter-ease: cubic-bezier(.2, .6, .2, 1);
    --criteo-enter-stagger: 45ms;
}

/* Cell that was a skeleton and now holds a card: crossfade under the grey. */
.criteo-ingrid__card--enter::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    background-color: #e2e8f0;
    border-radius: .375rem;
    animation: criteoSkeletonOut var(--criteo-enter-duration) var(--criteo-enter-ease) both;
    animation-delay: calc(var(--criteo-i, 0) * var(--criteo-enter-stagger));
}

/* The card underneath. No movement: the hole was already in place, and shifting it
   would fight the skeleton fading out above. */
.criteo-ingrid__card--enter > * {
    animation: criteoCardFade var(--criteo-enter-duration) var(--criteo-enter-ease) both;
    animation-delay: calc(var(--criteo-i, 0) * var(--criteo-enter-stagger));
}

/* INSERTED cell (production, no reserve): no skeleton to cross, so it rises in. */
.criteo-ingrid__card--enter-new > * {
    animation: criteoCardRise var(--criteo-enter-duration) var(--criteo-enter-ease) both;
    animation-delay: calc(var(--criteo-i, 0) * var(--criteo-enter-stagger));
}

@keyframes criteoSkeletonOut { to { opacity: 0; } }
@keyframes criteoCardFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes criteoCardRise {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: none; }
}

/* No motion: the skeleton is removed outright. The display:none is the safety net —
   if the animation never ran, a ::before at opacity 1 would cover the card forever. */
@media (prefers-reduced-motion: reduce) {
    .criteo-ingrid__card--enter::before { display: none; }
    .criteo-ingrid__card--enter > *,
    .criteo-ingrid__card--enter-new > * { animation: none; }
}
