/* =========================================================================
   BDS PDP — CLS fix (v2)                                 2026-09-27
   app/design/frontend/Magento/luma_child/web/css/product-cls-fix.css
   =========================================================================

   WHAT THIS FILE FIXES (all values measured on the live PDP, not estimated)

   1) THE GALLERY TRANSIENT  — the biggest single shift, 0.28 CLS on mobile
      widths and 0.048 on desktop widths.

      WHY IT HAPPENED
      Magento server-renders a static <img class="gallery-placeholder__image">
      inside .gallery-placeholder for the no-JS case. Fotorama then builds its
      own .fotorama-item *next to that image*, so for one frame both exist and
      both are in flow. Measured mid-build state (1280px, t=529ms):

        .gallery-placeholder            y463  h739   (!!)
          img.gallery-placeholder__image y463 h239   <- still in flow
          div.fotorama-item              y702 h500   <- min-height floor, in flow
            div.fotorama__nav-wrap       y702 h110   <- nav is the only in-flow child
                                                         because the stage is
                                                         position:absolute at this point

      The column is 739px tall for ~25ms, then Fotorama finishes, the static
      image is removed, the stage becomes position:relative and the column
      collapses to its real 610px -> everything below moves 239px and the nav
      rail jumps 500px from the top of the item to the bottom. Same mechanism at
      412px (image 287px + item 412px = 699px -> 462px).

      THE FIX
      - the static image is taken out of flow, so it can never add height;
      - the container gets a DEFINITE height (the final one), so no build order,
        no image, and no Fotorama internal state can change the page height;
      - the thumbnail/dots rail is positioned absolutely at the bottom, so it is
        already in its final place the instant it is created and never moves.

      Verified final geometry on the live PDP (must stay identical after this
      file ships — if it changes, the reservation is wrong):
        >= 1025px : stage 500 + rail 110            = 610   (rail flush, gap 0)
        <= 1024px : stage 100vw + 10 gap + 30 rail + 10 = 100vw + 50 (dots)

   2) THE WEBFONT SWAP — 0.026 shift on desktop widths.
      body inherits Bootstrap's --bs-font-sans-serif, whose first family is
      Nunito Sans. The font is inlined by Cloudflare Fonts with font-display:
      swap, so text paints in the fallback and reflows when Nunito arrives
      (measured 621ms vs FCP 620ms). Nunito's metrics differ from Arial's:
        Nunito Sans  box height 136px @100px   (ascent 101 + descent 35)
        Arial        box height 115px @100px   (ascent  91 + descent 21 + gap 3.3)
        widths       Nunito is ~2.5% narrower on average (per-string spread
                     0.93x .. 1.04x, so no single value is perfect)
      A metric-matched fallback face is inserted right after 'Nunito Sans', so
      the pre-swap text already occupies Nunito's box. Measured on the live DOM:
      the fallback resolves to a 136px box at 100px, i.e. pixel-identical.

   3) NO REBUILD/NO LESS COMPILE NEEDED. This is a plain CSS file that Magento
      already loads LAST in the PDP head, so it wins the cascade against
      bootstrap-custom.css (same :root specificity, later in document order).
   ========================================================================= */

/* -------------------------------------------------------------------------
   1. GALLERY — definite geometry, JS-order independent
   ------------------------------------------------------------------------- */

/* The containing block for the static image, and the element whose height is
   now fixed. It exists before any JS runs, which is why the reservation lives
   here and not on .fotorama-* (those do not exist until Fotorama has run). */
   .gallery-placeholder {
    position: relative;
}

/* The server-rendered no-JS image: keep it visible, but never let it contribute
   to the column height. It renders in exactly the same place it did before
   (top-left, full width) and is painted over by Fotorama's stage. */
.gallery-placeholder__image {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: auto;
}

/* The JS-built elements inherit the definite height of the placeholder above;
   min-height is zeroed so no Fotorama build-order state can change the box.
   Deliberately single-class selectors: Fotorama's own fullscreen rules are two
   classes (.fotorama--fullscreen ...) and must keep winning over this file. */
.fotorama-item,
.fotorama__wrap {
    height: 100%;
    min-height: 0;
}

/* -------------------------------------------------------------------------
   DESKTOP (>=1025px) — stage floor 500 + thumbnail rail 110 = 610
   ------------------------------------------------------------------------- */
@media (min-width: 1025px) {

    .gallery-placeholder {
        min-width: 250px;
        height: 610px;          /* MUST equal stage floor + rail = 500 + 110 */
    }

    /* The stage floor is what makes the final height a constant for every image
       in the catalogue (all source images are landscape, see the caveat at the
       end of this file). Keep it - it is also what the reservation relies on.
       Applied to the stage ONLY: putting it on .fotorama-item as well makes the
       item grow past the placeholder (measured: item 768px inside an 818px
       placeholder at 768px viewport, which drags the rail out of position). */
    .fotorama__stage {
        min-height: 500px;
    }

    /* The rail is in its final position from the instant it exists: absolutely
       positioned at the bottom of the wrap, so Fotorama flipping the stage from
       absolute to relative cannot move it (that move WAS the 0.048 entry). */
    .fotorama__nav-wrap {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 110px;
        min-height: 110px;
        margin: 0;
    }

    /* .fotorama__nav measures 114px - a 4px overflow of the 110px wrap caused by
       Fotorama's injected padding:2px with box-sizing:content-box. It does not
       contribute to layout; do not "correct" this to 114px or a 4px shift
       appears in the opposite direction. */
    .fotorama__nav {
        min-height: 110px;
    }

    .fotorama__nav__frame,
    .fotorama__thumb {
        width: 88px;
        height: 110px;
    }

    .product-info-main .product-add-form {
        min-height: 270px;
    }

    .page-layout-1column .product-info-main {
        min-width: 500px;
    }
}

/* -------------------------------------------------------------------------
   MOBILE BAND (<=1024px) — stage 100vw + 10 gap + 30 dots + 10 = 100vw + 50
   Measured identical at 412 / 768 / 900 / 1024px: stage == 100vw, dot rail 30px,
   10px above and below. Fotorama switches thumbs->dots at exactly 1024px, which
   is why this band matches the media query breakpoint.
   ------------------------------------------------------------------------- */
@media (max-width: 1024px) {

    .gallery-placeholder {
        height: calc(100vw + 50px);
    }

    /* 100vw, NOT min(100vw, 900px): the live stage also resolves to 100vw at
       900-1024px because min-height beats the old max-height:900px. Matching the
       real behaviour is what makes the reservation exact. Stage only - see the
       note in the desktop block. */
    .fotorama__stage {
        min-height: 100vw;
    }

    .fotorama__nav-wrap {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 10px;
        height: 30px;
        min-height: 30px;
        margin: 0;
    }

    .fotorama__nav--dots,
    .fotorama__nav__shaft--dots {
        height: 30px;
        min-height: 30px;
        margin: 0;
    }

    .fotorama__nav__frame--dot {
        width: 18px;
        height: 18px;
    }
}

/* Let Fotorama control position/transform of the stage image. Overriding those
   makes its JS fight the stylesheet and produce a second shift. */
.fotorama__stage__frame .fotorama__img {
    max-width: 100%;
    max-height: 100%;
}

/* =========================================================================
   2. FONT — metric-matched fallback, so the swap cannot reflow the page
   =========================================================================
   Numbers measured on the live PDP against the real Nunito Sans woff2:
     box height  Nunito 136px @100px  ->  ascent 101 + descent 35 + gap 0
     Arial       box height 115px @100px (ascent 91 + descent 21 + gap 3.3)
     widths      Nunito/Arial average ratio 0.975
   size-adjust is applied to the overrides as well, so the overrides are divided
   by it (101 / 0.975 = 103.59, 35 / 0.975 = 35.90) -> measured result: the
   fallback box is exactly 136px @100px, i.e. identical to Nunito.
   ========================================================================= */
@font-face {
    font-family: 'Nunito Sans Fallback';
    src: local('Arial'), local('Liberation Sans'), local('Helvetica Neue'), local('Helvetica');
    size-adjust: 97.5%;
    ascent-override: 103.59%;
    descent-override: 35.90%;
    line-gap-override: 0%;
}

/* The whole site's font stack comes from this one Bootstrap variable. This file
   is loaded after bootstrap-custom.css, so the same :root specificity wins. */
:root {
    --bs-font-sans-serif: "Nunito Sans", "Nunito Sans Fallback", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}

/* The theme hard-codes the same stack in a few places instead of using the
   variable; keep them in sync (source: web/css/source/_extend.less). */
.footer-link-class a {
    font-family: "Nunito Sans", "Nunito Sans Fallback", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}

/* =========================================================================
   KNOWN LIMIT OF THE FIXED NUMBER — read before changing the stage floor
   The reservation works because the stage height is FLOORED, so the final
   height is constant for every image. Two ways it can break:
     - an image taller than h/w = 1.567 (a portrait): at the 319px-wide desktop
       stage that exceeds the 500px floor and the stage grows past its box;
     - any future CSS change to the 500px stage floor or the 110px rail.
   Every source image sampled on live PDPs is landscape (700x524 .. 700x661,
   max h/w 0.944), so the floor always dominates. The permanent fix is to stop
   reserving a guessed number: emit images[0].w / images[0].h from
   Magento_Catalog::product/view/gallery.phtml into an aspect-ratio and drop the
   fixed heights. Do that instead of raising numbers if a portrait ever ships.
   ========================================================================= */
