/* ==========================================================================
   Parallax scene 1.0 — a layered scene that leans with the pointer
   --------------------------------------------------------------------------
   Plain CSS. No framework, no build step, no external request.

   Eleven plates sit at different depths inside one shared 3D space. The whole
   space tilts a few degrees toward the pointer, so the plates slide past each
   other at speeds set by their depth alone — no per-layer animation, no
   sprite sheet, no canvas. The script writes exactly two unitless numbers;
   everything visible is decided here.

   Without the script the scene renders at rest, fully composed. That is the
   real fallback, not a degraded one.

   PROPORTIONS
   The module is --pxs-scene-width, the width of the stage. Every length below
   derives from it, depths included, so one variable rescales the drawing AND
   the effect. Values were measured on the published reference (see README,
   "Provenance"); the depths are given as z / stage-width:

     stage ratio      1.36947                (width / height)
     perspective      1.175714 x stage width (= 1200 px at the reference size)
     tilt             10 deg on each axis, 0 on Z
     sky             -0.881786 deep, scaled 2.30, centred at 53% / 58%
     clouds          -0.685833 deep,             centred at 53% / 50%
     skyline         -0.538869 deep,             centred at 53% / 75%
     forest          -0.146964 deep,             centred at 53% / 70%
     figure          -0.048988 deep, leaning 10 deg, centred at 53% / 82%
     glow            +0.054867 near, scaled 1.50, centred at 52% / 55%, 99% wide
     rays            +0.111693 near, leaning 44 deg, centred at 50% / 48%, 71% wide
     ridge-far       +0.127369 near, leaning 15 deg, centred at 50% / 81%, 87% wide
     ridge-mid       +0.166560 near, leaning 15 deg, centred at 50% / 82%, 90% wide
     ridge-near      +0.293929 near, leaning 15 deg, centred at 43% / 88%, 92% wide
     vignette        +0.382107 near, scaled 1.20, 121% x 125% at -10.8% / -12.1%

   A plate at depth z renders at 1 / (1 - z / perspective) of its own size.
   That single line explains every apparent size in the scene: the sky is
   drawn at 230% but lands at 131%, the clouds at 100% land at 63%.

   Order of this file:
     1. Face declarations
     2. Design tokens
     3. Shell and stage
     4. Plates: depth and placement
     5. Artwork
     6. Headline
     7. Light rays
     8. Vignette
     9. Variants
    10. Fallbacks: reduced motion, forced colours, print

   Naming follows BEM: .parallax-scene, .parallax-scene__part,
   .parallax-scene--variant.
   ========================================================================== */

/* ==========================================================================
   1. Face declarations
   --------------------------------------------------------------------------
   One display weight, two subsets, both local. The headline is the only text
   the component draws; latin alone covers French and English, latin-ext is
   fetched only if the page actually uses a character that needs it.
   ========================================================================== */

@font-face {
  font-family: "Bricolage Grotesque";
  src: url("BricolageGrotesque-800-latin.woff2") format("woff2");
  font-weight: 800;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Bricolage Grotesque";
  src: url("BricolageGrotesque-800-latin-ext.woff2") format("woff2");
  font-weight: 800;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ==========================================================================
   2. Design tokens
   ========================================================================== */

.parallax-scene {
  /* --- The module -------------------------------------------------------
     Everything below is a ratio of this length. It defaults to whatever it
     takes to cover the component's own box: at least its width, and at least
     its height once corrected by the stage ratio. Override it with a single
     length — calc(100vw / 1.6) for instance — and the whole scene, effect
     included, comes with it. */
  --pxs-scene-ratio: 1.36947;
  --pxs-scene-width: max(100cqw, calc(100cqh * var(--pxs-scene-ratio)));

  /* Depths are ratios of this. It is the stage width, which is what makes the
     composition identical at every size. Pin it to a fixed length — 1020.66px
     — to get back the reference's behaviour, where the depths were absolute
     pixels and the effect therefore changed with the viewport height. */
  --pxs-depth-unit: var(--pxs-scene-width);
  --pxs-perspective: calc(1.175714 * var(--pxs-depth-unit));

  /* --- Tilt --------------------------------------------------------------
     Read straight from the reference. The script never sees these: it only
     writes --pxs-pointer-x / -y, two numbers between -1 and 1, and the
     multiplication happens below in plain CSS. */
  --pxs-tilt-x: 10deg;
  --pxs-tilt-y: 10deg;
  --pxs-tilt-z: 0deg;

  /* At rest the scene is square to the viewer. The script overwrites these. */
  --pxs-pointer-x: 0;
  --pxs-pointer-y: 0;

  /* --- Colour ------------------------------------------------------------
     The four "-0" variants are the same colours at zero alpha. They are spelt
     out rather than derived, because a gradient that fades to `transparent`
     fades through transparent *black* and leaves a grey halo, and because
     deriving them would rest on relative colour syntax for something that has
     to work everywhere. Change a colour, change its -0 twin. */
  --pxs-backdrop: #0e0c0e; /* the reference's page colour, and the vignette's */
  --pxs-backdrop-0: rgb(14 12 14 / 0);
  --pxs-light: #ebdfcf; /* the warm light: rays, sun, rim of the ridges */
  --pxs-light-0: rgb(235 223 207 / 0);
  --pxs-headline-color: #f4ede2;
  --pxs-rays-opacity: 0.3;

  /* --- Plates ------------------------------------------------------------
     One shared ratio for the plates that are meant to sit on top of one
     another. The reference froze a slightly different height on each of them
     — 1.36624 to 1.37833, an artefact of rounding each plate to a whole pixel
     at design time — which slid their contents up to 1 px apart and stretched
     the bloom by 7 px. See README, "The defect and its fix".

     Each plate reads its own variable first, so putting the reference's
     eleven values back is one line per plate and nothing else. */
  --pxs-plate-ratio: 1.36947;
  --pxs-ratio-sky: var(--pxs-plate-ratio); /* reference: 1.37054 */
  --pxs-ratio-clouds: var(--pxs-plate-ratio); /* reference: 1.36798 */
  --pxs-ratio-skyline: var(--pxs-plate-ratio); /* reference: 1.36748 */
  --pxs-ratio-forest: var(--pxs-plate-ratio); /* reference: 1.36842 */
  --pxs-ratio-figure: var(--pxs-plate-ratio); /* reference: 1.371   */
  --pxs-ratio-glow: var(--pxs-plate-ratio); /* reference: 1.37833 */
  --pxs-ratio-ridge-far: var(--pxs-plate-ratio); /* reference: 1.36624 */
  --pxs-ratio-ridge-mid: var(--pxs-plate-ratio); /* reference: 1.36853 */
  --pxs-ratio-ridge-near: var(--pxs-plate-ratio); /* reference: 1.36837 */
  /* The rays plate stands upright, not flat: its ratio is its own drawing's,
     not a variant of the shared one. */
  --pxs-ratio-rays: 0.739803;

  --pxs-z-sky: -0.881786;
  --pxs-z-clouds: -0.685833;
  --pxs-z-skyline: -0.538869;
  --pxs-z-forest: -0.146964;
  --pxs-z-figure: -0.048988;
  --pxs-z-glow: 0.054867;
  --pxs-z-rays: 0.111693;
  --pxs-z-ridge-far: 0.127369;
  --pxs-z-ridge-mid: 0.166560;
  --pxs-z-ridge-near: 0.293929;
  --pxs-z-vignette: 0.382107;

  /* --- Motion ------------------------------------------------------------
     The tilt itself is sprung by the script, frame by frame, so it has no
     transition. This one covers the fade-in only. */
  --pxs-enter-duration: 900ms;
  --pxs-enter-delay: 0ms;
  --pxs-enter-ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* ==========================================================================
   3. Shell and stage
   --------------------------------------------------------------------------
   The shell is the visible box: it clips, it carries the backdrop colour, and
   it is the container the module measures itself against. The stage is the
   3D space — it is the only element with preserve-3d, because every plate is
   a leaf and giving preserve-3d to a leaf is how pieces of a 3D scene start
   disappearing without an error.
   ========================================================================== */

.parallax-scene {
  /* The component declares its own box model rather than assuming a reset. */
  box-sizing: border-box;
  position: relative;
  display: grid;
  place-items: center;
  overflow: clip;
  background-color: var(--pxs-backdrop);

  /* 100cqw / 100cqh above need a container to measure. Sizing containment
     also means the box never depends on the plates inside it, which is what
     we want: the plates overflow it by design. */
  container-type: size;

  /* Only a fallback: give the component a height and this is ignored. */
  aspect-ratio: var(--pxs-scene-ratio);
}

.parallax-scene *,
.parallax-scene *::before,
.parallax-scene *::after {
  box-sizing: border-box;
}

.parallax-scene__stage {
  position: relative;
  width: var(--pxs-scene-width);
  aspect-ratio: var(--pxs-scene-ratio);

  /* The perspective is a transform function, not the perspective property.
     Combined with preserve-3d below, that is what projects the children's
     depths — and it is exactly how the reference does it. */
  transform: perspective(var(--pxs-perspective)) rotateX(calc(var(--pxs-tilt-x) * var(--pxs-pointer-y) * -1))
    rotateY(calc(var(--pxs-tilt-y) * var(--pxs-pointer-x))) rotateZ(calc(var(--pxs-tilt-z) * var(--pxs-pointer-x)));
  transform-style: preserve-3d;
  will-change: transform;
}

/* The probe. The script cannot read a length out of a custom property —
   getPropertyValue hands back the calc() unevaluated — so it reads the
   resolved width of this box instead. Hidden, but it still occupies a box:
   display:none would resolve to nothing at all. */
.parallax-scene__probe {
  position: absolute;
  top: 0;
  left: 0;
  visibility: hidden;
  pointer-events: none;
  width: var(--pxs-scene-width);
  height: 1px;
}

/* ==========================================================================
   4. Plates: depth and placement
   --------------------------------------------------------------------------
   Every plate is centred on a point given in percentages of the stage, then
   pushed to its depth. The order of the transform matters and is the
   reference's: centre first, then depth, then the plate's own scale or lean.
   ========================================================================== */

.parallax-scene__plate {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  aspect-ratio: var(--pxs-plate-ratio);
  transform: translate(-50%, -50%);

  /* A plate is a leaf: it must stay flat so that a filter on it — the
     brightness on the near ridge, the blur on the rays — cannot pull its
     contents out of the shared space. */
  transform-style: flat;
  pointer-events: none;
}

.parallax-scene__plate > img,
.parallax-scene__plate > svg {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* --- back to front ------------------------------------------------------ */

.parallax-scene__plate--sky {
  aspect-ratio: var(--pxs-ratio-sky);
  top: 58%;
  left: 53%;
  transform: translate(-50%, -50%) translateZ(calc(var(--pxs-z-sky) * var(--pxs-depth-unit))) scale(2.3);
  /* The headline rides on this plate. It stays flat — the reference's sky is
     flat too — because the headline has no depth of its own, and because a
     preserve-3d parent would stop its overlay blend from finding the sky to
     blend with. */
}

.parallax-scene__plate--clouds {
  aspect-ratio: var(--pxs-ratio-clouds);
  top: 50%;
  left: 53%;
  transform: translate(-50%, -50%) translateZ(calc(var(--pxs-z-clouds) * var(--pxs-depth-unit)));
}

.parallax-scene__plate--skyline {
  aspect-ratio: var(--pxs-ratio-skyline);
  top: 75%;
  left: 53%;
  transform: translate(-50%, -50%) translateZ(calc(var(--pxs-z-skyline) * var(--pxs-depth-unit)));
}

.parallax-scene__plate--forest {
  aspect-ratio: var(--pxs-ratio-forest);
  top: 70%;
  left: 53%;
  transform: translate(-50%, -50%) translateZ(calc(var(--pxs-z-forest) * var(--pxs-depth-unit)));
}

.parallax-scene__plate--figure {
  aspect-ratio: var(--pxs-ratio-figure);
  top: 82%;
  left: 53%;
  transform: translate(-50%, -50%) translateZ(calc(var(--pxs-z-figure) * var(--pxs-depth-unit))) rotateX(10deg);
}

.parallax-scene__plate--glow {
  aspect-ratio: var(--pxs-ratio-glow);
  top: 55%;
  left: 52%;
  width: 99%;
  transform: translate(-50%, -50%) translateZ(calc(var(--pxs-z-glow) * var(--pxs-depth-unit))) scale(1.5);
}

.parallax-scene__plate--rays {
  top: 48%;
  left: 50%;
  width: 71%;
  aspect-ratio: var(--pxs-ratio-rays);
  transform: translate(-50%, -50%) translateZ(calc(var(--pxs-z-rays) * var(--pxs-depth-unit))) rotateX(44deg);
}

.parallax-scene__plate--ridge-far {
  aspect-ratio: var(--pxs-ratio-ridge-far);
  top: 81%;
  left: 50%;
  width: 87%;
  transform: translate(-50%, -50%) translateZ(calc(var(--pxs-z-ridge-far) * var(--pxs-depth-unit))) rotateX(15deg);
}

.parallax-scene__plate--ridge-mid {
  aspect-ratio: var(--pxs-ratio-ridge-mid);
  top: 82%;
  left: 50%;
  width: 90%;
  transform: translate(-50%, -50%) translateZ(calc(var(--pxs-z-ridge-mid) * var(--pxs-depth-unit))) rotateX(15deg);
}

.parallax-scene__plate--ridge-near {
  aspect-ratio: var(--pxs-ratio-ridge-near);
  top: 88%;
  left: 43%;
  width: 92%;
  transform: translate(-50%, -50%) translateZ(calc(var(--pxs-z-ridge-near) * var(--pxs-depth-unit))) rotateX(15deg);
  /* The nearest ridge is the darkest: it is the one the light has left. */
  filter: brightness(0.73);
}

/* The vignette is the only plate the reference does not centre: it is pinned
   by its corner, which is why it has no translate(-50%, -50%). */
.parallax-scene__plate--vignette {
  top: -12.09%;
  left: -10.8071%;
  width: 121%;
  height: 125%;
  aspect-ratio: auto;
  transform: translateZ(calc(var(--pxs-z-vignette) * var(--pxs-depth-unit))) scale(1.2);
  background: radial-gradient(
    41% 43% at 50% 48.6%,
    var(--pxs-backdrop-0) 46.7677%,
    var(--pxs-backdrop) 70.9688%
  );
}

/* ==========================================================================
   5. Artwork
   --------------------------------------------------------------------------
   Each plate holds one SVG of its own. They are drawings, not photographs:
   they weigh a few kilobytes, they stay sharp at any size, and they are text
   you can open and edit. Swap any <img src> and the geometry above still
   holds — that is the whole point of keeping depth and drawing apart.
   ========================================================================== */

.parallax-scene__plate--sky > img,
.parallax-scene__plate--clouds > img {
  object-fit: cover;
}

.parallax-scene__plate--glow {
  /* The bloom is a gradient, not a file: nothing to download and no edge to
     betray it at any size. */
  /* Centred on 51% of the height of the plate, which lands on the sun in the
     sky — 56.5% of the scene. The calculation is in _mesures/05-placement.mjs:
     the plate overhangs the scene by 22.6% at the top, which has to be taken
     into account. */
  background: radial-gradient(
    closest-side circle at 50% 51%,
    rgb(235 223 207 / 0.3) 0%,
    rgb(235 223 207 / 0.13) 34%,
    rgb(235 223 207 / 0.04) 58%,
    var(--pxs-light-0) 76%
  );
}

/* ==========================================================================
   6. Headline
   --------------------------------------------------------------------------
   The reference bakes its headline into a 2962 x 880 PNG. Here it is real
   text: it can be read aloud, selected, translated and restyled, and it costs
   nothing to download. The overlay blend is what the reference used, and it
   is what makes the word sit *in* the sky rather than on top of it.
   ========================================================================== */

.parallax-scene__headline {
  position: absolute;
  top: 20%;
  left: 49%;
  width: 206%;
  margin: 0; /* a heading keeps its margin: the component assumes no reset */
  transform: translate(-50%, -50%) scale(0.5);

  font-family: "Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif;
  font-weight: 800;
  /* The chain that decides the apparent size: this value, halved by the
     headline's own scale, multiplied by 2.3 by the sky plate, then by 0.5714
     by the perspective — 0.657 in all. At 0.26 the cap height lands at 12.3%
     of the stage width, which is what the reference's wordmark measured. */
  font-size: calc(0.26 * var(--pxs-scene-width));
  line-height: 0.9;
  letter-spacing: -0.03em;
  text-align: center;
  text-transform: uppercase;
  text-wrap: balance;
  color: var(--pxs-headline-color);
  mix-blend-mode: overlay;
}

/* ==========================================================================
   7. Light rays
   --------------------------------------------------------------------------
   Three plain rectangles, laid back 44 degrees, masked so they fade out
   before their ends and blurred until the edges are gone. The reference does
   exactly this — the softness is a blur on a hard shape, not a soft image.

   The blur sits on the inner box, never on the plate: a filter flattens the
   element it is on, and on the plate it would take the whole beam out of the
   shared 3D space.
   ========================================================================== */

.parallax-scene__beams {
  position: absolute;
  top: 50.0703%;
  left: 0.285172%;
  width: 99%;
  /* The reference pins this box at a flat 2203 px, which is 2.15845 stage
     widths at the size it was measured. Kept as that ratio, the beam holds
     its shape at every size instead of stretching with the viewport. */
  height: calc(2.15845 * var(--pxs-scene-width));
  transform: translateY(-50%);
  opacity: var(--pxs-rays-opacity);
  /* On the inner box, never on the plate: a filter flattens what it touches,
     and on the plate it would take the beams out of the shared 3D space. */
  filter: blur(calc(0.0105 * var(--pxs-scene-width)));
  /* The fade along the beam. The reference had a second mask on top of this
     one — a bitmap that carved the beams into shape — which is artwork rather
     than construction; here each beam carries its own falloff instead, below.
     Without one or the other the beams read as three opaque slabs. */
  mask-image: linear-gradient(0deg, rgb(0 0 0 / 0) 30%, rgb(0 0 0) 52%, rgb(0 0 0) 76%, rgb(0 0 0 / 0) 97%);
}

.parallax-scene__beam {
  position: absolute;
  transform: translateY(-50%);
  /* The three boxes are the reference's, and side by side they span the whole
     plate — which is why the reference needed a bitmap on top to carve them.
     Here the carving is this gradient: each wide box holds one narrow shaft
     at its centre and is transparent everywhere else. */
  background: linear-gradient(
    90deg,
    var(--pxs-light-0) 22%,
    var(--pxs-light) 48%,
    var(--pxs-light) 52%,
    var(--pxs-light-0) 78%
  );
}

.parallax-scene__beam--1 {
  top: 50.1135%;
  left: 34.1463%;
  width: 32%;
  height: 108.943%; /* 2400 / 2203 */
}

.parallax-scene__beam--2 {
  top: 18.2932%;
  left: -1.26829%;
  width: 38%;
  height: 54.6075%; /* 1203 / 2203 */
}

.parallax-scene__beam--3 {
  top: 18.2932%;
  left: 60.8293%;
  width: 42%;
  height: 54.6075%;
}

/* ==========================================================================
   8. Entrance
   --------------------------------------------------------------------------
   The scene fades up once. The keyframe describes its start only, with
   fill-mode backwards, so the finished state stays with the rule above and
   nothing here overwrites an opacity or a transform set elsewhere.
   ========================================================================== */

@keyframes pxs-enter {
  from {
    opacity: 0;
    scale: 1.04;
  }
}

.parallax-scene__stage {
  animation: pxs-enter var(--pxs-enter-duration) var(--pxs-enter-ease) var(--pxs-enter-delay) backwards;
}

/* ==========================================================================
   9. Variants
   ========================================================================== */

/* Calmer: half the lean, for a scene sitting behind real content. */
.parallax-scene--subtle {
  --pxs-tilt-x: 5deg;
  --pxs-tilt-y: 5deg;
}

/* Flat: the plates keep their places, the space stops leaning. Useful when
   the scene is decoration under a form and any movement would be noise. */
.parallax-scene--still {
  --pxs-tilt-x: 0deg;
  --pxs-tilt-y: 0deg;
  --pxs-tilt-z: 0deg;
}

/* ==========================================================================
   10. Fallbacks
   ========================================================================== */

/* Reduced motion: no lean and no fade. A delay is movement spread over time,
   so it goes to zero too. What is a picture rather than a movement — the
   depth itself, the vignette, the rays — stays exactly as it is. */
@media (prefers-reduced-motion: reduce) {
  .parallax-scene {
    --pxs-tilt-x: 0deg;
    --pxs-tilt-y: 0deg;
    --pxs-tilt-z: 0deg;
    --pxs-enter-duration: 1ms;
    --pxs-enter-delay: 0ms;
  }

  .parallax-scene__stage {
    animation: none;
  }
}

/* Forced colours: the drawing carries no meaning, and the blend modes and the
   vignette would eat the headline. Everything decorative steps back so the
   one piece of text stays readable on the system's own colours. */
@media (forced-colors: active) {
  .parallax-scene {
    background: Canvas;
    forced-color-adjust: none;
  }

  .parallax-scene__plate {
    display: none;
  }

  .parallax-scene__headline {
    position: static;
    width: auto;
    transform: none;
    mix-blend-mode: normal;
    color: CanvasText;
    font-size: calc(0.1 * var(--pxs-scene-width));
  }
}

@media print {
  .parallax-scene__stage {
    animation: none;
  }
}
