/* =============================================================================
   GIFT CHECK — normie.css
   Route /normie. Product title on screen: "What's my gift worth?"
   The word "normie" lives in the URL and in this filename. Never on the screen.

   This is a SIBLING of the Intel Maps trader terminal, not a reskin of it.
   Same family (cobalt / champagne gold / deep navy / Inter), opposite goal:
   the terminal maximises information per pixel, this maximises CLARITY per
   pixel. One clear thing per screen, big calm type, generous air.

   SELF-CONTAINED. Loads no font, no image, no other stylesheet. If Inter is
   already warm in the browser cache from the main site it is used; if not the
   system UI face is used and nothing shifts, because the stack is metric-close
   and no layout here depends on a glyph width.

   -----------------------------------------------------------------------------
   CLASS VOCABULARY  (for whoever writes normie.html / normie.js)
   -----------------------------------------------------------------------------
   SHELL      .nm-page (on <body>)  .nm-wrap  .nm-mark  .nm-foot
   DOOR       .nm-door  .nm-door-head  .nm-q  .nm-sub  .nm-grid  .nm-tile
              .nm-tile-img  .nm-tile-name  .nm-alt  .nm-field  .nm-input
              .nm-btn  .nm-btn--ghost  .nm-chips  .nm-chip
   ANSWER     .nm-answer (the section)  .nm-card  .nm-card-h  .nm-actions
              .nm-wide (spans every desktop column)  .nm-hero (card A)
                                          (five cards, always this order)
     A        .nm-box  .nm-box-art  .nm-box-label  .nm-num  .nm-usd
              .nm-asof  .nm-guard
     B        .nm-queue  .nm-range  .nm-queue-price  .nm-queue-out
              .nm-queue-line
     C        .nm-moves  .nm-move  .nm-move-who  .nm-move-when  .nm-move-price
              .nm-moves-note
     D        .nm-who  .nm-bar  .nm-bar-fill
     E        .nm-attn  .nm-spark
   TRAIT      .nm-trait  .nm-trait-k  .nm-trait-v  .nm-trait-say
   STAT       .nm-stat  .nm-stat-k  .nm-stat-v    (a plain labelled figure. It
              exists so cards D and COMPARE stop borrowing .nm-trait, whose
              champagne gold is reserved for the trait verdict and nothing else)
   NEXT       .nm-next        (the way out of an answer: a rail of .nm-tile
                               built from collections already loaded)
   COMPARE    .nm-cmp  .nm-cmp-side  .nm-cmp-name  .nm-cmp-num  .nm-cmp-say
   RISK       .nm-risk  .nm-risk--severe  .nm-risk-h  .nm-risk-b   (ABOVE the
              number, always. Never carries the mascot — enforced in CSS.)
   STATE      .nm-slot  .nm-val  .nm-val--unknown  .nm-val--failed
              .nm-val--na  .nm-why        (envelope: known | unknown_not_measured
                                           | unknown_fetch_failed | not_applicable)
   MASCOT     .nm-say  .nm-face  .nm-say-b     (.nm-face collapses to nothing
                                                when the sprite manifest is empty)
   INTRO      .nm-intro  .nm-intro-lead  .nm-intro-pts        (§12)
   TABS       .nm-tabs  .nm-tab  .is-on                       (§12, Gifts|Coins)
   COINS      .nm-tokens  .nm-tklist  .nm-tk  .nm-tk-id  .nm-tk-img
              .nm-tk-sym  .nm-tk-nums  .nm-tk-px  .nm-tk-chg (.is-up/.is-down/
              .is-unk)  .nm-tk-risk (.is-low/.is-mid/.is-high/.is-unknown)
              .nm-tk-risk-h  .nm-tk-risk-d  .nm-tk-foot2  .nm-tk-foot
              .nm-skel--row                                    (§12)
   MISC       .nm-lock  .nm-ledger  .nm-ledger-item  .is-closed  .nm-new
              .nm-sheet  .nm-scrim  .nm-sheet-h  .nm-sheet-x  .nm-skel
              .nm-empty  .nm-more (chevron link)  .nm-hr  .nm-mono  .nm-sr
   -----------------------------------------------------------------------------
   HARD RULES THIS FILE ENFORCES STRUCTURALLY (not by asking nicely)
     1. The risk bar cannot be styled quiet. It is flat paint, square corners,
        no shadow, no glass, full-bleed, and identical in light and dark. It
        is the only element on the page made of a different material. On the
        desktop grid it is .nm-wide — it spans every column, so a wider screen
        makes the warning bigger and can never demote it into a side lane.
     2. The mascot cannot appear inside a risk bar. `display:none !important`.
     3. An unknown number occupies the SAME box at the SAME size and weight as
        a known one. There is no em-dash class and no blank class.
     4. Where :has() is supported, a non-known value with no .nm-why sentence
        beside it paints itself a loud broken outline in dev. A missing
        explanation is a visible defect, not a silent one.
   ============================================================================= */


/* =============================================================================
   1 · TOKENS
   Intel Maps rebrand palette (cobalt #2F6BFF, champagne #E9C77B, deep navy),
   re-weighted for calm: fewer steps, more contrast, bigger type, more air.
   ============================================================================= */

.nm-page {
  /* — ink & canvas — dark is the default, this is still Intel Maps — */
  --bg:        #070C17;
  --bg-2:      #0A1120;
  --card:      #101A2E;
  --card-2:    #16233C;
  --line:      rgba(255,255,255,0.09);
  --line-2:    rgba(255,255,255,0.16);
  --ink:       #FFFFFF;
  --ink-2:     #DCE4F2;
  --ink-3:     #98A3B8;   /* labels, meta. still readable at 13px */
  --ink-4:     #6E7A90;   /* placeholders only. NEVER content */

  /* — accents — */
  --blue:      #2F6BFF;
  --blue-hi:   #5B8CFF;
  --blue-soft: #8FB0FF;
  --blue-wash: rgba(47,107,255,0.12);
  --gold:      #E9C77B;   /* reserved for the trait verdict. The good news. */
  --gold-wash: rgba(233,199,123,0.11);
  --up:        #4ADE80;
  --down:      #FF6B7A;

  /* — ALARM. Theme-invariant on purpose: a warning must not get prettier
       because someone flipped themes. Dark ink on hot paint. — */
  --alarm:      #FFB454;
  --alarm-ink:  #1B1204;
  --alarm-edge: #B87816;
  --alarm-hi:      #FF5566;
  --alarm-hi-ink:  #17060A;
  --alarm-hi-edge: #A81F2E;

  /* — type — */
  --font: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
          Roboto, Helvetica, Arial, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --t-hero: clamp(46px, 15vw, 62px);   /* THE number. Only large thing. */
  --t-q:    clamp(27px, 8.2vw, 36px);  /* the question */
  --t-xl:   20px;
  --t-l:    17px;
  --t-m:    15px;
  --t-s:    13px;
  --t-xs:   12px;

  /* — geometry & motion — */
  --pad:  20px;
  --gap:  14px;
  --r:    18px;
  --r-s:  12px;
  --ease: cubic-bezier(.22,.72,.28,1);
  --dur:  260ms;

  --shadow: 0 1px 0 rgba(255,255,255,0.04) inset,
            0 10px 30px rgba(0,0,0,0.35);
}

/* LIGHT — removed 2026-08-06 (spec §7: dark and PYONYA only). Both the
   explicit :root[data-theme="light"] override and the
   @media (prefers-color-scheme: light) OS fallback lived here; the second
   deletion is the deliberate one — a light-mode OS now gets dark on
   purpose, it is not an oversight. */


/* =============================================================================
   2 · BASE
   ============================================================================= */

.nm-page * { box-sizing: border-box; }

.nm-page {
  margin: 0;
  min-height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--t-m);
  line-height: 1.5;
  letter-spacing: -0.006em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  -webkit-tap-highlight-color: transparent;
  overflow-x: hidden;
  overflow-y: auto;
  -webkit-text-size-adjust: 100%;
}

/* One very soft cobalt lift at the top so the page reads as Intel Maps
   without importing the terminal's aurora machinery. Static. Paints instantly.
   Fixed so it never re-rasterises while scrolling on a phone. */
.nm-page::before {
  content: "";
  position: fixed;
  inset: 0 0 auto 0;
  height: 340px;
  pointer-events: none;
  z-index: 0;
  background:
    radial-gradient(ellipse 90% 100% at 50% -20%,
                    var(--blue-wash), transparent 70%);
}

.nm-page > * { position: relative; z-index: 1; }

.nm-page h1, .nm-page h2, .nm-page h3, .nm-page p { margin: 0; }
.nm-page img { max-width: 100%; display: block; }
.nm-page button, .nm-page input { font: inherit; color: inherit; }
.nm-page a { color: var(--blue-hi); text-decoration: none; }
.nm-page a:hover { text-decoration: underline; }

.nm-mono { font-family: var(--mono); font-size: 0.92em; letter-spacing: 0; }

/* Numbers never jitter. Applied wherever a figure can change in place. */
.nm-num, .nm-val, .nm-queue-out, .nm-move-price, .nm-usd, .nm-tile-ask {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* Focus is a real ring, not a removal. 3:1 against every surface here. */
.nm-page :focus-visible {
  outline: 3px solid var(--blue-hi);
  outline-offset: 2px;
  border-radius: 6px;
}

.nm-sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}


/* =============================================================================
   3 · SHELL
   One column up to 900px — a phone, and the Mini App, are the base case and
   the bottom inset is respected. Above 900px §15 takes over and the page lays
   out properly instead of stretching: the measure of any single block of prose
   is still capped, it is the ARRANGEMENT that changes, never the type size or
   the order of the cards.
   ============================================================================= */

.nm-wrap {
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  padding: 18px var(--pad) calc(56px + env(safe-area-inset-bottom, 0px));
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}

.nm-mark {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 6px;
  font-size: var(--t-s);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ink-2);
}
.nm-mark small {
  font-weight: 500;
  font-size: var(--t-xs);
  color: var(--ink-3);
  letter-spacing: 0;
}
/* The trader link is a way out, not part of the wordmark: it goes to the far
   edge so it never reads as a subtitle of "What's my gift worth?". */
.nm-mark .nm-more { margin-left: auto; padding-top: 0; padding-bottom: 0; }

/* A 3px cobalt tick before the wordmark. Costs no image and no request. */
.nm-mark-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--blue);
  align-self: center;
}

.nm-foot {
  margin-top: 10px;
  font-size: var(--t-xs);
  line-height: 1.65;
  color: var(--ink-3);
}

.nm-hr { height: 1px; background: var(--line); border: 0; margin: 4px 0; }


/* =============================================================================
   4 · THE DOOR  (screen 0)
   Paints with zero network. Question, then twelve pictures. That is the page.
   ============================================================================= */

.nm-door { padding-top: 10px; }

.nm-q {
  font-size: var(--t-q);
  font-weight: 800;
  line-height: 1.12;
  letter-spacing: -0.033em;
  text-wrap: balance;
}

.nm-sub {
  margin-top: 12px;
  font-size: var(--t-m);
  line-height: 1.55;
  color: var(--ink-3);
  max-width: 34ch;
  text-wrap: pretty;
}

/* THE GRID — picture-first. This person knows their gift by its art, not by
   the string "Plush Pepe". 3-across on a phone, 4 once there is room. */
/* MARKETPLACE GRID. Two across on a phone rather than three: a bigger picture
   and a visible price beat one more tile per row, because the art is what this
   audience recognises and the price is what they came for. Widens with the
   viewport instead of stopping at four, so a desktop reads as a shop rather
   than as a phone screen stretched. */
.nm-grid {
  margin-top: 22px;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
}
@media (min-width: 430px) { .nm-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 700px) { .nm-grid { grid-template-columns: repeat(4, 1fr); gap: 14px; } }
@media (min-width: 1000px) { .nm-grid { grid-template-columns: repeat(5, 1fr); } }
@media (min-width: 1300px) { .nm-grid { grid-template-columns: repeat(6, 1fr); } }

/* A CARD, not a bare button. The surface, border and lift are what make it
   read as a thing you can buy rather than an icon you can tap. */
.nm-tile {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  padding: 8px 8px 10px;
  background: var(--card);
  text-align: center;
  cursor: pointer;
  border-radius: var(--r);
  transition: transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
.nm-tile:active { transform: scale(0.97); }
@media (hover: hover) {
  .nm-tile:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow);
    border-color: var(--blue);
  }
  .nm-tile:hover .nm-tile-name { color: var(--ink); }
}
/* Lift is decoration; it is the first thing to go when the reader has asked
   for less movement. */
@media (prefers-reduced-motion: reduce) {
  .nm-tile { transition: none; }
  .nm-tile:hover { transform: none; }
}

/* THE PRICE. Second-largest thing on the card after the art, because it is the
   second question — right after "which one is mine". */
.nm-tile-price {
  margin-top: 3px;
  font: 700 var(--t-m)/1.3 var(--mono);
  color: var(--ink);
  letter-spacing: -.01em;
}
/* NOT A PRICE, and it must not look like one: no monospace, no weight, no
   emphasis. "no price yet" is a fact about our index, not an offer of zero. */
.nm-tile-price--none {
  font: italic 400 var(--t-xs)/1.5 var(--font);
  color: var(--ink-3);
  letter-spacing: 0;
}

/* The grey block IS the loading state. No spinner, no shimmer on 12 tiles. */
.nm-tile-img {
  width: 100%;
  aspect-ratio: 1;
  border-radius: var(--r-s);
  background: var(--card-2);
  border: 1px solid var(--line);
  object-fit: cover;
  transition: border-color 160ms var(--ease);
}

.nm-tile-name {
  margin-top: 7px;
  font-size: var(--t-xs);
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.nm-tile-ask { display: block; margin-top: 2px; color: var(--ink-3); font-weight: 500; }

/* ---------------------------------------------------------------------------
   EVERY TILE THE SAME SIZE.

   The art was already square, but the text under it was not, and three
   independent things varied at once:
     1. the name clamps at two lines but a short name only used one;
     2. "no price yet" renders in a smaller italic face than a real price;
     3. .nm-tile-ask exists on some tiles and not others.
   Any one of those makes neighbouring cards disagree about their height. All
   three together is what reads as "the boxes are different sizes".

   The fix is to RESERVE the space rather than react to the content, so a tile
   with a one-word name and no price occupies exactly the same box as a tile
   with a long name, a price and an ask. Costs nothing when content is short.
   ------------------------------------------------------------------------ */
.nm-tile { height: 100%; }

/* Two lines' worth, always. 1.3 is the line-height declared above; em resolves
   against this element's own font-size, so the reservation tracks --t-xs
   automatically instead of hard-coding a pixel height that breaks on rescale. */
.nm-tile-name { min-height: 2.6em; }

/* Pinned to the bottom of the flex column so the price line sits on a shared
   baseline across the row no matter how tall the name above it ended up. The
   min-height is measured on the TALLER of the two price faces, so swapping a
   real price for "no price yet" does not resize the card. */
.nm-tile-price {
  margin-top: auto;
  padding-top: 3px;
  /* NOT `em`. The two price faces have different font-sizes, so an em-based
     reservation would resolve SMALLER for the italic "no price yet" variant and
     reintroduce exactly the mismatch this is here to remove. calc() against
     --t-m is absolute: both variants reserve the taller face's line box. */
  min-height: calc(1.3 * var(--t-m));
}

/* Demoted secondary paths: type a name, paste a wallet. */
.nm-alt {
  margin-top: 24px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.nm-alt-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--t-s);
  color: var(--ink-3);
}
.nm-alt-row > span { flex: 0 0 auto; }

.nm-field { display: flex; gap: 8px; width: 100%; }

.nm-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 48px;
  padding: 0 14px;
  border-radius: var(--r-s);
  border: 1px solid var(--line-2);
  background: var(--card);
  color: var(--ink);
  font-size: var(--t-m);
}
.nm-input::placeholder { color: var(--ink-4); }
.nm-input:focus { border-color: var(--blue); outline-offset: 1px; }

.nm-btn {
  flex: 0 0 auto;
  min-height: 48px;
  padding: 0 20px;
  border: 0;
  border-radius: var(--r-s);
  background: var(--blue);
  color: #fff;
  font-size: var(--t-m);
  font-weight: 700;
  letter-spacing: -0.01em;
  cursor: pointer;
  transition: transform 120ms var(--ease), background 160ms var(--ease);
}
.nm-btn:hover { background: var(--blue-hi); }
.nm-btn:active { transform: scale(0.97); }

.nm-btn--ghost {
  background: transparent;
  color: var(--ink-2);
  border: 1px solid var(--line-2);
}
.nm-btn--ghost:hover { background: var(--card-2); }

/* Trust chips. This audience has been trained to expect a scam; say the
   reassuring things out loud, small, at the bottom, and only true ones. */
.nm-chips {
  margin-top: 20px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.nm-chip {
  padding: 5px 11px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--card);
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--ink-3);
}


/* =============================================================================
   5 · CARDS  (screen 1 — five of them, always the same order)
   ============================================================================= */

.nm-card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--pad);
  box-shadow: var(--shadow);
}

.nm-card-h {
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 12px;
}

/* Chevron affordance. Used on every "there is more behind this" line — and
   the guard line is deliberately the most clickable thing on the page. */
.nm-more {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border: 0;
  padding: 8px 0;
  background: none;
  color: var(--blue-hi);
  font-size: var(--t-s);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.nm-more::after {
  content: "›";
  font-size: 1.25em;
  line-height: 1;
  transition: transform 160ms var(--ease);
}
.nm-more:hover::after { transform: translateX(3px); }


/* ── CARD A · THE BOX — the founder's "floor in a box" ────────────────────── */

.nm-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 26px var(--pad) 20px;
}

.nm-box-art {
  width: 84px; height: 84px;
  border-radius: 20px;
  object-fit: cover;
  background: var(--card-2);
  border: 1px solid var(--line);
  margin-bottom: 14px;
}

.nm-box-name {
  font-size: var(--t-xl);
  font-weight: 700;
  letter-spacing: -0.02em;
}

/* The label is not decoration. It is the honesty mechanism: the reader is
   told what kind of number this is BEFORE they read the number. */
.nm-box-label {
  margin-top: 14px;
  font-size: var(--t-s);
  font-weight: 600;
  color: var(--ink-3);
  letter-spacing: -0.005em;
}

.nm-num {
  margin-top: 4px;
  font-size: var(--t-hero);
  font-weight: 800;
  line-height: 1.02;
  letter-spacing: -0.04em;
  color: var(--ink);
}
.nm-num small {
  font-size: 0.38em;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin-left: 0.16em;
  color: var(--ink-2);
}

/* USD line. Rendered only when a rate genuinely exists. There is no styling
   here for "$0" or "$—" because neither is ever allowed to be emitted. */
.nm-usd { margin-top: 2px; font-size: 14px; color: var(--ink-3); }

/* The mandatory sentence beside an unknown floor. The box is centred; a
   sentence is not — it stays left-ragged inside a centred block so it reads
   like prose rather than like a poster. */
.nm-box .nm-why { text-align: left; margin-left: auto; margin-right: auto; }

.nm-asof { margin-top: 10px; font-size: var(--t-xs); color: var(--ink-3); }

.nm-delta { margin-top: 8px; font-size: var(--t-m); font-weight: 700; }
.nm-delta.is-up   { color: var(--up); }
.nm-delta.is-down { color: var(--down); }
.nm-delta.is-flat { color: var(--ink-3); font-weight: 600; }

/* THE GUARD LINE — the second of the two floors. It is never omitted and it
   is never the headline. Tinted panel so it reads as part of the answer, not
   as a disclaimer someone bolted on. */
.nm-guard {
  margin-top: 18px;
  width: 100%;
  text-align: left;
  padding: 14px 15px;
  border-radius: var(--r-s);
  background: var(--bg-2);
  border: 1px solid var(--line);
  font-size: var(--t-s);
  line-height: 1.55;
  color: var(--ink-2);
}
.nm-guard b { color: var(--ink); font-weight: 700; }
.nm-guard .nm-more { padding-bottom: 0; }

/* Thin-book variant: the guard could not run at all. Different edge so it is
   not mistaken for protection that was applied. */
.nm-guard.is-thin {
  border-color: var(--line-2);
  border-left: 3px solid var(--ink-3);
}


/* ── CARD B · THE QUEUE — "how many people are ahead of you" ──────────────── */

.nm-queue-out {
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.15;
}
.nm-queue-line { font-size: var(--t-m); color: var(--ink-2); margin-top: 2px; }

/* The price the slider is currently proposing. Sits between the control and
   the outcome so the reader sees WHAT they asked for before HOW it went. */
.nm-queue-price {
  margin-top: 4px;
  font-size: var(--t-s);
  font-weight: 600;
  color: var(--ink-3);
}
.nm-queue .nm-sub { margin-top: 10px; max-width: none; }

.nm-range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 44px;              /* touch target, not the visual height */
  margin: 12px 0 0;
  background: transparent;
  cursor: pointer;
}
.nm-range::-webkit-slider-runnable-track {
  height: 6px; border-radius: 999px; background: var(--card-2);
  border: 1px solid var(--line);
}
.nm-range::-moz-range-track {
  height: 6px; border-radius: 999px; background: var(--card-2);
  border: 1px solid var(--line);
}
.nm-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 28px; height: 28px; margin-top: -12px;
  border-radius: 50%;
  background: var(--blue);
  border: 3px solid var(--card);
}
.nm-range::-moz-range-thumb {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--blue);
  border: 3px solid var(--card);
}


/* ── CARD C · MOVES — transfers. NOT sales. The server says so itself. ────── */

.nm-moves { display: flex; flex-direction: column; }

.nm-move {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px 12px;
  align-items: baseline;
  padding: 12px 0;
  border-top: 1px solid var(--line);
}
.nm-move:first-child { border-top: 0; padding-top: 0; }

.nm-move-who  { font-size: var(--t-m); font-weight: 600; color: var(--ink-2); }
.nm-move-when { grid-column: 1; font-size: var(--t-xs); color: var(--ink-3); }

/* The price cell. A move we cannot price prints "changed hands" HERE, at this
   size, in this weight. Absence occupies the space a number would have had.
   There is deliberately no rule that shrinks, greys out or hides this cell. */
.nm-move-price {
  grid-row: span 2;
  align-self: center;
  font-size: var(--t-m);
  font-weight: 700;
  text-align: right;
  white-space: nowrap;
  color: var(--ink);
}
.nm-move-price.is-unknown { color: var(--ink-3); font-weight: 600; }

.nm-moves-note {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  font-size: var(--t-s);
  line-height: 1.6;
  color: var(--ink-3);
}


/* ── CARD D · WHO OWNS IT — concentration bar ─────────────────────────────── */

.nm-who { display: flex; flex-direction: column; gap: 12px; }

.nm-bar {
  height: 12px;
  border-radius: 999px;
  background: var(--card-2);
  border: 1px solid var(--line);
  overflow: hidden;
  display: flex;
}
.nm-bar-fill {
  height: 100%;
  background: var(--blue);
  transition: width 420ms var(--ease);
}
/* When the flag is up the bar is repainted in alarm paint. The number and the
   bar cannot disagree. */
.nm-bar-fill.is-flagged { background: var(--alarm); }
.nm-bar-fill.is-severe  { background: var(--alarm-hi); }


/* ── CARD E · ATTENTION ───────────────────────────────────────────────────── */

.nm-attn { font-size: var(--t-m); line-height: 1.6; color: var(--ink-2);
           text-wrap: pretty; }

.nm-spark { display: block; width: 100%; height: 44px; overflow: visible; }
.nm-spark path { fill: none; stroke: var(--blue-hi); stroke-width: 2; }


/* ── A LABELLED FIGURE — cards D and COMPARE ──────────────────────────────── */
/* Deliberately plain. The champagne gold of .nm-trait is the page's only
   "good news" colour and it is reserved for the trait verdict; a holder count
   is not good news or bad news, it is a fact, and it gets fact styling. */

.nm-stat {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 0;
  border-top: 1px solid var(--line);
}
.nm-stat:first-child { border-top: 0; padding-top: 0; }
.nm-stat-k { font-size: var(--t-s); color: var(--ink-3); }
.nm-stat-v { font-size: var(--t-l); font-weight: 700; letter-spacing: -0.02em;
             color: var(--ink); font-variant-numeric: tabular-nums; }


/* =============================================================================
   6 · THE TRAIT VERDICT
   The delight, and the whole reason to come back. Compares an ask to an ask,
   so it needs zero trade prices to be true. Champagne gold, used nowhere else.
   ============================================================================= */

.nm-trait {
  border: 1px solid var(--gold);
  background: var(--gold-wash);
  border-radius: var(--r);
  padding: var(--pad);
}
.nm-trait .nm-card-h { color: var(--gold); }
.nm-trait-k {
  font-size: var(--t-s);
  font-weight: 600;
  color: var(--ink-3);
}
.nm-trait-v {
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.2;
  margin-top: 2px;
}
.nm-trait-say {
  margin-top: 10px;
  font-size: var(--t-m);
  line-height: 1.6;
  color: var(--ink-2);
  text-wrap: pretty;
}
.nm-trait-say b { color: var(--ink); }


/* =============================================================================
   6b · WHAT HAPPENS NEXT
   An answer used to be a dead end: grid → tap → one long card → back. These
   are the two doors out, and neither invents data to exist. COMPARE spends the
   page's only optional request, and only when it is asked for. NEXT spends
   nothing at all — it is the collection list the door already loaded.
   ============================================================================= */

/* The row above the risk bar: back on the left, the way forward on the right.
   It stays a row on a phone; the labels are short enough to survive 320px. */
.nm-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.nm-actions .nm-btn { min-height: 42px; padding: 0 15px; }

/* COMPARE — two sides, and they are IDENTICAL. Neither is styled as the
   answer and neither as the challenger: an asymmetric layout here would put a
   thumb on the scale of a comparison the reader is making, not us. */
.nm-cmp {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.nm-cmp-side {
  min-width: 0;
  padding: 15px;
  border-radius: var(--r-s);
  background: var(--bg-2);
  border: 1px solid var(--line);
}
.nm-cmp-name {
  font-size: var(--t-s);
  font-weight: 700;
  color: var(--ink-3);
  line-height: 1.35;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  min-height: 2.7em;                 /* two names of different lengths still line up */
}
/* Same rule as the hero number, one step down: an unknown price here occupies
   the same box at the same weight as a known one (§8). It is smaller than
   .nm-num because there are two of them, not because it matters less. */
.nm-cmp-num {
  margin-top: 8px;
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.15;
  text-wrap: balance;
}
.nm-cmp-num .nm-usd { display: block; font-size: 12px; margin-top: 2px; }
.nm-cmp-side .nm-stat { margin-top: 12px; }
.nm-cmp-say {
  grid-column: 1 / -1;
  margin-top: 4px;
  font-size: var(--t-s);
  line-height: 1.6;
  color: var(--ink-2);
  text-wrap: pretty;
}

/* NEXT — a rail of the same tiles the door uses, so the gesture that got them
   here is the gesture that takes them onward. */
.nm-next {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}
@media (min-width: 430px) { .nm-next { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.nm-sheet-x { width: 100%; margin-top: 18px; }


/* =============================================================================
   7 · THE RISK BAR
   Sits ABOVE the number. A newcomer reads down to the big figure and stops;
   anything under it is not read.

   Deliberately built from a different material than the rest of the page:
   flat paint, square corners, NO radius, NO shadow, NO glass, NO gradient,
   full-bleed to the viewport edge, and IDENTICAL in light and dark. A warning
   here must survive the page being pretty. It cannot be toned down by a theme.
   ============================================================================= */

.nm-risk {
  margin: 2px calc(var(--pad) * -1) 6px;
  padding: 15px var(--pad) 16px;
  border: 0;
  border-radius: 0;
  border-top: 4px solid var(--alarm-edge);
  background: var(--alarm);
  color: var(--alarm-ink);
  box-shadow: none;
  /* 45° hazard hatch bleeding in from the left. Texture, not hue. */
  background-image:
    repeating-linear-gradient(45deg,
      rgba(0,0,0,0.14) 0 2px, rgba(0,0,0,0) 2px 8px);
  background-size: 44px 100%;
  background-repeat: no-repeat;
  background-position: left top;
}

.nm-risk-h {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--t-m);
  font-weight: 800;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}
.nm-risk-h::before {
  content: "!";
  flex: 0 0 auto;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: var(--alarm-ink);
  color: var(--alarm);
  font-size: 14px;
  font-weight: 900;
  line-height: 20px;
  text-align: center;
}

.nm-risk-b {
  margin-top: 7px;
  font-size: var(--t-m);
  line-height: 1.52;
  font-weight: 500;
  color: var(--alarm-ink);
  text-wrap: pretty;
}
.nm-risk-b b { font-weight: 800; }

/* Any link inside a warning stays inside the warning's palette. */
.nm-risk a, .nm-risk .nm-more {
  color: var(--alarm-ink);
  font-weight: 800;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.nm-risk--severe {
  background-color: var(--alarm-hi);
  border-top-color: var(--alarm-hi-edge);
  color: var(--alarm-hi-ink);
}
.nm-risk--severe .nm-risk-b { color: var(--alarm-hi-ink); }
.nm-risk--severe .nm-risk-h::before {
  background: var(--alarm-hi-ink);
  color: var(--alarm-hi);
}
.nm-risk--severe a, .nm-risk--severe .nm-more { color: var(--alarm-hi-ink); }

/* When the bar is the first thing in a card, square off the card's top so the
   band truly runs edge to edge and reads as a different surface. */
.nm-card > .nm-risk:first-child {
  margin-top: calc(var(--pad) * -1);
  border-top-left-radius: calc(var(--r) - 1px);
  border-top-right-radius: calc(var(--r) - 1px);
}

/* RULE 2, enforced rather than requested: the mascot may never stand next to
   a risk signal. Welcoming is not the same as reassuring. */
.nm-risk .nm-say,
.nm-risk .nm-face,
.nm-risk-b .nm-face { display: none !important; }

/* Forced-colours / high-contrast: keep the band a band. */
@media (forced-colors: active) {
  .nm-risk { border: 3px solid; forced-color-adjust: none;
             background: Highlight; color: HighlightText; }
}


/* =============================================================================
   8 · THE STATE ENVELOPE
   Every number crosses the wire as {value, state, as_of}.
   state ∈ known | unknown_not_measured | unknown_fetch_failed | not_applicable

   A number we do not have occupies the SAME BOX at the SAME SIZE AND WEIGHT as
   one we do. It is quieter in colour and carries a dotted rule so it reads as
   "missing", not as "tidy". There is no em-dash style and no blank style in
   this file, because a failed fetch must never look like an empty market.
   ============================================================================= */

.nm-slot { display: block; }

.nm-val { color: var(--ink); font-weight: inherit; }

.nm-val--unknown,
.nm-val--failed,
.nm-val--na {
  color: var(--ink-3);
  font-weight: inherit;             /* same weight. no shrinking away. */
  border-bottom: 2px dotted currentColor;
  padding-bottom: 1px;
}
/* A fetch that failed is not the same as a thing we never measured, and the
   reader is entitled to know which. Failed leans on the alarm hue's ink. */
.nm-val--failed { color: var(--down); border-bottom-style: dashed; }

/* THE MANDATORY SENTENCE. Every non-known state has one. */
.nm-why {
  display: block;
  margin-top: 7px;
  font-size: var(--t-s);
  line-height: 1.55;
  color: var(--ink-3);
  text-wrap: pretty;
  max-width: 46ch;
}
/* `display:block` above is an author rule and would otherwise beat the UA's
   [hidden]{display:none}, leaving an empty explanation reserving space beside
   a value that has nothing to explain. */
.nm-why[hidden] { display: none; }

/* A hero-sized absence is a phrase, not a paragraph — the sentence lives in
   .nm-why next to it. Balanced so it breaks evenly instead of orphaning a
   word. Rule 3 stands: no font-size change, no weight change, same box. */
.nm-num.nm-val--unknown,
.nm-num.nm-val--failed,
.nm-num.nm-val--na { text-wrap: balance; line-height: 1.06; }

/* RULE 4 — structural enforcement. If a slot holds a non-known value and no
   .nm-why sentence beside it, the slot paints itself broken. This is cheaper
   than remembering, and copy discipline has already failed at this repeatedly. */
@supports selector(:has(*)) {
  .nm-slot:has(.nm-val--unknown, .nm-val--failed, .nm-val--na):not(:has(.nm-why)) {
    outline: 2px dashed var(--down);
    outline-offset: 3px;
  }
  .nm-slot:has(.nm-val--unknown, .nm-val--failed, .nm-val--na):not(:has(.nm-why))::after {
    content: "missing required explanation";
    display: block;
    margin-top: 6px;
    font-size: var(--t-xs);
    font-weight: 700;
    color: var(--down);
  }
}


/* =============================================================================
   9 · PYONYA
   Explanations and good news only — never beside a risk signal (§7).
   The sprite manifest is currently empty. That is the DESIGNED default here,
   not a broken state: .nm-face collapses when it holds no image, the flex gap
   collapses with it, and the tip renders as a clean accented note. When art
   lands, the avatar appears and nothing else moves.
   ============================================================================= */

.nm-say {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 15px;
  border-radius: var(--r-s);
  background: var(--blue-wash);
  border-left: 3px solid var(--blue);
  font-size: var(--t-s);
  line-height: 1.6;
  color: var(--ink-2);
}
.nm-say b { color: var(--ink); }

.nm-face {
  flex: 0 0 auto;
  width: 38px; height: 38px;
  border-radius: 50%;
  background: var(--card-2);
  overflow: hidden;
}
.nm-face img { width: 100%; height: 100%; object-fit: contain; }
/* Empty manifest → no avatar, no gap, no hole. */
.nm-face:empty { display: none; }

.nm-say-b { flex: 1 1 auto; min-width: 0; }


/* =============================================================================
   10 · LOCKED, EMPTY, SKELETON
   "We could not fetch this" and "there is nothing here" are different
   sentences and they get different styling. Neither is ever a zero.
   ============================================================================= */

.nm-lock {
  padding: 16px;
  border-radius: var(--r-s);
  border: 1px dashed var(--line-2);
  background: var(--card-2);
  font-size: var(--t-m);
  line-height: 1.6;
  color: var(--ink-2);
}
.nm-lock b { color: var(--ink); }

.nm-empty {
  padding: 26px 16px;
  text-align: center;
  font-size: var(--t-m);
  line-height: 1.6;
  color: var(--ink-3);
}
.nm-empty strong { display: block; color: var(--ink-2); font-size: var(--t-l);
                   font-weight: 700; margin-bottom: 6px; }

/* Skeletons are shapes, never numerals. A skeleton must not be mistakable for
   a value, so it is never given a digit-like width or a colour with meaning. */
.nm-skel {
  border-radius: 8px;
  background: var(--card-2);
  min-height: 14px;
  animation: nm-pulse 1500ms var(--ease) infinite;
}
.nm-skel--num { height: 46px; width: 62%; margin: 6px auto; border-radius: 12px; }
.nm-skel--line { height: 13px; width: 100%; margin: 8px 0; }
@keyframes nm-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.45; } }


/* =============================================================================
   11 · THE SHRINKING LEDGER — we publish our own blind spots
   ============================================================================= */

.nm-ledger { list-style: none; margin: 0; padding: 0; }
.nm-ledger-item {
  position: relative;
  padding: 9px 0 9px 24px;
  font-size: var(--t-s);
  line-height: 1.55;
  color: var(--ink-3);
  border-top: 1px solid var(--line);
}
.nm-ledger-item:first-child { border-top: 0; }
.nm-ledger-item::before {
  content: "×";
  position: absolute; left: 4px; top: 8px;
  font-weight: 800;
  color: var(--ink-4);
}
.nm-ledger-item.is-closed { color: var(--ink-2); text-decoration: line-through;
                            text-decoration-color: var(--ink-4); }
.nm-ledger-item.is-closed::before { content: "✓"; color: var(--up); }

.nm-new {
  padding: 12px 15px;
  border-radius: var(--r-s);
  background: rgba(74,222,128,0.12);
  border-left: 3px solid var(--up);
  font-size: var(--t-s);
  line-height: 1.55;
  color: var(--ink-2);
}


/* =============================================================================
   12 · SHEET — every "why?" opens here. Nothing on this page is a dead end.
   ============================================================================= */

.nm-scrim {
  position: fixed; inset: 0; z-index: 40;
  background: rgba(4,7,14,0.66);
  animation: nm-fade var(--dur) var(--ease);
}
.nm-sheet {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 41;
  max-height: 86vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--card);
  border-top: 1px solid var(--line-2);
  border-radius: var(--r) var(--r) 0 0;
  padding: 22px var(--pad) calc(30px + env(safe-area-inset-bottom, 0px));
  animation: nm-up 300ms var(--ease);
}
@media (min-width: 520px) {
  .nm-sheet {
    left: 50%; right: auto; bottom: 5vh;
    width: 480px;
    transform: translateX(-50%);
    border-radius: var(--r);
    border: 1px solid var(--line-2);
  }
}
.nm-sheet-h {
  font-size: var(--t-l);
  font-weight: 700;
  letter-spacing: -0.02em;
  margin-bottom: 10px;
}
.nm-sheet p { font-size: var(--t-m); line-height: 1.62; color: var(--ink-2);
              margin-bottom: 12px; text-wrap: pretty; }
.nm-sheet[hidden], .nm-scrim[hidden] { display: none; }


/* =============================================================================
   13 · MOTION
   Delight that costs nothing: one reveal, one stagger, one slide. Nothing
   loops, nothing runs on scroll, nothing paints a second frame on a phone
   after the page has settled.
   ============================================================================= */

@keyframes nm-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes nm-up   { from { transform: translateY(14px); opacity: 0; }
                     to   { transform: translateY(0);    opacity: 1; } }
@keyframes nm-rise { from { opacity: 0; transform: translateY(9px) scale(.985); }
                     to   { opacity: 1; transform: none; } }

/* THE NUMBER REVEAL. It rises once, on arrival. Never on re-render, or the
   page will feel like it is arguing with itself. */
.nm-num[data-reveal] { animation: nm-rise 420ms var(--ease) both; }
.nm-box-label[data-reveal] { animation: nm-rise 380ms var(--ease) both; }

/* Card stagger. Four steps, then it stops — a long list must not cascade. */
.nm-card[data-in]     { animation: nm-rise 340ms var(--ease) both; }
.nm-card[data-in="1"] { animation-delay: 40ms; }
.nm-card[data-in="2"] { animation-delay: 80ms; }
.nm-card[data-in="3"] { animation-delay: 120ms; }
.nm-card[data-in="4"] { animation-delay: 160ms; }

/* The risk bar NEVER animates in. It is present from the first paint, at full
   contrast, or it is not present. A warning does not fade up. */
.nm-risk, .nm-risk[data-in] { animation: none !important; }

@media (prefers-reduced-motion: reduce) {
  .nm-page *,
  .nm-page *::before,
  .nm-page *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}


/* =============================================================================
   14 · VIEWPORTS — THE PHONE
   390×844 iPhone · ~380px Mini App with a bottom safe area. This is the base
   case and it is the one that must never regress: this audience is largely
   inside a Telegram Mini App. Everything below 900px is untouched by §15.
   ============================================================================= */

/* Mini App / small phones: tighten the gutter, keep the number big. */
@media (max-width: 392px) {
  .nm-page { --pad: 16px; --gap: 12px; }
  .nm-box { padding-left: 14px; padding-right: 14px; }
  .nm-move-price { font-size: var(--t-s); }
}

/* Short landscape (a Mini App in a rotated phone): drop the hero so the
   number and the guard line can be on screen together. */
@media (max-height: 560px) and (orientation: landscape) {
  .nm-page { --t-hero: 40px; }
  .nm-box { padding-top: 16px; }
}

@media (min-width: 720px) {
  .nm-wrap { padding-top: 44px; }
  .nm-page { --pad: 24px; }
}

/* Telegram Mini App: honour the host's own background when it supplies one,
   so the sheet does not sit on a hard seam against the client chrome. */
.nm-page.is-tma { background: var(--tg-theme-bg-color, var(--bg)); }


/* =============================================================================
   15 · THE DESKTOP LAYOUT  (≥900px)
   A stretched phone is not a layout. Above 900px the same markup, in the same
   order, is ARRANGED: the door becomes a question rail beside a wall of gifts,
   and the answer becomes a grid where the art and the number finally share a
   row instead of queueing up.

   FOUR THINGS THIS SECTION IS NOT ALLOWED TO DO, and does not:
     · It does not reorder the cards. A–E is the contract; a grid places them
       in DOM order and only decides how many sit side by side.
     · It does not touch anything below 900px. Every rule in this section sits
       inside a min-width query, so the phone and the Mini App get exactly the
       layout §14 leaves them — the desktop cannot regress the phone.
     · It does not shrink the warning. .nm-risk is .nm-wide — it spans every
       column at every width, so more screen means a bigger band, never a
       narrower one, and it stays above the number in every arrangement.
     · It does not widen the measure of running prose. .nm-why, .nm-sub and
       .nm-guard keep a ch-based cap; the columns get wider, the sentences do
       not get harder to read.

   `:not([hidden])` on the two switched sections is load-bearing: a bare
   `display:grid` is an author rule and would beat the UA's [hidden] rule,
   which would paint the answer screen and the door on top of each other.
   ============================================================================= */

@media (min-width: 900px) {
  .nm-page { --pad: 26px; --gap: 18px; --t-hero: clamp(54px, 4.6vw, 70px); }
  .nm-wrap { max-width: 1060px; padding-top: 40px; }

  /* ── THE DOOR ──────────────────────────────────────────────────────────
     The question stops being a banner and becomes a rail that stays with you
     while you scan the art — which is the actual task on this screen. */
  .nm-door:not([hidden]) {
    display: grid;
    grid-template-columns: minmax(210px, 290px) minmax(0, 1fr);
    column-gap: 44px;
    align-items: start;
    padding-top: 22px;
  }
  .nm-door-head { position: sticky; top: 34px; }
  .nm-q { font-size: 42px; }
  .nm-door .nm-grid { margin-top: 0; grid-template-columns: repeat(4, 1fr); gap: 16px; }
  .nm-door .nm-empty { grid-column: 2; text-align: left; }
  .nm-door .nm-skel { aspect-ratio: 1; }

  /* ── THE ANSWER ────────────────────────────────────────────────────────
     Auto-flow, not named areas: cards B, D, E and the trait verdict are all
     conditionally hidden, and a named-area layout would leave a hole in the
     grid every time one of them has nothing honest to say. */
  .nm-answer:not([hidden]) {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--gap);
    align-items: start;
  }
  .nm-answer > .nm-wide { grid-column: 1 / -1; }

  /* CARD A — the art and the big number share a row, and the guard line runs
     full width beneath both, where a second floor belongs. */
  .nm-hero .nm-box {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      "art label"
      "art num"
      "art asof"
      "guard guard";
    column-gap: 32px;
    align-content: center;
    text-align: left;
    padding: 32px var(--pad) 28px;
  }
  .nm-hero .nm-box-art {
    grid-area: art;
    align-self: center;
    width: 144px; height: 144px;
    margin: 0;
    border-radius: 30px;
  }
  .nm-hero .nm-box-label { grid-area: label; margin-top: 0; font-size: var(--t-m); }
  .nm-hero > .nm-box > .nm-slot { grid-area: num; }
  .nm-hero .nm-asof { grid-area: asof; }
  .nm-hero .nm-guard { grid-area: guard; max-width: 76ch; }

  .nm-next { grid-template-columns: repeat(8, minmax(0, 1fr)); }
  .nm-cmp { gap: 18px; }
  .nm-actions { margin-bottom: 2px; }

  /* Hover is real on a desktop, so tiles get a lift. `:not(:active)` keeps the
     existing press feedback — this rule is later in the cascade and would
     otherwise win during the click and swallow the tap response. */
  @media (hover: hover) {
    .nm-tile:hover:not(:active) { transform: translateY(-3px); }
  }
}

/* Three columns once there is honestly room for three. Card A keeps two of
   them — a 400px-wide hero number is a poster, not a layout — so the queue
   card lands beside it and the answer reads left to right, top to bottom. */
@media (min-width: 1200px) {
  .nm-wrap { max-width: 1240px; }
  .nm-answer:not([hidden]) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .nm-answer > .nm-hero.nm-wide { grid-column: span 2; }
  .nm-door .nm-grid { grid-template-columns: repeat(5, 1fr); }
}

/* ===========================================================================
   §12  INTRO, TABS AND THE COINS LIST
   ---------------------------------------------------------------------------
   Added 2026-08-06. Three things arrived together: the landing had no
   explanation of what the page is, gifts and coins needed one switch rather
   than two URLs, and coins needed a row that answers everything without a tap.

   Palette discipline: every colour here is an existing token, so the light
   theme at §2 keeps working without a second set of overrides.
   ======================================================================== */

/* ---- what this is ------------------------------------------------------ */
.nm-intro { margin-top: 18px; }
.nm-intro-lead {
  font-size: var(--t-l);
  line-height: 1.5;
  color: var(--ink-2);
  font-weight: 500;
  text-wrap: pretty;
}
.nm-intro-pts {
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 8px;
}
.nm-intro-pts li {
  position: relative;
  padding-left: 18px;
  font-size: var(--t-s);
  line-height: 1.5;
  color: var(--ink-3);
  text-wrap: pretty;
}
.nm-intro-pts li::before {
  content: "";
  position: absolute;
  left: 2px; top: 8px;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--blue);
}
.nm-intro-pts b { color: var(--ink-2); font-weight: 700; }
@media (min-width: 760px) {
  .nm-intro-pts { grid-template-columns: repeat(3, 1fr); gap: 14px; }
}

/* ---- the toggle -------------------------------------------------------- */
.nm-tabs {
  margin-top: 20px;
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: var(--r-s);
  background: var(--card);
}
.nm-tab {
  appearance: none;
  border: 0;
  cursor: pointer;
  padding: 9px 20px;
  border-radius: 9px;
  background: transparent;
  color: var(--ink-3);
  font: 700 var(--t-s)/1 var(--font);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.nm-tab.is-on { background: var(--blue); color: #fff; }
@media (hover: hover) {
  .nm-tab:not(.is-on):hover { color: var(--ink); background: var(--card-2); }
}
@media (prefers-reduced-motion: reduce) { .nm-tab { transition: none; } }

/* ---- the coins list ---------------------------------------------------- */
.nm-tokens { margin-top: 4px; }
.nm-tklist { margin-top: 18px; display: grid; gap: 12px; }
@media (min-width: 900px)  { .nm-tklist { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1340px) { .nm-tklist { grid-template-columns: repeat(3, 1fr); } }

.nm-tk {
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  padding: 14px 15px 13px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  /* Fill the grid row. Grid already stretches items to the tallest in their
     row; this makes the flex column inside actually use that height, which is
     what lets margin-top:auto on the footer do its job. */
  height: 100%;
}

.nm-tk-id { display: flex; align-items: center; gap: 10px; }
.nm-tk-img {
  width: 40px; height: 40px;
  border-radius: 50%;
  background: var(--card-2);
  border: 1px solid var(--line);
  object-fit: cover;
  flex: 0 0 auto;
}
.nm-tk-sym {
  font: 800 var(--t-l)/1.2 var(--font);
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nm-tk-nums { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.nm-tk-px { font: 800 var(--t-l)/1.2 var(--mono); color: var(--ink); }
.nm-tk-chg { font: 700 var(--t-s)/1.3 var(--font); }
.nm-tk-chg.is-up   { color: var(--up); }
.nm-tk-chg.is-down { color: var(--down); }
/* A missing move is not a flat move, and it must not read as one. */
.nm-tk-chg.is-unk  { color: var(--ink-4); font-weight: 500; font-style: italic; }

/* THE RISK BLOCK. Same idea as .nm-risk above: a coloured edge carries the
   verdict so it survives being skimmed. The UNKNOWN case is styled as loudly
   as a measured warning on purpose — quiet absence reads as reassurance, and
   that is the inference that costs money. */
.nm-tk-risk {
  border-left: 4px solid var(--line-2);
  padding: 3px 0 3px 11px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.nm-tk-risk-h {
  font: 800 var(--t-s)/1.35 var(--font);
  color: var(--ink-2);
  /* Two lines reserved — "A few wallets own most of it" wraps on a narrow
     column while "Spread out" does not. */
  min-height: calc(2 * 1.35 * var(--t-s));
}
.nm-tk-risk-d {
  font: 500 var(--t-xs)/1.5 var(--font);
  color: var(--ink-3);
  text-wrap: pretty;
  /* THE SAME LESSON AS THE GIFT TILES, and it caught this component too: on
     first render the coin rows measured 281–361px because each risk sentence
     wrapped to a different number of lines. Reserving three lines makes every
     card agree on its height.
     NOT a line-clamp. Truncating a risk explanation with an ellipsis would
     hide the half of the sentence that carries the warning — the reservation
     sets a floor, never a ceiling. */
  min-height: calc(3 * 1.5 * var(--t-xs));
}

.nm-tk-risk.is-low     { border-left-color: var(--up); }
.nm-tk-risk.is-low     .nm-tk-risk-h { color: var(--up); }
.nm-tk-risk.is-mid     { border-left-color: var(--alarm); }
.nm-tk-risk.is-mid     .nm-tk-risk-h { color: var(--alarm); }
.nm-tk-risk.is-high    { border-left-color: var(--down); }
.nm-tk-risk.is-high    .nm-tk-risk-h { color: var(--down); }
.nm-tk-risk.is-unknown { border-left-color: var(--alarm-edge); }
.nm-tk-risk.is-unknown .nm-tk-risk-h { color: var(--alarm); }

.nm-tk-foot2 {
  font: 500 var(--t-xs)/1.45 var(--font);
  color: var(--ink-4);
  border-top: 1px solid var(--line);
  padding-top: 9px;
  /* Pinned to the bottom so the hairline rule sits on one shared baseline
     across the row, and two lines reserved because "$1.79M today · $5.87M
     available to trade against" wraps where a smaller pair does not. */
  margin-top: auto;
  min-height: calc(2 * 1.45 * var(--t-xs));
}

.nm-tk-foot {
  margin-top: 16px;
  font: 500 var(--t-xs)/1.55 var(--font);
  color: var(--ink-4);
  text-wrap: pretty;
}

/* The loading block for a coin row: same height as the real thing, so the list
   does not jump when it resolves. */
.nm-skel--row { height: 152px; border-radius: var(--r); }
