/* Menu background treatment.
 *
 * The fal.ai video the plan calls for is deferred to last, so this has to carry
 * the menu's identity on its own — and it should, because a CSS treatment costs
 * nothing to load and cannot fail to fetch. Three layers:
 *
 *   1. a warm low sun bleeding in from the upper right, which is Rust's light,
 *   2. a slow drift of haze, so the screen is never completely static,
 *   3. a fine scanline/grain veil that ties the menu to the in-game post stack.
 *
 * All of it is behind `prefers-reduced-motion`, and none of it accepts pointer
 * events.
 */

.menu-bg {
  position: absolute; inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: -1;
}

/* the sun */
.menu-bg > span:nth-child(1) {
  position: absolute; top: -30vh; right: -18vw;
  width: 92vw; height: 92vw;
  background: radial-gradient(circle,
    rgba(255, 214, 140, 0.30) 0%,
    rgba(226, 154, 72, 0.16) 32%,
    rgba(120, 82, 46, 0.06) 56%,
    transparent 72%);
  filter: blur(12px);
}

/* the haze — two counter-drifting bands, so nothing has an obvious period */
.menu-bg > span:nth-child(2) {
  position: absolute; inset: -20%;
  background:
    radial-gradient(60% 40% at 20% 70%, rgba(180, 172, 150, 0.10), transparent 70%),
    radial-gradient(50% 34% at 76% 32%, rgba(214, 176, 118, 0.10), transparent 70%),
    radial-gradient(70% 50% at 50% 100%, rgba(30, 26, 22, 0.55), transparent 72%);
  animation: hazeDrift 68s ease-in-out infinite alternate;
}

/* the veil: scanlines plus a hint of vignette, matching the in-game composite */
.menu-bg > span:nth-child(3) {
  position: absolute; inset: 0;
  background:
    repeating-linear-gradient(0deg,
      rgba(255, 255, 255, 0.018) 0 1px,
      transparent 1px 3px),
    radial-gradient(120% 100% at 50% 40%, transparent 40%, rgba(4, 6, 8, 0.72) 100%);
  mix-blend-mode: screen;
}

/* A slow horizon line, the one hard edge in the composition. It reads as the
 * skyline of somewhere rather than as a gradient, which is the whole difference
 * between a background and a wash. */
.menu-bg::after {
  content: '';
  position: absolute; left: -5%; right: -5%; top: 62%;
  height: 34vh;
  background: linear-gradient(180deg, rgba(28, 24, 20, 0.0) 0%, rgba(20, 17, 14, 0.72) 22%, rgba(11, 13, 15, 0.96) 100%);
  clip-path: polygon(
    0% 26%, 6% 24%, 9% 12%, 12% 24%, 22% 22%, 26% 30%, 34% 27%,
    41% 8%, 44% 27%, 55% 25%, 60% 33%, 68% 29%, 74% 16%, 78% 30%,
    88% 27%, 94% 31%, 100% 28%, 100% 100%, 0% 100%);
}

@keyframes hazeDrift {
  0%   { transform: translate3d(-2%, 1%, 0) scale(1.02); }
  100% { transform: translate3d(3%, -2%, 0) scale(1.06); }
}

@media (prefers-reduced-motion: reduce) {
  .menu-bg > span:nth-child(2) { animation: none; }
}

/* ==========================================================================
   THE PLATE, AND WHAT USED TO BE HERE
   ==========================================================================
   This section carried a poster-still and looping-clip tier — `.menu-bg-still`,
   `.menu-bg-video`, promoted by `.has-still` and `.is-live` from js/menu-bg.js.
   All of it is deleted. js/menu-bg.js was imported by nothing and has since been
   removed from the tree, index.html never carried the <img> or the <video>, and
   the live in-engine backdrop (js/cinematics/menu.js) supersedes the idea
   entirely: it renders the yard rather than photographing it.

   Deleted rather than kept "in case", because a rule for an element nothing
   creates reads to the next person as a layer that is present. That is not
   hypothetical here — this file's own paint-order comment claimed a scrim at
   z-index 2 and js/cinematics/menu.js twice said its canvases sat under it, while
   nothing created the element, so the menu type sat on an undarkened render.
   `scripts/validate-ui-contract.mjs` check 7 now fails on exactly this, and it is
   what caught these four the moment js/menu-bg.js went away.

   Paint order, with z-index rather than DOM order, because the painted layers are
   addressed by :nth-child and cannot be reordered:

     0  the painted scene   — sun, haze, skyline. Cannot fail to fetch.
     1  the poster      }    a rendered frame of the SELECTED map, at menu shot 0,
                        }    t = 0. Mounted first in DOM order so the live plate
                        }    paints over it. js/cinematics/poster.js.
     1  the live plate  }    the three share 1, in DOM order poster -> plate ->
     1  the dissolve    }    dissolve, so each paints over the last and all of them
                        }    under the scrim
     2  the scrim           — keeps the left-hand menu column legible. Styled
                              here; the ELEMENT's lifetime belongs to the backdrop
                              it exists to darken, so js/cinematics/menu.js creates
                              and disposes it with the plate.
     3  the grain veil      — span:nth-child(3), stays on top so the menu and the
                              in-game post stack share a texture

   NOTE menu.js also sets `scrim.style.zIndex = '2'` inline, which beats the rule
   below. The two agree today; if this file's 2 is ever changed on its own it will
   silently do nothing. Change both, or neither.
   ========================================================================== */

/* The page floor: whatever fails, this remains. `.menu-bg` is z-index:-1, which
   paints it above #menu's own opaque background but below every bit of menu
   content — that works only because .screen is position:fixed with a z-index and
   therefore forms a stacking context. Do not remove either. */
.menu-bg { background-color: var(--s0); contain: layout paint; }

.menu-bg > span:nth-child(1) { z-index: 0; }
.menu-bg > span:nth-child(2) { z-index: 0; }
.menu-bg::after { z-index: 0; }
.menu-bg > span:nth-child(3) { z-index: 3; }

/* ==========================================================================
   THE STAND-DOWN, AND THE POSTER LAYER
   ==========================================================================
   These were injected from js/cinematics/menu-boot.js as a `<style>` element,
   because this file once carried another session's uncommitted work and three
   rules did not justify a merge conflict. They live here now, which is where
   docs/CINEMATICS.md §7e always said they belonged, and `STYLE`/`injectStyle` are
   gone from menu-boot.js rather than left behind to double-apply.

   `.has-poster` and `.has-live` are the exact descendants of the `.has-still`
   rules that used to sit at this point in the file, and they say the same thing:
   once a real picture of the map is on screen, the hand-painted sun and haze
   underneath it are not a fallback any more, they are a second, worse version of
   the same scene. They stand down. THE GRAIN VEIL DOES NOT — it is shared with the
   in-game composite and it is what makes the menu and the game look like one
   program.

   Both classes are added ONLY once the layer they name has demonstrably arrived:
   `.has-poster` on the image's `decode()` resolving, `.has-live` on a frame having
   actually been drawn. A class that goes on optimistically would blank the
   fallback in exactly the situation the fallback exists for. */
.menu-bg.has-poster > span:nth-child(1),
.menu-bg.has-poster > span:nth-child(2),
.menu-bg.has-live > span:nth-child(1),
.menu-bg.has-live > span:nth-child(2) { opacity: 0; transition: opacity 600ms linear; }
.menu-bg.has-poster::after,
.menu-bg.has-live::after { opacity: 0; transition: opacity 600ms linear; }

/* The plate is a render of the map, not a photograph, so it needs no desaturation
   trim the way the poster still did. It sits under the scrim, which
   js/cinematics/menu.js CREATES (nothing else did) and this file styles, and under
   the grain veil; menu.js sets every one of those z-indices itself. */
.menu-bg .menu-bg-live { will-change: opacity; }

/* The cross-dissolve canvas: the outgoing shot's last frame, held over the incoming
   one and faded out once per cut. Everything about its size and its opacity is set
   by js/cinematics/menu.js — this is only the compositor hint, and it is here rather
   than inline because a `will-change` written per frame is a promotion churned per
   frame. It had no rule at all while every other layer in this stack had one, which
   is the shape `validate-cinematic-menu.mjs` check 9 exists to catch. */
.menu-bg .menu-bg-dissolve { will-change: opacity; }

/* TIER 1 — the poster.
   `object-fit: cover` because the poster is rendered at one 16:9 aspect and the
   menu is whatever the window is; cropping the yard is correct and letterboxing it
   would put two black bars where the composition's whole job is to have none.
   `object-position` biases the crop toward the right of the frame, which is the
   third of the picture the scrim leaves bright and the third the menu column does
   not cover.
   OPACITY 1, WITH NO FADE AND NO JAVASCRIPT IN THE PATH — and both halves of that
   were measured rather than chosen.

   The first version started at opacity 0 and had js/cinematics/poster.js raise it
   on the image's `load` event. Two separate faults, in three runs:

     * the paint became gated on a JS callback. The bytes were on the wire at 65 ms
       and `load` did not dispatch until 788 ms, because the main thread was busy
       evaluating the game's own module graph — 723 ms of a picture the browser
       already had and was not allowed to show.
     * a CSS transition cannot be depended on for VISIBILITY. The document timeline
       is frozen in a hidden tab, so computed opacity sat at 0 for an entire
       measurement run while the inline value read 1.

   An `<img>` with no bytes yet paints nothing at all — no box, no border, no alt
   text, because `alt` is empty — so there is nothing to hide and nothing to fade
   from. The compositor puts the poster up the instant it can decode it, with no
   main-thread involvement whatsoever. `.has-poster` still stands the painted layers
   down on the `load` event, but that is cosmetic and nothing waits on it. */
.menu-bg .menu-bg-poster {
  position: absolute; inset: 0;
  z-index: 1;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 62% 50%;
  opacity: 1;
  pointer-events: none;
  border: 0;
}

/* Sits over the plate, and its geometry is taken from the menu grid in
   style.css rather than guessed:
     .menu-grid is min(1320px) centred, columns 0.95fr / 0.65fr with a big gap,
     so the brand and the button column occupy roughly the left 10%-52% of the
     viewport, and .menu-setup sits around 61%-90%.
   So the plateau is held heavy out to ~52% and only then released. It does not
   need to come back up on the right: .menu-setup carries its own
   rgba(11,14,16,0.82) panel and a backdrop blur, and .legal carries its own bar,
   so darkening those regions again would only mute the one part of the plate
   that is allowed to be a picture — the derrick, the light and the sky.

   Which is the whole trick: near-black under the column you read, and depth and
   low sun in the third you do not. Light where nothing competes with it is what
   reads as expensive. */
.menu-bg .menu-bg-scrim {
  position: absolute; inset: 0;
  z-index: 2;
  background:
    linear-gradient(
      90deg,
      rgba(6, 8, 10, 0.92) 0%,
      rgba(6, 8, 10, 0.88) 30%,
      rgba(6, 8, 10, 0.78) 44%,
      rgba(6, 8, 10, 0.52) 56%,
      rgba(6, 8, 10, 0.26) 68%,
      rgba(6, 8, 10, 0.18) 84%,
      rgba(6, 8, 10, 0.30) 100%
    ),
    linear-gradient(
      180deg,
      rgba(6, 8, 10, 0.52) 0%,
      rgba(6, 8, 10, 0.14) 22%,
      rgba(6, 8, 10, 0.20) 60%,
      rgba(4, 6, 9, 0.82) 100%
    ),
    /* cools the plate toward the in-game steel-blue, over the open right side only */
    radial-gradient(ellipse at 72% 46%, rgba(26, 42, 66, 0.16), transparent 60%);
}

/* THE SAME PLATE, IN THE LOBBY, WHERE THE GEOMETRY IS DIFFERENT.
 *
 * `js/cinematics/menu-boot.js` moves the one backdrop between `#menu .menu-bg`
 * and `#lobby .menu-bg` as the screens change. The scrim above is tuned to the
 * MENU's split — near-black under the left-hand column you read, open on the
 * right third — and the lobby is three rails across the whole width, so that
 * gradient darkens the deployment rail's side of the picture and leaves the
 * kit rail's side bright. Wrong third, both ways.
 *
 * So inside the lobby this element stands down to almost nothing and the lobby's
 * own vertical scrim (`#lobby > .menu-bg::before` in style.css) is the single
 * source of legibility there. It stands down rather than being removed, because
 * `createMenuBackdrop` READS THIS RULE BACK — `getComputedStyle(scrim)
 * .backgroundImage === 'none'` makes it inject its own left-heavy fallback and
 * log a warning, which is exactly the gradient being avoided. A declared
 * near-transparent wash satisfies the read-back and paints nothing.
 *
 * The lobby's rails carry their own 0.72 surface and a blur, so the plate under
 * them is already scrimmed twice; a third pass is what made the first version of
 * this screen a black rectangle with a map somewhere behind it. */
#lobby .menu-bg .menu-bg-scrim {
  background: linear-gradient(180deg, rgba(6, 8, 10, 0.06), rgba(6, 8, 10, 0.10));
}

/* Narrow viewports crop hard into the centre of the yard, and the menu grid
   stacks, so the scrim has to work top-to-bottom instead of left-to-right. */
@media (max-width: 760px) {
  .menu-bg .menu-bg-scrim {
    background:
      linear-gradient(180deg, rgba(6, 8, 10, 0.80) 0%, rgba(6, 8, 10, 0.52) 30%, rgba(6, 8, 10, 0.50) 62%, rgba(4, 6, 9, 0.92) 100%),
      radial-gradient(ellipse at 50% 42%, rgba(26, 42, 66, 0.12), transparent 66%);
  }
}
