.wasm-node[data-sector-portal="true"] .wasm-node__core {
  fill: #03040a;
  stroke: currentColor;
  stroke-width: 2.4;
}

.wasm-node[data-sector-portal="true"] .wasm-node__halo {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.2;
  stroke-dasharray: 3 5;
  opacity: .72;
}

/* WebGPU normally owns and therefore hides SVG soma geometry. A portal keeps
   its SVG core/halo as the black-hole skin over the ordinary GPU identity body;
   the canonical graph still carries only the underlying identity/org kind. */
.renderer-surface--wasm[data-renderer="webgpu"]
  .wasm-node[data-sector-portal="true"] .wasm-node__core,
.renderer-surface--wasm[data-renderer="webgpu"]
  .wasm-node[data-sector-portal="true"] .wasm-node__halo {
  opacity: 1;
}

.wasm-node[data-sector-portal="true"]:hover .wasm-node__halo,
.wasm-node[data-sector-portal="true"]:focus .wasm-node__halo,
.wasm-node[data-sector-portal="true"].wasm-node--selected .wasm-node__halo {
  opacity: 1;
  stroke-dasharray: 2 3;
}

/* The ordinary camera frames identity and organization bodies slightly larger
   than the repository minimum. These factors convert those exact existing
   focus diameters (18.8506587% and 17.4789078%) to the shared 32% portal
   contract without introducing a second layout or changing graph semantics. */
.renderer-surface--wasm[data-portal-focused="true"][data-portal-kind="identity"] {
  --portal-lens-scale: 1.6975534166;
}

.renderer-surface--wasm[data-portal-focused="true"][data-portal-kind="organization"] {
  --portal-lens-scale: 1.8307780032;
}

.renderer-surface--wasm .renderer-gpu-canvas,
.renderer-surface--wasm .renderer-svg-layer {
  transform-origin: 50% 50%;
  transition: transform 720ms cubic-bezier(.22, 1, .36, 1);
}

.renderer-surface--wasm[data-portal-focused="true"] .renderer-gpu-canvas,
.renderer-surface--wasm[data-portal-focused="true"] .renderer-svg-layer {
  transform: scale(var(--portal-lens-scale, 1));
}

/* Selection normally gives every soma a small local emphasis. A focused portal
   has an explicit 32% body contract, so the black-hole skin itself stays at
   scale 1 while the shared camera lens does the framing. */
.wasm-node[data-sector-portal="true"][data-portal-focused="true"] .wasm-node__core {
  fill: #000108;
  transform: scale(1);
}

.wasm-node[data-sector-portal="true"][data-portal-focused="true"] .wasm-node__halo {
  opacity: 1;
  stroke-dasharray: 1.5 3.5;
}

/* Step 3 is presentation-only matter. These particles are not graph nodes and
   are deliberately separate from the renderer's semantic asteroid type, where
   an asteroid means a failing check in flight. */
.portal-absorption {
  position: absolute;
  z-index: 4;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  contain: strict;
}

.portal-absorption__particle {
  --portal-angle: 0deg;
  --portal-duration: 3.8s;
  --portal-delay: 0s;
  position: absolute;
  width: .25rem;
  height: .25rem;
  border-radius: 999px;
  background: rgba(225, 234, 255, .92);
  box-shadow:
    0 0 .45rem rgba(146, 174, 255, .58),
    0 0 1rem rgba(82, 92, 255, .20);
  opacity: 0;
  transform: translate(-50%, -50%) rotate(var(--portal-angle)) scale(.72);
  animation: portal-infall var(--portal-duration) cubic-bezier(.34, .03, .72, .30)
    var(--portal-delay) infinite;
  will-change: left, top, transform, opacity;
}

.portal-absorption__particle--comet {
  width: .34rem;
  height: .34rem;
  background: rgba(245, 249, 255, .98);
  box-shadow:
    0 0 .6rem rgba(190, 218, 255, .78),
    0 0 1.4rem rgba(97, 132, 255, .28);
}

.portal-absorption__particle--comet::after {
  content: "";
  position: absolute;
  right: 48%;
  top: 50%;
  width: 2.2rem;
  height: 1px;
  transform: translateY(-50%);
  transform-origin: right center;
  background: linear-gradient(90deg, transparent, rgba(190, 218, 255, .62));
}

.portal-absorption__particle--asteroid {
  width: .42rem;
  height: .34rem;
  border-radius: 43% 57% 51% 49% / 58% 38% 62% 42%;
  background: rgba(106, 111, 128, .90);
  box-shadow:
    inset -.08rem -.08rem .12rem rgba(7, 8, 14, .55),
    0 0 .45rem rgba(163, 173, 210, .22);
}

.portal-absorption__particle:nth-child(1)  { left: 8%;  top: 7%;  --portal-angle: 43deg;  --portal-duration: 3.4s; --portal-delay: -1.7s; }
.portal-absorption__particle:nth-child(2)  { left: 28%; top: 2%;  --portal-angle: 66deg;  --portal-duration: 4.3s; --portal-delay: -3.2s; }
.portal-absorption__particle:nth-child(3)  { left: 49%; top: 5%;  --portal-angle: 89deg;  --portal-duration: 3.0s; --portal-delay: -.8s; }
.portal-absorption__particle:nth-child(4)  { left: 73%; top: 3%;  --portal-angle: 116deg; --portal-duration: 4.7s; --portal-delay: -2.6s; }
.portal-absorption__particle:nth-child(5)  { left: 92%; top: 12%; --portal-angle: 137deg; --portal-duration: 3.7s; --portal-delay: -1.1s; }
.portal-absorption__particle:nth-child(6)  { left: 98%; top: 32%; --portal-angle: 159deg; --portal-duration: 4.1s; --portal-delay: -3.5s; }
.portal-absorption__particle:nth-child(7)  { left: 95%; top: 56%; --portal-angle: 188deg; --portal-duration: 2.9s; --portal-delay: -1.9s; }
.portal-absorption__particle:nth-child(8)  { left: 98%; top: 80%; --portal-angle: 212deg; --portal-duration: 4.6s; --portal-delay: -.5s; }
.portal-absorption__particle:nth-child(9)  { left: 79%; top: 96%; --portal-angle: 239deg; --portal-duration: 3.3s; --portal-delay: -2.2s; }
.portal-absorption__particle:nth-child(10) { left: 58%; top: 99%; --portal-angle: 261deg; --portal-duration: 4.4s; --portal-delay: -3.8s; }
.portal-absorption__particle:nth-child(11) { left: 35%; top: 95%; --portal-angle: 289deg; --portal-duration: 3.1s; --portal-delay: -.9s; }
.portal-absorption__particle:nth-child(12) { left: 12%; top: 98%; --portal-angle: 314deg; --portal-duration: 4.8s; --portal-delay: -2.9s; }
.portal-absorption__particle:nth-child(13) { left: 3%;  top: 78%; --portal-angle: 337deg; --portal-duration: 3.6s; --portal-delay: -1.4s; }
.portal-absorption__particle:nth-child(14) { left: 6%;  top: 56%; --portal-angle: 8deg;   --portal-duration: 4.0s; --portal-delay: -3.0s; }
.portal-absorption__particle:nth-child(15) { left: 2%;  top: 35%; --portal-angle: 18deg;  --portal-duration: 3.2s; --portal-delay: -.3s; }
.portal-absorption__particle:nth-child(16) { left: 17%; top: 14%; --portal-angle: 48deg;  --portal-duration: 4.5s; --portal-delay: -2.4s; }
.portal-absorption__particle:nth-child(17) { left: 83%; top: 20%; --portal-angle: 132deg; --portal-duration: 3.5s; --portal-delay: -1.0s; }
.portal-absorption__particle:nth-child(18) { left: 88%; top: 72%; --portal-angle: 208deg; --portal-duration: 4.2s; --portal-delay: -3.4s; }
.portal-absorption__particle:nth-child(19) { left: 67%; top: 89%; --portal-angle: 249deg; --portal-duration: 2.8s; --portal-delay: -1.6s; }
.portal-absorption__particle:nth-child(20) { left: 42%; top: 93%; --portal-angle: 279deg; --portal-duration: 4.6s; --portal-delay: -3.9s; }
.portal-absorption__particle:nth-child(21) { left: 14%; top: 71%; --portal-angle: 331deg; --portal-duration: 3.4s; --portal-delay: -.7s; }
.portal-absorption__particle:nth-child(22) { left: 12%; top: 42%; --portal-angle: 12deg;  --portal-duration: 4.1s; --portal-delay: -2.7s; }
.portal-absorption__particle:nth-child(23) { left: 61%; top: 8%;  --portal-angle: 104deg; --portal-duration: 3.0s; --portal-delay: -1.3s; }
.portal-absorption__particle:nth-child(24) { left: 38%; top: 6%;  --portal-angle: 76deg;  --portal-duration: 4.9s; --portal-delay: -4.1s; }

/* Step 4 owns a cross-document transition rather than another graph body. The
   tunnel is intentionally state-driven: it may remain in-flight indefinitely
   while a cache miss is being prepared, but JS never allows arrival before the
   three-second product minimum. */
.portal-flight {
  position: fixed;
  z-index: 10000;
  inset: 0;
  overflow: hidden;
  isolation: isolate;
  background: #000108;
  pointer-events: auto;
  color: rgba(229, 236, 255, .9);
}

.portal-flight__void,
.portal-flight__tunnel,
.portal-flight__streaks,
.portal-flight__grain,
.portal-flight__message {
  position: absolute;
  inset: 0;
}

.portal-flight__void {
  z-index: 4;
  left: 50%;
  top: 50%;
  right: auto;
  bottom: auto;
  width: 34vmin;
  height: 34vmin;
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 50%, #000 0 38%, rgba(3, 4, 13, .96) 39% 56%, transparent 70%);
  box-shadow:
    0 0 5vmin rgba(119, 101, 198, .24),
    0 0 13vmin rgba(62, 73, 170, .16),
    0 0 26vmin rgba(22, 27, 64, .18);
  transform: translate(-50%, -50%) scale(.92);
  animation: portal-flight-void 1.35s cubic-bezier(.22, 1, .36, 1) infinite alternate;
}

.portal-flight__tunnel {
  z-index: 2;
  inset: -45%;
  opacity: .86;
  background:
    repeating-conic-gradient(
      from 4deg at 50% 50%,
      rgba(192, 208, 255, .0) 0deg 3deg,
      rgba(172, 191, 255, .35) 3.2deg 3.55deg,
      rgba(68, 81, 174, .08) 3.8deg 8deg
    );
  -webkit-mask-image: radial-gradient(circle, transparent 0 10%, #000 19% 76%, transparent 88%);
  mask-image: radial-gradient(circle, transparent 0 10%, #000 19% 76%, transparent 88%);
  transform-origin: 50% 50%;
  animation: portal-flight-spin 8s linear infinite;
}

.portal-flight__streaks {
  z-index: 3;
  inset: -25%;
  opacity: .76;
  background:
    repeating-conic-gradient(
      from 17deg at 50% 50%,
      transparent 0deg 7deg,
      rgba(226, 236, 255, .7) 7.08deg 7.16deg,
      transparent 7.28deg 14deg
    );
  -webkit-mask-image: radial-gradient(circle, transparent 0 15%, #000 29% 83%, transparent 93%);
  mask-image: radial-gradient(circle, transparent 0 15%, #000 29% 83%, transparent 93%);
  transform: scale(.74) rotate(0deg);
  transform-origin: 50% 50%;
  animation: portal-flight-dive .88s cubic-bezier(.33, .02, .64, 1) infinite;
}

.portal-flight__grain {
  z-index: 5;
  opacity: .16;
  background-image:
    radial-gradient(circle at 15% 27%, rgba(255, 255, 255, .65) 0 .7px, transparent .9px),
    radial-gradient(circle at 72% 38%, rgba(183, 202, 255, .55) 0 .8px, transparent 1px),
    radial-gradient(circle at 41% 77%, rgba(255, 255, 255, .42) 0 .6px, transparent .8px);
  background-size: 31px 37px, 43px 47px, 53px 59px;
  animation: portal-flight-grain .72s steps(2, end) infinite;
}

.portal-flight__message {
  z-index: 7;
  inset: auto 1.5rem 2rem;
  display: grid;
  place-items: center;
  min-height: 1.5rem;
  opacity: 0;
  font: inherit;
  letter-spacing: .02em;
  text-align: center;
  transition: opacity 180ms ease;
}

.portal-flight[data-phase="entering"] {
  animation: portal-flight-cover 420ms cubic-bezier(.2, .8, .2, 1) both;
}

.portal-flight[data-phase="entering"] .portal-flight__void {
  animation:
    portal-flight-engulf 420ms cubic-bezier(.16, .84, .28, 1) both,
    portal-flight-void 1.35s cubic-bezier(.22, 1, .36, 1) 420ms infinite alternate;
}

.portal-flight[data-phase="in-flight"] .portal-flight__tunnel,
.portal-flight[data-phase="in-flight"] .portal-flight__streaks {
  opacity: 1;
}

.portal-flight[data-source="cache"] .portal-flight__grain {
  opacity: .11;
}

.portal-flight[data-prepared-nodes="0"] .portal-flight__void {
  box-shadow:
    0 0 5vmin rgba(119, 101, 198, .18),
    0 0 16vmin rgba(39, 49, 135, .13);
}

.portal-flight[data-phase="landing"] {
  pointer-events: none;
  animation: portal-flight-land 900ms cubic-bezier(.22, 1, .36, 1) both;
}

.portal-flight[data-phase="landing"] .portal-flight__tunnel {
  animation: portal-flight-unwind 900ms cubic-bezier(.22, 1, .36, 1) both;
}

.portal-flight[data-phase="landing"] .portal-flight__streaks {
  animation: portal-flight-release 900ms cubic-bezier(.22, 1, .36, 1) both;
}

.portal-flight[data-phase="failed"] {
  background: rgba(0, 1, 8, .96);
}

.portal-flight[data-phase="failed"] .portal-flight__tunnel,
.portal-flight[data-phase="failed"] .portal-flight__streaks,
.portal-flight[data-phase="failed"] .portal-flight__grain {
  animation-play-state: paused;
  opacity: .1;
}

.portal-flight[data-phase="failed"] .portal-flight__message {
  opacity: 1;
}

@media (prefers-reduced-motion: no-preference) {
  .wasm-node[data-sector-portal="true"]:hover .wasm-node__halo,
  .wasm-node[data-sector-portal="true"]:focus .wasm-node__halo,
  .wasm-node[data-sector-portal="true"][data-portal-focused="true"] .wasm-node__halo {
    animation: portal-ring 1.8s linear infinite;
  }
}

@media (prefers-reduced-motion: reduce) {
  .renderer-surface--wasm .renderer-gpu-canvas,
  .renderer-surface--wasm .renderer-svg-layer {
    transition: none;
  }

  .portal-absorption {
    display: none;
  }

  .portal-flight,
  .portal-flight__void,
  .portal-flight__tunnel,
  .portal-flight__streaks,
  .portal-flight__grain {
    animation: none !important;
  }

  .portal-flight__tunnel,
  .portal-flight__streaks {
    opacity: .18;
  }

  .portal-flight[data-phase="landing"] {
    opacity: 0;
    transition: opacity 300ms ease;
  }
}

@keyframes portal-ring {
  to { stroke-dashoffset: -16; }
}

@keyframes portal-infall {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%) rotate(var(--portal-angle)) scale(.68);
  }
  12% {
    opacity: .88;
  }
  66% {
    opacity: .98;
    transform: translate(-50%, -50%) rotate(calc(var(--portal-angle) + 24deg)) scale(1);
  }
  88% {
    opacity: .74;
  }
  100% {
    left: 50%;
    top: 50%;
    opacity: 0;
    transform: translate(-50%, -50%) rotate(calc(var(--portal-angle) + 112deg)) scale(.04);
  }
}

@keyframes portal-flight-cover {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes portal-flight-engulf {
  from { transform: translate(-50%, -50%) scale(.88); }
  to { transform: translate(-50%, -50%) scale(5.8); }
}

@keyframes portal-flight-void {
  from { transform: translate(-50%, -50%) scale(5.55); }
  to { transform: translate(-50%, -50%) scale(5.95); }
}

@keyframes portal-flight-spin {
  to { transform: rotate(360deg); }
}

@keyframes portal-flight-dive {
  0% {
    opacity: .36;
    transform: scale(.68) rotate(0deg);
  }
  48% { opacity: 1; }
  100% {
    opacity: .46;
    transform: scale(1.28) rotate(6deg);
  }
}

@keyframes portal-flight-grain {
  0% { transform: translate3d(0, 0, 0); }
  50% { transform: translate3d(-7px, 5px, 0); }
  100% { transform: translate3d(4px, -6px, 0); }
}

@keyframes portal-flight-land {
  0%, 32% { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes portal-flight-unwind {
  from {
    opacity: 1;
    transform: rotate(0deg) scale(1.12);
  }
  to {
    opacity: 0;
    transform: rotate(-34deg) scale(2.2);
  }
}

@keyframes portal-flight-release {
  from {
    opacity: 1;
    transform: scale(1.22) rotate(0deg);
  }
  to {
    opacity: 0;
    transform: scale(2.7) rotate(-8deg);
  }
}
