/* ===========================================================================
   The arrival sequence — night platform, the Wedding Express pulling in, and
   the personalised ticket. Ported from the prototype; the only structural
   change is that this is now an overlay that hands off to the site below it.

   Two rules everything else depends on:
   - Nothing that moves is driven by swapping animation classes. Swapping a
     CSS animation restarts it at frame zero, which is what made the wheels
     spin backwards and the track stutter. Wheels, scenery and blur all read
     the train's real measured speed from one rAF loop instead.
   - The sky stays night for the whole arrival. Daylight only arrives after
     the guest taps to board.
   =========================================================================== */

/* height:100svh, not inset:0 — the gate below is 100svh, and on a phone a
   fixed inset:0 box is the LARGE viewport. Matching the unit is what keeps
   the names from shifting when the overlay hands over. */
#opening{position:fixed;left:0;right:0;top:0;height:100svh;overflow:hidden;z-index:100;
  background:var(--deep);transition:opacity 1.1s ease}
/* Once the guest has boarded this overlay must stop existing as far as the
   mouse is concerned. pointer-events:none on the parent alone is NOT enough:
   .ticket, .board and .next each set pointer-events:auto, and a descendant
   opting back in beats the parent's none — those invisible panels are
   inset:0 at z-index 100, so they swallowed every click on the whole site.
   visibility:hidden takes the subtree out of hit-testing outright, and the
   delayed transition lets it finish fading first. */
#opening.gone{opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity 1.1s ease,visibility 0s linear 1.1s}
#opening.gone *{pointer-events:none !important}
body.intro{overflow:hidden}

/* ---------- sky ---------- */
#opening .sky{position:absolute;inset:0;transition:opacity 3.4s ease}
#opening .sky-night{opacity:1;background:linear-gradient(#03060f 0%,#070d1c 42%,#0d1222 72%,#141322 100%)}
#opening .sky-dawn{opacity:0;background:linear-gradient(#0d1a3c 0%,#28315e 38%,#5b4568 68%,#8a5560 100%)}
/* identical to .skyfest on the gate — this is the sky the handover lands on */
#opening .sky-sun{opacity:0;background:linear-gradient(#1b2a63 0%,#4b3a86 18%,#8e4a86 38%,
  #d1557a 56%,#f07d4e 74%,#ffb659 88%,#ffe6a8 100%)}
#opening.boarded .sky-night{opacity:0}
#opening.boarded .sky-dawn{opacity:.35}
#opening.boarded .sky-sun{opacity:1}

#opening .stars{position:absolute;inset:0;transition:opacity 3s ease}
#opening.boarded .stars{opacity:0}
#opening .star{position:absolute;width:2px;height:2px;background:#fff;border-radius:50%;
  animation:tw 4s ease-in-out infinite}
@keyframes tw{0%,100%{opacity:.2}50%{opacity:.9}}

#opening .moon{position:absolute;top:11%;right:14%;width:54px;height:54px;border-radius:50%;
  background:radial-gradient(circle at 36% 34%,#fffbf0,#e6dcc4 62%,#c9bfa4);
  box-shadow:0 0 52px 14px rgba(255,248,222,.16);transition:opacity 3s ease,transform 4.5s ease}
#opening.boarded .moon{opacity:0;transform:translateY(-70px)}
#opening .sun{position:absolute;left:16%;bottom:14%;width:120px;height:120px;border-radius:50%;
  background:radial-gradient(circle,#fff3c4 0%,#ffc768 42%,#ff9a3c 72%,rgba(255,138,60,0) 78%);
  transform:translateY(130px) scale(.7);opacity:0;
  transition:transform 5s cubic-bezier(.3,.7,.3,1),opacity 3s ease}
#opening.boarded .sun{opacity:1;transform:translateY(-10px) scale(1)}
#opening .glow{position:absolute;left:0;right:0;bottom:16%;height:34%;opacity:0;pointer-events:none;
  background:linear-gradient(0deg,rgba(255,168,84,.42),rgba(255,168,84,0));transition:opacity 3.2s ease}
#opening.boarded .glow{opacity:1}

/* ---------- scenery ---------- */
#opening .layer{position:absolute;left:0;bottom:0;width:300%;height:100%}
#opening .hills{bottom:21%;height:30%;opacity:.55}
#opening .hills svg{width:33.333%;height:100%;display:block;float:left}
#opening .palms{bottom:20%;height:19%;opacity:.85}
#opening .palms svg{width:33.333%;height:100%;display:block;float:left}
#opening .layer,#opening .sleepers{will-change:transform}

/* ---------- ground, platform, track ---------- */
#opening .ground{position:absolute;left:0;right:0;bottom:0;height:20%;
  background:linear-gradient(#251d16,#140e0a)}
#opening .ballast{position:absolute;left:0;right:0;bottom:12%;height:8%;
  background:repeating-linear-gradient(94deg,#2c231a 0 6px,#211a13 6px 13px)}
/* 200% wide and scrolled with a transform, not background-position. Moving a
   background repaints the whole strip every frame; moving a transform is
   composited. On a phone that difference is the difference between smooth
   and juddering. */
#opening .sleepers{position:absolute;left:0;width:200%;bottom:12.6%;height:4.2%;
  background:repeating-linear-gradient(90deg,#3d2f22 0 24px,transparent 24px 64px);opacity:.9}
#opening .rail{position:absolute;left:0;right:0;height:3px;background:linear-gradient(#cfcabb,#77736a)}
#opening .rail.r1{bottom:16.5%} #opening .rail.r2{bottom:14.4%;opacity:.55}
#opening .platform{position:absolute;right:0;bottom:19%;width:34%;height:9%;
  background:linear-gradient(#4a423a,#2e2823);border-top:3px solid #6b6055;
  box-shadow:inset 0 3px 0 #857868;opacity:0;transition:opacity 1s ease}
#opening.arrived .platform{opacity:1}
#opening .platform::after{content:"";position:absolute;left:0;right:0;top:3px;height:6px;
  background:repeating-linear-gradient(90deg,#c9b26a 0 18px,transparent 18px 36px);opacity:.75}
#opening .signpost{position:absolute;bottom:28%;right:22%;text-align:center;opacity:0;
  transition:opacity .9s ease .35s}
#opening.arrived .signpost{opacity:1}
#opening .signboard{background:#f2d75e;color:#231d12;padding:6px 18px;border-radius:2px;
  font-family:var(--mono);font-size:12px;letter-spacing:.16em;
  box-shadow:0 8px 20px rgba(0,0,0,.55)}
#opening .signpole{width:5px;height:48px;margin:0 auto;background:linear-gradient(#585d66,#2b2f36)}

/* ---------- the train ----------
   0-45%: flat-out cruise at constant speed, heavy motion blur.
   45-100%: one smooth brake whose opening slope matches the cruise, so there
   is no velocity step at the handover and no snap at the end. */
#opening .train{position:absolute;bottom:14.5%;left:0;width:min(2100px,330vw);
  transform:translateX(420vw);will-change:transform}
#opening .train svg{width:100%;height:auto;display:block;
  filter:drop-shadow(0 20px 26px rgba(0,0,0,.62))}
#opening.rolling .train{animation:arrive 5s forwards}
@keyframes arrive{
  0%   {transform:translateX(420vw); animation-timing-function:linear}
  45%  {transform:translateX(168vw); animation-timing-function:cubic-bezier(.2,.4,.35,1)}
  100% {transform:translateX(0vw)}
}
#opening .mblur-xl{filter:url(#mbXl)}
#opening .mblur{filter:url(#mbHi)}
#opening .mblur-lo{filter:url(#mbLo)}
/* the drop-shadow is invisible under heavy blur and costs a lot — drop it */
#opening .train.mblur-xl svg,#opening .train.mblur svg{filter:none}
/* Wheel angle comes from JS as one CSS variable, so it can never jump or reverse. */
#opening .wheel{transform-box:fill-box;transform-origin:center;transform:rotate(var(--wa,0deg))}
#opening .settle{animation:settle 1.15s cubic-bezier(.2,.8,.3,1) both}
@keyframes settle{0%{transform:translateY(0)}30%{transform:translateY(2.5px)}
  65%{transform:translateY(-1.4px)}100%{transform:translateY(0)}}

#opening .beam{position:absolute;bottom:20%;left:0;width:0;height:24%;pointer-events:none;opacity:0;
  background:linear-gradient(90deg,rgba(255,238,186,.34),rgba(255,238,186,0));filter:blur(12px)}
#opening.rolling .beam{animation:beam 5s ease-out forwards}
@keyframes beam{0%{width:78vw;opacity:.95}62%{opacity:.55}100%{width:22vw;opacity:0}}
#opening .smoke{position:absolute;bottom:44%;left:12%;pointer-events:none}
#opening .puff{position:absolute;width:26px;height:26px;border-radius:50%;opacity:0;
  background:radial-gradient(circle,rgba(226,226,230,.5),rgba(226,226,230,0) 70%)}
#opening.arrived .puff{animation:puff 3.6s ease-out infinite}
@keyframes puff{0%{transform:translate(0,0) scale(.45);opacity:.55}
  100%{transform:translate(-90px,-165px) scale(3);opacity:0}}

/* ---------- the ticket ---------- */
.ticket-wrap{position:absolute;inset:0;display:grid;place-items:center;pointer-events:none;padding:16px}
/* drop-shadow, not box-shadow, so the shadow follows the punched notches */
.ticket-frame{filter:drop-shadow(0 26px 44px rgba(0,0,0,.6));
  transform:translate(-14vw,16vh) rotate(-13deg) scale(.24);opacity:0;
  transition:transform 1.1s cubic-bezier(.2,.9,.25,1),opacity .55s ease}
#opening.ticketed .ticket-frame{transform:none;opacity:1}
.ticket{--r:9px;position:relative;width:min(430px,94vw);background:var(--paper);color:var(--ink);
  border-radius:12px;padding:20px 22px 16px;pointer-events:auto;
  -webkit-mask:
    radial-gradient(var(--r) at left, #0000 98%, #000) left / 100% 30px repeat-y,
    radial-gradient(var(--r) at right,#0000 98%, #000) right/ 100% 30px repeat-y;
  -webkit-mask-composite:source-in;
  mask:
    radial-gradient(var(--r) at left, #0000 98%, #000) left / 100% 30px repeat-y,
    radial-gradient(var(--r) at right,#0000 98%, #000) right/ 100% 30px repeat-y;
  mask-composite:intersect}
.t-top{display:flex;justify-content:space-between;align-items:flex-start;
  border-bottom:2px dashed rgba(0,0,0,.24);padding-bottom:10px}
.t-brand{font-family:var(--mono);font-size:10.5px;letter-spacing:.2em;color:#7b6a46}
.t-title{font-size:23px;font-weight:700;line-height:1.1;margin:2px 0 0}
.t-pnr{text-align:right;font-family:var(--mono);font-size:10.5px;color:#7b6a46}
.t-pnr b{display:block;font-size:15px;color:var(--ink);letter-spacing:.06em}
.t-name{margin:14px 0 4px;text-align:center}
.t-name small{font-family:var(--mono);font-size:10px;letter-spacing:.2em;color:#8a7850}
.t-name div{font-family:var(--script);font-size:38px;line-height:1.15;color:#3d2f18}
.t-route{display:flex;align-items:center;gap:10px;margin:10px 0 14px}
.t-stn{flex:0 0 auto;text-align:center}
.t-stn b{display:block;font-size:16px;letter-spacing:.03em}
.t-stn span{font-family:var(--mono);font-size:9.5px;color:#8a7850;letter-spacing:.12em}
.t-arrow{flex:0 0 auto;color:#a9832f;font-size:17px}
.t-line{flex:1;height:2px;background:repeating-linear-gradient(90deg,#c2a96d 0 8px,transparent 8px 14px)}
.t-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;
  border-top:2px dashed rgba(0,0,0,.24);padding-top:12px}
.t-cell small{display:block;font-family:var(--mono);font-size:9px;letter-spacing:.13em;color:#8a7850}
.t-cell b{font-size:14.5px}
/* over the barcode, not over the fare line — a real stamp lands on the
   decorative part of a ticket, not the part you need to read */
.stamp{position:absolute;left:50%;bottom:30px;transform:translateX(-50%) rotate(-11deg);
  border:3px double var(--red);color:var(--red);border-radius:7px;padding:3px 10px;
  background:var(--paper);
  font-family:var(--mono);font-size:13px;letter-spacing:.15em;
  opacity:0;transition:opacity .35s ease .5s}
#opening.ticketed .stamp{opacity:.95}
.barcode{margin-top:12px;height:28px;border-radius:3px;
  background:repeating-linear-gradient(90deg,#241d16 0 2px,transparent 2px 4px,#241d16 4px 5px,transparent 5px 9px)}
.t-foot{display:flex;justify-content:space-between;margin-top:7px;
  font-family:var(--mono);font-size:9px;color:#8a7850;letter-spacing:.1em}

.board{margin-top:18px;text-align:center;pointer-events:auto;opacity:0;transition:opacity .5s ease .45s}
#opening.ticketed .board{opacity:1}
.board button{background:linear-gradient(#f3d668,#d9a939);color:#2a1e07;border:0;cursor:pointer;
  font-family:var(--mono);letter-spacing:.18em;font-size:13px;padding:13px 30px;
  border-radius:999px;box-shadow:0 12px 28px rgba(0,0,0,.45);animation:pulse 2s ease-in-out infinite}
@keyframes pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.04)}}

/* ---------- chrome ---------- */
.obtn{position:absolute;z-index:9;background:rgba(255,255,255,.08);color:#ece5d6;
  border:1px solid rgba(255,255,255,.2);border-radius:999px;cursor:pointer;
  font-family:var(--mono);font-size:11px;letter-spacing:.11em;
  padding:8px 13px;backdrop-filter:blur(6px)}
.obtn:hover{background:rgba(255,255,255,.17)}
#soundBtn{top:16px;left:16px} #skipBtn{top:16px;right:16px}
#opening.boarded #skipBtn{display:none}

/* ============================ boarding: the departure ====================
   One continuous camera move, in three overlapping beats:
     .boarding   the ticket drops away
     .departing  the train accelerates hard and runs right out of frame
     .pushin     the camera pushes slowly into the empty track at sunrise
     .gone       and the whole thing dissolves into the site
   The zoom and the fade live on #opening itself — one element, two long
   easings — so there is no seam anywhere between them. */

#opening.boarding .ticket-wrap{opacity:0;transform:translateY(34px) scale(.86);
  transition:opacity .5s ease,transform .6s cubic-bezier(.5,0,.75,0);pointer-events:none}

/* ease-in over a long run: visibly gathering speed all the way out of frame */
/* The curve matters more than the duration. cubic-bezier(.55,0,.85,.42) is
   such a hard ease-in that the train had covered 70px of 619 a second and a
   half after the tap — it looked stuck, then lurched. This one still reads as
   accelerating but is moving from the first frame. */
#opening.departing .train{animation:depart 3s cubic-bezier(.28,0,.62,.55) forwards}
/* The distance is measured, not guessed. It used to be a flat -165vw, but
   the train's own width is min(2100px,330vw) — capped on desktop, not on a
   phone — so on a 375px screen the train was 1238px wide and needed 1613px
   to clear the frame while only travelling 619px. It never actually left.
   JS sets --departX from the real width each run. */
@keyframes depart{from{transform:translateX(0)}
  to{transform:translateX(var(--departX,-165vw))}}

/* No camera move at the end. The overlay has to finish sitting exactly where
   the gate sits, and any leftover scale would show as a jump the moment the
   two cross-fade. The motion comes from the train, not the camera. */

/* Egmore rises behind the departing train. Until then we are trackside and
   the scenery is streaming past; once the train has gone we are standing at
   the station, so the parallax layers go with it. */
#opening .station{opacity:0;transform:translateY(-38px);
  transition:opacity 1.3s ease,transform 1.5s cubic-bezier(.2,.75,.3,1)}
#opening.arrivedat .station{opacity:1;transform:none}
#opening .forecourt,#opening .egsign{opacity:0;transition:opacity 1.3s ease}
#opening.arrivedat .forecourt,#opening.arrivedat .egsign{opacity:1}
/* the running line goes when the station arrives — we are at the frontage now */
#opening.arrivedat .ground,#opening.arrivedat .ballast,#opening.arrivedat .sleepers,
#opening.arrivedat .rail{opacity:0;transition:opacity 1.1s ease}
#opening .fireworks,#opening .skyglow{opacity:0;transition:opacity 1.6s ease}
#opening.arrivedat .fireworks,#opening.departing .skyglow{opacity:1}
#opening.departing .hills,#opening.departing .palms,
#opening.departing .platform,#opening.departing .signpost{opacity:0;transition:opacity 1.4s ease}
#opening .station{z-index:1} #opening .fireworks{z-index:3}

/* the names the train uncovers as it clears the frame */
#opening .railnames{opacity:0;transform:translateY(16px);
  transition:opacity 1.2s ease,transform 1.4s cubic-bezier(.2,.8,.3,1)}
#opening.named .railnames{opacity:1;transform:none}

/* ===================== phones ============================================
   The blur stays. What made it stutter was not the blur itself but how
   often it CHANGED: the tier swapped between three filters as the speed
   rose and fell, and every swap re-rasterises the whole ~1200px train
   from scratch. That is the hitching, and the flash as the new raster
   lands is the blinking.

   On a phone the JS now asks for one tier only, with hysteresis, so the
   filter is built once per run and the transform animates against a
   cached layer. What comes off here is only the extra work stacked on
   top: the train's drop-shadow and the headlight beam's 12px blur.
   ======================================================================= */
@media (max-width:820px),(pointer:coarse){
  #opening .train svg{filter:none}          /* no drop-shadow under blur */
  #opening .beam{display:none}              /* a 12px blur across 78vw */
}

@media (max-width:520px){
  .t-name div{font-size:30px} .t-title{font-size:19px} .t-stn b{font-size:14px}
  .t-cell b{font-size:12.5px} .t-grid{gap:6px}
}
@media (prefers-reduced-motion:reduce){
  #opening.rolling .train,#opening.departing .train{animation:none;transform:translateX(0)}
  #opening .railnames{opacity:1;transform:none}
  #opening .hills,#opening .palms,#opening .puff,.board button{animation:none !important}
  #opening .settle{animation:none}
}
