/* ============================================================================
   LOADER SHELL — DVH, NOT inset:0.

   THE BUG. This element was `position: fixed; inset: 0` with no height of its
   own, and a fixed box with top:0/bottom:0 sizes to the LAYOUT viewport — which
   on a phone is the LARGE viewport, the height with the browser chrome
   collapsed. While the address bar is actually showing, the bottom ~80-100px of
   this element sits underneath it. Measured at 390x844: the footer's bottom
   edge lands at 822 with only 22px of clearance, so a typical address bar eats
   the countdown from below and leaves the top of the digits showing. That is
   exactly the reported symptom.

   The scrolling site uses svh on purpose — see the viewport contract in
   tokens.css — because a long scene must not relayout when the chrome slides.
   That reasoning does not transfer here. This element is fixed, unscrollable,
   alive for about 1.5 seconds, and must fit the viewport that EXISTS right now.
   dvh is the correct unit for exactly this case, and it is scoped to the loader
   alone: nothing else in the project uses it.

   `inset: 0` is kept deliberately as the fallback. Where dvh is unsupported the
   height declaration is dropped at parse time and the old behaviour returns;
   where it is supported, top + height over-constrain the box and `bottom` is
   ignored, so dvh wins.

   translateZ(0) is here for containment, not for compositing: it makes this
   element the containing block for FIXED descendants. The 2px progress rule is
   `position: fixed; bottom: 0` and was therefore resolving against the viewport
   rather than against this box — a 2px line pinned to the large-viewport bottom
   is 100% hidden behind chrome, which is why it read as missing entirely. With
   a containing block it now pins to the bottom of the dvh box. The root is only
   ever animated on opacity/visibility, so a transform here is free.
   ============================================================================ */
.celestial-loader {
  position: fixed;
  z-index: 10000;
  inset: 0;
  height: 100dvh;
  transform: translateZ(0);
  overflow: hidden;
  color: var(--moon, #fff7e6);
  background: var(--void-deep, #04020a);
  font-family: var(--font-ui, Arial, sans-serif);
  transition: opacity 520ms cubic-bezier(.76,0,.18,1), visibility 520ms;
}

.celestial-loader__canvas,
.celestial-loader__nebula,
.celestial-loader__bloom,
.celestial-loader__flash,
.celestial-loader__grain {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.celestial-loader__canvas { z-index: 2; max-width: none; }

.celestial-loader__flash {
  z-index: 3;
  opacity: 0;
  background: radial-gradient(circle at 50% 50%, rgba(255,255,255,.95) 0%, rgba(46,233,255,.62) 24%, transparent 60%);
  mix-blend-mode: screen;
}
.celestial-loader.is-flashing .celestial-loader__flash { animation: loader-flash 460ms cubic-bezier(.16,1,.3,1) forwards; }
@keyframes loader-flash {
  0% { opacity: 0; transform: scale(.3); }
  22% { opacity: 1; transform: scale(1.4); }
  100% { opacity: 0; transform: scale(2.6); }
}

.celestial-loader__nebula {
  z-index: 0;
  background:
    radial-gradient(ellipse at 18% 22%, rgba(31,61,255,.55), transparent 38%),
    radial-gradient(ellipse at 82% 28%, rgba(255,31,125,.46), transparent 34%),
    radial-gradient(ellipse at 78% 80%, rgba(201,255,43,.3), transparent 37%),
    radial-gradient(ellipse at 22% 84%, rgba(255,165,10,.34), transparent 39%),
    linear-gradient(142deg, #04020a 0%, #200747 40%, #4a1180 70%, #04020a 100%);
  filter: saturate(1.22);
  transition: transform 1800ms cubic-bezier(.65,.05,0,1), filter 900ms ease, opacity 900ms ease;
}

.celestial-loader__bloom { z-index: 1; opacity: .6; }
.celestial-loader__bloom::before,
.celestial-loader__bloom::after,
.celestial-loader__bloom i {
  position: absolute;
  top: 50%;
  left: 50%;
  width: min(72vw, 68rem);
  aspect-ratio: 1.6;
  content: "";
  transform: translate(-50%,-50%) rotate(var(--turn, 0deg));
  transform-origin: 50% 50%;
  clip-path: polygon(50% 0, 62% 31%, 100% 50%, 64% 68%, 50% 100%, 36% 68%, 0 50%, 38% 31%);
  filter: blur(3.4rem);
  mix-blend-mode: screen;
  animation: loader-breathe 5.8s ease-in-out infinite alternate;
}
.celestial-loader__bloom::before { --turn: 18deg; background: rgba(122,45,255,.26); }
.celestial-loader__bloom::after { --turn: 81deg; width: min(64vw, 58rem); background: rgba(255,31,125,.2); animation-delay: -2.2s; }
.celestial-loader__bloom i:nth-child(1) { --turn: 137deg; width: min(54vw, 50rem); background: rgba(46,233,255,.15); animation-delay: -3.7s; }
.celestial-loader__bloom i:nth-child(2) { --turn: 223deg; width: min(42vw, 42rem); background: rgba(201,255,43,.1); animation-delay: -1.3s; }
.celestial-loader__bloom i:nth-child(3) { display: none; }

.celestial-loader__grain {
  z-index: 5;
  opacity: .11;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.92' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.7'/%3E%3C/svg%3E");
  mix-blend-mode: soft-light;
}

.celestial-loader__core {
  position: absolute;
  z-index: 6;
  top: 50%;
  left: 50%;
  display: grid;
  width: min(43rem, 54vw);
  justify-items: center;
  gap: clamp(.8rem, 1.6vw, 1.45rem);
  text-align: center;
  transform: translate(-50%,-50%);
}
.celestial-loader__core::before {
  position: absolute;
  z-index: -1;
  top: 50%;
  left: 50%;
  width: 118%;
  height: 260%;
  content: "";
  background: radial-gradient(ellipse, rgba(4,2,10,.92) 0 22%, rgba(27,4,66,.6) 42%, transparent 72%);
  filter: blur(1rem);
  transform: translate(-50%,-50%);
}
.celestial-loader__core p,
.celestial-loader__core strong {
  margin: 0;
  font-size: clamp(.5rem,.64vw,.68rem);
  font-weight: 500;
  line-height: 1;
  letter-spacing: .28em;
  text-transform: uppercase;
  transition: opacity 260ms ease, transform 680ms cubic-bezier(.23,1,.32,1);
}
.celestial-loader__core p { color: var(--electric-ice, #2ee9ff); }
.celestial-loader__core strong { color: var(--moon, #fff7e6); }
.celestial-loader__core img {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 0 1.8rem rgba(255,31,125,.4)) drop-shadow(0 0 4rem rgba(46,233,255,.26));
  transform: translateZ(0) scale(.985);
  transition: filter 900ms ease, transform 1300ms cubic-bezier(.23,1,.32,1), opacity 320ms ease;
}
.celestial-loader.is-charged .celestial-loader__core img { filter: drop-shadow(0 0 2.4rem rgba(46,233,255,.48)) drop-shadow(0 0 4.8rem rgba(255,31,125,.3)); transform: scale(1); }

.celestial-loader__footer {
  position: absolute;
  z-index: 7;
  right: max(clamp(1.35rem,4vw,4rem), env(safe-area-inset-right, 0px));
  /* index.html ships viewport-fit=cover, so on a notched phone the layout
     viewport runs UNDER the home indicator. Nothing in this file consumed a
     safe-area inset before, which put another ~34px of the footer out of
     reach on top of the chrome problem. */
  bottom: calc(clamp(1.4rem,4vw,3.6rem) + env(safe-area-inset-bottom, 0px));
  left: max(clamp(1.35rem,4vw,4rem), env(safe-area-inset-left, 0px));
  display: grid;
  grid-template-columns: 1fr minmax(10rem, 25rem);
  align-items: end;
  gap: 2rem;
  color: rgba(255,247,230,.64);
  font-size: .52rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  transition: opacity 280ms ease, transform 660ms cubic-bezier(.23,1,.32,1);
}
/* The counter is the protagonist now. A loader that shows a precise, large,
   tabular number reads as an instrument; a small number beside a rainbow bar
   reads as a widget. The site's voice downstream is coordinates, sections and
   scale bars — this matches it. */
.celestial-loader__meter { display: grid; grid-template-columns: 1fr auto; gap: 1.4rem; align-items: end; }
/* Still fixed, but the shell is now its containing block (see translateZ on
   .celestial-loader), so bottom:0 resolves to the bottom of the VISIBLE
   loader rather than the bottom of the large viewport. A 2px line pinned to
   the large-viewport bottom is 100% hidden behind chrome, which is why this
   read as missing entirely. The safe-area inset lifts it clear of the home
   indicator on top of that. */
.celestial-loader__meter > i {
  position: fixed;
  right: 0;
  bottom: env(safe-area-inset-bottom, 0px);
  left: 0;
  height: 2px;
  overflow: hidden;
  background: rgba(255,255,255,.1);
  clip-path: none;
}
/* Was a six-stop rainbow. One hue, full strength — the spectrum sweep already
   happens later on the velocity strip, and doing it twice spends the moment. */
.celestial-loader__meter b { display: block; width: 100%; height: 100%; background: var(--moon, #fff7e6); transform: scaleX(0); transform-origin: left; will-change: transform; }
.celestial-loader__meter span {
  color: var(--moon, #fff7e6);
  font: 400 clamp(3.2rem, 7vw, 6.4rem)/.82 var(--font-display, Georgia, serif);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.045em;
}

.celestial-loader.is-igniting .celestial-loader__footer { opacity: 0; transform: translateY(.8rem); }
.celestial-loader.is-igniting .celestial-loader__core p,
.celestial-loader.is-igniting .celestial-loader__core strong { opacity: 0; transform: translateY(var(--exit, -.55rem)); }
.celestial-loader.is-igniting .celestial-loader__core strong { --exit: .55rem; }
.celestial-loader.is-igniting .celestial-loader__core img { filter: drop-shadow(0 0 3.8rem rgba(46,233,255,.62)) drop-shadow(0 0 6rem rgba(255,31,125,.44)); transform: scale(1.055); }
.celestial-loader.is-detonating .celestial-loader__bloom { opacity: .22; transform: scale(.72); transition: opacity 380ms ease, transform 620ms cubic-bezier(.76,0,.24,1); }
.celestial-loader.is-detonating .celestial-loader__nebula { filter: saturate(1.7) brightness(.82); transform: scale(1.1); }
.celestial-loader.is-exiting { opacity: 0; visibility: hidden; pointer-events: none; }

@keyframes loader-breathe {
  from { opacity: .52; transform: translate(-50%,-50%) rotate(var(--turn,0deg)) scale(.9); }
  to { opacity: 1; transform: translate(-50%,-50%) rotate(calc(var(--turn,0deg) + 10deg)) scale(1.09); }
}

@media (max-width: 720px) {
  .celestial-loader__core { width: min(86vw, 30rem); gap: .8rem; }
  .celestial-loader__core::before { width: 112%; height: 310%; }
  .celestial-loader__core p,
  .celestial-loader__core strong { font-size: .44rem; letter-spacing: .22em; }
  .celestial-loader__footer { grid-template-columns: 1fr; gap: .85rem; }
  .celestial-loader__meter { grid-template-columns: 1fr 2rem; }
  .celestial-loader__bloom::before,
  .celestial-loader__bloom::after,
  .celestial-loader__bloom i { width: 126vw; filter: blur(2.8rem); }
}

@media (prefers-reduced-motion: reduce) {
  .celestial-loader__bloom::before,
  .celestial-loader__bloom::after,
  .celestial-loader__bloom i { animation: none; }
  .celestial-loader,
  .celestial-loader__core img,
  .celestial-loader__footer { transition-duration: 180ms; }
}
