/*
 * Effects layer for directions D, E and F.
 *
 * shared.css stays the austere token set that A/B/C are built from. This file
 * adds the depth those three deliberately do without: aurora fields, glass,
 * gradient borders, bento, marquee, cursor spotlight and the richer product
 * surfaces. It still may not introduce a raw colour — every gradient and glow
 * below is composed from the shared tokens or from the effect tokens declared
 * here, so a palette change is still a one-file change.
 *
 * Everything that moves is wrapped in prefers-reduced-motion, and nothing is
 * hidden before paint. The live site's failure mode is 71 elements sitting at
 * opacity 0 waiting for scroll JS; here content renders and the animation is
 * additive, so a JS failure costs polish rather than the page.
 */

:root {
  /* Effect tokens. Derived from --accent and the ink ramp, never new hues. */
  --fx-violet: #7C5CFF;
  --fx-glow-accent: 0 0 0 1px rgba(12, 99, 253, .35), 0 12px 40px -12px rgba(12, 99, 253, .55);

  /* The semantic ramp from shared.css is tuned for text on white and fails
     contrast on ink. These are the same three meanings, lightened for dark
     surfaces, plus the tints that back them. */
  --ok-on-dark: #6EE7A8;
  --warn-on-dark: #FDBA74;
  --bad-on-dark: #FF9B8F;
  --ok-tint-dark: rgba(15, 123, 79, .16);
  --warn-tint-dark: rgba(181, 71, 8, .14);
  --bad-tint-dark: rgba(180, 35, 24, .16);

  --fx-grad-brand: linear-gradient(120deg, var(--accent), var(--fx-violet));
  --fx-grad-text: linear-gradient(100deg, #FFFFFF 20%, #9EC1FF 55%, #C9B8FF 90%);
  --fx-grad-hair: linear-gradient(90deg, transparent, rgba(255,255,255,.28), transparent);

  --fx-glass: rgba(255, 255, 255, .055);
  --fx-glass-line: rgba(255, 255, 255, .11);
  --fx-glass-line-hi: rgba(255, 255, 255, .2);

  --fx-ink-1: #070C18;
  --fx-ink-2: #0C1426;

  --fx-ease: cubic-bezier(.22, .61, .36, 1);
  --fx-ease-out: cubic-bezier(.16, 1, .3, 1);
}

/* ============================================================
   Backgrounds
   ============================================================ */

.fx-dark {
  background: var(--fx-ink-1);
  color: var(--on-dark);
}

/* Atmosphere is layered across two elements on purpose. An element only has
   one ::before and one ::after, so stacking aurora + mesh + grain as three
   classes on the same node silently drops two of them — the orbs, the grid and
   the noise would fight over the same two pseudo-elements and last-declared
   would win. The section owns the orbs; a child .fx-bg owns grid and grain.
   Paint order: orbs (-3) -> grid + grain (-2) -> content (0). */
.fx-aurora { position: relative; isolation: isolate; overflow: clip; }
.fx-aurora::before,
.fx-aurora::after {
  content: '';
  position: absolute;
  z-index: -3;
  border-radius: var(--r-full);
  filter: blur(80px);
  pointer-events: none;
}
.fx-aurora::before {
  width: 900px; height: 700px;
  top: -300px; left: 50%; margin-left: -820px;
  opacity: .8;
  background: radial-gradient(closest-side, var(--accent), transparent 70%);
}
.fx-aurora::after {
  width: 820px; height: 640px;
  top: -260px; left: 50%; margin-left: 60px;
  opacity: .6;
  background: radial-gradient(closest-side, var(--fx-violet), transparent 70%);
}
@media (prefers-reduced-motion: no-preference) {
  /* transform and opacity only, so this never triggers layout or paint. */
  .fx-aurora::before { animation: drift-a 22s var(--fx-ease) infinite alternate; }
  .fx-aurora::after { animation: drift-b 26s var(--fx-ease) infinite alternate; }
  @keyframes drift-a { to { transform: translate3d(110px, 60px, 0) scale(1.14); } }
  @keyframes drift-b { to { transform: translate3d(-90px, 80px, 0) scale(1.1); } }
}

.fx-bg { position: absolute; inset: 0; z-index: -2; pointer-events: none; }

/* A 1px grid that fades out. Reads as "engineering" without a texture file. */
.fx-bg::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px);
  background-size: 58px 58px;
  -webkit-mask-image: radial-gradient(130% 95% at 50% 0%, #000 22%, transparent 76%);
  mask-image: radial-gradient(130% 95% at 50% 0%, #000 22%, transparent 76%);
}

/* Grain. Kills the banding large blurred gradients show on 8-bit panels. */
.fx-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  opacity: .3;
  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='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.42'/%3E%3C/svg%3E");
}

.fx-hair { height: 1px; border: 0; margin: 0; background: var(--fx-grad-hair); }

/* ============================================================
   Type
   ============================================================ */

.fx-display {
  font-size: clamp(40px, 6.4vw, 82px);
  line-height: 1.02;
  letter-spacing: -.045em;
  font-weight: 800;
  color: var(--white);
}
.fx-gradient-text {
  background: var(--fx-grad-text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.fx-balance { text-wrap: balance; }

/* ============================================================
   Surfaces
   ============================================================ */

.fx-glass {
  background: var(--fx-glass);
  border: 1px solid var(--fx-glass-line);
  border-radius: var(--r-md);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
}

/* Gradient border without a wrapper element: paint the gradient on the
   pseudo-element and mask out everything but the 1px ring. */
.fx-ring { position: relative; }
.fx-ring::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(140deg, rgba(255,255,255,.34), rgba(255,255,255,.05) 40%, transparent 70%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}

/* Cursor spotlight. fx.js writes --mx/--my as percentages on hover. */
.fx-spot { position: relative; overflow: hidden; }
.fx-spot::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity .35s var(--fx-ease);
  background: radial-gradient(340px circle at var(--mx, 50%) var(--my, 50%), rgba(12,99,253,.16), transparent 62%);
}
.fx-spot:hover::after { opacity: 1; }

.fx-lift { transition: transform .3s var(--fx-ease-out), border-color .3s var(--fx-ease); }
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .fx-lift:hover { transform: translateY(-3px); border-color: var(--fx-glass-line-hi); }
}

/* ============================================================
   Bento
   ============================================================ */

.bento {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--s-4);
}
.bento > * { min-width: 0; }
.b-2 { grid-column: span 2; }
.b-3 { grid-column: span 3; }
.b-4 { grid-column: span 4; }
.b-6 { grid-column: span 6; }
.b-tall { grid-row: span 2; }
@media (max-width: 1000px) {
  .bento { grid-template-columns: repeat(4, 1fr); }
  .b-2, .b-3 { grid-column: span 2; }
  .b-4, .b-6 { grid-column: span 4; }
  .b-tall { grid-row: auto; }
}
@media (max-width: 620px) {
  .bento { grid-template-columns: 1fr; }
  .b-2, .b-3, .b-4, .b-6 { grid-column: span 1; }
}

.bento-cell {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-5);
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  background: var(--white);
  overflow: hidden;
}
.bento-cell h3 { font-size: var(--t-lg); }
.bento-cell .caption { margin-top: -4px; }
.fx-dark .bento-cell { background: var(--fx-glass); border-color: var(--fx-glass-line); }
.fx-dark .bento-cell h3 { color: var(--white); }
.fx-dark .bento-cell p { color: var(--on-dark); }

/* ============================================================
   Marquee
   ============================================================ */

.marquee { display: flex; overflow: hidden; gap: var(--s-7); mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
.marquee__track { display: flex; gap: var(--s-7); flex: none; align-items: center; padding-right: var(--s-7); }
@media (prefers-reduced-motion: no-preference) {
  .marquee__track { animation: slide 38s linear infinite; }
  .marquee:hover .marquee__track { animation-play-state: paused; }
  @keyframes slide { to { transform: translateX(-100%); } }
}
.marquee img { height: 26px; width: 96px; object-fit: contain; flex: none; opacity: .68; filter: grayscale(1); transition: opacity .25s var(--fx-ease), filter .25s var(--fx-ease); }
.marquee img:hover { opacity: 1; filter: none; }
/* On ink, knock the marks to flat white silhouettes. `grayscale + invert`
   leaves the light ones (Shopify, Square) nearly black and invisible;
   `brightness(0) invert(1)` is unconditional. Hover restores brand colour. */
.fx-dark .marquee img { filter: brightness(0) invert(1); opacity: .42; }
.fx-dark .marquee img:hover { filter: none; opacity: 1; }

/* ============================================================
   Scroll reveal
   ============================================================ */

/* Gated on .js-reveal, which an inline script in <head> adds before first
   paint and then removes again shortly after `load` if fx.js never announced
   itself. Two failure modes are covered: no JS at all (the class is never
   added) and fx.js 404ing or throwing (the class is added, then withdrawn).
   Either way the page ends up visible, which is the opposite of the live site,
   where 71 elements sit at opacity 0 until Elementor's scroll handler fires. */
@media (prefers-reduced-motion: no-preference) {
  .js-reveal .fx-in { opacity: 0; transform: translateY(16px); transition: opacity .6s var(--fx-ease-out), transform .6s var(--fx-ease-out); }
  .js-reveal .fx-in.is-in { opacity: 1; transform: none; }
  .fx-in-1 { transition-delay: .06s } .fx-in-2 { transition-delay: .12s }
  .fx-in-3 { transition-delay: .18s } .fx-in-4 { transition-delay: .24s }
  .fx-in-5 { transition-delay: .3s }
}

/* Data-bearing surfaces draw themselves in when revealed. Every one of these
   is gated on .js-reveal for the same reason the reveals are: if fx.js never
   loads, the class is withdrawn and the bar, line or chart renders at its
   final value instead of at zero. A chart animating from empty is worth it;
   a chart *stuck* empty is a broken page. */
@media (prefers-reduced-motion: no-preference) {
  .js-reveal .fx-in .hist span { height: 0; }
  .js-reveal .fx-in.is-in .hist span { height: var(--h, 20%); transition: height .7s var(--fx-ease-out); }
  .js-reveal .fx-in .hist span:nth-child(2n) { transition-delay: .05s }
  .js-reveal .fx-in .hist span:nth-child(3n) { transition-delay: .1s }

  .js-reveal .fx-in .health__bar i { width: 0; }
  .js-reveal .fx-in.is-in .health__bar i { width: var(--w, 100%); transition: width .9s var(--fx-ease-out); }
  .js-reveal .fx-in .health__row:nth-child(3) ~ .health__row .health__bar i,
  .js-reveal .fx-in .health__bar:nth-of-type(2) i { transition-delay: .08s }

  /* 420 comfortably exceeds the path length of the 200x34 sparkline, so one
     dasharray covers any curve we draw into it. */
  .js-reveal .fx-in .spark path:not(.fill) { stroke-dasharray: 420; stroke-dashoffset: 420; }
  .js-reveal .fx-in.is-in .spark path:not(.fill) { stroke-dashoffset: 0; transition: stroke-dashoffset 1.5s var(--fx-ease-out); }
  .js-reveal .fx-in .spark .fill { opacity: 0; }
  .js-reveal .fx-in.is-in .spark .fill { opacity: 1; transition: opacity .7s .55s ease; }
}

/* ============================================================
   Reading progress
   ============================================================ */

/* Two pixels of accent under the sticky header. On a page this long it is the
   cheapest way to answer "how much of this is left". */
.fx-progress { position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 60; pointer-events: none; }
.fx-progress i {
  display: block; height: 100%; transform-origin: 0 50%;
  transform: scaleX(var(--p, 0));
  background: var(--fx-grad-brand);
}

/* ============================================================
   Live sync demo
   ============================================================ */

.demo { display: flex; flex-direction: column; gap: var(--s-3); }
.demo__head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); flex-wrap: wrap; }
.demo__title { display: flex; align-items: center; gap: var(--s-2); font-size: var(--t-sm); font-weight: 600; color: var(--white); }
.demo__live { display: inline-flex; align-items: center; gap: 6px; font-size: var(--t-xs); font-weight: 600; color: var(--ok); }
.demo__dot { width: 7px; height: 7px; border-radius: var(--r-full); background: var(--ok); }
@media (prefers-reduced-motion: no-preference) {
  .demo__dot { animation: blip 1.8s ease-in-out infinite; }
  @keyframes blip { 0%,100% { box-shadow: 0 0 0 0 rgba(15,123,79,.55) } 50% { box-shadow: 0 0 0 6px rgba(15,123,79,0) } }
}

.demo__rows { display: flex; flex-direction: column; gap: 7px; }
.demo__row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: var(--s-3);
  padding: 10px var(--s-3);
  border-radius: var(--r-sm);
  border: 1px solid var(--fx-glass-line);
  background: rgba(255,255,255,.03);
  transition: border-color .3s var(--fx-ease), background-color .3s var(--fx-ease);
}
.demo__row.is-source { border-color: rgba(12,99,253,.6); background: rgba(12,99,253,.1); }
.demo__row.is-hit { border-color: rgba(15,123,79,.5); background: rgba(15,123,79,.09); }
.demo__chan { display: flex; align-items: center; gap: var(--s-2); font-size: var(--t-sm); color: var(--white); font-weight: 500; flex-wrap: wrap; }
.demo__chan img { width: 17px; height: 17px; object-fit: contain; }
.demo__poll { font-size: 11px; font-weight: 500; color: var(--on-dark-dim); }
.demo__count { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--white); font-size: var(--t-base); min-width: 2.5ch; text-align: right; }
.demo__count.is-tick { animation: tick .45s var(--fx-ease); }
@keyframes tick { 0% { transform: translateY(-7px); opacity: 0; color: var(--accent-on-dark) } 100% { transform: none; opacity: 1 } }
.demo__state { font-size: 11px; color: var(--on-dark-dim); min-width: 8ch; text-align: right; font-variant-numeric: tabular-nums; }
.demo__state.is-ok { color: var(--ok); }
.demo__foot { display: flex; justify-content: space-between; gap: var(--s-3); font-size: var(--t-xs); color: var(--on-dark-dim); flex-wrap: wrap; }

/* Light variant of the demo. Promoted here from e-bento.css once a second
   page needed it — the point of this layer is that a component has one
   definition, not one per page. */
.demo--light .demo__title { color: var(--ink); }
.demo--light .demo__row { border-color: var(--line); background: var(--white); }
.demo--light .demo__row.is-source { border-color: var(--accent); background: var(--accent-tint); }
.demo--light .demo__row.is-hit { border-color: rgba(15,123,79,.4); background: var(--ok-tint); }
.demo--light .demo__chan { color: var(--ink); }
.demo--light .demo__poll { color: var(--muted); }
.demo--light .demo__count { color: var(--ink); }
.demo--light .demo__state { color: var(--muted); }
.demo--light .demo__state.is-ok { color: var(--ok); }
.demo--light .demo__foot { color: var(--muted); }

/* ============================================================
   Channel coverage rail
   ============================================================ */

.rail-scroll { display: flex; gap: var(--s-3); overflow-x: auto; padding-bottom: var(--s-3); scroll-snap-type: x mandatory; }
.rail-scroll::-webkit-scrollbar { height: 6px; }
.rail-scroll::-webkit-scrollbar-thumb { background: var(--line); border-radius: var(--r-full); }
.chan-card {
  flex: none; width: 224px; scroll-snap-align: start;
  display: flex; flex-direction: column; gap: var(--s-3);
  padding: var(--s-4); border-radius: var(--r-md);
  border: 1px solid var(--line); background: var(--white);
  transition: border-color .25s var(--fx-ease), transform .25s var(--fx-ease-out), box-shadow .25s var(--fx-ease);
}
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .chan-card:hover { border-color: var(--accent); transform: translateY(-3px); box-shadow: var(--shadow-md); }
}
.chan-card__top { display: flex; align-items: center; gap: var(--s-3); }
.chan-card__top img { width: 30px; height: 30px; object-fit: contain; }
.chan-card__name { font-weight: 700; color: var(--ink); font-size: var(--t-base); }
.chan-card__tags { display: flex; flex-wrap: wrap; gap: 5px; }
.chan-card__tag { font-size: 11px; padding: 3px 8px; border-radius: var(--r-full); background: var(--surface); color: var(--muted); }
/* baseline rather than center: centre is measured against the whole text
   block, so as soon as a timing line wraps the dot floats between the two
   lines and marks neither. Same intent as .hero__note .dot in home.css. */
.chan-card__lat { display: flex; align-items: baseline; gap: 6px; font-size: var(--t-xs); color: var(--muted); margin-top: auto; }

/* ============================================================
   Pricing as rows
   ============================================================ */

.price-rows { border-top: 1px solid var(--line); }
.price-row { display: grid; grid-template-columns: 1.2fr .8fr 2fr auto; gap: var(--s-5); padding: var(--s-5) 0; border-bottom: 1px solid var(--line); align-items: center; }
@media (max-width: 860px) { .price-row { grid-template-columns: 1fr 1fr; gap: var(--s-3); } }
.price-row__name { font-size: var(--t-lg); font-weight: 700; color: var(--ink); letter-spacing: -.02em; }
.price-row__amt { font-size: var(--t-2xl); font-weight: 800; color: var(--ink); letter-spacing: -.03em; }
.price-row__amt span { font-size: var(--t-sm); font-weight: 500; color: var(--muted); letter-spacing: 0; }
.price-row.is-featured .price-row__name::after {
  content: 'Most chosen';
  display: inline-block; margin-left: var(--s-2); padding: 2px 8px;
  border-radius: var(--r-full); background: var(--accent-tint); color: var(--accent);
  font-size: 11px; font-weight: 600; letter-spacing: 0; vertical-align: middle;
}

/* ============================================================
   Richer product surfaces
   ============================================================ */

.panel { border-radius: var(--r-md); border: 1px solid var(--line); background: var(--white); overflow: hidden; }
.panel__head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); padding: var(--s-3) var(--s-4); border-bottom: 1px solid var(--line); }
.panel__title { font-size: var(--t-sm); font-weight: 700; color: var(--ink); }
.panel__body { padding: var(--s-4); }
.fx-dark .panel { background: var(--fx-glass); border-color: var(--fx-glass-line); }
.fx-dark .panel__head { border-color: var(--fx-glass-line); }
.fx-dark .panel__title { color: var(--white); }

/* Latency histogram. Bar heights come from --h so the markup carries data
   rather than styling. */
.hist { display: flex; align-items: flex-end; gap: 3px; height: 76px; }
.hist span { flex: 1; border-radius: 3px 3px 0 0; background: var(--accent); opacity: .28; height: var(--h, 20%); transition: opacity .2s var(--fx-ease); }
.hist span.is-peak { opacity: 1; }
.hist:hover span { opacity: .55; }
.hist:hover span.is-peak { opacity: 1; }

/* Data-bearing sizes as classes rather than style="" attributes, so the
   markup stays lintable and the values stay enumerable. In the WordPress
   build these become a PHP helper that emits the same class names. */
.fx-h-7 { --h: 7% } .fx-h-10 { --h: 10% } .fx-h-14 { --h: 14% } .fx-h-18 { --h: 18% }
.fx-h-22 { --h: 22% } .fx-h-26 { --h: 26% } .fx-h-30 { --h: 30% } .fx-h-34 { --h: 34% }
.fx-h-40 { --h: 40% } .fx-h-48 { --h: 48% } .fx-h-52 { --h: 52% } .fx-h-60 { --h: 60% }
.fx-h-66 { --h: 66% } .fx-h-72 { --h: 72% } .fx-h-78 { --h: 78% } .fx-h-86 { --h: 86% }
.fx-h-96 { --h: 96% } .fx-h-100 { --h: 100% }

.fx-w-40 { --w: 40% } .fx-w-55 { --w: 55% } .fx-w-62 { --w: 62% } .fx-w-70 { --w: 70% }
.fx-w-78 { --w: 78% } .fx-w-84 { --w: 84% } .fx-w-88 { --w: 88% } .fx-w-92 { --w: 92% }
.fx-w-96 { --w: 96% } .fx-w-100 { --w: 100% }

.spark { display: block; width: 100%; height: 34px; }
.spark path { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.spark .fill { fill: var(--accent-tint); stroke: none; }
.fx-dark .spark .fill { fill: rgba(12,99,253,.16); }

.health { display: flex; flex-direction: column; gap: 9px; }
.health__row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: var(--s-3); font-size: var(--t-sm); }
.health__bar { grid-column: 1 / -1; height: 5px; border-radius: var(--r-full); background: var(--line); overflow: hidden; }
/* Accent rather than --ok. The fill never varies with the number — 100% and
   97.4% draw the same colour — so it encodes magnitude, not a pass/fail state,
   and shared.css reserves the semantic ramp ("Semantic only — never
   decorative"). The .hist directly below it in the same card already draws
   magnitude in --accent; two hues for one quantity was the visible clash. */
.health__bar i { display: block; height: 100%; border-radius: var(--r-full); background: var(--accent); width: var(--w, 100%); }
.fx-dark .health__bar { background: rgba(255,255,255,.1); }
/* --accent is 3.96:1 on --ink-deep — over the 3:1 non-text floor, but thin for
   a 5px bar. The on-dark step is 7.97:1 and exists for exactly this. */
.fx-dark .health__bar i { background: var(--accent-on-dark); }

.feed { display: flex; flex-direction: column; gap: var(--s-3); }
.feed__item { display: grid; grid-template-columns: auto 1fr auto; gap: var(--s-3); align-items: start; font-size: var(--t-sm); }
.feed__mark { width: 26px; height: 26px; border-radius: var(--r-sm); background: var(--surface); display: grid; place-items: center; }
.feed__mark img { width: 15px; height: 15px; object-fit: contain; }
.fx-dark .feed__mark { background: rgba(255,255,255,.07); }
.feed__when { font-size: var(--t-xs); color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.feed b { color: var(--ink); font-weight: 600; }
.fx-dark .feed b { color: var(--white); }

.toggle { width: 38px; height: 22px; border-radius: var(--r-full); background: var(--ok); position: relative; flex: none; }
.toggle::after { content: ''; position: absolute; top: 3px; left: 19px; width: 16px; height: 16px; border-radius: var(--r-full); background: var(--white); }
.toggle.is-off { background: var(--line); }
.toggle.is-off::after { left: 3px; }

.dot { width: 7px; height: 7px; border-radius: var(--r-full); flex: none; }
.dot--ok { background: var(--ok); }
.dot--warn { background: var(--warn); }

.stat-big { font-size: clamp(28px, 3.4vw, 44px); font-weight: 800; letter-spacing: -.035em; color: var(--ink); line-height: 1.05; }
.fx-dark .stat-big { color: var(--white); }

.chips { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px var(--s-3); border-radius: var(--r-full);
  border: 1px solid var(--line); background: var(--white);
  font-size: var(--t-xs); font-weight: 500; color: var(--body);
}
.chip img { width: 14px; height: 14px; object-fit: contain; }
/* For chips that name something outstanding rather than something present.
   Ties them to the .pill--warn state they usually sit under. */
.chip--warn { background: var(--warn-tint); border-color: color-mix(in srgb, var(--warn) 25%, transparent); color: var(--warn); }
.fx-dark .chip { background: rgba(255,255,255,.05); border-color: var(--fx-glass-line); color: var(--on-dark); }

/* ============================================================
   Scroll-narrative stage (direction F)
   ============================================================ */

.stage { display: grid; grid-template-columns: 5fr 7fr; gap: var(--s-8); align-items: start; }
@media (max-width: 980px) { .stage { grid-template-columns: 1fr; gap: var(--s-5); } }
/* --stage-top must clear whatever sticky header the page uses, or the first
   step parks underneath it. */
.stage__rail { position: sticky; top: var(--stage-top, var(--s-6)); display: flex; flex-direction: column; gap: var(--s-4); }
@media (max-width: 980px) { .stage__rail { position: static; } }
/* A track behind the step numbers, filled to the step you have reached. It
   turns five separate items into one thing with a beginning and an end, which
   is the whole point of a timeline. 14px is half the 28px .stage__n. */
.stage__steps { display: flex; flex-direction: column; position: relative; }
.stage__steps::before,
.stage__steps::after {
  content: '';
  position: absolute; left: 14px; top: var(--s-5); bottom: var(--s-5);
  width: 2px; border-radius: var(--r-full);
}
.stage__steps::before { background: var(--line); }
.stage__steps::after {
  background: var(--accent);
  transform: scaleY(var(--p, 0)); transform-origin: 50% 0;
  transition: transform .5s var(--fx-ease);
}
.stage__step {
  display: grid; grid-template-columns: auto 1fr; gap: var(--s-4);
  padding: var(--s-4) 0; border-top: 1px solid var(--line);
  /* Colour, not opacity: opacity .42 dropped --body on white to ~2:1. */
  color: var(--muted);
  transition: color .4s var(--fx-ease);
}
.stage__step.is-on { color: var(--ink); }
.stage__step.is-on p { color: var(--body); }
.stage__step:last-child { border-bottom: 1px solid var(--line); }
.stage__n {
  position: relative; z-index: 1;
  width: 28px; height: 28px; border-radius: var(--r-full);
  border: 1px solid var(--line); display: grid; place-items: center;
  font-size: var(--t-xs); font-weight: 700; color: var(--muted);
  /* Opaque so the progress track passes behind rather than through. Pages
     whose stage sits on a tinted ground override --stage-bg. */
  background: var(--stage-bg, var(--white));
  transition: background-color .4s var(--fx-ease), color .4s var(--fx-ease), border-color .4s var(--fx-ease);
}
.stage__step.is-on .stage__n { background: var(--accent); border-color: var(--accent); color: var(--white); }
.stage__panels { display: flex; flex-direction: column; gap: var(--s-6); }

/* ============================================================
   Misc
   ============================================================ */

.fx-cta-band {
  position: relative;
  border-radius: var(--r-md);
  padding: var(--s-9) var(--s-6);
  text-align: center;
  background: var(--fx-ink-2);
  overflow: hidden;
  isolation: isolate;
}
.fx-cta-band::before {
  content: '';
  position: absolute;
  z-index: -1;
  width: 620px; height: 420px;
  left: 50%; top: -180px; translate: -50% 0;
  border-radius: var(--r-full);
  filter: blur(60px);
  opacity: .55;
  background: var(--fx-grad-brand);
}

.btn--glow { box-shadow: var(--fx-glow-accent); }
.btn--glass { background: rgba(255,255,255,.08); color: var(--white); border-color: var(--fx-glass-line-hi); backdrop-filter: blur(10px); }
.btn--glass:hover { background: rgba(255,255,255,.15); }

.kbd {
  font-family: var(--mono); font-size: 11px;
  padding: 3px 7px; border-radius: 6px;
  border: 1px solid var(--fx-glass-line); background: rgba(255,255,255,.06); color: var(--on-dark);
}

.eyebrow--grad {
  background: var(--fx-grad-brand);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
