/* RarityStack - the network views: Pulse, Flow, Halving, Cycles.
   Loaded after app.css; every rule is scoped to .nw so it can only reach these
   four pages. It uses the semantic vocabulary from tokens.css and adds no
   competing accent, duplicate status green or decorative gradient.

   The reference for this page family is mempool.space, because these four views
   show the same data it does. What is taken from it are its CONVENTIONS, not its
   colours:
     value large, unit one step smaller and dimmed right behind it;
     real column heads, right-aligned tabular figures;
     hairlines and no row stripes;
     density is welcome - a surface that shows a lot and still sits still.

   Colour discipline, unchanged from the rest of the app:
     --up / --down    value change ONLY. Never a fee, never a category.
     --accent         interactive things only.
     --you            your own holding only.
     --warn           stale, uncertain, interrupted.
   Fees and congestion get their own slate-to-amber ramp (pulseviz/util.js), for
   exactly this reason: a rising fee is not a rising price.

   Dark is not an inversion. Everything below draws its colour from tokens, which
   carry their own dark pass; the handful of literal colours here (the scene
   stage, the supply-bar categories) are stated once and are deliberately the
   same in both themes, because they are paint on a dark canvas or data
   categories that must not swap meaning when the lights change. Every literal
   with white text on it was measured; the ratios are in the comments. */

/* ═══ shared ══════════════════════════════════════════════════════════════ */

.nw { --nw-stage: #0B0E14; }   /* the live scene is a dark stage in both themes */

.nw .num, .nw .col-num { font-variant-numeric: tabular-nums; }
/* an absent value is an em space wide, so the column keeps its rhythm without
   printing a figure that was never measured */
.nw .nw-dash { display: inline-block; inline-size: 1em; color: var(--ink3); }
.nw .nw-dash::before { content: '\00b7'; }
.nw .nw-basis { margin: var(--s-3) 0 0; font-size: var(--fs-xs); color: var(--ink3); }

/* ── the live strip ────────────────────────────────────────────────────────
   The one place a reader can tell a calm chain from a dead socket. It is a data
   row, not chrome, so it sits on the page without a box while everything is
   well, and only grows a ground when something is wrong. */
.nw-live {
  display: flex; align-items: center; gap: var(--s-2) var(--s-3); flex-wrap: wrap;
  margin-block: var(--s-4) 0; padding: 7px 0;
  font-size: var(--fs-sm); color: var(--ink2);
  min-block-size: 34px;
}
.nw-live-dot {
  inline-size: 8px; block-size: 8px; border-radius: 50%; flex: none;
  background: var(--accent);
}
.nw-live-label { font-weight: 700; color: var(--ink); }
.nw-live-detail { color: var(--ink3); font-variant-numeric: tabular-nums; }
.nw-live-say { flex-basis: 100%; margin: 0; font-size: var(--fs-sm); color: var(--ink2); }
.nw-live .nw-live-retry { padding: 5px 12px; font-size: var(--fs-sm); min-block-size: 28px; }
.nw-live[data-tone='warn'], .nw-live[data-tone='bad'] {
  padding: 10px 12px; border-radius: 0 var(--r-s) var(--r-s) 0;
  background: var(--warn-soft); border-inline-start: 3px solid var(--warn);
}
[dir='rtl'] .nw-live[data-tone='warn'], [dir='rtl'] .nw-live[data-tone='bad'] {
  border-radius: var(--r-s) 0 0 var(--r-s);
}
.nw-live[data-tone='warn'] .nw-live-dot, .nw-live[data-tone='bad'] .nw-live-dot { background: var(--warn); }
/* a dead connection differs from stale data by more than a shade: the dot is
   hollow, and the words say which it is. Colour is never the whole message. */
.nw-live[data-tone='bad'] .nw-live-dot { background: none; box-shadow: inset 0 0 0 2px var(--warn); }
.nw-live-chip { margin-block: var(--s-2) 0; }

/* ═══ view: pulse ═════════════════════════════════════════════════════════ */

/* the four network figures, one row, hairlines between them */
.nw-stats {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--surface); overflow: hidden;
}
.nw-stat { display: grid; gap: 2px; padding: var(--s-3) var(--s-4); min-inline-size: 0; }
.nw-stat + .nw-stat { border-inline-start: 1px solid var(--line); }
.nw-stat-k {
  font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink3);
}
.nw-stat-v { font-size: var(--fs-lg); font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.nw-stat-n { font-size: var(--fs-xs); color: var(--ink3); }

.nw-lane { min-inline-size: 0; }
.nw-mempool-lane { margin-block-start: var(--s-6); }
.nw-lane-h {
  margin: 0 0 var(--s-3); font-size: 11px; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink3);
}
/* a block is a real object, so it keeps a box. The lane scrolls sideways like
   the chain it represents. */
.nw-blocks {
  display: flex; gap: var(--s-3); overflow-x: auto; padding-block-end: var(--s-2);
  scroll-snap-type: x proximity; overscroll-behavior-inline: contain;
}
.nw-block {
  position: relative; flex: none; inline-size: 168px;
  display: grid; gap: 2px; align-content: start;
  padding: var(--s-3) var(--s-3) var(--s-3) calc(var(--s-3) + 4px);
  border: 1px solid var(--line); border-radius: var(--r-s);
  background: var(--surface); scroll-snap-align: start;
}
.nw-block.pending { border-style: dashed; background: var(--surface2); }
.nw-block-edge {
  position: absolute; inset-block: -1px; inset-inline-start: -1px; inline-size: 4px;
  border-start-start-radius: var(--r-s); border-end-start-radius: var(--r-s);
}
.nw-block-h { display: flex; align-items: center; gap: 6px; min-block-size: 22px; }
.nw-block-no { font-weight: 700; font-size: var(--fs-sm); }
.nw-block-fee { font-size: var(--fs-lg); font-weight: 700; letter-spacing: -0.01em; }
.nw-block-range, .nw-block-tx, .nw-block-when, .nw-block-pool { font-size: var(--fs-xs); color: var(--ink3); }
.nw-block-val { font-size: var(--fs-sm); font-weight: 600; }
.nw-block-pool { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── heatmap ───────────────────────────────────────────────────────────────
   Tile colours come from heatmap.js and are value change, so they are the one
   green/red surface on the page. Small tiles are NOT buttons: a 20 x 19 pixel
   target is not an interaction. Larger tiles remain direct links. */
.nw .heat-shell {
  position: relative; border: 1px solid var(--line);
  border-radius: var(--r); overflow: hidden; background: var(--nw-stage);
}
/* the proportions are set inline by heatmap.js, which also lays out against them */
.nw .heatmap { position: relative; inline-size: 100%; aspect-ratio: 960 / 520; }
  .nw .htile {
  position: absolute; margin: 0; padding: 3px 5px;
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  gap: 1px; overflow: hidden;
  border: 0;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 18%, transparent);
    font-size: var(--fs-xs); line-height: 1.15; text-align: center;
  }
  .nw .htile:hover { filter: none; }
  .nw button.htile { cursor: pointer; }
.nw button.htile:hover {
  filter: none; box-shadow: inset 0 0 0 2px currentColor; z-index: 2;
}
.nw button.htile:focus-visible { outline: 2px solid currentColor; outline-offset: -3px; z-index: 3; }
.nw .ht-sym { font-weight: 700; text-shadow: none; }
.nw .ht-price, .nw .ht-chg {
  font-size: 11px; opacity: 1; text-shadow: none;
  font-variant-numeric: tabular-nums;
}
.nw .nw-heat-full { min-block-size: 40px; padding: 8px 13px; }
.nw .nw-heat-exit {
  position: absolute;
  inset-block-start: max(12px, env(safe-area-inset-top));
  inset-inline-end: max(12px, env(safe-area-inset-left), env(safe-area-inset-right));
  z-index: 10; min-block-size: 44px;
  background: var(--veil); border-color: var(--control-line);
  box-shadow: var(--shadow-pop);
  -webkit-backdrop-filter: blur(12px) saturate(125%);
  backdrop-filter: blur(12px) saturate(125%);
}
.nw .nw-heat-exit[hidden] { display: none; }
.nw .heat-shell:fullscreen {
  position: fixed; inset: 0;
  inline-size: 100vw; block-size: 100vh; block-size: 100dvh;
  border: 0; border-radius: 0;
}
.nw .heat-shell:fullscreen .heatmap {
  inline-size: 100%; block-size: 100%; aspect-ratio: auto !important;
}

/* ── fear and greed ───────────────────────────────────────────────────────
   The value is plain. The scale places it and the class name says it in words,
   so red and green can stay what they are everywhere else: value change. */
.nw-fng { display: grid; gap: var(--s-4); }
.nw-fng-read { display: flex; align-items: baseline; gap: var(--s-2); flex-wrap: wrap; }
.nw-fng-value {
  font-family: var(--font-display); font-size: 44px; font-weight: 700;
  line-height: 1; letter-spacing: -0.02em;
}
.nw-fng-class { font-size: var(--fs-lg); font-weight: 700; margin-inline-start: var(--s-2); }
.nw-gauge { display: grid; gap: 5px; max-inline-size: 520px; }
.nw-gauge-track {
  position: relative; block-size: 8px; border-radius: 4px;
  background: var(--bg2); box-shadow: inset 0 0 0 1px var(--line);
}
.nw-gauge-mark {
  position: absolute; inset-block: -4px; inline-size: 3px; border-radius: 2px;
  background: var(--ink); transform: translateX(-1.5px);
}
[dir='rtl'] .nw-gauge-mark { transform: translateX(1.5px); }
.nw-gauge-ends {
  display: flex; justify-content: space-between; gap: var(--s-3);
  font-size: var(--fs-xs); color: var(--ink3);
}
.nw-fng-hist { display: grid; gap: 2px; max-inline-size: 520px; }
.nw-fng-hist .hint { margin: 0; font-size: var(--fs-xs); }
/* sparkline discipline: a hairline, never a filled area */
.nw .nw-spark { display: block; inline-size: 100%; block-size: 46px; }
.nw .nw-spark-line { fill: none; stroke: var(--ink2); stroke-width: 1.25; vector-effect: non-scaling-stroke; }
/* the attribution is a link, so it gets a real target instead of a 14px line */
.nw .nw-attr {
  display: inline-flex; align-items: center; min-block-size: 24px;
  font-size: var(--fs-xs); color: var(--ink3);
}
.nw .nw-attr:hover { color: var(--accent); }

/* ═══ view: flow ══════════════════════════════════════════════════════════ */

.nw .viz-bar {
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--s-3); flex-wrap: wrap; margin-block-end: var(--s-3);
}
.nw .viz-switch { display: flex; gap: var(--s-2); align-items: center; flex-wrap: wrap; }
.nw .viz-density { inline-size: auto; min-inline-size: 108px; padding: 6px 10px; font-size: var(--fs-sm); }
.nw .viz-bar-right { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.nw .viz-findwrap { display: inline-flex; flex-direction: column; gap: 2px; }
/* the pill radius used to arrive from an app.css copy of this rule; without it
   the field falls back to the .input radius and stops matching the pill chips
   standing next to it in the same bar */
.nw .viz-find {
  inline-size: clamp(140px, 22vw, 220px); padding: 6px 11px;
  font-size: var(--fs-sm); border-radius: var(--r-pill);
}
.nw .viz-find.hit { border-color: var(--accent); }
.nw .viz-find.miss { border-color: var(--warn); }
/* the result is said in words: a border colour on its own is not a message */
.nw .nw-find-msg { font-size: var(--fs-xs); color: var(--ink3); min-block-size: 14px; }
.nw .nw-find-msg[data-tone='warn'] { color: var(--warn); }
.nw .viz-bar-right .iconbtn.on, .nw .viz-play-btn.on {
  background: var(--accent-soft); color: var(--accent);
}
.nw .viz-play-btn { padding: 6px 12px; font-size: var(--fs-sm); }
/* the auto-follow chip keeps its label on one line; the switch row wraps, the
   label inside a chip does not */
.nw .viz-auto { white-space: nowrap; }

.nw .mp-stage { display: flex; gap: var(--s-3); align-items: stretch; }
.nw .mp-canvas-wrap { position: relative; flex: 1; min-inline-size: 0; }
/* The scene is a dark stage in BOTH themes: its painted colours (deep water,
   night platform) are the picture, not the page. */
.nw .mp-canvas {
  display: block; inline-size: 100%; block-size: clamp(320px, 52vh, 620px);
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--nw-stage);
}
.nw .mp-stage:fullscreen {
  position: fixed; inset: 0; display: block;
  inline-size: 100vw; block-size: 100vh; block-size: 100dvh;
  overflow: hidden; background: var(--nw-stage); padding: 0;
}
.nw .mp-stage:fullscreen .mp-canvas-wrap { inline-size: 100%; block-size: 100%; }
.nw .mp-stage:fullscreen .mp-canvas {
  inline-size: 100%; block-size: 100%; border: 0; border-radius: 0;
}
.nw .viz-full-exit {
  position: absolute;
  inset-block-start: max(64px, calc(env(safe-area-inset-top) + 12px));
  inset-inline-end: max(12px, env(safe-area-inset-left), env(safe-area-inset-right));
  z-index: 96; min-block-size: 44px;
  background: var(--veil); border-color: var(--control-line);
  box-shadow: var(--shadow-pop);
  -webkit-backdrop-filter: blur(12px) saturate(125%);
  backdrop-filter: blur(12px) saturate(125%);
}
.nw .viz-full-exit[hidden] { display: none; }

.nw .viz-hovertip, .nw .viz-txinfo {
  position: absolute; z-index: 20; inline-size: 244px;
  padding: 10px 12px; border-radius: var(--r-s);
  background: var(--surface); border: 1px solid var(--line2);
  box-shadow: var(--shadow-pop); font-size: var(--fs-sm);
}
.nw .viz-hovertip { position: fixed; pointer-events: none; }
.nw .viz-txinfo { inset-block-end: 12px; inset-inline-start: 12px; }
.nw .txi-head { display: flex; align-items: center; gap: 8px; margin-block-end: 6px; }
.nw .txi-tag {
  padding: 1px 7px; border-radius: var(--r-pill);
  background: var(--bg2); color: var(--ink3);
  font-size: 10px; font-weight: 700; letter-spacing: 0.08em;
}
.nw .txi-species { font-weight: 600; }
.nw .txi-close {
  margin-inline-start: auto; inline-size: 26px; block-size: 26px;
  border: 0; background: none; color: var(--ink3); font-size: 18px; line-height: 1; cursor: pointer;
}
.nw .txi-close:hover { color: var(--ink); }
.nw .txi-row { display: flex; justify-content: space-between; gap: var(--s-3); padding: 2px 0; }
.nw .txi-k { color: var(--ink3); }
.nw .txi-v { font-variant-numeric: tabular-nums; }
.nw .txi-v.accent { color: var(--accent); font-weight: 600; }
.nw .txi-link { color: var(--accent); text-decoration: none; }
.nw .txi-link:hover { text-decoration: underline; }
.nw .txi-creature { border-radius: 50%; background: var(--nw-stage); }

.nw .viz-blocks, .nw .viz-feed {
  flex: none; inline-size: 176px; display: flex; flex-direction: column;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--surface); overflow: hidden;
}
.nw .vb-head, .nw .vf-head {
  padding: 8px 10px; border-block-end: 1px solid var(--line);
  font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink3);
}
.nw .vb-scroll { flex: 1; overflow-y: auto; padding: 4px 8px; }
.nw .vb-item { display: flex; gap: 8px; align-items: flex-start; padding: 5px 2px; }
.nw .vb-item.fut { opacity: 0.72; }
.nw .vb-dot { inline-size: 9px; block-size: 9px; border-radius: 50%; flex: none; margin-block-start: 4px; }
.nw .vb-body { min-inline-size: 0; flex: 1; }
.nw .vb-l1 { display: flex; align-items: baseline; gap: 6px; }
.nw .vb-h { font-weight: 700; font-size: var(--fs-xs); }
.nw .vb-tag {
  padding: 0 6px; border-radius: var(--r-pill);
  background: var(--accent-soft); color: var(--accent); font-size: 10px; font-weight: 700;
}
.nw .vb-fee { margin-inline-start: auto; font-size: 10px; color: var(--ink3); }
.nw .vb-l2 { font-size: 10px; color: var(--ink3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nw .vb-empty { padding: var(--s-3); font-size: var(--fs-xs); color: var(--ink3); }
.nw .vb-mid {
  display: flex; align-items: center; gap: 6px; padding: 5px 8px;
  border-block: 1px solid var(--line); background: var(--surface2); font-size: 10px;
}
.nw .vb-mid-arrow { color: var(--accent); }
.nw .vb-mid-main { font-weight: 700; color: var(--accent); font-variant-numeric: tabular-nums; }
.nw .vb-mid-main.over { color: var(--warn); }
.nw .vb-mid-sub { margin-inline-start: auto; color: var(--ink3); }
.nw .vf-list { flex: 1; overflow: hidden; padding: 4px 8px; }
.nw .vf-item { display: flex; align-items: center; gap: 8px; padding: 4px 2px; font-size: var(--fs-xs); }
.nw .vf-item.vf-new { animation: nw-vf-in var(--dur-2) var(--ease); }
@keyframes nw-vf-in { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: none; } }
.nw .vf-dot { inline-size: 9px; block-size: 9px; border-radius: 50%; flex: none; }
.nw .vf-fee { color: var(--ink3); font-variant-numeric: tabular-nums; }
.nw .vf-usd { margin-inline-start: auto; font-weight: 600; font-variant-numeric: tabular-nums; }

.nw .ft-panel {
  margin-block-end: var(--s-3); padding: var(--s-3) var(--s-4);
  border: 1px solid var(--line); border-radius: var(--r); background: var(--surface);
}
.nw .ft-head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--s-3); flex-wrap: wrap; margin-block-end: 6px;
}
.nw .ft-title { font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink3); }
.nw .ft-hint { display: flex; gap: var(--s-3); align-items: baseline; font-size: var(--fs-sm); }
.nw .ft-hint-label { font-weight: 600; color: var(--ink); }
.nw .ft-hint-now { color: var(--ink3); font-variant-numeric: tabular-nums; }
.nw .ft-svg { display: block; inline-size: 100%; block-size: 60px; }
.nw .ft-line { fill: none; stroke: var(--accent); stroke-width: 1.25; vector-effect: non-scaling-stroke; }

.nw .tm-strip { display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap; margin-block-start: var(--s-4); }
.nw .tm-title { font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink3); }
.nw .tm-bar {
  flex: 1; min-inline-size: 160px; display: flex; block-size: 8px;
  border-radius: 4px; overflow: hidden; background: var(--bg2);
}
.nw .tm-legend { display: flex; flex-wrap: wrap; gap: 4px var(--s-3); font-size: var(--fs-xs); color: var(--ink3); }
.nw .tm-li { display: inline-flex; align-items: center; gap: 5px; }
.nw .tm-sw { inline-size: 9px; block-size: 9px; border-radius: 3px; box-shadow: inset 0 0 0 1px rgb(128 128 128 / 35%); }
/* five percentages need the size of the sample they were counted over */
.nw .tm-basis { flex-basis: 100%; font-size: var(--fs-xs); color: var(--ink3); }

.nw .viz-legend {
  display: flex; flex-wrap: wrap; gap: 6px var(--s-4); align-items: center;
  margin-block-start: var(--s-3); font-size: var(--fs-xs); color: var(--ink3);
}
.nw .vl-title { font-weight: 600; }
.nw .vl-item { display: inline-flex; align-items: center; gap: 5px; }
.nw .vl-sw { inline-size: 10px; block-size: 10px; border-radius: 3px; box-shadow: inset 0 0 0 1px rgb(128 128 128 / 35%); }
.nw .viz-creatures { display: flex; flex-wrap: wrap; gap: 6px; margin-block-start: var(--s-3); }
.nw .vc-all, .nw .vc-chip {
  display: inline-flex; align-items: center; gap: 6px; min-block-size: 32px;
  padding: 4px 10px; border: 1px solid var(--line2); border-radius: var(--r-pill);
  background: none; color: var(--ink2); font-size: var(--fs-xs); font-weight: 600; cursor: pointer;
}
.nw .vc-all:hover, .nw .vc-chip:hover { color: var(--ink); background: var(--bg2); }
.nw .vc-chip.off { opacity: 0.4; }
.nw .vc-ico { border-radius: 50%; background: var(--nw-stage); }
.nw .viz-timing { margin-block-start: var(--s-3); font-size: var(--fs-sm); font-weight: 600; color: var(--ink2); }
.nw .mp-foot {
  display: flex; justify-content: space-between; gap: var(--s-3); flex-wrap: wrap;
  margin-block-start: var(--s-2); font-size: var(--fs-xs); color: var(--ink3);
  font-variant-numeric: tabular-nums;
}
.nw .viz-fps {
  position: fixed; inset-inline-end: 12px; inset-block-end: 10px; z-index: 96;
  font-size: var(--fs-xs); color: var(--ink3); font-variant-numeric: tabular-nums;
}

/* ═══ view: halving ═══════════════════════════════════════════════════════ */

.nw .halving-body { display: grid; gap: var(--s-2); }
.nw .halving-hero { display: grid; gap: 4px; justify-items: center; text-align: center; margin-block-start: var(--s-6); }
.nw .hv-big {
  font-family: var(--font-display); font-size: clamp(48px, 7vw, 84px); font-weight: 700;
  line-height: 1; letter-spacing: -0.03em; font-variant-numeric: tabular-nums;
}
.nw .hv-big .unit { font-size: 0.26em; font-weight: 600; }
.nw .hv-big-cap { font-size: var(--fs-sm); color: var(--ink3); }
.nw .hv-eta { font-weight: 600; color: var(--ink2); }
.nw .hv-eta-note { font-size: var(--fs-xs); color: var(--ink3); }
.nw .halving-sub {
  display: flex; align-items: center; justify-content: center; gap: var(--s-6);
  margin-block-start: var(--s-6);
}
.nw .hv-sub-cell { display: grid; justify-items: center; gap: 2px; }
.nw .hv-sub-k, .nw .hv-sub-note { font-size: var(--fs-xs); color: var(--ink3); }
.nw .hv-sub-v { font-size: var(--fs-lg); font-weight: 700; font-variant-numeric: tabular-nums; }
.nw .hv-arrow { font-size: 22px; color: var(--ink3); }
.nw .hv-prog-foot { font-size: var(--fs-xs); color: var(--ink3); font-variant-numeric: tabular-nums; }
.nw .iss-meter { block-size: 8px; border-radius: 4px; background: var(--bg2); overflow: hidden; }
.nw .iss-fill { block-size: 100%; border-radius: 4px; background: var(--accent); }

.nw .halving-supply { display: grid; gap: var(--s-4); }
.nw .hv-supply-head { display: flex; justify-content: space-between; align-items: center; gap: var(--s-3); flex-wrap: wrap; }
.nw .hv-bar { display: flex; block-size: 34px; border-radius: var(--r-s); overflow: hidden; }
.nw .hv-seg { position: relative; display: grid; place-items: center; overflow: hidden; min-inline-size: 0; }
/* The shadow also used to come from an app.css copy and is kept so this cleanup
   changes no pixel. It is the one declaration here that the measured tones
   (5.16:1 to 7.63:1 against white, listed below) arguably make unnecessary, and
   dropping it is a design call for whoever owns this view, not for a deletion
   pass. */
.nw .hv-seg-lbl {
  font-size: var(--fs-xs); font-weight: 600; color: #fff; white-space: nowrap;
  text-shadow: 0 1px 3px rgb(0 0 0 / 50%);
}
/* Supply categories: flat data tones, no gradient, identical in both themes -
   a category that changes colour with the lights is a different category. Each
   one was measured against WHITE ink, because that is what sits on it:
     lost      #5A6672  5.87:1     etf       #6D57B8  5.67:1
     circ      #2A7290  5.37:1     countries #B0487D  5.16:1
     tomine    #454B9E  7.63:1     public    #96491C  6.43:1
     wallets   #2A7290  5.37:1     private   #7E651F  5.57:1
     exchanges #1F6C61  6.22:1
   The two the old palette got wrong were private (3.83:1) and exchanges
   (4.44:1); both only show a label once their share is large enough, which is
   why nothing caught them. */
.nw .hv-seg-lost { background: #5A6672; }
.nw .hv-seg-circ, .nw .hv-seg-wallets, .nw .hv-seg-individuals { background: #2A7290; }
.nw .hv-seg-tomine { background: #454B9E; }
.nw .hv-seg-etf { background: #6D57B8; }
.nw .hv-seg-countries { background: #B0487D; }
.nw .hv-seg-public { background: #96491C; }
.nw .hv-seg-private { background: #7E651F; }
.nw .hv-seg-exchanges { background: #1F6C61; }

.nw .hv-eras { display: grid; gap: 4px; }
.nw .hv-era-bar { display: flex; block-size: 18px; border-radius: var(--r-s); overflow: hidden; background: var(--bg2); }
.nw .hv-era-seg {
  display: grid; place-items: center; min-inline-size: 0; overflow: hidden;
  font-size: 10px; font-weight: 600; color: var(--ink3); background: var(--bg2);
}
.nw .hv-era-seg.alt { background: var(--surface2); }
.nw .hv-era-seg.cur { background: var(--accent-soft); color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.nw .hv-era-note { margin: 0; font-size: var(--fs-xs); color: var(--ink3); }

/* the legend is a real two-column list: label left, figure and share right on
   tabular figures, so the column reads down instead of drifting */
/* one row per category, not a wrapping card grid: the shares only read as a
   column when they sit under each other */
.nw .hv-legend { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; }
.nw .hv-leg {
  display: grid; grid-template-columns: 11px minmax(0, 1fr) 7.5rem 4.5rem;
  align-items: center; gap: var(--s-3); padding: 7px 2px; font-size: var(--fs-sm);
}
.nw .hv-leg + .hv-leg { border-block-start: 1px solid var(--line); }
.nw .hv-leg-sw { inline-size: 11px; block-size: 11px; border-radius: 3px; box-shadow: inset 0 0 0 1px rgb(128 128 128 / 35%); }
.nw .hv-leg-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nw .hv-leg-val, .nw .hv-leg-pct { text-align: end; font-variant-numeric: tabular-nums; white-space: nowrap; }
.nw .hv-leg-pct { color: var(--ink2); }
.nw .hv-supply-last { margin: 0; font-weight: 600; }
.nw .hv-supply-note, .nw .hv-blurb { margin: 0; font-size: var(--fs-xs); color: var(--ink3); }
.nw .hv-blurb { max-inline-size: 68ch; font-size: var(--fs-sm); margin-block-start: var(--s-8); }

/* ═══ view: cycles ════════════════════════════════════════════════════════ */

.nw .cyc-map { padding: var(--s-4) 0 0; }
.nw .cyc-svg { display: block; inline-size: 100%; block-size: auto; }
.nw .cyc-since { margin: var(--s-3) 0 0; font-size: var(--fs-sm); color: var(--ink3); }
/* the top margin used to arrive from an app.css copy of this rule; it is stated
   here now, so the card grid keeps the same distance from the section head */
.nw .cyc-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: var(--gap); margin-block-start: var(--gap);
}
.nw .cyc-card { display: grid; gap: 8px; align-content: start; }
.nw .cyc-card.live { border-color: var(--accent); }
.nw .cyc-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s-2); }
.nw .cyc-no { font-family: var(--font-display); font-weight: 700; }
.nw .cyc-era { font-size: var(--fs-xs); color: var(--ink3); }
.nw .cyc-line { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.nw .cyc-dot { inline-size: 9px; block-size: 9px; border-radius: 50%; flex: none; align-self: center; }
.nw .cyc-dot.hv { background: var(--accent); }
.nw .cyc-dot.top { background: var(--up); }
.nw .cyc-dot.bot { background: var(--down); }
.nw .cyc-dot.today { background: var(--warn); }
.nw .cyc-price { font-weight: 700; font-variant-numeric: tabular-nums; }
.nw .cyc-when { font-size: var(--fs-sm); color: var(--ink3); }
.nw .cyc-sub { font-size: var(--fs-xs); color: var(--ink3); margin-inline-start: 17px; }
.nw .cyc-len { font-size: var(--fs-xs); color: var(--ink3); border-block-start: 1px solid var(--line); padding-block-start: 7px; }
.nw .cyc-stk { display: grid; gap: 0; margin-block-start: var(--gap); }
.nw .cyc-stk-row {
  display: grid; grid-template-columns: 6.5rem minmax(0, 1fr) 7rem;
  align-items: baseline; gap: var(--s-3); padding: 8px 2px;
}
.nw .cyc-stk-row + .cyc-stk-row { border-block-start: 1px solid var(--line); }
.nw .cyc-stk-when { font-size: var(--fs-sm); color: var(--ink3); }
.nw .cyc-stk-val { font-weight: 600; font-variant-numeric: tabular-nums; }
.nw .cyc-stk-mult { text-align: end; font-size: var(--fs-sm); font-weight: 600; justify-content: flex-end; }

/* the fiction block: warn is the whole colour budget, and every card says the
   word on its face rather than only in the small print above it */
.nw .cyc-fc { margin-block-start: var(--s-5); }
.nw .cyc-fc-toggle {
  display: flex; gap: 10px; align-items: flex-start;
  font-size: var(--fs-sm); color: var(--ink2); cursor: pointer;
}
.nw .cyc-fc-toggle input {
  inline-size: 24px; block-size: 24px; flex: none; margin: 0;
  accent-color: var(--warn); cursor: pointer;
}
.nw .cyc-fc-body { margin-block-start: var(--s-4); display: grid; gap: var(--s-4); }
.nw .cyc-fc-warn {
  display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px;
  background: var(--warn-soft); border-inline-start: 3px solid var(--warn);
  border-radius: 0 var(--r-s) var(--r-s) 0; font-size: var(--fs-sm); color: var(--ink2);
}
[dir='rtl'] .nw .cyc-fc-warn { border-radius: var(--r-s) 0 0 var(--r-s); }
.nw .cyc-fc-badge { background: var(--warn-soft); color: var(--warn); font-weight: 700; box-shadow: inset 0 0 0 1px var(--warn); }
.nw .cyc-fc-card { border-style: dashed; }
.nw .cyc-fc-when { font-size: var(--fs-sm); color: var(--ink3); }
.nw .cyc-fc-range { font-size: var(--fs-lg); font-weight: 700; font-variant-numeric: tabular-nums; }
.nw .cyc-fc-extra { margin: 0; font-size: var(--fs-xs); color: var(--ink3); }

/* ═══ tap targets ═════════════════════════════════════════════════════════
   A control smaller than 24 x 24 CSS pixels is not operable on a phone, so no
   control in these views is allowed to be. Switch inputs are visually hidden
   but keep the full box of their label, so the target is the whole switch. */
.nw .switch { position: relative; min-block-size: 24px; }
.nw .switch input { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; margin: 0; opacity: 0; cursor: pointer; }
.nw .iconbtn { inline-size: 38px; block-size: 38px; }
.nw .chip { min-block-size: 32px; }

/* ═══ narrow ══════════════════════════════════════════════════════════════ */

@media (max-width: 900px) {
  .nw-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .nw-stat:nth-child(3) { border-inline-start: 0; }
  .nw-stat:nth-child(n+3) { border-block-start: 1px solid var(--line); }
  /* the side panels stack under the stage instead of squeezing it */
  .nw .mp-stage { flex-wrap: wrap; }
  .nw .viz-blocks, .nw .viz-feed { inline-size: 100%; max-block-size: 220px; }
}

@media (max-width: 620px) {
  .nw .halving-sub { gap: var(--s-4); }
  .nw .hv-leg { grid-template-columns: 11px minmax(0, 1fr) 5.6rem; }
  .nw .hv-leg-pct { grid-column: 3; }
  .nw .hv-leg-val { grid-column: 2 / 4; text-align: start; font-size: var(--fs-xs); color: var(--ink3); }
  .nw .cyc-stk-row { grid-template-columns: 5.2rem minmax(0, 1fr) 5rem; }
  .nw .viz-find { inline-size: 100%; }
  .nw .viz-findwrap { flex: 1 1 100%; }
  .nw .nw-block { inline-size: 152px; }
}

/* ═══ full-window pulse heatmap ═══════════════════════════════════════════
   Native fullscreen is requested from the heat shell. This fixed layer is the
   no-gesture/API fallback and shares the same modal focus/exit behavior. */
body.rs-pulsefull { overflow: hidden; }
body.rs-pulsefull .skip-link,
body.rs-pulsefull .app-header,
body.rs-pulsefull .market-strip,
body.rs-pulsefull .scale-strip,
body.rs-pulsefull .app-subnav,
body.rs-pulsefull .app-footer,
body.rs-pulsefull .nw-pulse > :not(.nw-heat-module),
body.rs-pulsefull .nw-heat-module > :not(.heat-shell) {
  visibility: hidden;
}
body.rs-pulsefull .nw .heat-shell {
  position: fixed; inset: 0; z-index: 100;
  inline-size: 100vw; block-size: 100vh; block-size: 100dvh;
  border: 0; border-radius: 0;
}
body.rs-pulsefull .nw .heatmap {
  inline-size: 100%; block-size: 100%; aspect-ratio: auto !important;
}

/* ═══ full-window flow ════════════════════════════════════════════════════
   The shareable /full state and the real Fullscreen API are two different
   things and both are supported. This block styles the first; the second is the
   browser's own layer, which the scenes detect themselves in order to scale up.
   These are the only rules in this file that reach outside .nw, and they may:
   body.rs-flowfull is set by flow.js alone, so the state IS the scope. The
   block used to live in app.css, which is why half of the flow view was
   described in one file and half in the other. */
body.rs-flowfull .app-header, body.rs-flowfull .app-nav, body.rs-flowfull .app-footer,
body.rs-flowfull .app-subnav, body.rs-flowfull .view-title, body.rs-flowfull .view-lede { display: none; }
body.rs-flowfull { overflow: hidden; }
body.rs-flowfull .app-main { max-width: none; padding: 0; }
body.rs-flowfull .nw-flow {
  position: fixed; inset: 0; z-index: 85;
  overflow: hidden; background: var(--nw-stage);
}
body.rs-flowfull .nw-live { display: none; }
body.rs-flowfull .viz-bar {
  padding: 8px 12px; margin: 0; position: fixed; inset-block-start: 0; inset-inline: 0; z-index: 90;
  background: var(--veil); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
}
body.rs-flowfull .mp-stage { position: absolute; inset: 0; gap: 0; }
body.rs-flowfull .mp-canvas-wrap { inline-size: 100%; block-size: 100%; }
body.rs-flowfull .nw .mp-canvas {
  inline-size: 100%; block-size: 100vh; block-size: 100dvh;
  border-radius: 0; border: 0;
}
body.rs-flowfull .viz-fs-btn { display: none; }
body.rs-flowfull .viz-blocks, body.rs-flowfull .viz-feed { position: fixed; inset-inline-end: 10px; inset-block-start: 64px; inset-block-end: 10px; z-index: 89; }
body.rs-flowfull .viz-feed { inset-inline-end: 192px; }
body.rs-flowfull .viz-timing { position: fixed; inset-inline-start: 12px; inset-block-end: 10px; z-index: 90; margin: 0; }
body.rs-flowfull .viz-legend, body.rs-flowfull .tm-strip, body.rs-flowfull .mp-foot, body.rs-flowfull .ft-panel, body.rs-flowfull .viz-creatures { display: none !important; }
/* Narrow AND full-window is the one case where the side panels have nowhere to
   go: the stage is the whole viewport, so a stacked panel would be an overlay
   on top of the picture rather than a column beside it. They are hidden here
   and ONLY here. In the normal narrow layout they stack under the stage (see
   the 900px block above), which is what the design asks for and what an
   app.css rule with `display: none !important` used to prevent. */
@media (max-width: 900px) {
  body.rs-flowfull .viz-blocks, body.rs-flowfull .viz-feed { display: none; }
}

/* ═══ reduced motion ══════════════════════════════════════════════════════
   Everything that moves stops. What is left is a FULL view, not a still with
   dead controls: the scenes repaint one frame on every interaction (flow.js),
   the tx feed fills at once instead of trickling, and the play control becomes a
   real two-way switch instead of a one-way Play. */
@media (prefers-reduced-motion: reduce) {
  .nw *, .nw *::before, .nw *::after {
    animation-duration: 0.001ms !important; animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important; scroll-behavior: auto !important;
  }
  .nw .vf-item.vf-new { animation: none; }
  .nw .nw-blocks { scroll-snap-type: none; }
}
