/* ============================================================================
   Topology motif + motion
   Loaded last, inside @layer components so it overrides 04 at equal
   specificity without needing higher-specificity selectors.

   The diagram is not decoration: every node, link, peer ring and packet
   position is generated from data/network.json by
   views/partials/components/network-topology.ejs.
   ============================================================================ */

@layer components {

  /* --- The map --------------------------------------------------------- */

  .topo {
    margin: 0;
    padding: var(--space-s);
    border: 1px solid var(--rule);
    border-radius: var(--radius-l);
    background-color: var(--surface);
  }

  .topo__svg {
    display: block;
    inline-size: 100%;
    block-size: auto;
    overflow: visible;
  }

  /* Links carry a slow dash crawl so idle links still read as carrying
     traffic rather than as static decoration. */
  .topo__link {
    fill: none;
    stroke: var(--topo-line-strong);
    stroke-width: 1.25;
    stroke-linecap: round;
    stroke-dasharray: 5 7;
    animation: topo-crawl 1.6s linear infinite;
  }

  /* One packet per satellite, travelling core → node on the real link path.
     The path is passed in as --link-path from the template. */
  .topo__packet {
    fill: var(--topo-packet);
    offset-path: var(--link-path);
    offset-distance: 0%;
    offset-rotate: 0deg;
    animation: topo-packet 4.2s linear infinite;
    animation-delay: var(--link-delay, 0s);
  }

  /* The Bursa hub breathes slowly — the one element that is genuinely the
     centre of the network. */
  .topo__halo {
    fill: none;
    stroke: var(--topo-core-halo);
    stroke-width: 1.5;
    transform-box: fill-box;
    transform-origin: center;
    animation: topo-pulse 3.6s var(--ease) infinite;
  }
  .topo__halo--outer {
    animation-delay: 1.8s;
  }

  .topo__core-dot {
    fill: var(--topo-core);
  }

  .topo__dot {
    fill: var(--topo-node);
  }

  /* Peer orbits: radius encodes how many peers that PoP has. The rings are
     dashed so they never compete with the solid link lines. */
  .topo__peers {
    fill: none;
    stroke: var(--topo-peer);
    stroke-width: 1;
    stroke-dasharray: 2 5;
  }
  .topo__peers--inner {
    opacity: 0.6;
  }

  .topo__label {
    fill: var(--ink);
    font-family: var(--font-mono);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
  }
  .topo__label--core {
    fill: var(--brand-deep);
  }

  .topo__meta {
    fill: var(--ink-3);
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.02em;
  }

  /* Tall variant: labels sit to the right of the spine, so they are
     left-anchored rather than centred under the node. */
  .topo__label--tall,
  .topo__meta--tall {
    text-anchor: start;
  }

  @keyframes topo-crawl {
    to { stroke-dashoffset: -12; }
  }

  @keyframes topo-packet {
    from { offset-distance: 0%; }
    to   { offset-distance: 100%; }
  }

  @keyframes topo-pulse {
    0%   { transform: scale(0.72); opacity: 0.9; }
    70%  { transform: scale(1.18); opacity: 0; }
    100% { transform: scale(1.18); opacity: 0; }
  }

  /* --- Home hero: cold-aisle walk ----------------------------------------
     Canvas drawn by assets/ts/dgn/hero-aisle.ts. It fades in once the first
     frame is painted so a slow script never flashes an empty box.

     The service card sits over the lit racks. Its backdrop blur cannot reach
     them (the .rise animation on its parent is a backdrop root), so inside
     the hero the card carries a denser surface of its own instead. */

  .hero > .container {
    position: relative;
    z-index: 1;
  }

  .hero__aisle {
    display: block;
    inline-size: 100%;
    block-size: 100%;
    opacity: 0;
    transition: opacity 1.2s var(--ease);
  }
  .hero__aisle[data-ready="true"] {
    opacity: 1;
  }

  .hero .herocards__stack {
    background-color: rgb(10 16 14 / 0.88);
  }

  /* --- Motion budget ----------------------------------------------------
     Note: .rise is NOT defined here. 01-tokens.css:3 declares the layer
     order `tokens, base, layout, components, utilities`, so utilities is
     LAST — 05-utilities.css owns .rise and wins over anything this file
     puts in @layer components, regardless of which stylesheet loads later.
     An earlier duplicate lived here and was dead code; removing it changes
     nothing on screen. Entrance timing is a single source of truth in 05. */

  /* Interactive surfaces get one consistent hover response. */
  .herocard,
  .card,
  .price-card {
    transition:
      border-color var(--dur-fast) var(--ease),
      background-color var(--dur-fast) var(--ease),
      transform var(--dur-fast) var(--ease);
  }

  .herocard:hover,
  .card:hover,
  .price-card:hover {
    transform: translateY(-2px);
    border-color: var(--rule-strong);
  }

  .herocard:focus-visible,
  .card:focus-visible,
  .price-card:focus-visible {
    transform: none;
  }

  /* --- Responsive ------------------------------------------------------- */

  /* Both layouts render from the same data; only one is shown. A five-column
     map cannot keep legible labels below 48em, so the narrow viewport gets
     the vertical spine instead of losing the diagram altogether. */
  .topo__svg--tall { display: none; }

  @media (max-width: 48em) {
    .topo__svg--wide { display: none; }
    .topo__svg--tall { display: block; }
  }

  @media (min-width: 48em) {
    .topo { padding: var(--space-m); }
  }

  /* Narrow phones: beside the logo the nav bar has ~267px at 390 but needs
     ~276px. It used to scroll sideways inside its own box (overflow-x: auto),
     and on iOS Safari that scroll box clipped the dropdowns, so submenus
     never appeared. Instead the bar takes its own full-width row under the
     logo, where it fits down to 320px; it wraps rather than scrolls if a
     translation ever runs long. No overflow ancestor = nothing to clip. */
  @media (max-width: 30em) {
    .site-logo { grid-row: 1; }
    .site-nav {
      grid-column: 1 / -1;
      grid-row: 2;
      margin-inline-end: 0;
    }
    .site-nav__list {
      flex-wrap: wrap;
      padding-inline-end: 0;
    }
    .site-nav__list > li:first-child > .site-nav__link { padding-inline-start: 0; }
  }

  /* --- Contrast correction ------------------------------------------------
     04-components.css pins `color: #fff` on the pill CTA, which lands white
     on the lime brand at 2.33:1 — well under WCAG AA's 4.5:1. --on-brand is
     the near-black that pairs with lime; restore it at equal specificity,
     later layer. */
  .btn--primary.btn--pill,
  .btn--primary.btn--pill:hover {
    color: var(--on-brand);
  }

  /* Coarse pointers: hover lifts are noise, and the map's tap targets are
     decorative rather than actionable. */
  @media (pointer: coarse) {
    .herocard:hover,
    .card:hover,
    .price-card:hover {
      transform: none;
    }
  }

  /* --- Reduced motion --------------------------------------------------- */

  @media (prefers-reduced-motion: reduce) {
    /* Packets are meaningless when frozen, so they are removed rather than
       parked at the end of their path. */
    .topo__packet { display: none; }
    .topo__link,
    .topo__halo,
    .rise,
    .herocard,
    .card,
    .price-card {
      animation: none !important;
      transition: none !important;
      transform: none !important;
      opacity: 1 !important;
    }
  }
}