/* ============================================================================
   viz-vault  ·  replaces viz-perimeter on the homepage deployment section
   ----------------------------------------------------------------------------
   MECHANIC        containment, expressed as a luminous field rather than a wall
   TEACHES         "Data moves freely inside. The boundary holds."
   CYCLE           3.6s story cycle. The aurora wash and the drifting motes
                   are ambient sublayers with no story, so they stay slow, the
                   same exemption viz-field gets.
   REPLACES        viz-perimeter (dashed wall + rigid icon orbit + scanning probe)

   Why the concept changed
   The old version drew a fence and a scanning laser, which reads as a security
   checkpoint. This one reads as a sealed environment that is alive inside. The
   boundary is not a line that stops things, it is a field that responds. That
   is a better picture of on-premise, because the point is not that data is
   blocked, it is that data never needed to leave.

   Why it is a dark stage on a light section
   Luminosity needs a dark ground. Sitting a dark inset panel inside the light
   section also gives the page a depth change it did not have, which is the
   "black and white" problem being solved. The section rhythm is preserved
   because the section itself stays light.

   Floatiness
   Every mote carries its own --a (angle), --r (radius), --dur and --d (delay),
   and the durations are deliberately coprime-ish so the field never visibly
   syncs up or loops in lockstep. Nothing travels a circle. They wander.
   ========================================================================= */

.viz-vault {
  /* The field is close to square, so the panel is capped and centred rather
     than stretching the full wrap width and stranding the circle in space. */
  max-width: 46rem;
  margin-inline: auto;
  --vault-bg:        #060D1E;
  --vault-bg-2:      #0C1B38;
  --vault-edge:      #2A69CE;
  --vault-glow:      #5B9BD9;
  --vault-hot:       #8BBEF8;
  --vault-cyan:      #4FD1E0;
  --vault-cycle:     3.6s;
  --vault-size:      clamp(15rem, 46cqw, 23rem);
}

.viz-vault__stage {
  /* Cardless. The visual carries itself, no panel, no border, no shadow.
     Whatever ambient glow it needs comes from a radial gradient that fades to
     nothing, so there is no edge for the eye to catch. */
  position: relative;
  display: grid;
  place-items: center;
  padding: clamp(.5rem, 2cqw, 1rem);
  background: radial-gradient(64% 58% at 50% 46%, rgba(42,105,206,.18) 0%, transparent 74%);
  border: 0;
  border-radius: 0;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  container-type: inline-size;
}

/* ---- the aurora, a slow coloured wash so the panel is never flat ---------- */
.viz-vault__aurora {
  position: absolute;
  inset: -30%;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(38% 44% at 26% 30%, #2A69CE59 0%, transparent 68%),
    radial-gradient(34% 40% at 76% 66%, #4FD1E03d 0%, transparent 70%),
    radial-gradient(46% 38% at 54% 96%, #8BBEF82e 0%, transparent 72%);
  filter: blur(6px);
  animation: vault-aurora 22s ease-in-out infinite;
  will-change: transform;
}
@keyframes vault-aurora {
  0%, 100% { transform: translate3d(-2%,  1%, 0) scale(1.04); }
  50%      { transform: translate3d( 2%, -2%, 0) scale(1.10); }
}

/* ---- the field, everything positional hangs off this --------------------- */
.viz-vault__field {
  position: relative;
  z-index: 1;
  width:  var(--vault-size);
  height: var(--vault-size);
  display: grid;
  place-items: center;
}

/* ---- the boundary. Two rings: a steady one and one that blooms ----------- */
.viz-vault__ring,
.viz-vault__bloom {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
}

.viz-vault__ring {
  border: 1px solid #5B9BD966;
  box-shadow:
    inset 0 0 40px -12px #2A69CE7a,
    0 0 28px -14px #5B9BD98c;
  background: radial-gradient(circle at 50% 50%, transparent 62%, #2A69CE14 88%, #2A69CE29 100%);
}

/* Blooms once per cycle, at the moment the wanderer reaches the edge. */
.viz-vault__bloom {
  border: 1px solid var(--vault-cyan);
  opacity: 0;
  animation: vault-bloom var(--vault-cycle) cubic-bezier(.22,.9,.24,1) infinite;
  will-change: transform, opacity;
}
@keyframes vault-bloom {
  0%,  44% { opacity: 0;   transform: scale(.97); }
  52%      { opacity: .85; transform: scale(1.015); }
  70%      { opacity: .18; transform: scale(1.05); }
  100%     { opacity: 0;   transform: scale(1.07); }
}

/* ---- the core ------------------------------------------------------------ */
.viz-vault__core {
  position: relative;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .6rem .9rem;
  border-radius: var(--r-md, 10px);
  background: linear-gradient(160deg, #163166 0%, #0E2148 100%);
  border: 1px solid #8BBEF84d;
  color: #EAF3FF;
  font: 600 var(--fs-micro, .75rem) / 1 var(--font-sans, inherit);
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
  box-shadow: 0 0 0 6px #2A69CE14, 0 12px 30px -16px #000;
  animation: vault-breathe var(--vault-cycle) ease-in-out infinite;
}
.viz-vault__core .ico { color: var(--vault-hot); }
@keyframes vault-breathe {
  0%, 100% { box-shadow: 0 0 0 6px  #2A69CE14, 0 12px 30px -16px #000; }
  50%      { box-shadow: 0 0 0 11px #2A69CE1f, 0 12px 30px -16px #000; }
}

/* ---- motes. Each wanders on its own clock -------------------------------- */
.viz-vault__mote {
  position: absolute;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 1.85rem;
  height: 1.85rem;
  border-radius: 50%;
  background: #0E1F40cc;
  border: 1px solid #5B9BD96b;
  color: var(--vault-hot);
  box-shadow: 0 0 14px -4px #5B9BD9a6;
  backdrop-filter: blur(2px);
  /* Sit the mote on its ring, then let the drift animation move it. */
  /* --rf is a fraction of the field, resolved to a real length. A percentage
     here would resolve against the mote itself and collapse the orbit. */
  --mote-r: calc(var(--vault-size) * var(--rf, .38));
  transform:
    rotate(calc(var(--a) * 1deg))
    translateY(calc(var(--mote-r) * -1))
    rotate(calc(var(--a) * -1deg));
  animation: vault-float var(--dur, 5s) ease-in-out infinite;
  animation-delay: var(--d, 0s);
  will-change: transform;
}
.viz-vault__mote .ico { width: .82rem; height: .82rem; }

/* Wander: a small offset in two axes plus a size breath. Because each mote has
   a different --dur, the field never lands on a shared beat. */
@keyframes vault-float {
  0%, 100% {
    transform:
      rotate(calc(var(--a) * 1deg)) translateY(calc(var(--mote-r) * -1)) rotate(calc(var(--a) * -1deg))
      translate3d(0, 0, 0) scale(1);
  }
  33% {
    transform:
      rotate(calc(var(--a) * 1deg)) translateY(calc(var(--mote-r) * -1)) rotate(calc(var(--a) * -1deg))
      translate3d(.42rem, -.5rem, 0) scale(1.06);
  }
  66% {
    transform:
      rotate(calc(var(--a) * 1deg)) translateY(calc(var(--mote-r) * -1)) rotate(calc(var(--a) * -1deg))
      translate3d(-.34rem, .42rem, 0) scale(.95);
  }
}

/* ---- the wanderer. Drifts out, meets the boundary, curves back ----------- */
.viz-vault__wander {
  position: absolute;
  z-index: 2;
  width: .5rem;
  height: .5rem;
  border-radius: 50%;
  background: var(--vault-cyan);
  box-shadow: 0 0 12px 2px #4FD1E0a6;
  animation: vault-wander var(--vault-cycle) cubic-bezier(.35,.02,.28,1) infinite;
  will-change: transform, opacity;
}
@keyframes vault-wander {
  0%   { transform: translate3d(0, 0, 0) scale(.7);            opacity: 0; }
  14%  { transform: translate3d(1.1rem, -1.6rem, 0) scale(1);  opacity: 1; }
  46%  { transform: translate3d(3.6rem, -5.2rem, 0) scale(1);  opacity: 1; }
  /* The turn. It does not stop dead, it is redirected. */
  58%  { transform: translate3d(3.1rem, -4.2rem, 0) scale(.92); opacity: 1; }
  80%  { transform: translate3d(.8rem, -1.1rem, 0) scale(.85);  opacity: .7; }
  100% { transform: translate3d(0, 0, 0) scale(.7);             opacity: 0; }
}

/* ---- tags ---------------------------------------------------------------- */
.viz-vault__tag {
  position: absolute;
  z-index: 4;
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .28rem .55rem;
  border-radius: var(--r-full, 40px);
  font: 600 var(--fs-nano, .6875rem) / 1 var(--font-sans, inherit);
  letter-spacing: .08em;
  text-transform: uppercase;
  white-space: nowrap;
}
.viz-vault__tag--in {
  top: .9rem; left: .9rem;
  background: #0E1F4099;
  border: 1px solid #5B9BD94d;
  color: #AFCFF2;
}
.viz-vault__tag--out {
  bottom: .9rem; right: .9rem;
  background: #0E1F4099;
  border: 1px solid #4FD1E040;
  color: #7FDCE8;
}
/* The out tag brightens exactly when the boundary blooms, so the two read as
   one event rather than two decorations. */
.viz-vault__tag--out {
  animation: vault-tag var(--vault-cycle) ease-out infinite;
}
@keyframes vault-tag {
  0%, 44% { opacity: .55; }
  54%     { opacity: 1; }
  100%    { opacity: .55; }
}

/* ---- narrower stages ----------------------------------------------------- */
@container (max-width: 30rem) {
  .viz-vault { --vault-size: clamp(12rem, 62cqw, 16rem); }
  .viz-vault__mote { width: 1.6rem; height: 1.6rem; }
  .viz-vault__tag { font-size: .625rem; }
}

/* ---- reduced motion: hold the informative frame, not frame one ----------- */
@media (prefers-reduced-motion: reduce) {
  .viz-vault__aurora,
  .viz-vault__core,
  .viz-vault__mote,
  .viz-vault__tag--out { animation: none; }

  .viz-vault__bloom {
    animation: none;
    opacity: .5;
    transform: scale(1.015);
  }
  /* Park the wanderer mid-turn, which is the moment that carries the meaning. */
  .viz-vault__wander {
    animation: none;
    transform: translate3d(3.3rem, -4.6rem, 0);
    opacity: 1;
  }
  .viz-vault__tag--out { opacity: 1; }
}

/* ---- reduced transparency ------------------------------------------------ */
@media (prefers-reduced-transparency: reduce) {
  .viz-vault__mote { backdrop-filter: none; background: #0E1F40; }
  .viz-vault__aurora { filter: none; opacity: .8; }
}

/* ============================================================================
   spot-card  ·  cursor-tracked dot reveal on a card
   ----------------------------------------------------------------------------
   Ported from the reference site's ss-spot, re-themed to PulsarOps blue and
   adapted to work on both light and dark sections via the context tokens.

   How it works, and why it is cheap
   Two pseudo-element layers hold the SAME dot grid. The resting layer sits at
   low opacity. The bright layer sits at opacity 0 and is revealed through a
   mask-image radial gradient positioned at --spot-x / --spot-y, which JS
   writes on pointermove. Nothing repaints except opacity and the mask origin,
   so a grid of these stays smooth.

   Add .spot-card to any .card. No markup change needed beyond the class.
   ========================================================================= */

.spot-card {
  --spot-x: 50%;
  --spot-y: 40%;
  --spot-size: 18px;
  --spot-reach: 13rem;
  position: relative;
  isolation: isolate;
  transition:
    transform .38s var(--ease-standard, cubic-bezier(.2,.7,.2,1)),
    border-color .38s var(--ease-standard, cubic-bezier(.2,.7,.2,1)),
    box-shadow .38s var(--ease-standard, cubic-bezier(.2,.7,.2,1));
}

.spot-card::before,
.spot-card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  border-radius: inherit;
  background-size: var(--spot-size) var(--spot-size);
  background-position: 0 0;
  transition: opacity .35s var(--ease-standard, cubic-bezier(.2,.7,.2,1));
}

/* Resting grid. Faint, present, gives the card a texture at rest. */
.spot-card::before {
  opacity: .5;
  background-image: radial-gradient(circle, var(--spot-dot-rest, #11141814) 1.4px, transparent 1.9px);
}

/* Bright grid, revealed only where the pointer is. */
.spot-card::after {
  opacity: 0;
  background-image: radial-gradient(circle, var(--spot-dot-live, #2A69CEd9) 1.5px, transparent 2px);
  -webkit-mask-image: radial-gradient(var(--spot-reach) circle at var(--spot-x) var(--spot-y),
                       #000 0%, #00000078 42%, transparent 70%);
          mask-image: radial-gradient(var(--spot-reach) circle at var(--spot-x) var(--spot-y),
                       #000 0%, #00000078 42%, transparent 70%);
}

.spot-card:hover::before,
.spot-card:focus-within::before { opacity: .75; }

.spot-card:hover::after,
.spot-card:focus-within::after { opacity: 1; }

.spot-card:hover,
.spot-card:focus-within {
  transform: translateY(-2px);
  border-color: var(--accent, #2A69CE);
  box-shadow: 0 18px 40px -28px #2A69CE8c;
}

/* Content must sit above both dot layers. */
.spot-card > * { position: relative; z-index: 1; }

/* Dark sections invert the grid so the dots read against the ground. */
.section--dark .spot-card,
.ctx-dark .spot-card {
  --spot-dot-rest: #8BBEF826;
  --spot-dot-live: #8BBEF8f2;
}

@media (prefers-reduced-motion: reduce) {
  .spot-card { transition: border-color .2s linear; }
  .spot-card:hover, .spot-card:focus-within { transform: none; }
  .spot-card::before, .spot-card::after { transition: opacity .2s linear; }
}

@media (hover: none) {
  /* No pointer to track. Keep the resting texture, drop the reveal. */
  .spot-card::after { display: none; }
}

/* ============================================================================
   flag  ·  country flags as inline SVG
   ----------------------------------------------------------------------------
   Flag EMOJI do not render on Windows. The system has no colour flag glyphs, so
   the browser falls back to the regional indicator letters and the user sees a
   "CA" letterbox instead of a Canadian flag. A large share of NOC staff are on
   Windows, so emoji was never an option here.

   These are inline SVG from a sprite, which renders identically on every
   platform, scales without blurring, and costs about 2KB per flag.

   Unlike the UI icon sprite these keep their own fills. A flag has to be its
   real colours, so it never inherits currentColor.

   <svg class="flag"><use href="assets/img/flags.svg#f-ca"/></svg>
   ========================================================================= */

.flag {
  display: inline-block;
  width: 1.5rem;
  height: 1.125rem;      /* 4:3 */
  flex: 0 0 auto;
  border-radius: 3px;
  /* A hairline keeps a white flag edge (Canada, Japan) from bleeding into a
     light background, and keeps every flag the same visual weight. */
  box-shadow: 0 0 0 1px #11141826, 0 1px 2px #11141814;
  vertical-align: -.18em;
  overflow: hidden;
}
.flag--lg { width: 2rem;   height: 1.5rem; }
.flag--sm { width: 1.125rem; height: .84375rem; }

/* On dark sections the ink hairline disappears, so switch to a light one. */
.section--dark .flag,
.ctx-dark .flag { box-shadow: 0 0 0 1px #ffffff2e, 0 1px 2px #0006; }

@media (prefers-contrast: more) {
  .flag { box-shadow: 0 0 0 1.5px currentColor; }
}

/* ============================================================================
   viz-ask  ·  hero overlay, not a card
   ----------------------------------------------------------------------------
   MECHANIC   typed query, then a resolving answer
   TEACHES    "One question. The cause, the fix, and what else it is breaking."
   CYCLE      3.6s  (4s hard ceiling, see _MOTION-LIBRARY.md rule one)

   Two things changed from the first build.

   1. IT IS AN OVERLAY, NOT A CARD.
      The browser chrome bar and the opaque white panel are gone. This is a
      translucent dark glass surface that lets the hero gradient through,
      matching the reference site ss-glass-dark treatment: a low alpha
      background, a real backdrop blur, and a hairline rim catching light at
      the top edge. It sits IN the hero rather than on top of it.

   2. THE TYPING IS PER CHARACTER, NOT clip-path steps().
      steps() moves the reveal edge in discrete jumps, which is what made the
      first version feel chunky. Each character now fades and unblurs on its
      own short transition with a small stagger, the same technique the
      reference site uses for its headline reveal. The caret glides linearly
      across the same span, so the motion reads continuous rather than
      ratcheted.
   ========================================================================= */

.viz-ask {
  --ask-cycle: 3.6s;
  --ask-type-dur: 0.38s;      /* the whole reveal wave, not per character */
  --ask-hot:  #8BBEF8;
  --ask-cyan: #4FD1E0;
  --ask-ok:   #6EE7A8;
  --ask-amber:#F4C77B;
}

.viz-ask__stage {
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
  /* .viz__stage ships overflow:hidden with a 20px radius. Stripping the card
     took its padding away too, so the avatar now sits flush in the corner and
     that curve was slicing it. Nothing here has a visible edge any more, so
     the radius has no job left to do. */
  border-radius: 0;
  container-type: inline-size;
}

/* ---- the glass surface --------------------------------------------------- */
.viz-ask__win {
  /* Cardless. No surface of its own: the prompt, the chips and the answer sit
     directly on the hero. Only the inner pieces carry any edge, and they are
     the minimum needed to read as an input and a reply. */
  position: relative;
  display: grid;
  max-width: 34rem;
  gap: 2rem;
  background: none;
  border: 0;
  box-shadow: none;
  padding: 0;
}

/* ---- rows ---------------------------------------------------------------- */
.viz-ask__row { display: flex; gap: 1rem; align-items: flex-start; }

.viz-ask__avatar {
  flex: 0 0 auto;
  width: 1.75rem; height: 1.75rem;
  display: grid; place-items: center;
  /* Square. A radius here read as neither circle nor square, so it goes to
     zero rather than to a value that has to be defended. */
  border-radius: 0;
  background: rgba(139,190,248,.10);
  border: 1px solid rgba(139,190,248,.28);
  color: #AFCFF2;
}
.viz-ask__avatar .ico { width: .88rem; height: .88rem; }
/* Square, same as the user avatar beside it. One of each read as a mistake. */
.viz-ask__avatar--ai {
  background: linear-gradient(150deg, #2A69CE 0%, #1B4E9E 100%);
  border-color: rgba(139,190,248,.45);
  color: #fff;
  opacity: 0;
  transition: opacity .3s ease-out;
}

/* ---- the prompt line ----------------------------------------------------- */
.viz-ask__prompt {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  padding: .55rem 0 1rem;
  border: 0;
  border-bottom: 1px solid rgba(139,190,248,.28);
  background: none;
}
.viz-ask__text {
  display: block;
  font: 500 var(--fs-body-sm, .86rem) / 1.6 var(--font-mono, ui-monospace, monospace);
  color: #E8F1FF;
  white-space: pre-wrap;
  word-break: break-word;
}

/* Each character is its own span, revealed on a short transition with a
   stagger. Blur plus a hair of vertical travel is what makes it read as soft
   rather than as a cursor scrubbing across the line. */
.viz-ask__ch {
  display: inline-block;
  opacity: 0;
  filter: blur(5px);
  transform: translateY(.08em);
  transition: opacity .09s linear, filter .09s linear, transform .09s linear;
  /* Each character runs the FULL cycle, so its delay is added on top of it,
     and the 4s ceiling is duration plus largest delay. A 52 character prompt
     therefore gets 0.4s of total stagger, not 0.4s per character. The prompt
     reads as a directional wash left to right rather than as a typewriter,
     which is the trade the ceiling forces and the right one: nobody waits
     through 52 keystrokes. Do not raise --ask-step without shortening
     --ask-cycle to match, and re-measure. */
}
.viz-ask__ch--sp { width: .6ch; }
}

.viz-ask__caret {
  position: absolute;
  transition: transform .06s linear;
  top: .52rem;
  left: .7rem;
  width: 1.5px;
  height: 1.05em;
  border-radius: 1px;
  background: var(--ask-cyan);
  box-shadow: 0 0 8px rgba(79,209,224,.85);
  animation: ask-blink 1s steps(2, end) infinite;
}
/* Glides continuously rather than stepping. --ask-w is the text width in ch. */
@keyframes ask-blink { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }

/* ---- evidence chips ------------------------------------------------------ */
.viz-ask__think { display: flex; flex-wrap: wrap; gap: .55rem; padding-left: 2.5rem; }
.viz-ask__chip {
  display: inline-flex; align-items: center; gap: .38rem;
  padding: .34rem .72rem;
  border-radius: 40px;
  background: rgba(139,190,248,.08);
  border: 1px solid rgba(139,190,248,.20);
  color: #AFCFF2;
  font: 600 var(--fs-nano, .6875rem) / 1 var(--font-sans, inherit);
  white-space: nowrap;
  opacity: 0;
  transition: opacity .24s ease-out, transform .24s cubic-bezier(.2,.8,.25,1);
  animation-delay: calc(var(--i) * .12s);
}
.viz-ask__chip .ico { width: .68rem; height: .68rem; color: var(--ask-hot); }

/* ---- the answer ---------------------------------------------------------- */
.viz-ask__answer {
  flex: 1 1 auto; min-width: 0;
  display: grid; gap: 1rem;
  padding: .15rem 0 .15rem 1.4rem;
  border-left: 2px solid rgba(139,190,248,.28);
  background: none;
  opacity: 0;
  transition: opacity .3s ease-out;
}

.viz-ask__line, .viz-ask__cause, .viz-ask__next {
  margin: 0;
  font: 500 var(--fs-body-sm, .84rem) / 1.68 var(--font-sans, inherit);
  color: #C6D8F2;
  opacity: 0;
  transition: opacity .28s ease-out, transform .28s cubic-bezier(.2,.8,.25,1);
  animation-delay: calc(var(--i) * .12s);
}
.viz-ask__cause {
  display: flex; align-items: center; gap: .35rem;
  margin-top: .35rem; padding-top: .95rem;
  border-top: 1px dashed rgba(139,190,248,.22);
  color: #EAF3FF; font-weight: 700;
}
.viz-ask__cause .ico { color: var(--ask-hot); width: .82rem; height: .82rem; }
.viz-ask__next { display: flex; align-items: center; gap: .35rem; color: var(--ask-ok); font-weight: 600; }
.viz-ask__next .ico { color: var(--ask-ok); width: .82rem; height: .82rem; }

/* ---- the correlation reveal, the punchline ------------------------------- */
.viz-ask__also {
  margin-top: 1.35rem; padding: .9rem 1.05rem;
  border-radius: 8px;
  background: rgba(244,199,123,.09);
  border: 1px solid rgba(244,199,123,.28);
  display: grid; gap: .45rem;
  opacity: 0;
  transition: opacity .34s ease-out, transform .34s cubic-bezier(.2,.85,.25,1);
}
.viz-ask__also-h {
  display: flex; align-items: center; gap: .32rem; margin: 0;
  font: 700 var(--fs-nano, .6875rem) / 1 var(--font-sans, inherit);
  letter-spacing: .07em; text-transform: uppercase; color: var(--ask-amber);
}
.viz-ask__also-h .ico { width: .78rem; height: .78rem; }
.viz-ask__also-b { margin: 0; font: 500 var(--fs-body-sm, .84rem) / 1.6 var(--font-sans, inherit); color: #EBDCC0; }
.viz-ask__also-tag {
  justify-self: start; margin-top: .08rem; padding: .14rem .4rem;
  border-radius: 40px; background: rgba(244,199,123,.9); color: #3A2A0C;
  font: 700 var(--fs-nano, .6875rem) / 1 var(--font-sans, inherit); letter-spacing: .04em;
}

@container (max-width: 26rem) {
  .viz-ask__think { padding-left: 0; }
  .viz-ask__chip { font-size: .625rem; }
}

@media (prefers-reduced-motion: reduce) {
  .viz-ask__ch { animation: none; opacity: 1; filter: none; transform: none; }
  .viz-ask__caret { display: none; }
  .viz-ask__chip, .viz-ask__line, .viz-ask__cause, .viz-ask__next,
  .viz-ask__answer, .viz-ask__also, .viz-ask__avatar--ai {
    animation: none; opacity: 1; transform: none;
  }
}


/* ---- vendor marks in the compatibility strip ---------------------------- */
.brand-mark {
  width: 1rem; height: 1rem;
  flex: 0 0 auto;
  opacity: .82;
  vertical-align: -.15em;
}
.pill--solid .brand-mark { opacity: .9; }
.pill:hover .brand-mark, .pill:focus-within .brand-mark { opacity: 1; }

/* ---- day in the life: colour runs along the narrative -------------------
   The story is a morning, so the accent moves from night to resolved rather
   than using four arbitrary colours. Each stage drives its own number badge,
   rule and spot reveal from --stage-accent.                              */
.flow__stage[style*="--stage-accent"] .flow__num,
.flow__stage[style*="--stage-accent"] .flow__label { color: var(--stage-accent); }
.flow__stage[style*="--stage-accent"] { border-top: 2px solid var(--stage-accent); }
.flow__stage.spot-card:hover { border-color: var(--stage-accent); }

/* ============================================================================
   viz-cycle  ·  the three stage diagram, rebuilt as live markup
   ----------------------------------------------------------------------------
   MECHANIC   circulation, a payload travelling a closed loop
   TEACHES    "Ingest, analyze, insights. The loop never stops."
   CYCLE      3.6s story cycle. The dashed orbit ring rotates on its own slow
              clock, which is ambient wallpaper with no start or end, the same
              exemption viz-field takes.
   REPLACES   the flat MIR slide image on about.html

   The slide showed three labelled circles with arrows between them. Rebuilding
   it as markup rather than shipping a JPEG means it is legible at any size,
   themeable, readable by a screen reader, and about 40x lighter. It also lets
   the payload actually travel the loop, which is the one thing a still image
   of a cycle cannot show.

   Stage names are theirs, verbatim, including INSIGHTS @ SCALE.
   ========================================================================= */

.viz-cycle {
  --cyc: 3.6s;
  --cyc-size: clamp(17rem, 30vw, 24rem);
  --cyc-r: calc(var(--cyc-size) * .36);
  max-width: 34rem;
  margin-inline: auto;
}
.viz-cycle__stage {
  /* Cardless. The visual carries itself, no panel, no border, no shadow.
     Whatever ambient glow it needs comes from a radial gradient that fades to
     nothing, so there is no edge for the eye to catch. */
  position: relative;
  display: grid;
  place-items: center;
  padding: clamp(.5rem, 2cqw, 1rem);
  background: radial-gradient(62% 56% at 50% 42%, rgba(42,105,206,.16) 0%, transparent 74%);
  border: 0;
  border-radius: 0;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  container-type: inline-size;
}
.viz-cycle__ring {
  position: relative;
  width: var(--cyc-size);
  height: var(--cyc-size);
  display: grid;
  place-items: center;
}
/* The orbit path, drawn once. */


/* Three stage nodes on the circle. --a is the angle. */
.viz-cycle__node {
  position: absolute;
  top: 50%; left: 50%;
  width: 7.1rem;
  margin: -3.55rem 0 0 -3.55rem;
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  gap: .1rem;
  text-align: center;
  border-radius: 50%;
  background: linear-gradient(160deg, rgba(24,52,102,.92), rgba(11,25,52,.92));
  border: 1px solid rgba(139,190,248,.24);
  box-shadow: 0 10px 26px -14px rgba(0,0,0,.8);
  transform: rotate(calc(var(--a) * 1deg)) translateY(calc(var(--cyc-r) * -1)) rotate(calc(var(--a) * -1deg));
}
/* Each node lights as the payload reaches it: 0s, 2s, 4s of a 6s loop. */
.viz-cycle__node::after {
  content: "";
  position: absolute; inset: -3px;
  border-radius: 50%;
  border: 1px solid var(--cyc-accent, #5B9BD9);
  opacity: 0;
  transform: scale(.94);
  transition: opacity .12s linear, transform .45s cubic-bezier(.2,.8,.25,1);
}
/* Lit by motion-d.js the instant the beam head crosses this node, so the ring
   and the beam cannot drift apart. */
.viz-cycle__node.is-hit::after { opacity: .95; transform: scale(1.06); }
.viz-cycle__num {
  font: 700 var(--fs-nano, .6875rem) / 1 var(--font-mono, ui-monospace, monospace);
  letter-spacing: .12em;
  color: var(--cyc-accent, #5B9BD9);
}
.viz-cycle__name {
  font: 700 var(--fs-micro, .74rem) / 1.25 var(--font-sans, inherit);
  color: #EAF3FF;
  padding-inline: .5rem;
}
.viz-cycle__sub {
  font: 500 .58rem / 1.3 var(--font-sans, inherit);
  color: #93B4DA;
  padding-inline: .55rem;
}
.viz-cycle__node .ico { width: .95rem; height: .95rem; color: var(--cyc-accent, #5B9BD9); }

/* The payload. Travels the full loop once per cycle. */


.viz-cycle__hub {
  position: relative; z-index: 2;
  display: grid; place-items: center; gap: .12rem;
  padding: .5rem .8rem;
  border-radius: 11px;
  background: rgba(6,13,30,.72);
  border: 1px solid rgba(139,190,248,.2);
  text-align: center;
}
.viz-cycle__hub b { font: 700 var(--fs-micro, .74rem)/1.2 var(--font-sans, inherit); color: #EAF3FF; }
.viz-cycle__hub span { font: 500 .58rem/1.2 var(--font-sans, inherit); color: #93B4DA; }

@container (max-width: 26rem) {
  .viz-cycle { --cyc-size: clamp(14rem, 74cqw, 18rem); }
  .viz-cycle__node { width: 5.6rem; margin: -2.8rem 0 0 -2.8rem; }
  .viz-cycle__name { font-size: .62rem; }
  .viz-cycle__sub { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .viz-cycle__orbit, .viz-cycle__payload { animation: none; }
  .viz-cycle__node::after {
  content: "";
  position: absolute; inset: -3px;
  border-radius: 50%;
  border: 1px solid var(--cyc-accent, #5B9BD9);
  opacity: 0;
  transform: scale(.94);
  transition: opacity .12s linear, transform .45s cubic-bezier(.2,.8,.25,1);
}
/* Lit by motion-d.js the instant the beam head crosses this node, so the ring
   and the beam cannot drift apart. */
.viz-cycle__node.is-hit::after { opacity: .95; transform: scale(1.06); }
  .viz-cycle__payload { transform: rotate(0deg) translateY(calc(var(--cyc-r) * -1)); }
}

/* ============================================================================
   viz-guard  ·  security overlay
   ----------------------------------------------------------------------------
   MECHANIC   inward sweep meeting a boundary
   TEACHES    "Your keys, your hardware, your perimeter."
   CYCLE      5s

   Three concentric arcs sweep inward toward a locked core and stop at the
   boundary. Deliberately almost wordless: three short labels, no sentences.
   ========================================================================= */

.viz-guard {
  --gd: 3.4s;
  --gd-size: clamp(13rem, 24vw, 18.5rem);
  max-width: 34rem;
  margin-inline: auto;
}
.viz-guard__stage {
  /* Cardless. The visual carries itself, no panel, no border, no shadow.
     Whatever ambient glow it needs comes from a radial gradient that fades to
     nothing, so there is no edge for the eye to catch. */
  position: relative;
  display: grid;
  place-items: center;
  padding: clamp(1.75rem, 5cqw, 3rem);
  /* The tag row is a second grid track, so the field sits above centre and the
     widest arc frame overshoots the top. Extra top padding buys it back. */
  padding-top: clamp(3.25rem, 8cqw, 4.75rem);
  overflow: visible;
  background: radial-gradient(58% 54% at 50% 46%, rgba(79,209,224,.12) 0%, transparent 72%);
  border: 0;
  border-radius: 0;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  container-type: inline-size;
}
.viz-guard__field { overflow: visible; position: relative; width: var(--gd-size); height: var(--gd-size); display: grid; place-items: center; }

/* Arcs sweep inward and stop. Nothing gets through. */
.viz-guard__arc {
  position: absolute; inset: 0;
  border-radius: 50%;
  border: 1px solid rgba(139,190,248,.30);
  border-top-color: #4FD1E0;
  opacity: 0;
  animation: gd-sweep var(--gd) cubic-bezier(.25,.7,.25,1) infinite;
  animation-delay: calc(var(--i) * .15s);
}
@keyframes gd-sweep {
  0%   { opacity: 0;   transform: scale(1.10) rotate(0deg); }
  16%  { opacity: .85; }
  62%  { opacity: .5;  transform: scale(1.02) rotate(150deg); }
  74%  { opacity: 0;   transform: scale(1) rotate(170deg); }
  100% { opacity: 0;   transform: scale(1) rotate(170deg); }
}

/* The boundary the arcs stop at, pulsing once per cycle. */
.viz-guard__wall {
  position: absolute; inset: 14%;
  border-radius: 50%;
  border: 1px solid rgba(79,209,224,.55);
  box-shadow: inset 0 0 34px -10px rgba(79,209,224,.5), 0 0 24px -12px rgba(79,209,224,.6);
  animation: gd-wall var(--gd) ease-in-out infinite;
}
@keyframes gd-wall {
  0%, 100% { transform: scale(1);    border-color: rgba(79,209,224,.4); }
  64%      { transform: scale(1.03); border-color: rgba(79,209,224,.95); }
}

.viz-guard__core {
  position: relative; z-index: 2;
  display: grid; place-items: center; gap: .3rem;
  width: 5.4rem; height: 5.4rem;
  border-radius: 50%;
  background: linear-gradient(160deg, rgba(24,52,102,.95), rgba(9,20,42,.95));
  border: 1px solid rgba(139,190,248,.3);
  color: #8BBEF8;
}
.viz-guard__core .ico { width: 1.5rem; height: 1.5rem; }
.viz-guard__core span {
  font: 700 .56rem/1 var(--font-sans, inherit);
  letter-spacing: .1em; text-transform: uppercase; color: #AFCFF2;
}
.viz-guard__tags { display: flex; flex-wrap: wrap; gap: .35rem; justify-content: center; margin-top: 1rem; }
.viz-guard__tag {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .24rem .55rem; border-radius: 40px;
  background: rgba(139,190,248,.08);
  border: 1px solid rgba(139,190,248,.2);
  color: #AFCFF2;
  font: 600 var(--fs-nano, .6875rem)/1 var(--font-sans, inherit);
  opacity: 0;
  animation: gd-tag var(--gd) ease-out infinite;
  animation-delay: calc(var(--i) * .12s);
}
.viz-guard__tag .ico { width: .7rem; height: .7rem; color: #4FD1E0; }
@keyframes gd-tag {
  0%, 56% { opacity: 0; transform: translateY(5px); }
  68%     { opacity: 1; transform: none; }
  92%     { opacity: 1; transform: none; }
  100%    { opacity: 0; }
}

@container (max-width: 26rem) {
  .viz-guard { --gd-size: clamp(12rem, 70cqw, 16rem); }
  .viz-guard__core { width: 4.4rem; height: 4.4rem; }
}
@media (prefers-reduced-motion: reduce) {
  .viz-guard__arc { animation: none; opacity: .4; transform: scale(1.05); }
  .viz-guard__wall { animation: none; }
  .viz-guard__tag { animation: none; opacity: 1; transform: none; }
}

/* ---- states driven by the JS timeline in motion-d.js --------------------- */
.viz-ask__ch.is-on   { opacity: 1; filter: blur(0); transform: none; }
.viz-ask__chip.is-on { opacity: 1; transform: none; }
.viz-ask__answer.is-on,
.viz-ask__avatar--ai.is-on { opacity: 1; }
.viz-ask__line.is-on,
.viz-ask__cause.is-on,
.viz-ask__next.is-on { opacity: 1; transform: none; }
.viz-ask__also.is-on { opacity: 1; transform: none; }

/* ---- the beam ------------------------------------------------------------
   An SVG circle carries the connecting line, and a second copy of the same
   circle carries a short bright arc that sweeps it. stroke-dasharray cuts the
   arc, stroke-dashoffset moves it, and pathLength normalises the maths so the
   numbers stay readable whatever the radius is.

   A travelling dot read as a bead on a wire. A beam reads as the signal
   actually moving through the connection, which is the point.             */
.viz-cycle__wire { position: absolute; inset: 0; overflow: visible; pointer-events: none; }
.viz-cycle__track {
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
  fill: none;
  stroke: rgba(139,190,248,.20);
  stroke-width: 1.25;
}
.viz-cycle__beam {
  /* Same rotation as the track, so path position 0 sits on node 01. */
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
  fill: none;
  stroke: url(#cycBeam);
  stroke-width: 2.25;
  stroke-linecap: round;
  /* 16% lit, 84% dark, so one clean streak runs the loop. */
  stroke-dasharray: 16 84;
  animation: cyc-beam var(--cyc) linear infinite;
  filter: drop-shadow(0 0 5px rgba(79,209,224,.75));
}
@keyframes cyc-beam {
  from { stroke-dashoffset: 16; }
  to   { stroke-dashoffset: -84; }
}
@media (prefers-reduced-motion: reduce) {
  .viz-cycle__beam { animation: none; stroke-dasharray: 16 84; stroke-dashoffset: -17; }
}

/* ---- vendor-flow  ·  infinite compatibility strip -------------------------
   The pill list wrapped to two rows and orphaned the last vendor. A marquee
   shows all of them in one line regardless of viewport, and reads as a feed
   rather than a grid.

   The track holds the list TWICE and travels exactly -50%, so the second copy
   lands precisely where the first started and the loop has no seam. The clone
   is aria-hidden so assistive tech reads the vendor list once.

   Edges are masked rather than faded with a gradient overlay, so the strip
   works on any section background without a colour-matched scrim.        */
.vendor-flow {
  position: relative;
  overflow: hidden;
  /* Full bleed. This rail is a claim about how much kit the platform speaks
     to, and a claim about breadth that stops at the text column argues
     against itself. Safe because body carries overflow-x: hidden, so 100vw
     cannot open a horizontal scrollbar. */
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
}
.vendor-flow__track {
  display: flex;
  align-items: flex-end;
  width: max-content;
  /* Rhythm comes from the items' own padding now, not a track gap, so the
     two halves of the loop stay exactly equal and the wrap is seamless. */
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  animation: vendor-flow var(--vf-dur, 30s) linear infinite;
  will-change: transform;
}

/* No pill. The logo is the object; the name is its caption, not a label
   competing with it for the same box. */
.vendor-flow__item {
  flex: 0 0 auto;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: .85rem;
  padding-inline: clamp(2.25rem, 5vw, 4.5rem);
  text-align: center;
}

/* A landscape box, not a square one. The marks are wildly different shapes:
   Fortinet and Windows are square, Juniper and Nokia are nearly 4:1. In a
   square box the wide ones letterbox down to a fraction of the height and
   read as small, which is what made the row look uneven. Fixing the HEIGHT
   and giving width room to spare lets each mark land at a comparable optical
   weight. */
.vendor-flow__item .brand-mark {
  width: 6rem;
  height: 3.25rem;
  opacity: 1;
}
.vendor-flow__name {
  font-size: .6875rem;
  font-weight: 650;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--fg-3);
  white-space: nowrap;
}
@keyframes vendor-flow {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .vendor-flow { overflow-x: auto; -webkit-mask-image: none; mask-image: none; }
  .vendor-flow__track { animation: none; }
}

/* ============================================================================
   quiz  ·  the qualification flow on demo.html
   ----------------------------------------------------------------------------
   One question per screen, centred, single section. Cardless like everything
   else: the question and its options sit on the page, nothing is boxed.
   ========================================================================= */
.quiz { position: relative; min-height: 30rem; display: grid; align-content: start; }
.quiz__rail { position: relative; height: 2px; background: rgba(139,190,248,.14); border-radius: 2px; margin-bottom: 3rem; }
.quiz__rail::after {
  content: ""; position: absolute; inset: 0 auto 0 0;
  width: calc(var(--p, 0) * 100%);
  background: linear-gradient(90deg, #2A69CE, #4FD1E0);
  border-radius: 2px;
  transition: width .5s cubic-bezier(.2,.8,.25,1);
}
.quiz__stage { position: relative; }

.q { display: grid; gap: 1.5rem; justify-items: center; text-align: center; max-width: 40rem; margin-inline: auto; }
.q__eyebrow {
  margin: 0; font: 600 var(--fs-nano, .6875rem)/1 var(--font-sans, inherit);
  letter-spacing: .16em; text-transform: uppercase; color: #7FA9D8;
}
.q__title {
  margin: 0; font: 700 clamp(1.5rem, 3.4vw, 2.3rem)/1.18 var(--font-sans, inherit);
  letter-spacing: -.02em; color: #F2F7FF; outline: none;
}
.q__help { margin: 0; max-width: 34rem; font: 500 .95rem/1.6 var(--font-sans, inherit); color: #9BB6DA; }

/* Options. Generous hit areas, generous gaps, nothing boxed in a panel. */
.q__opts { display: grid; gap: .75rem; width: 100%; max-width: 34rem; margin-top: .5rem; }
.q__opt {
  display: flex; align-items: center; gap: .9rem;
  width: 100%; padding: 1.05rem 1.25rem;
  border-radius: 12px;
  background: rgba(139,190,248,.05);
  border: 1px solid rgba(139,190,248,.16);
  color: #DCE9FA; text-align: left;
  font: 500 1rem/1.4 var(--font-sans, inherit);
  cursor: pointer;
  transition: background .18s, border-color .18s, transform .18s, opacity .28s;
}
.q__opt:hover { background: rgba(139,190,248,.10); border-color: rgba(139,190,248,.4); }
.q__opt:active { transform: scale(.985); }
.q__opt:focus-visible { outline: 2px solid #4FD1E0; outline-offset: 2px; }
.q__key {
  flex: 0 0 auto; display: grid; place-items: center;
  width: 1.6rem; height: 1.6rem; border-radius: 6px;
  background: rgba(139,190,248,.10); border: 1px solid rgba(139,190,248,.2);
  font: 600 .72rem/1 var(--font-mono, ui-monospace, monospace); color: #8BBEF8;
}
.q__opt.is-picked { background: rgba(79,209,224,.14); border-color: #4FD1E0; }
.q__opt.is-picked .q__key { background: #4FD1E0; border-color: #4FD1E0; color: #05203a; }
.q__opt.is-dimmed { opacity: .32; }

/* Steps slide a short distance. Long travel on a full-height panel reads as
   a page transition rather than as one question replacing another. */
.q { transition: opacity .34s ease, transform .34s cubic-bezier(.2,.8,.25,1); }
.q.is-enter      { opacity: 0; transform: translateX(26px); }
.q.is-enter-back { opacity: 0; transform: translateX(-26px); }
.q.is-exit       { opacity: 0; transform: translateX(-26px); position: absolute; inset: 0; }
.q.is-exit-back  { opacity: 0; transform: translateX(26px);  position: absolute; inset: 0; }

.q__form { display: grid; gap: 1.15rem; width: 100%; max-width: 30rem; text-align: left; }
.q__field { display: grid; gap: .4rem; }
.q__label { font: 600 .8rem/1 var(--font-sans, inherit); color: #9BB6DA; letter-spacing: .02em; }
.q__input {
  width: 100%; padding: .8rem .95rem; border-radius: 10px;
  background: rgba(6,13,30,.5); border: 1px solid rgba(139,190,248,.22);
  color: #EAF3FF; font: 500 .95rem/1.4 var(--font-sans, inherit);
}
.q__input:focus { outline: none; border-color: #4FD1E0; box-shadow: 0 0 0 3px rgba(79,209,224,.16); }
.q__input[aria-invalid="true"] { border-color: #E0736B; }
.q__err { margin: 0; min-height: 1em; font: 500 .78rem/1.3 var(--font-sans, inherit); color: #E0736B; }
.q__consent { display: flex; gap: .6rem; align-items: flex-start; font: 500 .82rem/1.5 var(--font-sans, inherit); color: #9BB6DA; }
.q__submit { justify-self: start; margin-top: .4rem; }
.q__actions { display: flex; flex-wrap: wrap; gap: .75rem; justify-content: center; margin-top: .5rem; }
.q__foot { margin: 0; font: 500 .8rem/1.5 var(--font-sans, inherit); color: #7C97BC; }

/* Booking placeholder, sized to a real calendar embed. */
.quiz-embed {
  width: 100%; max-width: 34rem; min-height: 19rem;
  display: grid; place-content: center; gap: .5rem; text-align: center;
  padding: 2rem; border-radius: 14px;
  border: 1px dashed rgba(139,190,248,.3);
  background: rgba(139,190,248,.04);
}
.quiz-embed__tag {
  justify-self: center; padding: .22rem .6rem; border-radius: 40px;
  background: rgba(244,199,123,.16); border: 1px solid rgba(244,199,123,.4);
  color: #F4C77B; font: 700 .66rem/1 var(--font-sans, inherit);
  letter-spacing: .12em; text-transform: uppercase;
}
.quiz-embed__t { margin: 0; font: 600 1.05rem/1.3 var(--font-sans, inherit); color: #DCE9FA; }
.quiz-embed__d { margin: 0; font: 500 .84rem/1.5 var(--font-sans, inherit); color: #8BA6CA; }

.quiz__back {
  justify-self: center; margin-top: 2.5rem;
  background: none; border: 0; cursor: pointer;
  font: 600 .82rem/1 var(--font-sans, inherit); color: #7FA9D8;
}
.quiz__back:hover { color: #BBD5F2; }

@media (prefers-reduced-motion: reduce) {
  .q { transition: opacity .18s ease; }
  .q.is-enter, .q.is-enter-back, .q.is-exit, .q.is-exit-back { transform: none; }
}

/* Logo chips carry a mark now, so they need to lay out as a row. */
.logo-chip { display: inline-flex; align-items: center; gap: .5rem; }
.logo-chip .brand-mark { opacity: .95; }

/* ============================================================================
   resources index pills
   ----------------------------------------------------------------------------
   These four sit directly above the deployment rail, so they are the last
   thing read before it. Centred to line up with the heading above them, and
   each one lit on its own offset so the row breathes rather than blinking as
   a block. Scoped to .hero__index, which only resources.html carries.
   ========================================================================= */
.hero__index { justify-content: center; }

.hero__index .pill {
  animation: pill-glow 3.4s cubic-bezier(.4, 0, .2, 1) infinite;
}
.hero__index .pill:nth-child(2) { animation-delay: .45s; }
.hero__index .pill:nth-child(3) { animation-delay: .90s; }
.hero__index .pill:nth-child(4) { animation-delay: 1.35s; }

@keyframes pill-glow {
  0%, 100% {
    box-shadow: 0 0 0 0 rgba(42, 105, 206, 0);
    border-color: var(--border);
  }
  50% {
    box-shadow:
      0 0 20px -4px rgba(42, 105, 206, .55),
      inset 0 0 14px -9px rgba(139, 190, 248, .65);
    border-color: rgba(139, 190, 248, .45);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero__index .pill { animation: none; }
}

/* The MIR lockup in the origin story byline. Their own file, not a redraw:
   wordmark plus the ascending bars, whose mid blue #2A69CE happens to be the
   accent this site already runs on. The light variant only recolours the
   wordmark for the dark hero; the bars keep their brand colours. */
.mir-logo {
  display: block;
  height: 1.4rem;
  width: auto;
}

/* ============================================================================
   comparison table, scroll affordance
   ----------------------------------------------------------------------------
   The table has min-width 720px and scrolls inside .cmp, so on a phone the
   third column is cut mid-word with nothing to say there is more. A fade on
   the right edge says it.

   It is painted on the WRAPPER, not on .cmp: the cells carry their own
   backgrounds and would paint straight over anything set on the scroll
   container, and a pseudo-element inside .cmp would scroll away with the
   content. The wrapper does not scroll, so it stays put.

   The fade clears itself once there is nothing left to reveal, which is what
   .is-scroll-end is for. That class is also set when the table fits, so the
   fade never appears on a screen wide enough not to need it.
   ========================================================================= */
.cmp-frame { position: relative; }

@media (max-width: 860px) {
  .cmp-frame::after {
    content: "";
    position: absolute;
    top: 1px;
    bottom: 1px;
    right: 1px;
    width: 3.25rem;
    pointer-events: none;
    border-radius: 0 var(--r-lg) var(--r-lg) 0;
    background: linear-gradient(to left, var(--surface), transparent);
    transition: opacity .22s ease;
  }
  .cmp-frame.is-scroll-end::after { opacity: 0; }
}

/* The deployment rail runs full bleed, so its label and caption were the only
   things still hanging off the left of a band that reads as centred. Scoped to
   the modifier: .viz__label and .viz__cap are shared by every visual on the
   site and are left aligned everywhere else on purpose. */
/* .viz__label is inline-flex, so text-align has to sit on the PARENT to move
   the box itself; setting it on the label only centres the text inside a box
   that is still hugging the left. justify-content covers the flex children. */
.viz--center { text-align: center; }
.viz--center .viz__label,
.viz--center .viz__cap,
.viz--center > .meta {
  justify-content: center;
  text-align: center;
}

/* Time-preference fields on the last step (2026-09-29). */
.q__group { margin: .6rem 0 -.3rem; padding-top: 1rem; border-top: 1px solid rgba(139,190,248,.16); font: 600 .72rem/1 var(--font-sans, inherit); letter-spacing: .12em; text-transform: uppercase; color: #4FD1E0; }
.q__select { appearance: none; cursor: pointer; padding-right: 2.4rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%239BB6DA' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .95rem center; }
.q__select option { background: #0B1630; color: #EAF3FF; }
.q__textarea { resize: vertical; min-height: 5.5rem; }
.q__alt { margin-top: 1rem; background: none; border: 0; padding: .25rem; cursor: pointer; color: #4FD1E0; font: 500 .9rem/1.4 var(--font-sans, inherit); text-decoration: underline; text-underline-offset: 3px; }
.q__alt:hover { color: #EAF3FF; }

/* GHL booking calendar on demo.html (ported from booking-site, 2026-09-29). */
.quiz-frame { display: block; width: 100%; min-height: 52rem; border: 0; border-radius: 12px; background: #fff; }
@media (max-width: 640px) { .quiz-frame { min-height: 48rem; } }
.q__escape { margin-top: 1rem; text-align: center; opacity: 0; visibility: hidden; transition: opacity .4s ease; }
.q__escape.is-on { opacity: 1; visibility: visible; }
