/* ==========================================================================
   Astrodyne — Orbital Infrastructure identity layer (Phase 2)
   ==========================================================================

   Loaded AFTER astrodyne.css. Phase 4A.2 built a correct design system; this
   file gives it a point of view. It is kept separate rather than merged so the
   art direction can be read, reviewed and reverted as one thing.

   The idea: Astrodyne sits between developers and model providers the way an
   orbital relay sits between ground stations. Everything here — the palette
   ladder, the background fields, the motion vocabulary — is in service of
   convergence: many trajectories resolving into one.

   Restraint is the brief. High-end aerospace instrumentation, not a space
   poster. Illumination, not saturation.

   Contents: palette · elevation ladder · background fields · section rhythm ·
   navigation · typography · motion vocabulary · microinteractions
   ========================================================================== */

/* ── 1. PALETTE ────────────────────────────────────────────────────────────
   The previous ladder was four blue-greys within ~8 points of lightness of one
   another, which is why every section bled into the next: the surfaces were
   nominally different and perceptually identical. This ladder has visible
   steps, and it moves the whole page from neutral charcoal toward deep navy so
   the cyan reads as illumination rather than as the only colour present.

   Brighter, but still unmistakably a dark site — the lift is in the mid
   surfaces, not the base. */
:root {
  /* base ladder — each step is a perceptible increment, not a nudge */
  --ad-bg:          #0B111C;   /* deep navy base                              */
  --ad-bg-raised:   #121B29;   /* midnight blue — alternating section surface */
  --ad-bg-panel:    #1A2434;   /* panels, cards                               */
  --ad-bg-elevated: #24314A;   /* selected / featured                         */
  --ad-bg-inset:    #070C14;   /* code wells, insets — deeper than the base   */

  /* ink — off-white through cool silver to steel */
  --ad-ink:       #EDF2F9;
  --ad-ink-soft:  #C4CFDD;
  --ad-ink-mute:  #93A1B5;

  /* accents. Cyan is the principal; the rest exist to give depth to
     illumination and are never used for text. */
  --ad-accent:    #8FD9E8;   /* icy cyan                                     */
  --ad-signal:    #58B6FF;   /* electric blue — signals, active paths        */
  --ad-steel:     #6E86A8;   /* desaturated steel — inert structure          */
  --ad-indigo:    #4C55B8;   /* glow only                                    */
  --ad-violet:    #7B6BD6;   /* glow only, sparingly                         */

  --ad-accent-soft:   rgba(143, 217, 232, 0.10);
  --ad-accent-border: rgba(143, 217, 232, 0.30);
  --ad-signal-soft:   rgba(88, 182, 255, 0.14);

  /* edges: a cool tint so borders read as lit rather than as grey lines */
  --ad-edge:        rgba(176, 206, 240, 0.16);
  --ad-edge-soft:   rgba(176, 206, 240, 0.095);
  --ad-edge-strong: rgba(176, 206, 240, 0.30);
  --ad-edge-lit:    rgba(143, 217, 232, 0.42);

  --ad-glass:   rgba(180, 208, 240, 0.045);
  --ad-glass-2: rgba(180, 208, 240, 0.075);

  /* one shared curve for everything that moves */
  --ad-ease-orbit: cubic-bezier(0.22, 0.61, 0.28, 1);

  --ad-nav-h: 72px;
}

/* ── 2. PAGE WASH ──────────────────────────────────────────────────────────
   The fixed wash is the deepest layer: a cool light source above the fold, a
   very restrained indigo counterweight to the right, and a horizon glow that
   keeps the bottom of the page from going flat. Radial only — no linear
   gradient runs the length of the page, because that is what makes a dark site
   look like a template. */
body.ad .ad-bg-wash {
  background:
    radial-gradient(1250px 780px at 16% -10%, rgba(88, 182, 255, 0.115), transparent 62%),
    radial-gradient(1000px 700px at 88%  -4%, rgba(76,  85, 184, 0.085), transparent 58%),
    radial-gradient(1500px 620px at 50% 104%, rgba(143, 217, 232, 0.065), transparent 66%),
    var(--ad-bg);
}

/* Grain. Barely there — it exists so large flat gradients do not band on
   8-bit displays, which is the single most common way a dark site looks
   cheap. Fixed so it does not travel with scroll. */
body.ad::after {
  content: "";
  position: fixed; inset: 0; z-index: -1;
  pointer-events: none; opacity: 0.032;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ── 3. BACKGROUND FIELDS ──────────────────────────────────────────────────
   A reusable vocabulary rather than one-off decoration. A section opts in with
   data-field="…" and gets an engineered texture that is masked to fade before
   it reaches the text column.

   Every field is drawn on the section's ::after at z-index 0 with the content
   above it, and every one is masked. Nothing here may reduce contrast on a
   word. */
section[data-field] { position: relative; isolation: isolate; overflow: hidden; }
section[data-field] > * { position: relative; z-index: 1; }
section[data-field]::after {
  content: "";
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none;
}

/* grid field — an instrument backdrop. 72px cells, faded from the top. */
section[data-field="grid"]::after {
  background-image:
    linear-gradient(var(--ad-edge-soft) 1px, transparent 1px),
    linear-gradient(90deg, var(--ad-edge-soft) 1px, transparent 1px);
  background-size: 72px 72px;
  opacity: 0.5;
  -webkit-mask-image: radial-gradient(120% 90% at 50% 0%, #000 12%, transparent 72%);
          mask-image: radial-gradient(120% 90% at 50% 0%, #000 12%, transparent 72%);
}

/* star coordinates — sparse, fixed, and deliberately irregular. Not a
   starfield texture: eighteen points, most of them nearly invisible, so it
   reads as a plotted chart rather than as wallpaper. */
section[data-field="stars"]::after,
section[data-field="orbit"]::after {
  background-image:
    radial-gradient(1.4px 1.4px at 12% 22%, rgba(199, 226, 255, 0.60), transparent 100%),
    radial-gradient(1px   1px   at 27% 71%, rgba(199, 226, 255, 0.30), transparent 100%),
    radial-gradient(1.2px 1.2px at 41% 12%, rgba(143, 217, 232, 0.45), transparent 100%),
    radial-gradient(1px   1px   at 56% 84%, rgba(199, 226, 255, 0.26), transparent 100%),
    radial-gradient(1.6px 1.6px at 68% 34%, rgba(199, 226, 255, 0.52), transparent 100%),
    radial-gradient(1px   1px   at 79% 62%, rgba(143, 217, 232, 0.34), transparent 100%),
    radial-gradient(1.2px 1.2px at 88% 17%, rgba(199, 226, 255, 0.40), transparent 100%),
    radial-gradient(1px   1px   at 94% 78%, rgba(199, 226, 255, 0.22), transparent 100%),
    radial-gradient(1px   1px   at 6%  56%, rgba(143, 217, 232, 0.28), transparent 100%),
    radial-gradient(1.3px 1.3px at 34% 44%, rgba(199, 226, 255, 0.20), transparent 100%);
  -webkit-mask-image: linear-gradient(180deg, transparent, #000 22%, #000 78%, transparent);
          mask-image: linear-gradient(180deg, transparent, #000 22%, #000 78%, transparent);
}

/* orbital arcs — three concentric hairlines struck from a centre well below
   the section, so what appears is the top of a very large orbit. Gradient
   rings rather than bordered circles: one paint, no layout. */
section[data-field="orbit"]::before {
  content: "";
  position: absolute; left: 50%; bottom: -78%;
  width: 190%; aspect-ratio: 1; transform: translateX(-50%);
  z-index: 0; pointer-events: none;
  background:
    radial-gradient(circle, transparent 49.55%, rgba(176, 206, 240, 0.13) 49.6%,
                    rgba(176, 206, 240, 0.13) 49.75%, transparent 49.8%),
    radial-gradient(circle, transparent 41.55%, rgba(143, 217, 232, 0.10) 41.6%,
                    rgba(143, 217, 232, 0.10) 41.75%, transparent 41.8%),
    radial-gradient(circle, transparent 33.55%, rgba(176, 206, 240, 0.075) 33.6%,
                    rgba(176, 206, 240, 0.075) 33.75%, transparent 33.8%);
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 30%, transparent 62%);
          mask-image: radial-gradient(circle at 50% 50%, #000 30%, transparent 62%);
}

/* horizon glow — a light source sitting just under the section's lower edge.
   Used where a band needs to feel like it opens onto something. */
section[data-field="horizon"]::after {
  background: radial-gradient(90% 62% at 50% 118%, rgba(88, 182, 255, 0.16),
                              rgba(76, 85, 184, 0.06) 42%, transparent 70%);
}

/* localized illumination — a soft source behind a signature composition */
section[data-field="glow"]::after {
  background: radial-gradient(52% 62% at 62% 44%, rgba(143, 217, 232, 0.11),
                              transparent 68%);
}

/* ── 4. SECTION RHYTHM ─────────────────────────────────────────────────────
   Two problems at once: sections bled together, and the page was 11,700px for
   an amount of information that does not warrant it. Both were caused by the
   same thing — every section was the same colour with the same enormous
   uniform padding.

   Fix: alternate the surface so adjacent sections separate on tone, and cut
   the default band padding by roughly a third. Signature sections keep their
   air; connective ones do not get to. */
.ad-band       { padding-block: clamp(56px, 6vw, 92px); }
.ad-band-tight { padding-block: clamp(36px, 4vw, 60px); }
.ad-section    { padding-block: clamp(56px, 6vw, 92px); }
.ad-section-sm { padding-block: clamp(40px, 4.5vw, 68px); }

/* a signature section earns more room */
.ad-band[data-weight="signature"] { padding-block: clamp(76px, 8vw, 124px); }

/* Alternating surfaces. `ad-surface` paints a section one step up from the
   page and gives it lit hairlines top and bottom, so the boundary is a
   deliberate edge rather than an accident of spacing. */
.ad-surface {
  background:
    linear-gradient(180deg, rgba(176, 206, 240, 0.028), transparent 38%),
    var(--ad-bg-raised);
  border-top: 1px solid var(--ad-edge-soft);
  border-bottom: 1px solid var(--ad-edge-soft);
}
.ad-surface-deep {
  background:
    linear-gradient(180deg, rgba(7, 12, 20, 0.55), transparent 40%),
    var(--ad-bg-inset);
  border-top: 1px solid var(--ad-edge-soft);
  border-bottom: 1px solid var(--ad-edge-soft);
}

/* The old hairline-between-every-band rule turned a device into wallpaper once
   surfaces started alternating. Retired here rather than deleted upstream so
   the Phase 4A.2 sheet stays intact for other pages. */
.ad-band + .ad-band::before,
.ad-band + .ad-section-sm::before,
.ad-section-sm + .ad-band::before { display: none !important; }

.ad-band-head { margin-bottom: clamp(28px, 3vw, 44px); }

/* ── 5. NAVIGATION ─────────────────────────────────────────────────────────
   The floating pill was translucent enough that section eyebrows and headings
   read straight through it while scrolling, which produced the collisions in
   review. Rather than make the pill more opaque and heavier, the nav now has
   two states:

     at rest (hero)  — floating pill, generous, part of the composition
     scrolled        — a full-width bar: slimmer, stronger backdrop, one lit
                       hairline at its lower edge, and a short scrim above the
                       content so nothing shows through the glass

   The transition is the nav settling onto the page, which is also the first
   piece of motion a visitor sees. */
.ad-nav { top: 0; padding: 14px var(--ad-s5) 0; transition: padding .32s var(--ad-ease-orbit); }
.ad-nav-inner {
  transition: padding .32s var(--ad-ease-orbit), max-width .32s var(--ad-ease-orbit),
              border-radius .32s var(--ad-ease-orbit), background .32s var(--ad-ease-orbit),
              box-shadow .32s var(--ad-ease-orbit), border-color .32s var(--ad-ease-orbit);
}
.ad-nav[data-scrolled="true"] { padding: 0; }
.ad-nav[data-scrolled="true"] .ad-nav-inner {
  max-width: 100%;
  padding: 8px clamp(12px, 3vw, 40px) 8px clamp(16px, 3.4vw, 44px);
  border-radius: 0;
  border-color: transparent;
  border-bottom-color: var(--ad-edge);
  background: linear-gradient(180deg, #0F1622 0%, #0B111C 100%);
  backdrop-filter: blur(26px) saturate(170%);
  -webkit-backdrop-filter: blur(26px) saturate(170%);
  box-shadow: 0 14px 38px -24px rgba(0, 0, 0, 0.95),
              0 1px 0 0 var(--ad-edge-soft);
}
/* the scrim: without it, a bright heading passing under 78%-opaque glass still
   shows as a ghost. Ten pixels of gradient is enough to kill it. */
.ad-nav[data-scrolled="true"] .ad-nav-inner::after {
  content: "";
  position: absolute; left: 0; right: 0; top: 100%; height: 10px;
  background: linear-gradient(180deg, rgba(11, 17, 28, 0.45), transparent);
  pointer-events: none;
}
.ad-nav-inner { position: relative; }

/* Anchors must not land under the bar. */
html { scroll-behavior: smooth; scroll-padding-top: 96px; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
section[id] { scroll-margin-top: 96px; }

/* ── 6. TYPOGRAPHY ─────────────────────────────────────────────────────────
   The serif was carrying every section headline, which meant no section
   outranked any other. Serif is now reserved for the hero and for genuine
   chapter statements; connective sections get a sans headline that is
   confident but visibly subordinate.

   Opt in with .ad-h2-sans on the heading. */
.ad-h2-sans {
  font-family: var(--ad-font-body);
  font-weight: 600;
  font-size: clamp(23px, 2.5vw, 31px);
  line-height: 1.18;
  letter-spacing: -0.018em;
  margin: 0;
  color: var(--ad-ink);
}
.ad-h3-sans {
  font-family: var(--ad-font-body);
  font-weight: 600;
  font-size: var(--ad-text-md);
  line-height: 1.3;
  letter-spacing: -0.01em;
  margin: 0;
}
/* Eyebrows sit closer to their heading than they did; at 0.3em tracking and
   full section spacing they were reading as their own element. */
.ad-eyebrow { margin-bottom: var(--ad-s3); letter-spacing: 0.22em; }

/* ── 7. MOTION VOCABULARY ──────────────────────────────────────────────────
   Four verbs, used everywhere, so motion means something:

     orbit    — slow rotation of a whole system
     traverse — a signal moving along a defined path
     draw     — a stroke being struck, once, on entry
     settle   — an element arriving (this replaces generic fade-up)

   Nothing bounces, nothing zooms, nothing loops faster than the eye relaxes
   into it. Everything is disabled under prefers-reduced-motion. */
@keyframes ad-orbit    { to   { transform: rotate(360deg); } }
@keyframes ad-traverse { from { offset-distance: 0%; } to { offset-distance: 100%; } }
@keyframes ad-draw     { to   { stroke-dashoffset: 0; } }
@keyframes ad-breathe  { 0%, 100% { opacity: .35; } 50% { opacity: .85; } }
@keyframes ad-drift    { 0%, 100% { transform: translate3d(0,0,0); }
                         50%      { transform: translate3d(0, -7px, 0); } }

/* `settle` is the entrance for everything: a short rise with no scale change,
   because scale is what makes an entrance feel like a template. */
.ad-reveal {
  opacity: 0; transform: translate3d(0, 14px, 0);
  transition: opacity .62s var(--ad-ease-orbit), transform .62s var(--ad-ease-orbit);
}
.ad-reveal[data-shown="true"] { opacity: 1; transform: none; }
.ad-reveal-1 { transition-delay: 70ms; }
.ad-reveal-2 { transition-delay: 140ms; }
.ad-reveal-3 { transition-delay: 210ms; }
.ad-reveal-4 { transition-delay: 280ms; }

/* Stroke drawing for any SVG path that should be struck rather than appear.
   The resting state is the finished drawing, so no-JS and reduced-motion both
   get a complete diagram. */
.ad-draw { stroke-dasharray: var(--len, 600); stroke-dashoffset: 0; }
[data-animate="true"] .ad-draw {
  stroke-dashoffset: var(--len, 600);
  animation: ad-draw 1.15s var(--ad-ease-orbit) forwards;
  animation-delay: var(--d, 0ms);
}

@media (prefers-reduced-motion: reduce) {
  .ad-reveal { opacity: 1; transform: none; transition: none; }
  .ad-draw, [data-animate="true"] .ad-draw { animation: none; stroke-dashoffset: 0; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* ── 8. MICROINTERACTIONS ──────────────────────────────────────────────────
   Illumination rather than movement. A control should look like it received
   power, not like it jumped. */
.ad-btn {
  position: relative;
  transition: transform .2s var(--ad-ease-orbit), box-shadow .2s var(--ad-ease-orbit),
              border-color .2s var(--ad-ease-orbit), background .2s var(--ad-ease-orbit);
}
.ad-btn-primary:hover {
  box-shadow: 0 16px 40px -18px rgba(0, 0, 0, .75),
              0 0 0 1px rgba(143, 217, 232, .40),
              0 0 26px -6px rgba(143, 217, 232, .34),
              inset 0 1px 0 rgba(255, 255, 255, .7);
}
.ad-btn-ghost:hover, .ad-btn-quiet:hover {
  border-color: var(--ad-edge-lit);
  box-shadow: 0 0 22px -10px rgba(143, 217, 232, .45);
}
.ad-btn:active { transform: translateY(0); }

/* Focus: one ring, everywhere, that is legible on every surface in the ladder. */
a:focus-visible, button:focus-visible, summary:focus-visible,
[role="radio"]:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--ad-accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* Cards lift on a lit edge rather than a shadow. */
.ad-card-hover:hover { border-color: var(--ad-edge-lit); transform: translateY(-2px); }

/* Links in body copy get an underline that draws in from the left. */
.ad-band p a:not(.ad-btn), .ad-lede a:not(.ad-btn) {
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0% 1px; background-repeat: no-repeat;
  background-position: 0 100%;
  transition: background-size .28s var(--ad-ease-orbit), color .2s var(--ad-ease-orbit);
}
.ad-band p a:not(.ad-btn):hover, .ad-lede a:not(.ad-btn):hover { background-size: 100% 1px; }

/* ── 9. BRAND MARKS ────────────────────────────────────────────────────────
   Recurring notation that rewards a second look: coordinate labels, telemetry
   ticks, node identifiers. Used sparingly and always decorative. */
.ad-coord {
  font-family: var(--ad-font-mono);
  font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ad-ink-mute); opacity: .62;
}
.ad-tick {
  display: inline-block; width: 1px; height: 8px;
  background: var(--ad-edge-strong); vertical-align: middle;
}
.ad-rule-lit {
  height: 1px; border: 0; margin: 0;
  background: linear-gradient(90deg, transparent, var(--ad-edge-lit) 18%,
                              var(--ad-edge) 50%, var(--ad-edge-soft) 82%, transparent);
}

/* ══════════════════════════════════════════════════════════════════════════
   HERO — orbital infrastructure
   ══════════════════════════════════════════════════════════════════════════
   Four kinds of motion, all slow enough to be felt rather than watched:

     satellites  traverse their trajectory       28–44s
     signals     travel a route, then rest        6–7s cycle, ~2s in flight
     core        breathes                             7s
     outer ring  turns once                         140s

   Everything is a stroke-dashoffset or an opacity. No transforms on paint-
   heavy elements, no filters, nothing that forces a layout. The whole figure
   composites on the GPU and idles at effectively zero cost.
   ═══════════════════════════════════════════════════════════════════════ */

.ad-orb { margin: 0; }
.ad-orb-svg { width: 100%; height: auto; display: block; overflow: visible; }

/* ── trajectories ──────────────────────────────────────────────────────── */
.ad-orb-ring {
  fill: none;
  stroke: var(--ad-edge-strong);
  stroke-width: 1;
  opacity: 0.7;
}
.ad-orb-ring-4 {
  stroke: var(--ad-edge-soft);
  transform-box: view-box;
  transform-origin: 272px 210px;
  animation: ad-orbit 140s linear infinite;
}

.ad-orb-sat {
  fill: none;
  stroke: var(--ad-accent);
  stroke-width: 3.4;
  stroke-linecap: round;
  stroke-dasharray: 4 996;
  opacity: 0.75;
  animation: ad-sat 34s linear infinite;
}
.ad-orb-sat-1 { animation-duration: 44s; }
.ad-orb-sat-2 { animation-duration: 34s; animation-delay: -12s; opacity: .6; }
.ad-orb-sat-3 { animation-duration: 28s; animation-delay: -20s; opacity: .5; }

/* ── inbound routes ────────────────────────────────────────────────────── */
.ad-orb-route {
  fill: none;
  stroke: var(--ad-steel);
  stroke-width: 1.4;
  opacity: 0.95;
}
.ad-orb-pulse {
  fill: none;
  stroke: var(--ad-signal);
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-dasharray: 22 978;
  opacity: 0;
}
[data-animate="true"] .ad-orb-pulse { animation: ad-signal 6.4s linear infinite; }
.ad-orb-pulse-1 { animation-delay: 1.8s; }
.ad-orb-pulse-2 { animation-delay: 3.9s; }
.ad-orb-pulse-3 { animation-delay: 5.6s; }

/* ── the core ──────────────────────────────────────────────────────────── */
.ad-orb-halo { animation: ad-breathe 7s ease-in-out infinite; }
.ad-orb-core-ring {
  fill: none;
  stroke: var(--ad-accent);
  stroke-width: 1.1;
  opacity: 0.55;
}
.ad-orb-core-ring-2 {
  opacity: 0.22;
  stroke-dasharray: 3 7;
}
.ad-orb-core { fill: var(--ad-accent); }
.ad-orb-core-label {
  font-family: var(--ad-font-mono);
  font-size: 10px;
  letter-spacing: 0.26em;
  fill: var(--ad-ink-soft);
}

/* ── the single integration ────────────────────────────────────────────── */
.ad-orb-trunk { fill: none; stroke-width: 1.6; }
.ad-orb-trunk-pulse {
  fill: none;
  stroke: var(--ad-accent);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-dasharray: 30 970;
  opacity: 0;
}
[data-animate="true"] .ad-orb-trunk-pulse {
  animation: ad-signal 6.4s linear infinite;
  animation-delay: 2.9s;
}

.ad-orb-tick line { stroke: var(--ad-edge-strong); stroke-width: 1; }
.ad-orb-tick text {
  font-family: var(--ad-font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  fill: var(--ad-ink-mute);
}
.ad-orb-endpoint { fill: var(--ad-signal); }
.ad-orb-endpoint-halo { fill: var(--ad-signal); opacity: 0.14; }
.ad-orb-endpoint-label {
  font-family: var(--ad-font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  fill: var(--ad-ink-mute);
}

/* ── family nodes ──────────────────────────────────────────────────────── */
.ad-orb-node-halo { fill: var(--ad-accent); opacity: 0.09; }
.ad-orb-node-disc {
  fill: var(--ad-bg-panel);
  stroke: var(--ad-edge-strong);
  stroke-width: 1.1;
}
.ad-orb-node-dot { fill: var(--ad-accent); }
.ad-orb-node-label {
  font-family: var(--ad-font-body);
  font-size: 14.5px;
  font-weight: 500;
  fill: var(--ad-ink-soft);
}

/* ── mission-control notation ──────────────────────────────────────────── */
.ad-orb-coord {
  font-family: var(--ad-font-mono);
  font-size: 9px;
  letter-spacing: 0.2em;
  fill: var(--ad-ink-mute);
  opacity: 0.5;
}

/* ── legend ────────────────────────────────────────────────────────────── */
.ad-hero-legend {
  display: flex; flex-wrap: wrap; gap: var(--ad-s6);
  margin-top: var(--ad-s5);
  font-family: var(--ad-font-mono); font-size: var(--ad-text-xs);
  color: var(--ad-ink-mute);
}
.ad-hero-legend span { display: flex; align-items: center; gap: var(--ad-s2); }
.ad-orb-key {
  width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex: none;
}
.ad-orb-key-node { background: var(--ad-bg-panel); border: 1px solid var(--ad-edge-strong); }
.ad-orb-key-core { background: var(--ad-accent); box-shadow: 0 0 8px -1px var(--ad-accent); }

/* ── entrance ──────────────────────────────────────────────────────────── */
/* Resting state is the finished diagram. The build only ever subtracts, and
   only once data-animate is set — which the controller does after asking about
   reduced motion. */
[data-sequence][data-animate="true"] .ad-orb-node,
[data-sequence][data-animate="true"] .ad-orb-tick,
[data-sequence][data-animate="true"] .ad-orb-arrive {
  animation: ad-arrive .6s var(--ad-ease-orbit) backwards;
  animation-delay: var(--d, 0ms);
}
[data-sequence][data-animate="true"] .ad-orb-arrive { animation-delay: 1500ms; }

@keyframes ad-arrive { from { opacity: 0; } to { opacity: 1; } }
@keyframes ad-sat    { from { stroke-dashoffset: 1000; } to { stroke-dashoffset: 0; } }
/* A signal is in flight for about a third of its cycle and absent for the
   rest. A continuous stream of dots is a crypto graphic; an occasional one is
   telemetry. */
@keyframes ad-signal {
  0%   { stroke-dashoffset: 0;     opacity: 0; }
  4%   { opacity: 0.95; }
  30%  { stroke-dashoffset: -1000; opacity: 0.95; }
  36%  { stroke-dashoffset: -1000; opacity: 0; }
  100% { stroke-dashoffset: -1000; opacity: 0; }
}

/* ── responsive ────────────────────────────────────────────────────────────
   A 600-unit viewBox rendered 390px wide scales an 11px label to 7px, which is
   how a diagram "simply shrinks until unreadable". Below 900px the labels are
   set in larger user units so they land at a real size, the decorative
   notation and the outer ring are dropped, and the composition keeps only what
   it needs to still say: families in orbit, one core, one integration. */
@media (max-width: 900px) {
  .ad-orb-node-label   { font-size: 17px; }
  .ad-orb-core-label   { font-size: 14px; letter-spacing: .22em; }
  .ad-orb-tick text    { font-size: 13px; }
  .ad-orb-endpoint-label { font-size: 13px; }
  .ad-orb-coord        { display: none; }
  .ad-orb-ring-4       { display: none; }
  .ad-orb-sat-3        { display: none; }
  .ad-hero-legend      { gap: var(--ad-s4); font-size: 11px; }
}

@media (prefers-reduced-motion: reduce) {
  .ad-orb-sat, .ad-orb-pulse, .ad-orb-trunk-pulse,
  .ad-orb-halo, .ad-orb-ring-4 { animation: none !important; }
  /* A satellite that cannot travel should still be somewhere on its orbit
     rather than absent, and a signal that cannot travel should simply not be
     drawn — a stationary dot mid-route would read as a stalled request. */
  .ad-orb-sat { stroke-dashoffset: 250; }
  .ad-orb-pulse, .ad-orb-trunk-pulse { opacity: 0; }
  .ad-orb-halo { opacity: 0.7; }
}

/* ══════════════════════════════════════════════════════════════════════════
   MODEL ECOSYSTEM — families on a shared trajectory
   ══════════════════════════════════════════════════════════════════════════ */

.ad-constellation {
  position: relative;
  margin-top: clamp(28px, 3.5vw, 48px);
  padding-block: var(--ad-s4);
}

/* The trajectory. Brightest in the middle, resolving to nothing at both ends,
   so it reads as a segment of something longer rather than as a divider. */
.ad-constellation-line {
  position: absolute; left: 0; right: 0; top: 44px; height: 1px;
  background: linear-gradient(90deg, transparent, var(--ad-edge) 12%,
                              var(--ad-edge-lit) 50%, var(--ad-edge) 88%, transparent);
  transform-origin: 50% 50%;
  transform: scaleX(1);
}
[data-sequence][data-animate="true"] .ad-constellation-line {
  animation: ad-strike .9s var(--ad-ease-orbit) backwards;
}
@keyframes ad-strike { from { transform: scaleX(0); opacity: 0; } to { transform: scaleX(1); opacity: 1; } }

.ad-families {
  list-style: none; margin: 0; padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr));
  gap: var(--ad-s6);
  position: relative;
}

.ad-family {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--ad-s2); text-align: center; min-width: 0;
}
[data-sequence][data-animate="true"] .ad-family {
  animation: ad-arrive .55s var(--ad-ease-orbit) backwards;
  animation-delay: calc(340ms + var(--i) * 110ms);
}

/* The mark slot. Sized and styled now so an official wordmark can be dropped
   in without touching layout: 56px disc, 26px artwork, centred. */
.ad-family-mark {
  width: 56px; height: 56px; border-radius: 50%;
  display: grid; place-items: center;
  color: var(--ad-accent);
  background:
    radial-gradient(circle at 50% 32%, rgba(143, 217, 232, 0.10), transparent 70%),
    var(--ad-bg-raised);
  border: 1px solid var(--ad-edge);
  box-shadow: 0 0 0 6px var(--ad-bg-raised);
  transition: border-color .25s var(--ad-ease-orbit),
              box-shadow .25s var(--ad-ease-orbit), color .25s var(--ad-ease-orbit);
}
.ad-family:hover .ad-family-mark {
  border-color: var(--ad-edge-lit);
  color: var(--ad-ink);
  box-shadow: 0 0 0 6px var(--ad-bg-raised), 0 0 26px -6px rgba(143, 217, 232, .5);
}

.ad-family-name {
  font-size: var(--ad-text-md); font-weight: 600; color: var(--ad-ink);
  letter-spacing: -0.01em; margin-top: var(--ad-s2);
}
.ad-family-count {
  font-family: var(--ad-font-mono); font-size: var(--ad-text-xs);
  color: var(--ad-ink-mute); line-height: 1.5;
}
.ad-family-coord { margin-top: var(--ad-s1); }
.ad-family-note {
  margin: clamp(24px, 3vw, 36px) 0 0;
  font-size: var(--ad-text-xs); max-width: 68ch;
}

.ad-section-lede { color: var(--ad-ink-soft); line-height: 1.65; max-width: 68ch; margin: var(--ad-s3) 0 0; }
.ad-code-inline  { font-family: var(--ad-font-mono); font-size: 0.9em; color: var(--ad-ink); }

/* At one column the trajectory is meaningless — three marks stacked vertically
   are not on a shared line, and drawing one through them would be decoration
   claiming to be information. */
@media (max-width: 620px) {
  .ad-constellation-line { display: none; }
  .ad-families { gap: var(--ad-s8); }
  .ad-family-mark { box-shadow: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   ECONOMICS — fragmentation vs convergence
   ══════════════════════════════════════════════════════════════════════════
   Left is inert: steel strokes, no illumination, four of everything. Right is
   powered: one lit core, one trunk, the same reach. The two panels differ in
   TEMPERATURE, not in decoration — nothing on the right is bigger or busier,
   it is simply the only side with a light source.
   ═══════════════════════════════════════════════════════════════════════ */

.adc-node      { fill: var(--ad-bg-panel); stroke: var(--ad-edge-strong); stroke-width: 1.1; }
.adc-node-dot  { fill: var(--ad-steel); }
.adc-node-ours { stroke: var(--ad-accent-border); }
.adc-node-dot-ours { fill: var(--ad-accent); }

.adc-lane-line { stroke: var(--ad-steel); stroke-width: 1.1; opacity: .8; fill: none; }
.adc-tick      { stroke: var(--ad-edge-strong); stroke-width: 1.1; }
.adc-tick-ours { stroke: var(--ad-accent-border); }
.adc-end       { fill: var(--ad-bg-panel); stroke: var(--ad-edge-strong); stroke-width: 1.15; }
.adc-end-ours  { stroke: var(--ad-accent-border); }

.adc-trunk { stroke: var(--ad-accent); stroke-width: 1.6; opacity: .9; fill: none; }
.adc-route { stroke: var(--ad-signal); stroke-width: 1.2; opacity: .75; fill: none; }

.adc-core-halo { fill: var(--ad-accent); opacity: .10; }
.adc-core-ring { fill: none; stroke: var(--ad-accent); stroke-width: 1.1; opacity: .5; }
.adc-core      { fill: var(--ad-accent); }

.adc-axis {
  font-family: var(--ad-font-mono); font-size: 9px; letter-spacing: .1em;
  fill: var(--ad-ink-mute); opacity: .75;
}
.adc-axis-ours { fill: var(--ad-accent); opacity: .9; }

/* ── the build ─────────────────────────────────────────────────────────────
   Left lanes arrive one at a time — four identical events, which is how the
   drawing says "you do this again for every provider". Right converges in one
   gesture, because that is the whole difference. */
[data-sequence][data-animate="true"] .adc-lane .ad-draw {
  animation-delay: calc(160ms + var(--i) * 260ms);
}
[data-sequence][data-animate="true"] .adc-lane .adc-node,
[data-sequence][data-animate="true"] .adc-lane .adc-node-dot,
[data-sequence][data-animate="true"] .adc-lane .adc-tick,
[data-sequence][data-animate="true"] .adc-lane .adc-end {
  animation: ad-arrive .4s var(--ad-ease-orbit) backwards;
  animation-delay: calc(160ms + var(--i) * 260ms);
}
[data-sequence][data-animate="true"] .adc-core-halo,
[data-sequence][data-animate="true"] .adc-core-ring,
[data-sequence][data-animate="true"] .adc-core {
  animation: ad-arrive .7s var(--ad-ease-orbit) backwards;
  animation-delay: 620ms;
}
[data-sequence][data-animate="true"] .adc-end-ours {
  animation: ad-arrive .5s var(--ad-ease-orbit) backwards;
  animation-delay: 1500ms;
}
[data-sequence][data-animate="true"] .adc-core-halo { animation-name: ad-core-in; }
@keyframes ad-core-in { from { opacity: 0; } to { opacity: .10; } }

/* The panels themselves: the Astrodyne side sits one step up the ladder and
   carries a lit top edge, so it reads as the elevated option before a word of
   it is read. */
.ad-compare-panel {
  background: var(--ad-bg-panel);
  border: 1px solid var(--ad-edge-soft);
  border-radius: var(--ad-r-lg);
  padding: var(--ad-s6);
}
.ad-compare-panel-ours {
  background:
    linear-gradient(180deg, rgba(143, 217, 232, .07), transparent 42%),
    var(--ad-bg-elevated);
  border-color: var(--ad-accent-border);
  box-shadow: 0 0 0 1px rgba(143, 217, 232, .06),
              0 34px 80px -46px rgba(0, 0, 0, .9);
}
.ad-compare-title { font-family: var(--ad-font-body); font-weight: 600; font-size: var(--ad-text-md); margin: 0; }
.ad-compare-sub   { margin: var(--ad-s1) 0 0; color: var(--ad-ink-mute); font-size: var(--ad-text-sm); }
/* Axis labels sit under the leftmost node, which puts them a couple of units
   outside the viewBox. The panel has padding for them; the SVG just has to
   stop clipping. */
.ad-compare-svg   { width: 100%; height: auto; display: block; margin: var(--ad-s6) 0 0;
                    overflow: visible; }

@media (prefers-reduced-motion: reduce) {
  [data-sequence][data-animate="true"] .adc-core-halo { opacity: .10; }
}

/* ══════════════════════════════════════════════════════════════════════════
   COST EXPLORER — a financial instrument, including when it has no figure
   ══════════════════════════════════════════════════════════════════════════ */

/* ── the picker ────────────────────────────────────────────────────────── */
.adx-cost-opt {
  display: flex; align-items: center; gap: var(--ad-s3);
  position: relative;
  background: var(--ad-bg-raised);
  border: 1px solid var(--ad-edge-soft);
  border-radius: var(--ad-r-sm);
  transition: background .2s var(--ad-ease-orbit), border-color .2s var(--ad-ease-orbit);
}
/* The selected row is marked by a lit edge, not by a fill: a fill competes with
   the readout beside it, and an edge is how the rest of the page marks state. */
.adx-cost-opt::before {
  content: ""; position: absolute; left: 0; top: 12%; bottom: 12%; width: 2px;
  border-radius: 2px; background: var(--ad-accent);
  opacity: 0; transition: opacity .2s var(--ad-ease-orbit);
}
.adx-cost-opt[data-selected="true"]::before { opacity: 1; }
.adx-cost-opt[data-selected="true"] {
  background: var(--ad-bg-panel);
  border-color: var(--ad-accent-border);
}
.adx-cost-opt-mark { display: grid; place-items: center; color: var(--ad-ink-mute); flex: none; }
.adx-cost-opt[data-selected="true"] .adx-cost-opt-mark { color: var(--ad-accent); }
.adx-cost-opt-name { flex: 1 1 auto; text-align: left; min-width: 0; }
.adx-cost-opt-fam  { flex: none; }

/* ── status ────────────────────────────────────────────────────────────── */
.adx-cost-state {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--ad-font-mono); font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; white-space: nowrap;
  color: var(--ad-ink-mute);
  border: 1px solid var(--ad-edge); border-radius: var(--ad-r-pill);
  padding: 5px 11px;
  background: var(--ad-bg-inset);
}
.adx-cost-state-dot {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  background: var(--ad-warm);
  box-shadow: 0 0 8px -1px var(--ad-warm);
  animation: ad-breathe 3.4s ease-in-out infinite;
}

/* ── value wells ───────────────────────────────────────────────────────────
   The honest empty state, made deliberate. A well is a real inset with a real
   label — not a skeleton bar, which would promise a figure is loading, and not
   an em-dash, which reads as missing. */
.adx-well {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 92px; padding: 5px 12px;
  font-family: var(--ad-font-mono); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ad-ink-mute);
  background: var(--ad-bg-inset);
  border: 1px dashed var(--ad-edge);
  border-radius: var(--ad-r-sm);
}
.adx-well-lg { min-width: 132px; padding: 9px 16px; font-size: 12px; }

/* ── the difference ────────────────────────────────────────────────────── */
.adx-cost-delta {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--ad-s4); flex-wrap: wrap;
  margin-top: var(--ad-s5); padding: var(--ad-s5);
  border-radius: var(--ad-r);
  background:
    linear-gradient(180deg, rgba(143, 217, 232, .06), transparent 60%),
    var(--ad-bg-inset);
  border: 1px solid var(--ad-accent-border);
}
.adx-cost-delta-label {
  font-family: var(--ad-font-mono); font-size: 11px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ad-ink-soft);
}
.adx-cost-delta-val { font-size: var(--ad-text-lg); color: var(--ad-ink); }

/* ── the mechanism rail ────────────────────────────────────────────────── */
.adx-cost-how {
  list-style: none; margin: var(--ad-s5) 0 0; padding: 0;
  display: grid; gap: 0;
  position: relative;
}
.adx-cost-how li {
  display: grid; grid-template-columns: 22px 1fr; align-items: start;
  gap: var(--ad-s3);
  padding: 9px 0 9px 0;
  font-size: var(--ad-text-sm); color: var(--ad-ink-mute);
}
/* The connecting line makes three steps a sequence rather than a list. */
.adx-cost-how li:not(:last-child)::before {
  content: ""; position: absolute; left: 10px; width: 1px;
  margin-top: 26px; height: 22px;
  background: var(--ad-edge);
}
.adx-cost-how li { position: relative; }
.adx-cost-how b {
  width: 21px; height: 21px; border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--ad-font-mono); font-size: 10px; font-weight: 400;
  color: var(--ad-accent);
  background: var(--ad-bg-inset);
  border: 1px solid var(--ad-accent-border);
}

@media (max-width: 720px) {
  .adx-cost-delta { flex-direction: column; align-items: flex-start; gap: var(--ad-s3); }
  .adx-well { min-width: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .adx-cost-state-dot { animation: none; opacity: .9; }
}

/* ══════════════════════════════════════════════════════════════════════════
   INTEGRATION — the migration, as an instrument
   ══════════════════════════════════════════════════════════════════════════ */

.adx-morph {
  max-width: 820px; margin-inline: auto;
  background:
    linear-gradient(180deg, rgba(143, 217, 232, .045), transparent 34%),
    var(--ad-bg-inset);
  border: 1px solid var(--ad-accent-border);
  border-radius: var(--ad-r-lg);
  padding: 0 0 var(--ad-s5);
  overflow: hidden;
  box-shadow: 0 34px 80px -50px rgba(0, 0, 0, .95);
}
.adx-morph-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--ad-s4);
  padding: 11px var(--ad-s6);
  font-family: var(--ad-font-mono); font-size: 10px; letter-spacing: .22em;
  text-transform: uppercase; color: var(--ad-ink-mute);
  border-bottom: 1px solid var(--ad-edge-soft);
  background: linear-gradient(90deg, rgba(143, 217, 232, .07), transparent 60%);
}
.adx-morph-count { color: var(--ad-accent); letter-spacing: .16em; }

.adx-morph-line { padding-inline: var(--ad-s6); }
/* An introduced line carries a lit edge for as long as the page is open. The
   sweep is a moment; the edge is the emphasis the brief asked for. */
.adx-morph-in {
  position: relative;
  box-shadow: inset 2px 0 0 var(--ad-accent);
  background: linear-gradient(90deg, rgba(143, 217, 232, .05), transparent 52%);
}
.adx-morph-foot {
  padding-inline: var(--ad-s6);
  border-top: 1px solid var(--ad-edge-soft);
  padding-top: var(--ad-s4); margin-top: var(--ad-s3);
}

/* The transform. One pass of light across each new value, 620ms after the
   block settles, and never again. The old lines stay: the whole claim is about
   what did not change, so removing the "before" would remove the argument. */
.adx-morph-new { position: relative; }
.adx-morph-new::after {
  content: ""; position: absolute; inset: -2px -6px; border-radius: 4px;
  background: linear-gradient(90deg, transparent,
              rgba(143, 217, 232, .30) 45%, rgba(143, 217, 232, .05) 70%, transparent);
  opacity: 0; pointer-events: none;
}
.adx-morph[data-swapped="true"] .adx-morph-new::after {
  animation: ad-sweep 1.05s var(--ad-ease-orbit) forwards;
}
.adx-morph[data-swapped="true"] .adx-morph-line:nth-of-type(5) .adx-morph-new::after {
  animation-delay: 200ms;
}
@keyframes ad-sweep {
  0%   { opacity: 0; transform: translateX(-42%); }
  22%  { opacity: 1; }
  78%  { opacity: 1; }
  100% { opacity: 0; transform: translateX(42%); }
}

/* ══════════════════════════════════════════════════════════════════════════
   HOW IT WORKS — three drawings
   ══════════════════════════════════════════════════════════════════════════ */

.ad-steps {
  display: grid; gap: var(--ad-s5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}
.ad-step {
  padding: var(--ad-s6);
  border-radius: var(--ad-r-lg);
  background: var(--ad-bg-panel);
  border: 1px solid var(--ad-edge-soft);
  transition: border-color .25s var(--ad-ease-orbit);
}
.ad-step:hover { border-color: var(--ad-edge); }
.ad-step-fig { margin: calc(var(--ad-s2) * -1) 0 var(--ad-s5); }
.ad-step-fig svg { width: 100%; height: auto; display: block; }
.ad-step .ad-flow-num { display: block; }
.ad-step .ad-card-title { margin: 6px 0 var(--ad-s2); }

.ad-step-node  { fill: var(--ad-bg-inset); stroke: var(--ad-edge-strong); stroke-width: 1.1; }
.ad-step-dot   { fill: var(--ad-steel); }
.ad-step-line  { stroke: var(--ad-accent); stroke-width: 1.4; fill: none; opacity: .85; }
.ad-step-tick  { stroke: var(--ad-edge-strong); stroke-width: 1.1; }
.ad-step-halo  { fill: var(--ad-accent); opacity: .11; }
.ad-step-core  { fill: var(--ad-accent); }
.ad-step-orbit { fill: none; stroke: var(--ad-edge); stroke-width: 1; opacity: .8; }
.ad-step-pick  { stroke: var(--ad-accent-border); }
.ad-step-base  { stroke: var(--ad-edge); stroke-width: 1; }
.ad-step-bar   { stroke: var(--ad-steel); stroke-width: 2.6; stroke-linecap: round; opacity: .75; }

/* The build. Step 1 draws its route, step 2's selection lights, step 3's
   telemetry rises left to right and resolves into one charged event. */
[data-sequence][data-animate="true"] .ad-step-bar {
  animation: ad-rise .42s var(--ad-ease-orbit) backwards;
  animation-delay: calc(320ms + var(--i) * 55ms);
  transform-box: fill-box; transform-origin: 50% 100%;
}
@keyframes ad-rise { from { transform: scaleY(0); opacity: 0; } to { transform: scaleY(1); opacity: .75; } }

[data-sequence][data-animate="true"] .ad-step-pick-halo,
[data-sequence][data-animate="true"] .ad-step-pick,
[data-sequence][data-animate="true"] .ad-step .ad-step-core {
  animation: ad-arrive .55s var(--ad-ease-orbit) backwards;
  animation-delay: 700ms;
}
[data-sequence][data-animate="true"] .ad-step-pick-halo { animation-name: ad-halo-in; }
@keyframes ad-halo-in { from { opacity: 0; } to { opacity: .11; } }

@media (prefers-reduced-motion: reduce) {
  .adx-morph[data-swapped="true"] .adx-morph-new::after { animation: none; opacity: 0; }
  [data-sequence][data-animate="true"] .ad-step-bar { animation: none; transform: none; opacity: .75; }
  [data-sequence][data-animate="true"] .ad-step-pick-halo { animation: none; opacity: .11; }
}

/* ══════════════════════════════════════════════════════════════════════════
   CONSOLE — a device, not another card
   ══════════════════════════════════════════════════════════════════════════ */

.adx-console-frame {
  position: relative;
  border-radius: calc(var(--ad-r-xl) + 8px);
  padding: 8px;
  background: linear-gradient(180deg, var(--ad-edge) 0%, transparent 40%),
              var(--ad-bg-raised);
  box-shadow: 0 60px 120px -50px rgba(0, 0, 0, .95),
              0 0 0 1px var(--ad-edge-soft);
}
/* The light behind the screen. It sits outside the frame so the panel appears
   to be lit from behind rather than to be glowing itself. */
.adx-console-frame::before {
  content: ""; position: absolute; inset: -14% -8% -22%; z-index: -1;
  border-radius: 50%;
  background: radial-gradient(50% 46% at 50% 50%, rgba(88, 182, 255, .16),
                              rgba(76, 85, 184, .06) 46%, transparent 72%);
  pointer-events: none;
}
.adx-console {
  border-radius: var(--ad-r-xl);
  overflow: hidden;
  border: 1px solid var(--ad-edge);
  background: var(--ad-bg-panel);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06);
}

/* One slow pass of light across the most recent row. It carries no data: the
   figures never change, and the "Illustrative data" badge is part of the
   chrome. A number that moved would read as a measurement. */
.adx-tr-latest { position: relative; overflow: hidden; }
.adx-tr-latest::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(90deg, transparent,
              rgba(143, 217, 232, .10) 45%, transparent 72%);
  animation: ad-scan 7.5s var(--ad-ease-orbit) infinite;
}
@keyframes ad-scan {
  0%   { transform: translateX(-100%); opacity: 0; }
  8%   { opacity: 1; }
  34%  { transform: translateX(100%); opacity: 1; }
  40%, 100% { transform: translateX(100%); opacity: 0; }
}

/* ══════════════════════════════════════════════════════════════════════════
   COST MECHANICS — the chain
   ══════════════════════════════════════════════════════════════════════════ */

.ad-chain {
  list-style: none; margin: 0 0 clamp(32px, 4vw, 52px); padding: 0;
  display: grid; gap: var(--ad-s3);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 168px), 1fr));
}
.ad-chain-step {
  position: relative;
  padding: var(--ad-s4) var(--ad-s4) var(--ad-s4) var(--ad-s5);
  border-radius: var(--ad-r-sm);
  background: var(--ad-bg-panel);
  border: 1px solid var(--ad-edge-soft);
  display: flex; flex-direction: column; gap: 3px; min-width: 0;
}
/* Each stage is marked by a tick on its leading edge, and the last one — the
   bill — is the only one lit. The chain reads toward the thing it costs. */
.ad-chain-mark {
  position: absolute; left: 0; top: 22%; bottom: 22%; width: 2px;
  border-radius: 2px; background: var(--ad-edge-strong);
}
.ad-chain-step-end { border-color: var(--ad-accent-border); background: var(--ad-bg-elevated); }
.ad-chain-step-end .ad-chain-mark { background: var(--ad-accent); }
.ad-chain-k { font-size: var(--ad-text-sm); font-weight: 600; color: var(--ad-ink); line-height: 1.3; }
.ad-chain-v { font-family: var(--ad-font-mono); font-size: 11px; color: var(--ad-ink-mute); line-height: 1.4; }
[data-sequence][data-animate="true"] .ad-chain-step {
  animation: ad-arrive .5s var(--ad-ease-orbit) backwards;
  animation-delay: calc(120ms + var(--i) * 110ms);
}

/* The panels below tighten to three columns so the 4+2 grid stops leaving a
   hole in the bottom right. */
.ad-cost-grid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); }

/* ══════════════════════════════════════════════════════════════════════════
   AGENTS — the execution map
   ══════════════════════════════════════════════════════════════════════════ */

.adx-ag-line { fill: none; stroke: var(--ad-edge-strong); stroke-width: 1; opacity: .85; }
.adx-ag-return { stroke: var(--ad-signal); opacity: .6; }

[data-sequence][data-animate="true"] .adx-ag-step rect,
[data-sequence][data-animate="true"] .adx-ag-step text {
  animation: ad-arrive .45s var(--ad-ease-orbit) backwards;
  animation-delay: calc(320ms + var(--ags-i) * 90ms);
}
/* The calls arrive as a burst per step — that is the "dozens" in the headline,
   and it has to feel like more than one event. */
[data-sequence][data-animate="true"] .adx-ag-call {
  animation: ad-arrive .3s var(--ad-ease-orbit) backwards;
  animation-delay: calc(760ms + var(--ags-i) * 90ms + var(--agc-i) * 55ms);
}
[data-sequence][data-animate="true"] .adx-ag-sink rect,
[data-sequence][data-animate="true"] .adx-ag-sink text,
[data-sequence][data-animate="true"] .adx-ag-sink line {
  animation: ad-arrive .6s var(--ad-ease-orbit) backwards;
  animation-delay: 1600ms;
}

@media (prefers-reduced-motion: reduce) {
  .adx-tr-latest::after { animation: none; opacity: 0; }
}

/* ── the cost chain reads as a chain ────────────────────────────────────────
   Five boxes in a row are five boxes. A chevron in each gap makes them a
   sequence. Only drawn while the strip is genuinely on one line — once it
   wraps, a connector points at nothing. */
@media (min-width: 980px) {
  .ad-chain-step:not(:last-child)::after {
    content: "";
    position: absolute; right: -10px; top: 50%;
    width: 6px; height: 6px; margin-top: -3px;
    border-right: 1px solid var(--ad-edge-strong);
    border-top: 1px solid var(--ad-edge-strong);
    transform: rotate(45deg);
  }
}

/* ── spacing: connective sections give up their air ─────────────────────────
   Whitespace should be deliberate rather than uniform. The three sections that
   simply carry the reader from one argument to the next lose about a third of
   their padding; the signature sections keep theirs. */
#how-it-works, #capabilities, #catalog-preview { padding-block: clamp(34px, 3.6vw, 56px); }
#integration { padding-block: clamp(40px, 4.2vw, 64px); }
#model-families { padding-block: clamp(36px, 4vw, 60px); }

/* Adjacent sections on the same surface do not each need a full band of air
   between them — the boundary is already carrying it. */
.ad-surface + .ad-surface { border-top: 0; }

/* ── FAQ: grouping, not decoration ─────────────────────────────────────────
   Fast, crawlable and accessible is the whole brief here. The only changes are
   hierarchy: a group title that reads as a label rather than a heading, rows
   that separate on a hairline, and a disclosure that lights on hover. */
.adx-faq-group-title {
  font-family: var(--ad-font-mono); font-size: 11px; letter-spacing: .2em;
  text-transform: uppercase; color: var(--ad-accent);
  margin: 0 0 var(--ad-s3);
}
.adx-faq-item {
  border-bottom: 1px solid var(--ad-edge-soft);
  transition: background .2s var(--ad-ease-orbit);
}
.adx-faq-item:hover { background: var(--ad-glass); }
.adx-faq-item[open] { background: var(--ad-glass); }
.adx-faq-item summary { cursor: pointer; }
.adx-faq-chev { transition: transform .24s var(--ad-ease-orbit); color: var(--ad-ink-mute); }
.adx-faq-item[open] .adx-faq-chev { transform: rotate(180deg); color: var(--ad-accent); }

/* ── closing ───────────────────────────────────────────────────────────────
   The last thing on the page is the same idea as the first, so it gets the
   same light. */
.adx-close-card {
  position: relative;
  background:
    linear-gradient(180deg, rgba(143, 217, 232, .06), transparent 44%),
    var(--ad-bg-panel);
  border: 1px solid var(--ad-edge);
  border-radius: var(--ad-r-xl);
  box-shadow: 0 50px 110px -60px rgba(0, 0, 0, .95);
}
.adx-close-one { color: var(--ad-ink); }
.adx-close-acct { color: var(--ad-accent); }

/* ── mobile: the model picker stays a compact scrolling row ─────────────────
   The picker becomes a horizontally scrollable row below 720px so six options
   do not push the readout off the screen. The Phase 2 rule added a 12px gap
   for the desktop layout and, loading later, overrode the 2px the mobile row
   needs — each chip grew and the row scrolled further than it should. The mark
   is dropped here too: at chip size it costs width and adds nothing the family
   label is not already saying. */
@media (max-width: 720px) {
  .adx-cost-opt { gap: 2px; align-items: flex-start; }
  .adx-cost-opt-mark { display: none; }
  .adx-cost-opt::before { top: 8%; bottom: 8%; }
}

/* ── mobile: the trajectory turns ninety degrees ────────────────────────────
   Stacked centre-aligned families cost about 930px of phone screen for three
   short facts, and dropping the line lost the one idea the section exists to
   carry. Below 620px the composition rotates instead: marks in a column, the
   trajectory running vertically through them, text to the right. Same
   argument, a third of the height, and the line survives.

   This is the "mobile-specific composition" case — not the desktop layout at a
   smaller size. */
@media (max-width: 620px) {
  .ad-constellation { padding-left: 4px; }
  .ad-constellation-line {
    display: block;
    left: 28px; right: auto; top: 26px; bottom: 26px;
    width: 1px; height: auto;
    background: linear-gradient(180deg, transparent, var(--ad-edge) 10%,
                                var(--ad-edge-lit) 50%, var(--ad-edge) 90%, transparent);
  }
  [data-sequence][data-animate="true"] .ad-constellation-line {
    animation-name: ad-strike-v;
  }
  @keyframes ad-strike-v {
    from { transform: scaleY(0); opacity: 0; }
    to   { transform: scaleY(1); opacity: 1; }
  }
  .ad-families { gap: var(--ad-s5); }
  .ad-family {
    flex-direction: row; align-items: center; text-align: left;
    gap: var(--ad-s4);
    display: grid;
    grid-template-columns: 56px 1fr;
    grid-template-areas: "mark name" "mark count";
    row-gap: 2px;
  }
  .ad-family-mark  { grid-area: mark; box-shadow: 0 0 0 5px var(--ad-bg-raised); }
  .ad-family-name  { grid-area: name; margin-top: 0; align-self: end; }
  .ad-family-count { grid-area: count; align-self: start; }
  .ad-family-coord { display: none; }
}

/* ── mobile: anchors clear the two-row bar ──────────────────────────────────
   The bar wraps to two rows below 720px — brand and actions above, links
   below — which is about 105px. A 96px scroll offset lands a section title
   under its last row. */
@media (max-width: 720px) {
  html { scroll-padding-top: 124px; }
  section[id] { scroll-margin-top: 124px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   OPTIONAL BRAND ASSETS (Phase 3)
   ══════════════════════════════════════════════════════════════════════════
   None of these files exist yet. Every rule below is inert until one does,
   and none of them can change the layout when one arrives — the slots are
   fixed-size and the atmosphere is absolutely positioned.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── provider marks ────────────────────────────────────────────────────────
   The filter is a guarantee rather than a preference: whatever file is
   supplied — full colour, black, or already white — it renders as a single
   monochrome value on the dark surface. Four full-colour vendor marks in one
   row would become a rainbow and break the palette, and that is not a thing
   we want to be relying on the artwork to avoid. */
.ad-family-logo,
.adx-cost-opt-logo {
  display: block;
  object-fit: contain;
  filter: brightness(0) invert(1);
  opacity: .84;
  transition: opacity .25s var(--ad-ease-orbit);
}
.ad-family-logo   { width: 26px; height: 26px; }
.adx-cost-opt-logo { width: 14px; height: 14px; opacity: .7; }
.ad-family:hover .ad-family-logo { opacity: 1; }
.adx-cost-opt[data-selected="true"] .adx-cost-opt-logo { opacity: 1; }

/* ── hero atmosphere ───────────────────────────────────────────────────────
   Sits under everything in the hero and cannot be interacted with. The mask is
   the important line: the layer fades to nothing across the copy column, so
   the headline, lede and both CTAs sit on flat page colour no matter what the
   artwork turns out to be. Contrast is guaranteed structurally rather than
   left to the composition of an image nobody has made yet. */
.ad-hero-wrap > .ad-atmos {
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none; overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, transparent 32%, #000 68%);
          mask-image: linear-gradient(90deg, transparent 0%, transparent 32%, #000 68%);
}
.ad-atmos-still,
.ad-atmos-video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  object-fit: cover;
  object-position: 72% 42%;
}
.ad-atmos-still { opacity: .11; }
/* The video fades up over the still once it is genuinely playing, so a slow
   or failed load is invisible rather than a flash of empty rectangle. */
.ad-atmos-video { opacity: 0; transition: opacity 1.4s var(--ad-ease-orbit); }
.ad-atmos-video[data-playing="true"] { opacity: .11; }

/* Mobile gets the still only, cropped tighter and quieter, and masked from the
   top instead of the side — the hero stacks there, so the copy is above the
   figure rather than beside it. */
@media (max-width: 900px) {
  .ad-hero-wrap > .ad-atmos {
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, transparent 26%, #000 62%);
            mask-image: linear-gradient(180deg, transparent 0%, transparent 26%, #000 62%);
  }
  .ad-atmos-still { opacity: .08; object-position: 60% 55%; }
  .ad-atmos-video { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .ad-atmos-video { display: none; }
  .ad-atmos-still { opacity: .09; }
}

/* ══════════════════════════════════════════════════════════════════════════
   HERO — the self-serve answer
   ══════════════════════════════════════════════════════════════════════════ */
.ad-hero-selfserve {
  /* Explicit, not inherited: this rule and `.ad-hero-copy > * + *` have equal
     specificity, so `margin: 0` here silently won the hero's vertical rhythm
     and parked the line under the buttons. */
  margin: var(--ad-s5) 0 0;
  font-size: var(--ad-text-base);
  line-height: 1.6;
  color: var(--ad-ink-soft);
  max-width: 54ch;
}
/* The gate is a real constraint and gets its own weight — quieter than the
   invitation, louder than a footnote. Stating it plainly next to what IS
   available is what stops it reading as "you cannot have this". */
.ad-selfserve-gate { display: block; color: var(--ad-ink-mute); font-size: var(--ad-text-sm); }

/* ══════════════════════════════════════════════════════════════════════════
   TRUST — six properties, not six promises
   ══════════════════════════════════════════════════════════════════════════
   A ruled list rather than another card grid. The page already has four card
   grids; a fifth would make this read as marketing furniture, and the whole
   point of the section is that it is not.
   ═══════════════════════════════════════════════════════════════════════ */
.ad-trust {
  list-style: none; margin: clamp(24px, 3vw, 40px) 0 0; padding: 0;
  display: grid; gap: var(--ad-s6) var(--ad-s8);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
}
.ad-trust-item {
  display: grid;
  grid-template-columns: 18px 1fr;
  grid-template-areas: "mark k" ".    v";
  column-gap: var(--ad-s3); row-gap: 5px;
  min-width: 0;
  padding-top: var(--ad-s4);
  border-top: 1px solid var(--ad-edge-soft);
}
/* A struck tick, in the page's own notation — not a checkmark icon, which
   would read as a feature list rather than a statement of fact. */
.ad-trust-mark {
  grid-area: mark;
  width: 9px; height: 9px; margin-top: 6px;
  border-left: 1px solid var(--ad-accent);
  border-bottom: 1px solid var(--ad-accent);
}
.ad-trust-k {
  grid-area: k;
  font-size: var(--ad-text-base); font-weight: 600; color: var(--ad-ink);
  line-height: 1.35; letter-spacing: -0.008em;
}
.ad-trust-v {
  grid-area: v;
  font-size: var(--ad-text-sm); color: var(--ad-ink-soft); line-height: 1.6;
}
.ad-trust-foot {
  margin: clamp(24px, 3vw, 36px) 0 0;
  font-family: var(--ad-font-mono); font-size: var(--ad-text-xs);
  color: var(--ad-ink-mute);
}
.ad-trust-foot a { color: var(--ad-ink-soft); }
.ad-trust-foot a:hover { color: var(--ad-accent); }

[data-sequence][data-animate="true"] .ad-trust-item {
  animation: ad-arrive .5s var(--ad-ease-orbit) backwards;
  animation-delay: calc(90ms + var(--i) * 80ms);
}

@media (max-width: 620px) {
  .ad-trust { gap: var(--ad-s5); }
}


/* ── above-the-fold entrance, without JavaScript ───────────────────────────
   The hero H1 is the LCP element. `.ad-reveal` holds its subject at opacity 0
   until motion.js sets data-shown, so LCP waited for parse, then a deferred
   script, then a 620ms transition — 1568ms measured, almost all of it our own
   animation pipeline rather than the page arriving.

   Same curve, same distance, same stagger; it just starts at first style
   resolution instead. Only used above the fold. Everything below keeps the
   IntersectionObserver reveal, which genuinely needs a scroll trigger.

   The H1 carries no delay at all — it is the thing being measured. */
.ad-enter {
  animation: ad-enter .58s var(--ad-ease-orbit) backwards;
}
.ad-enter-2 { animation-delay: 90ms; }
.ad-enter-3 { animation-delay: 150ms; }
.ad-enter-4 { animation-delay: 210ms; }

@keyframes ad-enter {
  from { opacity: 0; transform: translate3d(0, 12px, 0); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .ad-enter { animation: none; }
}

/* ── mobile: the closing flow stacks instead of wrapping ────────────────────
   At 390px the row wrapped mid-shape and left an arrow pointing at the end of
   a line — a connector with nothing to connect. It is decorative and
   aria-hidden, so the honest options are to stack it or drop it; stacked, the
   many-to-one shape still reads, which is the only reason it is there. */
@media (max-width: 620px) {
  .adx-close-flow {
    flex-direction: column;
    align-items: center;
    gap: var(--ad-s3);
  }
  .adx-close-arrow {
    width: 1px;
    height: 18px;
    min-width: 0;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   PHASE 5 — agent-led hierarchy
   ══════════════════════════════════════════════════════════════════════════ */

/* The bridge under the integration headline — one line, centred with the
   heading it belongs to. */
.ad-integration-bridge {
  margin: var(--ad-s4) auto 0;
  max-width: 60ch;
  font-size: var(--ad-text-base);
  line-height: 1.65;
  color: var(--ad-ink-soft);
}

/* What Astrodyne is NOT, in the section most at risk of being misread as a
   claim to be an agent platform. Set apart from the bullet list above it by a
   rule rather than by a box, so it reads as a clarification and not a feature. */
.adx-agents-scope {
  margin: var(--ad-s5) 0 0;
  padding-top: var(--ad-s4);
  border-top: 1px solid var(--ad-edge-soft);
  font-size: var(--ad-text-sm);
  line-height: 1.6;
  color: var(--ad-ink-mute);
  max-width: 56ch;
}

/* ── the diagram's boundary ────────────────────────────────────────────────
   The single most important stroke in the drawing. Dashed rather than solid
   because it is a boundary of ownership, not a component; low-contrast because
   it must frame without competing with what it contains. */
.adx-ag-scope {
  fill: none;
  stroke: var(--ad-edge);
  stroke-width: 1;
  stroke-dasharray: 4 5;
  opacity: 0.75;
}
.adx-ag-zone {
  font-family: var(--ad-font-mono);
  font-size: 8.5px;
  letter-spacing: 0.18em;
  fill: var(--ad-ink-mute);
  opacity: 0.85;
}
.adx-ag-zone-hi { fill: var(--ad-accent); opacity: 0.95; }

@media (max-width: 620px) {
  /* At phone width the zone labels are the first thing to become unreadable,
     and the boundary alone still carries the separation. */
  .adx-ag-zone { font-size: 11px; letter-spacing: 0.1em; }
}


/* The Phase 5 headline is a sentence rather than three clipped beats, so it
   needs the browser to distribute its lines instead of filling greedily —
   otherwise it breaks as "AI agents make a / lot of model calls.", mid-phrase.
   The <br> before the italic stays: which sentence opens the second beat is an
   editorial decision, not a typesetting one. */
.ad-hero-title { text-wrap: balance; }

/* ── nav lockup: restore the mobile rules the component broke ───────────────
   REGRESSION, introduced in Phase 4 and missed there. astrodyne.css carries a
   @media (max-width: 720px) block that reorders the navigation and shrinks the
   brand wordmark:

       .ad-brand { order: 1; }
       .ad-brand span { font-size: var(--ad-text-base); }

   Replacing `.ad-brand` with the shared component took both with it. The
   wordmark stayed at its desktop 21px, the lockup went from 113px wide to
   143px, and at 390px the navigation wrapped from two rows to three — costing
   about forty pixels of the mobile fold on every page.

   The Phase 4 check compared desktop screenshots and measured aspect ratio and
   overflow, none of which can see a flex row gaining a line. What catches it is
   measuring the lockup against the markup it replaced, which is what found it.

   Same breakpoint and same values, now addressing the component. */
@media (max-width: 720px) {
  .ad-nav .adl { order: 1; }
  .ad-nav .adl-word { font-size: var(--ad-text-base); }
}

/* ══════════════════════════════════════════════════════════════════════════
   PHASE 7
   ══════════════════════════════════════════════════════════════════════════ */

/* ── technical proof strip ─────────────────────────────────────────────────
   A spec plate, not feature chips. Mono, uppercase, hairline-ruled between
   items and enclosed top and bottom — the visual language of a rating label on
   a piece of equipment. Nothing here has a pill, a background fill or an icon,
   because all three would make it read as marketing rather than as fact. */
.ad-spec {
  list-style: none;
  margin: clamp(20px, 2.4vw, 30px) 0 0;
  padding: var(--ad-s3) 0;
  display: flex; flex-wrap: wrap;
  border-top: 1px solid var(--ad-edge-soft);
  border-bottom: 1px solid var(--ad-edge-soft);
  font-family: var(--ad-font-mono);
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ad-ink-mute);
}
.ad-spec-item {
  padding: 3px var(--ad-s4);
  white-space: nowrap;
  border-left: 1px solid var(--ad-edge-soft);
}
.ad-spec-item:first-child { border-left: 0; padding-left: 0; }

@media (max-width: 620px) {
  /* Wrapped items make the leading rules land mid-row, which reads as a
     mistake. Below this width the strip becomes a plain wrapped list. */
  .ad-spec { gap: 2px var(--ad-s2); font-size: 10px; letter-spacing: 0.12em; }
  .ad-spec-item { border-left: 0; padding-inline: 0; }
  .ad-spec-item:not(:last-child)::after { content: " ·"; color: var(--ad-edge-strong); }
}

/* ── console: status and latency ───────────────────────────────────────────
   Both are real customer-visible request metadata. Six columns fit down to
   about 1024; below that latency goes first because it is the least
   consequential of the six, then status, and the existing two-column collapse
   takes over on phones. */
.adx-tr {
  grid-template-columns:
    minmax(0, 1.05fr) minmax(0, 1.15fr) 72px 74px 84px 88px;
}
.adx-status {
  font-family: var(--ad-font-mono);
  font-size: 11px;
  color: var(--ad-ink-mute);
}
.adx-th .adx-status { color: var(--ad-ink-mute); }

@media (max-width: 1023px) {
  .adx-tr { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1.15fr) 72px 74px 88px; }
  .adx-lat { display: none; }
}
@media (max-width: 860px) {
  .adx-tr { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1.15fr) 74px 88px; }
  .adx-status { display: none; }
}

/* ── pricing: awaiting a figure ────────────────────────────────────────────
   Four dashed PENDING wells made a truthful empty state look like a large
   unfinished feature. One badge above states the condition; each row now holds
   a muted rule where its figure will go. The em dash is the same character a
   table uses for "no value yet", which is exactly what this is. */
.adx-await {
  display: inline-block;
  min-width: 44px;
  text-align: right;
  font-family: var(--ad-font-mono);
  font-size: var(--ad-text-md);
  color: var(--ad-ink-mute);
  opacity: 0.7;
}
/* The difference zone keeps its shape so the section does not reflow when a
   figure arrives, but drops its accent while there is nothing to celebrate. */
.adx-cost-delta[data-pending="true"] {
  background: var(--ad-bg-inset);
  border-color: var(--ad-edge-soft);
}
.adx-cost-delta[data-pending="true"] .adx-cost-delta-label { color: var(--ad-ink-mute); }

.ad-cost-conclusion{margin:clamp(24px,3vw,36px) 0 0;max-width:64ch;font-size:var(--ad-text-md);line-height:1.6;color:var(--ad-ink-soft)}
