  :root {
    --bg: #0b0e11;
    --panel: #161a1e;
    --border: #2b3139;
    --text: #eaecef;
    --dim: #848e9c;
    --green: #0ecb81;
    --red: #f6465d;
    --green-bg: rgba(14, 203, 129, 0.12);
    --red-bg: rgba(246, 70, 93, 0.12);
    --accent: #f0b90b;
    /* The scrollbar thumb, and the same again for hover. One step lighter than
       --border rather than --border itself: a thumb the colour of the panel
       edge it runs beside cannot be told from it. */
    --scroll: #3b434d;
    --scroll-hi: #5a6472;
    /* The line between two columns of the Accounts table. One step darker than
       --border: that line runs inside a panel whose own edge is --border, and
       two lines of the same colour a few pixels apart read as one mistake. */
    --sep: #21272f;
    /* The space between two panels. It used to be the grid's `gap`; it is now
       a track of its own with a drag handle in it, and it is the same 8px, so
       nothing moved. */
    --gut: 8px;
    /* Says the page is dark, which is what every control the browser draws
       itself reads before it picks its own colours: the <select> popup, the
       form controls, the caret, and any scrollbar not caught by the rules
       below. Without it the browser draws a light control on a dark page. */
    color-scheme: dark;
  }
  * { box-sizing: border-box; margin: 0; padding: 0; }

  /* Scrollbars. What a browser draws by default here is a white track with an
     arrow button at each end, which against this page reads as a rendering
     fault rather than as a control.
     The two engines are styled through different properties and, where a
     browser understands both, the standard `scrollbar-*` properties win and the
     `::-webkit-scrollbar` rules are dropped. That is not academic: measured in
     Chromium 141 on Linux, `scrollbar-width: thin` + `scrollbar-color` gives a
     15px bar WITH the arrow buttons still on it, while the pseudo-elements give
     a 10px bar with none. So the standard properties are given only to the
     engines that have no pseudo-elements to use instead, which means Firefox.
     Chromium and Safari get the pseudo-elements. Both paths end at the same
     scrollbar: no buttons, no track, a rounded thumb.
     `scrollbar-width` and `scrollbar-color` are inherited, so :root reaches
     every scroller on the page; the pseudo-elements are not inherited, which is
     why theirs are written against no selector at all: that form applies to
     every element. */
  @supports not selector(::-webkit-scrollbar) {
    :root { scrollbar-width: thin; scrollbar-color: var(--scroll) transparent; }
  }
  ::-webkit-scrollbar { width: 10px; height: 10px; }
  ::-webkit-scrollbar-track { background: transparent; }
  ::-webkit-scrollbar-thumb { background: var(--scroll); border-radius: 5px; min-height: 24px; }
  /* Firefox has no hover state for a scrollbar thumb. `scrollbar-color` is one
     colour and there is no pseudo-class for it. So this is a Chromium and
     Safari nicety rather than something both engines do. */
  ::-webkit-scrollbar-thumb:hover { background: var(--scroll-hi); }
  /* The arrow buttons at each end, which is the part that looks broken. */
  ::-webkit-scrollbar-button { display: none; }
  /* Where a vertical and a horizontal bar meet. Left alone it is drawn in the
     browser's own colour, which is the white this is all here to remove. */
  ::-webkit-scrollbar-corner { background: transparent; }
  /* `hidden` has to win, and without this it does not. A browser hides the
     attribute with `[hidden] { display: none }` in its own stylesheet, and any
     rule in this one beats it whatever its specificity, because an author
     stylesheet outranks the browser's. So `.route { display: flex }` was
     quietly keeping the route buttons on screen for every operator running no
     inbox, the one case the code sets `hidden` to cover, and #anchors below
     would have had the same problem. */
  [hidden] { display: none !important; }
  /* The page is a column: header, verification strip, then the grid taking
     whatever is left. Laid out this way rather than subtracting a fixed pixel
     height from 100vh, because that number silently goes stale the moment a
     bar is added above the grid, which is exactly what happened when the
     verification strip arrived and pushed the bottom row off screen. */
  body {
    background: var(--bg);
    color: var(--text);
    font-family: -apple-system, "Segoe UI", Roboto, monospace;
    font-size: 13px;
    display: flex;
    flex-direction: column;
    height: 100vh;
    overflow: hidden;
  }
  header {
    display: flex;
    align-items: center;
    gap: 24px;
    /* 7px and not the 10px this had. Both bars at the top of the page are paid
       for on every screen and read once: the header names the exchange and
       holds the pairs, and the strip under it is the verification. Three pixels
       above and below each of them is 12px of market at every width, and 12px
       is a row of the order book. */
    padding: 7px 16px;
    border-bottom: 1px solid var(--border);
    background: var(--panel);
  }
  .logo { font-weight: 700; font-size: 15px; }
  .tabs { display: flex; gap: 4px; }
  .tab {
    padding: 6px 14px;
    border-radius: 4px;
    cursor: pointer;
    color: var(--dim);
    background: transparent;
    border: none;
    font: inherit;
  }
  .tab.active { background: var(--border); color: var(--text); font-weight: 600; }
  /* Between the symbols and the buttons, and centred in whatever is left of
     the header. It used to be `margin-left: auto`, which puts the whole run of
     figures against the right edge: at 2560 that left a 1500px hole between
     BTC-USDC and "Last", and the eye had to cross it to read a price. Two auto
     margins split that space instead, so the figures are a block in the middle
     of the header at any width, and the header still has the name at one end
     and its controls at the other. */
  .ticker { display: flex; gap: 20px; margin: 0 auto; color: var(--dim); }
  .ticker b { color: var(--text); font-weight: 600; }
  .ticker .up { color: var(--green); }
  .ticker .down { color: var(--red); }

  /* The verification strip. Everything here answers "how would you know the
     operator did not lie", which is what the rest of the screen cannot show:
     an order book looks identical whether or not it is honest. */
  .verify {
    display: flex;
    align-items: center;
    /* The claims are spread across the bar rather than packed against its left
       edge. They come to about 1330px, so on anything wider the rest of the bar
       was empty: measured at 2560, 1200px of it, with the whole strip in the
       left half of a bar that runs the width of the screen. Spread, the gap
       between two claims is the only thing that grows, each claim keeps its own
       5px of internal spacing, and both ends of the bar are used at every
       width. A line holding one item, the anchor at 1440, still starts at the
       left, which is where the item before it would have put it. */
    justify-content: space-between;
    /* 22px between two claims on a line, 5px between two lines of them. It was
       22px both ways, which is the gap for reading along a line and far too
       much for stacking them: measured at 768, where the strip takes two lines,
       that put 22px of nothing between them and made the bar 65px tall on a
       screen that shows 1024. */
    gap: 5px 22px;
    padding: 4px 16px;
    background: var(--panel);
    border-bottom: 1px solid var(--border);
    font-size: 11px;
    color: var(--dim);
    flex-wrap: wrap;
  }
  .verify .k { color: var(--dim); }
  /* One item is one claim: a label, its number, and whatever qualifies it.

     It is a box of its own so the strip breaks between claims and never inside
     one. Measured at 390px before this: "validators 5179378" ended a line and
     "· 3 of 3" began the next, which reads as a fragment of nothing.

     The box also carries its own spacing. Every span on this strip used to be
     a flex item of `.verify` itself, so all of them sat the strip's 22px
     apart: a label was as far from its own number as it was from the next
     claim, and the strip was one run of evenly spaced words rather than seven
     things. 22px between claims now, 5px inside one. */
  .verify .item { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 5px; }
  /* Two lengths of the same label, and one of them is shown. The long one
     wherever the window has room for it, the short one under 1700px. See the
     rule under this one for the widths, and `vk` in the script for what each
     pair says. Written into the page rather than swapped by the script,
     because the width that decides it is the window's and the browser already
     knows that without being asked. */
  .verify .sm { display: none; }
  /* Measured with the strip laid out on one line and nothing allowed to wrap:
     the long labels need 1621px and the short ones 1139px. So above 1653px of
     window the long form fits on one line, and under it the strip takes two
     rows of the screen, 65px instead of 33px, on a window that may only be
     600px tall. The short form fits one line from 1171px, which is every
     desktop width this page is likely to be opened at.

     1700 and not 1653: those numbers are the numbers on the strip today, and
     `5412834` becomes `54128345` about every eight months this exchange runs.
     Four counters growing a digit is 32px, and a breakpoint that is exactly the
     width of today's figures is a strip that starts wrapping later without
     anything in this file changing. */
  @media (max-width: 1699px) {
    .verify .lg { display: none; }
    .verify .sm { display: inline; }
  }
  .verify .v { color: var(--text); font-weight: 600; }
  .verify .ok { color: var(--green); }
  .verify .bad { color: var(--red); font-weight: 600; }
  .verify .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
  /* No separator between two claims. There was a `|` between every pair of
     them, and once each claim is a box of its own the `|` says what the 22px
     already says. It also wrapped badly: a strip that breaks onto a second line
     put the `|` alone at the end of the first line or alone at the start of the
     second, at every window width that wrapped it. */

  /* Three columns and the Traders row, with a gutter track between each pair
     of them. The widths are custom properties because they are the visitor's
     to change. See "Resizable layout" in the script. The fallbacks here are
     the sizes the page has always had, and they are what it lays out at if the
     script never runs, so they are repeated in DEFAULTS there and the two have
     to agree. minmax(0, …) rather than a bare width so that a window too
     narrow for both side columns squeezes them instead of pushing the right
     one off screen. */
  main {
    display: grid;
    grid-template-columns:
      minmax(0, var(--col-left, 340px)) var(--gut)
      minmax(0, 1fr) var(--gut)
      minmax(0, var(--col-right, 340px));
    grid-template-rows: minmax(160px, 1fr) var(--gut) auto;
    gap: 0;
    padding: 8px;
    flex: 1;
    min-height: 0;
  }
  /* The handles. Each one sits in the 8px that used to be gap, so the page is
     laid out exactly as it was; the grip only shows under the pointer, so the
     page also looks exactly as it did until someone reaches for it. */
  .gut { position: relative; z-index: 2; touch-action: none; }
  .gut::after {
    content: "";
    position: absolute;
    background: transparent;
    border-radius: 1px;
  }
  .gut-col { cursor: col-resize; }
  .gut-col::after { top: 50%; margin-top: -14px; height: 28px; left: 50%; margin-left: -1px; width: 2px; }
  .gut-row { cursor: row-resize; }
  .gut-row::after { left: 50%; margin-left: -14px; width: 28px; top: 50%; margin-top: -1px; height: 2px; }
  .gut:hover::after, .gut.on::after { background: var(--dim); }
  .gut:focus { outline: none; }
  .gut:focus-visible::after { background: var(--accent); }
  /* Held while a handle is being dragged: the pointer keeps its resize shape
     wherever it goes, and dragging across the page does not select text. */
  body.rs-col, body.rs-col * { cursor: col-resize !important; }
  body.rs-row, body.rs-row * { cursor: row-resize !important; }
  body.rs-col, body.rs-row { user-select: none; }
  .panel {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 4px;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    min-height: 0;
  }
  .panel h2 {
    font-size: 12px;
    color: var(--dim);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    padding: 10px 12px 6px;
  }
  /* Every table on the page is one of these. The columns are capped rather
     than shared out with `1fr`: at 1fr, three columns in a 1900px panel put
     Price against the left edge, Qty in the middle and Time against the right
     edge with a thousand pixels of nothing between them, which reads as a
     rendering fault rather than as a table. Capped, a column keeps the width
     it has in a 340px panel and anything wider than that is left over on the
     right, so the figures stay in one band whatever the panel is dragged to.
     Each table sets its own caps as `--cols`, beside the panel it belongs to;
     the default here is the Order Book's and Recent Trades'.

     The caps are in `ch` because the figures are tabular: one ch is one digit,
     so `13ch` is a column that holds a thirteen digit number and says so.

     The floor is min-content for a column of numbers, and a few characters for
     a column of text. That asymmetry is deliberate. Right-aligned text is cut
     at the front, so a number squeezed below its own width would not be
     shortened, it would be *wrong*, 1234.56 printed as 34.56, and a column
     of numbers is therefore never allowed to be narrower than the number in
     it. A name cut with an ellipsis is obviously cut, so text may lose its
     tail; but it needs a floor of its own, because with the numbers holding
     their ground a text column with no floor is the one that collapses when
     the panel is too narrow for all of them. */
  .col-head, .row {
    display: grid;
    grid-template-columns: var(--cols, repeat(3, minmax(min-content, 13ch)));
    justify-content: start;
    padding: 1px 12px;
    font-variant-numeric: tabular-nums;
  }
  /* Nothing here wraps. A cell that wraps makes its row twice as tall as every
     other row, which breaks the one thing a table of numbers is for. */
  .col-head > *, .row > * { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* "nothing resting", "no fills yet", "flat": one span standing in for a
     whole table, so it gets the whole table's width and may wrap. */
  .row > span:only-child { grid-column: 1 / -1; white-space: normal; overflow: visible; }
  .col-head { color: var(--dim); font-size: 11px; padding-bottom: 4px; }
  .row { position: relative; line-height: 18px; cursor: default; }
  /* The depth bar behind the book. Measured against the columns (`--band`, the
     sum of the caps) and not against the panel, because a bar that filled the
     panel would drift away from the numbers it belongs to the moment the panel
     was dragged wider. The SVG rectangle carries the percentage without an
     inline style, so the page can run under a strict style policy. */
  .row .bar {
    position: absolute;
    top: 0; bottom: 0;
    right: max(12px, 100% - 12px - var(--band, 100%));
    width: min(100% - 24px, var(--band, 100%));
    height: 100%;
    z-index: 0;
    pointer-events: none;
  }
  .row span { position: relative; z-index: 1; }
  /* The space to the left of a figure belongs to the figure, not to the column
     it happens to be sitting in. A profit column's cap can be set a character
     wider than the longest number expected in it, but profit here has no
     upper bound; this demo's bot passes +9999.99 in half an hour. The moment
     a number is as wide as its column, "+16024.69" and "+5240.60" print as
     "+16024.69+5240.60". Padding is part of what the column has to be wide
     enough for, so this gap survives any number.

     The same is true of two words. This rule gives the gap to figures only,
     and only on their left, so a text column next to another text column got
     no gap at all: the Account Trades table printed "MERKLE-USDCBuy". A word
     is not made wrong by a missing space the way a number is, but it is read
     as one word, and the reader has to work out where it ends. So a fitted
     table pads every cell on both sides. See `.fitted` below. This rule is
     what is left for the Order Book, whose rows are drawn over depth bars and
     are not fitted. */
  .num { text-align: right; padding-left: 8px; }
  .ask .price { color: var(--red); }
  .ask .bar rect { fill: var(--red-bg); }
  .bid .price { color: var(--green); }
  .bid .bar rect { fill: var(--green-bg); }

  /* `min-height: 0` is not redundant. `flex: 1` on its own leaves the minimum
     height at `auto`, which is the height of the content, so #book grows past
     the panel instead of clipping inside it. At 1280x720 that put #book at
     515px inside a 276px panel and the spread bar 13px below the bottom edge
     of it, where nobody could read the best bid or the best ask. It also makes
     the height of #book depend on how many rows are in it, and fitBook
     measures that height, so without this line the count locks at its first
     value and never follows the panel again.

     `--row-h` is a declaration and not a number in the script, so a later
     change to the line height re-derives the level count for free. The script
     still measures a real row because padding counts too. See fitBook. */
  #book { flex: 1; min-height: 0; display: flex; flex-direction: column; --row-h: 18px; }
  /* 3 × 13ch, the caps above: where the columns stop, and so where the depth
     bar stops too. */
  #book .row { --band: 39ch; line-height: var(--row-h); }
  #asks { display: flex; flex-direction: column; justify-content: flex-end; overflow: hidden; flex: 1; }
  #bids { overflow: hidden; flex: 1; }
  #spread {
    display: flex;
    align-items: baseline;
    gap: 12px;
    padding: 6px 12px;
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
  }
  #spread .last { font-size: 18px; font-weight: 700; }
  #spread .lbl { color: var(--dim); font-size: 11px; }

  .scroll { flex: 1; overflow-y: auto; }
  /* The two stacked columns. gap: 0 and a handle between the panels instead.
     The handle is the same 8px the gap was.

     Both columns are built the same way: the top panel takes the share the
     visitor dragged it to, the panel under it takes what is left. Written the
     same way for both so that one clamp in `share()` covers both splits and
     neither panel can be dragged to nothing. The middle column holds the chart
     over the order ticket; the right column holds Recent Trades over Order
     Flow. */
  #midcol, #rightcol { display: flex; flex-direction: column; gap: 0; min-width: 0; min-height: 0; }
  #midcol > .gut, #rightcol > .gut { flex: 0 0 var(--gut); }
  #chart-panel { flex: 0 0 var(--chart-share, 70%); min-height: 0; }
  /* How short the ticket panel is allowed to be, which is how tall the chart
     is allowed to grow. It is written here, next to nothing, and read by the
     layout script, because the number depends on how many columns the ticket
     lays its controls out in and that is decided a few rules below this one.
     Measured with the panel at its content height: 385px at two columns
     (a 1280px window), 234px at three (1440 and 1680) and 180px at six (2000
     and up).

     `#midcol` is the container because the column count follows the ticket's
     own width, which is this column less its border and its padding: the 640px
     and 1100px in the ticket's rules are 666px and 1126px here. */
  #midcol { container-type: inline-size; }
  #chart-panel { --ticket-floor: 390px; }
  @container (min-width: 666px) { #chart-panel { --ticket-floor: 240px; } }
  @container (min-width: 1126px) { #chart-panel { --ticket-floor: 185px; } }
  #trade-panel { flex: 1; min-height: 0; }
  #trades-panel { flex: 0 0 var(--trades-share, 55%); min-height: 0; }
  #flow-panel { flex: 1; min-height: 0; }
  #chart-wrap { flex: 1; position: relative; min-height: 0; }
  #chart { position: absolute; inset: 0; width: 100%; height: 100%; }
  /* A drag on the chart moves the window, so the browser must not read the
     same drag as a scroll of the page. Without this a swipe on a phone
     scrolls the page and the chart never moves. */
  #chart, #pnl-chart { touch-action: none; cursor: grab; }
  #chart:focus-visible, #pnl-chart:focus-visible { outline: 1px solid var(--dim); outline-offset: -1px; }
  /* The zoom controls sit at the right of the heading, after the interval
     buttons. At 420px the heading wraps instead of pushing them off. */
  .zoom { float: right; margin-left: 8px; white-space: nowrap; }
  .zoom .iv { padding: 1px 6px; }
  #pnl-zoom { float: none; margin-left: auto; }
  #intervals { float: right; }
  .iv {
    padding: 1px 8px;
    margin-left: 2px;
    border-radius: 3px;
    cursor: pointer;
    color: var(--dim);
    background: transparent;
    border: none;
    font: inherit;
    font-size: 11px;
    text-transform: none;
  }
  .iv.active { background: var(--border); color: var(--text); }
  .trade-buy .price { color: var(--green); }
  .trade-sell .price { color: var(--red); }
  .flash { animation: flash 0.6s; }
  @keyframes flash { from { background: rgba(234, 236, 239, 0.15); } to { background: transparent; } }

  .flow-new .side-Buy { color: var(--green); }
  .flow-new .side-Sell { color: var(--red); }
  .flow-cancel { color: var(--dim); }
  .flow-cancel .price { color: var(--dim); }
  .mono { font-variant-numeric: tabular-nums; }
  #status { color: var(--dim); font-size: 11px; padding: 4px 12px; border-top: 1px solid var(--border); }
  /* Empty is the normal state, and an empty line still draws a border and 8px
     of padding. Those belong to the flow list above it while there is nothing
     to say. */
  #status:empty { display: none; }
  /* An order term on a flow row: market, immediate-or-cancel, fill-or-kill,
     post-only. Quieter than .badge, which marks who an account is. A term is
     about one order and there can be two of them on one row. */
  .term {
    font-size: 9px; padding: 0 3px; border-radius: 2px; vertical-align: 1px;
    color: var(--dim); border: 1px solid var(--border);
    letter-spacing: 0.5px;
  }

  /* Traders: who is on the other side of the flow, and how they are doing.
     Three panels rather than one panel split into three columns. They are three
     different tables about three different things: every account, the selected
     account's positions, the selected account's fills. Under one heading
     with two hairlines between them they read as one table that has lost its
     alignment. `#traders` itself draws nothing now: it is the grid cell the
     three boxes are laid out in. */
  #traders {
    grid-column: 1 / -1;
    grid-row: 3;
    height: var(--traders-h, 296px);
    display: flex;
    flex-direction: column;
    min-height: 0;
  }
  #gut-traders { grid-column: 1 / -1; grid-row: 2; }
  /* Collapsed, the three headers stay and everything under them goes. Not the
     whole body, the way it was when the toggle sat in a heading of its own:
     the button that brings the row back lives in the Trades header, and hiding
     the body would take it with it. The height is what those headers come to
     rather than the 33px they came to here: a fixed height is a number that has
     to be re-measured every time a heading changes, on every platform whose
     fonts are not these, and it clips the button it is one pixel short of. */
  #traders.collapsed { height: auto; }
  #traders.collapsed .tbox > :not(h2) { display: none; }
  #traders.collapsed .gut-abs { display: none; }
  /* Each box's own heading, at the weight every other panel's heading has
     (`.panel h2`); what is added here is the row the title, its note and the
     figure beside it sit in. */
  .tbox > h2 { display: flex; align-items: baseline; gap: 8px; white-space: nowrap; }
  .tbox .note { text-transform: none; letter-spacing: 0; font-weight: 400; opacity: 0.75; }
  /* The aggregate of the list under it, so it sits in the Accounts header, at
     the far end of it. */
  #zero-sum { margin-left: auto; text-transform: none; letter-spacing: 0; }
  #zero-sum b { font-weight: 600; color: var(--text); }
  /* Far right of the Trades header, which is where it was when it was far right
     of the whole row. Never shrunk: it is the way back from a collapsed row. */
  #traders-toggle { margin-left: auto; flex: 0 0 auto; }
  /* What gives when a box is too narrow for its whole header. The notes go
     first and by a factor of a thousand, so the figures beside them are still
     whole at widths where the note has already gone: the note is a label and
     "Net 0.00" is a live check on the engine. Cut rather than wrapped, because
     a header that takes a second line puts that box's table one row below the
     other two, and cut with an ellipsis rather than left to overflow, because
     the box clips what leaves it, which is how "Net 0.00" was rendered as
     "Net 0." at 1440. The whole note is on its own title attribute. */
  .tbox > h2 > .note { flex: 0 1000 auto; }
  .tbox > h2 > .note, #zero-sum { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  /* 27.4% / 31.5% is what 1fr / 1.15fr / 1.5fr came to, written as percentages
     so a handle can be put on the boundary at the same coordinate. The two
     handles here are positioned over the boundaries rather than given tracks of
     their own, so the three boxes still meet exactly where they did. */
  #traders-body {
    flex: 1;
    display: grid;
    grid-template-columns: var(--tcol-1, 27.4%) var(--tcol-2, 31.5%) minmax(0, 1fr);
    min-height: 0;
    position: relative;
  }
  .gut-abs { position: absolute; top: 0; bottom: 0; width: 9px; margin-left: -4px; }
  #gut-t1 { left: var(--tcol-1, 27.4%); }
  #gut-t2 { left: calc(var(--tcol-1, 27.4%) + var(--tcol-2, 31.5%)); }
  /* The space between the boxes is padding inside each track, not `gap` between
     the tracks. `gap` would move the boundaries: the handles are placed at
     var(--tcol-1) and at the sum of both, measured from this grid's own left
     edge, and a gap that the percentages know nothing about would leave both of
     them several pixels off the seam they resize. Padding leaves every boundary
     exactly where it was, and the 8px gap straddles it: 4px from each box,
     the same 8px --gut the rest of the grid is spaced with. The outer edges are
     flush with the panels above, so only the two seams get the gap. */
  .tcol { display: flex; flex-direction: column; min-height: 0; padding: 0 4px; }
  .tcol:first-child { padding-left: 0; }
  /* "the box with no box after it", rather than the third child: the two drag
     handles are children of this grid as well, so counting children here is a
     rule that quietly points at the wrong box the moment anything is added. */
  .tcol:not(:has(~ .tcol)) { padding-right: 0; }
  .tbox { flex: 1; min-height: 0; }
  /* The one sub-heading inside a box: the chart under the positions table. */
  .tbox h3 {
    font-size: 11px; font-weight: 600; color: var(--dim);
    text-transform: uppercase; letter-spacing: 0.5px;
    padding: 2px 12px 5px; display: flex; align-items: baseline; gap: 8px;
  }
  /* The heading of this table sits inside the scrolling box now, so the box
     takes the height the heading and the rows took between them: 82px of rows
     under a 19px heading. The chart under it keeps the rest of the panel. */
  #positions-scroll { flex: 0 0 auto; max-height: 101px; }
  #pnl-wrap { flex: 1; position: relative; min-height: 56px; }
  #pnl-chart { position: absolute; inset: 0; width: 100%; height: 100%; }
  .legend { display: flex; gap: 10px; flex-wrap: wrap; }
  .legend i { display: inline-block; width: 9px; height: 2px; vertical-align: middle; margin-right: 4px; }
  .legend .pnl-key { background: var(--accent); }
  .legend b { font-weight: 600; }
  .pos { color: var(--green); }
  .neg { color: var(--red); }
  .dim { color: var(--dim); }
  .badge {
    font-size: 9px; padding: 0 4px; border-radius: 3px; vertical-align: 1px;
    background: rgba(240, 185, 11, 0.15); color: var(--accent);
    border: 1px solid rgba(240, 185, 11, 0.35);
    text-transform: uppercase; letter-spacing: 0.5px;
  }
  .acct-row { cursor: pointer; }
  .acct-row:hover { background: rgba(234, 236, 239, 0.06); }
  .acct-row.sel { background: rgba(234, 236, 239, 0.11); }
  .acct-row.sel .acct-id { font-weight: 700; }
  /* A fitted table: Accounts, Recent Trades, Order Flow, Positions and Account
     Trades. Each of them used to set caps in characters, per column, and that
     had four faults this block replaces. Nothing here names a column: which
     column holds text, and which columns go when the room runs out, is in the
     table's description in the script. See TABLE_LIST.

     One grid, not two. The heading and the rows were separate grids that each
     read --cols. Two grids line up only while every track is a fixed character
     count, because 13ch measures the same in both. A track sized from its
     content does not: the heading track measures "Realized", the data track
     measures "+42928.74", and the columns drift apart. So .table is the grid
     and both the heading and every row are subgrids of it. A subgrid takes the
     tracks of its real parent, so .rows is display: contents and each row is a
     direct child of .table; a box in between ends the chain.

     A number is never cut. A cell with overflow: hidden contributes nothing to
     min-content, so `minmax(min-content, 8ch)` was really `minmax(0, 8ch)`: the
     track gave away the room it needed and the cell hid the rest, which is how
     the Open column printed "31.…" next to "223k". "31.…" is not a shorter
     number, it is a different one. So no cell hides anything, and its track is
     floored at its content.

     One column per table holds text and keeps both. An ellipsis in a name is
     plainly an ellipsis, and that column gets a floor in characters instead.
     The floor is only a floor: the column takes 1.4 shares of the room the
     numbers leave, so it is far wider than the floor on a panel with room. In
     Accounts what the ellipsis takes first is the "you" badge after the id,
     which is the right thing to lose. The row a visitor owns is highlighted
     already, so the badge repeats what the highlight says while the id does
     not.

     Every cell has a gap on both sides. The reason is under `.num` above.

     The widths are decided in the script, not here: see fitTable. */
  .fitted .table {
    display: grid;
    grid-template-columns: var(--cols);
    align-content: start;
  }
  .fitted .col-head, .fitted .row {
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
  }
  /* The heading is inside the scrolling box now, so it has to be held at the
     top, and it needs a background of its own or the rows pass through it. The
     line under it is where the heading ends and the table starts. */
  .fitted .col-head {
    position: sticky;
    top: 0;
    z-index: 3;
    background: var(--panel);
    border-bottom: 1px solid var(--border);
  }
  .fitted .rows { display: contents; }
  .fitted .row > * {
    overflow: visible;
    text-overflow: clip;
  }
  /* `data-text` names the one column that may be cut. It gives the number of
     that column, not its place in the row, because the column that holds text
     is the second one in Account Trades and the first one in the other four. */
  .fitted[data-text="1"] .row > :nth-child(1),
  .fitted[data-text="2"] .row > :nth-child(2) {
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* A heading may be shortened, a value may not.

     A track sized at max-content is as wide as the widest thing in it, and the
     heading is one of those things. In Positions at 1280 the widest thing in
     the Quantity column was the word "Quantity", not any quantity, so that
     track could not go under the width of a label, and the room it held went
     missing from the Symbol column beside it, which printed "MERKLE-US…".

     A reader who has lost the tail of "Quantity" still knows what the column
     is: it is where it always was, and the figures under it are quantities. A
     reader who has lost the tail of "MERKLE-USDC" is reading another symbol,
     and one who has lost it from a figure is reading another number. So the
     heading gives way first.

     `contain: inline-size` is what takes the heading out of the sizing, not
     `overflow: hidden`. Hidden overflow drops a cell's min-content to zero but
     leaves its max-content at the whole word, and max-content is what these
     tracks are floored at. Containment says the width of this box does not
     depend on what is in it, so the track is sized by the rows alone.

     min-width keeps a heading from disappearing altogether. Boxes here are
     border-box and this ch is the heading's own 11px font, so 5ch is a 33px
     cell: the 8px gap on each side and the line take 17px of it, and about
     two characters and the ellipsis are left. It is reached at 420, where the
     Account Trades Quantity heading reads "Qua…". */
  .fitted .col-head > * {
    contain: inline-size;
    min-width: 5ch;
  }
  /* The gap on both sides of a cell, and one line per column boundary, drawn on
     the cell so that it runs unbroken from the heading down to the last row.

     Neither one on the outer edge of the table. The row already stops 12px
     short of the panel there, so 8px more would put the first heading out of
     line with the panel title over it, and a line floating at the right edge
     reads as a fault. `data-first` and `data-last` name the outermost columns
     still on screen, which are not the first and the last columns once a
     column is dropped. */
  .fitted .col-head > *, .fitted .row > * {
    padding-left: 8px;
    padding-right: 8px;
    border-right: 1px solid var(--sep);
  }
  .fitted .col-head > *:first-child, .fitted .row > *:first-child,
  .fitted[data-first="2"] :is(.col-head, .row) > :nth-child(2) { padding-left: 0; }
  .fitted .col-head > *:last-child, .fitted .row > *:last-child {
    padding-right: 0;
    border-right: 0;
  }
  .fitted[data-last="3"] :is(.col-head, .row) > :nth-child(3),
  .fitted[data-last="4"] :is(.col-head, .row) > :nth-child(4),
  .fitted[data-last="5"] :is(.col-head, .row) > :nth-child(5) {
    padding-right: 0;
    border-right: 0;
  }
  /* The columns the panel has no room for. `data-hide` lists them by number, so
     the fit can try a smaller set without building the rows again. */
  .fitted[data-hide~="1"] :is(.col-head, .row) > :nth-child(1),
  .fitted[data-hide~="2"] :is(.col-head, .row) > :nth-child(2),
  .fitted[data-hide~="3"] :is(.col-head, .row) > :nth-child(3),
  .fitted[data-hide~="4"] :is(.col-head, .row) > :nth-child(4),
  .fitted[data-hide~="5"] :is(.col-head, .row) > :nth-child(5),
  .fitted[data-hide~="6"] :is(.col-head, .row) > :nth-child(6) { display: none; }
  /* The handle that resizes a column. 11px wide so a pointer can catch it, with
     the line it moves drawn 1px at its right edge. Same behaviour as the panel
     handles: nothing shows until the pointer is on it. */
  .fitted .col-head > * { position: relative; }
  .fitted .ch {
    position: absolute;
    top: 0; bottom: 0; right: 0;
    width: 11px;
    cursor: col-resize;
    touch-action: none;
    z-index: 1;
  }
  .fitted .ch::after {
    content: "";
    position: absolute;
    top: 0; bottom: 0; right: 0;
    width: 1px;
    background: transparent;
  }
  .fitted .ch:hover::after, .fitted .ch.on::after { background: var(--dim); }
  .fitted .ch:focus { outline: none; }
  .fitted .ch:focus-visible::after { background: var(--accent); }

  /* The order ticket.

     It sits under the chart, in the middle column, which is about 1850px wide
     at 2560 and 728px at 1440 and is the visitor's to drag to anything in
     between. So the ticket is laid out against that panel and not against the
     window: `#trade-body` is the container, and the rules below ask how wide it
     is. The window would be the wrong thing to ask twice over: the panel is
     dragged, and the same 728px panel is what a 768px tablet gives the ticket
     across the whole width of the screen.

     A grid of equal columns, not a row of cells that wraps. Wrapping left the
     last line short: measured at 1440 with the sentences off, Symbol, Side,
     Order and Price filled the first line and Quantity and Buy ended the second
     357px before the edge of the panel. The column counts are 1, 2, 3 and 6.
     A market ticket adds the slippage control as a seventh, and a panel at
     least 1400px wide gives every control its own column.

     Nothing here counts the controls. `.tk-say` spans one column less than the
     grid it is in, so the sentence takes the rest of whatever row it lands on,
     whether that row starts with Buy or with a control added later. */
  #trade-body { container-type: inline-size; }
  #trade-panel h2 { display: flex; align-items: baseline; gap: 8px; }
  #my-account { margin-left: auto; text-transform: none; letter-spacing: 0; color: var(--text); }
  #trade-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 0 12px 10px;
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  .ticket { display: grid; grid-template-columns: minmax(0, 1fr); align-items: end; gap: 10px 14px; }
  /* One control to a row is the floor, for a panel dragged to nothing and for
     the smallest phone: a 320px window gives this box 306px, and half of that
     is 146px against the 140px "MERKLE-USDC" needs at the 16px a phone sets
     below. 6px is not a margin, it is luck. Two columns from 320px, which is
     what a 360px phone gives it.

     The other two counts are set by one control. The Order list holds "Limit,
     immediate or cancel", which is 170px of text at 13px, and a <select> too
     narrow for the option it is showing cuts the option, so a cell that holds
     it needs 205px with the field's own padding and its caret. Three columns
     from 640px, where a cell is 204px and every window under that is a phone
     drawing the same list at 16px. Six from 1100px, where a cell is 172px and
     the Order list has a row of its own below. */
  @container (min-width: 320px) { .ticket { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
  @container (min-width: 640px) { .ticket { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
  @container (min-width: 1100px) { .ticket { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
  @container (min-width: 1400px) { .ticket { grid-template-columns: repeat(7, minmax(0, 1fr)); } }
  /* The sentence takes the rest of the row it starts on. */
  /* Two columns is not enough for the Order list at any font this page uses, so
     it takes the row. So do the route buttons, the Buy button and the sentence
     after it. */
  @container (max-width: 639px) {
    #route, .tk:has(> #terms), .tk:has(> #send), .tk-say { grid-column: 1 / -1; }
    /* On its own row under everything, so the line that separates it from the
       controls goes above it rather than down its left edge. */
    .tk-say {
      border-left: none;
      border-top: 1px solid var(--border);
      padding-left: 0;
      padding-top: 8px;
    }
  }
  @container (min-width: 640px) { .tk-say { grid-column: span 2; } }
  @container (min-width: 1100px) { .tk-say { grid-column: span 5; } }
  @container (min-width: 1400px) { .tk-say { grid-column: span 6; } }
  /* Every control fills the cell it was given. In the row this used to be, each
     one carried a width of its own, 150px for Symbol and 118px for a number,
     and those numbers are now the widths the cells are sized from. Buy and Sell
     share their cell; see `.sides` below. */
  /* One labelled control: the label on top, the control under it. */
  .tk { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
  .tk > label, .tk > .tk-l {
    color: var(--dim); font-size: 11px; line-height: 14px; white-space: nowrap;
  }
  .tk input, .tk select {
    width: 100%;
    min-width: 0;
    background: var(--bg);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 5px 7px;
    font: inherit;
    font-variant-numeric: tabular-nums;
  }
  .tk input:focus, .tk select:focus { outline: none; border-color: var(--dim); }
  /* The line above takes the browser's focus ring off, and for a pointer that
     is what is wanted: the border already lights up. For a keyboard it is not:
     the ring is the only thing saying where the next keystroke goes.
     :focus-visible is the browser's own answer to which of the two it is, so
     the ring is put back on that and not on :focus. Same specificity as the
     rule above, so it has to stay below it. */
  .tk input:focus-visible, .tk select:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -1px;
  }
  .tk input::placeholder { color: var(--dim); opacity: 1; }
  /* The one native dropdown on the page. `appearance: none` takes the platform
     control off it so it matches the two inputs beside it, and the caret is an
     inline SVG data URI: this page fetches nothing it was not served.
     The list it opens is drawn by the operating system and cannot be styled;
     `color-scheme: dark` at the top of this sheet is what makes that list dark,
     and that is as far as this goes. Replacing it with a div-and-JS dropdown
     would style the list and lose the keyboard behaviour, the mobile picker and
     the accessibility that come with the native control for free. */
  .tk select {
    appearance: none;
    padding-right: 26px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23848e9c' stroke-width='1.4' stroke-linecap='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 9px center;
    cursor: pointer;
  }
  .tk select:hover { border-color: var(--dim); }
  /* Read on the platforms that draw the list out of the page's own colours. */
  .tk select option { background: var(--panel); color: var(--text); }
  /* What each cell is allowed to take. The two number fields keep a floor wide
     enough for the number in them, and nothing else grows: the sentence at the
     end of the row is what takes the leftover width. */
  .tk-sym { flex: 0 1 150px; }
  .tk-num { flex: 0 1 118px; min-width: 88px; }
  .tk-fixed { flex: 0 0 auto; }
  /* Two buttons in one cell of the ticket grid, half of it each. */
  .sides { display: flex; gap: 6px; }
  .sides button { flex: 1 1 0; }
  .sides button {
    padding: 5px 14px;
    border-radius: 3px;
    border: 1px solid var(--border);
    background: transparent;
    color: var(--dim);
    font: inherit;
    cursor: pointer;
    white-space: nowrap;
  }
  .sides button:hover { border-color: var(--dim); color: var(--text); }
  .sides button.on-buy { background: var(--green-bg); border-color: var(--green); color: var(--green); }
  .sides button.on-sell { background: var(--red-bg); border-color: var(--red); color: var(--red); }
  /* Which path the same signed order takes. Deliberately not styled like
     Buy/Sell: it is not a second kind of order, it is the same order on another
     path to the same exchange, so it gets the accent colour the rest of the
     page uses for verification rather than green and red. */
  .route button.on {
    background: rgba(240, 185, 11, 0.15);
    border-color: rgba(240, 185, 11, 0.55);
    color: var(--accent);
  }
  #send {
    padding: 6px 26px;
    border: none;
    border-radius: 3px;
    font: inherit;
    font-weight: 600;
    cursor: pointer;
    background: var(--green);
    color: #04150d;
    white-space: nowrap;
  }
  #send.sell { background: var(--red); color: #1c0409; }
  #send:disabled { background: var(--border); color: var(--dim); cursor: default; }
  /* The end of the ticket row: what the selected route does. It grows, so the
     row of controls reaches the far edge of the panel instead of stopping a
     third of the way across it. */
  .tk-say {
    flex: 1 1 300px;
    min-width: 210px;
    align-self: stretch;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding-left: 14px;
    border-left: 1px solid var(--border);
  }
  #route-note { color: var(--dim); font-size: 11px; line-height: 15px; }
  #route-note b { color: var(--text); font-weight: 600; }

  /* What this page says about itself.

     Everything marked `.explains` is the page explaining the page: what the
     selected order kind does, what the two paths to the exchange are, what the
     key in this browser is, and the three steps an order takes. A visitor who
     has never seen this exchange needs all of it. An operator who runs one
     reads it once and then reads it again on every load for the life of the
     deployment, and on a 390px screen it is 340px of prose standing between
     the ticket and the rest of the page.

     So it is off, and the header's `explain` button turns it on for a browser
     that asks. `body:not(.explaining)` and not a class that hides: these are
     flex boxes, list items and grid cells with display values of their own,
     and a rule that switched them to `display: none` and back would have to
     name every one of those values a second time.

     The separate service goes with the sentences. It is not a second exchange:
     it is the same signed order taking another path to the same sequencer, and
     it is on this page to show that the path can be watched. Nobody trading
     needs the choice, so an order goes Direct until someone asks to see it.
     `setExplain` in the script puts the route back when the button goes off. */
  body:not(.explaining) .explains { display: none; }
  /* With the sentences gone this is an empty box at the end of the ticket row,
     so it gives up its border and its width. Not hidden, because `renderTerms`
     writes live warnings into the same box, "Your order was a market order
     and is now a limit order", and those are not the page explaining itself,
     they are what has just happened to the ticket on screen. */
  body:not(.explaining) .tk-say {
    flex: 0 1 auto;
    min-width: 0;
    padding-left: 0;
    border-left: none;
  }

  /* Under the ticket: what this browser has resting, what happened to the last
     order, and what the key is. Three blocks side by side where the panel is
     wide, stacked where it is not. `auto-fit` rather than a media query,
     because the width here is the visitor's to drag and has nothing to do with
     the width of the window. `min(330px, 100%)` and not 330px, because a track
     floored at 330px inside a panel narrower than that is a table hanging over
     the side of it: measured in a 320px window, where this panel is 306px
     wide, the three blocks were laid out 342px wide and the last 36px of every
     row in them was off the screen. */
  .tk-cols {
    flex: 1 1 auto;
    min-height: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(330px, 100%), 1fr));
    gap: 12px 20px;
  }
  /* No `min-height: 0` here, and that is load-bearing. A grid item whose
     minimum size is zero gives its row track a base size of zero, and a row
     track of zero in a container shorter than its content lets all three blocks
     paint on top of each other. Measured at 1440x900 before this was removed:
     the three headings landed 29px apart with their tables written over each
     other. With the automatic minimum left alone the tracks keep their content
     height, the grid overflows instead, and #trade-body scrolls.
     `align-content` is left at its default of stretch so that a single row of
     blocks fills a tall panel rather than leaving it empty underneath. */
  /* `container-type` is what lets the Open orders table below drop columns
     when the block it sits in is too narrow for all of them. The window is the
     wrong thing to ask: these blocks are laid out three, two or one to a row
     depending on the panel, and the panel is dragged. Measured at a 1440px
     window, the middle column is 728px and this block is 341px of it, near
     enough the 353px the same block is given by a 768px tablet, where the
     ticket panel runs the whole width. One question answers both: how wide is
     this block.
     `inline-size` and not `size`: the width of this block comes from the grid
     track above it, but its height is still its content's. */
  .tk-col { display: flex; flex-direction: column; min-width: 0; container-type: inline-size; }
  .tk-col > h3 {
    font-size: 11px; font-weight: 600; color: var(--dim);
    text-transform: uppercase; letter-spacing: 0.5px;
    padding: 0 0 5px;
  }
  /* The panel already pads its body, so the tables inside it must not pad
     again: a row indented 12px from a column edge that is itself 12px in reads
     as a table that has slipped. */
  .tk-col .col-head, .tk-col .row { padding-left: 0; padding-right: 0; }
  /* Open orders. Seven columns rather than five: the symbol, and how far the
     price is from the last trade, are what say whether an order will ever
     fill. Capped like every other table here. See `.col-head, .row`. */
  #my-orders-head, #my-orders .row {
    --cols: minmax(min-content, 7ch) minmax(5ch, 10ch) minmax(min-content, 6ch)
            minmax(min-content, 8ch) minmax(min-content, 8ch)
            minmax(min-content, 8ch) minmax(min-content, 9ch);
  }
  /* Open orders drops two columns when the block it is in is too narrow for
     seven, the way every fitted table on this page drops columns it has no room
     for. But this table's widths are written here rather than measured, so the
     two it gives up are named here too.

     This is not only about small screens. The seven columns come to 56ch, which
     is 437px in this font, and the block that holds them is 341px wide at a
     1440px window: measured there with one resting order, the row ran 369px
     wide, so its last 28px, half of the Cancel button, were cut off by
     `overflow-x: hidden` on a box that does not scroll sideways. The one order
     a visitor had placed was the one order they could not take back. At 2560 the block is 594px, the row is 470px, and all seven stay.

     The order id goes first: the row it names is on screen, and its Cancel
     button carries the id in its own tooltip. "From last" goes next: it is the
     distance from the price of the last trade, which is the number at the top
     of the Order Book. What is left says what the order is: symbol, side,
     price and quantity, and it offers the button that takes it back.

     `:not(:only-child)` keeps the "You have no open orders" line out of this.
     That line is one span standing in for a whole row, and it is the first
     child of its row. */
  @container (max-width: 440px) {
    #my-orders-head, #my-orders .row {
      --cols: minmax(5ch, 10ch) minmax(min-content, 6ch) minmax(min-content, 8ch)
              minmax(min-content, 8ch) minmax(min-content, 9ch);
    }
    #my-orders-head > :nth-child(1), #my-orders-head > :nth-child(6),
    #my-orders .row > :nth-child(1):not(:only-child),
    #my-orders .row > :nth-child(6):not(:only-child) { display: none; }
  }
  /* The floor is what one row and a heading come to. It is the number that sets
     how tall this block is at a window too short for all three blocks, so it is
     kept small: the list scrolls, the panel does not have to. */
  #my-orders { flex: 1 1 auto; min-height: 56px; overflow-y: auto; overflow-x: hidden; }
  /* Taller rows than the read-only tables on this page. Every row here holds a
     button, and an 18px row makes that button hard to hit. */
  #my-orders .row { line-height: 22px; }
  #my-orders .row:hover { background: rgba(234, 236, 239, 0.06); }
  #my-orders .row + .row { border-top: 1px solid rgba(43, 49, 57, 0.7); }
  #my-orders .cancel { text-align: right; }
  /* A bordered control rather than an underlined word. Cancelling is the one
     thing a visitor does from this table, and it should not look like a
     footnote. Red only under the pointer: a row of red buttons reads as a row
     of errors. */
  .cancel-btn {
    border: 1px solid var(--border);
    border-radius: 3px;
    background: var(--bg);
    color: var(--dim);
    font: inherit;
    font-size: 11px;
    line-height: 15px;
    padding: 0 7px;
    cursor: pointer;
  }
  .cancel-btn:hover { border-color: var(--red); color: var(--red); background: var(--red-bg); }
  /* An empty list keeps its heading and drops its column headings:
     `Symbol Side Price Quantity` above no rows names nothing, and what is left
     is one line under one heading, which is the shape the block beside it has.
     The two then start at the same place. The room under them belongs to the
     list and stays empty, because the panel is sized by the layout and not by
     what is in it: at 2560 the ticket panel is 534px tall and everything in it
     comes to 150px. */
  .tk-col:has(.tk-empty) .col-head { display: none; }
  .tk-empty { color: var(--dim); line-height: 17px; padding: 4px 0; }
  #trade-result { font-size: 11px; line-height: 15px; min-height: 15px; word-break: break-word; }
  #trade-result .why { color: var(--dim); }
  #trade-result code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    color: var(--text);
  }
  /* The one line that says what the key is. Deliberately plain: it is a
     browser-storage demo key, and dressing it up as a wallet would be a claim
     about safety that nothing here backs. */
  .keynote { color: var(--dim); font-size: 11px; line-height: 16px; }
  .tk-steps {
    list-style: decimal;
    padding-left: 17px;
    margin-top: 7px;
    color: var(--dim);
    font-size: 11px;
    line-height: 16px;
  }
  .tk-steps li { margin-top: 2px; }
  .linkish {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    font-size: 11px;
    color: var(--dim);
    text-decoration: underline;
    cursor: pointer;
  }
  .linkish:hover { color: var(--text); }
  /* One order's path through the separate service: one block per submission, kept
     until the page is reloaded, because "the sequencer took 0.3s" is only
     evidence if it is still on screen after it happened. */
  #hatch { flex: 0 1 auto; min-height: 0; overflow-y: auto; margin-top: 6px; }
  #hatch .h-row { padding: 3px 0; border-top: 1px solid var(--border); }
  #hatch .h-row:first-child { border-top: none; }
  #hatch .h-what { line-height: 16px; }
  #hatch .h-state { color: var(--dim); font-size: 11px; line-height: 15px; }
  #hatch .mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    color: var(--dim);
  }

  /* The anchor, on the verification strip.
     Deliberately not styled like the counters beside it. Every other item on
     that strip is the matcher reporting on its own work; this one the browser
     read out of a contract on Base, over an RPC this operator does not run. A
     reader who cannot tell those two apart at a glance has been given the more
     important number in the weaker one's clothes, so this gets a box and the
     accent colour the page already uses for verification. */
  .verify .base {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    padding: 1px 7px;
    border-radius: 3px;
    border: 1px solid rgba(240, 185, 11, 0.45);
    background: rgba(240, 185, 11, 0.10);
    color: var(--accent);
    font: inherit;
    cursor: pointer;
  }
  .verify .base:hover { border-color: var(--accent); background: rgba(240, 185, 11, 0.18); }
  .verify .base .n { color: var(--text); font-weight: 600; }
  .verify .base.down { border-color: rgba(246, 70, 93, 0.5); background: var(--red-bg); color: var(--red); }
  .verify .base.down .n { color: var(--red); }
  /* A reading that arrived, and a chain this browser cannot reach right now.
     Neither the accent of a live read nor the red of a failure: the number is
     still true, and what is wrong is that it is not being refreshed. */
  .verify .base.stale {
    border-color: var(--border);
    background: transparent;
    color: var(--dim);
  }
  .verify .base.stale .n { color: var(--dim); }
  /* The box and the link out are one item, so they sit together rather than a
     strip gap apart: opening the list and opening the explorer are two ways to
     look at the same contract. */
  .verify .anchor { display: inline-flex; align-items: center; gap: 5px; }
  .verify .out { color: var(--accent); text-decoration: none; }
  .verify .out:hover { color: var(--text); }

  /* The anchors view. A second full-height view rather than another panel in
     the grid: the grid is full, every boundary in it is the visitor's to drag,
     and a table of 64-character hashes does not belong in a 340px column. */
  #anchors {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    /* The one scroll on this page that is allowed to exist. Vertical only:
       everything too wide gets its own `.hscroll` below, so the body never
       scrolls sideways. */
    overflow-x: hidden;
    padding: 8px;
  }
  #anchors .panel { overflow: visible; margin-bottom: 8px; }
  #anchors h2 { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
  #anchors h2 .iv { margin-left: auto; }
  /* The way out. Top left, before the title, and drawn as a control rather than
     as the faint inline link it was at the right end of this heading: this view
     fills the window and reads as a separate page, so the thing that leaves it
     has to be where a reader looks for one. Two other ways do the same: the
     browser's back button, because the view has an address of its own, and
     Escape, both in `closeAnchors`. */
  #ax-close {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 5px 11px 5px 9px;
    border: 1px solid var(--border);
    border-radius: 3px;
    background: var(--bg);
    color: var(--text);
    font: inherit;
    font-size: 12px;
    text-transform: none;
    letter-spacing: 0;
    cursor: pointer;
  }
  #ax-close:hover { border-color: var(--dim); background: rgba(234, 236, 239, 0.06); }
  #ax-close .arrow { font-size: 15px; line-height: 1; color: var(--dim); }
  #ax-close:hover .arrow { color: var(--text); }
  #anchors .body { padding: 0 12px 12px; }
  #anchors p { color: var(--dim); font-size: 12px; line-height: 17px; max-width: 96ch; margin: 6px 0; }
  #anchors p b, #anchors p .n { color: var(--text); font-weight: 600; }
  #anchors code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    color: var(--text);
    word-break: break-all;
  }
  /* Commands are meant to be copied whole, so they keep their own line breaks
     and scroll on their own rather than wrapping mid-flag. */
  #anchors pre {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px;
    line-height: 16px;
    color: var(--text);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 7px 9px;
    margin: 6px 0;
    overflow-x: auto;
  }
  #anchors a { color: var(--accent); }
  /* Anything wider than the window scrolls inside this, never the page. */
  .hscroll { overflow-x: auto; overflow-y: hidden; }
  /* The last column is the verify button, and it now carries what the click
     will cost, "verify · 13,774 msg · 1.7 MB", so it is capped at the width
     of that sentence rather than at the width of the word "verify". */
  #ax-rows .row, #ax-head {
    --cols: minmax(min-content, 5ch) minmax(min-content, 20ch) minmax(min-content, 11ch)
            minmax(14ch, 16ch) minmax(14ch, 16ch) minmax(11ch, 13ch) minmax(7ch, 30ch);
  }
  /* The button that starts something expensive, after the page has said what it
     costs. Heavier than the `linkish` buttons around it on purpose: this is the
     click that spends the megabytes, so it should not look like a footnote. */
  #anchors .go {
    padding: 3px 10px;
    border: 1px solid var(--border);
    border-radius: 3px;
    background: var(--bg);
    color: var(--text);
    font: inherit;
    font-size: 11px;
    cursor: pointer;
  }
  #anchors .go:hover { border-color: var(--dim); }
  #anchors .go:disabled { color: var(--dim); cursor: default; }
  /* Elsewhere on this page a right-aligned figure is followed by another
     right-aligned figure, so `.num`'s left padding is the whole gap. Here a
     right-aligned message id is followed by a left-aligned hash, and without a
     gap on this side the two print as one string: "19,77112058c23c488…". */
  #ax-head > *, #ax-rows .row > * { padding-right: 10px; }
  #ax-rows .row:hover { background: rgba(234, 236, 239, 0.06); }
  .copy {
    background: none; border: none; padding: 0; font: inherit; color: var(--dim);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    cursor: pointer; text-align: left;
  }
  .copy:hover { color: var(--text); }
  /* One anchor's verification, under the row it belongs to. Full width and
     allowed to wrap: it is a sentence with two 64-character hashes in it, and
     a hash cut with an ellipsis is not evidence of anything. */
  .ax-result {
    padding: 4px 12px 8px;
    font-size: 11px;
    line-height: 16px;
    color: var(--dim);
    border-bottom: 1px solid var(--border);
    word-break: break-word;
  }
  .ax-result .mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    word-break: break-all;
  }
  /* The session alarm. Loud on purpose: every other failure this page can show
     is a service being unreachable, and this one is the operator serving a
     history that is not the history they committed to. */
  #ax-alarm:not(:empty) {
    border: 1px solid var(--red);
    background: var(--red-bg);
    border-radius: 4px;
    padding: 10px 12px;
    margin-bottom: 8px;
    line-height: 18px;
  }
  #ax-alarm .what { color: var(--red); font-weight: 700; font-size: 14px; }
  #ax-alarm .why { color: var(--text); font-size: 12px; }
  #ax-alarm .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; }
  .ax-order { padding: 6px 0; border-top: 1px solid var(--border); line-height: 17px; }
  .ax-order:first-child { border-top: none; }
  .ax-order .said { color: var(--dim); font-size: 11px; line-height: 15px; }
  .ax-order .proven { color: var(--green); font-size: 11px; line-height: 16px; }
  /* A 64-character root in one of these lines. Without this it is drawn in the
     body font and cannot break, and `#anchors` hides its horizontal overflow,
     so the tail of the hash is cut off, and half a hash is not evidence. The
     same rule `.ax-result .mono` already carries, for the same reason. */
  .ax-order .mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    word-break: break-all;
  }
  /* The checks one order offers, side by side. They answer different questions
     and cost different amounts, so they are separate controls rather than one,
     and the table above them says which rests on what. */
  .ax-order .checks { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 3px; align-items: center; }
  /* What every check on this page depends on, above the orders. A table rather
     than a paragraph because the whole point is the comparison down the middle
     column, and three sentences do not line up the way three rows do. */
  .ax-rests {
    border-collapse: collapse;
    margin: 8px 0 10px;
    font-size: 11px;
    line-height: 16px;
    color: var(--dim);
  }
  .ax-rests th, .ax-rests td { text-align: left; padding: 4px 18px 4px 0; vertical-align: top; }
  .ax-rests th {
    color: var(--dim);
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 10px;
    border-bottom: 1px solid var(--border);
  }
  .ax-rests td { border-bottom: 1px solid var(--border); }
  /* The first row is the check the buttons start with, so it is the one row
     drawn in the body colour. */
  .ax-rests tbody tr:first-child td { color: var(--text); }
  .ax-rests code { font-size: 11px; }
  /* The checks that end at the operator's own signature. Still here, still one
     click away, and no longer sitting beside the one that does not. */
  details.ax-other { margin-top: 3px; }
  details.ax-other > summary {
    color: var(--dim);
    font-size: 11px;
    line-height: 15px;
    cursor: pointer;
    list-style-position: outside;
    width: fit-content;
  }
  details.ax-other > summary:hover { color: var(--text); }
  /* One of the other checks: its button, and directly under it the line that
     check writes. Grouped so a reader never has to work out which of three
     answers belongs to which of three buttons. */
  .ax-alt { margin-top: 5px; }

  /* ===========================================================================
     Windows the layout above does not fit in

     Everything above lays this page out as a fixed frame: the body is one
     screen tall with its overflow hidden, `main` is three columns with the
     Traders row under them, and each panel scrolls inside itself. Nothing in it
     is allowed to move the page, because there is no page to move.

     That frame holds down to about 840px of width and no further. Measured in a
     820px window: the header's "reset layout" button runs from 772px to 827px,
     so 7px of it is off the right edge and no scroll reaches it. On a phone it
     is not cramped, it is gone. Measured in a 390px window the document is
     827px wide, which leaves the chart, the ticket, Recent Trades and Order
     Flow entirely off the right edge, and `overflow: hidden` on the body means
     nothing brings them back.

     So under 1100px the panels are laid out again rather than squeezed, and the
     page is allowed to scroll. Each panel is given a height here, so the panels
     keep their own scrollbars: 50 accounts still scroll inside a 300px box
     instead of making the page 1000px longer.

     Nothing below changes what the page does or what the script measures. The
     script fits the book and the tables from `clientHeight` and `clientWidth`
     of the boxes they are drawn in, so both follow a panel that changed shape
     without being told that it did. See fitBook and fitTable. The custom
     properties the layout script writes (--col-left, --chart-share,
     --traders-h and the rest) are still written and are simply not read by
     these rules, and the script only stores a size when a handle is dragged,
     which is not something these widths let happen. A layout dragged on a
     desktop therefore comes back whole on that desktop after the same browser
     has been on a phone.
     =========================================================================== */

  @media (max-width: 1099px) {
    /* The page scrolls now. `dvh` and not `vh`, because on a phone `100vh` is
       the height the window has once the address bar has scrolled away, so a
       `100vh` page starts with its last 50-odd pixels under a bar that is still
       there. `vh` first, for a browser that does not know `dvh`. */
    body {
      height: auto;
      min-height: 100vh;
      min-height: 100dvh;
      overflow-x: hidden;
      overflow-y: auto;
    }
    /* The header is one row until it runs out of room. Measured in a 820px
       window, the "reset layout" button ran from 772px to 827px; in a 740px
       window the ticker alone is 382px wide and takes the last two figures out
       with it. Wrapped, each of them starts a line of its own and nothing
       leaves the window. */
    /* Both bars wrap here, so both of them are paid for by the line and not by
       the bar: the header is the name and the pairs, then the figures; the
       strip is two lines of claims at 768. Measured there before this: 73px of
       header over a 65px strip, 138px of a 1024px screen before the first
       price. The padding above and below each bar goes from 10px to 7px and
       from 6px to 4px, the space between two lines of the header from 10px to
       6px, and the 16px at each end to 12px, which is also 8px more for the
       strip to pack its claims into. */
    header { flex-wrap: wrap; gap: 6px 20px; padding: 7px 12px; }
    .verify { padding: 4px 12px; }
    /* The figures take whatever is left of the line they are on and spread
       across it. Measured at 768px, where the header wraps: the run of figures
       is 632px on a 748px line, and with the auto margins above it sat against
       the right edge with the 116px on the left of it. Filling the line puts
       the same six figures at even distances instead of leaving a hole in one
       place. */
    .ticker { flex: 1 1 auto; margin: 0; justify-content: space-evenly; }
    /* A symbol is one word. Without this, "MERKLE-USDC" is broken over two
       lines the moment the tab bar is short of room, and a tab twice the
       height of the tabs beside it reads as the selected one. */
    .tab { white-space: nowrap; }
    /* The layout it resets is the three columns and the Traders row, and none
       of those boundaries exist at this width: every handle below is hidden,
       because an 8px target is not something a finger can take hold of and
       there is nothing left to drag it to. So the button that puts them back is
       hidden with them. */
    #layout-reset { display: none; }
    .gut, .fitted .ch { display: none; }

    /* The two column boxes stop being boxes. `display: contents` puts the four
       panels inside them into `main`'s grid directly, so the one set of areas
       below places all six panels. #midcol and #rightcol no longer have a
       height for #chart-panel and #trades-panel to take a share of, which is
       why both are given a row here instead. */
    #midcol, #rightcol { display: contents; }
    main {
      grid-template-columns: minmax(300px, 1fr) minmax(0, 1.4fr);
      grid-template-rows: 360px auto 300px auto;
      grid-template-areas:
        "book    chart"
        "trade   trade"
        "trades  flow"
        "traders traders";
      gap: var(--gut);
    }
    #book-panel { grid-area: book; }
    #chart-panel { grid-area: chart; }
    #trade-panel { grid-area: trade; }
    #trades-panel { grid-area: trades; }
    #flow-panel { grid-area: flow; }
    /* `height` and not the row, because the base rule sets one in pixels. */
    #traders { grid-area: traders; height: auto; }

    /* The ticket takes the height it needs and the page scrolls past it, rather
       than scrolling inside a panel that is itself inside a scrolling page. The
       two lists in it are still capped: an account with 30 resting orders would
       otherwise put 700px of table between the ticket and the rest of the page.
       */
    #my-orders, #hatch { max-height: 210px; }

    /* Three boxes where there is room for three, two where there is room for
       two, one on a phone. `auto-fit` and not a width breakpoint, for the
       reason the ticket's own columns give above: what these boxes have to fit
       into is the window less the padding, and 300px is what the narrowest of
       them needs before its columns start dropping: Positions, whose table is
       Symbol, Quantity and Unreal. */
    #traders-body {
      grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
      grid-auto-rows: 280px;
      gap: var(--gut);
    }
    /* The 8px between the boxes is the grid's `gap` here, not padding inside
       each track: the two handles that made padding necessary are hidden. */
    .tcol { padding: 0; }
    /* Collapsed, the three boxes are three headings, and a heading is not
       280px tall. */
    #traders.collapsed #traders-body { grid-auto-rows: auto; }

    /* The book fills the panel. Above 1100px the three columns are capped at
       13ch each and whatever is left over is empty on the right, because that
       panel is a column the visitor drags and figures that drift further apart
       every time it widens are harder to read. Here it is neither dragged nor a
       column: it is a row of the page, as wide as the window. Measured in a
       600px window, where the panel is 588px: three columns of 13ch are 86px
       each, so a row ended 270px in and left 304px of itself empty. `--band` is
       what the depth bar behind the rows is measured against, so it follows the
       columns out to the edge. */
    #book-panel .col-head, #book .row { --cols: repeat(3, minmax(min-content, 1fr)); }
    #book .row { --band: 100%; }

    /* A drag on a chart pans it, and until now that drag was the only thing a
       finger could do on it: `touch-action: none` says this element handles
       every gesture itself. On a page that scrolls, a vertical swipe that
       starts on the chart has to scroll the page. The chart is 280px of a
       phone screen, and a reader who cannot get past it is stuck. `pan-y`
       hands vertical swipes back to the browser and keeps sideways drags for
       the chart, and `pinch-zoom` leaves the reader able to zoom the page in
       from anywhere on it. The pan handler needs no change: the browser sends
       `pointercancel` when it takes a gesture over, and that is already how a
       drag ends here. */
    #chart, #pnl-chart { touch-action: pan-y pinch-zoom; }
  }

  /* Two boxes to a row leaves the third one alone on the second row with half
     of that row empty. It takes the whole row instead: the same 280px of
     height, and an Account Trades table 736px wide rather than 364px. 931px is
     where a third column starts to fit: three 300px boxes, two 8px gutters and
     main's 16px of padding. Below 640px the row is one column and this rule
     changes nothing. */
  @media (max-width: 931px) {
    #traders-body .tcol:nth-child(3) { grid-column: 1 / -1; }
  }

  /* One column. Two panels side by side stop being readable at about 640px:
     the Order Book's own figures are 39ch wide, which is 273px in this font,
     and half of 640 less the padding is 312px. */
  @media (max-width: 639px) {
    main {
      grid-template-columns: minmax(0, 1fr);
      grid-template-rows: repeat(6, auto);
      grid-template-areas: "book" "chart" "trade" "trades" "flow" "traders";
      gap: 6px;
      padding: 6px;
    }
    /* What each panel is worth on a screen this size. The book gets the most:
       at 372px it holds 7 levels a side, which is a book rather than a sample
       of one. */
    #book-panel { height: 372px; }
    #chart-panel { height: 280px; }
    #trades-panel { height: 260px; }
    #flow-panel { height: 280px; }
    #traders-body { grid-auto-rows: auto; }
    .tcol { height: 300px; }
    /* Positions is a table and a chart in one box, so it is the one box that
       needs more than the other two. */
    #traders-body .tcol:nth-child(2) { height: 340px; }
    #traders.collapsed .tcol { height: auto; }

    /* Three rows: the name with the `explain` button at the end of it, then the
       symbols, then the figures. Wrapping put `explain` on a fourth line of its
       own, 24px of header for one word, because the ticker's last line was
       already full. A grid says where it goes instead of leaving it to
       whatever the ticker did not use. `reset layout` takes no cell: it is
       hidden at this width, and a hidden item is not laid out. */
    header {
      display: grid;
      grid-template-columns: minmax(0, 1fr) auto;
      align-items: center;
      gap: 7px 14px;
      padding: 7px 10px;
    }
    .logo { font-size: 14px; grid-column: 1; grid-row: 1; }
    #explain { grid-column: 2; grid-row: 1; justify-self: end; }
    /* The pairs are a group of buttons and they are centred as one: the gap
       between two of them says they belong together, and spreading them to the
       edges of a 390px screen would say the opposite. */
    .tabs { grid-column: 1 / -1; grid-row: 2; flex-wrap: wrap; justify-content: center; }
    /* The figures are six separate readings, so they share the row they are on
       rather than sitting in a group at one end of it. Two lines at 390px:
       Last, Bid, Ask and Spread on the first, Volume and Trades on the second,
       and `space-evenly` gives the second line the same width as the first
       instead of leaving 150px of it empty on the right. */
    .ticker {
      grid-column: 1 / -1;
      grid-row: 3;
      flex-wrap: wrap;
      justify-content: space-evenly;
      margin: 0;
      gap: 2px 14px;
    }
    /* The gap between two claims is three times the gap inside one, which is
       what tells them apart now. */
    .verify { gap: 5px 16px; padding: 4px 10px; }

    /* How many controls a row of the ticket holds is decided against the panel
       and not against the window. See the `@container` rules beside `.ticket`
       above. What is left here is what a finger needs, which is a property of
       the phone and not of the panel.

       Big enough to hit. The controls above are 26px tall, which is a size
       chosen for a pointer; a finger needs about 34px. */
    .tk input, .tk select { padding: 8px 9px; }
    /* And this is not a size, it is a rule: Safari on iOS zooms the whole page
       in when a field it is about to focus has text under 16px, and it does not
       zoom back out afterwards. The page is 13px everywhere else and stays
       there; these four controls, two fields and two lists, are the only
       ones a visitor taps into. */
    .tk input, .tk select { font-size: 16px; }
    .sides { width: 100%; }
    .sides button { padding: 8px 10px; }
    #send { width: 100%; padding: 9px 26px; }
    .iv { padding: 4px 9px; margin-left: 3px; }
    #my-orders .row { line-height: 26px; }
    .cancel-btn { padding: 3px 9px; line-height: 18px; }

    /* The anchors view is text and 64-character hashes, and both already wrap;
       what it needs here is the room its padding was taking. */
    #anchors { padding: 6px; }
    #anchors .body { padding: 0 10px 10px; }
  }


  /* Short and wide: a desktop window dragged to half the height of the screen.
     The three columns still fit, nothing here is about width, so they stay,
     and the page scrolls under them rather than the panels being squeezed to
     nothing. Measured in a 1440x600 window: the three columns had 264px
     between the verification strip and the bottom of the screen, which the
     middle column split into a 127px chart over a 137px ticket: a heading,
     one row of controls, and a scrollbar for the rest of it.

     A height and not a floor. `main` is a flex item with `flex: 1`, and in a
     body that is no longer one screen tall there is no leftover height to give
     it, so it takes the height of what is in it instead: measured at 1440x600
     with `min-height: 700px` and nothing else, `main` came out 2097px tall and
     the page 2210px. A height is a height at any window size.

     700px is what the row above Traders needs to stay a market. 296px of
     Traders, the 8px gutter and main's own 16px come off it, which leaves
     380px: a 182px chart over a 190px ticket, and 7 levels a side in the book.
     What is left of the window goes to the header and the strip, and the page
     scrolls when there is not enough for both. */
  @media (min-width: 1100px) and (max-height: 699px) {
    body {
      height: auto;
      min-height: 100vh;
      min-height: 100dvh;
      overflow-x: hidden;
      overflow-y: auto;
    }
    main { flex: none; height: 700px; }
    /* The ticket gives up the line under its two headings before the chart
       gives up half its candles. At 1440x600 the middle column is 380px: with
       the 240px the ticket asks for elsewhere the chart is 132px, and with
       this the chart is 182px and the ticket 198px, which still holds the
       controls, both headings and the first line under them. */
    #chart-panel { --ticket-floor: 190px; }
  }
