/* ============================================================================
   exotic — theme map · thememap.css
   Pick a theme, see its assets as a calm zoomable treemap. Letter-page
   language: paper surfaces, hairlines, mono data voice. Tiles tint from the
   brand ramp (--g1..6); box size = recommended weight in the bucket.
   ========================================================================== */

/* the map is the stage: the section breaks the page rails (like the globe)
   and the canvas takes the viewport — head + chips + map fill one screen */
.tm-wrap{ max-width:none; }
.tm-layout{ display:grid; grid-template-columns:minmax(0,1fr);
  align-items:start; margin-top:clamp(16px, 2.2vw, 24px); }
.tm-main{ min-width:0; }

/* ---------- shared tag chips (both views) + view toggle on the right ---------- */
.tm-tagrow{ display:flex; align-items:flex-end; gap:14px; margin:0 0 14px; }
.tm-tags{ margin:0; align-items:center; flex:1; min-width:0; }
.tm-tags .pc{ margin-left:8px; font-size:10px; letter-spacing:.02em; }
/* direction on an ink fill speaks the shared on-ink tokens (D04) */
.tm-tags button[aria-pressed="true"] .pc.up{ color:var(--up-on-ink); }
.tm-tags button[aria-pressed="true"] .pc.down{ color:var(--down-on-ink); }
/* the "+N more" chip — quiet, dashed, never reads as a filter itself */
.tm-tags .t-more{ border-style:dashed; color:var(--muted); }
.tm-tags .t-more:hover{ color:var(--ink); border-color:var(--ink); }

/* phones: the chip wall becomes one thumb-scrollable line; tapping "+N more"
   opens the full vocabulary as a wrapped block */
@media (max-width:600px){
  .tm-tags{ flex-wrap:nowrap; overflow-x:auto; padding:2px 2px 6px; margin:-2px -2px 0;
    scrollbar-width:none;
    mask-image:linear-gradient(90deg, #000 calc(100% - 28px), transparent 100%);
    -webkit-mask-image:linear-gradient(90deg, #000 calc(100% - 28px), transparent 100%); }
  .tm-tags::-webkit-scrollbar{ display:none; }
  .tm-tags.expanded{ flex-wrap:wrap; overflow-x:visible;
    mask-image:none; -webkit-mask-image:none; }
}

/* ---------- view toggle pill ---------- */

/* ---------- card view: one grid, everything at once ---------- */
.mkt-grid{ display:grid; grid-template-columns:repeat(auto-fill, minmax(186px, 1fr)); gap:12px; align-items:stretch; }
.mkt-grid .mcard{ width:auto; min-height:150px; }
.mkt-grid .mcard.thesis{ grid-column:span 2; }
.mkt-grid .mkt-empty{ grid-column:1 / -1; text-align:center; padding:34px 0; border:1px dashed var(--line); border-radius:14px; }
@media (max-width:560px){ .mkt-grid .mcard.thesis{ grid-column:span 1; } }

/* collapsed to ~3 rows; the tail softly blurs away until expanded.
   padding + negative margin keep hover lifts/shadows from getting clipped */
.mkt-clip{ position:relative; max-height:532px; overflow:hidden; padding:8px 8px 0; margin:-8px -8px 0; }
.mkt-clip.clipped::after{ content:""; position:absolute; left:0; right:0; bottom:0; height:130px; pointer-events:none;
  backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
  background:linear-gradient(180deg, transparent, var(--paper-bg, var(--paper)) 96%);
  -webkit-mask-image:linear-gradient(180deg, transparent, #000 55%);
  mask-image:linear-gradient(180deg, transparent, #000 55%); }
.mkt-clip.open{ max-height:none; }
.mkt-more{ display:flex; justify-content:center; position:relative; z-index:2; margin-top:-16px; }
.mkt-clip.open + .mkt-more{ margin-top:16px; }
.mkt-more[hidden]{ display:none; }
.mkt-more button{ font-family:var(--font-num); font-size:10.5px; letter-spacing:.08em; text-transform:uppercase;
  padding:9px 18px; border-radius:999px; border:1px solid var(--line); background:var(--card); color:var(--ink);
  cursor:pointer; transition:border-color var(--t-fast) var(--ease-out); box-shadow:var(--elev-2); }
.mkt-more button:hover{ border-color:var(--ink); }

/* ---------- view toggle (map ⇄ simple) in the board head ---------- */
.tm-view{ display:flex; gap:3px; padding:3px; border:1px solid var(--line); border-radius:999px;
  background:var(--card); flex:0 0 auto; }
.tm-view button{ display:grid; place-items:center; padding:7px 13px; border:0; border-radius:999px;
  background:none; color:var(--muted); cursor:pointer;
  transition:color var(--t-fast) var(--ease-out),background var(--t-fast) var(--ease-out),
    transform var(--t-fast) var(--ease-out); }
.tm-view button svg{ display:block; }
.tm-view button:hover{ color:var(--ink); }
.tm-view button[aria-pressed="true"]{background:var(--card);color:var(--ink);box-shadow:var(--crisp)}

/* empty state when tags intersect to nothing */
.tm-empty{ position:absolute; inset:0; display:grid; place-items:center; font-size:13.5px; color:var(--muted);
  border:1px dashed var(--line); border-radius:14px; }
.tm-pane[hidden]{ display:none; }
.tm-pane .mkt-lane:first-of-type{ margin-top:clamp(16px, 2.4vw, 24px); }

/* ---------- the map ---------- */
/* full screen: the head (~46px) + tag row (~50px) + section breathing room
   (~64px) sit above; the canvas owns the rest of the viewport.
   Overflow stays visible so a leaning edge-tile's boundary and painted
   edge aren't sheared off at the canvas line — clipping is only needed
   while the zoom transform blows the world up (and under Explore's
   full-canvas ticker sheet). */
.tm-canvas{ position:relative; height:max(480px, calc(100svh - 160px)); }
.tm-canvas:has(.tm-world.zooming), .tm-canvas.detail-on{ overflow:hidden; }
.tm-world{ position:absolute; inset:0; transform-origin:0 0; }
.tm-world.zooming{ transition:transform .34s cubic-bezier(.5,.08,.24,1); will-change:transform; }
.tm-world.zooming .tm-t > span, .tm-world.zooming .tm-gh, .tm-world.zooming .tm-agg span{ opacity:0; }
.tm-t > span, .tm-gh, .tm-agg span{ transition:opacity .18s; }

/* group = a decisive FRAME, not a card — flat ground, one confident border
   tone shared by every level (the compounding gray hairlines + wells +
   shadows read as mush; frames read as a map) */
.tm-g{ position:absolute; border:1px solid color-mix(in oklab, var(--ink) 34%, transparent);
  border-radius:10px; background:none; overflow:hidden; }
body.theme-dark .tm-g{ border-color:oklch(1 0 0 / .22); }
.tm-gh{ position:absolute; left:0; right:0; top:0; display:flex; align-items:center; gap:8px;
  padding:0 12px; border:0; background:none; cursor:pointer; text-align:left; min-width:0; }
.tm-gh:hover .gn{ text-decoration:underline; text-underline-offset:3px; }
.tm-gh .gn{ font-family:var(--font-num); font-size:10.5px; font-weight:600; letter-spacing:.1em;
  text-transform:uppercase; color:var(--ink-2); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.tm-gh .gc{ font-family:var(--font-num); font-size:10px; color:var(--muted); flex:0 0 auto; }
.tm-gh .gv{ font-family:var(--font-num); font-size:10.5px; letter-spacing:.02em; margin-left:auto; white-space:nowrap; }
.tm-gb{ position:absolute; inset:0; }

/* root box header — the level you're inside; click it to go back up */
.tm-gh.root{ cursor:pointer; }
.tm-gh.root .gn{ font-size:11.5px; }
.tm-gh.root:hover .gn{ text-decoration:none; }

/* aggregated group (too small to open) — zooms on click */
.tm-agg{ position:absolute; display:flex; flex-direction:column; align-items:flex-start; justify-content:center;
  gap:2px; padding:8px 10px; border:1px solid color-mix(in oklab, var(--ink) 26%, transparent);
  border-radius:8px; background:var(--card); cursor:pointer; overflow:hidden; text-align:left; }
.tm-agg:hover{ border-color:color-mix(in oklab, var(--ink) 40%, transparent); }
.tm-agg .an{ font-family:var(--font-num); font-size:10.5px; font-weight:600; letter-spacing:.07em;
  text-transform:uppercase; max-width:100%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.tm-agg .av{ font-family:var(--font-num); font-size:10px; opacity:.7; white-space:nowrap; }
/* narrow group headers: name first — count and pct step aside (js adds .slim) */
.tm-gh.slim .gc,.tm-gh.slim .gv{ display:none; }

/* ---------- cards in the back: the ancestor trail, stacked ----------
   Zoomed into a subtree, every parent level is a card BEHIND the front
   one. Only its top edge peeks out — and instead of ending, its sides
   dissolve before the canvas edge: there's more on that level than this
   view holds. Flat like every other frame on the map (no shadow — the
   stacked up-shadows read as a dirty wash); the label whispers until the
   pointer asks. Click the edge to travel back up. */
/* 40px padding = the front card's 28px inset + its header's 12px, so the
   trail's labels and the front title stack on one vertical line (values
   mirror it on the right); the 32px fade lives in the margin OUTSIDE the
   front card's edge */
.tm-back{ position:absolute; display:flex; align-items:flex-start; gap:10px; padding:0 40px;
  border:1px solid color-mix(in oklab, var(--ink) 26%, transparent);
  border-radius:12px 12px 0 0; background:none; cursor:pointer; text-align:left; min-width:0;
  -webkit-mask-image:linear-gradient(90deg, transparent, #000 32px, #000 calc(100% - 32px), transparent);
  mask-image:linear-gradient(90deg, transparent, #000 32px, #000 calc(100% - 32px), transparent); }
.tm-back > span{ line-height:29px; transition:opacity .18s, color .15s; }
.tm-back .bn{ font-family:var(--font-num); font-size:10.5px; font-weight:600; letter-spacing:.1em;
  text-transform:uppercase; color:var(--muted);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.tm-back .bc{ font-family:var(--font-num); font-size:10px; color:var(--muted); opacity:.65; }
.tm-back .bv{ font-family:var(--font-num); font-size:10.5px; letter-spacing:.02em;
  margin-left:auto; opacity:.7; }
.tm-back:hover .bn, .tm-back:focus-visible .bn{ color:var(--ink); }
.tm-back:hover .bv, .tm-back:focus-visible .bv{ opacity:1; }
body.theme-dark .tm-back{ border-color:oklch(1 0 0 / .16); }
.tm-world.zooming .tm-back > span{ opacity:0; }
/* "+N more" — collapsed unreadable leaf tiles; dashed, centered, zooms in;
   ghost on the well, not a card (it's an overflow, not a market) */
.tm-agg.tm-more{ align-items:center; justify-content:center; text-align:center;
  border-style:dashed; background:none; color:var(--muted); }
.tm-agg.tm-more .av{ font-size:9px; }

/* ---------- leaf ticker cell — FLAT, the Paradigm read: a quiet semantic
   fill (direction, whisper-strength), one decisive border, no shadow.
   Hover deepens the fill and commits the border instead of lifting. ---------- */
.tm-t{ position:absolute; display:flex; flex-direction:column; align-items:stretch; gap:6px;
  padding:11px 12px; border:1px solid color-mix(in oklab, var(--ink) 26%, transparent);
  border-radius:8px; background:var(--card);
  cursor:pointer; overflow:hidden;
  text-align:left; transition:background .15s, border-color .15s; }
.tm-t.dir-up{ background:color-mix(in oklab, var(--up) 5%, var(--card)); }
.tm-t.dir-down{ background:color-mix(in oklab, var(--down) 5%, var(--card)); }
/* hover keeps the resting border — the lift + painted edge (below) carry
   the affordance now; the ink slab read as bold and dead */
.tm-t.dir-up:hover{ background:color-mix(in oklab, var(--up) 10%, var(--card)); }
.tm-t.dir-down:hover{ background:color-mix(in oklab, var(--down) 10%, var(--card)); }
.tm-t.on{ outline:2px solid var(--ink); outline-offset:-2px; z-index:2; }
.tm-t .th{ display:flex; align-items:center; gap:9px; min-width:0; }
.tm-t .tl{ width:28px; height:28px; flex:0 0 auto; border-radius:50%;
  background:var(--surface-2, oklch(0.965 0.005 255));
  box-shadow:inset 0 0 0 1px oklch(0 0 0 / .06); display:grid; place-items:center; overflow:hidden; }
.tm-t .tl img{ width:100%; height:100%; object-fit:cover; border-radius:50%; }
/* local marks are lockups, not circle icons — contain on a quiet field;
   .inv = white-on-transparent marks that need the ink field to read */
.tm-t .tl img.lm{ object-fit:contain; padding:13%; background:#fff; }
.tm-t .tl img.lm.inv{ background:#1c1c20; }
.tm-t .tl .mono-txt{ font-family:var(--font-num); font-size:10.5px; font-weight:600; color:var(--ink-2); }
.tm-t .tt{ display:flex; flex-direction:column; gap:1px; min-width:0; }
.tm-t .tnm{ font-size:13px; font-weight:600; letter-spacing:-.01em; line-height:1.2;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.tm-t .tsx{ font-family:var(--font-num); font-size:9.5px; letter-spacing:.04em; opacity:.62;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.tm-t .row{ display:flex; align-items:center; gap:8px; margin-top:auto; }
/* the change wears the app's pill, not naked text — visible from the price
   rung up (the ladder promised "price" means price AND direction) */
.tm-t .tv{ font-family:var(--font-num); font-size:11px; font-weight:600; margin-left:auto;
  padding:2px 7px; border-radius:6px; white-space:nowrap; }
/* direction is never tint-alone at rest — Home's map keeps the delta pill
   visible, same law as Explore's .xp-map rule (BL12) */
#markets .tm-t .tv{ opacity:1; }
.tm-t .tv.up{ background:color-mix(in oklab, var(--up) 13%, transparent); }
.tm-t .tv.down{ background:color-mix(in oklab, var(--down) 13%, transparent); }
/* the card anatomy from the name rung up: price block top-right, a
   full-bleed spark band through the middle (dashed baseline = the open),
   fact rows, then the story (summary · news) as the tile keeps growing.
   Facts/story come from page hooks (EXOTIC_TM_TILEINFO / _TILESTORY)
   with engine fallbacks, so Home and Explore both fill their space. */
.tm-t .tpx{ font-family:var(--font-num); font-variant-numeric:tabular-nums; font-size:12px; font-weight:500; }
.tm-t .th .tpr{ margin-left:auto; flex:0 0 auto; display:flex; flex-direction:column; align-items:flex-end; gap:4px; }
.tm-t .th .tpr .tpx{ font-size:14px; letter-spacing:-0.01em; }
/* the band bleeds to the card edges — a hero, not a floating squiggle */
.tm-t .tsp{ flex:1 1 auto; display:flex; align-items:flex-end; min-height:0;
  margin:10px -12px 10px; opacity:.9; }
.tm-t .tsp svg{ width:100%; height:100%; max-height:64px; display:block; }
.tm-t .tinf{ display:flex; flex-direction:column; min-width:0; }
.tm-t .tinf .kv{ display:flex; align-items:baseline; justify-content:space-between; gap:12px; min-width:0;
  padding:6px 0; }
.tm-t .tinf .kv + .kv{ border-top:1px solid oklch(0 0 0 / .05); }
.tm-t .tinf .kv i{ font-style:normal; font-size:11px; color:var(--muted); white-space:nowrap; }
.tm-t .tinf .kv b{ font-family:var(--font-num); font-weight:450; font-variant-numeric:tabular-nums; font-size:11.5px;
  color:var(--ink-2); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.tm-t .tinf .kv b.up{ color:var(--up); } .tm-t .tinf .kv b.down{ color:var(--down); }
body.theme-dark .tm-t .tinf .kv + .kv{ border-top-color:oklch(1 0 0 / .07); }
.tm-t.sz-xl .tinf .kv i{ font-size:11.5px; }
.tm-t.sz-xl .tinf .kv b{ font-size:12.5px; }
/* deep and very tall tiles (the squarifier's skyscrapers): the band rises
   with the tile — but a chart keeps a chart's PROPORTIONS. On towers the
   spark floats centered in the middle band, capped by aspect so the walk
   never stretches into a seismograph (max-height:none shipped once: a
   700px-tall spark read as noise), and the air frames it evenly. */
.tm-t.deep .tsp svg{ max-height:150px; }
.tm-t.tall .tsp{ align-items:center; }
.tm-t.tall .tsp svg{ height:auto; aspect-ratio:2.3 / 1; max-height:300px; }

/* ---------- the story rungs: summary prose, then headline rows ---------- */
.tm-t .tsum{ border-top:1px solid oklch(0 0 0 / .05); padding-top:8px;
  font-size:11.5px; line-height:1.55; color:var(--ink-2); text-wrap:pretty;
  display:-webkit-box; -webkit-line-clamp:var(--cl, 2); -webkit-box-orient:vertical; overflow:hidden; }
.tm-t .tnws{ display:flex; flex-direction:column; gap:7px;
  border-top:1px solid oklch(0 0 0 / .05); margin-top:2px; padding-top:8px; }
.tm-t .tnws .nw{ display:flex; flex-direction:column; gap:2px; min-width:0; text-align:left; }
.tm-t .tnws .nw b{ font-size:11.5px; font-weight:550; line-height:1.4; letter-spacing:-.005em;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.tm-t .tnws .nw i{ font-style:normal; font-family:var(--font-num); font-size:9.5px;
  letter-spacing:.04em; color:var(--muted); }
body.theme-dark .tm-t .tsum, body.theme-dark .tm-t .tnws{ border-top-color:oklch(1 0 0 / .07); }
.tm-t.sz-xl .tsum{ font-size:12.5px; }
.tm-t.sz-xl .tnws .nw b{ font-size:12px; }

/* ---------- badge tiles — every rung below the name ----------
   The logo IS the smallest card; ticker, price and chart join as the tile
   grows, centered as a stack. Short tiles (.strip) lay the same rungs in
   one row. The name never renders here — hover/tap carries it. */
.tm-t.m-badge{ align-items:center; justify-content:center; gap:4px; padding:6px; text-align:center; }
/* if the stack still overflows, clip the LAST rung, never the first —
   safe center falls back to start-alignment when there's no room */
.tm-t.m-badge{ justify-content:safe center; }
.tm-t.m-badge.tiny{ padding:2px; gap:2px; }
.tm-t.m-badge .tl{ width:26px; height:26px; }
.tm-t.m-badge .tsx{ max-width:100%; }
.tm-t.m-badge .bp{ display:flex; flex-wrap:wrap; align-items:baseline; justify-content:center;
  gap:2px 7px; max-width:100%; }
.tm-t.m-badge .bp .tpx{ font-size:11px; }
.tm-t.m-badge .tv{ margin-left:0; padding:1px 6px; font-size:10px; }
.tm-t.m-badge .tsp{ align-self:stretch; flex:1 1 auto; margin:2px -6px -6px; }
.tm-t.m-badge.sz-xs .tl{ width:20px; height:20px; }
.tm-t.m-badge.sz-xs .tl .mono-txt{ font-size:8.5px; }
.tm-t.m-badge.strip{ flex-direction:row; justify-content:flex-start; text-align:left;
  gap:7px; padding:6px 9px; }
.tm-t.m-badge.strip .bp{ margin-left:auto; flex-wrap:nowrap; }

/* size steps — badges own their padding/anatomy above; these set type */
.tm-t.sz-xs .tsx{ font-size:9px; }
.tm-t.sz-s:not(.m-badge){ padding:9px 10px; gap:4px; }
.tm-t.sz-s:not(.m-badge) .tl{ width:22px; height:22px; }
.tm-t.sz-s .tl .mono-txt{ font-size:9px; }
.tm-t.sz-s .tnm{ font-size:12px; }
.tm-t.sz-l{ padding:14px 16px; }
.tm-t.sz-l .tl{ width:34px; height:34px; } .tm-t.sz-l .tnm{ font-size:15px; }
.tm-t.sz-l .tsx{ font-size:10.5px; }
.tm-t.sz-l .tsp{ margin-inline:-16px; }
.tm-t.sz-l .th .tpr .tpx{ font-size:15px; }
.tm-t.sz-xl{ padding:18px 20px; }
.tm-t.sz-xl .tl{ width:42px; height:42px; } .tm-t.sz-xl .tl .mono-txt{ font-size:13px; }
.tm-t.sz-xl .tnm{ font-size:18px; } .tm-t.sz-xl .tsx{ font-size:11px; }
.tm-t.sz-xl .tsp{ margin-inline:-20px; }
.tm-t.sz-xl .th .tpr .tpx{ font-size:17px; }
/* price + chip up top read from the first row — big headers top-align */
.tm-t.sz-l .th, .tm-t.sz-xl .th{ align-items:flex-start; }
.tm-t.sz-l .th .tt, .tm-t.sz-xl .th .tt{ margin-top:1px; }

/* ---------- monochrome surfaces: color belongs to the DATA ----------
   The bucket tints are gone (they read as decoration and fought every
   element placed on them). Identity comes from grouping; depth from the
   well ⇄ card step; the only hue on a tile is semantic — the up/down
   pill and the spark. The .c1–.c6 classes still land on tiles (the data
   layer stamps them) but paint nothing. */
.tm-t, .tm-agg{ color:var(--ink); }
body.theme-dark .tm-t{ border-color:oklch(1 0 0 / .18); }
/* dark tile bases ride the ONE dark palette's card token (hue 262, D02) */
body.theme-dark .tm-t.dir-up{ background:color-mix(in oklab, var(--up) 8%, var(--card)); }
body.theme-dark .tm-t.dir-down{ background:color-mix(in oklab, var(--down) 8%, var(--card)); }
body.theme-dark .tm-t:hover{ border-color:oklch(1 0 0 / .65); }
body.theme-dark .tm-t.dir-up:hover{ background:color-mix(in oklab, var(--up) 14%, var(--card)); }
body.theme-dark .tm-t.dir-down:hover{ background:color-mix(in oklab, var(--down) 14%, var(--card)); }
body.theme-dark .tm-agg{ background:var(--card); border-color:oklch(1 0 0 / .14); }
body.theme-dark .tm-agg.tm-more{ background:none; }
body.theme-dark .tm-t .tl{ background:oklch(0 0 0 / .28); box-shadow:inset 0 0 0 1px oklch(1 0 0 / .1); }
body.theme-dark .tm-t .tl .mono-txt{ color:oklch(0.9 0.01 262); }
.up{ color:var(--up); } .down{ color:var(--down); }

/* the hovered surface leans back into the pointer (js/kit/thememap-hover.js
   drives --trx/--try/--tl with the trade-cards spring) — it lifts a touch
   and its shadow deepens with the lift, so "raised" reads as "selectable".
   Every node in the tree speaks this language: leaf tiles and "+N more" as
   themselves, a group frame (.tm-g) as one plate when its header is hovered
   — the whole subtree in focus moves. Unscoped on purpose: this is the
   map's shared feel, Home and Explore alike. No boundary treatment — the
   lean, the lift shadow and the deepened fill are the whole affordance. */
.tm-t.tilting, .tm-agg.tilting, .tm-g.tilting{
  transform:perspective(800px) rotateX(var(--trx, 0deg)) rotateY(var(--try, 0deg))
    scale(calc(1 + var(--tl, 0) * 0.016));
  box-shadow:0 calc(var(--tl, 0) * 12px) calc(var(--tl, 0) * 26px) calc(var(--tl, 0) * -12px)
    oklch(0.3 0.02 260 / .35);
  z-index:3; will-change:transform; }
/* a leaning frame needs ground under it — the transparent group picks up
   the paper so its children don't read as floating off the page */
.tm-g.tilting{ background:var(--paper); }
/* released (pointer moved on): the spring lets go and a transition carries
   the surface home — vars are removed, so this eases back to flat */
.tilting.settling{
  transition:transform .34s var(--ease-emph), box-shadow .34s var(--ease-emph); }
@media (prefers-reduced-motion: reduce){
  .tm-t.tilting, .tm-agg.tilting, .tm-g.tilting{
    transform:none; box-shadow:none; background:none; }
}

/* ---------- hover card (js/kit/thememap-hover.js) ----------
   Explore's anatomy, the letter's data, a whisper of the trade-cards feel:
   the outer node only places; .hv-card is the object — it deals in with a
   small rise-and-settle and leans ±2° into the pointer's light (--rx/--ry),
   with a faint sheen at --mx/--my. Scoped to #markets: Explore styles its
   own hover in css/app/explore.css. */
#markets .tm-hover{ position:absolute; z-index:6; pointer-events:none; perspective:700px; }
#markets .tm-hover[hidden]{ display:none; }
#markets .hv-card{ position:relative; background:var(--card); border:1px solid var(--line);
  border-radius:14px; padding:13px 15px;
  box-shadow:var(--shadow-pop);
  transform:rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  opacity:0; translate:0 5px; scale:.97;
  transition:opacity .16s var(--ease-out),
    translate .24s var(--ease-emph), scale .24s var(--ease-emph); }
#markets .tm-hover.on .hv-card{ opacity:1; translate:0 0; scale:1; }
/* the ramp arrives only as light: a quiet gradient hairline on the rim */
#markets .hv-card::before{ content:""; position:absolute; inset:-1px; border-radius:inherit;
  padding:1px; background:linear-gradient(calc(130deg + var(--ry, 0deg) * 14), var(--g1), var(--g3) 45%, var(--g5));
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor; mask-composite:exclude;
  opacity:.3; pointer-events:none; }
#markets .hv-glare{ position:absolute; inset:0; border-radius:inherit; pointer-events:none;
  background:radial-gradient(52% 44% at calc(var(--mx, .5) * 100%) calc(var(--my, .2) * 100%),
    oklch(1 0 0/.09), transparent 70%);
  mix-blend-mode:screen; }
#markets .hv-head{ display:flex; align-items:center; gap:10px; min-width:0; }
#markets .hv-head .hv-logo, #markets .hv-head .hv-mono{ flex:0 0 auto; display:grid; place-items:center;
  border-radius:9px; background:oklch(1 0 0/.75); box-shadow:inset 0 0 0 1px oklch(0 0 0/.07); overflow:hidden; }
#markets .hv-head .hv-logo img{ width:66%; height:66%; object-fit:contain; }
#markets .hv-head .hv-mono{ font-family:var(--font-num); font-size:12px; font-weight:700; }
body.theme-dark #markets .hv-head .hv-logo, body.theme-dark #markets .hv-head .hv-mono{
  background:oklch(0 0 0/.28); box-shadow:inset 0 0 0 1px oklch(1 0 0/.1); }
#markets .hv-head .nm{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:1px; }
#markets .hv-head .nm b{ font-size:13.5px; font-weight:600; letter-spacing:-.01em;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
#markets .hv-head .nm i{ font-style:normal; font-family:var(--font-num); font-size:10px; color:var(--muted); }
#markets .hv-head .px{ flex:0 0 auto; display:flex; flex-direction:column; align-items:flex-end; gap:1px; }
#markets .hv-head .px b{ font-family:var(--font-num); font-variant-numeric:tabular-nums; font-size:13.5px; font-weight:500; }
#markets .hv-head .px i{ font-style:normal; font-family:var(--font-num); font-size:11px; font-weight:600; }
#markets .hv-dek{ margin:9px 0 0; font-size:12px; line-height:1.5; color:var(--ink-2); text-wrap:pretty; }
#markets .hv-rows{ display:flex; flex-direction:column; gap:6px; border-top:1px solid var(--line);
  margin-top:10px; padding-top:9px; }
#markets .hv-rows .kv{ display:flex; align-items:baseline; justify-content:space-between; gap:12px; }
#markets .hv-rows .kv > span:first-child{ font-size:11.5px; color:var(--muted);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
#markets .hv-rows .kv .v{ font-family:var(--font-num); font-variant-numeric:tabular-nums;
  font-size:11.5px; flex:0 0 auto; }
#markets .hv-hint{ margin-top:10px; padding-top:8px; border-top:1px solid var(--line);
  font-family:var(--font-num); font-size:10px; letter-spacing:.05em; color:var(--muted); }
@media (prefers-reduced-motion: reduce){
  #markets .hv-card{ transform:none; translate:none; scale:none; transition:opacity .15s ease-out; }
}

@media (max-width:1120px){
  .tm-canvas{ height:max(420px, calc(100svh - 150px)); }
}
@media (prefers-reduced-motion: reduce){
  .tm-world.zooming{ transition:none; }
}

/* phones: one tall card column — the 130px fade band swallowed the cut
   card whole (a ghosted card + floating button read as a paint bug). A
   shorter band lets the cut card's top row read through the fade. */
@media (max-width:640px){
  .mkt-clip.clipped::after{ height:76px; }
}

/* touch — comfortable tap targets (fine pointers / desktop unaffected) */
@media (pointer:coarse){
  .tm-view button{ min-height:44px; min-width:44px; }
  .mkt-more button{ min-height:44px; }
}
