/* ==========================================================================
   pricing-roadmap.css — the interactive roadmap hero on pricing.html.
   Added 2026-09-14. Loaded ONLY by pricing.html. Vanilla CSS, no build step.

   NAMESPACING
   -----------
   Every selector is prefixed .roadmap-* / #roadmap-*. No rule in this file can
   reach the Tailwind utilities or the canonical pricing tables below the hero,
   and no other page links it. Nothing shared was edited.

   THE ONE DECISION THIS FILE TURNS ON
   -----------------------------------
   The finished state IS the default state. Nothing starts at opacity 0 waiting
   for JS or for an animation to reveal it: all eight hubs, their labels, the
   roads and the price box are painted by CSS alone. That single choice is what
   makes JS-off, prefers-reduced-motion and zero-CLS fall out together instead
   of needing three separate fallbacks.

   TWO ROAD SYSTEMS, AND WHY
   -------------------------
   Wide (>=900px): a real inline-SVG network. Nodes are absolutely positioned at
   percentage coordinates and the SVG uses viewBox="0 0 100 100" with
   preserveAspectRatio="none", so path coordinates and node coordinates are the
   same numbers at every viewport width — no JS measurement, no drift. The
   non-uniform scale would normally smear the stroke, which is what
   vector-effect="non-scaling-stroke" on every path is there to stop.

   Stacked (<900px): the SVG network is replaced by a CSS spine. SVG geometry
   cannot track node boxes whose heights depend on how their labels wrap without
   measuring them in JS, and a measured path would then be absent with JS off —
   which is precisely the case the road exists to survive. The spine is exact at
   any content height. Deviation from the brief's "SVG path network", recorded
   deliberately; the visual and behavioural contract is unchanged.
   ========================================================================== */

/* ---------------------------------------------------------------- tokens */
.roadmap {
  --rm-ink: #0f172a;
  --rm-line: rgba(148, 163, 184, 0.55);
  --rm-line-lit: rgba(37, 99, 235, 0.85);
  --rm-card: #ffffff;
  --rm-muted: #64748b;
  --rm-stage-bg: linear-gradient(160deg, #f8fafc 0%, #eef2ff 55%, #f5f3ff 100%);

  /* hub families: one colour + one timing signature each (brief section 6) */
  --rm-cool: #2563eb;
  --rm-amber: #d97706;
  --rm-violet: #7c3aed;
  --rm-teal: #0d9488;
  --rm-terracotta: #c2410c;
  --rm-sage: #4d7c6f;

  position: relative;
  margin: 3rem 0 1rem;
}

/* Each family carries two colours, not one. --rm-hue is the ink used on the
   light map (white cards, pale stage); --rm-hue-lift is a lighter sibling for
   use on the near-black price box, where the mid-tone hues — sage and blue
   especially — go muddy against #0f172a. Same identity, readable on both
   grounds. */
.roadmap-hub--cool       { --rm-hue: var(--rm-cool);       --rm-hue-lift: #60a5fa; --rm-beat: 1;    }
.roadmap-hub--amber      { --rm-hue: var(--rm-amber);      --rm-hue-lift: #fbbf24; --rm-beat: 0.86; }
.roadmap-hub--violet     { --rm-hue: var(--rm-violet);     --rm-hue-lift: #a78bfa; --rm-beat: 0.7;  }
.roadmap-hub--teal       { --rm-hue: var(--rm-teal);       --rm-hue-lift: #2dd4bf; --rm-beat: 0.78; }
.roadmap-hub--terracotta { --rm-hue: var(--rm-terracotta); --rm-hue-lift: #fb923c; --rm-beat: 1.25; }
.roadmap-hub--sage       { --rm-hue: var(--rm-sage);       --rm-hue-lift: #86c5b4; --rm-beat: 1.1;  }

/* ------------------------------------------------------------------ head */
.roadmap-head { text-align: center; margin-bottom: 1.5rem; }

.roadmap-kicker {
  display: inline-block;
  padding: 5px 16px;
  border-radius: 999px;
  background: rgba(37, 99, 235, 0.1);
  color: #1d4ed8;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.roadmap-title {
  margin: 0.75rem 0 0.35rem;
  font-size: clamp(1.4rem, 2.6vw, 2rem);
  font-weight: 700;
  color: var(--rm-ink);
}

.roadmap-sub { margin: 0; color: var(--rm-muted); font-size: 0.98rem; }

/* ----------------------------------------------------------------- stage */
.roadmap-stage {
  position: relative;
  border-radius: 20px;
  background: var(--rm-stage-bg);
  border: 1px solid rgba(148, 163, 184, 0.35);
  padding: 1.25rem;
  overflow: hidden;
}

.roadmap-clusterlabel {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #94a3b8;
}

/* ----------------------------------------------------------- roads (wide) */
.roadmap-roads {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}

.roadmap-road {
  fill: none;
  stroke: var(--rm-line);
  stroke-width: 1.5;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.roadmap-road-flow {
  fill: none;
  stroke: var(--rm-line);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-dasharray: 3 9;
  vector-effect: non-scaling-stroke;
  opacity: 0.7;
  animation: roadmap-drift 14s linear infinite;
}

@keyframes roadmap-drift {
  from { stroke-dashoffset: 0; }
  to   { stroke-dashoffset: -120; }
}

/* A road belonging to the selected hub brightens and speeds up; the rest of
   the network stays at ambient so the lit path is unambiguous. */
.roadmap-roadgroup.is-lit .roadmap-road      { stroke: var(--rm-hue, var(--rm-line-lit)); opacity: 0.9; }
.roadmap-roadgroup.is-lit .roadmap-road-flow { stroke: var(--rm-hue, var(--rm-line-lit)); animation-duration: 3.2s; opacity: 1; }

/* Travelling signal: one bright core plus two fainter echoes. Hidden until JS
   arms it — a permanently bright segment parked on a road reads as a fault. */
.roadmap-signal {
  fill: none;
  stroke: var(--rm-hue, var(--rm-line-lit));
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  opacity: 0;
}
.roadmap-signal--core  { stroke-width: 3.5; filter: drop-shadow(0 0 5px currentColor); }
.roadmap-signal--echo1 { stroke-width: 2.5; }
.roadmap-signal--echo2 { stroke-width: 1.75; }
.roadmap-roadgroup.is-running .roadmap-signal--core  { opacity: 1; }
.roadmap-roadgroup.is-running .roadmap-signal--echo1 { opacity: 0.45; }
.roadmap-roadgroup.is-running .roadmap-signal--echo2 { opacity: 0.22; }

/* ---------------------------------------------------------------- layout */
.roadmap-field { position: relative; z-index: 1; }

/* ------------------------------------------------------------------ hubs */
.roadmap-hub {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.7rem;
  width: 100%;
  padding: 0.7rem 0.85rem;
  border: 0;
  border-radius: 12px;
  background: var(--rm-card);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06), 0 6px 18px rgba(15, 23, 42, 0.05);
  color: var(--rm-ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  /* 44px minimum touch target (brief section 11) */
  min-height: 56px;
  transition: box-shadow 0.25s ease, transform 0.25s ease;
}

.roadmap-hub:hover { box-shadow: 0 2px 4px rgba(15, 23, 42, 0.08), 0 10px 26px rgba(15, 23, 42, 0.1); }

.roadmap-hub:focus-visible {
  outline: 3px solid var(--rm-hue, var(--rm-cool));
  outline-offset: 3px;
}

/* Selection is persistent and must not be mistakable for hover. */
.roadmap-hub[aria-expanded="true"] {
  box-shadow: 0 0 0 2px var(--rm-hue), 0 10px 26px rgba(15, 23, 42, 0.14);
}

.roadmap-hub-prop {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  fill: none;
  stroke: var(--rm-hue, var(--rm-cool));
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.roadmap-hub-text { min-width: 0; }
.roadmap-hub-name { display: block; font-weight: 700; font-size: 0.9rem; line-height: 1.25; }
.roadmap-hub-micro { display: block; font-size: 0.75rem; color: var(--rm-muted); line-height: 1.3; }

/* ---- corner-lock brackets (SVG strokes, not CSS borders) ---- */
.roadmap-corner {
  position: absolute;
  width: 16px;
  height: 16px;
  overflow: visible;
  pointer-events: none;
  fill: none;
  stroke: var(--rm-hue, var(--rm-cool));
  stroke-width: 1.6;
  stroke-dasharray: 18;
  stroke-dashoffset: 18;   /* idle: undrawn. Selection draws them in. */
  opacity: 0;
  transition: opacity 0.2s ease;
}
.roadmap-corner--tl { top: -2px;    left: -2px; }
.roadmap-corner--tr { top: -2px;    right: -2px; transform: scaleX(-1); }
.roadmap-corner--br { bottom: -2px; right: -2px; transform: scale(-1); }
.roadmap-corner--bl { bottom: -2px; left: -2px;  transform: scaleY(-1); }

.roadmap-hub[aria-expanded="true"] .roadmap-corner { opacity: 1; stroke-dashoffset: 0; }

/* The lock pass: entry corner first, adjacent ~80ms apart, far corner ~90ms
   after, with a 1.08 overshoot that settles. Timings from the brief's contract
   (approach 0-300 / flash 300-360 / brackets 360-650 / bloom 650-780), scaled
   by each hub's --rm-beat so families differ by timing, not by pathology. */
.roadmap-hub.is-locking .roadmap-corner {
  animation: roadmap-draw calc(290ms * var(--rm-beat, 1)) cubic-bezier(.2,.9,.25,1.08) both;
  animation-delay: calc(360ms * var(--rm-beat, 1) + var(--rm-c) * 80ms * var(--rm-beat, 1));
  will-change: stroke-dashoffset;
}

@keyframes roadmap-draw {
  from { stroke-dashoffset: 18; opacity: 1; }
  to   { stroke-dashoffset: 0;  opacity: 1; }
}

.roadmap-flash {
  position: absolute;
  top: -3px;
  left: -3px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--rm-hue, var(--rm-cool));
  opacity: 0;
  pointer-events: none;
}
.roadmap-hub.is-locking .roadmap-flash {
  animation: roadmap-flash calc(360ms * var(--rm-beat, 1)) ease-out calc(300ms * var(--rm-beat, 1)) both;
  will-change: opacity, transform;
}
@keyframes roadmap-flash {
  0%   { opacity: 0;    transform: scale(0.3); }
  11%  { opacity: 0.95; transform: scale(1); }
  100% { opacity: 0;    transform: scale(2.4); }
}

/* inner glow: bloom on lock, then an ambient ~15% hum */
.roadmap-hub::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 12px;
  pointer-events: none;
  background: radial-gradient(ellipse at 50% 50%, var(--rm-hue, var(--rm-cool)) 0%, transparent 72%);
  opacity: 0;
  transition: opacity 0.3s ease;
}
.roadmap-hub[aria-expanded="true"]::after {
  opacity: 0.15;
  animation: roadmap-hum calc(4s * var(--rm-beat, 1)) ease-in-out infinite;
}
@keyframes roadmap-hum {
  0%, 100% { opacity: 0.10; }
  50%      { opacity: 0.20; }
}
.roadmap-hub.is-locking::after {
  animation: roadmap-bloom calc(500ms * var(--rm-beat, 1)) ease-out calc(650ms * var(--rm-beat, 1)) both;
  will-change: opacity;
}
@keyframes roadmap-bloom {
  0%   { opacity: 0.15; }
  22%  { opacity: 0.55; }
  100% { opacity: 0.15; }
}

/* The one permitted signature variation (brief section 6): AI/automation hubs
   flicker instead of locking, then never quite settle. */
.roadmap-hub--glitch.is-locking .roadmap-corner { animation-name: roadmap-glitch-draw; }
@keyframes roadmap-glitch-draw {
  0%   { stroke-dashoffset: 18; opacity: 0.2; }
  18%  { stroke-dashoffset: 4;  opacity: 1; }
  30%  { stroke-dashoffset: 12; opacity: 0.25; }
  46%  { stroke-dashoffset: 0;  opacity: 1; }
  58%  { stroke-dashoffset: 6;  opacity: 0.4; }
  100% { stroke-dashoffset: 0;  opacity: 1; }
}
.roadmap-hub--glitch[aria-expanded="true"]::after { animation-name: roadmap-shimmer; }
@keyframes roadmap-shimmer {
  0%, 100% { opacity: 0.12; }
  38%      { opacity: 0.24; }
  55%      { opacity: 0.16; }
  72%      { opacity: 0.26; }
}

/* ---------------------------------------------------------- detail panel */
/* The panel wears the hub's colour. It opens on the far side of the map from
   the hub that was clicked (so the lit road and the price box stay visible),
   which leaves the two visually unrelated — colour is what ties them back
   together. JS copies --rm-hue onto the panel when it opens. */
.roadmap-panel {
  grid-column: 1 / -1;
  margin-top: 0.85rem;
  border-radius: 14px;
  background: var(--rm-card);
  background: color-mix(in srgb, var(--rm-hue, #2563eb) 4%, #fff);
  border: 1px solid rgba(148, 163, 184, 0.4);
  border-top: 3px solid var(--rm-hue, #2563eb);
  box-shadow: 0 12px 34px rgba(15, 23, 42, 0.12);
  overflow: hidden;
}
.roadmap-panel[hidden] { display: none !important; }

.roadmap-panel-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 1rem;
  border-bottom: 1px solid rgba(148, 163, 184, 0.3);
  background: color-mix(in srgb, var(--rm-hue, #2563eb) 9%, #fff);
}
.roadmap-panel-title { margin: 0; font-size: 1rem; font-weight: 700; color: var(--rm-hue, #0f172a); }
.roadmap-panel-close {
  margin-left: auto;
  min-width: 44px;
  min-height: 44px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--rm-muted);
  font-size: 1.1rem;
  cursor: pointer;
}
.roadmap-panel-close:hover { background: rgba(148, 163, 184, 0.16); }
.roadmap-panel-close:focus-visible { outline: 3px solid var(--rm-cool); outline-offset: 2px; }

.roadmap-cats { padding: 0.5rem 1rem 1rem; margin: 0; list-style: none; }
.roadmap-cat + .roadmap-cat { margin-top: 0.9rem; }

.roadmap-cat-name {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 0.5rem;
  font-size: 0.9rem;
  font-weight: 700;
}
.roadmap-cat-prop {
  width: 20px; height: 20px; fill: none;
  stroke: var(--rm-hue, var(--rm-cool)); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}

/* Tiers are visible sub-labels, not a second hidden tap layer: hub -> tier is
   the whole path to a price (brief 5.2, two-step rule). */
.roadmap-tiers { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0; padding: 0; list-style: none; }

.roadmap-tier {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 44px;
  padding: 0.5rem 0.8rem;
  border: 1px solid rgba(148, 163, 184, 0.5);
  border-radius: 10px;
  background: #fff;
  color: var(--rm-ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.2s ease, background 0.2s ease;
}
.roadmap-tier:hover { border-color: var(--rm-hue, var(--rm-cool)); background: rgba(148, 163, 184, 0.07); }
.roadmap-tier:focus-visible { outline: 3px solid var(--rm-hue, var(--rm-cool)); outline-offset: 2px; }
.roadmap-tier[aria-pressed="true"] {
  border-color: var(--rm-hue, var(--rm-cool));
  box-shadow: inset 0 0 0 1px var(--rm-hue, var(--rm-cool));
}
.roadmap-tier-name { font-size: 0.82rem; font-weight: 600; }
.roadmap-tier-price { font-size: 0.78rem; color: var(--rm-muted); }
.roadmap-tier-badge {
  align-self: flex-start;
  margin-top: 2px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--rm-hue, var(--rm-cool));
  color: #fff;
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.roadmap-cat-all {
  display: inline-block;
  margin-top: 0.45rem;
  font-size: 0.78rem;
  font-weight: 600;
  color: #1d4ed8;
}

/* ------------------------------------------------------------- price box */
.roadmap-box {
  position: relative;
  border-radius: 16px;
  /* Plain navy first, then the tinted version. A browser without color-mix
     drops the second declaration whole and keeps the navy, so the tint is
     progressive enhancement rather than a dependency. The wash is layered into
     the background rather than added as a ::before, because a positioned
     pseudo-element in this stacking context paints ABOVE the box's own text. */
  background: #0f172a;
  background:
    radial-gradient(130% 95% at 50% 0%,
      color-mix(in srgb, var(--rm-hue-lift, #60a5fa) 24%, transparent) 0%,
      transparent 62%),
    #0f172a;
  color: #fff;
  padding: 1.1rem 1.15rem 1.2rem;
  box-shadow: 0 16px 40px rgba(15, 23, 42, 0.28);
  transition: background 0.45s ease;
}

.roadmap-box-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}
.roadmap-box-perimeter {
  fill: none;
  stroke: var(--rm-hue-lift, #f8fafc);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
  opacity: 0;
}
.roadmap-box.is-locked .roadmap-box-perimeter { opacity: 0.85; }
.roadmap-box.is-locking .roadmap-box-perimeter {
  opacity: 0.85;
  animation: roadmap-perimeter 400ms ease-out both;
  will-change: stroke-dashoffset;
}
@keyframes roadmap-perimeter {
  from { stroke-dashoffset: var(--rm-perimeter, 1200); }
  to   { stroke-dashoffset: 0; }
}

/* All four corners of the box lock together — deliberately different from the
   staggered diagonal on a hub, so "arrived" never reads as "another hub". */
.roadmap-box-corner {
  position: absolute;
  width: 18px; height: 18px;
  overflow: visible; pointer-events: none;
  fill: none; stroke: var(--rm-hue-lift, #fff); stroke-width: 2;
  stroke-dasharray: 20; stroke-dashoffset: 20;
}
.roadmap-box.is-locked .roadmap-box-corner { stroke-dashoffset: 0; }
.roadmap-box.is-locking .roadmap-box-corner { animation: roadmap-boxdraw 260ms ease-out both; }
@keyframes roadmap-boxdraw { from { stroke-dashoffset: 20; } to { stroke-dashoffset: 0; } }
.roadmap-box-corner--tl { top: -1px;    left: -1px; }
.roadmap-box-corner--tr { top: -1px;    right: -1px; transform: scaleX(-1); }
.roadmap-box-corner--br { bottom: -1px; right: -1px; transform: scale(-1); }
.roadmap-box-corner--bl { bottom: -1px; left: -1px;  transform: scaleY(-1); }

.roadmap-pulse {
  position: absolute;
  left: 50%; top: 50%;
  width: 24px; height: 24px;
  margin: -12px 0 0 -12px;
  border-radius: 50%;
  border: 2px solid var(--rm-hue-lift, #60a5fa);
  opacity: 0;
  pointer-events: none;
}
.roadmap-box.is-pulsing .roadmap-pulse { animation: roadmap-pulse 620ms ease-out both; }
@keyframes roadmap-pulse {
  from { opacity: 0.7; transform: scale(0.4); }
  to   { opacity: 0;   transform: scale(9); }
}

/* The light that runs round the box.

   Three stroked rects on the same frame as the perimeter, so this is the same
   stroke language as the corner brackets rather than a second visual system.
   pathLength="100" normalises each rect's length, so the dash figures below are
   in percent-of-perimeter and stay correct at any box size — no measuring.

   Two speeds, deliberately. A price that has just landed gets ONE bright fast
   lap with two trailing echoes; after that the light settles to a faint slow
   orbit. A permanently bright light circling the price is the most likely thing
   on this page to pull the eye off the number, which the brief rules out, so the
   loud version is strictly a one-off and the resting version is ~25% opacity.

   Both laps end at -100, a whole loop, so the hand-off from the arrival lap back
   to the ambient orbit lands on the same point and does not jump. */
.roadmap-box-orbit {
  fill: none;
  stroke: var(--rm-hue-lift, #60a5fa);
  stroke-width: 2;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 11 89;
  stroke-dashoffset: 0;
  opacity: 0;
}

.roadmap-box.is-locked .roadmap-box-orbit--core {
  opacity: 0.28;
  animation: roadmap-orbit 11s linear infinite;
}

.roadmap-box.is-arriving .roadmap-box-orbit {
  opacity: 1;
  animation: roadmap-orbit-lap 860ms cubic-bezier(.34, .04, .22, 1) both;
  animation-delay: calc(var(--rm-o, 0) * 45ms);
  filter: drop-shadow(0 0 5px var(--rm-hue-lift, #60a5fa));
  will-change: stroke-dashoffset;
}
.roadmap-box.is-arriving .roadmap-box-orbit--echo1 { opacity: 0.45; stroke-width: 1.5; }
.roadmap-box.is-arriving .roadmap-box-orbit--echo2 { opacity: 0.22; stroke-width: 1; }

@keyframes roadmap-orbit     { to { stroke-dashoffset: -100; } }
@keyframes roadmap-orbit-lap { from { stroke-dashoffset: 0; } to { stroke-dashoffset: -100; } }

.roadmap-box-label {
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #94a3b8;
}
.roadmap-box-service { margin: 0.35rem 0 0; font-size: 0.95rem; font-weight: 700; line-height: 1.3; }
.roadmap-box-tier { margin: 0.1rem 0 0; font-size: 0.8rem; color: #cbd5e1; }

.roadmap-box-price {
  display: block;
  margin: 0.5rem 0 0;
  font-size: clamp(1.5rem, 3vw, 2.1rem);
  font-weight: 800;
  line-height: 1.1;
  transform-origin: left center;
}
.roadmap-box.is-punching .roadmap-box-price { animation: roadmap-punch 120ms ease-out; }
@keyframes roadmap-punch {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.06); }
  100% { transform: scale(1); }
}
.roadmap-box-period { font-size: 0.8rem; font-weight: 600; color: #94a3b8; }
.roadmap-box-note { margin: 0.35rem 0 0; font-size: 0.75rem; color: #94a3b8; line-height: 1.45; }

.roadmap-box-cta,
.roadmap-box-cta2 {
  display: block;
  margin-top: 0.8rem;
  padding: 0.7rem 1rem;
  border-radius: 10px;
  text-align: center;
  font-weight: 700;
  font-size: 0.85rem;
  text-decoration: none;
}
.roadmap-box-cta  { background: #f0a83c; color: #10203a; }
.roadmap-box-cta:hover { background: #ffb954; }
.roadmap-box-cta2 {
  margin-top: 0.5rem;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.35);
  color: #e2e8f0;
  font-weight: 600;
}
.roadmap-box-cta2:hover { background: rgba(255, 255, 255, 0.08); }
.roadmap-box-cta:focus-visible,
.roadmap-box-cta2:focus-visible { outline: 3px solid #fbbf24; outline-offset: 3px; }

.roadmap-sr {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Canonical-table row highlight driven from the hero. Namespaced, additive —
   it never touches the table's own Tailwind classes. */
.roadmap-linked {
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.55) !important;
  border-radius: 12px;
  transition: box-shadow 0.3s ease;
}

/* ====================================================== WIDE (map) layout */
@media (min-width: 900px) {
  .roadmap-stage { padding: 1.5rem; }

  .roadmap-field {
    position: relative;
    height: clamp(420px, 56vh, 540px);
  }

  /* Percentage coordinates here are the SAME NUMBERS as the SVG path
     coordinates, because the SVG viewBox is 0 0 100 100 with
     preserveAspectRatio="none". That is the whole trick: no JS measurement,
     and the roads stay attached to the nodes at every width. */
  .roadmap-hub {
    position: absolute;
    width: 20.5%;
    min-width: 168px;
    transform: translate(-50%, -50%);
  }
  .roadmap-hub[data-x] { left: var(--rm-x); top: var(--rm-y); }

  /* display:contents dissolves the cluster wrapper on the map, so each hub
     still positions against .roadmap-field and the authored percentage
     coordinates keep matching the SVG path coordinates. The wrapper exists
     only so that, once stacked, each cluster's label travels with its own
     four hubs instead of both labels piling up at the top. */
  .roadmap-cluster { display: contents; }
  .roadmap-clusterlabel { position: absolute; top: 0; }
  .roadmap-cluster--left  .roadmap-clusterlabel { left: 0; }
  .roadmap-cluster--right .roadmap-clusterlabel { right: 0; }

  .roadmap-box {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 30%;
    min-width: 236px;
    transform: translate(-50%, -50%);
  }

  /* The panel is an overlay on the map rather than an inserted block: nothing
     in the stage reflows when it opens, so opening one cannot move the price
     box or shift the page (CLS gate, brief section 12).

     It docks to the side its hub sits on, rather than centring. A centred
     panel covered the price box completely — so selecting a tier updated a
     number the visitor could not see, which is the one thing this layout
     exists to put in front of them. Docked, the map's centre column stays
     clear and the box is visible the whole time. */
  .roadmap-panel {
    position: absolute;
    top: 50%;
    /* 33% + half the box's 30% = 48%, so the docked panel stops short of the
       box's edge instead of clipping it. */
    width: 33%;
    min-width: 290px;
    max-height: 94%;
    margin-top: 0;
    transform: translateY(-50%);
    overflow-y: auto;
    z-index: 3;
  }
  .roadmap-panel[data-side="left"]  { left: 0; }
  .roadmap-panel[data-side="right"] { right: 0; }
}

/* ================================================== STACKED (spine) layout */
@media (max-width: 899.98px) {
  .roadmap-roads { display: none; }
  .roadmap-clusterlabel { margin: 0.25rem 0 0.6rem; }
  .roadmap-cluster + .roadmap-cluster .roadmap-clusterlabel { margin-top: 1.1rem; }

  .roadmap-field { position: relative; padding-left: 1.5rem; }

  /* The CSS spine that replaces the SVG network here. */
  .roadmap-field::before {
    content: '';
    position: absolute;
    left: 0.45rem;
    top: 0.5rem;
    bottom: 0.5rem;
    width: 2px;
    background: repeating-linear-gradient(
      to bottom, var(--rm-line) 0 6px, transparent 6px 16px);
    animation: roadmap-spine 14s linear infinite;
  }
  @keyframes roadmap-spine {
    from { background-position-y: 0; }
    to   { background-position-y: 160px; }
  }

  .roadmap-field.is-running::after {
    content: '';
    position: absolute;
    left: 0.2rem;
    top: 0;
    width: 10px;
    height: 46px;
    border-radius: 6px;
    background: linear-gradient(to bottom, transparent, var(--rm-hue, var(--rm-cool)), transparent);
    filter: blur(1px);
    animation: roadmap-spark 900ms ease-in-out both;
  }
  @keyframes roadmap-spark {
    from { top: 0; opacity: 0; }
    15%  { opacity: 1; }
    to   { top: calc(100% - 46px); opacity: 0; }
  }

  .roadmap-hub { margin-bottom: 0.6rem; }

  /* connector stub from the spine to each hub */
  .roadmap-hub::before {
    content: '';
    position: absolute;
    left: -1.05rem;
    top: 50%;
    width: 1.05rem;
    height: 2px;
    background: var(--rm-line);
  }
  .roadmap-hub[aria-expanded="true"]::before { background: var(--rm-hue, var(--rm-cool)); }

  .roadmap-box { margin-top: 1rem; }

  /* In flow on a phone, never overlaying: an overlay panel here would cover
     the content the visitor scrolled to read (brief 5.4). */
  .roadmap-panel { position: static; transform: none; }
}

/* NOT sticky beyond the hero — a deliberate, reported deviation from the brief's
   "sticky on desktop". The price box is the convergence point of the map and
   stays visible for as long as the map is on screen, which is the whole of the
   above-the-fold experience. Pinning the stage past the hero would park a
   ~62vh panel over the canonical pricing tables for the entire scroll, i.e.
   the hero would cover the prices — the failure the brief's own rule
   ("if an animation competes with the price, it is a bug") exists to prevent,
   and the same objection it raises against a sticky box on mobile. Sticky is
   also structurally blocked here: .roadmap-stage clips its roads with
   overflow:hidden, and position:sticky does not work inside a clipping
   ancestor. */

/* ----------------------------------------------------------- low-end trim */
/* Small screens get a reduced animation budget (brief section 7). */
@media (max-width: 480px) {
  .roadmap-hub.is-locking .roadmap-corner { animation-duration: 200ms; }
  .roadmap-field::before { animation: none; }
}

/* --------------------------------------------------------- reduced motion */
/* Safe to switch these off outright: nothing here is hidden at rest, so
   removing the motion removes movement, never content. The static roadmap —
   all eight hubs, roads, price box, every interaction still working — is the
   product for everyone in this mode. */
@media (prefers-reduced-motion: reduce) {
  .roadmap-road-flow,
  .roadmap-field::before,
  .roadmap-field.is-running::after,
  .roadmap-hub::after,
  .roadmap-hub.is-locking .roadmap-corner,
  .roadmap-hub.is-locking .roadmap-flash,
  .roadmap-hub.is-locking::after,
  .roadmap-hub[aria-expanded="true"]::after,
  .roadmap-box.is-locking .roadmap-box-perimeter,
  .roadmap-box.is-locking .roadmap-box-corner,
  .roadmap-box.is-pulsing .roadmap-pulse,
  .roadmap-box.is-punching .roadmap-box-price,
  .roadmap-box.is-locked .roadmap-box-orbit--core,
  .roadmap-box.is-arriving .roadmap-box-orbit {
    animation: none !important;
  }
  /* The colour stays — tint is not motion. Only the travelling light goes, and
     it goes completely rather than freezing part-way round the edge. */
  .roadmap-box-orbit { opacity: 0 !important; }
  .roadmap-signal { opacity: 0 !important; }
  .roadmap-hub[aria-expanded="true"]::after { opacity: 0.15; }
  .roadmap-hub[aria-expanded="true"] .roadmap-corner { stroke-dashoffset: 0; opacity: 1; }
  .roadmap-box.is-locked .roadmap-box-corner { stroke-dashoffset: 0; }
  /* Kill transitions outright inside the roadmap, not just the keyframes.
     motion-guard.css crushes every transition-duration site-wide to 0.01ms
     rather than removing them, which leaves a real (if instant) transition on
     every animatable property — including stroke-dashoffset on the corner
     brackets. A 0.01ms transition normally settles on the next frame, but in a
     throttled or background tab it can sit at its start value, which showed up
     here as box corners rendering undrawn while the box was already locked.
     The higher specificity of `.roadmap *` beats motion-guard's `*` even
     though motion-guard loads later, so this wins without touching it. */
  .roadmap, .roadmap *, .roadmap *::before, .roadmap *::after {
    transition: none !important;
  }
}
