/* ═══════════════════════════════════════════
   SPA TRANSITIONS — Mobile-app-like page animations
   ═══════════════════════════════════════════ */

/* ── Viewport ── */
#spa-viewport {
  position: relative;
  overflow-x: hidden;

  /* iOS WKWebView: guarantee the viewport always participates in layout.
     Without this, WKWebView can "forget" to composite the viewport after
     child innerHTML swaps, causing a blank frame that never recovers. */
  min-height: 100dvh;
  min-height: 100vh; /* fallback for older iOS */
}

/* ── Page wrapper ──
   NO will-change at rest (breaks position:fixed inside — atmosphere, modals, overlays)
   will-change is applied ONLY on animation classes for the brief transition */
.spa-page {
  width: 100%;
}

.spa-page.active {
  position: relative;
}

/* ── Push: slide in from right ── */
@keyframes spaSlideInRight {
  from { transform: translateX(100%); }
  to   { transform: translateX(0); }
}
@keyframes spaSlideOutLeft {
  from { transform: translateX(0); opacity: 1; }
  to   { transform: translateX(-30%); opacity: .4; }
}

/* ── Pop: slide in from left ── */
@keyframes spaSlideInLeft {
  from { transform: translateX(-30%); opacity: .4; }
  to   { transform: translateX(0); opacity: 1; }
}
@keyframes spaSlideOutRight {
  from { transform: translateX(0); }
  to   { transform: translateX(100%); }
}

/* ── Tab switch: crossfade ── */
@keyframes spaFadeIn {
  from { opacity: 0; transform: scale(.98); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes spaFadeOut {
  from { opacity: 1; transform: scale(1); }
  to   { opacity: 0; transform: scale(.98); }
}

/* ── Animation classes ──
   will-change added here only (briefly, during the ~0.3s animation)
   so position:fixed inside the page works correctly at rest */
.spa-slide-in-right {
  will-change: transform;
  animation: spaSlideInRight .32s cubic-bezier(.22,.68,0,1) both;
  z-index: 2;
  box-shadow: -6px 0 32px rgba(0,0,0,.3);
}
.spa-slide-out-left {
  will-change: transform, opacity;
  position: absolute; top: 0; left: 0; width: 100%;
  animation: spaSlideOutLeft .32s cubic-bezier(.22,.68,0,1) both;
  z-index: 1;
}

.spa-slide-in-left {
  will-change: transform, opacity;
  position: absolute; top: 0; left: 0; width: 100%;
  animation: spaSlideInLeft .32s cubic-bezier(.22,.68,0,1) both;
  z-index: 1;
}
.spa-slide-out-right {
  will-change: transform;
  animation: spaSlideOutRight .32s cubic-bezier(.22,.68,0,1) both;
  z-index: 2;
  box-shadow: -6px 0 32px rgba(0,0,0,.3);
}

.spa-fade-in {
  will-change: opacity, transform;
  animation: spaFadeIn .22s ease both;
  z-index: 2;
}
.spa-fade-out {
  will-change: opacity, transform;
  position: absolute; top: 0; left: 0; width: 100%;
  animation: spaFadeOut .22s ease both;
  z-index: 1;
}

/* ═══════ NAV PROGRESS BAR ═══════
   A thin gradient line pinned to the top of the viewport. Shown for the
   brief window between tap → route ready. Gives the user instant visual
   feedback so navigation feels responsive even before the transition starts.
   Hidden by default; router toggles .active + .done to drive the animation. */
.spa-progress {
  position: fixed; top: 0; left: 0; right: 0;
  height: 2px; z-index: 9999;
  background: linear-gradient(90deg,
    transparent 0%,
    var(--accent, #6eb9ff) 40%,
    var(--accent, #6eb9ff) 60%,
    transparent 100%);
  background-size: 200% 100%;
  background-position: 100% 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity .1s ease;
}
.spa-progress.active {
  opacity: 1;
  animation: spaProgressSlide .8s linear infinite;
}
.spa-progress.done {
  opacity: 0;
  transition: opacity .2s ease .05s;
}
@keyframes spaProgressSlide {
  from { background-position: 100% 0; }
  to   { background-position: -100% 0; }
}
