/* Shell and panel layout.
 *
 * The shell is a grid of chart panels with draggable dividers between them. A panel is a whole
 * chart in its own document, so this file only has to place them and get out of the way.
 */

/* ------------------------------------------------------------------ shell */

/* Same reasoning as the panel: the grid it takes is whatever is left. */
body.shell {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  overflow: hidden;
}
body.shell > .sh-grid { flex: 1 1 auto; min-height: 0; }
body.shell > .menubar,
body.shell > .statusbar { flex: 0 0 auto; }

:root { --split: 4px; }

/* THE GRID PLACES ITS CELLS ABSOLUTELY, and that is a layout-engine decision rather than a
   styling one: nesting cells inside row elements means a pane that changes row is re-parented,
   and re-parenting an iframe reloads the chart inside it. One host and four percentages per cell
   means no pane element ever moves again. The gaps between panes are this background showing
   through - each cell gives up half of --split on every edge it shares with a neighbour. */
.sh-grid {
  position: relative;
  min-height: 0;
  background: var(--line);
  overflow: hidden;
}

.sh-cell {
  position: absolute;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  background: var(--bg);
  /* An inset outline rather than a border: a border would change the cell's size and shift the
     chart by a pixel every time focus moved, which reads as the layout twitching. */
  outline: 1px solid transparent;
  outline-offset: -1px;
}
.sh-cell.focused { outline-color: #33333a; }

.sh-frame {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  background: var(--bg);
}

/* While a divider is dragged the frames must not receive the pointer, or the iframe swallows it
   the moment the cursor crosses one and the divider sticks where it was. */
.sh-grid.dragging .sh-frame { pointer-events: none; }
/* While a TAB is carried the frames also give up the pointer, so the drag falls through to the
   cells. Separate class: divider drags and tab drags start and end in different places. */
.sh-grid.tabdrag .sh-frame { pointer-events: none; }
/* The update pill: one quiet capsule above the status bar. It appears only when the build under
   this tab has moved on, and clicking it is the only thing it does. */
.up-pill {
  position: fixed;
  right: 14px;
  bottom: 34px;
  z-index: 80;
  padding: 7px 14px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--panel-2);
  color: var(--ink);
  font: var(--t-sm) var(--ui);
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .35);
}
.up-pill:hover { border-color: var(--ink-faint); }
/* The drop hint: which half of which pane the drop would become. A wash and a line, in the
   product's own focus colour - the zone is a statement, not a decoration. */
.sh-drop {
  position: fixed;
  z-index: 40;
  pointer-events: none;
  background: color-mix(in srgb, var(--focus, #2962ff) 14%, transparent);
  outline: 1px solid color-mix(in srgb, var(--focus, #2962ff) 55%, transparent);
  border-radius: var(--r-sm);
}
/* A drop on the grid's outer edge takes that whole side of the window rather than half of one
   pane, so it says so with a heavier line - the two drops do different things and must not look
   identical while the tab is in the air. */
.sh-drop.edge {
  background: color-mix(in srgb, var(--focus, #2962ff) 18%, transparent);
  outline-width: 2px;
}

/* Panel name strip. It only appears when there is more than one panel, because a label saying
   "chart" above a single chart is noise. */

/* dividers
   The gap between two panes is already drawn - it is the grid's background - so a divider is a
   grab area sitting over it, transparent until the pointer is on it. It is wider than the line
   for the same reason a native splitter is: 4px is too thin to catch reliably. */
.sh-div-row,
.sh-div-col {
  position: absolute;
  z-index: 6;
  background: transparent;
}
.sh-div-row { cursor: row-resize; }
.sh-div-col { cursor: col-resize; }
.sh-div-row::after,
.sh-div-col::after {
  content: '';
  position: absolute;
  inset: calc(50% - var(--split) / 2) 0;
}
.sh-div-col::after { inset: 0 calc(50% - var(--split) / 2); }
.sh-div-row:hover::after,
.sh-div-col:hover::after { background: var(--line-2); }

/* ------------------------------------------------------------------ panel */

/* FLEX, not a fixed grid template.
 *
 * This was `grid-template-rows: auto auto auto 1fr`, which assumes exactly four visible children
 * forever. Hiding the tab strip leaves three - so the stage lands in an auto row, the 1fr row
 * stays empty, and a band of background shows below the lowest indicator. Any future element that
 * can be hidden would do the same.
 *
 * A column flex has no such assumption: the stage takes whatever is left, however many siblings
 * are visible. min-height:0 is what lets it SHRINK too, without which a tall canvas pushes the
 * layout past the viewport instead of fitting inside it. */
body.panel {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  overflow: hidden;
}
body.panel > .stage { flex: 1 1 auto; min-height: 0; }
body.panel > .tabstrip,
body.panel > .bar,
body.panel > .info { flex: 0 0 auto; }

/* A panel has no menu bar or status bar of its own - the shell owns those - so its chrome starts
   at the tab strip. */
body.panel .menubar,
body.panel .statusbar { display: none; }

@media (max-width: 640px) {
  /* On a phone one chart fills the screen. Splitting a 390px viewport into panels produces two
     charts too small to read, so the grid collapses to whichever panel has focus. */
  /* The cells carry their rectangle in inline styles, so the phone rule has to be stronger than
     an inline declaration to take the whole screen back. */
  .sh-cell { display: none; inset: 0 !important; }
  .sh-cell.focused { display: block; }
  .sh-div-row, .sh-div-col { display: none; }
}

/* ------------------------------------------------------------ period picker */

/* ------------------------------------------------------------- new tab page */

/* Covers the stage while a tab has no instrument. The page IS the empty state - it never sits
   over a chart, because a fresh tab has nothing to show yet. */
/* Both edges are set from the stage in JS; these are the fallbacks for the first frame. */
.newtab {
  position: fixed;
  inset: var(--newtab-top, 123px) 0 0 0; /* tab strip + toolbar now both sit above the stage */
  z-index: 30;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 28px 32px;
  overflow: auto;
}
/* The Web step is a directory, not a choice: it hangs from the top and uses the width it needs. */
.newtab:has(.weblinks) { justify-content: flex-start; align-items: flex-start; }

/* The choices live in the middle of the room, as a grid of equal tiles. */
.newtab-tiles {
  display: grid;
  grid-template-columns: repeat(2, 200px);
  gap: 10px;
}
/* The view choices on a blank tab carry the button face, like every other standalone button. They
   are the entire point of this page - four choices on an empty surface - so leaving them as text
   made the page read as a list rather than as something to act on.
 *
 * The face itself is defined once in style.css; this only sets the geometry. It used to reset
 * background, border and radius here, which silently undid it: panel.css loads after style.css,
 * so a `border: 0` written for a flat button beat the shared rule without saying so. */
.newtab-close {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: var(--ink-faint);
  font-size: var(--t-md);
  cursor: pointer;
}
.newtab-close:hover { color: var(--ink); }

/* A tile is the shared FIELD - hairline frame, transparent body, from controls.css - wearing
   tile geometry: taller, rounder, contents stacked. The radius is the one deliberate departure
   from the control value, because a 96px tile at a 4px radius reads as a crate; geometry is NOT
   themed, so this is identical on every appearance. */
.newtab-view {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  font: inherit;
  font-size: var(--t-md);
  color: var(--ink);
  padding: 14px 16px 12px;
  min-height: 96px;
  border-radius: var(--r-surface);
  cursor: pointer;
  text-align: left;
}
.newtab-view svg { margin-bottom: 5px; color: var(--ink-dim); }
.newtab-view:hover svg { color: var(--ink); }
.newtab-name { font-weight: var(--w-medium); }
/* A choice that does not exist yet is not pressable, so it does not get a face. */
.newtab-view.off {
  color: var(--ink-faint);
  cursor: default;
  background: transparent;
  border: 1px dashed var(--line-2);
}
.newtab-view.off svg { color: var(--ink-faint); }
.newtab-view.off:hover { background: transparent; border-color: var(--line-2); }
.newtab-view.off:hover svg { color: var(--ink-faint); }
.newtab-view.off:active { background: transparent; }
.newtab-why { font-size: var(--t-xs); color: var(--ink-faint); font-weight: var(--w-regular); }
/* The corner menu - the reference's own new-tab: quiet rows top-left, each a view. */
/* BACK IN THE CORNER, at the owner's word: "the old version was in the top left corner, on top
 * of each other, it was in a better way - right now it's in the center."
 *
 * The centred block was an answer to a real complaint about a wide window, but it cost the thing
 * the page is for. A fresh tab is a panel waiting to be told what it is, and a menu that hangs
 * off the corner reads as exactly that; the same rows centred in the room read as a landing
 * page - a screen you are meant to look at rather than a list you are meant to act on. It also
 * moved the choices away from the corner the cursor is already in after opening a tab.
 *
 * So: one running column, pinned top-left, groups stacked on top of each other. */
.newtab-menu {
  position: absolute;
  top: 18px;
  left: 18px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 200px;
}

.newtab-mrow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: var(--ink);
  font: var(--w-semi) var(--t-md) var(--ui);
  text-align: left;
  cursor: pointer;
}
.newtab-mrow:hover { background: var(--sel); }
.newtab-mrow.off { color: var(--ink-faint); cursor: default; }
.newtab-mrow.off:hover { background: transparent; }
.newtab-mlabel { flex: 0 0 auto; }
.newtab-mwhy { margin-left: 8px; color: var(--ink-faint); font: var(--t-xs) var(--ui); }
/* A group heading in the corner menu. Quiet: it is a signpost, not a row. */
/* Same treatment as the widget drawer's section labels, deliberately: two surfaces that both
   answer "what can this panel be" should not caption themselves in two different voices. */
.newtab-mgroup {
  padding: 12px 10px 4px;
}
.newtab-mgroup:first-child { padding-top: 0; }

/* A wallet room as a WIDGET: one table, no tiles, no segmented bar. The page's own header stays,
   because a table of somebody's positions has to say whose - a cell showing an account with no
   address on it is a cell you cannot trust. It gets tighter, not removed.
 *
 * A CLASS, not the hidden attribute. Both of these are given `display: grid` and `display: flex`
 * by their own rules, and a class rule beats the user agent's [hidden] - so setting the property
 * in JS did exactly nothing and the widget shipped wearing the whole page's furniture. This file
 * loads after windows.css, so the rule below is the one that wins. */
.wv-single .wv-kpis,
.wv-single .wv-tabs { display: none; }
.wv-single /* .wv-head geometry now comes from the shared .panel-head base. */
/* .wv-head geometry now comes from the shared .panel-head base. */
.wv-head { }
.wv-single .wv-body { padding-top: 0; }

/* ------------------------------------------------------- the book, as a panel */

/* A view fills the cell. The new-tab page centres its contents, which is right for a menu and
   wrong for anything that IS the panel - so these take the whole area back. */
.bookview {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--panel);
}
.bookview-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  border-bottom: 1px solid var(--line);
}
.bookview-sym {
  font: var(--w-semi) var(--t-md) var(--ui);
  color: var(--ink);
}
.bookview-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* The watchlist's own rules moved to web/watchlist.css - a component whose class names have
   collided twice with a neighbour's in this file should not be filed next to them. */

.newtab-hint {
  position: absolute;
  top: 22px;
  left: 20px;
  color: var(--ink-dim);
  font: var(--t-md) var(--ui);
}
.newtab-hint + .newtab-back { position: absolute; top: 52px; left: 12px; }

.newtab-back {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: inherit;
  font-size: var(--t-sm);
  color: var(--ink-faint);
  background: transparent;
  border: 0;
  padding: 8px 14px;
  cursor: pointer;
}
.newtab-back:hover { color: var(--ink); }

/* Panels this half added: the per-indicator windows and the key card.
 *
 * Kept in their own file so two people can work on the look at once without fighting over one
 * stylesheet. Everything reads the same custom properties as the rest of the app, so a theme
 * change moves these too.
 *
 * The indicator picker that opened this section is GONE: it was a second implementation of the
 * Indicators menu (lib/indicators-menu.js), reachable from exactly one menu entry, and two
 * dialogs answering one label is how a product reads as two products. Its pick-* rules left
 * with it. */

/* ---------------------------------------------------------------- theme panel */

.th-root { display: flex; flex-direction: column; gap: 10px; padding: 10px; height: 100%; }

/* The pair that does the job people actually came for. */
.th-quick { display: flex; gap: 6px; }
/* Geometry only - the face, the hover and the latch are the control system's, in controls.css.
   Declaring background and border here re-opted this button out of the system it belongs to,
   which is how it kept a gradient after the gradient was removed everywhere else. */
.th-preset {
  font: inherit;
  font-size: var(--t-md);
  height: var(--c-md);
  padding: 0 16px;
  cursor: pointer;
}
.th-preset.on { color: var(--ink); }

.th-split { display: flex; gap: 12px; flex: 1; min-height: 0; }
.th-col { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; }
.th-head {
  padding: 2px 2px 4px;
}
.th-list {
  display: flex;
  flex-direction: column;
  gap: 1px;
  overflow-y: auto;
  border: 1px solid var(--line);
  background: var(--bg);
  padding: 2px;
  flex: 1;
  min-height: 120px;
}
/* THREE CHOICES DO NOT NEED A THOUSAND PIXELS.
 *
 * Both lists take the room the split gives them, which is right for the themes - twenty-one packs
 * that scroll - and wrong for the typography list, which holds three rows and never holds more.
 * It drew an empty frame down the whole left of the window. A list that cannot grow is not given
 * room to. */
.th-fixedlist { flex: 0 0 auto; min-height: 0; }

.th-item {
  font: inherit;
  font-size: var(--t-sm);
  text-align: left;
  color: var(--ink-dim);
  background: transparent;
  border: 0;
  border-radius: 2px;
  padding: 5px 8px;
  cursor: pointer;
}
.th-item:hover { color: var(--ink); }
/* Themed ink. #fff on var(--sel) is white-on-near-white the moment a light theme is picked -
   in the one panel a user is guaranteed to be standing in when they pick it. */
.th-item.on { background: var(--sel); color: var(--ink); }
/* Same inset as every other window footer, so the button clears the resize grip in the corner
   instead of sitting underneath it. */
.th-foot { display: flex; justify-content: flex-end; padding: 8px 12px; }

/* A theme row that names the choice and says what it is for, rather than a bare font stack. */
.th-item { display: flex; flex-direction: column; gap: 1px; }
.th-item-name { font-size: var(--t-sm); }
.th-item-blurb { font-size: var(--t-xs); color: var(--ink-faint); }
.th-item.on .th-item-blurb { color: var(--ink-dim); }

/* A THEME ROW SHOWS ITS THEME.
 *
 * Twenty-one packs named Nord, Darcula, Gruvbox Dark Hard and Material Oceanic is a list only
 * usable by someone who already knows all twenty-one, and the one question anybody actually has
 * standing in front of it - is this one light or dark - has no answer in a word. The pack is six
 * colours (see CHROME_THEMES); three of them drawn at swatch size answer it before it is asked.
 *
 * The chip carries its own ground and its own border, so it also shows how much contrast a pack
 * puts between a panel and the rule around it - which is the other thing that separates two
 * greys with different names. */
.th-item.th-pack { flex-direction: row; align-items: center; gap: 9px; }
.th-swatch {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 3px;
  width: 34px;
  height: 20px;
  padding: 0 5px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  overflow: hidden;
}
.th-swatch-ink { height: 3px; border-radius: 2px; }
.th-swatch-dim { height: 3px; width: 60%; border-radius: 2px; }

/* ------------------------------------------------------------ web directory

   The other tools a desk keeps open, in the reference terminal's own grouping: a name on the left
   and what it IS on the right, so the list can be scanned by purpose rather than by brand. */
/* GROUP BLOCKS side by side, every list inside them vertical - one screen, no scroll. A
   directory you scroll is a directory you search instead, and at that point the browser's
   address bar has already won. */
.weblinks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 380px));
  gap: 26px 48px;
  align-content: start;
  align-items: start;
  width: 100%;
  max-width: 860px;
}
/* The directory's rows are `wd-`. They were `wl-`, the watchlist's own prefix, and because this
   block sits later in the file its flex rule beat the watchlist table's layout - which is why
   that list's columns never lined up with its header. Two features cannot share a prefix. */
.wd-group { display: flex; flex-direction: column; }
/* A group head in a directory is a group head: the micro-label, from style.css. The rule under it
   stays - in a page that is three lists side by side the line is what separates them - but the
   type was its own, twelve pixels of full-ink title case, which is a fourth caption family on a
   page that already has none of its own. */
.wd-head {
  margin: 18px 0 6px;
  padding-bottom: 5px;
  border-bottom: 1px solid var(--line);
}
.wd-group .wd-head { margin-top: 0; }
.wd-row {
  display: flex;
  align-items: baseline;
  gap: 16px;
  padding: 7px 10px;
  border-radius: var(--r);
  color: var(--ink-dim);
  text-decoration: none;
  font-size: var(--t-md);
}
.wd-row:hover .wd-name { color: var(--ink); }
.wd-name { flex: 0 0 190px; }
/* What the tool IS, not a description. The reference prints one or two words here and that is
   exactly enough to pick from a list of twenty. */
.wd-what { flex: 1 1 auto; color: var(--ink-faint); font-size: var(--t-sm); }
.wd-note {
  grid-column: 1 / -1;
  margin-top: 20px;
  max-width: 560px;
  color: var(--ink-faint);
  font-size: var(--t-sm);
  line-height: var(--lh-text);
}

/* ---------------------------------------------------------------- boot screen

   Shown until the first completed frame, then it fades and the chart fades in together - one
   arrival rather than bars popping in and the auto-fit jumping afterwards. */
.boot {
  position: fixed;
  inset: 0;
  z-index: 45;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  transition: opacity var(--t-pop) var(--ease), visibility 0s linear var(--t-pop);
}
/* Driven by the same class the canvas reveal uses, so the two can never disagree. */
body.chart-ready .boot {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

/* THE SKELETON IS THE LOADER.
 *
 * What was here before was a ring spinning at 900ms with an elapsed-seconds counter beside it,
 * centred on an empty ground. Both are the furniture of a web page waiting on a request: a
 * spinner is a duty cycle pretending to be progress, and a counter that ticks to "0.4s" and
 * disappears reads as debug output nobody removed. Neither says anything about THIS product.
 *
 * A terminal arrives differently. The chrome is drawn at its real size first - toolbar band,
 * rail, price axis, time axis - so the eye settles into the layout while the data is still in
 * flight, and the first real frame lands on top of it without moving a single edge. The only
 * motion is one hairline crossing under the toolbar, which is what a machine loading something
 * looks like when it is not asking for attention.
 */
.boot-skel {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
}

/* The toolbar band, and the only moving thing on screen. */
.boot-top {
  position: relative;
  flex: 0 0 var(--boot-top-h, 38px);
  border-bottom: 1px solid var(--line);
  background: var(--panel);
  overflow: hidden;
}
.boot-scan {
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 34%;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--ink-faint), transparent);
  animation: boot-scan 1400ms cubic-bezier(.45, 0, .25, 1) infinite;
}
@keyframes boot-scan {
  from { transform: translateX(-100%); }
  to { transform: translateX(390%); }
}

.boot-mid { flex: 1 1 auto; display: flex; min-height: 0; }
.boot-rail {
  flex: 0 0 var(--boot-rail-w, 46px);
  border-right: 1px solid var(--line);
  background: var(--panel);
}
/* Where the candles will be. Left empty on purpose: drawn placeholder bars would be invented
   data, and the one thing a chart must never do is show a shape that is not a price. */
.boot-plot {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
}
.boot-axis {
  flex: 0 0 var(--boot-axis-w, 74px);
  border-left: 1px solid var(--line);
  background: var(--bg);
}
.boot-time {
  flex: 0 0 20px;
  border-top: 1px solid var(--line);
  background: var(--bg);
}

/* The tab's own identity, set where the watermark will be and at the watermark's weight, so the
   name does not jump when the real one paints under it. Empty on a fresh tab and invisible - a
   blank line under a name that is not there reads as a missing image. */
.boot-sym {
  color: var(--ink);
  opacity: .07;
  /* type-exempt: this is the WATERMARK's size and face, not the interface's. It stands in for the
     mark app.js draws on the canvas, so it has to be set in the same stack that mark uses -
     Segoe UI first, deliberately, because that element alone is a 1:1 port of a Windows desktop
     application's watermark. It named Inter, so the placeholder changed face the instant the
     real mark painted under it, which is exactly the jump this element exists to prevent. */
  font: var(--w-bold) 44px "Segoe UI", "Segoe UI Variable Text", "Plex Sans", system-ui, -apple-system, sans-serif;
  letter-spacing: var(--tr-ticker);
  user-select: none;
}
.boot-sym:empty { display: none; }

@media (prefers-reduced-motion: reduce) {
  .boot-scan { animation: none; opacity: .35; }
  .boot { transition: none; }
}


/* ------------------------------------------------------------------- flows

   The liquidation feed. One line per print, newest on top, read out of the corner of the eye.

   Every decision here is about the COLUMN rather than the row: the coin, the size and the price
   are set in tabular figures at fixed widths so the eye runs straight down them, and the words
   between them are the quiet part. A feed where the numbers wander is a feed nobody scans. */
/* A view page is not a chart. The chart's own toolbar - symbol, timeframes, tick, indicator
   chips - describes an instrument this page has not got, so it goes; the tab strip stays, because
   that is the way back. */
body.view-page .bar { display: none; }

.fl-root {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--bg);
}

/* .fl-head geometry now comes from the shared .panel-head base. */
/* .fl-head geometry now comes from the shared .panel-head base. */
.fl-head { gap: 12px; }
.fl-title { display: flex; align-items: baseline; gap: 9px; min-width: 0; }
.fl-name {
  color: var(--ink);
  font-size: var(--t-lg);
  font-weight: var(--w-semi);
  letter-spacing: var(--tr-ticker);
  /* The name holds its full length and the status beside it gives way, which is the right order
     to lose them in - both children declare nowrap, so without this the title would shrink first
     and the feed would be headed "Liquidati...". */
  flex: 0 0 auto;
  white-space: nowrap;
}
/* Says what it is watching once it knows, and admits it when the stream drops. A feed that has
   gone quiet because the socket died looks exactly like a calm market, which is the one thing it
   must never be mistaken for. */
.fl-status {
  color: var(--ink-faint);
  font-size: var(--t-sm);
  /* ONE LINE, AND IT SHORTENS RATHER THAN WRAPS. On a 390px phone "Hyperliquid - 40 markets" broke
     across three lines and pushed the feed a header's worth down the screen. A subtitle is the
     lowest-priority thing in a header; it should lose characters before it takes rows. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.fl-status.on { color: var(--ink-dim); }

.fl-filter { margin-left: auto; position: relative; }
.fl-fbtn { display: inline-flex; align-items: center; gap: 6px; }
.fl-fbtn-label { font-variant-numeric: tabular-nums; }
.fl-fbtn.on { color: var(--ink); }
.fl-fbtn.open { background: var(--sel); }

/* The filter is a panel, not a menu: two named groups with room to breathe, because a dense strip
   of controls in the corner of a feed is the thing that reads as unfinished. */
.fl-panel {
  position: absolute;
  top: calc(100% + 7px);
  right: 0;
  z-index: 40;
  width: 320px;
  max-height: min(62vh, 520px);
  overflow-y: auto;
  padding: 13px 14px 12px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  background: var(--panel);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  box-shadow: 0 12px 34px rgba(0, 0, 0, .46);
}
/* An explicit `display` in an author rule BEATS the hidden attribute's UA `display:none`, so
   both of these have to say it themselves - without these two lines the filter panel and the jump
   pill are painted permanently, whatever the code believes about them. */
.fl-panel[hidden], .fl-jump[hidden] { display: none; }

.fl-prow { display: flex; flex-direction: column; gap: 8px; }
.fl-phead { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
/* SENTENCE CASE, NOT A SHOUT. An uppercase letter-spaced caption is this product's TABLE-head
   convention, and wearing it over three settings groups in a popover is what made the panel read
   as a generated dashboard. The heads are quiet labels here, the way the wallet rail sets them. */
.fl-plabel {
  text-transform: none;
  letter-spacing: var(--tr-none);
  font-size: var(--t-sm);
  font-weight: var(--w-regular);
  color: var(--ink-faint);
}
.fl-plink {
  background: none;
  border: 0;
  padding: 0;
  color: var(--ink-dim);
  font: inherit;
  font-size: var(--t-sm);
  cursor: pointer;
}
.fl-plink:hover { color: var(--ink); }
.fl-plink.on { color: var(--ink-faint); cursor: default; }

.fl-steps { display: flex; flex-wrap: wrap; gap: 5px; }
/* The feed's filters are chips like every other chip: the field, the latch and the radius are the
   control system's. They sat at a fifth radius of their own - 5px, which followed no appearance. */
.fl-step { font-variant-numeric: tabular-nums; }

/* THE SIZE SCALE IS ONE CONTROL. Seven separate buttons wrapped into two ragged rows and read as
   seven choices; a welded track reads as a scale with one answer on it, which is what it is. The
   segments are equal columns rather than content-width, so the track is a ruler and the eye can
   tell 100K from 1M by where it sits as well as by what it says. */
.fl-seg {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  height: var(--c-sm);
  border: 1px solid var(--c-line);
  border-radius: var(--r);
  overflow: hidden;
}
.fl-segbtn {
  border: 0;
  border-right: 1px solid var(--line);
  border-radius: 0;
  background: transparent;
  color: var(--ink-dim);
  font: inherit;
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  padding: 0;
  min-width: 0;
}
.fl-segbtn:last-child { border-right: 0; }
.fl-segbtn:hover { color: var(--ink); }
/* The latch is the system's: the quiet ground and full-strength ink, no accent and no frame of
   its own inside a track that already has one. */
.fl-segbtn.on { background: var(--sel); color: var(--ink); }

/* THE PANEL IS A LIST OF LINES, NOT A GRID OF CARDS.
 *
 * "Make the buttons clean, like others use and not AI common default selectors." What stood here
 * was the generated-settings shape: each option a bordered card in a two-column grid with a
 * coloured tick and a count inside it, under a shouted uppercase heading - three control languages
 * in one 320px panel. Every option is a line now: name, what it is worth, and the switch this
 * product already uses in the news panel (.nw-switch). One grammar, and it is one this app wrote
 * before this panel existed.
 */
.fl-kinds { display: flex; flex-direction: column; }
/* The markets list scrolls rather than capping itself and explaining the cap in a sentence. */
.fl-coins {
  display: flex;
  flex-direction: column;
  max-height: 232px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.fl-opt {
  display: flex;
  align-items: center;
  gap: 10px;
  height: var(--c-md);
  padding: 0 4px;
  border-radius: var(--r);
  color: var(--ink-dim);
  font-size: var(--t-sm);
  cursor: pointer;
}
.fl-opt:hover { background: var(--sel); color: var(--ink); }
.fl-optname { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fl-optnote {
  flex: 0 0 auto;
  color: var(--ink-faint);
  font-size: var(--t-2xs);
  font-variant-numeric: tabular-nums;
}

/* The market search: the same field the news feed's search wears, because it is the same act. */
.fl-find {
  height: var(--c-sm);
  padding: 0 8px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--r);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-sm);
  outline: none;
  min-width: 0;
}
.fl-find:focus { border-color: var(--line-2); }
.fl-find::placeholder { color: var(--ink-faint); }
.fl-find[hidden] { display: none; }
/* The chip rail this list replaced is gone; what stood here was its second copy of `.fl-coins`,
   which kept winning `flex-wrap` because the new rule never mentioned it - the markets laid out in
   three columns and ran off the panel. Two copies of a rule set conflict in whatever NEITHER
   mentions, which is the third time this lane has paid for that. */
.fl-pnote { color: var(--ink-faint); font-size: var(--t-sm); line-height: var(--lh-ui); }

.fl-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* The stack is pushed to the BOTTOM of the scroller so a half-empty feed reads as a chat that has
   only just started rather than as a table with a gap under it. */
.fl-stack {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 6px 0 10px;
}
.fl-empty { padding: 22px 16px; color: var(--ink-faint); font-size: var(--t-md); }

.fl-older {
  padding: 0;
  color: var(--ink-faint);
  font-size: var(--t-sm);
  text-align: center;
}
.fl-older.on { padding: 10px 0 8px; }

/* A chat's date rule: a hairline through the width with the day sitting in it. */
/* THE DAY RULE: a hairline through the feed with the day standing in the middle of it.
 *
 * The shape was already right and the TYPE was not: the label sat at row size in row ink, so it
 * read as a row that had lost its columns rather than as the line between two days. It takes the
 * feed's own micro-label - the grammar of the column heads above it - and the rule takes the same
 * hairline the rows are separated by, at the same strength, so the divider belongs to the table
 * instead of interrupting it.
 *
 * MORE AIR ABOVE THAN BELOW, because the line belongs to the day underneath it. */
.fl-day {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 18px 16px 6px;
  font: var(--w-medium) var(--t-2xs)/1 var(--ui);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--ink-faint);
  /* The glyphs land on the device lattice rather than half a pixel off it, which is the whole
     difference between a crisp small-caps label and a smudged one. */
  transform: translateZ(0);
}
.fl-day::before, .fl-day::after {
  content: '';
  flex: 1 1 auto;
  height: 1px;
  background: color-mix(in srgb, var(--line) 55%, transparent);
}

.fl-row {
  display: grid;
  /* Nine columns, and the MARKER HAS ONE OF ITS OWN. It was appended into an eight-column grid,
     which does not widen a grid - it wraps: every marked row pushed its timestamp onto a second
     line and the rows stopped being the same height. A fixed width rather than auto so the wallet
     and the time stay aligned down the column whether a row carries markers or not.

     THE SLACK BELONGS TO ONE PLACE, AND IT IS NOT THE MIDDLE OF THE SENTENCE. The price track held
     the 1fr, so at any real panel width the row broke into two islands with six hundred pixels of
     nothing between the price and the markers - the markers in particular read as floating rather
     than as belonging to the print. Every text column is fixed now and the WALLET carries the 1fr,
     right-aligned: the sentence stays tight and continuous, the metadata sits against the right
     edge, and the one gap in the row is the one that separates them. Fixed rather than max-content
     because each row is its OWN grid, so a content-sized track aligns with nothing above it. */
  /* THE PHRASE TRACK IS FIXED, AND THAT IS WHAT MAKES THIS READ AS AN INSTRUMENT.
     It was minmax(116px, max-content), and max-content is measured per row because every row is
     its own grid - so the track was 116px on a liquidation, 175px on a TWAP carrying a pace chip
     and 224px on an imminent warning. Measured over 1,718 rows of the live feed, the size column
     began at SIX different x positions across a 108px spread, and so did the price and the wallet.
     Numbers that do not form a vertical rule are the single loudest reason a feed reads as a list
     of sentences rather than a tape. 228px is the widest phrase the feed actually produces, with a
     little headroom; minmax(0, ...) so the one narrow band between 641px and 654px shrinks it
     rather than overflowing the row. */
  /* THE SLACK GOES TO THE EVENT, NOT TO A HOLE BEFORE THE WALLET. With the flexible track on the
     wallet, a 1440px panel put six hundred pixels of nothing between the price and the address:
     the row read as two islands rather than one line. Giving it to the phrase spreads the columns
     the way a table spreads them - names left, numbers right - and because every row shares one
     template the alignment is unaffected. The chip inside the phrase is pushed to the right of its
     track, so it still sits against the size it qualifies. */
  /* EVERY FLEXIBLE TRACK HAS TO BE ABLE TO REACH ZERO, or a grid does not shrink to fit its row -
     it overflows it. minmax(228px, 1fr) on the phrase and a fixed 150px on the wallet summed to a
     722px minimum, and this layout applies from 641px up: between 641 and 802 the wallet and the
     timestamp were painted outside the row, up to 145px past its right edge. The 1fr still takes
     every spare pixel at the widths this is actually read at, so the wide layout is unchanged; it
     just gives way now instead of overflowing. The wallet is the other one that can afford to,
     because an address is already elided - the same reasoning the narrow layout below uses. */
  grid-template-columns: 18px 84px minmax(0, 1fr) 92px 108px minmax(0, 150px) 46px;
  align-items: center;
  gap: 8px;
  padding: 5px 16px;
  font-size: var(--t-md);
  line-height: var(--lh-tight);
  /* With the ground quiet, the rule between rows is what makes this a table. One hairline at a
     third strength: legible as structure down a screen of forty rows, invisible as an object. */
  border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
  /* EIGHT COLUMNS, not nine: the marker track went with the emoji.

     THE GROUND IS THE SIZE, mixed from one number the row carries - see --fl-heat in
     flows-view.js. SQUARED, and that part took a look at the rendered feed to get right. The ramp
     itself is honest - log of notional, see heatOf - but painting it straight put a $70K print,
     which is an ordinary Tuesday, on a solid dark band: at the feed's own $50K floor the ramp
     already starts a third of the way up, so nearly every row arrived pre-shouted and the size
     that mattered had nothing left to be louder than. Squaring holds the bottom two thirds quiet
     and spends the ink where the ramp is nearly full - $50K lands at 9%, $100K at 18%, $377K at
     45%, and a million at all of it.

     AND SIXTEEN PER CENT AT THE TOP, NOT SEVENTY-EIGHT. This is the change that took the feed from
     reading as conditional formatting to reading as a tape. At 78 a large print was a solid block
     of brand colour with white text on it - a notification banner - and because the ramp starts a
     third of the way up at the feed's own floor, a screen of ordinary prints was a candy stripe of
     red and green bands. Nothing could be louder than anything else because everything already
     was.

     The reference terminal this is measured against paints NO row grounds at all: its colour lives
     on the numbers and in 3px bars, and its tables are hairlines on one near-black. Sixteen keeps
     the one thing a wash is genuinely good for - a million dollars is visible from across the room
     - and gives up the part that was decoration. A whale lands at 16%, $377K at 9%, $100K at 4%
     and the $50K floor at under 2%, which is to say: invisible until it matters. */
  background: color-mix(in srgb, var(--sell) calc(var(--fl-heat, 0) * var(--fl-heat, 0) * 16%), transparent);
  /* The whale's backdrop is absolutely positioned inside this, and the cells sit over it. */
  position: relative;
  isolation: isolate;
}
.fl-row.up {
  background: color-mix(in srgb, var(--buy) calc(var(--fl-heat, 0) * var(--fl-heat, 0) * 16%), transparent);
}
.fl-row > * { position: relative; z-index: 1; }
/* THE LEADING EDGE, at full strength from the first pixel of heat. The ground says how big; this
   says which side, and it has to be legible on a row that is barely tinted at all - so it is not
   on the squared ramp. Reaching full by a sixth of the way up, and absent at zero rather than
   drawn in a colour nobody can see. */
.fl-row { box-shadow: inset 2px 0 0 color-mix(in srgb, var(--sell-hi) calc(min(1, var(--fl-heat, 0) * 6) * 100%), transparent); }
.fl-row.up { box-shadow: inset 2px 0 0 color-mix(in srgb, var(--buy-hi) calc(min(1, var(--fl-heat, 0) * 6) * 100%), transparent); }
/* HOVER LAYERS OVER THE TINT, it does not replace it. `background` alone wiped a tiered row's
   colour on the way in and put it back on the way out, so passing the cursor down a run of them made
   every row flash from red to grey and back - the size of a print is not something that should
   change because the mouse went past it. A box-shadow overlay leaves the tint underneath, and
   inset_0 after the edge rule keeps the leading bar visible on the row being pointed at. */
.fl-row:hover {
  box-shadow: inset 0 0 0 100vmax var(--sel),
    inset 2px 0 0 color-mix(in srgb, var(--sell-hi) calc(min(1, var(--fl-heat, 0) * 6) * 100%), transparent);
}
.fl-row.up:hover {
  box-shadow: inset 0 0 0 100vmax var(--sel),
    inset 2px 0 0 color-mix(in srgb, var(--buy-hi) calc(min(1, var(--fl-heat, 0) * 6) * 100%), transparent);
}

/* A DEPOSIT TAKES NEITHER SIDE'S INK. Nothing was bought and nothing was sold: somebody arrived
   with money. It gets the chart's own VWAP blue - the line this application already draws for the
   thing that is neither a bid nor an ask - and no ground at all. `flat` rather than a third side,
   because it is the absence of a side.

   A literal, like the imminent row's amber above it, and for the same reason: these two are the
   only inks in the product that mean an EVENT KIND rather than a direction, and neither has a
   token because neither belongs to the buy/sell pair the palette is built around. */
.fl-row.flat { box-shadow: inset 2px 0 0 transparent; }
.fl-row.deposit .fl-size { color: #7c9cf5; }
/* AND A THIRD, for the same reason there is a second: staking is HYPE leaving the market or a
   seven-day queue handing it back, which is neither a bid nor an ask nor money arriving. The
   palette has no violet, so this is a literal like the amber and the blue above it - the three
   inks in this product that name an EVENT KIND rather than a direction, none of which belongs to
   the buy/sell pair everything else is built from. Far enough from the VWAP blue beside it to be
   told apart in a column read at speed, and nowhere near either green. */
.fl-row.stake .fl-size { color: #a78bfa; }
/* Where it came from, in the cell a price would have used. A wallet, so it is set like the other
   wallet in the row rather than like a number. */
.fl-price.fl-from { font-size: var(--t-sm); color: var(--ink-faint); }

/* THE ASSET'S OWN MARK, at the head of the row - the coin's badge, the equity's logo, the
   commodity's. It replaced a 3px bar in the side's colour, and the reason is that the bar was the
   feed's fourth way of saying something the row already says in two others: the words carry the
   side, and the whole row is painted with it. What the leftmost cell could NOT say was which
   market, which is the one thing the eye scans a flow feed for and the one thing four different
   tickers in identical grey capitals cannot answer at a glance.

   THE ART IS ITS OWN SHAPE. A coin's mark is already a disc, and setting one inside a bordered
   square is the double container that reads as a template rather than as a table - so the box is
   drawn only for the initials, which need an edge to be a mark rather than two loose letters.
   Both are the same size, so the column has one left edge either way. Same rule, same sizes and
   the same fallback as the watchlist's first column: one instrument, one grammar. */
.fl-mark {
  width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.fl-initials {
  width: 100%;
  height: 100%;
  border-radius: var(--r-sm);
  background: var(--panel-2);
  border: 1px solid var(--line);
  color: var(--ink-dim);
  font: var(--w-medium) var(--t-2xs)/1 var(--ui);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.fl-mark.art .fl-initials { display: none; }
.fl-logo {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  object-fit: contain;
  display: block;
}
.fl-venue-lg { width: 16px; height: 16px; display: block; }

.fl-coin {
  color: var(--ink);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-ticker);
  overflow: hidden;
  text-overflow: ellipsis;
}
/* One line. With a PnL chip beside it the phrase outgrew its track and wrapped, and because the
   row is a grid the wrapped chip painted outside its cell and across the row beneath. */
.fl-what {
  color: var(--ink-dim);
  display: flex;
  align-items: baseline;
  white-space: nowrap;
  /* The phrase holds the left of its track and the qualifier chip is pushed to the right of it,
     so a chip sits hard against the size it qualifies instead of floating wherever the sentence
     happened to end. On a row with no chip this is simply left-aligned. */
  justify-content: space-between;
  min-width: 0;
  overflow: hidden;
}
/* Wide shows the whole phrase, narrow shows the side alone. */
.fl-what-abbr { display: none; }
/* AND BETWEEN THE TWO, AN ELLIPSIS RATHER THAN A CUT. The abbreviated spelling only takes over
   below 640px, so at the widths in between the longest phrase in the feed - "Imminent Short
   Liquidation" - was clipped hard by the cell's overflow and printed as "Imminent Short Liquida".
   A word that is not a word reads as a bug; a word with an ellipsis reads as a narrow column. */
.fl-what-full { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Realised PnL rides with the phrase rather than taking a column of its own: only closes carry
   one, and a track that is empty on most rows is a gap down the middle of the feed. */
.fl-pnl {
  margin-left: 7px;
  padding: 1px 5px;
  border-radius: var(--r-sm);
  font-size: var(--t-sm);
  font-weight: var(--w-medium);
  font-variant-numeric: tabular-nums;
}
.fl-pnl.fl-win { color: var(--buy-hi); background: color-mix(in srgb, var(--buy) 14%, transparent); }
.fl-pnl.fl-loss { color: var(--sell-hi); background: color-mix(in srgb, var(--sell) 14%, transparent); }
/* AN IMMINENT ROW IS A FORECAST, and the feed must never let it be read as a report.
 *
 * So it takes none of the size ground - the wash on every other row means "this much was forced
 * out", and nothing has been - and wears the warning ink the liquidation map already uses for the
 * level under the pointer. Same colour, same meaning, two surfaces: attention, not outcome. */
.fl-row.imminent { background: color-mix(in srgb, #e8b14d 5%, transparent); }
.fl-row.imminent,
.fl-row.imminent.up { box-shadow: inset 2px 0 0 #e8b14d; }
.fl-row.imminent:hover,
.fl-row.imminent.up:hover { box-shadow: inset 0 0 0 100vmax var(--sel), inset 2px 0 0 #e8b14d; }
/* The PHRASE keeps its side colour - a long being liquidated is still a long, and that is the
   first thing read off the row. The warning is carried by the edge, the ground and the chip, which
   is enough to say "this has not happened yet" without taking the side away to say it. */
.fl-row.imminent .fl-size { color: #e8b14d; }
/* How far the mark still has to travel. Rides with the phrase the way a close's realised PnL
   does, because only these rows carry one. */
.fl-away {
  margin-left: 7px;
  padding: 1px 5px;
  border-radius: var(--r-sm);
  font-size: var(--t-sm);
  font-weight: var(--w-medium);
  font-variant-numeric: tabular-nums;
  color: #e8b14d;
  background: color-mix(in srgb, #e8b14d 14%, transparent);
}

/* A close is a decision, not an accident - it does not get the alarm tint a big liquidation wears. */
/* AN ANNOUNCED ORDER IS NOT A PRINT. A TWAP takes none of the size wash - the wash says how much
   was forced out of somebody, and nothing has been forced out yet - but it keeps the direction bar,
   because the one thing the row is about is which way eleven million dollars is going to lean for
   the next two hours. */
.fl-row.twap { background: none; }
.fl-row.twap, .fl-row.twap.up { box-shadow: inset 2px 0 0 var(--ink-faint); }
.fl-row.twap:hover, .fl-row.twap.up:hover { box-shadow: inset 0 0 0 100vmax var(--sel), inset 2px 0 0 var(--ink-faint); }
.fl-row.twap .fl-size { color: var(--ink); }
/* THE PACE CHIP IS NOT A WARNING. It shares the imminent row's shape because it does the same job -
   the qualifier that makes the number mean something - but not its amber, which this feed spends on
   one thing only: a position about to be closed out. An announcement wearing an alarm's colour is
   the feed crying wolf in a hue. */
.fl-pace { color: var(--ink-dim); background: var(--panel-2); border-color: var(--line); }

/* ---------------------------------------------------------------- arrival
 *
 * THE ONE PLACE THIS PRODUCT MOVES, and it is the feed, because the feed is the one surface where
 * something genuinely happens while you are looking at it. A print appearing with no transition is
 * a row that was always there as far as the eye is concerned - you notice the list is longer, not
 * that something arrived - and a feed nobody can see arriving is a feed that may as well be a
 * table.
 *
 * WHAT IT IS NOT: no bounce, no slide across, no flash of colour, nothing that repeats. The row
 * rises four pixels into place and its ink comes up with it, once, in a quarter of a second, on
 * the curve a released object follows. That is the whole effect. A trading feed that celebrates
 * its own arrivals is a toy, and the loudest AI tell a piece of motion can wear is being
 * proportionally louder than the thing it announces.
 *
 * TRANSFORM AND OPACITY ONLY. The row occupies its full height from the first frame, so the list
 * beneath it never moves and the reader's scroll position is never touched - see the note on
 * appendRow. Anything that animated height or margin would reflow the whole stack sixty times a
 * second and jump the feed under a reader's cursor.
 */
@keyframes fl-arrive {
  from { opacity: 0; transform: translate3d(0, 4px, 0); }
  to { opacity: 1; transform: none; }
}
.fl-row.fresh {
  animation: fl-arrive .24s cubic-bezier(.22, 1, .36, 1) both;
  /* Its own layer for the duration, so a burst of arrivals does not repaint the rows above them. */
  will-change: opacity, transform;
}
/* A RUN OF TWENTY PRINTS must not become twenty staggered animations - the stagger is capped in
   the view at four, and the rest arrive with the last of them. */
.fl-row.fresh.d1 { animation-delay: .04s; }
.fl-row.fresh.d2 { animation-delay: .08s; }
.fl-row.fresh.d3 { animation-delay: .12s; }


@media (prefers-reduced-motion: reduce) {
  .fl-row.fresh { animation: none; }
}

.fl-row.close { background: none; }
/* THE COLOUR IS ON THE NUMBER, AND ONLY ON THE NUMBER.
 *
 * "The typography and the weight structure and the wallet address text just looks AI sloppy."
 * It did, and the cause was a column that could not decide what it was: the PHRASE was red or
 * green or blue or violet by kind, then white again past a size threshold, while the size beside
 * it - the thing the row is actually about - was the same white on every row. Reading down, the
 * loudest ink in the feed marked the words that repeat and the quietest marked the one number
 * that does not.
 *
 * The reference paints no row in colour at all: its ink lives on the numbers. So the phrase is one
 * dim tone on every row of every kind, and the SIZE carries the side. */
.fl-what { color: var(--ink-dim); }
.fl-row.up .fl-size { color: var(--buy-hi); }
.fl-row.down .fl-size { color: var(--sell-hi); }

.fl-size {
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  font-weight: var(--w-medium);
  text-align: right;
}
/* THE COLUMN HEADER, which is the reference's own grammar and this feed's was missing.
   Uppercase, letter-spaced, dim, and it uses the row's own template so a heading cannot drift from
   the column under it. It also earns the removal of the connective words: "at" and "liq" were
   grammar rather than information, and a column labelled PRICE does not need a preposition. */
.fl-colhead {
  position: sticky;
  top: 0;
  z-index: 3;
  padding-top: 7px;
  padding-bottom: 6px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}
.fl-colhead:hover { box-shadow: none; }
.fl-colhead .r { text-align: right; }
/* The wallet. Sourcing the feed from Hyperliquid is what makes this column possible at all, so it
   is a control rather than a label: it goes somewhere. */
.fl-who {
  padding: 0;
  /* One tone down the column. A wallet is metadata on every row - the largest print in the feed
     does not make its address more of an address. */
  color: var(--ink-faint);
  font-family: var(--mono);
  font-size: var(--t-xs);
  font-weight: var(--w-regular);
  font-variant-numeric: tabular-nums;
  text-align: right;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* THE ROW IS THE CONTROL. Every row that names a wallet opens it, so the pointer says so across
   the whole width rather than over the thirty pixels of address at the far end - and the address
   brightens with the rest of the row instead of being a target of its own. */
.fl-row.opens { cursor: pointer; }
.fl-row.opens:hover .fl-who { color: var(--ink); }
/* Right-aligned, like the size beside it: two number columns reading down as two clean rules is
   the whole difference between a table and a list. */
.fl-price { color: var(--ink-dim); font-variant-numeric: tabular-nums; text-align: right; }
.fl-time { color: var(--ink-faint); font-variant-numeric: tabular-nums; text-align: right;
  font-size: var(--t-sm); opacity: 0.8; }
/* The repeated minute is hidden, not removed: the cell holds its column so the rows above and
   below stay on the same grid, and pointing at the row brings its stamp back. */
.fl-row.same-min .fl-time { opacity: 0; }
.fl-row.same-min:hover .fl-time { opacity: 0.55; }

/* ------------------------------------------------------ how big it was, as ink

   There were three tiers here, each a flat tint at 4 / 8 / 12 per cent with a hard rule at the
   leading edge, plus up to three emoji in a column of their own. The owner removed both after
   putting Coinglass's real-time feed beside ours, and the reasons hold up: emoji are the loudest
   AI tell a trading tool can wear, they render as tofu boxes wherever colour-emoji coverage is
   missing - which reads as a broken font rather than as a marker - and three steps flatten a
   quantity that is genuinely smooth. A $180K print and a $900K print were the same row.

   Size is the continuous ground above now. What is left here is only what a ramp CANNOT carry.

   TYPE AND CONTRAST, which do not interpolate. Past HOT_USD the ground is dark enough that the
   feed's dimmed inks stop being readable on it, so the whole row comes up to full ink at once;
   the size takes the extra weight because that is the thing being read. */
/* ONE COLUMN, ONE TREATMENT. Every one of these used to change with the row's size - the phrase
   and the price came up to full ink, the wallet and the stamp changed tone, the size changed
   weight - so a screen of forty rows carried three different greys in the wallet column and two in
   every other. That is the "weight structure" that reads as generated: emphasis applied to a whole
   row instead of to the one cell that earned it. Only the SIZE answers to size now. */
.fl-row.hot .fl-size { font-weight: var(--w-semi); }
.fl-row.hot { border-bottom-color: var(--line); }

/* -------------------------------------------------------------------- the whale

   A million dollars, forced out of one account in one print. It happens a handful of times a day,
   and the owner's instruction was that it should not read as the row above it with more colour on
   it - "some background gif, a whale swimming in the ocean, and then it is super high opacity,
   bright, vibrant". So the row stops being a line of a feed and becomes an event: full ground,
   white text on it, a hairline top and bottom, and something moving behind the words.

   WHY THE BACKDROP IS DRAWN AND NOT A STOCK CLIP. Every clip that could go here is either
   somebody else's licensed footage or generated art, and this product's rule is human brand assets
   or nothing. What is drawn instead is what a clip of water would give you anyway - slow light
   moving across a dark surface. If the owner picks a real clip it drops in without touching this
   file:

       :root { --fl-whale-media: url("/media/whale.webp"); }

   and the drawn sweep stands down for it. Any still or animated format the browser will accept in
   a background-image works, gif and webp included. */
/* NO RULE AROUND IT. The event used to be drawn with a coloured hairline top and bottom, which on
   a quiet ground reads as an outlined box - a row that looks selected rather than large. The
   backdrop and the white ink are the treatment; the border was the part that made it look made
   rather than designed. */
.fl-row.mega { --fl-heat: 1; }
/* WHITE, not brightened dim. At full ground there is one legible ink and this is it - a whale row
   is the only place in the feed where the text is lighter than its ground is dark. */
/* A whale keeps its ground and its backdrop; what it does not get is a second typography. The
   ticker and the size come up - those two are the event - and the rest of the row is set exactly
   as every other row is. */
.fl-row.whale .fl-coin,
.fl-row.whale .fl-size { color: #fff; font-weight: var(--w-semi); }

.fl-media {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  /* The clip, when there is one. `none` is a valid background-image and paints nothing, so the
     default costs a layer and no pixels. */
  background-image: var(--fl-whale-media, none);
  background-size: cover;
  background-position: center;
  /* TEXTURE, NOT GLARE - and this was set by looking at the render rather than by taste. The
     first pass ran a 55%-white band at three-tenths opacity in screen blend, which put a bright
     smear through the middle of the row and took the size and the price down with it. A backdrop
     a reader has to look past has taken the row away from the number on it. */
  opacity: 0.5;
  /* Soft-light rather than screen: it lifts and drops the ground the sweep crosses instead of
     adding white to it, so the row keeps its own colour and the movement is in the shading. */
  mix-blend-mode: soft-light;
}
/* Two broad, soft bands crossing at rates that do not divide into each other, so there is no loop
   to catch. Wide and heavily blurred on purpose: a narrow band is a highlight travelling over a
   surface, which reads as a loading shimmer, and a wide one is the surface itself moving. */
.fl-media::before,
.fl-media::after {
  content: '';
  position: absolute;
  top: -70%;
  bottom: -70%;
  width: 55%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .62) 45%, transparent);
  filter: blur(16px);
}
/* ONLY NEAR THE LIVE END, and this is a performance fix with a number behind it.

   The backdrop was animating on EVERY million-dollar row in the scrollback. Measured on production
   with 2,031 rows loaded and 114 of them carrying one:

                     idle median   idle frames >20ms   scroll median   scroll frames >50ms
     animating          34.7ms         119 of 119         55.6ms           91 of 159
     static              6.9ms           0 of 119         27.8ms           15 of 159

   Five times the frame time doing nothing, and half the frame rate while scrolling - a hundred and
   fourteen blurred, soft-light-blended gradients crossing forever in a list nobody is looking at.
   A feed that scrolls at eighteen frames a second is the most visceral kind of cheap there is.

   The backdrop MEANS "this has just landed", so the rows it belongs on are the ones at the live
   end. Everything older keeps the sheen and loses the motion, which is what the reduced-motion
   rule below already does for everyone who asks for it. Roughly three rows animate instead of a
   hundred and fourteen.

   -of-type rather than -child because every child of the stack is a div - rows and day rules
   alike - so this is honestly "the last eighty things in the feed". */
.fl-stack > :nth-last-of-type(-n + 80) .fl-media::before { animation: fl-swim 11s linear infinite; }
.fl-media::after {
  width: 30%;
  opacity: .55;
}
/* A negative delay starts it mid-crossing, so a row that has only just arrived is already in
   motion rather than beginning on an empty ground. */
.fl-stack > :nth-last-of-type(-n + 80) .fl-media::after { animation: fl-swim 17s linear infinite -6s; }
@keyframes fl-swim {
  from { transform: translateX(-160%) skewX(-14deg); }
  to { transform: translateX(400%) skewX(-14deg); }
}
/* The one moving thing in the application, so it is also the one thing that has to be able to
   stop. Nothing is lost when it does - the row is still the brightest in the feed. */
@media (prefers-reduced-motion: reduce) {
  .fl-media::before, .fl-media::after { animation: none; }
}

/* Only present while the reader is away from the bottom. */
.fl-jump {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 13px;
  background: var(--panel);
  border: 1px solid var(--line-2);
  border-radius: 999px;
  color: var(--ink);
  font: inherit;
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .42);
}
.fl-jump:hover { border-color: var(--ink-faint); }

@media (max-width: 640px) {
  /* ONE TRACK PER VISIBLE CHILD. `display: none` takes a grid item out of the flow completely, so
     a template holding a 0-width slot for the "at" it hides shifted every cell after it one track
     left: the price landed in the zero-width slot and only showed because nothing clipped it, the
     time took the flexible track meant for the price, and the last track sat empty. The narrow
     layout names exactly the columns it actually has - seven, with the wallet. */
  .fl-row {
    /* The price needs room for its longest real value - 0.00439971 and 77,270.7399 both occur -
       so it gets a floor of 76px and the wallet gives way instead; an address is already elided,
       and losing two characters of it costs nothing a price cannot afford to lose. */
    /* Seven columns here, not eight. At this width the connective "at" is the one cell that can
       go: it is grammar rather than information, and the price it separated is still hard against
       the size it belongs to. Everything else in the sentence survives. */
    /* THE WALLET IS THE TRACK THAT GIVES, and it has to be ABLE to. Every track here was a fixed
       width except the price, so at a real phone width - 390px, not the 640 this query opens at -
       the fixed ones summed past the row and the timestamp was drawn twenty-eight pixels outside
       it, off the screen. A grid does not shrink a fixed track to make its row fit; it overflows.

       minmax(0, 76px) is the fix and it is the honest one: the address is the only cell here that
       is already elided, so it is the only cell that can lose characters without losing a fact.
       It holds its full 76px wherever there is room and gives way where there is not, and the
       time, which is four characters and cannot be abbreviated, stops being the thing pushed off
       the edge. */
    grid-template-columns: 14px 56px 44px 74px minmax(64px, 1fr) minmax(0, 76px) 34px;
    gap: 5px;
    padding: 6px 10px;
  }
  /* NO COLUMN HEADINGS ON A NARROW PANEL. Below this width the row stops being a table row - the
     phrase drops to one word and, on a phone, the whole thing folds onto two lines with named grid
     areas the heading's cells know nothing about, so its seven labels auto-placed into the fold and
     printed PRICE over WALLET. A two-line card does not need column headings; it reads as a card. */
  .fl-colhead { display: none; }
  /* Overflow, not just shrink: min-width alone lets a grid item paint outside its own track. */
  .fl-price { overflow: hidden; text-overflow: ellipsis; }
  /* The verb goes, the side stays - see fl-what-abbr. */
  .fl-what-full { display: none; }
  .fl-what-abbr { display: inline; }
  .fl-what { min-width: 0; overflow: hidden; white-space: nowrap; }
  /* THE QUALIFIER GOES BEFORE THE SENTENCE DOES. At this width the phrase cell holds a word and
     the chip beside it was being cut mid-number - "2.5" and "30" with no unit, which states
     nothing and looks broken. The distance on an imminent row survives because it IS the row's
     news; a TWAP's pace is the second-best fact on a row whose notional is already printed. */
  .fl-pace { display: none; }
  /* A REALISED PROFIT CUT TO "+" IS NOT A NUMBER. Same rule as the pace chip: the close row still
     states what closed, which way and how much, and the figure it cannot fit is on the row at any
     width the panel is actually read at. */
  .fl-pnl { display: none; }
  .fl-who { min-width: 0; }
  .fl-price { min-width: 0; }
  .fl-panel { width: min(320px, calc(100vw - 24px)); }
}

/* A TRUNCATED WORD IS WORSE THAN NO WORD. Below this the subtitle has room for "Hyperliq...",
   which reads as a header that broke rather than as a header that was shortened - and the venue
   is named on every row of the feed underneath it anyway. The size filter stays, because that one
   is a control and says what is being hidden. */
@media (max-width: 460px) {
  .fl-status { display: none; }
}

/* ------------------------------------------------------------ add widget drawer
   The reference dashboard's right-hand drawer: categories, a search, cards with a skeleton
   of the widget and an "Added" mark on the ones already placed. */
.aw {
  position: fixed; top: 0; right: 0; bottom: 0; width: 412px; z-index: 60;
  display: flex; flex-direction: column;
  background: var(--panel); border-left: 1px solid var(--line);
  /* NO FLOAT. A sheet anchored to the screen's edge is held by that edge and its hairline; a
     24px-blur shadow under it is the "card hovering over a page" read, which is the most common
     single tell of a generated interface. Windows float and keep their elevation; this does not. */
}
.aw-head { display: flex; align-items: baseline; padding: 16px 16px 2px; }
.aw-title { margin: 0; flex: 1 1 auto; font: var(--w-semi) var(--t-lg) var(--ui); color: var(--ink); letter-spacing: var(--tr-tight); }
.aw-x {
  /* type-exempt: a close glyph rather than a word - it takes no tracking, and the size is the
     mark's own rather than a heading's. It stands at the window-head rung: every close control
     in the product is the same mark at the same size, and one drawer closing larger than its
     windows is a rung nobody asked for. */
  background: none; border: 0; color: var(--ink-faint); font-size: var(--t-lg); cursor: pointer;
  line-height: 1; padding: 0 2px; align-self: flex-start;
}
.aw-x:hover { color: var(--ink); }
.aw-sub { margin: 0 16px 12px; color: var(--ink-dim); font: var(--t-md) var(--ui); }

/* THE CATEGORY BAR IS A RULE WITH A MARKER ON IT, not a row of pills.
 *
 * The reference dashboard puts its categories on a hairline and slides a two-pixel marker under
 * the live one. Pills were the wrong grammar for the same set of words: a pill is a button you
 * press, and these are places you are - so the pill's ground competed with the cards below it for
 * the one piece of emphasis on the panel.
 *
 * The marker is INK, not a brand colour. The reference underlines in orange; this application's
 * tokens say no brand accent in the chrome and colour spent on data only, and one orange rule at
 * the top of a grey frame is enough to make the whole thing read as a web app. Ink at full
 * strength against dimmed neighbours is the same signal without the cost. */
.aw-tools {
  display: flex; align-items: stretch; gap: 10px;
  padding: 0 16px; border-bottom: 1px solid var(--line);
}
/* Six categories in a four-hundred-pixel drawer is a genuinely tight fit, so the strip scrolls -
   and SAYS it does, by fading its last few pixels instead of guillotining a word. A cut-off
   "TOOL" reads as a layout that broke; a fade reads as a strip with more on it. */
.aw-cats {
  display: flex; gap: 12px; flex: 1 1 auto;
  overflow-x: auto; scrollbar-width: none;
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 18px), transparent);
  mask-image: linear-gradient(90deg, #000 calc(100% - 18px), transparent);
}
.aw-cats::-webkit-scrollbar { display: none; }
/* type-exempt: a TAB, not a caption. It is pressable, it latches, and it sits at the drawer's own
   tab size - a caption's ten pixels would make the one control on this row the smallest thing in
   the drawer. Uppercase because the drawer's tabs are set that way, not because it names a group. */
.aw-cat {
  position: relative;
  background: none; border: 0; padding: 9px 0 10px;
  color: var(--ink-faint); font: var(--w-semi) var(--t-sm) var(--ui);
  letter-spacing: var(--tr-label); text-transform: uppercase;
  cursor: pointer; white-space: nowrap;
  transition: color var(--t-tap) var(--ease);
}
.aw-cat:hover { color: var(--ink-dim); }
.aw-cat.on { color: var(--ink); }
/* Drawn on the tab and pulled down onto the container's own hairline, so the marker sits IN the
   rule rather than above it. */
.aw-cat.on::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: var(--ink);
  border-radius: 1px;
}

.aw-searchrow { padding: 10px 16px; border-bottom: 1px solid var(--line); }
/* The hairline, the corner, the ground and the focus are the field role - carried by the wrapper
   because this field sets its glyph beside the text rather than behind it. Twenty-six was a
   height nothing else in the product stood at. */
.aw-searchwrap {
  align-self: center;
  flex: 1 1 auto;
  display: flex; align-items: center; gap: 6px;
  padding: 0 8px;
  color: var(--ink-faint);
}
.aw-searchwrap:focus-within { color: var(--ink-dim); }
.aw-searchwrap svg { flex: 0 0 auto; }
.aw-search {
  flex: 1 1 auto; min-width: 0;
  padding: 0; background: none; color: var(--ink);
  border: 0; font: var(--t-md) var(--ui); outline: none;
}
.aw-search::placeholder { color: var(--ink-faint); }

.aw-body { flex: 1 1 auto; overflow-y: auto; padding: 14px 16px 18px; }
.aw-section {
  margin: 16px 0 9px;
}
.aw-section:first-child { margin-top: 0; }
.aw-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
/* A card has a face at rest, so on hover its face lightens and its ink lights - the hover law in
   style.css, applied rather than restated. The ground does NOT change to the selection colour,
   which was reading as "this one is chosen" on a surface where nothing is chosen yet. */
.aw-card {
  display: flex; flex-direction: column; gap: 9px; text-align: left;
  padding: 11px 11px 12px; background: var(--panel-2);
  border: 1px solid var(--line); border-radius: var(--r-surface);
  color: var(--ink); cursor: pointer;
  transition: border-color var(--t-tap) var(--ease), background var(--t-tap) var(--ease);
}
.aw-card:hover { border-color: var(--line-2); background: var(--panel-3); }
.aw-cardhead { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.aw-cardtitle { font: var(--w-semi) var(--t-md) var(--ui); }
/* "Added" is a STATE, so it gets a chip - a bare grey word at the end of a title reads as part of
   the title. */
.aw-added {
  flex: 0 0 auto;
  padding: 1px 6px; border-radius: 999px;
  background: var(--sel); color: var(--ink-dim);
  /* A CHIP is a mark, not a caption over a group, so it takes the badge tracking. */
  font: var(--w-semi) var(--t-2xs) var(--ui); letter-spacing: var(--tr-micro); text-transform: uppercase;
}
.aw-desc { color: var(--ink-dim); font: var(--t-sm) var(--ui); line-height: var(--lh-ui); }
/* The preview sits on the panel's own ground rather than on the card's, so the card reads as a
   frame around a picture instead of as two flat rectangles. */
.aw-skel { display: flex; gap: 3px; height: 50px; padding: 5px; border-radius: var(--r-sm); background: var(--bg); }
.aw-skel i { display: block; background: var(--line-2); border-radius: 2px; flex: 1 1 0; }
.aw-skel-chart { align-items: flex-end; }
.aw-skel-chart i:nth-child(1) { height: 40%; } .aw-skel-chart i:nth-child(2) { height: 70%; }
.aw-skel-chart i:nth-child(3) { height: 55%; } .aw-skel-chart i:nth-child(4) { height: 90%; }
.aw-skel-chart i:nth-child(5) { height: 65%; } .aw-skel-chart i:nth-child(6) { height: 80%; }
.aw-skel-bars { flex-direction: column; align-items: flex-end; }
.aw-skel-bars i { width: 60%; height: 4px; flex: 0 0 auto; }
.aw-skel-bars i:nth-child(odd) { width: 35%; }
.aw-skel-rows { flex-direction: column; }
.aw-skel-rows i { height: 5px; flex: 0 0 auto; }

/* A BOOK, from across the room: sizes running out from a spine, sells above and buys below. The
   colours are the application's own buy and sell at a quarter strength - this is the one place a
   preview may carry colour, because the colour IS the distinguishing fact. */
.aw-skel-book { flex-direction: column; gap: 2px; }
.aw-skel-book i {
  display: flex; align-items: center; justify-content: center;
  gap: 2px; height: 5px; flex: 0 0 auto; background: none;
}
.aw-skel-book u, .aw-skel-book s {
  display: block; height: 100%; border-radius: 2px; text-decoration: none;
}
.aw-skel-book u { flex: 0 0 auto; background: var(--line-2); }
.aw-skel-book s { flex: 0 0 auto; background: var(--line-2); }
.aw-skel-book i:nth-child(1) u { width: 12%; } .aw-skel-book i:nth-child(1) s { width: 30%; background: color-mix(in srgb, var(--sell) 55%, transparent); }
.aw-skel-book i:nth-child(2) u { width: 8%; }  .aw-skel-book i:nth-child(2) s { width: 46%; background: color-mix(in srgb, var(--sell) 55%, transparent); }
.aw-skel-book i:nth-child(3) u { width: 22%; } .aw-skel-book i:nth-child(3) s { width: 18%; background: color-mix(in srgb, var(--sell) 40%, transparent); }
.aw-skel-book i:nth-child(4) u { width: 40%; background: color-mix(in srgb, var(--buy) 55%, transparent); } .aw-skel-book i:nth-child(4) s { width: 10%; }
.aw-skel-book i:nth-child(5) u { width: 26%; background: color-mix(in srgb, var(--buy) 45%, transparent); } .aw-skel-book i:nth-child(5) s { width: 6%; }

/* A WATCHLIST: a name on the left, a number on the right, four times. */
.aw-skel-watch { flex-direction: column; gap: 4px; justify-content: center; }
.aw-skel-watch i {
  display: flex; align-items: center; justify-content: space-between;
  height: 5px; flex: 0 0 auto; background: none;
}
/* The row leads with the asset's mark now, so the preview does too. A silhouette that has lost
   the one element the row is recognised by is a silhouette of the previous version. */
.aw-skel-watch i::before {
  content: ''; width: 5px; height: 5px; border-radius: 50%;
  background: var(--line-2); flex: 0 0 auto; margin-right: 4px;
}
.aw-skel-watch u, .aw-skel-watch s {
  display: block; height: 100%; border-radius: 2px; background: var(--line-2); text-decoration: none;
}
.aw-skel-watch u { width: 30%; }
.aw-skel-watch s { width: 22%; }
.aw-skel-watch i:nth-child(even) u { width: 40%; }
.aw-skel-watch i:nth-child(even) s { width: 17%; }
/* NEWS: a square mark, a headline that runs most of the width, and a short pill under it. That
   silhouette is the row itself at a third of the size, which is the whole point of these previews:
   four widgets that all preview as grey bars distinguish nothing. */
.aw-skel-news { flex-direction: column; gap: 5px; justify-content: center; }
.aw-skel-news i {
  display: flex; align-items: center; gap: 4px;
  height: 8px; flex: 0 0 auto; background: none;
}
.aw-skel-news u, .aw-skel-news s {
  display: block; border-radius: 2px; background: var(--line-2); text-decoration: none;
}
.aw-skel-news u { width: 8px; height: 8px; border-radius: 2px; flex: 0 0 auto; }
.aw-skel-news s { height: 4px; width: 70%; }
.aw-skel-news i:nth-child(even) s { width: 52%; }
.aw-skel-news i:nth-child(3) s { width: 30%; border-radius: 2px; background: var(--line); }

.aw-skel-table { flex-direction: column; }
.aw-skel-table i { height: 7px; flex: 0 0 auto; }
.aw-skel-table i:first-child { background: var(--ink-faint); }

/* THE PANEL'S TITLE BAR AS ITS HANDLE. A view mounted as a grid cell boots bare, so its header is
   what someone grabs to move the panel - the same drag a tab starts. Nothing about it changes but
   the cursor: a header that grew a grip would be chrome invented for one gesture. */
.panel-head.panel-handle { cursor: grab; }

/* THE PANEL HEADER, ONE PATTERN SITE-WIDE: 44px tall, one ground step up, a 1px divider, the
   back chevron on the left. Every view panel (DOM, TPO, news, flows, liqmap, wallet, watchlist)
   stands on this box; a view may add furniture to the row but may not re-shape it. */
.panel-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 12px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}
.panel-head.panel-handle:active { cursor: grabbing; }

.sh-viewx {
  position: absolute; top: 6px; right: 6px; z-index: 3;
  width: 22px; height: 22px; border: 0; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--panel) 85%, transparent); color: var(--ink-dim);
  font-size: 16px; /* type-exempt: the close glyph in a 22px button - glyph sizing, not type */
  line-height: 1; cursor: pointer; opacity: 0;
}
.sh-cell:hover .sh-viewx { opacity: 1; }
.sh-viewx:hover { color: var(--ink); background: var(--sel); }


/* A VOLUME SURGE IS NOT A PRINT, so it takes the quiet treatment the other non-print rows take:
   no size wash, and its ink says which way the five minutes netted rather than which side was
   taken out. The leading edge is the buy/sell pair, because that is what the row means. */
.fl-row.surge { background: none; }
/* ================================================================== FLOWS QUIET
 *
 * HIS ASK: no logos on the liquidation rows, and the colour pulled back to the
 * lighter pair he liked from the short-lived CALM pass - not the dot, just the ink.
 *
 * THE MARK COLUMN GOES. The coin name already says the market; the badge repeated
 * it in colour. With the mark gone the row is one track shorter, so the grid's
 * first track goes with it at both widths - otherwise every cell slides left into
 * an 18px box and the tape loses its columns. */

.fl-root .fl-mark { display: none; }
.fl-root .fl-row {
  grid-template-columns: 84px minmax(0, 1fr) 92px 108px minmax(0, 150px) 46px;
}
@media (max-width: 640px) {
  .fl-root .fl-row {
    grid-template-columns: 56px 44px 74px minmax(64px, 1fr) minmax(0, 76px) 34px;
  }
}

/* THE LIGHTER PAIR, as before: the side colour lifted 55% toward white, on the size,
   the leading edge, the size wash and the PnL chips. No dot this time. */
.fl-root {
  --fl-up: color-mix(in srgb, var(--buy-hi) 55%, #ffffff);
  --fl-down: color-mix(in srgb, var(--sell-hi) 55%, #ffffff);
}
.fl-root .fl-row {
  background: color-mix(in srgb, var(--fl-down) calc(var(--fl-heat, 0) * var(--fl-heat, 0) * 16%), transparent);
  box-shadow: inset 2px 0 0 color-mix(in srgb, var(--fl-down) calc(min(1, var(--fl-heat, 0) * 6) * 100%), transparent);
}
.fl-root .fl-row.up {
  background: color-mix(in srgb, var(--fl-up) calc(var(--fl-heat, 0) * var(--fl-heat, 0) * 16%), transparent);
  box-shadow: inset 2px 0 0 color-mix(in srgb, var(--fl-up) calc(min(1, var(--fl-heat, 0) * 6) * 100%), transparent);
}
.fl-root .fl-row:hover {
  box-shadow: inset 0 0 0 100vmax var(--sel),
    inset 2px 0 0 color-mix(in srgb, var(--fl-down) calc(min(1, var(--fl-heat, 0) * 6) * 100%), transparent);
}
.fl-root .fl-row.up:hover {
  box-shadow: inset 0 0 0 100vmax var(--sel),
    inset 2px 0 0 color-mix(in srgb, var(--fl-up) calc(min(1, var(--fl-heat, 0) * 6) * 100%), transparent);
}
.fl-root .fl-row.up .fl-size { color: var(--fl-up); }
.fl-root .fl-row.down .fl-size { color: var(--fl-down); }
.fl-root .fl-pnl.fl-win { color: var(--fl-up); background: color-mix(in srgb, var(--fl-up) 14%, transparent); }
.fl-root .fl-pnl.fl-loss { color: var(--fl-down); background: color-mix(in srgb, var(--fl-down) 14%, transparent); }

/* The heading row was built with an empty spacer where the mark track sat; with the mark gone
   the spacer stole the first track and TIME wrapped underneath. It goes the way of the mark. */
.fl-root .fl-colhead span:first-child { display: none; }

/* The heading row went the way of the mark: the columns are self-evident. */
.fl-root .fl-colhead { display: none; }

/* EVERY COLUMN OF DIGITS IS TABULAR - the ladder, the tape, the level values - so a digit
   changing width can never shift its neighbours. */
.bookview-body, .fl-price, .fl-size, .tpf-dvalue { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------------------------------
 * SHARED LIST STATES (.lstate) - the one empty/loading/error pattern, built by lib/list-states.js.
 * Icon in a soft disc, one message, optional action. A skeleton (`.lstate-sk`) is the loading
 * state: bars matching the list's own row shape, pulsing - never a spinner. Colours, radii and
 * type come from the DS token layer; nothing here re-declares a raw value the tokens already own.
 * ------------------------------------------------------------------------------------------- */
.lstate { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; padding: 48px 24px; text-align: center; color: var(--ink-dim); }
.lstate-fig { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%; background: color-mix(in srgb, var(--ink) 5%, transparent); color: var(--ink-faint); }
.lstate-msg { font-size: var(--t-sm, 12.5px); line-height: 1.5; max-width: 30em; }
.lstate-act { margin-top: 2px; border: 0; border-radius: var(--ds-radius-sm, 6px); background: var(--sel, color-mix(in srgb, var(--ink) 8%, transparent)); color: var(--ink); font: inherit; font-size: var(--t-sm, 12.5px); font-weight: 600; padding: 8px 16px; cursor: pointer; }
.lstate-act:active { background: color-mix(in srgb, var(--ink) 14%, transparent); }
.lstate-error .lstate-fig { color: var(--sell-hi, #f23645); background: color-mix(in srgb, var(--sell-hi, #f23645) 10%, transparent); }
.lstate-sk { padding: 8px 0; }
.lstate-sk-row { display: flex; align-items: center; gap: 12px; padding: 6px 16px; }
.lstate-sk-dot { flex: 0 0 auto; width: 32px; height: 32px; border-radius: 50%; }
.lstate-sk-lines { flex: 1 1 auto; display: flex; flex-direction: column; gap: 6px; }
.lstate-sk-bar { height: 10px; border-radius: 5px; }
.lstate-sk-dot, .lstate-sk-bar { background: color-mix(in srgb, var(--ink) 7%, transparent); animation: lstatePulse 1.4s ease-in-out infinite; }
.lstate-sk-bar:last-child { width: 40%; }
@keyframes lstatePulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }