/* QRC20 — brutalist monospace. Black & white, one font, one size, one row height everywhere.
   Uniform lines via the "black gap" grid: container is black, cells are white, 2px gaps read as
   uniform rules. */

@font-face {
  font-family: "ProtoMono";
  src: url("fonts/ProtoMono-Light.otf") format("opentype");
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

:root {
  --line: 1.5px;       /* structural lines: panel borders, title bars, row dividers (the "black gap" grid) */
  --line-ctl: 1px; /* control frames (inputs, buttons, PFP frame) — 25% thinner than the structural --line */
  --sp: 10px;     /* every gap/padding, same */
  --fs: 11px;     /* every character, same size */
  --rowh: 28px;   /* content rows — unified to the title-bar height (2026-07-23) */
  --barh: 28px;   /* title bars (same height as a content row now) */
  --ctl: 19px;    /* input/button height — comfortably inset in a 28px row (~4.5px each side) */
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  background: #fff;
  color: #000;
  font-family: "ProtoMono", ui-monospace, "SF Mono", "DejaVu Sans Mono", Menlo, Consolas, "Courier New", monospace;
  font-size: var(--fs);
  line-height: 1.4;
  font-weight: 400;
  text-transform: uppercase; /* everything is all-caps except the console content */
  height: 100%;
  overflow: hidden;          /* the page itself never scrolls in either axis */
  overscroll-behavior: none; /* kill the macOS rubber-band bounce + any scroll-chaining from inner panels */
}

body { padding: 5px; height: 100vh; overflow: hidden; } /* the grid fills the viewport, no page scroll */

/* ---------- DARK MODE (dev switch) ----------
   The whole UI is pure black & white, including the JS-drawn canvases (chart, viz, ladders) and the
   portrait, so a single invert on the root flips every colour at once: white becomes black, each grey maps
   to its mirror (e.g. the #ededed alternating rows become a dark ~#121212), and the portrait renders as
   white-on-black from the exact same generation. Toggle with the DARK_MODE switch in index.html's <head>,
   which adds class="dark" to <html>. brightness() after the invert dims the bright end (pure white → a
   light grey ~#c7) so it is not harsh, while black stays black (0 × anything = 0). */
html.dark { filter: invert(1) brightness(0.80); }

/* ---------- WHOLE-PAGE SHELL: one outer frame + page-wide tabs (TOKEN | SKY) ----------
   TOKEN shows the whole current grid (#tokenView); SKY swaps the content area for the sky (#skyView).
   Reuses the .bar / .bar-tabs / .tab language so the page-wide tabs read as native. */
.shell {
  height: 100%;
  display: flex;
  flex-direction: column;
  border: var(--line) solid #000;
  background: #fff;
  overflow: hidden;
}
.shell-tabs { flex: 0 0 auto; }                  /* the page-wide tab bar keeps its bar height */
.shell-body { flex: 1; min-height: 0; padding: 5px; }   /* one view at a time, inset 5px from the frame */
.sky {                                           /* #fff → black after the html.dark invert; the sky canvases sit inside */
  height: 100%;
  position: relative;
  overflow: hidden;
  background: #fff;
}
.sky-stage { position: absolute; inset: 0 0 var(--barh) 0; overflow: hidden; z-index: 0; }   /* canvas area — reserves ONLY the collapsed bar height at the bottom, so it never resizes on expand/collapse; z-index:0 keeps the whole canvas stack below the drawer; skyhost sizes to THIS */
/* the sky's canvas stack — each layer fills the stage; z-order matches the standalone sky.html */
.sky-stage canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; pointer-events: none; }
/* the SNAPSHOTS section — a DRAWER along the bottom of the SKY view: the title bar is the handle on TOP, the
   plate row slides open below it. The whole section is an absolute overlay anchored to the bottom, so opening
   it floats over (hides) the bottom strip of the canvas instead of resizing the canvas. Built like the token
   sections (a shaded .bar + a scrollable row of plates + LIVE + the (+) tile). */
.panel-snaps {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 20;   /* overlay the bottom of the canvas, above the canvases (z ≤ 9) */
  border: 0; border-top: var(--line) solid #000; background: #fff; /* opaque → hides what it covers; the divider above the section */
}
.snap-row {
  height: clamp(70px, 11vh, 132px);                               /* the plate-row body, below the title bar (bar stays on top) */
  display: flex; gap: 8px; align-items: stretch; padding: 8px; overflow-x: auto; overflow-y: hidden;
  container-type: size;                                            /* size container → tiles/text scale (cqmin) */
}
.snap-tile, .snap-plate {
  flex: 0 0 auto; aspect-ratio: 1 / 1; height: 100%; box-sizing: border-box;
  border: var(--line) solid #000; background: #fff; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  font-family: "ProtoMono", ui-monospace, monospace; user-select: none;
}
.snap-plate { align-items: flex-start; justify-content: space-evenly; padding: 6cqmin; gap: 0; }   /* three lines evenly spaced top→bottom */
.bar-count { flex: 0 0 auto; margin-left: 0.6ch; letter-spacing: 0; }   /* the plate count [N], right beside the SNAPSHOTS title */
/* text stays at the site's --fs (11px) MAX and only scales DOWN when the plate is too small to hold it */
.snap-l { font-size: min(var(--fs), 15cqmin); line-height: 1.15; letter-spacing: 0.03em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.snap-tile { font-size: min(var(--fs), 15cqmin); letter-spacing: 0.06em; }
.snap-new { border-style: dashed; }
/* the inverted fill is only transient feedback — on hover and while pressed — then it resets to the plain
   outlined plate. The selected plate keeps NO persistent fill (that read too strong). */
.snap-tile:hover, .snap-plate:hover, .snap-tile:active, .snap-plate:active { background: #000; color: #fff; }
.snap-new.busy { cursor: default; }
/* collapse / reveal toggle on the right of the SNAPSHOTS bar — a normal framed site button (base .button
   style, like the PROFILE buttons), pushed to the right, with a bigger +/− glyph */
.bar-toggle {
  margin-left: auto; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;   /* center the +/− glyph in the frame */
  width: var(--ctl); height: var(--ctl); padding: 0;                    /* square control, like the site's buttons */
  font-size: 16px; line-height: 1; letter-spacing: 0;                  /* bigger glyph */
}
.panel-snaps.collapsed .snap-row { display: none; }   /* drawer closed → the plate row is hidden, only the title bar shows */
.panel-snaps.collapsed .bar { border-bottom: 0; }     /* row hidden → drop the bar's divider so it isn't a 2nd line by the frame */
#skyNet    { z-index: 1; }   /* haystack circuit, underneath */
#sky       { z-index: 2; }   /* the holder field */
#skyLines  { z-index: 3; }   /* centre dots + id labels */
#skyCurves { z-index: 4; }   /* scaling-law curves (seam-only) */
#skyPath   { z-index: 5; }   /* committed-ETH road (seam-only) */
#skyBridges{ z-index: 6; }   /* block-height bridges (seam-only) */
#skyTicks  { z-index: 7; }   /* block-time bar (seam-only) */
#skyGlobe  { z-index: 8; }   /* block-fullness sphere (modulates the field) */
#skyDivision { z-index: 9; }   /* spatial division — dashed constellation-style region borders, laid on top */

.app {
  display: grid;
  height: 100%;
  /* minmax(0,1fr) tracks (a long value can't grow a track). VIZ/CHART/INFO is a 2×2 block top-LEFT; the
     right column stacks the merged PROFILE+ACCOUNT (a 1×2 cell, rows 1–2) then CHAT — a 1×2 room (rows 3–4).
     The left column stacks RECENT TRADES (row 3) over HOLDERS (row 4); the middle column stacks BUY/SELL
     (row 3) over CONSOLE (row 4). */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: repeat(4, minmax(0, 1fr));
  grid-template-areas:
    "chart   chart    pfp"
    "chart   chart    pfp"
    "trades  buysell  chat"
    "holders console  chat";
  gap: 5px;
  background: #fff;
}
.panel-chart    { grid-area: chart; }
.panel-console  { grid-area: console; }
.panel-buysell  { grid-area: buysell; }
.panel-holders  { grid-area: holders; }
.panel-pfp      { grid-area: pfp; }
.panel-trades   { grid-area: trades; }
.panel-chat     { grid-area: chat; }

/* mobile: stack in one column and let the page scroll normally */
@media (max-width: 780px) {
  html, body { height: auto; overflow: visible; } /* undo the desktop clamp so the stacked page can scroll */
  .shell { height: auto; overflow: visible; }     /* let the frame grow with the stacked content */
  .app { height: auto; grid-template-columns: minmax(0, 1fr); grid-template-rows: none; grid-template-areas: none; }
  .panel { grid-area: auto !important; }
  .sky { min-height: 80vh; }                       /* the sky needs a real height when the page stacks */
}

/* overflow:hidden keeps any over-long content clipped inside its panel instead of spilling over neighbors */
.panel { background: #fff; border: var(--line) solid #000; display: flex; flex-direction: column; overflow: hidden; }
.panel.wide { grid-column: 1 / -1; }
.panel.span2 { grid-column: span 2; } /* two-cell-wide panel (price chart) */

/* header bar — a bit shorter than a content row */
.bar {
  height: var(--barh);
  display: flex;
  align-items: center;
  padding: 0 var(--sp);
  background: #000;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border-bottom: var(--line) solid #000;
}
/* title bars carry the viz sphere's shading: a soft glow easing INWARD from all four sides to a solid core,
   like light spilling in from the box's edges. An inset shadow does this uniformly on every edge (corners
   strongest, where two edges meet) and keeps the core solid. Text flips to dark so it reads on the light.
   Applies to the tabbed bars too — their tab buttons go transparent so the glow shows through (see .bar-tabs). */
.bar {
  background: #fff;
  box-shadow: inset 0 0 15px 0 rgba(0, 0, 0, 0.55);
  color: #000;
}
/* a bar can carry a right-aligned data readout beside its title (holders summary, spot price) */
.bar-title { flex: 0 0 auto; }
.bar-info {
  flex: 1 1 auto;
  min-width: 0;
  margin-left: var(--sp);
  text-align: right;
  letter-spacing: 0;         /* live data, not a title — keep the digits tight and legible */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* readouts that sit right after their title (e.g. "HOLDERS [151]", "RECENT TRADES [+.. -..]"), not right-aligned */
#holdersSummary, #tradesSummary { flex: 0 0 auto; }

.body { display: flex; flex-direction: column; min-width: 0; flex: 1; min-height: 0; overflow-y: auto; }

/* every row: same height, same padding, one rule below */
.row {
  height: var(--rowh);
  flex-shrink: 0;                      /* keep the fixed height — never squeeze to content, like .hrow/.trow */
  display: flex;
  align-items: center;
  gap: var(--sp);
  padding: 0 var(--sp);
  border-bottom: var(--line) solid #000;
  min-width: 0;
}
.body > .row:last-child { border-bottom: 0; } /* a filled panel's own bottom border is the divider */
/* empty status rows collapse so they never show as an empty boxed row */
.row:has(> .status:empty) { display: none; }
.row.btns { gap: var(--sp); justify-content: flex-end; } /* GENERATE + SAVE sit at the right */
.row.merge { border-bottom: 0; } /* attach a row to the one below it (passphrase + its buttons) */
/* TRIAL: drop the internal horizontal dividers inside PROFILE and BUY/SELL / TRANSFER */
.panel-pfp .row,
.panel-buysell .row { border-bottom: 0; }
.panel-pfp .pfp-account { border-top: 0; }

/* passphrase field reads right-aligned like the other values. It is the ONE field that keeps its
   true casing: text-transform is visual only, so caps here would show a phrase that differs from
   what actually derives the key, and a user who copied it by hand could lose access. */
#pass { text-align: right; text-transform: none; }
/* half-width amount inputs (address inputs stay full width) */
input.sm { flex: 0 1 13ch; min-width: 6ch; }
/* trade panel: one uniform label column so every input shares the same left edge */
.trade .k { flex-basis: 12ch; }
.trade .pct { flex: 0 0 auto; }                          /* the "%" sitting after the slippage input */
.trade .row:has(> #estDetail:empty) { display: none; }   /* the min/impact row stays hidden until an estimate exists */

.k { flex: 0 0 6.5ch; text-transform: uppercase; white-space: nowrap; }
.v { flex: 1; min-width: 0; text-align: right; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* the identity value is click-to-copy, like the holders / trades ids: underline on hover, pointer cursor */
.idcopy { cursor: pointer; }
.idcopy:hover { text-decoration: underline; text-underline-offset: 2px; }
.status { flex: 1; min-width: 0; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* controls: small, tight, same height */
input, button {
  font: inherit;
  color: #000;
  background: #fff;
  border: var(--line-ctl) solid #000;
  height: var(--ctl);
  text-transform: uppercase; /* typed text + placeholders render caps (values stay as typed) */
}
input { flex: 1; min-width: 0; padding: 0 6px; }
input::placeholder { color: #000; opacity: 0.5; } /* black at 50% opacity — the only place softened */
input:focus { outline: 0; } /* no thickening on focus — just the native blinking caret */

button {
  padding: 0 12px;
  cursor: pointer;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  background: #fff; /* buttons are light by default: black frame, white fill, black text */
  color: #000;
}
/* only a clickable button flips to white on black on hover; disabled ones never flip */
button:hover:not(:disabled), button:active:not(:disabled) { background: #000; color: #fff; }
button:focus-visible { outline: var(--line) solid #000; outline-offset: var(--line); }
button:disabled { cursor: default; } /* looks identical to the rest, just doesn't invert or click — never greyed, no stop cursor */

/* console: black on white, fits its cell. Two columns: timestamp | content, with wrapped
   content hanging under the content column (never under the timestamp gutter). */
.console {
  background: #fff;
  color: #000;
  padding: var(--sp);
  flex: 1;
  min-height: 0;
  overflow: auto;
  font: inherit;
  text-transform: none; /* the only place that keeps its original casing */
}
.ln { display: flex; gap: var(--sp); align-items: center; min-height: var(--barh); } /* console rows match every other row */
.lt { flex: 0 0 auto; white-space: nowrap; font-variant-numeric: tabular-nums; }
.lc { flex: 1; min-width: 0; white-space: pre-wrap; word-break: break-word; }

/* chart cell: a tabbed body holding the price chart and the QUANTUM STATE viz, one shown at a time */
.bar-tabs { padding: 0; }
.bar-tabs .tab {
  height: 100%;
  border: 0;
  background: transparent;   /* let the bar's inset glow show through the inactive tabs */
  color: #000;
  font: inherit;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 0 var(--sp);
  cursor: pointer;
}
.bar-tabs .tab:hover,
.bar-tabs .tab.active { background: #000; color: #fff; } /* active/hover: a solid block, inverse of the light core */
.bar-tabs .tab + .tab { border-left: var(--line) solid #000; } /* a vertical line between adjacent tabs */

.chart-body { position: relative; flex: 1; min-height: 0; min-width: 0; }
.chart, .stage { position: absolute; inset: 0; }
.chart { display: block; width: 100%; height: 100%; background: #fff; }
[hidden] { display: none !important; } /* one reliable rule so every tab toggle (canvas, body, panel) hides */

/* INFO view: a full-page, two-column read — the intro/steps on the LEFT, the technical dropdown on the RIGHT.
   Each column scrolls on its own; a black rule divides them. */
/* fill past the shell-body's 5px top/bottom padding so the column divider reaches the frame (no gap) */
.info { height: calc(100% + 10px); margin: -5px 0; display: flex; background: #fff; overflow: hidden; }
.info > .prose { flex: 1 1 0; min-width: 0; overflow-y: auto; padding: calc(var(--sp) * 1.4); text-transform: none; line-height: 1.5; }
.info > .prose + .prose { border-left: var(--line) solid #000; }   /* divider between the two columns */
.info .prose p + p { margin-top: var(--sp); }
.info .prose ol + p { margin-top: var(--sp); }   /* a paragraph after the steps list skips a line, like p + p */
.info .prose p:first-child, .info .prose h3:first-child { margin-top: 0; }
/* section headings + numbered steps in the info copy */
.info .prose h3 { font-size: inherit; font-weight: 400; text-transform: uppercase; letter-spacing: 0.06em; margin: calc(var(--sp) * 1.8) 0 6px; }
.info .prose ol { margin: 6px 0 0 2ch; padding-left: 2.6ch; }
.info .prose li { margin-top: 6px; }
.info .prose li:first-child { margin-top: 0; }
/* TECHNICAL: a dropdown (disclosure triangle) whose summary reads like the h3 headings; ▸/▾ marks open state. */
.info .prose details.tech summary {
  cursor: pointer;
  list-style: none;                 /* drop the native marker (Firefox) — we draw our own */
  display: flex;
  align-items: baseline;
  gap: 0.9ch;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.info .prose details.tech summary::-webkit-details-marker { display: none; } /* drop native marker (WebKit) */
.info .prose details.tech summary::before { content: "\25B8"; letter-spacing: 0; } /* ▸ collapsed */
.info .prose details.tech[open] summary::before { content: "\25BE"; }              /* ▾ expanded */
.info .prose details.tech summary:hover { text-decoration: underline; text-underline-offset: 2px; }
.info .prose details.tech > p:first-of-type { margin-top: 6px; }
/* mobile: stack the two columns, let them grow */
@media (max-width: 780px) {
  .info { flex-direction: column; overflow: visible; }
  .info > .prose { overflow: visible; }
  .info > .prose + .prose { border-left: 0; border-top: var(--line) solid #000; }
}
/* spot price sitting in the (padding-less) tabbed BUY/SELL bar, pushed to the right */
.bar-tabs .bar-info { padding-right: var(--sp); }

/* QUANTUM STATE viz: three columns (price meter · sphere + charts · ladder) — same split as the sketch */
.stage { --col1: 33%; --col3: 33%; --sphereH: 75%; }
#vizPrice  { position: absolute; top: 0; left: 0; width: var(--col1); height: 100%; display: block; }
#vizSphere { position: absolute; top: 0; left: var(--col1); width: calc(100% - var(--col1) - var(--col3)); height: var(--sphereH); display: block; cursor: grab; }
#vizCharts { position: absolute; top: var(--sphereH); left: var(--col1); width: calc(100% - var(--col1) - var(--col3)); height: calc(100% - var(--sphereH)); display: block; }
#vizLadder { position: absolute; top: 0; right: 0; width: var(--col3); height: 100%; display: block; }

/* mobile: the panel stacks and is auto-height, so give the tabbed body a real height to draw into */
@media (max-width: 780px) { .chart-body { min-height: 66vh; } }

/* QUANTUM PORTRAIT: the deterministic identity portrait sits LEFT as an always-square canvas (its side =
   the body height, so it stays square at every viewport size), with a right-hand button column — PAUSE
   toggle on top, GIF/PNG export beneath. Outputs are dark-on-white, so they sit on the white body as-is. */
.pfp-body {
  flex: 0 0 auto;        /* content-height: the body hugs the portrait so the account rows rise right below it */
  min-height: 0;
  display: flex;
  align-items: stretch;   /* the side column stretches to the portrait's height → its buttons sit at the portrait's bottom */
  gap: var(--sp);
  padding: var(--sp);
  background: #fff;
  overflow: hidden;
}
/* the ACCOUNT rows merged under the PROFILE portrait: a fixed block below the portrait area (the pfp-body
   above takes the remaining height), split off by a rule */
.pfp-account { flex: 0 0 auto; overflow: visible; border-top: var(--line) solid #000; }
.pfp-frame {
  flex: 0 0 auto;        /* left-aligned; doesn't grow — width follows the square's height */
  width: 47%;            /* sized off the body WIDTH (definite → no circular height dependency), top-aligned */
  height: auto;
  align-self: flex-start;
  aspect-ratio: 1 / 1;   /* always square */
  position: relative;    /* anchors the empty-state prompt overlay */
  background: #fff;
  border: var(--line-ctl) solid #000;  /* the frame: always present, hugs the portrait exactly (no gap),
                                       and stands as an empty white square when no identity is connected */
}
.pfp-frame canvas { display: block; width: 100%; height: 100%; }
/* no identity yet: a random portrait still animates in the frame, dimmed to 50% so it reads as a
   background placeholder — present but clearly inactive until an identity is loaded */
.pfp-frame canvas.pfp-dim { opacity: 0.5; }
.pfp-frame canvas:not(.pfp-dim) { cursor: zoom-in; } /* a loaded portrait clicks to pop up large */
/* per-PFP theme toggle (the WHITE/BLACK button), independent of the global dark mode. .pfp-dark = a
   white-on-black portrait. Because the whole page may already be inverted by html.dark, the portrait needs
   an extra invert only when exactly one of {global dark, pfp dark} is set, and none when they agree. */
.pfp-frame.pfp-dark, .pfp-modal-box.pfp-dark { filter: invert(1); }
html.dark .pfp-frame, html.dark .pfp-modal-box { filter: invert(1); }
html.dark .pfp-frame.pfp-dark, html.dark .pfp-modal-box.pfp-dark { filter: none; }
/* the right-hand column: identity metadata up top (aligned with the frame's top edge), the button
   block pinned to the bottom (aligned with the frame's base) */
.pfp-side {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp);
}
.pfp-meta {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
/* each row is its own flex line: the label takes only its own width, so a long value (e.g. the
   "[buy $qrc to set]" hint) can use ALL the remaining width up to its short label, not a column edge
   sized to the widest label. Values still right-align to the frame edge. */
.pfp-metarow { display: flex; align-items: center; gap: var(--sp); min-width: 0; }
.pfp-meta .k { white-space: nowrap; letter-spacing: 0.02em; flex: 0 0 auto; }
.pfp-meta .v {
  flex: 1;
  min-width: 0;
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-transform: none;   /* names/values keep their own casing */
}
/* the username cell holds either the name or — when there's none — just the EDIT button, right-aligned
   into the value position (no "UNNAMED" placeholder text) */
.pfp-username { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp); }
.pfp-username #pfpUsername { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.pfp-edit { height: var(--ctl); padding: 0 8px; letter-spacing: 0.04em; }  /* same height as the site's buttons */
/* the inline username editor that replaces the name while EDIT is active */
.pfp-name-input { height: var(--ctl); flex: 1 1 auto; min-width: 0; text-align: right; text-transform: none; }

.pfp-actions {
  margin-top: auto;   /* pushed to the bottom of the column, aligned with the frame's base */
  display: flex;
  flex-direction: column;
  gap: var(--sp);
}
.pfp-actions button { height: var(--ctl); }   /* same height as every other button on the site */
.pfp-export { display: flex; gap: var(--sp); } /* a row of equal-width buttons (PAUSE|THEME, GIF|PNG) */
.pfp-export button { flex: 1; min-width: 0; }

/* mobile: the panel is auto-height when stacked, so drive the square off the column width instead */
@media (max-width: 780px) { .pfp-frame { height: auto; width: 40%; } }

/* holders leaderboard: rank | identity | amount | % */
.holders { display: flex; flex-direction: column; min-width: 0; flex: 1; min-height: 0; overflow-y: auto; }
.hrow {
  display: flex;
  align-items: center;
  gap: var(--sp);
  min-width: 0;
  height: var(--barh);                /* one uniform row height across every section */
  flex-shrink: 0;                      /* don't let the scrolling list squeeze rows back to content height */
  padding: 0 var(--sp);
  background: #fff;                    /* alternating grey/white like the chat feed — no black divider rules */
}
.hrow:nth-child(even) { background: #ededed; }
.hrow .rank { flex: 0 0 3.5ch; }
/* the identity fills the row's free width and ellipsizes just before the amount; click-to-copy like a trade */
.hrow .hid { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; }
.hrow .hid:hover { text-decoration: underline; text-underline-offset: 2px; }
.hrow .amt { flex: 0 0 auto; margin-left: auto; font-variant-numeric: tabular-nums; } /* sits at the right edge */

/* recent-trades tape: side | identity | amount | time (narrow column, so the identity ellipsizes) */
.trades { display: flex; flex-direction: column; min-width: 0; flex: 1; min-height: 0; overflow-y: auto; }
/* hide the browser scrollbar in the trades feed, holders list, and chatroom (they stay scrollable) */
.trades, .holders, .chat-log { scrollbar-width: none; -ms-overflow-style: none; }
.trades::-webkit-scrollbar, .holders::-webkit-scrollbar, .chat-log::-webkit-scrollbar { display: none; }
.trow {
  display: flex;
  align-items: center;
  gap: var(--sp);
  min-width: 0;
  height: var(--barh);                 /* one uniform row height across every section */
  flex-shrink: 0;                       /* don't let the scrolling list squeeze rows back to content height */
  padding: 0 var(--sp);
  background: #fff;                     /* alternating grey/white like the holders list, for row clarity */
}
.trow:nth-child(even) { background: #ededed; }
.trow .side { flex: 0 0 4ch; letter-spacing: 0.04em; }        /* BUY / SELL */
.trow .tid  { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; }
.trow .tid:hover { text-decoration: underline; } /* the party address is click-to-copy */
.trow .tamt { flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.trow .ttime { flex: 0 0 auto; font-variant-numeric: tabular-nums; }

/* HOLDERS tab: every holder's portrait in a responsive grid — 3 columns on a narrow screen up to 9 on a
   wide one, fluid between (gap-aware min/max so the extremes land exactly on 3 and 9). */
/* ORBITS tab — a full-bleed canvas board, authored dark-on-white so html.dark inverts it to white lines */
.orbits-view { height: 100%; min-height: 0; background: #fff; overflow: hidden; position: relative; }
.orbits-view canvas { display: block; width: 100%; height: 100%; }

.holders-view { height: 100%; min-height: 0; display: flex; flex-direction: column; background: #fff; }
.holders-grid {
  flex: 1; min-height: 0; overflow-y: auto;
  display: grid;
  gap: var(--sp);
  padding: var(--sp);
  align-content: start;
  grid-template-columns: repeat(auto-fill, minmax(min((100% - 2 * var(--sp)) / 3, max((100% - 8 * var(--sp)) / 9, 150px)), 1fr));
}
.holders-grid { scrollbar-width: none; -ms-overflow-style: none; }
.holders-grid::-webkit-scrollbar { display: none; }
.hcell { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
/* the portrait frame follows the page theme (no counter-invert): on the dark default it reads as the
   inverted look — a luminous white-on-black portrait in a black tile. Click the portrait to play/pause it. */
.hpfp-frame {
  position: relative;
  aspect-ratio: 1 / 1;
  border: var(--line-ctl) solid #000;
  background: #fff;
  overflow: hidden;
  cursor: pointer;
}
.hpfp-frame canvas { display: block; width: 100%; height: 100%; }
.hmeta { display: flex; flex-direction: column; min-width: 0; }
.huser, .hid, .hbal, .hage, .hmsg { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.35; }
.huser { text-transform: none; }                 /* usernames keep their real casing */
.hid { cursor: pointer; }
.hid:hover { text-decoration: underline; text-underline-offset: 2px; }
.hbal, .hage, .hmsg { font-variant-numeric: tabular-nums; }

/* CHAT: a scrolling message log fills the cell; a compose row is pinned to the base. You read only your
   own wealth class in plaintext — other classes render as their cipher stream (braille/hex/binary). */
.chat-log {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  text-transform: none;   /* messages carry their own (already-uppercased) content + ciphers */
}
/* each message is a full-width row of uniform padding; alternating grey / white backgrounds divide them */
.cmsg { min-width: 0; padding: 7px var(--sp); background: #fff; }
.cmsg:nth-child(even) { background: #ededed; }
/* Dark mode: the invert+brightness would crush #ededed to near-black, killing the alternation. Author a
   lighter grey here so it survives the filter as a subtle tint (value is pre-inverted: (255-220)*0.70 ≈ 24). */
html.dark .hrow:nth-child(even),
html.dark .trow:nth-child(even),
html.dark .cmsg:nth-child(even) { background: #dcdcdc; }
.cempty { text-align: center; padding: var(--sp); letter-spacing: 0.04em; }
/* username + time softened to 65% black; the message body stays full black */
.chead { display: flex; align-items: baseline; gap: var(--sp); color: rgba(0, 0, 0, 0.5); }
.cname { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; letter-spacing: 0.04em; }
.ctime { flex: 0 0 auto; white-space: nowrap; font-variant-numeric: tabular-nums; }
.cbody { min-width: 0; margin-top: 2px; white-space: pre-wrap; word-break: break-word; line-height: 1.35; }
.cbody.craw { word-break: break-all; } /* raw on-chain hex (non-holder view) is one long token — break anywhere */
/* corrupt text keeps the message's spaces, so it wraps at word boundaries like normal text */
.cpending .ctime { letter-spacing: 0.04em; }          /* the optimistic message reads "sending…" until it lands */

.chat-compose { display: flex; gap: var(--sp); padding: var(--sp); border-top: var(--line) solid #000; }
.chat-compose input { flex: 1; min-width: 0; } /* all-caps like the whole site; encode uppercases it too */
.chat-compose button { flex: 0 0 auto; }
/* the status line only appears mid-transaction (empty rows already collapse), and carries no extra border */
.panel-chat .chat-status-row { border-bottom: 0; height: auto; padding: 6px var(--sp); }

a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
a:hover { background: #000; color: #fff; text-decoration: none; }
/* the INFO/text tab's contract links are inline in the prose (label then link), each on its own line */
.info .links a { white-space: nowrap; }

/* wallet chooser overlay. The veil is authored WHITE so the html.dark invert renders it as a DARK dim (a black
   veil would invert to a light wash) — same trick used by the onboarding + portrait overlays below. */
.modal { position: fixed; inset: 0; z-index: 10; display: flex; align-items: center; justify-content: center; padding: 10px; background: rgba(255, 255, 255, 0.6); }
.modal[hidden] { display: none; }
.modal-box { background: #fff; border: var(--line) solid #000; width: min(340px, 92vw); }
.modal-actions { justify-content: flex-end; border-bottom: 0; }
.modal-box .bar { border-bottom-color: #fff; } /* separate the title from the black wallet rows */
.wallet-choice { display: block; width: 100%; height: var(--rowh); padding: 0 var(--sp); border: 0; border-bottom: var(--line) solid #000; text-align: left; letter-spacing: 0; cursor: pointer; }

/* ONBOARDING modal: BLURS the page behind it (backdrop-filter) + a dark veil, so the setup card is the only
   focus. A centered brutalist card with the two steps + a plain-language "why" for each; it pops on open.
   X / Esc / ENTER SITE dismiss it to browse read-only. Veil authored WHITE so the html.dark invert dims it dark. */
.onboard { background: rgba(255, 255, 255, 0.62); backdrop-filter: blur(7px); -webkit-backdrop-filter: blur(7px); }
.onboard-box {
  position: relative;
  background: #fff;
  border: var(--line) solid #000;
  width: min(600px, 94vw);             /* 25% wider than before */
  max-height: 94vh;
  overflow-y: auto;
  padding: calc(var(--sp) * 2);
  display: flex;
  flex-direction: column;              /* margins (not gap) so pairs can sit tight while sections break wide */
  animation: pfp-pop 170ms ease-out;   /* the same scale-up "pop" as the portrait lightbox */
}
.onboard-x { position: absolute; top: var(--sp); right: var(--sp); height: var(--ctl); width: var(--ctl); padding: 0; line-height: 1; }
.onboard-title { letter-spacing: 0.06em; } /* normal font size, a touch of tracking */
/* vertical rhythm: a normal block gap by default; a label:value PAIR sits tight (like two lines of a
   paragraph); an extra break sets the ETH WALLET section apart from the QUANTUM IDENTITY section */
.onboard-why, .onboard-note, .onboard-btns, .onboard-line, .onboard-foot, .onboard-box input { margin-top: var(--sp); }
.onboard-line + .onboard-line { margin-top: 3px; }                   /* identity+balance / wallet+balance sit tight */
.onboard-line + .onboard-why { margin-top: calc(var(--sp) * 1.8); }  /* line-skip above the ETH WALLET section */
.onboard-why, .onboard-note { text-transform: none; line-height: 1.5; } /* explanatory prose */
/* flex:0 0 auto so the site's global `input{flex:1}` doesn't stretch/squash it vertically in this flex column */
.onboard-box input { flex: 0 0 auto; height: var(--ctl); width: 100%; text-align: left; text-transform: none; } /* passphrase keeps its true casing */
.onboard-btns { display: flex; gap: var(--sp); flex-wrap: wrap; }
.onboard-btns button { height: var(--ctl); flex: 1 1 auto; }
/* label : value ( : button ) rows for identity / balances / wallet */
.onboard-line { display: flex; align-items: center; gap: var(--sp); min-width: 0; }
.onboard-line .k { flex: 0 0 auto; white-space: nowrap; letter-spacing: 0.02em; color: rgba(0, 0, 0, 0.75); } /* labels lightened (artist-granted grey) */
.onboard-line .v { flex: 1; min-width: 0; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-transform: none; }
.onboard-line button { flex: 0 0 auto; height: var(--ctl); }
/* ETH WALLET row: disconnected → CONNECT fills the row; connected → the address takes the space and the
   button becomes DISCONNECT (so you can switch wallets). Keyed off the address span being empty. */
.onboard-line .v:empty { flex: 0 0 0; }
.onboard-line:has(.v:empty) #obConnect { flex: 1 1 auto; }
.onboard-foot { display: flex; gap: var(--sp); }
.onboard-foot button { flex: 1 1 auto; height: var(--ctl); }

/* PROFILE lightbox: click the portrait to pop it up large. Blurs + dims the page behind, darker than the
   onboarding so the portrait is the only focus; the box pops (scales up) on open. Click anywhere or press Esc
   to close. Veil authored WHITE so the html.dark invert dims it dark. */
.pfp-modal { background: rgba(255, 255, 255, 0.85); backdrop-filter: blur(7px); -webkit-backdrop-filter: blur(7px); cursor: zoom-out; }
.pfp-modal-box {
  width: min(86vw, 86vh, 760px);
  aspect-ratio: 1 / 1;                 /* always square, whatever the viewport */
  background: #fff;
  border: var(--line) solid #000;
  animation: pfp-pop 170ms ease-out;
}
.pfp-modal-box canvas { display: block; width: 100%; height: 100%; }
@keyframes pfp-pop { from { transform: scale(0.86); opacity: 0; } to { transform: scale(1); opacity: 1; } }

@media (prefers-reduced-motion: reduce) { * { transition: none !important; } .pfp-modal-box { animation: none; } }
