/* ===========================================================
   HOME-DOMAIN EXPLORER — TOKEN GRID

   Built in the screener's card language, because that is the
   house style and this page should look like it belongs to the
   same product. explorer.html loads screener.css for .tk-logo
   and .tk-letter so the badge behaves exactly as it does on the
   homepage: a colour-hashed letter tile with the issuer's own
   logo laid over it, so a missing or broken image uncovers the
   letter instead of leaving a hole.

   The one thing this page must do that the homepage does not:
   the homepage lists fifty assets that all have a price, a
   market and a history. This lists fifty-seven THOUSAND, of
   which a few hundred trade and the rest are declarations. The
   cards have to make that difference visible without saying a
   word about it, because the order is random and ranking is no
   longer doing that job.

   So evidence drives the card's whole appearance:

     traded   full colour, accent volume figure
     pooled   full colour, quieter — it has a market, not a fill
     quiet    logo desaturated, card recedes, "No market" stated

   A token with no pool and no trades cannot look like a live
   asset, whatever logo it publishes and whoever it names itself
   after. That is the entire safety mechanism on this page.
   =========================================================== */

.xp-body {
  background: var(--bg-base);
  color: var(--text);
  min-height: 100dvh;
}

/* ---------- Headline figures ---------- */
/* Six explicit columns, not auto-fit. auto-fit packs as many as will fit and
   leaves the remainder stranded — at 780px it made five columns and gave the
   sixth stat a full-width row of its own, which reads as a mistake. Six
   divides into 3 and 2 cleanly, so every breakpoint fills its last row. */
/* Same 2013 grid-gap-as-hairline trick as the screener's strip; same fix.
   Space and hierarchy carry a stat row, not rules between every figure. */
.xp-stats {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 0;
  background: var(--bg-surface);
  border-bottom: 1px solid var(--border);
}
@media (max-width: 1100px) { .xp-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 620px)  { .xp-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.xp-stat {
  padding: 14px 20px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.xp-k {
  font-size: var(--fs-sm);
  line-height: 17px;
  color: var(--text-4);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.xp-stat .num {
  font-size: var(--fs-lg);
  line-height: 22px;
  font-weight: var(--fw-head);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.xp-stat-hero .num { color: var(--accent); }

/* ---------- Page frame ---------- */
.xp-wrap {
  max-width: 1560px;
  margin: 0 auto;
  padding: var(--s5) var(--s6) var(--s12);
}

/* The old page spent its entire first screen on two paragraphs and a
   warning box. The argument still needs making — it is the reason this
   page exists — but it makes it in one line, with the rest one click away
   for anyone who wants it. Data starts above the fold. */
.xp-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-bottom: var(--s2);
}
.xp-title {
  font-size: var(--fs-2xl);
  line-height: 32px;
  font-weight: var(--fw-head);
  letter-spacing: -0.02em;
  margin: 0;
}
.xp-lede {
  font-size: var(--fs-md);
  line-height: 21px;
  color: var(--text-2);
  margin: 0 0 var(--s3);
  max-width: 92ch;
}
.xp-lede b { color: var(--text); font-weight: var(--fw-label); }

.xp-more {
  border: 0;
  background: none;
  padding: 0;
  color: var(--accent);
  font: var(--fw-label) var(--fs-base)/1 inherit;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.xp-more:hover { color: var(--accent-alt); }

.xp-note {
  display: none;
  margin: 0 0 var(--s4);
  padding: var(--s3) 0 var(--s3) var(--s3);
  border-left: 2px solid var(--warn);
  background: var(--warn-dim);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-size: var(--fs-base);
  line-height: 20px;
  color: var(--text-2);
  max-width: 88ch;
}
.xp-note.open { display: block; }
.xp-note p { margin: 0 0 6px; }
.xp-note p:last-child { margin-bottom: 0; }
.xp-note strong { color: var(--text); }
.xp-mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .92em;
  color: var(--text);
}

/* ---------- Controls ---------- */
.xp-toolbar {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-bottom: var(--s3);
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--bg-base);
  padding: var(--s2) 0;
}
.xp-filters { display: flex; gap: 6px; flex-wrap: wrap; }
.xp-chip {
  height: 30px;
  padding: 0 12px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-3);
  font: var(--fw-label) var(--fs-base)/1 inherit;
  cursor: pointer;
  white-space: nowrap;
  transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
}
.xp-chip:hover { color: var(--text); border-color: var(--border-bright); }
.xp-chip.active {
  color: var(--on-accent-fill);
  background: var(--accent-fill);
  border-color: var(--accent-fill);
}
.xp-spacer { flex: 1 1 auto; }
.xp-count {
  font-size: var(--fs-base);
  color: var(--text-3);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.xp-count b { color: var(--text); font-weight: var(--fw-label); }
.xp-search {
  height: 32px;
  width: 240px;
  max-width: 100%;
  padding: 0 12px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border-bright);
  background: var(--bg-input);
  color: var(--text);
  font: var(--fw-body) var(--fs-base)/1 inherit;
}
.xp-search::placeholder { color: var(--text-4); }
.xp-search:focus { outline: none; border-color: var(--accent); }

.xp-shuffle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 14px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border-bright);
  background: var(--bg-surface);
  color: var(--text);
  font: var(--fw-label) var(--fs-base)/1 inherit;
  cursor: pointer;
  white-space: nowrap;
}
.xp-shuffle:hover { border-color: var(--accent); color: var(--accent); }
.xp-shuffle svg { width: 14px; height: 14px; }
.xp-shuffle.spin svg { animation: xpSpin 520ms ease; }
@keyframes xpSpin { to { transform: rotate(360deg); } }

/* ---------- The virtual list ----------
   Two spacers and a window. Fifty-seven thousand cards cannot all be in
   the document, and a windowed slice inside a normal CSS grid keeps the
   layout the browser is good at while the spacers carry the scroll
   height. Card height is fixed here so the row maths stays honest — if
   the card grows, --tk-h grows with it. */
/* 34 (top) + 26 (chart) + 15 (domain) + 31 (figures) + 3x6 gaps + 22 padding
   = 146. The card was 144 and flex took the difference out of the only
   shrinkable child, so the domain line rendered 5px tall — a smear of pixels
   under the chart. Size the box to its contents and pin the line. */
:root { --tk-h: 156px; --tk-gap: 10px; }

.xp-viewport { position: relative; }
.xp-grid {
  display: grid;
  gap: var(--tk-gap);
  align-content: start;
  /* Every card's position is set by render() against the #xpTop spacer, so
     browser scroll anchoring inside the grid can only fight that maths. When
     the tab-order fix first moved the anchored card in the DOM, Chrome
     answered each taller #xpTop with a further scroll of the same height and
     the page ran to the bottom on its own. Anchoring still applies above. */
  overflow-anchor: none;
}

.tk-card {
  height: var(--tk-h);
  background: var(--bg-surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--r-md);
  padding: 11px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  overflow: hidden;
  transition: border-color 130ms ease, background 130ms ease, transform 130ms ease;
}
.tk-card:hover {
  border-color: var(--border-bright);
  background: var(--bg-elevated);
  transform: translateY(-1px);
}

.tk-top { display: flex; align-items: center; gap: 9px; min-width: 0; }
.tk-badge { flex: 0 0 auto; width: 30px; height: 30px; }
.tk-id { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 1px; }
.tk-code {
  font-size: var(--fs-md);
  font-weight: var(--fw-head);
  line-height: 18px;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The issuer's own words for its token. Muted, because that is all it is. */
.tk-name {
  /* screener.css sets .tk-name to display:flex for its own two-line variant.
     Inheriting that here turns this single-line span into a flex container,
     where text-overflow has nothing to clip and the ellipsis never appears.
     Stated explicitly rather than renamed, because the collision is the point
     of borrowing that stylesheet and the next person needs to see it. */
  display: block;
  font-size: var(--fs-xs);
  line-height: 15px;
  color: var(--text-4);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The evidence chip. This is what replaces ranking. */
.tk-sig {
  flex: 0 0 auto;
  margin-left: auto;
  font-size: var(--fs-2xs);
  line-height: 16px;
  font-weight: var(--fw-label);
  padding: 1px 7px;
  border-radius: 3px;
  white-space: nowrap;
}
.tk-sig-live { background: var(--up-dim); color: var(--up-text); }
/* Traded once is a fact, not an endorsement. Stated in the neutral ink, so
   it cannot be mistaken at a glance for the market above it. */
.tk-sig-trace { background: var(--bg-input); color: var(--text-3); }
.tk-sig-pooled { background: var(--accent-dim); color: var(--accent); }
.tk-sig-quiet  { background: var(--bg-input); color: var(--text-4); }

/* The home domain is a claim, so it is set as a quotation would be: small,
   secondary, and never in the same weight as anything measured. */
.tk-domrow {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  min-width: 0;
}
.tk-dom {
  /* Shrinks, the chip does not: a clipped domain still reads as a domain,
     a clipped "No market" reads as nothing.

     Clipped from the LEFT, though. These domains are chosen to be mistaken
     for each other and the distinguishing part is at the end:
     "japan.dtcc…" tells you nothing, "…dtcc.markets" tells you which of the
     eleven dtcc subdomains this is. rtl moves the ellipsis to the front
     while plaintext keeps the ASCII running the right way round. */
  flex: 1 1 auto;
  direction: rtl;
  text-align: left;
  font-size: var(--fs-xs);
  line-height: 15px;
  color: var(--text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.tk-foot { display: flex; gap: var(--s3); margin-top: auto; min-width: 0; }
.tk-foot span { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 0; }
.tk-foot i {
  font-style: normal;
  font-size: var(--fs-2xs);
  line-height: 13px;
  color: var(--text-4);
  white-space: nowrap;
  /* The value below already clips itself; the label did not, so a column too
     narrow for "Trustlines" let it run across the gap and sit on top of the
     next figure's label instead of being cut. A backstop, so a future layout
     can crowd a column without producing overlapping words. */
  overflow: hidden;
  text-overflow: ellipsis;
}
.tk-foot b {
  font-size: var(--fs-base);
  font-weight: var(--fw-label);
  line-height: 17px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tk-vol-live { color: var(--up-text); }
/* Never checked is not the same as checked and found nothing. */
.tk-unmeasured { color: var(--text-4); font-style: italic; }

/* ---------- Evidence states ----------
   A token with no pool and no trades does not get to look like a live
   asset. The logo desaturates and the card recedes; everything measured
   stays perfectly legible. Nothing is hidden and nothing is accused. */
/* A card, not an absence.

   Quiet cards were transparent with a dashed --border-soft edge. In dark that
   reads as a deliberate outline grid. In light --border-soft is #F2F2F2 on a
   #F5F5F5 page — 1.03:1, which is not a faint edge, it is no edge — so 94% of
   the grid was text floating on grey with nothing holding it.

   They get the same surface as every other card now, and the dashed edge is
   drawn in the bright border token so it is visible in both themes. "No
   market" is still said by the dash, the chip, and the absent price; none of
   those needed the card to be missing. */
.tk-card[data-ev="quiet"] {
  background: var(--bg-surface);
  border-style: dashed;
  border-color: var(--border-bright);
}
/* The logos keep their colour.

   They used to be desaturated on any token with no market, which is 52,380 of
   55,263 — 94% — so the page was grey almost everywhere and looked broken
   rather than quiet. The intent was sound: a logo is published by whoever
   registered the domain, and draining it stopped an unbacked NVIDIA looking
   like a live asset. But it cost the colour of the whole page to mark the
   ordinary case, which is backwards — 94% is not an exception, it is the
   list.

   The signal survives without it, said in words and structure rather than by
   dimming a picture: the dashed border, the "No market" chip, and a card with
   no price and no volume on it. Those are read, not merely sensed, and they
   do not need the other 6% to be dull for contrast. */
.tk-card[data-ev="quiet"] .tk-code { color: var(--text-2); font-weight: var(--fw-label); }

.tk-card[data-ev="traded"] { border-color: var(--border); }

/* ---------- States ---------- */
.xp-empty, .xp-loading {
  padding: var(--s12) var(--s4);
  text-align: center;
  color: var(--text-4);
  font-size: var(--fs-md);
}
.xp-empty b { color: var(--text-2); }

.xp-foot {
  margin-top: var(--s5);
  font-size: var(--fs-sm);
  line-height: 19px;
  color: var(--text-3);
  max-width: 96ch;
}
.xp-foot strong { color: var(--text-2); font-weight: var(--fw-label); }

/* ---------- Phone ---------- */
@media (max-width: 900px) {
  :root { --tk-h: 152px; --tk-gap: 8px; }
  .xp-wrap { padding: var(--s4) var(--s3) var(--s10); }
  .xp-title { font-size: var(--fs-xl); line-height: 26px; }
  .xp-lede { font-size: var(--fs-base); line-height: 19px; }
  .xp-stat { padding: 10px 12px; }
  .xp-stat .num { font-size: var(--fs-md); line-height: 19px; }
  .xp-toolbar { gap: var(--s2); }
  .xp-filters {
    order: 3;
    width: 100%;
    min-width: 0;   /* a flex item will not shrink to scroll without this */
    overflow-x: auto;
    flex-wrap: nowrap;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .xp-filters::-webkit-scrollbar { display: none; }
  .xp-chip { flex: 0 0 auto; }
  .xp-search { order: 1; flex: 1 1 auto; width: auto; }
  .xp-shuffle { order: 2; }
  .xp-spacer { display: none; }
  .xp-count { order: 4; width: 100%; }
  .tk-foot { gap: var(--s2); }
  .tk-foot b { font-size: var(--fs-sm); }
}

/* .tk-letter is sized for the screener's 24px rows. At 30px in a card the
   two-letter fallback needs its own scale, or it sits like a full stop. */
.tk-badge.tk-letter, .tk-badge .tk-letter {
  font-size: var(--fs-xs);
  font-weight: var(--fw-head);
  letter-spacing: -0.02em;
}
.tk-badge img {
  width: 100%; height: 100%; object-fit: cover; border-radius: 50%;
  /* Transparent, not --bg-elevated. screener.css gives the logo a solid
     backing, which in light theme is #F5F5F5: a logo published with a
     transparent background then renders as a plain grey disc, and it covers
     the coloured letter tile that exists precisely to be seen when the logo
     is not. Letting the transparency through means a partial logo sits on its
     own initials instead of on a blank. */
  background: transparent;
}

/* Two columns on a phone leaves ~171px a card, and three figures in 147px of
   usable width gives each label 49px — "Trustlines" does not fit and
   truncates to nonsense.

   This only ever applied to the three-figure row, which is the CHART card:
   Price, Trustlines, 24h volume. Trustlines is the one that goes, because on
   a token with a real market the price and the volume are what the card is
   for. (An earlier comment here claimed it dropped Supply, which is not even
   in this row — the quiet card's two figures live in .tk-foot-lg and are
   both kept. The rule was right and the reasoning was about a different
   card.)

   ...but the chart card is not the only three-figure row. A pool-quoted token
   with no accumulated history takes the no-chart layout with Price,
   Trustlines and Supply, in LARGER type, and :not(.tk-foot-lg) exempted it:
   it rendered "0.00…" / "191…" / "99.…" with the "Trustlines" label spilling
   into "Supply". explorer.js marks that case .tk-foot-3 so it drops the same
   column; at two columns the widest each figure has to carry is "440,000"
   and "999.99B", which fit. The quiet card's two figures stay plain
   .tk-foot-lg and keep both. */
@media (max-width: 560px) {
  .tk-foot:not(.tk-foot-lg) span:nth-child(2),
  .tk-foot-3 span:nth-child(2) { display: none; }
}

/* Says which ordering rule is in force. The list is shuffled until you type,
   and a list that silently changes how it is ordered is more confusing than
   one that never had an order at all. */
.xp-order {
  color: var(--text-4);
  font-size: var(--fs-sm);
}
.xp-order::before { content: '· '; }

/* ---------- The chart band ----------
   Every card carries one, so the grid keeps a single row height and the
   virtual list's arithmetic stays trivial. A token with no history gets a
   flat rule rather than an empty box: the slot is answered, not skipped. */
.tk-spark { height: 26px; flex: 0 0 26px; margin: 0; }
.tk-spark-svg { width: 100%; height: 26px; display: block; overflow: visible; }
.tk-spark-svg path {
  fill: none;
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.tk-spark-svg path.up { stroke: var(--up); }
.tk-spark-svg path.down { stroke: var(--down); }
.tk-spark-none {
  stroke: var(--text-4);
  stroke-width: 1;
  stroke-dasharray: 2 3;
  opacity: .45;
  vector-effect: non-scaling-stroke;
}
.tk-px { color: var(--text); }

/* The nav's call to action duplicates a link that is already in the nav, and
   at 375px the pair pushed "Trade" off the edge. The link stays; the button
   goes. */
@media (max-width: 560px) {
  .xp-body .ex-nav-r .btn-primary { display: none; }
}

/* ---------- The large figures block ----------
   Where there is no chart there is no chart band, and the 32px it would have
   taken goes here instead. Two or three facts, set at the size they deserve:
   on a token nobody has traded, "740.00B supply, 3 trustlines" IS the story,
   and it should not be whispered in 13px under an empty graph. */
.tk-foot-lg {
  flex: 1 1 auto;
  align-items: center;
  gap: var(--s4);
  margin-top: 0;
}
.tk-foot-lg i { font-size: var(--fs-2xs); line-height: 14px; }
.tk-foot-lg b {
  font-size: var(--fs-lg);
  line-height: 24px;
  letter-spacing: -0.015em;
  color: var(--text);
}
/* A quiet token's figures stay fully legible — the card recedes, the
   measurements do not. Only the logo and the border say "no market"; the
   evidence is never dimmed, because the evidence is the point. */
.tk-card[data-ev="quiet"] .tk-foot-lg b { color: var(--text-2); }

@media (max-width: 560px) {
  .tk-foot-lg { gap: var(--s3); }
  .tk-foot-lg b { font-size: var(--fs-md); line-height: 20px; }
}

/* The count rides inside the chip, dimmer than the label, so the row reads as
   four filters that happen to also be the summary — not as a stat strip that
   happens to be clickable. */
.xp-chip-n {
  font-variant-numeric: tabular-nums;
  opacity: .62;
  font-weight: var(--fw-body);
  margin-left: 1px;
}
.xp-chip.active .xp-chip-n { opacity: .72; }

/* ---------- Token detail ----------
   A sheet, not a page. The grid keeps its scroll position underneath, which
   matters when the order is random: navigating away and back would reshuffle
   and the token you were looking at would be gone. */
.xp-detail {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: none;
  background: var(--scrim);
  padding: var(--s6);
  overflow-y: auto;
}
.xp-detail.open { display: block; }
.xp-locked { overflow: hidden; }

.xp-sheet {
  position: relative;
  max-width: 460px;
  margin: 4vh auto;
  background: var(--bg-surface);
  border: 1px solid var(--border-bright);
  border-radius: var(--r-lg);
  padding: var(--s5);
  box-shadow: var(--shadow-lg);
}
.xp-close {
  position: absolute;
  top: 10px; right: 12px;
  width: 30px; height: 30px;
  border: 0; border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-3);
  font-size: 22px; line-height: 1;
  cursor: pointer;
}
.xp-close:hover { background: var(--bg-elevated); color: var(--text); }

.xp-sheet-head {
  display: flex; align-items: center; gap: var(--s3);
  padding-right: 34px;
  margin-bottom: var(--s4);
}
.xp-sheet-head .tk-badge { width: 40px; height: 40px; }
.xp-sheet-code {
  font-size: var(--fs-xl); font-weight: var(--fw-head);
  letter-spacing: -0.02em; line-height: 24px;
  word-break: break-all;
}
.xp-sheet-name { font-size: var(--fs-base); color: var(--text-4); margin-top: 2px; }

.xp-sheet-sec { border-top: 1px solid var(--border-soft); padding-top: var(--s3); margin-top: var(--s3); }
.xp-sheet-sec h4 {
  font-size: var(--fs-2xs); font-weight: var(--fw-label);
  text-transform: uppercase; letter-spacing: .09em;
  color: var(--text-4); margin: 0 0 var(--s2);
}
.xp-sheet-sec dl {
  display: grid; grid-template-columns: auto 1fr;
  gap: 5px var(--s4); margin: 0;
}
.xp-sheet-sec dt { font-size: var(--fs-base); color: var(--text-3); }
.xp-sheet-sec dd {
  margin: 0; text-align: right;
  font-size: var(--fs-base); font-weight: var(--fw-label);
  color: var(--text); font-variant-numeric: tabular-nums;
  word-break: break-word;
}
.xp-sheet-note {
  margin: var(--s2) 0 0;
  font-size: var(--fs-sm); line-height: 17px; color: var(--text-4);
}
.xp-addr {
  display: block;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--fs-xs); line-height: 17px;
  color: var(--text-2);
  word-break: break-all;
  background: var(--bg-input);
  border-radius: var(--r-sm);
  padding: 8px 10px;
}
.xp-out {
  display: inline-block; margin-top: var(--s2);
  font-size: var(--fs-base); color: var(--accent); text-decoration: none;
}
.xp-out:hover { text-decoration: underline; }

.tk-card { cursor: pointer; }

@media (max-width: 560px) {
  .xp-detail { padding: 0; }
  .xp-sheet { margin: 0; min-height: 100dvh; border-radius: 0; border: 0; }
}

/* The dataset's own numbers, stated as a sentence. Six stat tiles say the
   list is large; this says what it is. */
.xp-lede-n {
  color: var(--text-3);
  margin-bottom: var(--s3);
}
.xp-lede-n b {
  color: var(--text);
  font-weight: var(--fw-head);
  font-variant-numeric: tabular-nums;
}

/* The card is a button now, so it has to look focusable to a keyboard. */
.tk-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-color: var(--accent);
}
.xp-close:focus-visible, .xp-chip:focus-visible, .xp-shuffle:focus-visible,
.xp-more:focus-visible, .xp-out:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* A link that drives the grid rather than the browser. Styled as a link
   because that is what it behaves like, but it is a button because it does
   not go anywhere. */
.xp-link {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--accent);
  cursor: pointer;
  text-align: right;
}
.xp-link:hover { text-decoration: underline; }
.xp-sheet-dupe {
  margin: 0 0 var(--s2);
  font-size: var(--fs-base);
  line-height: 19px;
  color: var(--text-2);
}
.xp-sheet-dupe b { color: var(--text); font-weight: var(--fw-label); }

/* Random is the default and the point of the page; this is the escape hatch
   for when you want the list to answer a question instead of surprise you. */
.xp-sortwrap { display: inline-flex; align-items: center; gap: 6px; }
.xp-sortlab { font-size: var(--fs-sm); color: var(--text-4); white-space: nowrap; }
.xp-sort {
  height: 32px;
  padding: 0 8px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: var(--bg-input);
  color: var(--text);
  font: var(--fw-body) var(--fs-base)/1 inherit;
  cursor: pointer;
}
.xp-sort:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

@media (max-width: 900px) {
  .xp-sortwrap { order: 5; }
  .xp-sortlab { display: none; }
  .xp-sort { flex: 1 1 auto; }
}

/* ---------- Phone: get to the tokens sooner ----------
   The stat block and the toolbar together were spending 520 of an 812px
   screen before the first card. Two of the six tiles say what the sentence
   above the grid already says in words — the domain count and the issuing
   accounts — so on a phone they go, and the count and the order control
   share a row instead of taking one each. About 165px back. */
@media (max-width: 620px) {
  .xp-stat:nth-child(1), .xp-stat:nth-child(2) { display: none; }
  /* The count line is nowrap and runs to 428px — "56,908 of 56,908 tokens
     12.16M XLM traded across 947 measured". On a 390px phone that one line
     set the width of the whole toolbar, which in turn set the width of
     .xp-filters (width:100%), so the chip strip could never scroll inside
     itself and the PAGE scrolled sideways instead. Letting the count wrap
     fixes the strip too, because the strip was only ever as wide as this. */
  .xp-count { order: 4; width: auto; white-space: normal; min-width: 0; flex: 1 1 auto; }
  .xp-sortwrap { order: 5; margin-left: auto; }
  .xp-sort { flex: 0 0 auto; }
}

/* On a phone the lede's middle two sentences are the same argument the
   disclosure below makes at length, and they were costing three lines above
   the fold. The opening sentence and the numbers stay; the reasoning is one
   tap away where it always was. */
@media (max-width: 620px) {
  .xp-wide { display: none; }
}

/* ---------- Skeleton ----------
   The shape of what is coming. Deliberately wordless and numberless: a
   placeholder that showed plausible figures would be a lie for two seconds,
   and on a page whose whole argument is about the difference between claimed
   and measured, that is not a lie worth telling for a loading state. */
.tk-skel {
  height: var(--tk-h);
  /* A surface, not the page. In light theme --bg-elevated is #F5F5F5, which
     is exactly --bg-base, so bars drawn on a transparent card were invisible
     — a loading state that loads nothing. The card takes the panel colour and
     the bars take the hover colour, which has contrast against it in both
     themes. */
  background: var(--bg-surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--r-md);
  padding: 11px 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.tk-skel-top { display: flex; align-items: center; gap: 9px; }
.tk-skel-badge { width: 30px; height: 30px; flex: 0 0 auto; }
.tk-skel-lines { display: flex; flex-direction: column; gap: 5px; flex: 1 1 auto; }
.tk-skel-foot { display: flex; gap: var(--s4); margin-top: auto; }
.tk-skel-foot i { width: 46px; height: 22px; }
.tk-skel i, .tk-skel-badge {
  display: block;
  height: 9px;
  border-radius: 3px;
  background: var(--bg-hover);
  animation: tkPulse 1.4s ease-in-out infinite;
}
/* After the shared rule, or the 3px above wins and the badge is a square —
   the one element on the placeholder whose shape people actually read. */
.tk-skel-badge { border-radius: 50%; }
.tk-skel-bar { width: 64%; }
@keyframes tkPulse { 0%, 100% { opacity: .45; } 50% { opacity: .9; } }

/* ---------- Motion ----------
   Everything that moves here is decoration: a shuffle spin, a card lifting a
   pixel on hover, a skeleton breathing. None of it carries meaning, so all of
   it goes when the reader has said they do not want it. */
@media (prefers-reduced-motion: reduce) {
  .tk-card, .xp-chip, .xp-shuffle { transition: none; }
  .tk-card:hover { transform: none; }
  .xp-shuffle.spin svg { animation: none; }
  .tk-skel i, .tk-skel-badge { animation: none; opacity: .6; }
}

/* ===========================================================
   LIGHT-THEME INK

   The house up/down/accent colours are tuned for the terminal, which is
   dark, and they are excellent there — 7.23:1, 4.93:1, 8.23:1 on the
   panel. On white they are not text colours at all:

     --up      #2EBD85   2.40:1
     --down    #F6465D   3.53:1
     --accent  #E4A82A   2.11:1     (AA wants 4.5)

   The terminal's own book and tape are a separate argument — those are
   dense financial figures on a dark ground and Binance's palette is the
   reference. This page is prose and cards, half of it read on white, so
   it takes darker inks of the same hues rather than bending the tokens
   the whole product shares.

   Sparkline strokes are graphics, not text, so 3:1 is the bar; the
   stroke green is lifted just past it and the red already clears it.
   =========================================================== */
/* This block used to key off :root:not([data-theme="dark"]) plus a
   prefers-color-scheme query, which is a theming model this app does not
   use. The app's default with NO data-theme attribute is DARK (base.css
   :root is the dark palette; the inline bootstrap stamps the attribute only
   for light). So :not([data-theme="dark"]) matched precisely the default
   dark state, and a first-time visitor on a light OS got these light inks
   painted on the dark ground — #1E7D58 on #181A20 is 3.42:1.

   base.css now carries --up-text / --down-text per theme for exactly this
   job, so the page-local inks are aliases and the broken selector is gone.
   Only the sparkline strokes stay distinct: they are graphics at a 3:1 bar,
   so the light green is lifted rather than darkened to text weight. */
:root {
  --xp-up-ink: var(--up-text);
  --xp-down-ink: var(--down-text);
  --xp-accent-ink: var(--accent);
  --xp-spark-up: var(--up);
  --xp-spark-down: var(--down);
}
:root[data-theme="light"] {
  --xp-spark-up: #29A876;    /* 3.02:1, graphics threshold */
  --xp-spark-down: #F6465D;  /* 3.53:1 */
}

.tk-vol-live { color: var(--xp-up-ink); }
.tk-sig-live { color: var(--xp-up-ink); }
.xp-more, .xp-out, .xp-link { color: var(--xp-accent-ink); }
.xp-more:hover, .xp-out:hover, .xp-link:hover { color: var(--xp-accent-ink); }
.tk-spark-svg path.up { stroke: var(--xp-spark-up); }
.tk-spark-svg path.down { stroke: var(--xp-spark-down); }

/* ---------- A to Z rail ----------
   Sticky under the toolbar while the list is alphabetical. Twenty-seven
   equal cells so the rail is the same width whatever is in it. */
.xp-az {
  position: sticky;
  /* Sticks BELOW the toolbar, and the toolbar's height is what decides where
     that is. A hardcoded 56px was right at one breakpoint and wrong at the
     rest — on a phone the toolbar wraps to 126px, so the rail stuck at 0 and
     the two overlapped. The toolbar measures itself into --xp-tb-h. */
  top: var(--xp-tb-h, 56px);
  z-index: 4;
  display: grid;
  grid-template-columns: repeat(27, minmax(0, 1fr));
  gap: 2px;
  background: var(--bg-base);
  padding: 0 0 var(--s2);
}
.xp-az-l {
  height: 26px;
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  background: var(--bg-surface);
  color: var(--text-2);
  font: var(--fw-label) var(--fs-sm)/1 inherit;
  cursor: pointer;
  padding: 0;
}
.xp-az-l:hover { border-color: var(--accent); color: var(--accent); }
.xp-az-l:disabled { color: var(--text-4); opacity: .7; cursor: default; }
.xp-az-l:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
@media (max-width: 620px) {
  /* Two rows of letters, and NOT sticky. The phone toolbar wraps to three
     rows; pinning the rail under it would hold 224px of an 812px screen
     permanently, to keep a control that is used once and then wanted out of
     the way. It scrolls off with the rest and the toolbar keeps the pin. */
  .xp-az { position: static; grid-template-columns: repeat(14, minmax(0, 1fr)); }
  .xp-az-l { height: 30px; }
}

/* ---------- Let sticky stick ----------
   The terminal sets overflow-x: hidden on html AND body for its phone
   layout. When both carry an overflow value, body stops sharing the
   viewport's scroll and becomes a scroll container of its own — and a
   position: sticky toolbar inside it then has no viewport edge to stick to,
   so on a phone the toolbar and the A-Z rail simply scrolled away with the
   page. Desktop was unaffected because that rule is phone-only.

   This page never overflows horizontally (measured at 375, 780, 1200 and
   1400px), so it does not need the guard. Restoring normal overflow on both
   elements hands scrolling back to the viewport and sticky works again. */
html:has(body.xp-body), body.xp-body { overflow: visible; overflow-x: visible; }

/* ---------- [hidden] has to actually hide ----------
   The card pool parks fields it does not need by setting el.hidden, which
   relies on the browser's [hidden] { display: none }. That rule is a single
   attribute selector, and .tk-foot span { display: flex } outranks it — so a
   parked figure kept its flex column and squeezed the two real ones. It was
   on 38 of 40 cards.

   Anything the pool toggles is listed here at a specificity that wins. Adding
   a display rule to one of these elements without adding it here again is the
   way this comes back. */
.tk-foot > span[hidden],
/* The parked cards themselves were missing from this list: .tk-card is
   display:flex, so a parked card stayed laid out (all 10 parked cards on the
   live page computed display:flex), stayed in the tab order with tabindex 0,
   and was read out with a token no longer on screen. */
.tk-card[hidden],
.tk-name[hidden],
.tk-spark[hidden],
.tk-badge img[hidden] { display: none; }

/* ---------- What to do with a token ----------
   The sheet answers "what is this". These answer "and then what". */
.xp-acts { display: flex; gap: var(--s2); flex-wrap: wrap; }
.xp-act {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 38px;
  padding: 0 var(--s4);
  border-radius: var(--r-sm);
  border: 1px solid var(--border-bright);
  color: var(--text);
  background: var(--bg-elevated);
  font-size: var(--fs-base);
  font-weight: var(--fw-label);
  text-decoration: none;
  flex: 1 1 auto;
}
.xp-act:hover { border-color: var(--accent); color: var(--accent); }
.xp-act-primary {
  background: var(--accent-fill);
  border-color: var(--accent-fill);
  color: var(--on-accent-fill);
}
.xp-act-primary:hover { background: var(--accent-alt); border-color: var(--accent-alt); color: var(--on-accent); }
.xp-act:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.xp-acts-none { margin: 0; }

/* The traded total for whatever is on screen. The stat bar's figure is the
   whole index and never moves; this one answers "and this lot?" */
.xp-vol { color: var(--xp-up-ink); font-variant-numeric: tabular-nums; }
.xp-vol::before { content: '· '; color: var(--text-4); }


/* The claimed-domain disagreement. Not an error state — most tokens agree,
   and the ones that do should look unremarkable. */
.xp-differs { color: var(--warn); font-weight: var(--fw-head); }
.xp-none    { color: var(--text-4); font-style: italic; }
.xp-same    { color: var(--text-2); }
/* A contested issuer's declared home domain. It agrees with nothing and
   disagrees with nothing, because the row above it names no claimant at all —
   .xp-same would claim an agreement and .xp-differs did claim a disagreement,
   putting the only on-chain fact on the sheet in the warning colour. Stated,
   with the weight that being the one verifiable line here earns it. */
.xp-declared { color: var(--text-2); font-weight: var(--fw-label); }
.xp-sheet-warn {
  margin: 8px 0 0; padding: 8px 10px;
  background: var(--warn-dim); border-left: 2px solid var(--warn);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-size: var(--fs-sm); line-height: 17px; color: var(--text-2);
}
.xp-sheet-warn b { color: var(--text); font-weight: var(--fw-label); }


/* A price quoted by a pool holding a few XLM is not the same kind of fact as
   a traded price, and the card should not print them identically. Muted and
   italic rather than warning-coloured: this is most of the priced set, so a
   red flag on 386 cards would just become wallpaper. The detail sheet gives
   the actual number. */
.tk-px-thin { color: var(--text-3) !important; font-style: italic; }
.tk-px-thin::after {
  content: '~'; margin-left: 2px; font-style: normal;
  color: var(--text-4); font-size: 0.85em;
}

/* A code no domain lists. It carries no borrowed name, and says so in the
   quietest voice on the card: this is an absence, not an alert. */
.tk-dom.tk-dom-off { color: var(--text-4); font-style: italic; direction: ltr; }
