/**
 * 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. */
    .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) transitioned to 1fr. That is the
       only way to animate to "however tall the content turns out to be" without
       naming a number — and naming a number is exactly what the reserve did.
       `transform: scale()` was the other candidate and does NOT work here: it is
       not a layout property, so the page below would stay put and the band would
       paint over it instead of pushing it down.

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

       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 may have vertical size, or that much
          IS a reserve. Hence no margin-bottom here: the air below the band is the
          inner's own my-6, which stays INSIDE the box (a grid container does not
          collapse margins with its children) and therefore animates with it.

       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;
    }
    .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;
    }
    /* The band's real reserve, applied only ONCE THE BAND IS IN THE GRID (the
       class is added by earlyPlaceListingBand as it swaps into the server-side
       hole). One product row tall — the same floor the sponsored cells use — so
       the grid keeps its rhythm and the band never sizes itself off the ad. */
    /* Height of the band's row. 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.

       Stepped by breakpoint because a product row does not measure the same
       everywhere: the card is driven by its image, which is square and so grows
       with the column. Measured 2026-08-28 on rendered rows:

         375px  (2 col)  row 361-423      414px  (2 col)  row 380-442
         768px  (2 col)  row 448-515     1024px  (2 col)  row 501-569
        1440px  (4 col)  row 434-502     1920px  (4 col)  row 498-566

       Not monotonic: at 1280 the grid goes from 2 to 4 columns, the cards narrow
       and the row drops again. A single number was only right near 1440. */
    .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: 480px; }
    }
    @media (min-width: 1024px) {
        .criteo-band-reserve, .criteo-slot--reserve-ingrid { min-height: 530px; }
    }
    @media (min-width: 1280px) {
        .criteo-band-reserve, .criteo-slot--reserve-ingrid { min-height: 480px; }
    }
    @media (min-width: 1600px) {
        .criteo-band-reserve, .criteo-slot--reserve-ingrid { min-height: 530px; }
    }
    .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; }
}
