/* =========================================================
   Cinema Skin FX — atmosphere layer
   Two jobs, both pure enhancement:
     1. Ambient light behind the page, so no band reads flat.
     2. A scene change on every section — the page re-lights
        and the block dissolves in as it arrives.
   Loads after interactive.css. Delete this file and the
   page still reads, works and keeps its brand colours.
   Colours here are the BRAND.md palette only, carried as
   rgba so light can stack without introducing new hues.
   ========================================================= */

/* ---------- Scene colours ----------
   Registering these as <color> is the whole trick: only a
   registered custom property can be interpolated, so the
   scene swap *fades* instead of snapping. Unregistered
   (older engines) it still works — the tint just cuts. */
@property --sc-a    { syntax:"<color>"; inherits:true; initial-value:rgba(250,238,200,.60); }
@property --sc-b    { syntax:"<color>"; inherits:true; initial-value:rgba(198,164,85,.20); }
@property --sc-c    { syntax:"<color>"; inherits:true; initial-value:rgba(250,238,200,.22); }
@property --sc-veil { syntax:"<color>"; inherits:true; initial-value:rgba(253,251,247,.92); }
@property --sc-seam { syntax:"<color>"; inherits:true; initial-value:rgba(250,238,200,.55); }
@property --sc-line { syntax:"<color>"; inherits:true; initial-value:rgba(198,164,85,.55); }

:root{
  --atmos-drift:0px;     /* scroll parallax, written by main.js */
  /* Pointer position over the WINDOW, 0 to 100, eased — also written by
     main.js. Unitless on purpose: the orbs multiply it into pixels and the
     cursor glow multiplies it into percentages, and a single pair of numbers
     is one write per frame instead of one per layer.
     50/50 is the centre, which is why it is the resting value: every layer's
     offset is written against (value - 50), so an unwritten property is
     exactly no offset at all. */
  --atmos-px:50;
  --atmos-py:50;
  --scene-fade:1.5s;
  --scene-dissolve:1.05s;
}

/* The tints live on the root so one attribute swap re-lights
   the whole page — and the transition has to live here too,
   because this is where the value actually changes. */
html{
  background:var(--cream);
  transition:
    --sc-a    var(--scene-fade) var(--ease-soft),
    --sc-b    var(--scene-fade) var(--ease-soft),
    --sc-c    var(--scene-fade) var(--ease-soft),
    --sc-veil var(--scene-fade) var(--ease-soft),
    --sc-seam var(--scene-fade) var(--ease-soft),
    --sc-line var(--scene-fade) var(--ease-soft);
}
/* Cream moves to <html> so the fixed atmosphere can sit
   under the whole document at z-index -1. */
body{background:transparent;}

:root[data-scene="gold"]{
  --sc-a:rgba(250,238,200,.62);
  --sc-b:rgba(198,164,85,.20);
  --sc-c:rgba(250,238,200,.24);
  --sc-veil:rgba(253,251,247,.92);
  --sc-seam:rgba(250,238,200,.42);
  --sc-line:rgba(198,164,85,.55);
}
:root[data-scene="sea"]{
  --sc-a:rgba(148,183,200,.50);
  --sc-b:rgba(250,238,200,.26);
  --sc-c:rgba(234,242,246,.42);
  --sc-veil:rgba(250,252,253,.92);
  --sc-seam:rgba(148,183,200,.34);
  --sc-line:rgba(148,183,200,.62);
}
:root[data-scene="sand"]{
  --sc-a:rgba(184,175,163,.34);
  --sc-b:rgba(250,238,200,.42);
  --sc-c:rgba(247,243,235,.55);
  --sc-veil:rgba(253,251,247,.92);
  --sc-seam:rgba(184,175,163,.26);
  --sc-line:rgba(198,164,85,.45);
}
:root[data-scene="noir"]{
  --sc-a:rgba(198,164,85,.30);
  --sc-b:rgba(148,183,200,.12);
  --sc-c:rgba(198,164,85,.10);
  --sc-veil:rgba(4,4,4,.94);
  --sc-seam:rgba(198,164,85,.24);
  --sc-line:rgba(198,164,85,.70);
}

/* =========================================================
   The ambient layer
   ========================================================= */
.atmos{
  position:fixed;
  inset:-12vh 0;   /* no horizontal bleed — it would widen the page */
  z-index:-1;
  pointer-events:none;
  contain:layout paint style;
}

/* Slow soft light pools. Drift is animated on `translate` and
   `scale` so `transform` stays free for scroll parallax — the
   two never fight over the same property. */
.atmos-orb{
  position:absolute;
  border-radius:50%;
  will-change:transform, translate;
}
.atmos-orb-a{
  width:min(82vw,940px);aspect-ratio:1;
  top:-16%;right:-10%;
  background:radial-gradient(circle at 50% 50%, var(--sc-a) 0%, transparent 68%);
  /* ⚠️ transform, while orbFloatA animates TRANSLATE and SCALE. Those are the
     independent properties and they compose with transform rather than
     replacing it — which is the only reason the scroll drift here was ever
     working, and the only reason the pointer can join it. Put any of this in
     the keyframes and the animation would win, as it has four times already
     elsewhere in this project. */
  transform:translate3d(
    calc((var(--atmos-px) - 50) * .34px),
    calc(var(--atmos-drift) * .55 + (var(--atmos-py) - 50) * .3px),
    0);
  animation:orbFloatA 38s var(--ease-soft) infinite;
}
.atmos-orb-b{
  width:min(70vw,780px);aspect-ratio:1;
  bottom:-14%;left:-12%;
  background:radial-gradient(circle at 50% 50%, var(--sc-b) 0%, transparent 66%);
  /* opposite sign to orb A: they open away from each other as the pointer
     crosses, which is what reads as two lights at different depths rather
     than one sheet sliding */
  transform:translate3d(
    calc((var(--atmos-px) - 50) * -.22px),
    calc(var(--atmos-drift) * -.85 + (var(--atmos-py) - 50) * -.18px),
    0);
  animation:orbFloatB 46s var(--ease-soft) infinite;
}
.atmos-orb-c{
  width:min(56vw,640px);aspect-ratio:1;
  top:38%;left:34%;
  background:radial-gradient(circle at 50% 50%, var(--sc-c) 0%, transparent 70%);
  /* the nearest of the three, so it travels furthest */
  transform:translate3d(
    calc((var(--atmos-px) - 50) * .62px),
    calc(var(--atmos-drift) * 1.15 + (var(--atmos-py) - 50) * .52px),
    0);
  animation:orbFloatC 52s var(--ease-soft) infinite;
}
@keyframes orbFloatA{
  0%,100%{ translate:0 0; scale:1; }
  50%    { translate:-5vw 4vh; scale:1.09; }
}
@keyframes orbFloatB{
  0%,100%{ translate:0 0; scale:1.05; }
  50%    { translate:6vw -5vh; scale:.94; }
}
@keyframes orbFloatC{
  0%,100%{ translate:0 0; scale:.96; }
  33%    { translate:7vw 3vh; scale:1.1; }
  66%    { translate:-4vw -4vh; scale:1; }
}

/* Projector leak: one wide raking beam crossing the frame. */
.atmos-beam{
  position:absolute;
  top:-40%;left:0;
  width:38%;height:180%;
  rotate:11deg;
  background:linear-gradient(90deg, transparent 0%, var(--sc-c) 48%, transparent 100%);
  filter:blur(44px);
  opacity:0;
  animation:beamSweep 54s linear infinite;
}
@keyframes beamSweep{
  0%  { translate:-60vw 0; opacity:0; }
  12% { opacity:.55; }
  88% { opacity:.55; }
  100%{ translate:150vw 0; opacity:0; }
}

/* Faint gate lines — structure without noise. Masked to the
   centre so they never reach the type at the edges. */
.atmos-grid{
  position:absolute;inset:0;
  background-image:linear-gradient(90deg, rgba(184,175,163,.12) 0 1px, transparent 1px 100%);
  background-size:clamp(88px,9vw,140px) 100%;
  -webkit-mask-image:radial-gradient(120% 78% at 50% 32%, #000 0%, transparent 74%);
          mask-image:radial-gradient(120% 78% at 50% 32%, #000 0%, transparent 74%);
  opacity:.42;
  /* the grid is the furthest thing back, so it barely moves — but it does
     move, and that is what stops the ruled lines reading as a fixed pane of
     glass in front of everything else */
  transform:translate3d(
    calc((var(--atmos-px) - 50) * -.09px),
    calc(var(--atmos-drift) * .3 + (var(--atmos-py) - 50) * -.07px),
    0);
}

/* Lens vignette — pulls the eye into the centre of the frame. */
.atmos-vignette{
  position:absolute;inset:0;
  background:radial-gradient(132% 100% at 50% 38%, transparent 44%, rgba(60,52,38,.11) 100%);
}

/* ---------- The light under the cursor ----------
   A pseudo-element rather than a new <span>: .atmos is a fixed, page-wide
   layer that already exists on every section, and adding markup for this
   would mean the effect could be deleted from the HTML while its CSS stayed.

   ⚠️ .is-live, and it comes from main.js. Without it a scriptless page would
   sit with a champagne glow parked dead centre for ever — a light that never
   moves is worse than no light, because it reads as a printing fault. It also
   means the glow only exists where the pointer does: the class is only added
   on a fine pointer with motion allowed. */
.atmos::after{
  content:"";
  position:absolute;inset:0;
  opacity:0;
  transition:opacity .6s var(--ease-soft);
  background:radial-gradient(
    38vmax 38vmax at calc(var(--atmos-px) * 1%) calc(var(--atmos-py) * 1%),
    var(--sc-c) 0%,
    color-mix(in srgb, var(--sc-c) 45%, transparent) 34%,
    transparent 68%);
}
/* ⚠️ Deliberately faint. Five sections already carry their own
   [data-spotlight] glow, which is brighter and sharper because it belongs to
   one block; this is the room light behind all of them, and at any more than
   this the two stack into a hotspot wherever they overlap. */
.atmos.is-live::after{opacity:.55;}

@media (prefers-reduced-motion:reduce){
  .atmos::after{display:none;}
}
:root[data-scene="noir"] .atmos-vignette{
  background:radial-gradient(132% 100% at 50% 38%, transparent 40%, rgba(0,0,0,.5) 100%);
}

/* =========================================================
   Section backgrounds open up so the light reads through
   ========================================================= */
.hero{
  background:
    radial-gradient(120% 95% at 78% 8%, rgba(255,253,248,.82) 0%, rgba(255,253,248,0) 58%),
    linear-gradient(180deg, rgba(255,255,255,.70) 0%, rgba(253,251,247,.34) 100%);
}
.trust{ background:rgba(255,255,255,.60); }
.section{ background:transparent; }

/* The footer stays black and breathes: two slow-shifting pools of gold and
   seafoam instead of one static wash.

   ⚠️ THIS RULE USED TO CARRY .cta-band TOO, and splitting them is the whole
     of "change the band, not the footer". Anything added here from now on
     reaches the footer ONLY. */
.site-footer[data-scene]{
  background-color:var(--black);
  background-image:
    radial-gradient(62% 72% at 18% 108%, rgba(198,164,85,.24) 0%, transparent 62%),
    radial-gradient(54% 62% at 88% -10%, rgba(148,183,200,.11) 0%, transparent 60%);
  background-repeat:no-repeat;
  animation:noirBreathe 26s var(--ease-soft) infinite;
}
@keyframes noirBreathe{
  0%,100%{ background-position:0% 0%, 0% 0%; }
  50%    { background-position:9% -7%, -7% 7%; }
}

/* ⚠️ THE CLOSING BAND IS ESPRESSO, NOT BLACK — asked for explicitly, and
   worth knowing that BRAND.md is the reason it was black: "Built on Black
   for luxury and contrast", "Black is the dominant ground". The footer below
   still is, so black still closes the page.

   ⚠️ IT ENDS IN THE FOOTER'S BLACK, AND THAT IS THE POINT OF THE THIRD
     LAYER. An espresso band meeting a black footer at a hard line reads as a
     mistake before it reads as a design; the band settles back down over its
     last 40% and the join becomes a fade. The layer is listed FIRST so it
     paints over both pools rather than under them.

   ⚠️ ITS OWN KEYFRAMES, and not a nicety. noirBreathe lists TWO
     background-positions because the noir ground has two layers. A
     background-position list shorter than the layer list REPEATS, so with
     three layers the fade would have been handed the first position and
     would have drifted across the seam with the pools. ctaBreathe names all
     three and pins the fade at 0. */
/* ⚠️ STILL EXACTLY THREE LAYERS. ctaBreathe names three background-positions,
   and a position list shorter than the layer list REPEATS — the note above
   this rule is about precisely that. The ground went from espresso to sand,
   the layer COUNT did not move. */
.cta-band[data-scene]{
  background-color:var(--sand);
  background-image:
    linear-gradient(180deg, rgba(253,251,247,0) 58%, rgba(253,251,247,.9) 100%),
    radial-gradient(62% 72% at 18% 108%, rgba(198,164,85,.34) 0%, transparent 62%),
    radial-gradient(54% 62% at 88% -10%, rgba(148,183,200,.14) 0%, transparent 60%);
  background-repeat:no-repeat;
  animation:ctaBreathe 26s var(--ease-soft) infinite;
}
@keyframes ctaBreathe{
  0%,100%{ background-position:0% 0%, 0% 0%,  0% 0%; }
  50%    { background-position:0% 0%, 9% -7%, -7% 7%; }
}

/* =========================================================
   Scene transition — dissolve + seam
   `.has-scenes` is set by main.js, so with JS off nothing is
   ever left veiled.
   ========================================================= */
[data-scene]{
  position:relative;
  isolation:isolate;
}

/* The dissolve. Sits above the section's own background and
   below its content, and clears as the scene arrives. */
[data-scene]::after{
  content:"";
  position:absolute;inset:0;
  z-index:-1;
  pointer-events:none;
  opacity:0;
  background:linear-gradient(180deg,
      var(--sc-veil) 0%,
      color-mix(in srgb, var(--sc-veil) 34%, transparent) 100%);
  transition:opacity var(--scene-dissolve) var(--ease-out);
}
.has-scenes [data-scene]::after{ opacity:1; }
.has-scenes [data-scene].is-scene-in::after{ opacity:0; }

/* The seam: a soft bleed off the previous scene plus a gold
   hairline that draws itself across the cut. Injected by JS
   into every scene but the first. */
.scene-seam{
  position:absolute;
  top:0;left:0;right:0;
  height:clamp(120px,15vw,220px);
  z-index:-1;
  pointer-events:none;
  background-image:
    linear-gradient(90deg, transparent 0%, var(--sc-line) 50%, transparent 100%),
    linear-gradient(180deg, transparent 0%, var(--sc-seam) 28%, transparent 100%);
  background-repeat:no-repeat;
  background-position:top center, top center;
  background-size:0% 1px, 100% 100%;
  transition:background-size 1.15s var(--ease-out) .2s;
}
.is-scene-in > .scene-seam{ background-size:82% 1px, 100% 100%; }

/* =========================================================
   Scene cut — the wipe on an in-page jump
   ========================================================= */
.scene-cut{
  position:fixed;inset:0;
  z-index:250;                /* over the header, under the modals */
  pointer-events:none;
  opacity:0;
  background-image:linear-gradient(104deg,
      transparent 0%,
      var(--sc-line) 44%,
      rgba(255,255,255,.5) 50%,
      var(--sc-line) 56%,
      transparent 100%);
  background-repeat:no-repeat;
  /* Oversized so only a narrow lit band crosses the frame at
     any moment — a raking light, not a white-out. */
  background-size:220% 100%;
  background-position:210% 0;
}
.scene-cut.is-running{ animation:sceneCut .72s var(--ease-soft); }
@keyframes sceneCut{
  0%  { opacity:0;   background-position:210% 0; }
  20% { opacity:.42; }
  70% { opacity:.42; }
  100%{ opacity:0;   background-position:-100% 0; }
}

/* =========================================================
   Reduced motion — the light stays, the movement stops
   ========================================================= */
@media (prefers-reduced-motion:reduce){
  .atmos-orb,
  .atmos-grid,
  .cta-band[data-scene],          /* ctaBreathe */
  .site-footer[data-scene]{ animation:none !important; transform:none !important; }
  .atmos-beam{ display:none; }
  .scene-cut{ display:none; }
  html{ transition:none; }
  [data-scene]::after{ transition:none; }
  .has-scenes [data-scene]::after{ opacity:0; }
  .scene-seam{ background-size:82% 1px, 100% 100%; transition:none; }
}

@media print{
  .atmos,
  .scene-seam,
  .scene-cut{ display:none !important; }
  [data-scene]::after{ display:none !important; }
  .cta-band[data-scene],
  .site-footer[data-scene]{ animation:none; }
}
