/* ── Switchboard portal transition (#214) ────────────────────────────────────
   A gentle "fade + zoom through a field of lights" — the page eases away into a
   bloom of blinking dots (the switchboard powering up) and the destination eases
   back in. No swirl / black-hole — calmer, more "transported" than dizzy. Pure
   transform/opacity (GPU-composited). */

.portal {
  position: fixed; inset: 0; z-index: 2147483647; overflow: hidden;
  background: #05070c; opacity: 0; pointer-events: none;
  display: grid; place-items: center;
  --accent: #2f9bff; --accent2: #66bbff;
}
.portal.active { pointer-events: all; }

/* Faint starfield wash. */
.portal::before {
  content: ""; position: absolute; inset: -10%;
  background:
    radial-gradient(1.5px 1.5px at 20% 30%, rgba(255,255,255,.55), transparent),
    radial-gradient(1.5px 1.5px at 75% 18%, rgba(255,255,255,.4), transparent),
    radial-gradient(1.5px 1.5px at 60% 70%, rgba(255,255,255,.5), transparent),
    radial-gradient(1.5px 1.5px at 35% 80%, rgba(255,255,255,.35), transparent),
    radial-gradient(2px 2px at 12% 62%, color-mix(in srgb, var(--accent) 70%, transparent), transparent),
    radial-gradient(2px 2px at 50% 42%, color-mix(in srgb, var(--accent2) 60%, transparent), transparent);
  opacity: 0;
}

/* Soft accent bloom centred behind the dots — the "magic" glow. */
.portal-glow {
  position: absolute; width: 90vmax; height: 90vmax; border-radius: 50%;
  background: radial-gradient(circle, color-mix(in srgb, var(--accent) 26%, transparent) 0%, transparent 58%);
  opacity: 0; transform: scale(.6); mix-blend-mode: screen;
}

/* The dot grid — the star of the show. */
.portal-dots {
  position: absolute; inset: 0; display: grid; gap: clamp(14px, 4.5vmin, 40px);
  grid-auto-rows: 1fr; padding: 6vmin; place-items: center; opacity: 0;
}
.portal-dot {
  width: clamp(5px, 1.1vmin, 9px); height: clamp(5px, 1.1vmin, 9px); border-radius: 50%;
  background: var(--accent2); opacity: 0; transform: scale(.3);
  box-shadow: 0 0 10px var(--accent), 0 0 20px color-mix(in srgb, var(--accent) 55%, transparent);
}

/* ── EXIT (accelerate INTO the lights) ────────────────────────────────────────
   #218: one continuous journey, not the dots blinking twice. The dot FIELD warps
   toward the viewer (scale up) while the page zooms away — you fly into it. The
   entrance picks the same warp up and carries it THROUGH (the dots never re-blink;
   they're already lit and rush past). */
.portal.exit            { animation: portal-bg-in .42s ease forwards; }
.portal.exit::before    { animation: portal-stars-in .6s ease forwards; }
/* #219: after the warp-in lands, the glow + dot field keep gently breathing
   (infinite idle) instead of freezing — so while the destination page is still
   loading the portal stays ALIVE rather than showing a dead, stuttered frame. The
   idle pulses around scale(1.5), exactly where the entrance warp picks up, so the
   hand-off across the page swap is seamless. */
.portal.exit .portal-glow { animation: portal-glow-in .6s ease forwards, portal-glow-idle 2.6s .6s ease-in-out infinite; }
.portal.exit .portal-dots { animation: portal-dots-show .01s .06s forwards, portal-warp-in .62s cubic-bezier(.4,0,.75,1) forwards, portal-warp-idle 2.6s .68s ease-in-out infinite; }
.portal.exit .portal-dot  { animation: portal-dot-on .5s ease forwards; }   /* ripple on ONCE */
body.portal-leaving .app-shell, body.portal-leaving .wrap, body.portal-leaving .tabbar-m {
  animation: portal-zoom-away .58s cubic-bezier(.45,0,.55,1) forwards; transform-origin: center;
}

@keyframes portal-bg-in    { to { opacity: 1; } }
@keyframes portal-stars-in { 0% { opacity: 0; transform: scale(1.1); } 100% { opacity: .85; transform: scale(1); } }
@keyframes portal-glow-in  { 0% { opacity: 0; transform: scale(.6); } 100% { opacity: 1; transform: scale(1); } }
@keyframes portal-dots-show { to { opacity: 1; } }
@keyframes portal-dot-on   { 0% { opacity: 0; transform: scale(.3); } 55% { opacity: 1; transform: scale(1.35); } 100% { opacity: .9; transform: scale(1); } }
@keyframes portal-warp-in  { 0% { transform: scale(.55); } 100% { transform: scale(1.5); } }
/* #219: gentle "breathing" hold during the page load gap — keeps motion alive,
   centred on scale(1.5) so the entrance warp-through resumes without a visible jump. */
@keyframes portal-warp-idle { 0%, 100% { transform: scale(1.5); } 50% { transform: scale(1.64); } }
@keyframes portal-glow-idle { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .82; transform: scale(1.06); } }
@keyframes portal-zoom-away { to { transform: scale(1.14); opacity: 0; filter: blur(3px); } }

/* ── ENTRANCE (continue THROUGH the lights → page) ───────────────────────────── */
.portal.enter            { opacity: 1; }
.portal.enter::before    { animation: portal-stars-out 1.15s ease forwards; }
.portal.enter .portal-glow { animation: portal-glow-out 1.15s ease forwards; }
/* Dots are ALREADY lit (no re-blink) and keep warping past as they dissolve. */
.portal.enter .portal-dots { opacity: 1; animation: portal-warp-through 1.05s cubic-bezier(.3,0,.5,1) forwards; }
.portal.enter .portal-dot  { opacity: .9; transform: scale(1); }
.portal.enter.done       { animation: portal-fade-out .45s ease forwards; }
body.portal-arriving .app-shell, body.portal-arriving .wrap, body.portal-arriving .tabbar-m {
  animation: portal-zoom-in .62s cubic-bezier(.2,.7,.3,1) .42s both; transform-origin: center;
}

@keyframes portal-stars-out { 0% { opacity: .85; } 100% { opacity: 0; transform: scale(1.2); } }
@keyframes portal-glow-out  { 0% { opacity: 1; } 55% { opacity: 1; } 100% { opacity: 0; transform: scale(1.3); } }
@keyframes portal-warp-through { 0% { transform: scale(1.5); opacity: 1; } 100% { transform: scale(3.4); opacity: 0; } }
@keyframes portal-zoom-in   { 0% { transform: scale(1.12); opacity: 0; filter: blur(3px); } 100% { transform: none; opacity: 1; filter: none; } }
@keyframes portal-fade-out  { to { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  .portal, .portal *,
  body.portal-leaving .app-shell, body.portal-leaving .wrap, body.portal-leaving .tabbar-m,
  body.portal-arriving .app-shell, body.portal-arriving .wrap, body.portal-arriving .tabbar-m {
    animation-duration: .01ms !important; animation-delay: 0s !important;
  }
}
