/* NetUtils — motion layer.
   Per-page hero backdrops, each unique:
   - info:    left-to-right flowing data packet streams (L→R, no shape)
   - tools:   network constellation mesh (nodes + edges + traveling pulses)
   Pricing page uses no backdrop (clean text only).
   Plus page-load reveals, scroll-triggered entrances, and micro-interactions.
   All motion is transform/opacity-only (GPU-composited) and disabled under
   prefers-reduced-motion. Kept separate from app.css for file-size hygiene. */

/* ───── motion backdrop container (shared) ───────────────── */
.motion-bg { position: relative; overflow: hidden; isolation: isolate; }
.motion-bg > .motion-backdrop { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.motion-bg > :not(.motion-backdrop) { position: relative; z-index: 2; }
.motion-backdrop { background: transparent; color: var(--accent); }

/* ───── info: "flow field" ─────────────────────────────────
   A canvas of accent particles streaming strictly left→right in depth-layered
   speed bands (motion.js → [data-info-flux]) — data racing down a cable. The
   stage is masked top-and-bottom so the field fades to the page background,
   leaving no hard edge and blending into the adjacent black section below. */
.info-flux-stage {
  position: absolute; inset: 0; pointer-events: none; isolation: isolate;
  -webkit-mask-image: linear-gradient(to bottom,
    transparent 0%, #000 20%, #000 80%, transparent 100%);
          mask-image: linear-gradient(to bottom,
    transparent 0%, #000 20%, #000 80%, transparent 100%);
}

/* The particle canvas itself — sized/painted by motion.js. */
.info-flux {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
}

/* ───── info "under the hood": halftone ripple field ──────
   A halftone dot field with expanding ripple wavefronts (painted by motion.js →
   [data-info-hex]) behind the white section content. The backdrop is masked
   top/bottom so the field fades into white, leaving no hard edge. */
.info-hex-host { position: relative; isolation: isolate; }
.info-hex-host > .section-inner { position: relative; z-index: 1; }
.info-hex-bg {
  position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden;
  opacity: 0.2;
  -webkit-mask-image: linear-gradient(to bottom,
    transparent 0%, #000 18%, #000 82%, transparent 100%);
          mask-image: linear-gradient(to bottom,
    transparent 0%, #000 18%, #000 82%, transparent 100%);
}
.info-hex-bg canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* ───── pricing FAQ: isometric terrain in the left column ──
   The terrain wave ([data-pricing-terrain]) sits behind the FAQ heading in the
   left column (to the left of the questions), filling its height. Masked
   top/bottom so it fades into white; the heading sits above it. Shown only on
   the two-column (desktop) layout, where the empty left column has room. */
.faq-head { position: relative; }
.faq-head > h2, .faq-head > p { position: relative; z-index: 1; }
.faq-terrain { display: none; }
@media (min-width: 1024px) {
  .faq-terrain {
    display: block; position: absolute; inset: 0; z-index: 0;
    pointer-events: none; overflow: hidden;
    -webkit-mask-image: linear-gradient(to bottom,
      transparent 0%, #000 14%, #000 86%, transparent 100%);
            mask-image: linear-gradient(to bottom,
      transparent 0%, #000 14%, #000 86%, transparent 100%);
  }
}
.faq-terrain canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* ───── tools: network constellation mesh ──────────────────
   Inline SVG of nodes + thin edges with pulsing nodes and a few
   "data" pulses traveling along select edges via CSS offset-path.
   Distributed across the full hero, no vignette, blends seamlessly
   into adjacent black sections. */
.tools-mesh {
  position: absolute; inset: 0; width: 100%; height: 100%;
  color: var(--accent);
  pointer-events: none;
}
.mesh-nodes .node {
  transform-box: fill-box;
  transform-origin: center;
  animation: node-pulse 4.2s ease-in-out infinite;
  will-change: opacity, transform;
}
.mesh-nodes .node:nth-child(3n)   { animation-delay: 1.4s; animation-duration: 5.0s; }
.mesh-nodes .node:nth-child(3n+1) { animation-delay: 0.6s; animation-duration: 4.6s; }
.mesh-nodes .node:nth-child(5n+2) { animation-delay: 2.2s; }
@keyframes node-pulse {
  0%, 100% { opacity: 0.55; transform: scale(1); }
  50%      { opacity: 1;    transform: scale(1.55); }
}
.mesh-pulses .pulse {
  offset-rotate: 0deg;
  animation: pulse-travel var(--dur, 6s) linear infinite;
  animation-delay: var(--delay, 0s);
  filter: drop-shadow(0 0 4px currentColor);
  will-change: offset-distance;
}
@keyframes pulse-travel {
  from { offset-distance: 0%;   opacity: 0; }
  8%   { opacity: 1; }
  92%  { opacity: 1; }
  to   { offset-distance: 100%; opacity: 0; }
}

/* ───── page-load reveal ─────────────────────────────────── */
.no-entrance .hero-inner > *,
.no-entrance .page-hero-inner > * {
  opacity: 1;
  transform: none;
  animation: none;
}
.hero-inner > *,
.page-hero-inner > * {
  opacity: 0;
  transform: translateY(14px);
  animation: load-rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
}
.hero-inner > *:nth-child(1) { animation-delay: 0.05s; }
.hero-inner > *:nth-child(2) { animation-delay: 0.12s; }
.hero-inner > *:nth-child(3) { animation-delay: 0.19s; }
.hero-inner > *:nth-child(4) { animation-delay: 0.26s; }
.hero-inner > *:nth-child(5) { animation-delay: 0.33s; }
.page-hero-inner > *:nth-child(1) { animation-delay: 0.04s; }
.page-hero-inner > *:nth-child(2) { animation-delay: 0.12s; }
.page-hero-inner > *:nth-child(3) { animation-delay: 0.20s; }
@keyframes load-rise {
  to { opacity: 1; transform: translateY(0); }
}

/* ───── scroll reveal ────────────────────────────────────── */
.reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 0.7s cubic-bezier(0.2, 0.7, 0.2, 1), transform 0.7s cubic-bezier(0.2, 0.7, 0.2, 1);
  will-change: opacity, transform;
}
.reveal.in { opacity: 1; transform: translateY(0); }

/* ───── micro-interactions ───────────────────────────────── */
.pillar, .tool-card, .tier, .endpoint, .fact-row {
  transition: background 160ms, transform 220ms cubic-bezier(0.2, 0.7, 0.2, 1);
}
.pillar:hover, .tier:hover {
  transform: translateY(-3px);
}
.btn { transition: opacity 140ms, background 140ms, color 140ms, transform 120ms; }
.btn:active { transform: translateY(1px); }
.icon-btn:active { transform: translateY(1px); }

/* ───── reduced motion ─────────────────────────────────────
   Freeze the backdrop to a static resting state (mesh visible, stream
   paused) so the hero never goes blank. */
@media (prefers-reduced-motion: reduce) {
  .mesh-nodes .node, .mesh-pulses .pulse { animation: none !important; }
  .hero-inner > *, .page-hero-inner > * { opacity: 1; transform: none; animation: none; }
  .reveal { opacity: 1; transform: none; transition: none; }
  .pillar, .tool-card, .tier, .endpoint, .fact-row, .btn, .icon-btn, .result-panel { transition: none; }
  .tool-card-trace rect { transition: none; }
  .result-panel { grid-template-rows: 0fr; }
  .result-panel[data-visible="true"] { grid-template-rows: 1fr; opacity: 1; }
}