/* Alephium Price Index — monochrome by design.
   Alephium's own identity is a white mark on black with Inter, so the palette
   here is neutral throughout and colour is reserved for meaning: confidence and
   disagreement are the only things allowed to be coloured. */

:root {
  --bg: #08090a;
  --surface: #0e1011;
  --surface-2: #14171a;
  --line: #1e2226;
  --line-soft: #16191c;
  --fg: #f2f4f5;
  --fg-2: #99a1a8;
  /* The quietest text: column headers, labels, a token's name, the footer.
     It was #626a71, 3.3 to 3.6:1 on the three grounds at 10 to 12 px, where
     small text wants 4.5:1. This is 4.8 to 5.3:1; the light theme's is 4.6 to
     5.0:1, with its second grey darkened to keep the step between them. */
  --fg-3: #7d858c;
  --good: #d8dbde;
  --warn: #d2a12f;
  --bad: #cc6152;
  /* Direction is the only non-confidence meaning allowed colour on this page.
     Desaturated on purpose: a wall of 191 saturated green/red rows reads as
     decoration, and the point is that colour here carries information. */
  --up: #5aa87c;
  --down: #cc6152;
  --flat: #7d858c;
  --focus: #6ea8fe;
  /* The API banner's own palette: the one decorative element on the page, so
     the one place blue appears without meaning anything. Kept pale. */
  --promo-bg: linear-gradient(105deg, #0e1011 0%, #0e1011 36%, #0f1622 68%, #13203a 100%);
  --promo-top-a: rgba(150, 182, 232, .14); --promo-top-b: rgba(150, 182, 232, .05);
  --promo-left-a: rgba(126, 160, 222, .05); --promo-left-b: rgba(126, 160, 222, .15);
  --promo-right-a: rgba(104, 142, 212, .16); --promo-right-b: rgba(104, 142, 212, .04);
  --promo-edge: rgba(170, 198, 240, .10); --promo-edge-lit: rgba(196, 218, 250, .38);
  --promo-line: rgba(110, 168, 254, .32);
  --promo-dot: #6ea8fe;
  --promo-kicker: #8fb4ea;
  --radius: 12px;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --gutter: 16px;
}

/* Light is defined once and applied twice: when the OS asks for it and the
   reader has not overridden, and when the reader has explicitly chosen it.
   Keeping one declaration block means the two can never drift apart. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg: #fbfbfc; --surface: #fff; --surface-2: #f4f5f6;
    --line: #e3e6e8; --line-soft: #eef0f1;
    --fg: #0d1114; --fg-2: #4f575e; --fg-3: #687077;
    --good: #1c2226; --warn: #8a670f; --bad: #a83f30;
    --up: #2f7d52; --down: #a83f30; --flat: #687077;
    --promo-bg: linear-gradient(105deg, #fff 0%, #fcfdff 40%, #f4f8fd 100%);
    --promo-top-a: rgba(236, 243, 252, .92); --promo-top-b: rgba(221, 232, 249, .85);
    --promo-left-a: rgba(228, 237, 250, .92); --promo-left-b: rgba(188, 210, 242, .88);
    --promo-right-a: rgba(204, 221, 246, .9); --promo-right-b: rgba(225, 235, 250, .55);
    --promo-edge: rgba(255, 255, 255, .7); --promo-edge-lit: #fff; --promo-line: rgba(118, 157, 214, .5);
    --promo-dot: #8eb0e0; --promo-kicker: #3f6eb0;
  }
}
:root[data-theme="light"] {
  --bg: #fbfbfc; --surface: #fff; --surface-2: #f4f5f6;
  --line: #e3e6e8; --line-soft: #eef0f1;
  --fg: #0d1114; --fg-2: #4f575e; --fg-3: #687077;
  --good: #1c2226; --warn: #8a670f; --bad: #a83f30;
  --up: #2f7d52; --down: #a83f30; --flat: #687077;
  --promo-bg: linear-gradient(105deg, #fff 0%, #fcfdff 40%, #f4f8fd 100%);
  --promo-top-a: rgba(236, 243, 252, .92); --promo-top-b: rgba(221, 232, 249, .85);
  --promo-left-a: rgba(228, 237, 250, .92); --promo-left-b: rgba(188, 210, 242, .88);
  --promo-right-a: rgba(204, 221, 246, .9); --promo-right-b: rgba(225, 235, 250, .55);
  --promo-edge: rgba(255, 255, 255, .7); --promo-edge-lit: #fff; --promo-line: rgba(118, 157, 214, .5);
  --promo-dot: #8eb0e0; --promo-kicker: #3f6eb0;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  font-variant-numeric: tabular-nums;
}
h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -0.015em; }
p { margin: 0; }
a { color: inherit; }

.wrap { width: 100%; max-width: 1280px; margin: 0 auto; padding-inline: var(--gutter); }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 10;
  background: var(--fg); color: var(--bg); padding: 10px 14px; border-radius: 0 0 8px 0;
}
.skip:focus { left: 0; }

:where(a, button, input, th[data-sort], tr[tabindex]):focus-visible {
  outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 4px;
}

/* ---------- header ---------- */
.top { border-bottom: 1px solid var(--line); background: var(--bg); }
.top-in {
  display: flex; align-items: center;
  gap: 14px 22px; padding-block: 16px; flex-wrap: wrap;
}
.brand { display: flex; align-items: center; gap: 13px; min-width: 0; }
/* The mark is pale by design: measured 13.3:1 against the dark ground and
   1.49:1 against white, i.e. invisible in light mode. It sits on the same dark
   tile the app icon uses, which keeps the brand mark itself untouched and the
   two treatments consistent. On the dark theme the tile is indistinguishable
   from the header. */
.mark {
  width: 34px; height: 34px; flex: none; display: block;
  background: #0b0d0f; border-radius: 8px; padding: 3px;
  box-sizing: border-box;
}
.brand-txt h1 { font-size: 1.0625rem; }
.brand-txt p { color: var(--fg-2); font-size: .8125rem; margin-top: 2px; }

/* The site's pages, beside the name on every one of them. Method and API were
   links in the footer alone, under a table 12,000 px long (2026-10-02). The
   page a reader is on is marked (aria-current). */
.nav { display: flex; align-items: center; gap: 2px; }
.nav a {
  color: var(--fg-2); text-decoration: none; font-size: .875rem; font-weight: 500;
  padding: 6px 10px; border-radius: 7px; white-space: nowrap;
}
.nav a:hover { color: var(--fg); }
.nav a[aria-current="page"] { color: var(--fg); background: var(--surface-2); }

.ticker { display: flex; align-items: center; gap: 16px; font-size: .8125rem; color: var(--fg-2); margin-left: auto; }
/* #ticker is now a child of .ticker rather than being it, so it needs its own
   gap — otherwise the spans it renders butt together. */
#ticker { display: flex; align-items: center; gap: 16px; }
.ticker b { color: var(--fg); font-weight: 500; font-family: var(--mono); }
/* Theme switch. Three states rather than two, because "follow the system" is
   a real preference and a two-way toggle silently destroys it. */
.theme {
  display: inline-flex; align-items: center; gap: 2px;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 8px; padding: 2px;
}
.theme button {
  background: none; border: 0; color: var(--fg-3); cursor: pointer;
  padding: 3px 7px; border-radius: 6px; line-height: 1; display: grid; place-items: center;
}
.theme button svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.theme button:hover { color: var(--fg-2); }
.theme button[aria-pressed="true"] { color: var(--fg); background: var(--surface); box-shadow: 0 1px 2px rgba(0,0,0,.18); }

/* The header holds one line down to 860 px: the name's tagline gives way
   first, then the ticker's counts, which the dashboard's cards say too. */
@media (max-width: 1380px) { #ticker span:nth-child(2) { display: none; } }
@media (max-width: 1120px) { .tagline { display: none; } }
/* Under that, two rows: the name and the Swap button, then the pages, the
   ticker and the theme switch. A phone has no room for the ticker there, and
   it takes a third row. The ticker's box is taken away (contents), so its two
   parts are placed on their own. */
@media (max-width: 860px) {
  .top-in {
    display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center;
    grid-template-areas: "brand brand swap" "nav tick theme";
    gap: 8px 12px; padding-block: 12px;
  }
  .brand { grid-area: brand; }
  .swap-btn { grid-area: swap; justify-self: end; }
  .nav { grid-area: nav; }
  .ticker { display: contents; }
  .theme { grid-area: theme; justify-self: end; }
  #ticker { grid-area: tick; justify-self: end; font-size: .75rem; gap: 14px; min-height: 1.5em; }
}
@media (max-width: 560px) {
  .top-in { grid-template-areas: "brand brand swap" "nav nav theme" "tick tick tick"; }
  #ticker { justify-self: start; }
}

/* The swap (ADR 0010): the one filled button in the header, at its far right.
   The server marks the page data-swap="on" when there is a swap to open
   (src/server.ts); without it nothing here shows. */
.swap-btn {
  display: none; align-items: center; gap: 6px; flex: none;
  background: var(--fg); color: var(--bg); text-decoration: none;
  font: inherit; font-size: .8125rem; font-weight: 600; line-height: 1;
  padding: 7px 13px 7px 10px; border: 0; border-radius: 8px; cursor: pointer;
}
:root[data-swap="on"] .swap-btn { display: inline-flex; }
.swap-btn svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.swap-btn:hover { opacity: .9; }

.dot { width: 6px; height: 6px; border-radius: 50%; background: var(--good); display: inline-block; margin-right: 6px; }
.dot.stale { background: var(--warn); }
.dot.down { background: var(--bad); }

/* ---------- banners ---------- */
/* Two panels in the brand's own art, kept to the right behind a fade so the
   words read first: the swap's at the head of the page, with a trade to try
   in it, and the API's under the table. What to press sits under the words
   (Dennis, 2026-10-03: from 900 px it had stood beside them, in the middle
   of the panel), and neither panel can be put away. */
.promo {
  position: relative; isolation: isolate; overflow: hidden;
  margin-top: 18px;
  background: var(--promo-bg);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.promo-art {
  position: absolute; inset: 0 0 0 auto; width: min(760px, 70%); height: 100%;
  z-index: -1; pointer-events: none;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 38%);
  mask-image: linear-gradient(90deg, transparent, #000 38%);
}
/* Two isometric boxes seen from above, as in the brand art: a pale top, a left
   side deepening toward the fold, a right side fading out, and the light
   caught along the V where the top meets the sides. */
.promo-top-a { stop-color: var(--promo-top-a); } .promo-top-b { stop-color: var(--promo-top-b); }
.promo-left-a { stop-color: var(--promo-left-a); } .promo-left-b { stop-color: var(--promo-left-b); }
.promo-right-a { stop-color: var(--promo-right-a); } .promo-right-b { stop-color: var(--promo-right-b); }
.promo-art .face-top { fill: url(#promoTop); }
.promo-art .face-left { fill: url(#promoLeft); }
.promo-art .face-right { fill: url(#promoRight); }
.promo-art .box path:not(.edge-lit) { stroke: var(--promo-edge); stroke-width: 1; }
.promo-art .edge-lit { fill: none; stroke: var(--promo-edge-lit); stroke-width: 1.25; stroke-linecap: round; }
.promo-guides line { stroke: var(--promo-line); stroke-width: 1; stroke-dasharray: 4 6; opacity: .7; }
.promo-guides line.faint { opacity: .35; }
.promo-ray { stroke: var(--promo-line); stroke-width: 1; }
.promo-dot { fill: var(--promo-dot); }
/* A few pulses, then still: a page left open would otherwise repaint the art
   every frame for as long as it stays open. They end where they rest. */
@media (prefers-reduced-motion: no-preference) {
  .promo-dot { animation: promo-pulse 4.5s ease-in-out 3; }
  .promo-dot-late { animation-delay: -2.2s; }
}
@keyframes promo-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }

.promo-body { padding: 20px 24px; max-width: 640px; }

/* The swap's own picture: a few lines on the brand art's own slant, the one
   the API banner's ray is on, and on four of them a streak of light running
   up toward the trade's box. (Dennis, 2026-10-03: the two discs it had were
   too much.) Quiet: it sits behind the words. */
.swap-rays line { stroke: var(--promo-line); stroke-width: 1; }
.swap-rays .strong { stroke-width: 1.5; }
.swap-rays .faint { opacity: .5; }
.streak-a { stop-color: var(--promo-dot); stop-opacity: 0; } .streak-b { stop-color: var(--promo-dot); stop-opacity: .95; }
.streak line { stroke: url(#swapStreak); stroke-width: 1.75; stroke-linecap: round; }
.streak.s2 line, .streak.s4 line { stroke-width: 2.25; }
.streak circle { fill: var(--promo-dot); }
/* Each runs its line three times when the page comes up, and stops where it
   rests: nothing here repaints while the banner just sits there. */
@media (prefers-reduced-motion: no-preference) {
  .streak { animation: streak-run 2.4s cubic-bezier(.3, 0, .2, 1) 3 both; }
  .streak.s2 { animation-delay: .35s; }
  .streak.s3 { animation-delay: .7s; }
  .streak.s4 { animation-delay: 1.05s; }
}
@keyframes streak-run { 0% { transform: translate(-280px, 140px); opacity: 0; } 18% { opacity: 1; } 100% { transform: none; opacity: 1; } }
.promo-kicker {
  color: var(--promo-kicker); font-size: .6875rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .12em; margin-bottom: 6px;
}
.promo-title { font-size: 1.25rem; line-height: 1.25; letter-spacing: -.02em; }
.promo-text { color: var(--fg-2); font-size: .875rem; margin-top: 6px; max-width: 62ch; }
.promo-cta { display: flex; align-items: center; flex-wrap: wrap; gap: 10px 20px; margin-top: 14px; }
.promo-btn {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--fg); color: var(--bg); text-decoration: none;
  font-size: .875rem; font-weight: 500; padding: 9px 15px; border-radius: 9px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .12), 0 6px 18px -8px rgba(60, 110, 190, .55);
  transition: transform .15s ease, box-shadow .15s ease;
}
.promo-btn svg { flex: none; width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; transition: transform .15s ease; }
.promo-btn:hover { box-shadow: 0 1px 2px rgba(0, 0, 0, .12), 0 8px 22px -8px rgba(60, 110, 190, .75); }
.promo-btn:hover svg { transform: translateX(2px); }
.promo-x {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--fg-2); text-decoration: none; font-size: .875rem;
}
.promo-x svg { width: 13px; height: 13px; fill: currentColor; }
.promo-x:hover { color: var(--fg); text-decoration: underline; text-underline-offset: 3px; }

/* The swap's panel: the words, a trade to try, and what to press. On a wide
   screen the trade stands at the right, where the art alone was, and what to
   press under the words; stacked, it comes between the two, so a reader
   meets the answer before the button. Without a swap there is no panel. */
/* Its one column is held to the panel's width (minmax(0, 1fr)): left to its
   contents it grew to the button's label, which is one line, and beside a
   symbol of twenty-one letters the trade's box was cut off at the panel's
   edge. */
.swapbox { display: grid; grid-template-columns: minmax(0, 1fr); grid-template-areas: "body" "quote" "cta"; gap: 14px 32px; padding: 20px 24px; }
:root:not([data-swap="on"]) .swapbox { display: none; }
.swapbox .promo-body { grid-area: body; padding: 0; max-width: 560px; }
.swapbox .promo-cta { grid-area: cta; margin-top: 0; min-width: 0; }
.quote { grid-area: quote; min-width: 0; }
/* Side by side from 641 px, wherever the panel is not a phone's. From 641 to
   899 it was stacked and 400 px tall, twice what it had been, and at 899 by
   800 no row of the table was in sight: there is room for the box beside the
   words there, without their paragraph, which comes back at 900. */
@media (min-width: 641px) {
  /* The two rows share what the trade's box is taller by, so the words and
     what to press sit together at the middle of its height. */
  /* The words keep 220 px, which is the button's width with its trade on it:
     at 641 px the box took its full 372 and the button read "Swap 1,000 …". */
  .swapbox { grid-template-columns: minmax(220px, 1fr) minmax(300px, 372px); grid-template-areas: "body quote" "cta quote"; padding-block: 16px; }
  .swapbox .promo-body { align-self: end; }
  .swapbox .promo-cta { align-self: start; }
}
@media (max-width: 899px) {
  .swapbox .promo-text { display: none; }
}
/* Under 1280 px the words reach under the picture: its lines, and a streak at
   rest, crossed the paragraph (1.3:1 at the worst point). It fades in later
   there, past where the words end. */
@media (min-width: 641px) and (max-width: 1279px) {
  .swapbox .promo-art {
    -webkit-mask-image: linear-gradient(90deg, transparent 34%, #000 64%);
    mask-image: linear-gradient(90deg, transparent 34%, #000 64%);
  }
}
/* A trade, and what each would pay for it (app.js, "the swap's box"): the
   amount and the two tokens in one line, then this route and each DEX, the
   one that leaves the most first. The rows' places are held (index.html), and
   a row is one height whatever it says, so the panel does not move when the
   answer comes. The line under the rows is one line for what it mostly says;
   a longer one (a large trade past what was read of a pool, a refusal with
   its hint) is two, and the panel 18 px taller while it shows. */
.quote {
  background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
  padding: 10px 12px 8px;
}
.quote-ask { display: flex; align-items: center; gap: 8px; }
.quote-ask input, .pick {
  height: 36px; min-width: 0; background: var(--surface-2); border: 1px solid var(--line); border-radius: 8px;
  color: var(--fg); font: inherit; font-size: .875rem;
}
.quote-ask input { flex: 1 1 0; width: 0; min-width: 60px; padding: 0 10px; font-family: var(--mono); }
/* A token: its symbol and a mark that it opens, under the select that
   chooses it (index.html). As wide as the symbol, and no wider than it has
   room for. */
.pick { position: relative; flex: 0 1 auto; min-width: 3.5em; display: inline-flex; align-items: center; gap: 5px; padding: 0 8px 0 11px; font-weight: 500; }
/* One symbol on this chain is twenty-one letters. Uncut, it left the other
   token two letters and the amount four digits; it is the one cut. */
.pick > span { max-width: 10ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick select { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; font-size: 1rem; }
.pick:hover { border-color: var(--fg-3); }
.pick:has(select:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }
/* A browser without :has (Firefox before 121) shows the ring for a pointer
   too, rather than none for the keyboard. */
@supports not selector(:has(*)) {
  .pick:focus-within { outline: 2px solid var(--focus); outline-offset: 2px; }
}
.quote-ask svg { flex: none; width: 15px; height: 15px; fill: none; stroke: var(--fg-3); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.pick svg { width: 13px; height: 13px; }
.quote-rows { list-style: none; margin: 8px 0 0; padding: 0; font-size: .8125rem; transition: opacity .15s ease; }
/* At least 27 px, not 27: with the text alone enlarged to twice its size it
   ran over rows of a fixed height. */
.quote-rows li {
  display: grid; grid-template-columns: minmax(0, 1fr) auto 5.75em; column-gap: 10px; align-items: center;
  min-height: 27px; color: var(--fg-2);
}
.quote-rows li + li { border-top: 1px solid var(--line-soft); }
.quote-rows li > :first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.quote-rows .num { font-family: var(--mono); text-align: right; white-space: nowrap; }
.quote-rows .vs { text-align: right; white-space: nowrap; }
.quote-rows .ours { color: var(--fg); font-weight: 600; }
.quote-rows .ours .vs { font-weight: 500; }
.quote-rows .up { color: var(--up); }
.quote-rows .down { color: var(--down); }
/* While another trade's answer is on its way, the last one stands, dimmed. */
.quote.pending .quote-rows { opacity: .5; }
/* Prices withheld as too old (the stale banner): the last answer, like the
   table's last rows, is not current. */
.quote.is-stale .quote-rows { opacity: .4; }
.quote-foot { margin-top: 6px; min-height: 1.45em; font-size: .75rem; line-height: 1.45; color: var(--fg-3); }
.quote-foot.warn { color: var(--warn); }
.quote-foot.bad { color: var(--bad); }
/* The button says the trade, and a long symbol is cut, not wrapped: at 375 px
   it made the button two lines tall. */
.swapbox .promo-btn { max-width: 100%; min-width: 0; }
#qGoText { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Said, not seen: the box's answer, for a reader who cannot see its rows. */
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
@media (max-width: 640px) {
  .promo-body { padding: 16px 18px; }
  .swapbox { padding: 14px 16px 16px; gap: 12px; }
  .promo-title { font-size: 1.125rem; }
  /* A phone's panel is the trade and its button: the title in one line, and
     the link for someone with a site of their own left to a wider screen. */
  .promo-title .wide, .swapbox .promo-x { display: none; }
  /* Room in the amount's field for six digits: at 70 px it cut 100000 off. */
  .quote-ask { gap: 6px; }
  .quote-ask input { padding: 0 8px; }
  .pick { padding: 0 6px 0 9px; gap: 3px; }
  .pick > span { max-width: 8ch; }
  /* A phone's first screen is for prices: the title says what is offered. */
  .promo-text { display: none; }
  .promo-art { width: 100%; opacity: .45; }
  .promo-art-swap { opacity: .28; }
}
/* The smallest phones: the arrow between the tokens and the marks that they
   open make room for the symbols themselves, cut to two letters at 320 px. */
@media (max-width: 359px) {
  .quote-ask svg { display: none; }
  .pick { padding-inline: 10px; }
  /* A narrower last column, so "Blockflow Index" is whole at 320 px. */
  .quote-rows li { column-gap: 6px; grid-template-columns: minmax(0, 1fr) auto 4.5em; }
}

/* ---------- stale banner ---------- */
/* Shown when the service withholds prices because its last snapshot is past
   the freshness limit (ADR 0005). Loud on purpose: whatever the table still
   shows is not current, and a small dot in the header did not say so. */
.banner {
  margin-block: 26px; padding: 12px 16px;
  background: var(--surface); border: 1px solid var(--line); border-left: 3px solid var(--bad);
  border-radius: 10px; color: var(--fg-2); font-size: .875rem; line-height: 1.55;
}
.banner b { color: var(--fg); font-weight: 600; }
.panel.is-stale tbody { opacity: .4; }

/* ---------- stats ---------- */
.stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px;
  /* The 1px gap IS the divider: cells sit on this background. It is also what
     an empty trailing cell shows, so it must read as part of the panel. */
  background: var(--line-soft);
  border: 1px solid var(--line); border-radius: var(--radius);
  margin-block: 18px; overflow: hidden;
}
/* The dashboard's five cards (#stats; the method page's stay a grid) need
   about 1000 px to sit side by side. Under that they are one row that scrolls
   sideways, each as wide as it needs, the next one showing at the edge:
   wrapped, they were 351 px of a phone's first screen, left an empty cell,
   and broke their figures across lines on a tablet. */
@media (max-width: 1000px) {
  #stats { display: flex; overflow-x: auto; scroll-snap-type: x proximity; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
  #stats::-webkit-scrollbar { display: none; }
  #stats .stat { flex: 1 0 auto; min-width: 158px; max-width: 232px; scroll-snap-align: start; }
  /* One card, "Waiting for the first snapshot", takes the row and no more:
     as wide as its words, it ran 49 px past a 320 px phone. */
  #stats .stat:only-child { flex: 1 1 0; min-width: 0; max-width: none; }
}
/* A row that scrolls with no scrollbar is for a finger. Under a mouse the
   last cards were cut off with nothing to say there was more, at every width
   under 965 px. There the cards are a grid, all in sight: three over two
   above a phone's width, and two columns at it (the rules for .stats, in the
   phone's block below), as a narrow window had before. */
@media (max-width: 1000px) and (pointer: fine) {
  #stats { display: grid; overflow: hidden; }
  #stats .stat { min-width: 0; max-width: none; }
}
@media (min-width: 721px) and (max-width: 1000px) and (pointer: fine) {
  #stats { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  #stats .stat { grid-column: span 2; }
  #stats .stat:nth-child(n + 4) { grid-column: span 3; }
  #stats .stat:only-child { grid-column: 1 / -1; }
}

/* Nothing loaded yet, or prices withheld: an empty grid would draw as a stray
   bordered line. */
.stats:empty { display: none; }
/* A <dl> carries a default 1em block margin. Inside a gap-as-divider grid that
   margin shows through as a broad band between rows. */
.stat { background: var(--surface); padding: 13px 16px; margin: 0; position: relative; }
.stat dl { margin: 0; position: relative; }
.stat dt {
  color: var(--fg-3); font-size: .6875rem; text-transform: uppercase; letter-spacing: .07em; margin: 0 0 6px;
  display: flex; align-items: center; justify-content: space-between; gap: 8px; white-space: nowrap;
}
.stat dd { margin: 0; font-size: 1.25rem; font-weight: 500; font-family: var(--mono); letter-spacing: -.02em; }
.stat small { display: block; color: var(--fg-3); font-size: .75rem; font-family: var(--sans); margin-top: 3px; }
/* The Total depth card opens its panel (app.js statCard): a table row's hover,
   and the focus ring drawn inside the card, since .stats clips outside it. */
.stat-open { cursor: pointer; }
.stat-open:hover { background: var(--surface-2); }
.stat-open:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.stat .chev { margin-left: 7px; color: var(--fg-3); font-family: var(--sans); font-weight: 400; }

/* ---------- panel ---------- */
/* Clipped to its rounded corners, not scrolled: `hidden` makes a box its
   children stick to, and the table's header then never stuck to the window.
   A browser without `clip` keeps `hidden`, and the header that scrolls away. */
.panel { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; overflow: clip; }
.panel-head {
  display: flex; gap: 14px; align-items: center; justify-content: space-between;
  padding: 13px 16px; border-bottom: 1px solid var(--line); flex-wrap: wrap;
}
.search { position: relative; flex: 1 1 240px; display: flex; align-items: center; }
.search svg {
  position: absolute; left: 11px; width: 15px; height: 15px; pointer-events: none;
  fill: none; stroke: var(--fg-3); stroke-width: 1.8; stroke-linecap: round;
}
.search input {
  width: 100%; background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 8px; color: var(--fg); font: inherit; font-size: .875rem;
  padding: 8px 12px 8px 33px;
}
.search input::placeholder { color: var(--fg-3); }
/* Filter chips, one per group of rows. Pressed means the group is shown. */
.filter-chips { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: .8125rem; }
.filter-chips button {
  background: none; border: 1px solid var(--line); color: var(--fg-2);
  font: inherit; font-size: .75rem; padding: 3px 10px; border-radius: 999px; cursor: pointer;
}
.filter-chips button:hover { color: var(--fg); }
.filter-chips button[aria-pressed="true"] { color: var(--fg); background: var(--surface-2); border-color: var(--fg-3); }
.filter-chips .n { font-family: var(--mono); color: var(--fg-3); margin-left: 3px; }

/* A box that scrolls sideways is also one its children stick to, so the
   header scrolled away with the first screen of a table 190 rows long. From
   880 px the table fits its panel (it needs 788 px of the 846 there, and the
   columns that would not fit wider are hidden, below), so nothing scrolls and
   the header sticks to the window. Between the phone's cards and that, the
   table still scrolls sideways, and its header with it. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
@media (min-width: 880px) { .table-scroll { overflow-x: visible; } }
/* A row too wide for the panel all the same (a price of thirty digits is one
   anyone can mint) would be cut off out of reach. While there is one, the
   table scrolls sideways again and its header gives up sticking (app.js
   fitTable). */
.table-scroll.wide { overflow-x: auto; }
/* What takes the focus is not left under the header that sticks. On the
   dashboard alone: the method page's answers and the swap have none, and a
   link to one of them stopped 44 px short. */
@media (min-width: 880px) { html:has(#tbl) { scroll-padding-top: 44px; } }
table { width: 100%; border-collapse: collapse; font-size: .875rem; }
thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--surface); color: var(--fg-3);
  font-size: .6875rem; font-weight: 500; text-transform: uppercase; letter-spacing: .07em;
  text-align: right; padding: 10px 11px; white-space: nowrap;
  /* A shadow, not a border: a collapsed table's borders stay behind when the
     header sticks. */
  box-shadow: inset 0 -1px 0 var(--line); cursor: pointer;
}
thead th.c-tok { text-align: left; }
thead th.c-spark, td.c-spark { width: 104px; }
thead th span { border-bottom: 1px dotted var(--fg-3); cursor: help; }
thead th[aria-sort] { color: var(--fg); }
thead th[aria-sort="descending"]::after { content: " ↓"; }
thead th[aria-sort="ascending"]::after { content: " ↑"; }

/* The line between the priced tokens and the ones that are not (app.js
   renderRows): what the rows under it are, how many, and a way to fold them
   away, at the place a reader meets them. */
tbody tr.group { cursor: default; }
tbody tr.group:hover { background: none; }
tr.group td {
  text-align: left; white-space: normal; padding: 9px 11px;
  background: var(--surface-2); color: var(--fg-2); font-size: .8125rem;
}
tr.group b { color: var(--fg); font-weight: 500; }
.link-btn {
  background: none; border: 0; padding: 2px 4px; margin-left: 6px; font: inherit; color: var(--fg);
  text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--fg-3); cursor: pointer;
}
.link-btn:hover { text-decoration-color: currentColor; }

/* What holds a card's or a row's place until the first snapshot (index.html).
   A card's words are there, unseen, under a bar of their own length. */
.skel .ghost {
  color: transparent; background: var(--line); border-radius: 4px; -webkit-user-select: none; user-select: none;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}
.bar { display: block; height: .75em; border-radius: 4px; background: var(--line); }
td .bar { margin-left: auto; }
td.c-tok .bar { margin-left: 0; }
.bar.w1 { width: 22px; } .bar.w2 { width: 36px; } .bar.w3 { width: 64px; } .bar.w4 { width: 92px; } .bar.w5 { width: 128px; }
.bar.tall { height: 1.1em; margin-block: .2em; }
.bar.low { margin-top: 7px; opacity: .6; }
tr.skel { cursor: default; }
tr.skel:hover { background: none; }
/* As tall as the rows they stand for, so nothing moves when those arrive. */
tr.skel td { height: 60px; }
@media (prefers-reduced-motion: no-preference) {
  .skel .bar, .skel .ghost, tr.skel .ph { animation: skel 1.4s ease-in-out infinite; }
}
@keyframes skel { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

/* A phone's sort (index.html): the table's column headers are what sorts it,
   and the cards have none. */
.sort { display: none; }

tbody tr { border-bottom: 1px solid var(--line-soft); cursor: pointer; }
tbody tr:last-child { border-bottom: 0; }
tbody tr:hover, tbody tr:focus-visible { background: var(--surface-2); }
td { padding: 11px 11px; text-align: right; white-space: nowrap; }
td.c-tok { text-align: left; }

.tok { display: flex; align-items: center; gap: 10px; }
.tok img, .tok .ph {
  width: 22px; height: 22px; border-radius: 50%; flex: none;
  background: var(--surface-2); object-fit: cover;
}
.tok .ph { display: grid; place-items: center; font-size: .625rem; color: var(--fg-3); font-weight: 600; overflow: hidden; }
/* The symbol and, for a token off the official list, its badge: separate boxes,
   so the symbol's own text direction cannot turn the badge around, on a line
   that wraps. Whoever deploys a token picks its symbol, and one padded with
   spaces pushed an inline badge out of sight; this one moves under it. */
.tok .head { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 7px; }
.tok b { font-weight: 500; }
/* The badge as it sits inline elsewhere: as tall as its text, raised 1px. The
   gap is the line's, so a badge that wraps lines up with the symbol. */
.tok .badge { margin-left: 0; line-height: normal; position: relative; top: -1px; }
/* By class, not `.tok span`: the unlisted badge and the logo placeholder are
   spans too, and that rule put the badge on a grey line of its own. */
.tok .name {
  color: var(--fg-3); font-size: .75rem; display: block; margin-top: -2px;
  max-width: 19ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
td.c-tok { max-width: 240px; }
.tok > div { min-width: 0; }
.badge {
  font-size: .6875rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--warn); border: 1px solid currentColor; border-radius: 4px;
  padding: 1px 4px; margin-left: 7px; vertical-align: 1px;
}
.num { font-family: var(--mono); }
.muted { color: var(--fg-3); }

.conf { display: inline-flex; align-items: center; gap: 8px; }
.conf i {
  width: 34px; height: 4px; border-radius: 2px; background: var(--line);
  overflow: hidden; display: block; flex: none;
}
.conf i b { display: block; height: 100%; background: currentColor; }
/* Colour is kept for the scores a reader should stop at, the ones under 0.3
   that the "low confidence" chip filters. A middling score was amber and a
   low one red, and with most tokens middling the column read as a wall of
   warnings (Dennis, 2026-10-02); red is also the 24h column's for a fall. A
   middling score is grey, a high one the page's own bright, and the bar's
   length says the rest. */
.conf.hi { color: var(--good); }
.conf.mid { color: var(--fg-2); }
/* The light theme's amber was #9a7413: as the one coloured score it stood at
   4.3:1 on the panel and 3.9:1 on a row under the pointer. It is #8a670f, 5.2
   and 4.8:1, wherever the theme warns. */
.conf.lo { color: var(--warn); }
.conf em { font-style: normal; font-family: var(--mono); font-size: .8125rem; }

/* ---------- sparkline + change ---------- */
.spark { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
.spark svg { display: block; flex: none; overflow: visible; width: 52px; }
.spark svg path { fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.spark .chg { font-family: var(--mono); font-size: .8125rem; min-width: 5.5ch; text-align: right; }
/* A stats card's 24h line, drawn faintly behind the whole card so the card
   stays the height of its neighbours, and its change at the end of the
   label. The text sits above the line (.stat dl is positioned). */
.stat-trend { position: absolute; left: 0; right: 0; bottom: 0; width: 100%; height: 64%; pointer-events: none; }
.stat-trend .area { fill: currentColor; opacity: .09; }
.stat-trend .line { fill: none; stroke: currentColor; stroke-width: 1.5; vector-effect: non-scaling-stroke; opacity: .5; }
.stat-chg { flex: none; font-family: var(--mono); font-size: .75rem; letter-spacing: 0; text-transform: none; }
.up { color: var(--up); }
.down { color: var(--down); }
.flat { color: var(--flat); }
.spark .none { color: var(--fg-3); font-size: .75rem; }
/* The unit of a 24h line (app.js sparkCell), shown only where nothing else
   gives it: on ALPH's own row, in dollars under a header that says ALPH, and
   on a phone, which has no header (see the mobile block). On ALPH's row it
   sits under the change, which already reserves its width: inline, it made
   the table scroll sideways at 820px, where it fits without it. */
.spark .unit {
  display: none; font-family: var(--sans); color: var(--fg-3); font-size: .6875rem;
  text-transform: uppercase; letter-spacing: .06em; white-space: nowrap;
}
.spark .unit.own { display: block; }

/* ---------- large chart ---------- */
.chart { margin-bottom: 22px; }
.chart-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 10px; flex-wrap: wrap; }
.chart-head h3 { margin: 0; }
/* A chart's heading, and the depth panel's switch beside it. */
.chart-title { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.ranges { display: flex; gap: 2px; }
.ranges button {
  background: none; border: 1px solid transparent; color: var(--fg-3);
  font: inherit; font-size: .75rem; padding: 3px 9px; border-radius: 6px; cursor: pointer;
}
.ranges button:hover { color: var(--fg); }
.ranges button[aria-pressed="true"] { color: var(--fg); background: var(--surface-2); border-color: var(--line); }
.chart-box { position: relative; isolation: isolate; background: var(--surface-2); border: 1px solid var(--line); border-radius: 9px; padding: 12px; }
/* The token's logo, faint and centred behind its chart. Grey, because the
   line's colour already means something (up or down), and a coloured logo
   under it would compete. z-index -1 inside the isolated box puts it above
   the box's background and below everything drawn in it. */
.chart-box::before {
  content: ""; position: absolute; z-index: -1; left: 50%; top: 50%;
  width: 104px; height: 104px; transform: translate(-50%, -50%);
  border-radius: 50%; background: var(--mark, none) center / cover no-repeat;
  opacity: .1; filter: grayscale(1); pointer-events: none;
}
/* A finger dragged across the chart moves the hover (app.js chartHover); one
   dragged up or down still scrolls the panel, and two still zoom the page.
   A finger held on an axis label marks a point, and selects no text. */
.chart-box svg {
  display: block; width: 100%; height: auto; max-width: 100%; cursor: crosshair;
  touch-action: pan-y pinch-zoom; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
}
.chart-box .line { fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linejoin: round; stroke-linecap: round; }
.chart-box .area { stroke: none; opacity: .09; }
.chart-box .grid { stroke: var(--line); stroke-width: 1; }
.chart-box .axis { fill: var(--fg-3); font-size: 10px; font-family: var(--mono); }
/* The hover: a dashed line down the plot at the point nearest the pointer, a
   dot on the line there, both in the line's colour, and the point's time and
   value in a box beside it, in the page's own colour whichever way the line
   went. None of it takes the pointer from the chart. */
.chart-box .hover { pointer-events: none; }
.chart-box .hover line { stroke: currentColor; stroke-width: 1; stroke-dasharray: 3 3; opacity: .75; }
.chart-box .hover circle { fill: currentColor; stroke: var(--surface-2); stroke-width: 2; }
.chart-tip {
  position: absolute; left: 0; top: 0; z-index: 1; pointer-events: none;
  display: flex; flex-direction: column; gap: 2px; white-space: nowrap;
  padding: 6px 9px; border-radius: 7px; border: 1px solid var(--line);
  background: var(--surface); color: var(--fg); font-size: .75rem; line-height: 1.35;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .18);
}
.chart-tip[hidden] { display: none; }
.chart-tip .when { color: var(--fg-3); font-size: .6875rem; }
.chart-tip b { font-family: var(--mono); font-weight: 600; }
.chart-note { color: var(--fg-3); font-size: .75rem; margin-top: 9px; }
.chart-empty { display: grid; place-items: center; height: 190px; color: var(--fg-3); font-size: .8125rem; text-align: center; padding: 0 20px; }

.empty { padding: 34px; text-align: center; color: var(--fg-3); }

/* ---------- footer ---------- */
.foot { padding-block: 22px 40px; border-top: 1px solid var(--line); color: var(--fg-3); font-size: .75rem; display: flex; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
.foot-links { display: flex; gap: 18px; }
.foot a { color: var(--fg-2); text-decoration: none; border-bottom: 1px solid var(--line); }
.foot a:hover { color: var(--fg); }

/* ---------- detail dialog ---------- */
dialog {
  width: min(760px, calc(100vw - 32px)); max-height: 86vh; padding: 0;
  background: var(--surface); color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius);
  overflow: auto; margin: auto;
}
dialog::backdrop { background: rgba(0,0,0,.66); backdrop-filter: blur(2px); }
/* Full page (app.js): the whole window, for a larger chart. Stretched by its
   fixed position rather than sized in vw, which counts a scrollbar's width. */
dialog.full {
  inset: 0; margin: 0; width: auto; height: auto; max-width: none; max-height: none;
  border: 0; border-radius: 0;
}
.d-head {
  display: flex; justify-content: space-between; align-items: flex-start; gap: 16px;
  padding: 19px 20px; border-bottom: 1px solid var(--line);
  position: sticky; top: 0; background: var(--surface); z-index: 1;
}
/* `anywhere`, not `break-word`: only `anywhere` lets the title's width shrink,
   so a symbol wider than the row (BEARZWIFGUNZ180042069 at 375 px) breaks
   rather than push the buttons out of the panel. */
.d-head h2 { font-size: 1.0625rem; overflow-wrap: anywhere; }
.d-head p { color: var(--fg-2); font-size: .8125rem; margin-top: 3px; }
/* The header's buttons: square, the icon drawn at the centre rather than a
   text glyph set on a baseline, which sat off-centre in its focus ring. */
.d-actions { display: flex; gap: 6px; flex: none; }
.d-btn {
  width: 32px; height: 32px; padding: 0; flex: none;
  display: grid; place-items: center;
  background: none; border: 1px solid var(--line); border-radius: 8px;
  color: var(--fg-2); cursor: pointer; text-decoration: none;
}
.d-btn[hidden] { display: none; }
/* The token panel's Swap: the one action there with a word on it, and the
   header button's arrows before it, so the two read as the same thing. */
.d-swap {
  display: inline-flex; align-items: center; gap: 6px;
  height: 32px; padding: 0 13px 0 10px; flex: none; font: inherit; font-size: .8125rem; font-weight: 600;
  background: var(--fg); color: var(--bg); border: 0; border-radius: 8px; cursor: pointer;
}
.d-swap svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.d-swap[hidden] { display: none; }
.d-swap:hover { opacity: .9; }

/* The swap's dialog: the swap app in a frame, as any site would embed it. The
   app draws its own panel, so the dialog is only the ground it stands on. */
#swapDialog {
  width: min(452px, calc(100vw - 20px)); max-height: 94vh; overflow: auto;
  background: none; border: 0; border-radius: 0; padding: 0;
}
.s-bar { display: flex; justify-content: flex-end; gap: 6px; margin-bottom: 8px; }
.s-bar .d-btn { background: var(--surface); }
#swapFrame { display: block; width: 100%; height: 640px; border: 0; background: none; color-scheme: normal; }
/* A phone has no room for a title beside five actions: they take their own row. */
@media (max-width: 480px) {
  .d-head { flex-wrap: wrap; }
  .d-actions { margin-left: auto; }
}
.d-btn:hover { color: var(--fg); background: var(--surface-2); }
.d-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.d-btn svg .solid { fill: currentColor; stroke: none; }
#dFull .i-shrink, #dFull[aria-pressed="true"] .i-grow { display: none; }
#dFull[aria-pressed="true"] .i-shrink { display: block; }
/* The panel opens with focus on its title, which a screen reader announces;
   a ring round a heading there would read as a control. */
#dTitle:focus { outline: none; }
#dBody { padding: 20px; }
/* The logo beside a panel's title, as the table has it beside a symbol. */
.d-logo { display: inline-block; vertical-align: -6px; margin-right: 9px; }
.d-logo img, .d-logo .ph {
  width: 24px; height: 24px; border-radius: 50%; display: block;
  background: var(--surface-2); object-fit: cover;
}
.d-logo .ph { display: grid; place-items: center; font-size: .625rem; color: var(--fg-3); font-weight: 600; overflow: hidden; }
/* A token's price at the head of its panel (app.js openDetail), with the
   chart's change over its range beside it. */
.d-price { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 14px; margin-bottom: 18px; }
.d-price .fig { font-family: var(--mono); font-size: 1.625rem; font-weight: 500; letter-spacing: -.02em; line-height: 1.2; }
.d-price .fig small { font-size: .8125rem; font-weight: 400; letter-spacing: 0; color: var(--fg-3); margin-left: 6px; }
.d-price .alt { font-family: var(--mono); font-size: .9375rem; color: var(--fg-2); }
.d-chg {
  font-family: var(--mono); font-size: .8125rem; white-space: nowrap;
  /* A fainter tint than 13%: on that the text fell under 4.5:1. */
  padding: 2px 9px; border-radius: 999px; background: color-mix(in srgb, currentColor 8%, transparent);
  align-self: center;
}
.d-chg[hidden] { display: none; }
.kv { display: grid; grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); gap: 15px; margin-bottom: 24px; }
.kv div dt { color: var(--fg-3); font-size: .6875rem; text-transform: uppercase; letter-spacing: .07em; margin-bottom: 4px; }
.kv div dd { margin: 0; font-family: var(--mono); font-size: .9375rem; }
#dBody h3 { font-size: .6875rem; text-transform: uppercase; letter-spacing: .09em; color: var(--fg-3); margin-bottom: 10px; }
#dBody table { font-size: .8125rem; }
#dBody thead th { position: static; cursor: default; background: none; }
#dBody thead th span { border: 0; cursor: default; }
#dBody tbody tr { cursor: default; }
#dBody tbody tr:hover { background: none; }
/* A pool links to its contract on the explorer. It reads as a name until it is
   pointed at; the arrow says the link leaves the page. */
.obs a.pool { text-decoration: none; }
.obs a.pool span { color: var(--fg-3); font-size: .75rem; }
/* The second content hides the arrow from screen readers, which otherwise read
   "north east arrow" on every pool; a browser without the syntax keeps the
   first. The link's title says it opens a new tab. */
.obs a.pool::after { content: " ↗"; content: " ↗" / ""; color: var(--fg-3); font-size: .75rem; }
.obs a.pool:hover span, .obs a.pool:focus-visible span { color: var(--fg); text-decoration: underline; }
/* The depth panel's venues: the pool count under the name, as an address sits
   under a pool's. */
.obs td.c-tok .muted { font-size: .75rem; }
.dev-pos { color: var(--warn); }
.dev-neg { color: var(--bad); }
.note { color: var(--fg-3); font-size: .75rem; margin-top: 14px; line-height: 1.6; }

/* Secondary columns go first: Pools and Spread are both in the detail view, and
   Confidence must never be the thing that falls off the right edge — it is the
   column that tells you whether to trust the row. */
/* Measured: with all eight columns the table needs a ~1180px container, i.e. a
   ~1212px viewport. Hiding the two secondary columns below 1240 rather than
   1060 closes the band where the table used to overflow into a horizontal
   scrollbar on a 13-inch laptop. Both are still in the detail view. */
@media (max-width: 1240px) {
  .c-hide-s { display: none; }
  th, td { padding-inline: 9px; }
}

/* ---------- mobile: the table becomes cards ---------- */
@media (max-width: 720px) {
  .stats { grid-template-columns: repeat(2, 1fr); }
  /* Five cards in two columns leave one empty. Let the last one take it rather
     than leave a hole — it also has the longest text. */
  .stat:last-child { grid-column: 1 / -1; }
  .panel-head { gap: 10px; }
  /* Beside the search, which gives it the room; the chips take the rows under. */
  .search { flex: 1 1 130px; }
  .sort {
    display: block; flex: none; max-width: 100%;
    background: var(--surface-2); border: 1px solid var(--line); border-radius: 8px;
    color: var(--fg); font: inherit; font-size: .8125rem; padding: 8px 8px;
  }
  .filter-chips { flex: 1 1 100%; }

  /* Scoped to the main table ON PURPOSE: the detail dialog has its own table,
     and an unscoped `table { display: block }` tore it apart. */
  #tbl thead { display: none; }
  #tbl, #tbl tbody, #tbl tr, #tbl td { display: block; width: 100%; }
  /* `#tbl td` outranks `.c-hide-s`, so the columns hidden at the wider
     breakpoint come back here unless they are hidden again by a selector at
     least as specific. */
  #tbl td.c-hide-s { display: none; }

  /* Named areas, because the visible cell count changes with the breakpoints
     above — a grid-row span would silently mis-align when Pools and Spread are
     hidden. */
  #tbl tbody tr {
    padding: 13px 15px;
    border-bottom: 1px solid var(--line-soft);
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "tok   alph"
      "tok   usd"
      "tok   spark"
      "depth conf";
    gap: 2px 14px;
    align-items: center;
  }
  #tbl td { padding: 0; text-align: right; }
  #tbl tbody tr.group { display: block; padding: 0; }
  #tbl tbody tr.skel { min-height: 125px; }
  #tbl tr.skel td { height: auto; }
  #tbl tr.group td { padding: 10px 15px; text-align: left; }
  #tbl td.c-tok { grid-area: tok; text-align: left; align-self: center; }
  #tbl td:nth-child(2) { grid-area: alph; font-size: .9375rem; }
  #tbl td:nth-child(3) { grid-area: usd; color: var(--fg-2); font-size: .8125rem; }
  #tbl td.c-spark { grid-area: spark; }
  /* No header here, and the line sits under the dollar price: every change
     says its unit, after it, "-6.4% vs ALPH". */
  .spark .unit, .spark .unit.own { display: inline; margin-left: 5px; }
  #tbl td:nth-child(5) { grid-area: depth; text-align: left; margin-top: 7px; }
  #tbl td:last-child   { grid-area: conf; margin-top: 7px; }

  /* Labels only where the number is ambiguous on its own. A price does not need
     one; a depth figure does. */
  #tbl td[data-l="Depth 1%"]::before,
  #tbl td[data-l="Confidence"]::before {
    content: attr(data-l) " ";
    color: var(--fg-3); font-size: .6875rem; text-transform: uppercase;
    letter-spacing: .06em; font-family: var(--sans); margin-right: 6px;
  }
  #tbl td[data-l="ALPH"]::after { content: " ALPH"; color: var(--fg-3); font-size: .75rem; }
  .tok .name { max-width: 16ch; }

  .foot { flex-direction: column; }
  dialog { width: calc(100vw - 20px); max-height: 90vh; }
  #dBody { padding: 16px; }
  .kv { grid-template-columns: repeat(2, 1fr); gap: 13px; }
  /* The dialog table stays a table; it just gets tighter and may scroll. */
  #dBody table { font-size: .75rem; }
  #dBody td, #dBody th { padding: 8px 5px; white-space: nowrap; }
  #dBody .obs { overflow-x: auto; -webkit-overflow-scrolling: touch; margin-inline: -16px; padding-inline: 16px; }
  #dBody .obs .c-tok span { max-width: 11ch; display: inline-block; overflow: hidden; text-overflow: ellipsis; vertical-align: bottom; }
  /* Neither a venue name nor a symbol may set a column's width here: unbroken,
     "Powfi Concentrated" pushed each panel's deviations off the screen, and one
     21-letter symbol did the same to ALPH's depths. The name wraps at its
     space; a symbol is cut short, as the address is, and the address two
     characters shorter than before, to leave the arrow its room. Measured at
     375px, a panel's table is then as wide as it was before the links. */
  #dBody .obs a.pool b { white-space: normal; }
  /* The depth panel's venues wrap the same way: unbroken, "Powfi Concentrated"
     pushed the 5% column off the screen at 375px. */
  #dBody .venues td.c-tok { white-space: normal; }
  #dBody .obs a.pool span { max-width: 9ch; }
  #dBody .obs .sym { max-width: 9ch; display: inline-block; overflow: hidden; text-overflow: ellipsis; vertical-align: bottom; }
}

/* Tokens the index deliberately does not price. Dimmed, not hidden: they are
   part of an honest answer to "what is on this chain", and a reader who
   searches for one should find it saying why rather than finding nothing. */
/* Quieter by colour, not by fading the row: at 62% the name and the badge
   stood at 2.5:1 on 135 of 190 rows. */
tr.nomkt td, tr.nomkt .tok b { color: var(--fg-2); }
tr.nomkt .tok img, tr.nomkt .tok .ph { opacity: .55; }
tr.nomkt:hover .tok b, tr.nomkt:focus-visible .tok b { color: var(--fg); }
tr.nomkt:hover .tok img, tr.nomkt:hover .tok .ph, tr.nomkt:focus-visible .tok img, tr.nomkt:focus-visible .tok .ph { opacity: 1; }
.badge.nomkt-tag { margin-left: 0; color: var(--fg-3); }
.nomkt-why {
  margin: 0 0 18px; max-width: 62ch;
  font-size: .875rem; line-height: 1.6; color: var(--fg-2);
}
.mono { font-family: var(--mono); }

/* A finger needs more to press than a pointer does: the theme switch was 28
   by 20 px, a chip 26 tall, a range 43 by 26, a panel's buttons 32. */
@media (pointer: coarse) {
  .nav a { padding-block: 9px; }
  .theme button { padding: 8px 10px; }
  .filter-chips button { padding: 7px 12px; }
  .ranges button { padding: 7px 11px; }
  .d-btn { width: 40px; height: 40px; }
  .d-swap { height: 40px; }
  .link-btn { padding: 8px 6px; }
  /* A phone's browser zooms the page to a field whose text is under 16 px. */
  .search input, .sort, .quote-ask input { font-size: 1rem; }
  .quote-ask input, .pick { height: 40px; }
}
/* The smallest phones: five 40 px buttons and six ranges ran to the panel's
   edge at 320 px. */
@media (pointer: coarse) and (max-width: 359px) {
  .d-actions { gap: 4px; }
  .d-btn { width: 36px; height: 36px; }
  .d-swap { height: 36px; }
  .ranges button { padding: 7px 8px; }
}
