/* Boot cinematic for price.studily.org.
 *
 * What it shows, in order:
 *   1. the two Studily arrows rise from below the fold into the mark position
 *   2. a galaxy blooms open behind them — real gas, not a disc
 *   3. the arrows accelerate forward THROUGH a field of stars and into the
 *      galaxy, which swells until it fills the frame and swallows them
 *   4. the overlay dissolves and the page's own galaxy is already underneath
 *
 * Beat 3 is fired by preload.js adding `pl-warping` to <html>, once the intro
 * has had its moment AND the page has its data. So a fast load gets the whole
 * sequence and a slow one holds on a turning galaxy - which is a real busy
 * state - instead of either cutting the animation short or revealing a page
 * whose prices have not arrived.
 *
 * COLOUR IS NOT INVENTED HERE. Every tone is lifted from galaxy.js, which paints
 * the sky this overlay hands over to:
 *   ARM_LIT 186,158,255   ARM_MID 124,118,238   ARM_OUT 74,108,210
 *   CORE_HOT 255,250,240  CORE_WARM 255,228,196  CORE_COOL 168,128,252
 * The previous version was cyan/magenta and did not match the page at all, which
 * is why the reveal looked like a jump between two different sites.
 *
 * TWO RULES THIS FILE EXISTS TO KEEP:
 *
 *   NOTHING HERE IS EVER WHITE. The old burst was a rgba(255,255,255,0.96) disc
 *   that scaled to 3.4x - about 300px on a 354px phone - so the "reveal" whitened
 *   the entire panel before the page appeared. A preload must go from the galaxy
 *   to the galaxy; the hottest thing on screen is the core's warm cream.
 *
 *   NOTHING HERE IS A CIRCLE. The old portal was a conic-gradient disc under a
 *   border-radius:50% ring, i.e. literally a rotating hoop. The gas below is
 *   several irregular off-centre blobs with NO shared outline and NO ring, so
 *   there is no circular edge to read as a wheel.
 *
 * Everything animated is transform / opacity only. A preload runs while the main
 * thread is compiling the page's own scripts, so anything needing layout or
 * paint per frame (width, top, filter) would stutter exactly when it matters.
 * The blurs are static: set once, never animated.
 */

/* ─── The overlay ───────────────────────────────────────────────────────── */
/* A literal colour, NOT var(--space). This element is on screen before
   styles.css has necessarily applied, and a custom property that has not
   resolved yet leaves the background transparent - which shows the browser's
   white page through it. That is a white flash on every cold load. */
.preload {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: grid;
  place-items: center;
  overflow: hidden;
  background: #06070f;
}

/* ─── The sky: the page's galaxy, pre-drawn in CSS ──────────────────────── */
/* The page behind is a fixed galaxy canvas that has not painted yet, so this
   paints the same thing cheaply: a deep wash, four spiral-arm blobs, and stars.
   It is what the overlay dissolves INTO, so the two must agree. */
.pl-sky {
  position: absolute;
  inset: -10%;
  opacity: 0;
  animation: pl-sky-in 0.9s ease-out forwards;
  will-change: opacity;
}
.pl-sky::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(46% 34% at 62% 34%, rgba(124, 118, 238, 0.3) 0%, rgba(124, 118, 238, 0) 68%),
    radial-gradient(38% 30% at 34% 62%, rgba(74, 108, 210, 0.26) 0%, rgba(74, 108, 210, 0) 70%),
    radial-gradient(30% 26% at 76% 72%, rgba(186, 158, 255, 0.2) 0%, rgba(186, 158, 255, 0) 72%),
    radial-gradient(24% 20% at 22% 24%, rgba(168, 128, 252, 0.22) 0%, rgba(168, 128, 252, 0) 74%),
    radial-gradient(70% 55% at 50% 45%, rgba(7, 8, 22, 0.7) 0%, rgba(6, 7, 15, 0.95) 100%);
}
@keyframes pl-sky-in {
  to {
    opacity: 1;
  }
}

/* The star field itself. Position, size and opacity come from preload.js; this
   only has to make a 1-2px box look like a star. */
.pl-starsky {
  position: absolute;
  inset: 0;
}
.pl-starsky i {
  position: absolute;
  display: block;
  border-radius: 50%;
  background: #ffffff;
}

/* ─── Stage ─────────────────────────────────────────────────────────────── */
/* One square, centred. The galaxy and the mark are both absolutely positioned
   inside it and share a centre, which is what lets the arrows fly INTO the
   galaxy by scaling about their own midpoint. */
.preload-stage {
  position: relative;
  width: 236px;
  height: 236px;
  display: grid;
  place-items: center;
}

/* ─── Beat 2: the galaxy opens ──────────────────────────────────────────── */
.pl-nebula {
  position: absolute;
  inset: -22%;
  opacity: 0;
  /* Opens as the arrows settle, so it reads as the galaxy arriving for them. */
  animation: pl-nebula-in 0.9s cubic-bezier(0.2, 0.9, 0.25, 1) 0.3s forwards;
  will-change: transform, opacity;
}
@keyframes pl-nebula-in {
  0% {
    opacity: 0;
    transform: scale(0.34);
  }
  70% {
    opacity: 1;
    transform: scale(1.05);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

/* The gas itself. Five irregular blobs at different offsets and scales.
   There is deliberately NO border-radius circle, no ring and no shared outline
   anywhere in this layer - the shape has to come from where the blobs overlap,
   which is what makes it read as cloud instead of as a spinning disc. The
   container is the only thing that turns, slowly, and because the blobs inside
   it are off-centre that reads as a galaxy wheeling rather than a hoop. */
.pl-gas {
  position: absolute;
  inset: 0;
  animation: pl-turn 34s linear infinite;
  will-change: transform;
}
.pl-gas i {
  position: absolute;
  border-radius: 50%;
  filter: blur(17px);
}
.pl-gas i:nth-child(1) {
  left: 8%;
  top: 16%;
  width: 62%;
  height: 48%;
  background: radial-gradient(closest-side, rgba(124, 118, 238, 0.5), rgba(124, 118, 238, 0) 100%);
}
.pl-gas i:nth-child(2) {
  left: 34%;
  top: 40%;
  width: 66%;
  height: 52%;
  background: radial-gradient(closest-side, rgba(74, 108, 210, 0.46), rgba(74, 108, 210, 0) 100%);
}
.pl-gas i:nth-child(3) {
  left: 48%;
  top: 6%;
  width: 46%;
  height: 42%;
  background: radial-gradient(closest-side, rgba(186, 158, 255, 0.4), rgba(186, 158, 255, 0) 100%);
}
.pl-gas i:nth-child(4) {
  left: 4%;
  top: 52%;
  width: 44%;
  height: 38%;
  background: radial-gradient(closest-side, rgba(168, 128, 252, 0.36), rgba(168, 128, 252, 0) 100%);
}
.pl-gas i:nth-child(5) {
  left: 26%;
  top: 26%;
  width: 52%;
  height: 44%;
  background: radial-gradient(closest-side, rgba(124, 118, 238, 0.34), rgba(124, 118, 238, 0) 100%);
}
@keyframes pl-turn {
  to {
    transform: rotate(360deg);
  }
}

/* Dust: pinpoint stars sitting IN the gas, with a slow breath so the cloud is
   not a frozen illustration while the load runs. */
.pl-dust {
  position: absolute;
  inset: 0;
  animation: pl-breathe 6.5s ease-in-out infinite;
  will-change: opacity;
}
.pl-dust i {
  position: absolute;
  width: 2px;
  height: 2px;
  border-radius: 50%;
  background: #ffffff;
  opacity: 0.75;
}
@keyframes pl-breathe {
  0%,
  100% {
    opacity: 0.65;
  }
  50% {
    opacity: 1;
  }
}

/* The core: the warm heart of the galaxy, and the thing the arrows fly into -
   so it is the brightest element on screen. Deliberately CORE_HOT but nudged
   warmer and a shade down from full opacity (255,244,226 at 0.9 rather than
   255,250,240 at 0.95): it flares and scales during the warp, and a cream that
   hot at that size starts reading as a white disc again. */
.pl-core {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 88px;
  height: 88px;
  margin: -44px 0 0 -44px;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    rgba(255, 244, 226, 0.9) 0%,
    rgba(255, 228, 196, 0.5) 24%,
    rgba(168, 128, 252, 0.28) 52%,
    rgba(124, 118, 238, 0.1) 72%,
    rgba(124, 118, 238, 0) 86%
  );
  animation: pl-core-beat 2.8s ease-in-out infinite;
  will-change: transform, opacity;
}
@keyframes pl-core-beat {
  0%,
  100% {
    transform: scale(1);
    opacity: 0.9;
  }
  50% {
    transform: scale(1.13);
    opacity: 1;
  }
}

/* ─── Beat 3: the stars the arrows fly through ──────────────────────────── */
/* preload.js fills this with the streaks (angles in --a, so the markup stays a
   single empty element). Each streak is a 2px line pinned at the stage centre
   by `transform-origin: 0 50%`, so `rotate(--a) translateX(...) scaleX(...)`
   throws it radially outward while it stretches - which is what a star passing
   the camera looks like from inside the ship. */
.pl-warp {
  position: absolute;
  inset: 0;
  opacity: 0;
}
.pl-warp i {
  position: absolute;
  left: 50%;
  top: 50%;
  height: 2px;
  width: 92px;
  margin-top: -1px;
  transform-origin: 0 50%;
  border-radius: 1px;
  background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.92) 100%);
  opacity: 0;
  will-change: transform, opacity;
}

/* Warp in: the streaks fire, staggered from the centre outward so the field
   builds instead of every line appearing at once. */
.pl-warping .pl-warp {
  opacity: 1;
}
.pl-warping .pl-warp i {
  animation: pl-streak 0.95s cubic-bezier(0.3, 0, 0.7, 1) both;
  animation-delay: var(--d, 0s);
}
@keyframes pl-streak {
  0% {
    opacity: 0;
    transform: rotate(var(--a, 0deg)) translateX(26px) scaleX(0.05);
  }
  14% {
    opacity: 0.95;
  }
  70% {
    opacity: 0.85;
  }
  100% {
    opacity: 0;
    transform: rotate(var(--a, 0deg)) translateX(430px) scaleX(1.5);
  }
}

/* ─── Beat 1: the arrows rise from below ────────────────────────────────── */
/* The mark is two elbows on one 100-box. They are separate SVGs here, each
   holding the SAME viewBox and its own path, so the pair overlays exactly as it
   does in the nav while each one can be animated on its own beat. */
.preload-mark {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.pl-arrow {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  /* Scaling about the box centre pulls the arrow toward the galaxy's centre,
     because the box centre IS the galaxy centre. */
  transform-origin: 50% 50%;
  will-change: transform, opacity;
  animation: pl-rise 0.86s cubic-bezier(0.16, 1, 0.3, 1) var(--rise-delay, 0s) both;
}
/* Pink leads, blue follows - the same order as the nav mark, where the light
   travels up-right along the arrows. */
.pl-arrow--pink {
  --rise-delay: 0s;
}
.pl-arrow--blue {
  --rise-delay: 0.13s;
}
@keyframes pl-rise {
  0% {
    opacity: 0;
    transform: translateY(158%) scale(0.82);
  }
  62% {
    opacity: 1;
    transform: translateY(-7%) scale(1.03);
  }
  100% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

/* ─── Beat 3b: the arrows accelerate into the galaxy ────────────────────── */
/* `both` plus a delay means the mark holds its risen pose for the whole warp
   before this starts, so nothing snaps at the handover. The easing is ease-IN:
   slow to let go, then very fast - that is what reads as "faster". */
.pl-warping .pl-arrow {
  animation: pl-arrow-fly 0.72s cubic-bezier(0.62, 0, 0.9, 0.24) 0.24s both;
}
.pl-warping .pl-arrow--blue {
  animation-delay: 0.3s;
}
@keyframes pl-arrow-fly {
  0% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  22% {
    /* a beat of anticipation - they gather themselves before they go */
    opacity: 1;
    transform: translateY(3%) scale(0.95);
  }
  100% {
    /* Past the core: grown, spent and gone, with a mirrored twist so the pair
       spirals in rather than tumbling one way. */
    opacity: 0;
    transform: translateY(-6%) scale(2.7) rotate(var(--fly-spin, 18deg));
  }
}
.pl-warping .pl-arrow--pink {
  --fly-spin: -26deg;
}
.pl-warping .pl-arrow--blue {
  --fly-spin: 22deg;
}

/* The galaxy swells as they arrive and swallows the frame. This is the beat the
   old version got wrong: it must expand into the SAME colours the page is about
   to show, so the dissolve at the end has nothing to jump to. */
.pl-warping .pl-nebula {
  animation: pl-engulf 1.25s cubic-bezier(0.4, 0, 0.2, 1) 0.34s both;
}
@keyframes pl-engulf {
  0% {
    opacity: 1;
    transform: scale(1);
  }
  35% {
    opacity: 1;
    transform: scale(1.5);
  }
  100% {
    opacity: 1;
    transform: scale(5.6);
  }
}
/* The core flares once as the arrows land, then settles back down - a flare,
   not a flash. It stays inside the gas, so it never fills the screen. */
.pl-warping .pl-core {
  animation: pl-core-flare 1s cubic-bezier(0.2, 0.8, 0.3, 1) 0.4s both;
}
@keyframes pl-core-flare {
  0% {
    transform: scale(1);
    opacity: 1;
  }
  30% {
    transform: scale(1.75);
    opacity: 1;
  }
  100% {
    transform: scale(3.1);
    opacity: 0;
  }
}
/* The sky's blobs drift outward with the swell, so the background is moving
   with the camera rather than sitting still behind it. */
.pl-warping .pl-sky {
  animation: pl-sky-out 1.1s cubic-bezier(0.5, 0, 0.75, 0) 0.3s both;
}
@keyframes pl-sky-out {
  0% {
    opacity: 1;
    transform: scale(1);
  }
  100% {
    opacity: 1;
    transform: scale(1.7);
  }
}

/* ─── The note ──────────────────────────────────────────────────────────── */
/* Deliberately late and quiet: during the intro the animation is the message,
   and this only has to be there for the case where the load runs long and the
   turning galaxy needs words next to it. */
.preload-note {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 12%;
  margin: 0;
  text-align: center;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.2px;
  color: #8b93b8;
  opacity: 0;
  animation: pl-note-in 0.5s ease-out 1.6s forwards;
}
@keyframes pl-note-in {
  to {
    opacity: 1;
  }
}

/* ─── Beat 4: dissolve into the page's own galaxy ───────────────────── */
/* preload.js adds `pl-reveal` to the OVERLAY when the sequence is done. A plain
   opacity drop over a slight push-in: the page's galaxy is already painted
   underneath and is the same colour, so this cross-fades galaxy into galaxy.

   THE SELECTOR IS SCOPED TO `.preload` ON PURPOSE. Written bare as
   `.pl-reveal`, the keyframes applied to whatever carried the class - and the
   driver used to add it to <html>, which faded the ENTIRE DOCUMENT to
   opacity 0 (the visitor saw the browser's default white page, with no content)
   and turned <html> into a containing block for `position: fixed`, detaching
   the galaxy canvas behind it. Never let this rule match the root element. */
.preload.pl-reveal {
  pointer-events: none;
  transform-origin: 50% 44%;
  animation: pl-dissolve 0.74s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
@keyframes pl-dissolve {
  0% {
    opacity: 1;
    transform: scale(1);
  }
  100% {
    opacity: 0;
    transform: scale(1.12);
  }
}

/* ─── Reduced motion ────────────────────────────────────────────────────── */
/* No cinematic at all: a still mark on a still galaxy, and preload.js lifts it
   the moment the data is in. Anyone who has asked the OS for less motion is not
   waiting two seconds to be shown a logo. */
@media (prefers-reduced-motion: reduce) {
  .pl-sky,
  .pl-sky::before,
  .pl-nebula,
  .pl-gas,
  .pl-gas i,
  .pl-dust,
  .pl-core,
  .pl-arrow,
  .pl-warp i,
  .preload-note {
    animation: none !important;
  }
  .pl-sky {
    opacity: 1;
  }
  .pl-arrow {
    opacity: 1;
    transform: none;
  }
  .pl-nebula {
    opacity: 0.9;
    transform: scale(1);
  }
  .preload-note {
    opacity: 1;
  }
  .preload.pl-reveal {
    animation-duration: 0.18s;
  }
}

/* ─── Small screens ─────────────────────────────────────────────────────── */
@media (max-width: 620px) {
  .preload-stage {
    width: 188px;
    height: 188px;
  }
  .pl-core {
    width: 72px;
    height: 72px;
    margin: -36px 0 0 -36px;
  }
  .preload-note {
    bottom: 16%;
  }
}
