.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; }

@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;
  }
}

@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);
  }
}
