  /* Trade Calculator — phone-first.
     The device most likely to be in your hand when a stop needs sizing is an
     iPhone, so the layout below is built for a 390px column first and only
     spreads out when there is room. Everything that isn't a number you decide
     from is either folded away or gone. */
  :root {
    --bg: #0f1115;
    --panel: #171a21;
    --panel2: #1e222b;
    --border: #2a2f3a;
    --text: #e6e9ef;
    --muted: #8a92a3;
    --accent: #4c8dff;
    --green: #3ecf8e;
    --red: #ff5c6c;
    --amber: #ffb020;
    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    --tap: 44px;              /* minimum comfortable touch target */
    --gap: 12px;
  }
  @media (prefers-color-scheme: light) {
    :root {
      --bg: #f4f6fa; --panel: #ffffff; --panel2: #eef1f6; --border: #d7dce4;
      --text: #1a1d24; --muted: #5b6472; --accent: #2f6fed;
      --green: #12a06a; --red: #d63a4a; --amber: #b57a00;
    }
  }
  * { box-sizing: border-box; }
  html { -webkit-text-size-adjust: 100%; }
  /* One focus ring for everything the keyboard can land on. The UA default on
     these custom-background buttons is faint to invisible in Safari, and two
     hand-placed rings (the book's and the watchboard's rows) were the only
     ones in the file. :focus-visible, not :focus — a tap should not leave a
     ring behind. Rows and the pill-shaped segments pull the ring inward,
     because their containers clip at the edge where an outset ring would go. */
  button:focus-visible, summary:focus-visible, [tabindex]:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 2px;
  }
  .seg button:focus-visible, tr[tabindex]:focus-visible,
  .bk-row:focus-visible, .wm-row:focus-visible {
    outline-offset: -2px;
  }
  body {
    margin: 0; background: var(--bg); color: var(--text);
    font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    padding: calc(10px + env(safe-area-inset-top)) calc(12px + env(safe-area-inset-right))
             calc(16px + env(safe-area-inset-bottom)) calc(12px + env(safe-area-inset-left));
  }
  /* No 300ms tap delay, no grey flash on every button, no accidental text
     selection when a card is double-tapped. */
  button, summary, .seg, tbody tr { -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
  button, summary, label, th { -webkit-user-select: none; user-select: none; }
  .wrap { max-width: 860px; margin: 0 auto; }
  .hidden { display: none !important; }

  /* ---- header ---- */
  /* The tab bar, and one button, on one line. Everything that used to live up
     here — the exchange switch, the signed-in bar, the key panel, and finally
     the app's own name — is behind that button now or gone; what survives on
     screen is the three screens and the one word you check rather than set.
     The rule under the tabs is the header's, not the nav's, so it runs the full
     width of the column and the button sits on it rather than beside it, with
     the tabs' own underline riding a pixel over it on the active one. */
  .topbar {
    display: flex; align-items: flex-start; gap: 12px; margin-bottom: 14px;
    border-bottom: 1px solid var(--border);
  }
  .envhint:not(:empty) { display: block; margin: 8px 0 0; }

  /* ---- trading is locked ---- */
  /* Directly under the tab bar, so it is read before the screen it sits on
     whichever screen that is — being locked is a fact about the account, not
     about the tab you happen to have open, and the screener is where you go
     looking for the trade the pause exists to prevent.
     Red and full width on purpose. Every other warning in this app is a note
     inside the card it is about; this one is deliberately not, because the
     failure it guards against is the one where you do the work first and meet
     the answer last. */
  .lockbar {
    margin: -4px 0 14px; padding: 10px 12px; border-radius: 10px;
    background: color-mix(in srgb, var(--red) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--red) 45%, transparent);
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
    font: 13px/1.45 inherit; color: var(--text);
  }
  .lb-tag {
    font: 700 11px var(--mono); letter-spacing: .08em; text-transform: uppercase;
    color: var(--red); white-space: nowrap;
  }
  /* The countdown is the one number here anybody acts on, so it is the one
     thing set in the mono face the rest of the app reserves for figures. */
  .lb-left { font: 700 13px var(--mono); white-space: nowrap; }
  .lb-why { color: var(--text); min-width: 0; }
  .lb-until { color: var(--muted); font: 12px var(--mono); white-space: nowrap; }
  /* Second line, quieter: what the pause does NOT stop. Somebody reading this
     bar while holding a losing position needs to know the exit still works
     before they start looking for a way around the app. */
  .lb-note { flex-basis: 100%; color: var(--muted); font-size: 11px; line-height: 1.5; }
  footer { color: var(--muted); font-size: 11px; margin-top: 14px; text-align: center; }

  /* The environment pill *is* the settings button: which exchange you are on is
     the only thing in there worth saying on every screen, so it does the saying
     and carries the gear. The dot appears when what is behind it needs you —
     signed out, or no key for this environment — because a warning that only
     exists inside a closed sheet is a warning nobody reads. */
  .settingsbtn {
    display: flex; align-items: center; gap: 8px; flex: none; align-self: center;
    background: var(--panel2); border: 1px solid var(--border); border-radius: 9px;
    min-height: 32px; padding: 0 9px; cursor: pointer; color: var(--muted); font: 13px inherit;
  }
  .settingsbtn .gear { font-size: 15px; line-height: 1; position: relative; }
  .settingsbtn.attn .gear::after {
    content: ""; position: absolute; top: -2px; right: -4px; width: 7px; height: 7px;
    border-radius: 50%; background: var(--amber); border: 1px solid var(--panel2);
  }

  /* ---- settings sheet ---- */
  /* A modal rather than another panel in the column: the page behind it is held
     still (a phone will happily scroll the app under your finger otherwise), and
     it lands as a bottom sheet on a phone, a centred card where there is room. */
  .sheet { position: fixed; inset: 0; z-index: 60; display: flex; align-items: flex-end; justify-content: center; }
  .sheet-scrim { position: absolute; inset: 0; background: rgba(0, 0, 0, .55); }
  .sheet-panel {
    position: relative; width: 100%; max-width: 720px; max-height: 92vh;
    display: flex; flex-direction: column; background: var(--bg);
    border: 1px solid var(--border); border-radius: 16px 16px 0 0; outline: none;
  }
  .sheet-h {
    display: flex; align-items: center; gap: 10px; flex: none;
    padding: 12px 14px; border-bottom: 1px solid var(--border);
  }
  .sheet-h h2 { font-size: 16px; margin: 0; margin-right: auto; }
  .sheet-h .icon { width: 36px; height: 36px; font-size: 14px; }
  .sheet-body {
    overflow-y: auto; -webkit-overflow-scrolling: touch;
    padding: 14px 14px calc(16px + env(safe-area-inset-bottom));
  }
  .stab-body { display: none; }
  .stab-body.on { display: block; }
  .stab-body > .panel:last-child, .stab-body > .userbar:last-child,
  .stab-body > .sset:last-child { margin-bottom: 0; }
  .sset { margin-bottom: 14px; }
  .sset h3 { font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 0 0 8px; }

  /* Exchange is an ordinary section of the Account tab now, so it takes the
     sheet body's own padding like every other one. It used to be pinned above
     the tab bar with padding of its own — the argument being that it is the
     mode the page is tinted by rather than a setting filed under one. That is
     still true, and it is still said on the settings button, which is the
     reading; what was pinned here was a second copy of the reading with a
     switch attached, above every visit to every other tab. */

  /* Only the sheet is scrollable while it is up. The body keeps its width and
     its place, and the inline `top` app code sets is what puts the scroll
     position back when it closes. */
  body.sheet-open { position: fixed; left: 0; right: 0; overflow: hidden; }
  /* Where there is room it stops being a sheet and becomes a centred card. */
  @media (min-width: 641px) {
    .sheet { align-items: center; padding: 24px 16px; }
    .sheet-panel { border-radius: 14px; max-height: 88vh; }
  }

  /* ---- the sign-in gate ---- */
  /* Opaque, and over everything including the settings sheet. The point is that
     nothing behind it is readable: a balance, a position or a journal row left
     showing through would be a number that was true when it was fetched and is
     now anybody's guess, sitting next to a form that says the app can no longer
     read the account. It sits at the top on a phone so the keyboard has room,
     and centres where there is any. */
  .gate {
    position: fixed; inset: 0; z-index: 80; overflow-y: auto; background: var(--bg);
    display: flex; align-items: flex-start; justify-content: center;
    padding: calc(24px + env(safe-area-inset-top)) calc(12px + env(safe-area-inset-right))
             calc(24px + env(safe-area-inset-bottom)) calc(12px + env(safe-area-inset-left));
  }
  .gate-panel { width: 100%; max-width: 420px; margin-bottom: 0; }
  /* The gate scrolls; the page underneath must not scroll with it. */
  body.gated { overflow: hidden; }
  @media (min-width: 641px) { .gate { align-items: center; } }

  /* ---- panels ---- */
  .panel { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 14px; margin-bottom: 12px; }
  .sub { color: var(--muted); font-size: 12px; margin-bottom: 12px; }

  /* A second-tier section inside a panel: settings you set once, a credential, a
     mode you rarely leave. The headline value on the summary line means what is
     behind it can be checked without opening it. (Whole panels used to fold this
     way too — the account and the journal. They are tabs now.) */
  details.more > summary { cursor: pointer; list-style: none; display: flex; align-items: center; gap: 10px; }
  details.more > summary::-webkit-details-marker { display: none; }
  details.more > summary::after {
    content: ""; width: 7px; height: 7px; margin-left: auto; flex: none;
    border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
    transform: rotate(45deg) translate(-2px, -2px); transition: transform .15s;
  }
  details.more[open] > summary::after { transform: rotate(-135deg); }
  /* The headline value sits between the title and the chevron: with the chevron
     also margin-left:auto, the free space splits between them and an empty value
     collapses cleanly to nothing. */
  .fold-v { color: var(--muted); font: 13px var(--mono); margin-left: auto; }
  details.more { margin-top: 12px; border-top: 1px solid var(--border); padding-top: 10px; }
  details.more > summary { min-height: 32px; color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }
  details.more > summary::after { margin-left: 8px; width: 6px; height: 6px; }
  details.more[open] > summary { margin-bottom: 10px; }

  /* ---- form ---- */
  /* One grid: exactly two columns on a phone — including a 320px SE, where an
     auto-fit track collapses to one and doubles the height of the form — and as
     many as fit above that. Anything needing the full line says .row-wide. */
  .form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gap); align-items: start; }
  @media (min-width: 641px) {
    .form { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
    /* The form is phone-shaped by design; on a wide screen it is capped rather
       than stretched, so a two-state Long/Short switch doesn't end up 800px wide.
       The chart above it takes the whole column — it is a picture, and pictures
       want the room. The close button takes the whole column too, for the
       opposite reason: it is not a form, and while it is on screen the shape
       form is hidden, so a cap would align it with nothing on the page. */
    .shape, .sizing .form { max-width: 620px; }
    /* A ticker is eight characters. A field for it that runs the width of a
       laptop says it is expecting a sentence. */
    .tickerbar { max-width: 420px; }
    /* Capped with it, and to the same width: the two are one block at the top
       of the screen, and a record that ran wider than the field under it would
       read as a header for the whole page rather than as a line above a ticker. */
    .scorecard { max-width: 420px; }
  }
  .form + .form { margin-top: var(--gap); }
  .row-wide { grid-column: 1 / -1; }
  .field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
  .field label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
  .hint { font-size: 11px; color: var(--muted); text-transform: none; letter-spacing: 0; font-weight: 400; }
  .ladder { grid-column: 1 / -1; margin-top: -6px; }
  /* value + its unit switch on one line */
  .inline { display: flex; gap: 8px; align-items: center; min-width: 0; }
  .inline > input { flex: 1 1 90px; min-width: 0; }

  input[type="text"], input[type="number"], input[type="password"], select {
    background: var(--panel2); border: 1px solid var(--border); color: var(--text);
    border-radius: 9px; padding: 0 12px; font: 16px var(--mono); width: 100%;
    height: var(--tap); -webkit-appearance: none; appearance: none;
  }
  select { padding-right: 30px; cursor: pointer;
    background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
    background-position: right 15px center, right 10px center; background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; }
  input:focus, select:focus { outline: none; border-color: var(--accent); }
  input::placeholder { color: var(--muted); opacity: .7; }

  /* ---- segmented controls ---- */
  .seg { display: flex; width: 100%; border: 1px solid var(--border); border-radius: 9px; overflow: hidden; }
  .seg button {
    flex: 1; background: var(--panel2); color: var(--muted); border: none;
    min-height: var(--tap); padding: 0 10px; cursor: pointer; font: 13px/1 inherit; white-space: nowrap;
  }
  .seg button.on { background: var(--accent); color: #fff; }
  .seg.compact { width: auto; flex: none; }
  .seg.compact button { flex: none; padding: 0 12px; font-size: 12px; }
  /* A unit switch that belongs to the input above it: full width of that field,
     so the pair reads as one control rather than two. */
  .seg.units button { min-height: 38px; font-size: 12px; }
  .seg.side button.on.long { background: var(--green); color: #04150d; }
  .seg.side button.on.short { background: var(--red); color: #fff; }
  /* Mode tabs read as tabs, not as another blue control competing with Side.
     Overflow is visible here, unlike the pill-shaped segments above: there are
     no rounded corners left to clip, and the clipping was eating the bottom
     half of the active tab's underline where it overhangs the rule. */
  .seg.tabs {
    width: auto; border: none; border-radius: 0; overflow: visible;
    gap: 18px; margin-bottom: 14px; border-bottom: 1px solid var(--border);
  }
  .seg.tabs button {
    background: none; padding: 0 0 9px; min-height: 32px; font-size: 14px; color: var(--muted);
    border-bottom: 2px solid transparent; margin-bottom: -1px;
  }
  .seg.tabs button.on { background: none; color: var(--text); border-bottom-color: var(--accent); }

  /* ---- the settings sheet's tabs ---- */
  /* Risk / Keys / Account, pinned under the exchange switch. The rule they sit
     on is this element's own bottom border, so it runs the full width of the
     sheet like the header's does while the labels stay inset with everything
     else. Hidden entirely by js/auth.js when fewer than two of them are
     reachable: standalone mode has no account and no keys, and a bar with one
     tab on it is not a bar.
     Natural widths, packed left — the `flex: 1` turned off here is `.seg`'s,
     which is right for every other bar in the app and wrong for this one: four
     labels stretched across the sheet read as a segmented control offering four
     answers to one question, which is the opposite of what a tab says.
     They scroll sideways rather than wrap, because a tab bar on its second line
     stops reading as a bar — and four labels plus their dots is close enough to
     the width of a small phone that it is worth not depending on. */
  .seg.tabs.sheet-tabs {
    flex: none; margin: 0; padding: 2px 14px 0;
    overflow-x: auto; scrollbar-width: none;
  }
  .seg.tabs.sheet-tabs::-webkit-scrollbar { display: none; }
  .seg.tabs.sheet-tabs button { flex: none; display: flex; align-items: center; gap: 6px; }
  /* The same dot the gear wears, on the tab that is the reason for it — a
     warning behind two closed doors is worse than one behind one. */
  .seg.tabs.sheet-tabs button.attn::after {
    content: ""; flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--amber);
  }

  /* ---- top-level tabs ---- */
  /* Trade / Account / Journal — the only tabs left, now that the calculator has
     one mode. These three used to be one column you scrolled: the report and its
     chart sat between the inputs and the account, so checking a balance meant
     scrolling past a whole trade plan to reach a fold that was shut.
     Wider than the natural widths above, because each label carries a value
     underneath it and the values need room. That second line is the point of the
     bar: it is the same number the folds used to print on their summary lines,
     so the glance still costs nothing and only reading costs a tap. It keeps its
     height when empty so the three underlines stay level.
     The room is shared out from what each tab actually holds rather than in
     equal shares: a tab with nothing to print spent its share on white space
     while a four-figure balance next to it lost its last characters to an
     ellipsis. Spare width is still split evenly, so they stay comparable — a tab
     is just no longer paid for room it has nothing to put in. The Trade tab is
     the one that swings: silent while you are flat, and claiming its share the
     moment a position opens — which is what the other three give a little back
     for, and why what it prints is kept as short as two signed numbers can be.
     The rule these underlines sit on belongs to the header, so the nav gives up
     its own and lets the buttons' overhang land on that one instead. */
  .seg.tabs.nav { flex: 1; min-width: 0; gap: 8px; margin-bottom: 0; border-bottom: none; }
  .seg.tabs.nav button {
    flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column;
    align-items: flex-start; justify-content: flex-start; gap: 2px;
    min-height: 0; padding: 4px 0 6px; font: 13px/1.2 inherit; text-align: left;
  }
  .tab-v {
    font: 10px/1.2 var(--mono); color: var(--muted); min-height: 12px;
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .tab-body { display: none; }
  .tab-body.on { display: block; }

  /* ---- optional blocks, and the reader's say over them ---- */
  /* A block switched off in the Layout tab. Its own class, not .hidden: that
     one belongs to the block modules, which toggle it on every paint to say
     whether they have data — a preference written there would be clobbered
     within seconds. js/blocks.js owns this one and nothing else touches it. */
  .blk-off { display: none !important; }
  /* The two zones a reader can reorder become flex columns so `order` works;
     the layout is otherwise exactly the block layout it was. The trade screen
     keeps its non-optional tail (.trade-cols) pinned after the strips with an
     order past any a block can be given. */
  #tab-trade.on { display: flex; flex-direction: column; }
  #tab-trade > .trade-cols { order: 1000; }
  .trade-rail { display: flex; flex-direction: column; }

  /* The Layout tab's rows: one block each — a checkbox, and in the zones with
     an order, the pair of arrows. */
  .blk-row { display: flex; align-items: center; gap: 8px; border-top: 1px solid var(--border); padding: 2px 0; }
  .blk-row .acct-check { flex: 1; min-width: 0; }
  .blk-row .blk-move { flex: none; width: 40px; padding: 0; }
  .blk-foot { margin-top: 12px; }

  /* ---- your record, above the ticker ---- */
  /* The first thing on the trade screen, and deliberately the quietest thing on
     it. Bare on the page rather than in a card, like everything else above the
     chart: a panel would make it a section you read, and this is meant to be
     something you have already taken in by the time your thumb reaches the
     ticker field.
     Two rows. The windows across the top, each one a stack — how much evidence,
     the rate, and what it actually paid — because the rate is the number you
     glance at and the R is the number that says whether the rate is good news.
     The sides underneath, on one line: a slower fact, read less often, and it
     earns less room. */
  .scorecard {
    display: flex; flex-wrap: wrap; gap: 8px 10px;
    font: 12px var(--mono); margin: 0 0 12px;
  }
  .sc-wins { display: flex; gap: 10px; width: 100%; min-width: 0; }
  .sc-cell { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
  .sc-sides { display: flex; flex-wrap: wrap; gap: 4px 16px; width: 100%; }
  .sc-side { display: flex; align-items: baseline; gap: 6px; }
  .sc-lbl {
    font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .sc-cell .sc-rate { font: 700 17px/1.15 var(--mono); color: var(--text); }
  .sc-side .sc-rate { font: 700 13px var(--mono); color: var(--text); }
  /* The expectancy is what carries the colour, never the win rate. 65% is not
     good news on its own — it is good news at +0.4R a trade and a warning at
     +0.05R — and colouring the rate would say the opposite of that. */
  .sc-r { font-size: 11px; color: var(--muted); }
  .sc-r.up { color: var(--green); }
  .sc-r.down { color: var(--red); }
  /* Too few trades behind it to colour. The number is still printed — it is a
     fact about what happened — but it is not dressed up as a finding. */
  .sc-rate.quiet, .sc-r.quiet { color: var(--muted); }
  .sc-none { color: var(--muted); font-size: 11px; }

  /* ---- the loss limits, in the settings sheet ---- */
  /* Under the risk ladder, because it is the same question continued: that
     table says what three losses in a row cost, this says what the app then
     does about it. Every rule is a row of prose plus the two numbers that make
     it, so the setting and the sentence explaining it cannot drift apart —
     which is exactly what happened while the rules lived in an env file. */
  .lockrules { margin-top: 18px; }
  .lr-now { margin: 0 0 10px; font-size: 13px; color: var(--muted); }
  .lr-now.locked { color: var(--text); }
  .lr-now.locked b { color: var(--red); }
  /* A block per rule, not a table. Three columns put the reason text — the half
     that makes a threshold mean anything — in a cell that shoved both inputs
     off the side of a sheet 400px wide. Here the numbers come first and the
     argument runs underneath at full width. */
  .lr-list { display: flex; flex-direction: column; }
  .lr-rule { padding: 10px 0; border-top: 1px solid var(--border); }
  .lr-head { font-size: 13px; color: var(--text); }
  .lr-set { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin: 7px 0 5px; }
  .lr-pause { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); }
  .lr-set > .lr-pause { margin-left: 4px; }
  /* What the field becomes once its delay is up. Amber rather than red: this is
     a change that is going to happen, not a warning — and it is the only thing
     that makes a box snapping back to its old value read as "waiting" instead
     of as "that did not work". */
  .lr-next {
    font: 600 11px var(--mono); color: var(--amber); white-space: nowrap;
  }
  /* The reason the rule exists, under its name and quieter than it. This is the
     half that was missing entirely: a threshold with no argument attached is a
     number somebody else picked. */
  .lr-why { display: block; margin-top: 3px; color: var(--muted); font-size: 11px; line-height: 1.45; }
  .lr-field { display: inline-flex; align-items: baseline; gap: 5px; white-space: nowrap; }
  .lr-field input {
    width: 62px; padding: 4px 6px; text-align: right;
    background: var(--panel2); color: var(--text);
    border: 1px solid var(--border); border-radius: 7px; font: 13px var(--mono);
  }
  .lr-field input:disabled { opacity: .45; }
  .lr-field .k { font-size: 11px; color: var(--muted); }
  .lr-off {
    display: flex; align-items: center; gap: 8px; margin-top: 12px;
    font-size: 13px; color: var(--muted); cursor: pointer;
  }
  .lr-pending { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
  .lr-pending span { flex: 1 1 220px; min-width: 0; }
  .lr-off .lr-next { margin-left: 2px; }

  /* ---- every position at once, in the rail beside the chart ---- */
  /* A LIST where the strip over the chart is a paragraph, and that is the whole
     visual argument. Rows of one shape, one line each, so three positions are
     read down a column instead of one at a time — which is the only thing the
     strip over the chart cannot do, no matter how good it is.
     Not tinted the way .chart-pos is. One accented block on a screen is a
     signal; four of them are wallpaper. The accent is spent on exactly one row
     here — the one the chart is drawing — and the rest earn their weight from
     alignment and a hairline. */
  .book { margin: 0 0 12px; font: 11.5px var(--mono); }
  /* Capped to the widest thing in this column rather than to the ticker's 420px:
     a row carries five facts where the field above it carries eight characters,
     and squeezing it to the field's width would wrap every row on a laptop to
     match a phone it isn't on. */
  @media (min-width: 641px) { .book { max-width: 620px; } }

  /* What the rows add up to, in the register the other strips above the ticker
     use — small, uppercase, muted — with one exception. The total is the number
     you came here for, so it keeps its case, its size and its colour. */
  .bk-h {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 10px;
    font-size: 10px; text-transform: uppercase; letter-spacing: .05em;
    color: var(--muted); margin-bottom: 5px;
  }
  .bk-n { color: var(--text); }
  .bk-risk { font-variant-numeric: tabular-nums; }
  /* A leveraged position with nothing behind it, on a coin you are not looking
     at. The same pill .chart-pos uses for the same fact, because it is the same
     fact — said here about a symbol that has no other way to say it. */
  .bk-naked {
    color: var(--red); border: 1px solid var(--red); border-radius: 999px;
    padding: 0 6px; letter-spacing: .04em;
  }
  .bk-total {
    margin-left: auto; font-size: 12.5px; text-transform: none; letter-spacing: 0;
    color: var(--text); font-variant-numeric: tabular-nums;
  }
  .bk-total.up { color: var(--green); }
  .bk-total.down { color: var(--red); }

  .bk-rows { display: flex; flex-direction: column; }
  /* A button, because it does something: it points the chart. Stripped back to
     a row of text — the affordance is the hairline grid and the pointer, not a
     border and a fill, which at four rows would look like four Confirm buttons
     stacked above the ticker. */
  .bk-row {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px;
    width: 100%; min-height: 40px; text-align: left; cursor: pointer;
    font: inherit; color: var(--text); background: transparent;
    border: 0; border-top: 1px solid var(--border); border-left: 2px solid transparent;
    padding: 7px 4px 7px 8px;
  }
  .bk-rows .bk-row:last-child { border-bottom: 1px solid var(--border); }
  /* The one the chart below is drawing. Marked where it stands rather than
     moved to the top: a list that reorders itself when you tap it is a list
     whose next tap lands on the wrong row. */
  .bk-row.on {
    border-left-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 7%, transparent);
  }
  @media (hover: hover) {
    .bk-row:hover { background: var(--panel2); }
    .bk-row.on:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
  }
  /* (The rows' focus ring is the global :focus-visible rule at the top.) */
  .bk-side { font-weight: 700; letter-spacing: .04em; }
  .bk-side.long { color: var(--green); }
  .bk-side.short { color: var(--red); }
  .bk-sym { font-weight: 600; }
  /* Tabular figures on everything that re-prints on a tick. These sit in a
     column three rows deep, and a digit that changes width drags the whole
     column sideways — the one thing a list is for is that it lines up. */
  .bk-stop { color: var(--muted); font-variant-numeric: tabular-nums; }
  .bk-locked { color: var(--green); }
  .bk-quiet { color: var(--muted); }
  .bk-warn { color: var(--amber); }
  .bk-no {
    color: var(--red); border: 1px solid var(--red); border-radius: 999px;
    padding: 0 6px; font-size: 10.5px; letter-spacing: .04em;
  }
  /* The number that moves on its own, at the far end of the row where the eye
     finds it without reading the rest — the same place .chart-pos keeps it. */
  .bk-pnl { margin-left: auto; font-size: 13px; font-variant-numeric: tabular-nums; }
  .bk-pnl.up { color: var(--green); }
  .bk-pnl.down { color: var(--red); }

  /* ---- what the watchlist is waiting for ---- */
  /* One line per starred coin, under the book in the rail beside the chart: the
     block exists because a coin you starred this morning is a coin you are
     waiting on, and waiting is only useful if it happens where you already are.
     Shaped like the book above it on purpose — same header register, same
     hairline rows, same tap-to-chart — because they answer the same kind of
     question ("how are the others doing") and a screen that asked them in two
     different shapes would be teaching you two languages for one thing.
     Two reading groups per row, 5m then 15m, pushed to the far end: the name
     is what you scan for, the readings are what you scan by. */
  .watchmon { margin: 0 0 12px; font: 11.5px var(--mono); }
  @media (min-width: 641px) { .watchmon { max-width: 620px; } }
  .wm-h {
    display: flex; align-items: baseline; gap: 10px;
    font-size: 10px; text-transform: uppercase; letter-spacing: .05em;
    color: var(--muted); margin-bottom: 5px;
  }
  .wm-n { color: var(--text); }
  .wm-rows { display: flex; flex-direction: column; }
  .wm-row {
    display: flex; align-items: baseline; gap: 2px 10px;
    width: 100%; min-height: 34px; text-align: left; cursor: pointer;
    font: inherit; color: var(--text); background: transparent;
    border: 0; border-top: 1px solid var(--border);
    padding: 6px 4px 6px 8px;
  }
  .wm-rows .wm-row:last-child { border-bottom: 1px solid var(--border); }
  @media (hover: hover) { .wm-row:hover { background: var(--panel2); } }
  .wm-sym { font-weight: 600; }
  .wm-quote { font-size: 9px; color: var(--muted); }
  /* Two lines per reading group, the screener's volume cell in miniature:
     turnover leads and price sits under it, smaller — they are one reading
     ("a crowd turned up, and this is what it did"), read together or not at
     all, so they share a cell. */
  .wm-tf { display: flex; flex-direction: column; gap: 1px; margin-left: auto; }
  .wm-tf + .wm-tf { margin-left: 10px; }
  .wm-l1 { display: flex; align-items: baseline; gap: 5px; }
  .wm-tag { font-size: 9px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
  /* The multiple is the headline: muted while the coin is trading like itself,
     and the accent from three times up — the one colour on the row, so a spike
     is findable in the time it takes to glance at the block. Tabular figures,
     because a multiple that reprints every bar must not drag its neighbours. */
  .wm-mult { min-width: 34px; text-align: right; font-variant-numeric: tabular-nums; }
  .wm-mult.quiet { color: var(--muted); }
  .wm-mult.spike { color: var(--accent); font-weight: 700; }
  /* The price move, indented to sit under the multiple it qualifies. */
  .wm-px { padding-left: 23px; font-size: 10px; font-variant-numeric: tabular-nums; }
  .wm-px.quiet { color: var(--muted); }
  .wm-px.profit { color: var(--green); }
  .wm-px.loss { color: var(--red); }
  /* The directional score, third line of a cell. Aligned under the price so a
     column of coins reads down rather than zig-zagging, and quiet by default —
     it is the slowest-moving figure in the cell and should not out-shout the
     spike above it. */
  .wm-pred { padding-left: 23px; font-size: 10px; font-variant-numeric: tabular-nums; }
  .wm-pred.quiet { color: var(--muted); }
  .wm-pred.up { color: var(--green); }
  .wm-pred.down { color: var(--red); }
  .wm-pred b { font-weight: 600; }
  /* The forming bar and the reversal mark hold their slots even when they have
     nothing to say — a row whose width breathes every time a • appears is a
     row you cannot read the line below. Visible only beats invisible here by
     one thing: the glyph you were ignoring turning the colour of a spike. */
  .wm-live, .wm-rev { visibility: hidden; }
  .wm-live.on { visibility: visible; color: var(--accent); animation: wm-pulse 2s ease-in-out infinite; }
  .wm-rev.up { visibility: visible; color: var(--green); }
  .wm-rev.down { visibility: visible; color: var(--red); }
  @keyframes wm-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.35; }
  }
  @media (prefers-reduced-motion: reduce) {
    .wm-live.on { animation: none; }
    details.more > summary::after,
    .journal-tbl tbody tr > td:first-child::before { transition: none; }
  }


  /* ---- BTC and ETH, above everything ---- */
  /* The first line on the trade screen and the cheapest one: two prices and two
     day-changes, on a single row, in the strip below's own 11px mono so the two
     read as one block of market context rather than two features.
     Natural widths packed left, not `flex: 1` in two halves — these are two
     facts of the same kind, and stretching them to the column would space BTC
     and ETH like a segmented control offering a choice between them.
     Tabular figures because a price re-prints every fifteen seconds, and the %
     beside it is where the drift would show. */
  .majors { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: 0 0 10px; font: 11px/1 var(--mono); }
  .maj { display: flex; align-items: baseline; gap: 5px; min-width: 0; }
  .maj-sym { font-size: 9px; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
  .maj-px { font-size: 12px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
  .maj-chg { font-size: 10px; color: var(--muted); font-variant-numeric: tabular-nums; }
  .maj-chg.profit { color: var(--green); }
  .maj-chg.loss { color: var(--red); }

  /* ---- the market's lean, above everything ---- */
  /* The screener's four MACD timeframe tabs, reduced to what they say and
     nothing they do: on that screen they PICK which timeframe the three cards
     below describe, so they carry a selected state and a tap target 44px tall.
     Here there is nothing below them to describe, so there is no selection, no
     button and no padding spent on being pressable — four numbers, four bars
     and one line of context, in the height of two lines of text. */
  .macdstrip { margin: 0 0 10px; font: 11px/1 var(--mono); }
  .mstrip-h {
    font-size: 9px; text-transform: uppercase; letter-spacing: .05em;
    color: var(--muted); margin-bottom: 5px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .mstrip-tfs { display: flex; gap: 8px; }
  .mstrip-tf { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
  .mstrip-lbl { font-size: 9px; color: var(--muted); text-transform: uppercase; }
  .mstrip-avg { font-size: 12px; font-variant-numeric: tabular-nums; color: var(--text); }
  .mstrip-avg.profit { color: var(--green); }
  .mstrip-avg.loss { color: var(--red); }
  .mstrip-avg.quiet { color: var(--muted); }
  /* The bar is the breadth: green is the share of coins leaning positive, and
     the red behind it is the rest. Same red-ground/green-fill construction the
     screener's tabs use, so the two screens read as one reading. */
  .mstrip-bar {
    display: block; height: 3px; border-radius: 2px; overflow: hidden;
    background: color-mix(in srgb, var(--red) 55%, transparent);
  }
  /* No answer yet is not the same as nought per cent positive, so an unseeded
     timeframe shows a flat rule rather than a full red bar. */
  .mstrip-bar[data-empty] { background: var(--border); }
  .mstrip-bar > span { display: block; height: 100%; background: var(--green); }
  .mstrip-split { font-size: 9px; color: var(--muted); }

  /* ---- the ticker field ---- */
  /* One control above the chart, and it is a combobox rather than an input with
     a "previous trades" select beside it. Two controls that both set the ticker
     meant the history was a row of chrome on screen even when it was empty, and
     typing a symbol you had traded before ignored the list sitting next to it.
     Focused, it offers what you have traded; typed into, every symbol Binance
     lists. */
  .tickerbar { display: flex; gap: 8px; align-items: center; margin-bottom: 10px; }
  .ac { position: relative; flex: 1; min-width: 0; }
  .ac > input { font-size: 17px; letter-spacing: .02em; }
  /* Over the chart, not above it: a list that pushed the page down would move
     the thing you are choosing a ticker to look at. */
  .ac-menu {
    position: absolute; z-index: 30; top: calc(100% + 4px); left: 0; right: 0;
    max-height: min(46vh, 340px); overflow-y: auto; -webkit-overflow-scrolling: touch;
    background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 10px 28px rgba(0, 0, 0, .35); padding: 4px;
  }
  .ac-opt {
    display: flex; align-items: center; gap: 8px; width: 100%; min-height: 40px;
    padding: 0 10px; border: none; border-radius: 7px; background: none; cursor: pointer;
    color: var(--text); font: 15px var(--mono); text-align: left;
  }
  .ac-opt .sym { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  /* What this symbol is to you: a position you are in, or a trade you have
     taken. Both are the reason it is at the top of the list. */
  .ac-opt .mark { flex: none; font: 10px/1.9 inherit; letter-spacing: .06em; color: var(--muted); }
  .ac-opt .mark.long { color: var(--green); }
  .ac-opt .mark.short { color: var(--red); }
  .ac-opt.on, .ac-opt:hover { background: var(--panel2); }
  .ac-note { padding: 7px 10px; color: var(--muted); font-size: 11px; }

  /* ---- the chart, on the page ---- */
  /* Not inside a card. It is the picture of the ticker named directly above it,
     and a border around it made it look like a section of the report below —
     which is written about a trade that does not exist yet, while this is the
     market. Empty (no ticker fetched yet) it takes no room at all. */
  #chartHost:not(:empty) { margin-bottom: 14px; }
  .shape { margin-bottom: 4px; }
  /* ---- the entry/stop fold ---- */
  /* Sits under the side switch inside the same form grid, so it takes the whole
     line and the fields behind it get their own two-column grid back when it
     opens. Closed it is one row: the title, and the override if you have set
     one. No rule above it — the switch and these two boxes are one block about
     one trade, and a border there would cut the trade in half. */
  details.shaping { margin-top: 0; border-top: none; padding-top: 0; }
  /* Not a row: a control the width of its own words. A full-bleed summary line
     is what the sizing fold is, and it earns it — it is four facts. This one is
     a title with usually nothing after it, and stretched to the full column it
     read as a stray label with a chevron marooned at the far end, on a line
     that did not even end where the sizing chevron does. So the chevron hugs
     the words, the whole thing left-aligns under the LONG button, and what is
     left is something that looks like what it is: a way in. */
  details.shaping > summary {
    display: inline-flex; width: auto; max-width: 100%; flex-wrap: wrap;
    gap: 2px 8px; min-height: 30px;
    text-transform: none; letter-spacing: 0; font: 13px var(--mono);
  }
  details.shaping > summary::after { margin-left: 0; }
  /* Inline with the title rather than pushed to an edge there no longer is, and
     gone entirely when nothing is overridden — an empty span would otherwise
     hold its own gap open and float the chevron away from the words. */
  #shapeSummary { margin-left: 0; }
  #shapeSummary:empty { display: none; }
  /* Open, the boxes are the answer and the line above them would be the same
     two numbers read twice within an inch. */
  details.shaping[open] > summary > #shapeSummary { display: none; }
  /* An entry or a stop you typed is a number the app did not choose, and it is
     bending every size in the report below. Same amber the sizing line uses for
     a risk typed over the ladder's, for the same reason. */
  .fold-v .own { color: var(--amber); }

  /* ---- moving the stop ---- */
  /* The move you make while staying in the trade, standing above the one that
     ends it. Closed it is a button and a line saying where the stop is now;
     open it is a small form and, under it, the one figure the whole thing
     exists to show — what the position will lose if this stop is the one that
     gets hit. */
  .stopbox { margin-bottom: 10px; }
  .stopbox > .hint { display: block; margin-top: 8px; }
  /* A position with nothing behind it is the loudest thing this app can say,
     and it is said in the same red the strip above the chart uses. */
  .stopbox > .hint.stopless { color: var(--red); }
  .stopbox .arm { margin-top: 0; padding-top: 0; border-top: none; }
  /* One column, not the form grid's two: the value and its unit are one
     decision, and the ATR row underneath is a continuation of it. */
  .form.stopedit { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .stop-quick { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
  /* What it would cost, in the place a preview belongs: between the controls
     that decide it and the button that commits it. Wraps as groups, so a
     price and the money beside it never break apart on a phone. */
  .stop-prev {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 12px;
    margin-top: 10px; min-height: 20px; font: 13px var(--mono); color: var(--text);
  }
  .stop-prev.bad { color: var(--red); font-size: 12px; }
  .stop-prev .sp-main { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
  .stop-prev .sp-q, .stop-prev .sp-was { color: var(--muted); font-size: 11px; }
  /* The delta is the sentence — less at risk, or more — so it is the one part
     of this line allowed a colour. Green is not "profit" here, it is "you are
     taking risk off"; red is the opposite, and both are decisions, not results. */
  .stop-prev .sp-delta { font-size: 12px; }
  .stop-prev .sp-delta.up { color: var(--green); }
  .stop-prev .sp-delta.down { color: var(--amber); }

  /* ---- closing the position ---- */
  /* The way out, standing exactly where the side/entry/stop row stands when
     there is nothing open — because from inside a position that row describes a
     trade this app will not place, and this is the only move there is. It arms
     like an order (it is one), so it borrows the ticket's bar and buttons; what
     it drops is the rule above them, which exists to separate the ticket from
     the card it grew out of and has nothing to separate here. */
  .closebox { margin-bottom: 4px; }
  .closebox .arm { margin-top: 0; padding-top: 0; border-top: none; }
  .closebox > .hint { display: block; margin-top: 8px; }

  /* ---- sizing fold ---- */
  /* Deposit, risk and leverage. The deposit follows the Binance wallet and the
     risk follows the ladder, so all three are facts to check rather than fields
     to fill — and the closed summary IS those facts. The fold is only ever
     opened to change one, never to read one. */
  details.sizing > summary { text-transform: none; letter-spacing: 0; color: var(--text); min-height: 40px; }
  details.sizing[open] > summary { margin-bottom: 12px; }
  .sz-line {
    display: flex; flex: 1; min-width: 0; flex-wrap: wrap; align-items: baseline;
    gap: 2px 12px; font: 13px var(--mono);
  }
  .sz-line .k { color: var(--muted); font-size: 11px; }
  /* A value the app did not choose — a risk you typed over the ladder's, or a
     deposit that is no longer the wallet balance. Worth a colour, since the
     whole point of this line is that it can be trusted at a glance. */
  .sz-line .own { color: var(--amber); }
  .resets { display: flex; flex-wrap: wrap; gap: 8px; }
  .resets:empty { display: none; }

  /* ---- buttons ---- */
  button.act {
    background: var(--accent); color: #fff; border: none; border-radius: 9px;
    min-height: var(--tap); padding: 0 18px; cursor: pointer; font: 600 15px inherit; width: 100%;
  }
  button.act:disabled { opacity: .5; cursor: default; }
  button.act.danger { background: var(--red); }
  /* The way past the noise filter, and it has to read as neither of the buttons
     it sits between: not the blue "Place this trade" — this one crosses a rule
     on purpose — and not the filled red Confirm, which is the trigger. Outlined
     in the same red the flagged row and its note already use, so it reads as
     the flagged path rather than the ordinary one. */
  button.act.risky {
    background: none; color: var(--red);
    border: 1px solid color-mix(in srgb, var(--red) 50%, transparent);
  }
  button.mini {
    background: var(--panel2); color: var(--text); border: 1px solid var(--border); border-radius: 8px;
    min-height: 36px; padding: 0 12px; cursor: pointer; font: 13px inherit;
  }
  button.mini.danger { color: var(--red); border-color: color-mix(in srgb, var(--red) 40%, transparent); }
  button.mini.trade { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
  /* Refresh is maintenance, not a decision — an icon beside the ticker rather
     than a full-width blue bar that outranks everything around it. */
  button.icon {
    flex: none; width: var(--tap); height: var(--tap); border-radius: 9px; cursor: pointer;
    background: var(--panel2); border: 1px solid var(--border); color: var(--muted); font-size: 17px;
  }
  button.icon:disabled { opacity: .45; }
  /* The watch star. Two states and no label, so the difference between them has
     to be readable at a glance and from the corner of the eye: an outline in the
     muted colour for a coin you are not watching, a filled accent glyph for one
     you are. Colour alone would not do it — the glyph changes too, which is what
     survives a colourblind reading and a screenshot. */
  button.icon.watch.on { color: var(--accent); border-color: var(--accent); }

  /* ---- result summary strip ---- */
  /* One line above the variants, carrying only what the report itself knows:
     the price it was computed from, and the risk, reward and volatility every
     variant shares. The ticker, the side and the live price used to head it too
     — they are the field, the switch and the chart directly above it now. */
  .sum { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; margin-bottom: 12px; }
  .sum .meta { width: 100%; color: var(--muted); font: 11px/1.6 var(--mono); }
  .micro { color: var(--muted); font-size: 11px; margin-top: 8px; }

  /* ---- table of variants ---- */
  table { width: 100%; border-collapse: collapse; font: 13px var(--mono); }
  .tbl-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  /* A scrolling table whose first column is the row's name pins it, so the
     symbol is still on screen when the numbers it names are three columns
     deep. The screener's table has always done this with rules of its own;
     this is the same treatment as a class the account's tables opt into. */
  .tbl-sticky th:first-child, .tbl-sticky td:first-child {
    position: sticky; left: 0; background: var(--panel);
    border-right: 1px solid var(--border);
  }
  th, td { text-align: right; padding: 8px; border-bottom: 1px solid var(--border); white-space: nowrap; }
  th { color: var(--muted); font-weight: 500; font-size: 11px; text-transform: uppercase; }
  th:first-child, td:first-child { text-align: left; }
  tr.bad td { color: var(--muted); }
  tr.bad td:first-child::before { content: "! "; color: var(--red); }
  tr.heavy td { background: color-mix(in srgb, var(--amber) 12%, transparent); }
  tr.pick td { background: color-mix(in srgb, var(--accent) 14%, transparent); }
  tbody tr { cursor: pointer; }
  tr.sel td { background: color-mix(in srgb, var(--green) 16%, transparent); }
  tr.sel td:first-child { box-shadow: inset 3px 0 0 var(--green); }
  .tbl-scroll th.act-col, .tbl-scroll td.act-col {
    position: sticky; right: 0; background: var(--panel);
    border-left: 1px solid var(--border); text-align: center; padding-left: 10px;
  }
  td.loss, .big.loss, td .loss, .val.loss { color: var(--red); }
  td.profit, .big.profit, td .profit, .val.profit { color: var(--green); }
  /* Neither a win nor a loss — a trade that ran unprotected, or a stop that
     moved. Worth catching the eye without claiming the money went the wrong way. */
  td.warn, .big.warn, td .warn, .val.warn { color: var(--amber); }

  /* ---- journal table ---- */
  /* The journal is the one table in the app you read rather than decide from,
     and reading a row means reading all of it. Its cells carry a second line —
     the stop under the entry, the account percentage under the risk, the money
     under the R — because those qualifiers sat beside their numbers until the
     row was wider than any laptop. Stacked, they cost height, which this page
     has, instead of width, which it does not. */
  .journal-tbl { line-height: 1.35; }
  .journal-tbl th, .journal-tbl td { padding: 6px 8px; }
  /* "See it on the chart" — small enough to sit beside the symbol without
     competing with it, and never the row's own toggle: bindRowToggle() in
     journal.js checks for it before it checks for the row. */
  .row-chart-btn {
    margin-left: 4px; padding: 1px 8px; font: 11px var(--mono); color: var(--accent);
    background: transparent; border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
    border-radius: 999px; cursor: pointer; vertical-align: middle;
  }
  .row-chart-btn:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
  .cell-sub { margin-top: 1px; font-size: 11px; line-height: 1.25; }
  /* Deliberately outranks the cell's own profit/loss colour: a qualifier under a
     number is not a second copy of that number's news, and a row that is red
     twice reads as twice as bad. */
  td span.quiet { color: var(--muted); }
  /* Every other column is a number as wide as its content. These two are prose,
     so they are the give in the table: they wrap and the row still fits. */
  .journal-tbl th:nth-child(2), .journal-tbl td:nth-child(2),
  .journal-tbl th:last-child, .journal-tbl td:last-child {
    text-align: left; white-space: normal;
  }
  /* A journal row is a record, not a control. It inherits the pointer from the
     variant table, where a row IS the thing you tap — and below it turns into
     something that looks exactly like the cards you do tap over there. */
  .journal-tbl tbody tr { cursor: default; }

  /* Below the width where eight columns of monospace can stand side by side, a
     trade becomes a card — the same fold the variant table does on a phone, but
     at its own width, because this table has one more column and no room to
     spare. The variant table's version of this carries a trade button and three
     selection states; a journal row is read and never tapped, so this is only
     the layout, and the two are not worth sharing a block for.

     900px, not 640: between the two the row fits only by wrapping its prose
     columns to three lines each, which is a table in name and a mess to read. */
  @media (max-width: 900px) {
    .journal-tbl { overflow-x: visible; }
    .journal-tbl table, .journal-tbl tbody { display: block; }
    .journal-tbl thead { display: none; }
    .journal-tbl tbody tr {
      display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
      gap: 5px 10px; align-items: start;
      background: var(--panel2); border: 1px solid var(--border);
      border-radius: 10px; padding: 9px 11px; margin-bottom: 7px;
    }
    .journal-tbl td { display: block; border: none; padding: 0; text-align: left; white-space: normal; overflow-wrap: anywhere; }
    /* The header row is gone, so each value carries its own column heading —
       beside it rather than above it. Stacked captions cost a line of card
       height per grid row and there are four of them, which is most of the
       difference between a card you scroll past and a card you scroll through. */
    .journal-tbl td[data-k]::before {
      content: attr(data-k) " ";
      font: 9.5px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
      color: var(--muted); text-transform: uppercase; letter-spacing: .04em;
    }
    /* Not DOM order: the two things you scan a journal for — what it paid and
       how it ended — belong on the top two lines beside the trade's name, not
       in the middle of a grid of reference prices. */
    .journal-tbl td:nth-child(1) { grid-area: 1 / 1 / auto / 3; font-size: 13px; }
    .journal-tbl td:nth-child(6) { grid-area: 1 / 3; text-align: right; }
    .journal-tbl td:nth-child(2) { grid-area: 2 / 1 / auto / 3; }
    .journal-tbl td:nth-child(8) { grid-area: 2 / 3; text-align: right; }
    /* The entry takes two columns because its second line is two numbers — a
       stop and its distance — and a third of a phone breaks that across two
       lines, which costs more height than the column saves. */
    .journal-tbl td:nth-child(3) { grid-area: 3 / 1 / auto / 3; }
    .journal-tbl td:nth-child(4) { grid-area: 3 / 3; }
    .journal-tbl td:nth-child(5) { grid-area: 4 / 1; }
    .journal-tbl td:nth-child(7) { grid-area: 4 / 2 / auto / -1; }
    .journal-tbl tbody tr:hover td { background: none; }

    /* Shut, a card is two lines: which trade and when, against what it paid and
       how it ended. That is the whole of what you scan a journal for, and it is
       54px instead of 150 — thirty trades go from four and a half screens of
       scrolling to one and a half. The other five fields are a tap away, and
       the tap is on the row rather than on a control, because the row is the
       only thing on the card worth aiming at. */
    .journal-tbl tbody tr { cursor: pointer; }
    .journal-tbl tbody tr:not(.open) td:nth-child(2),
    .journal-tbl tbody tr:not(.open) td:nth-child(3),
    .journal-tbl tbody tr:not(.open) td:nth-child(4),
    .journal-tbl tbody tr:not(.open) td:nth-child(5),
    .journal-tbl tbody tr:not(.open) td:nth-child(7) { display: none; }
    /* The dollars under the R go too: shut, the right-hand column is one number
       over one phrase, and a third line under the R breaks that pairing. */
    .journal-tbl tbody tr:not(.open) td:nth-child(6) .cell-sub { display: none; }
    /* Shut, the card is two rows of two, and the trade's name and date are the
       left of both — not one cell on the first row with the outcome stranded
       under it. The right half takes two columns because a shut card has the
       room, and "ran with no stop" should not be three words on two lines. */
    /* Two columns, not three, and the left one sized to the trade's own name:
       a third of a phone is a hair under "DOGEUSDT S" and breaks the side off
       onto its own line. */
    .journal-tbl tbody tr:not(.open) { grid-template-columns: auto minmax(0, 1fr); }
    .journal-tbl tbody tr:not(.open) td:first-child { grid-area: 1 / 1 / 3 / 2; }
    .journal-tbl tbody tr:not(.open) td:nth-child(6) { grid-area: 1 / 2 / auto / -1; }
    .journal-tbl tbody tr:not(.open) td:nth-child(8) { grid-area: 2 / 2 / auto / -1; }
    /* The same disclosure chevron the folds elsewhere in the app use, turned to
       point down when the row is open. */
    .journal-tbl tbody tr > td:first-child::before {
      content: ""; display: inline-block; width: 6px; height: 6px; margin-right: 9px;
      border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
      transform: rotate(-45deg) translateY(-2px); transition: transform .15s;
    }
    .journal-tbl tbody tr.open > td:first-child::before { transform: rotate(45deg) translateY(-2px); }
  }

  /* The rest of the app is capped at a reading measure because the rest of the
     app is a form and a chart, and neither wants to be 1200px wide. Two things
     do want the room. The journal: on a laptop the 860px column was the only
     reason its table had to be scrolled sideways, and the screen it was
     scrolling on had 500px of empty margin either side of it. The trade
     screen: a desktop window is where the book and the watchboard finally fit
     BESIDE the chart instead of in its column — see the trade-cols block below.

     The whole column widens, not the panel inside it: a panel that reached past
     the tab bar above it read as a mistake rather than as a wider screen. The
     tabs, the screen and the footer are one edge again, and the tabs move
     because the screen under them did — every other tab leaves them where they
     were.

     The subtraction is what keeps this safe: the column is never wider than the
     viewport less 80px, which is more than any scrollbar 100vw might be hiding,
     so widening either screen can never be what makes the page scroll. */
  @media (min-width: 1024px) {
    .wrap:has(#tab-journal.on) { max-width: min(1180px, 100vw - 80px); }
    .wrap:has(#tab-trade.on) { max-width: min(1360px, 100vw - 80px); }
  }

  /* ---- the trade screen, where there is room for two columns ---- */
  /* On a phone the trade screen is one column, and the DOM order is the layout:
     ticker, chart, then the two rails UNDER the chart, then the trade itself.
     Under, not above — the chart pins to the top of a phone while everything
     beneath it scrolls, so a rail under the chart is the one place the rails
     and the picture are on screen at once, however many rows the rails grow.
     Above the ticker they pushed the chart off the screen, and no scroll
     position showed the two together.

     From 1024px the window is wide enough for the layout the column was always
     standing in for: the chart takes a column of its own, the rails take a
     narrow one beside it, and both are on screen without a scroll. The grid is
     laid on .trade-cols rather than on the tab so the three wrappers keep
     their phone order in the DOM — head, rail, tail — and the areas put the
     rail beside the chart and the report, not beside the ticker: the ticker
     spans the full width on its own row, and the rail starts where the chart
     column starts, level with the timeframe row rather than with the candles.
     Starting it at the candles instead put the chart's own chrome — the
     timeframe row, the live price, the strips over the picture — on a row with
     nothing beside it, and a watchlist standing alone in the rail then began
     40px down the screen behind a band of nothing. It switches on only when a
     rail has something to show (:has, the same test book.js and watchmon.js
     already paint with): flat, with nothing watched, the column would be 360px
     of nothing and the chart would be narrower for no reason.

     .trade-head is display: contents here so the ticker and the chart can take
     separate grid areas — the same trick the phone layout uses so sticky works,
     but for a different reason on a wide screen. The chart host stays a box:
     everything inside it is one stack — chrome over candles — and dissolving it
     into the grid only stacked those pieces into a single cell on top of one
     another.

     The rail is sticky and caps at the viewport, so a book of any height stays
     readable next to the report — its own scroll, and only its own, is spent
     on the rows that do not fit. */
  @media (min-width: 1024px) {
    /* :not(.blk-off) alongside :not(.hidden): a rail whose blocks are all
       switched off in the Layout tab must not hold a 360px column open for
       nothing, exactly as one whose blocks are merely empty does not. */
    .trade-cols:has(#posBook:not(.hidden):not(.blk-off)),
    .trade-cols:has(#watchMon:not(.hidden):not(.blk-off)) {
      display: grid;
      grid-template-columns: minmax(0, 1fr) 360px;
      grid-template-rows: auto auto minmax(0, 1fr);
      grid-template-areas:
        "ticker ticker"
        "chart  rail"
        "tail   rail";
      column-gap: 28px;
      align-items: start;
    }
    .trade-head { display: contents; }
    .tickerbar { grid-area: ticker; }
    #chartHost { grid-area: chart; min-width: 0; }
    .trade-tail { grid-area: tail; min-width: 0; }
    .trade-rail {
      grid-area: rail;
      position: sticky; top: 12px;
      max-height: calc(100vh - 24px);
      overflow-y: auto;
    }
    /* The market's lean is four windows across; given a 1300px measure it
       would space them like a table with four words in it. */
    #tab-trade .macdstrip { max-width: 620px; }
  }

  /* ---- selected-variant card ---- */
  .rec { border-left: 3px solid var(--green); padding: 12px 14px; background: var(--panel2); border-radius: 0 10px 10px 0; margin-top: 14px; }
  .rec-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 2px; }
  .rec-name { font: 15px var(--mono); margin-right: auto; }
  .rec-why { color: var(--muted); font-size: 12px; margin: 0 0 10px; }
  .rec .big { font: 15px var(--mono); }
  .rec-tag { font: 11px inherit; color: var(--muted); background: transparent; border: 1px solid var(--border); border-radius: 999px; padding: 3px 10px; }
  .rec-tag.on { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
  button.rec-tag { cursor: pointer; min-height: 30px; }
  .kv { display: grid; grid-template-columns: auto 1fr; gap: 3px 14px; font: 13px var(--mono); align-items: baseline; }
  .kv .k { color: var(--muted); }
  .rec-actions { display: flex; gap: 10px; align-items: center; margin-top: 12px; flex-wrap: wrap; }

  /* ---- the same card, armed ---- */
  /* Arming does not open a second panel — it turns this card into the order
     ticket. So the rail stops meaning "the variant you are reading" and starts
     meaning "the exchange this fires at": red for production, amber for the
     testnet, the same code the page border and the environment switch use. */
  .rec.armed.test { border-left-color: var(--amber); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--amber) 35%, transparent); }
  .rec.armed.prod { border-left-color: var(--red); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--red) 45%, transparent); }
  /* Editable where it is already printed: leverage and the slippage cap are set
     in the rows that read them out, and the margin above re-figures as you type.
     16px because anything smaller makes iOS zoom the page on focus. */
  input.kv-in {
    width: 5.5ch; height: 34px; padding: 0 7px; text-align: right;
    font: 16px var(--mono); border-radius: 7px;
    background: var(--bg); border: 1px solid var(--accent);
    -moz-appearance: textfield;
  }
  /* A stepper inside a 5-character box leaves room for about one digit. */
  input.kv-in::-webkit-outer-spin-button,
  input.kv-in::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
  .arm { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
  /* One line for the things arithmetic can't carry: that this is now an order,
     and which account on which exchange it lands in. */
  .arm-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; font: 12px var(--mono); color: var(--muted); }
  .arm-bar b { color: var(--text); }
  .arm-go { display: flex; gap: 10px; align-items: center; }
  .arm-go .act { flex: 1; }

  /* ---- notes ---- */
  .note { margin-top: 10px; padding: 9px 12px; border-radius: 9px; font-size: 13px; }
  .note.warn { background: color-mix(in srgb, var(--amber) 15%, transparent); color: var(--text); border: 1px solid color-mix(in srgb, var(--amber) 40%, transparent); }
  .note.stop { background: color-mix(in srgb, var(--red) 15%, transparent); border: 1px solid color-mix(in srgb, var(--red) 45%, transparent); }
  .note.err { background: color-mix(in srgb, var(--red) 12%, transparent); border: 1px solid color-mix(in srgb, var(--red) 40%, transparent); }
  .note.ok { background: color-mix(in srgb, var(--green) 15%, transparent); border: 1px solid color-mix(in srgb, var(--green) 45%, transparent); }
  /* Standing conditions of the strategy — true on every trade, so they read as
     footnotes rather than as amber alarms that mean nothing after the tenth time. */
  .note.quiet { background: none; border: none; padding: 0; margin-top: 10px; color: var(--muted); font-size: 11px; line-height: 1.6; }

  /* ---- chart ---- */
  .chart-wrap { margin: 2px 0 10px; }
  /* The library measures this box and draws to fill it, so the height is the
     app's decision and the width is the column's. Both charts in the app are
     the same shape: shorter on a phone, where the picture is pinned to the top
     of the viewport and every pixel of it is a row of table it costs. */
  .chart-canvas { width: 100%; display: block; position: relative; }
  /* The badge tooltip. Positioned against the chart's own box by chart.js, and
     deliberately inert: it follows the cursor, so anything it swallowed would
     be a click the reader aimed at the chart underneath it. */
  .chart-tip {
    position: absolute; z-index: 3; pointer-events: none;
    background: var(--panel2); border: 1px solid var(--border); border-radius: 8px;
    padding: 7px 9px; font: 11px/1.5 var(--mono); color: var(--text);
    box-shadow: 0 4px 14px rgba(0, 0, 0, .35); white-space: nowrap;
  }
  .chart-tip .tt-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 4px; }
  .chart-tip .tt-side { font-weight: 700; letter-spacing: .04em; }
  .chart-tip .tt-side.long { color: var(--green); }
  .chart-tip .tt-side.short { color: var(--red); }
  .chart-tip .tt-note { color: var(--muted); }
  .chart-tip .tt-r { font-weight: 700; }
  /* Three columns, so the prices line up under each other however wide the
     labels are — a tooltip whose numbers stagger is one you have to read twice. */
  .chart-tip .tt-rows { display: grid; grid-template-columns: auto auto auto; gap: 0 10px; }
  .chart-tip .tt-rows .k { color: var(--muted); }
  .chart-tip .tt-rows .v { text-align: right; }
  .chart-tip .tt-rows .q { color: var(--muted); }
  /* The OHLC readout, in the corner every chart puts it in. Inert like the
     tooltip and for the same reason — it sits over the candles, and anything it
     swallowed would be a click the reader aimed at the chart underneath.

     Boxed between a left edge and a right one rather than given a width: the
     right edge is roughly where the price axis starts, and it is there so the
     row WRAPS before it reaches it. Four nine-digit prices, a percentage and a
     volume are past 400px of monospace, which fits a laptop and does not fit a
     phone; wrapping to two lines is the only thing that is right on both. */
  .chart-ohlc {
    position: absolute; z-index: 2; pointer-events: none;
    top: 3px; left: 6px; right: 76px;
    display: flex; flex-wrap: wrap; gap: 0 10px;
    font: 11px/1.45 var(--mono); color: var(--text);
    font-variant-numeric: tabular-nums;
  }
  /* Green when the bar closed above its open, red when below — the same
     comparison that paints the body, so the figure and the candle can never
     disagree. Flat bars stay the colour of plain text. */
  .chart-ohlc.up { color: var(--green); }
  .chart-ohlc.down { color: var(--red); }
  .chart-ohlc .chg { font-weight: 700; }
  /* The O, H, L and C themselves are labels, not readings, and stay muted
     through the colour above — hence a rule that outranks it. */
  .chart-ohlc .oc .k { color: var(--muted); margin-right: 4px; }
  @media (max-width: 640px) {
    /* The picture is 230px tall on a phone and the corner is worth about a
       fifteenth of it, so it is spent at the smallest size still readable. */
    .chart-ohlc { font-size: 10px; right: 62px; gap: 0 8px; }
  }
  .chart-legends { margin-top: 8px; }
  /* Wraps for the 320px case: six timeframes plus the indicator menu is wider
     than an SE, and a row that overflowed would take the whole page sideways. */
  .chart-tfs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
  .chart-tfs button {
    background: transparent; color: var(--muted); border: 1px solid var(--border);
    border-radius: 7px; min-height: 32px; padding: 0 11px; cursor: pointer; font: 11px var(--mono);
  }
  .chart-tfs button.on { background: var(--accent); border-color: var(--accent); color: #fff; }
  /* The indicator menu, at the far end of the timeframe row. A menu rather
     than three more buttons in the row: the row already carries six, and a
     phone has no width left for nine. The trigger inherits the row's own
     button styling and lights the same way, carrying the count of panes on. */
  .chart-ind { position: relative; margin-left: auto; }
  .ind-menu {
    position: absolute; z-index: 30; top: calc(100% + 4px); right: 0; min-width: 176px;
    background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 10px 28px rgba(0, 0, 0, .35); padding: 4px;
  }
  .ind-menu .ind-opt {
    display: flex; align-items: center; gap: 8px; width: 100%; min-height: 40px;
    padding: 0 10px; border: none; border-radius: 7px; background: none;
    cursor: pointer; color: var(--text); font: 13px var(--mono); text-align: left;
  }
  @media (hover: hover) { .ind-menu .ind-opt:hover { background: var(--panel2); } }
  /* The check holds its slot either way, so the labels stand in one column and
     a toggle moves nothing but the mark. */
  .ind-ck { visibility: hidden; color: var(--accent); font-weight: 700; }
  .ind-ck.on { visibility: visible; }
  /* Wraps, because three window figures joined the price, the candle % and the
     label on one row — on a phone that row is longer than the screen is wide,
     and a number that overflows its box is worse than a strip two lines tall. */
  .chart-live { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; margin: 0 0 6px; font: 12px var(--mono); }
  /* The price is the one thing on this strip that changes on its own, and the
     strip is a flex row — so its width is the % change's position, and the
     symbol label's, and the age chip's. fmtPrice holds the decimals fixed per
     symbol, which is what stops the number changing width mid-tick; the room
     it needs is reserved in buildLiveBar, measured from a real price of the
     symbol on screen rather than written down here, because a number in this
     stylesheet would have to fit BTC and would then sit as dead space beside
     everything shorter. tabular-nums is belt and braces for the day the mono
     stack falls back to a proportional face. */
  .chart-live .lp-price {
    font-size: 24px; color: var(--text); transition: color .25s;
    font-variant-numeric: tabular-nums;
  }
  /* Six characters is "+0.09%" — what a percent change reads for all but a
     violent session. min-width does not clip, so the rare move past 10% simply
     takes the character it needs. */
  .chart-live .lp-chg { min-width: 6ch; font-variant-numeric: tabular-nums; }
  .chart-live .lp-price.up, .chart-live .lp-chg.up { color: var(--green); }
  .chart-live .lp-price.down, .chart-live .lp-chg.down { color: var(--red); }
  /* The 1h/4h/24h figures: numbers at the candle %'s size with labels at the
     symbol label's — they are answers of the same kind as the one beside the
     price, on clocks that don't change with the chart. One span per window so
     wrapping keeps each label with its number; the same 6ch reservation as
     lp-chg, for the same reason. */
  .chart-live .lp-wins { display: flex; align-items: baseline; gap: 10px; font-variant-numeric: tabular-nums; }
  .chart-live .lp-win { display: inline-flex; align-items: baseline; gap: 3px; }
  .chart-live .lp-wlab { color: var(--muted); font-size: 11px; }
  /* Muted by default so the "—" of a window with no baseline yet reads as an
     absence rather than a figure; up/down recolour it the moment there is one. */
  .chart-live .lp-wval { min-width: 6ch; color: var(--muted); }
  .chart-live .lp-wval.up { color: var(--green); }
  .chart-live .lp-wval.down { color: var(--red); }
  .chart-live .lp-lbl, .chart-live .lp-age { color: var(--muted); font-size: 11px; }
  /* Reserved for the same reason the price and the % are, and more urgently:
     the age is the last item before the status chip, so it is what decides
     where this strip wraps. "just now" is eight characters, "12s ago" seven,
     and a feed that has said nothing yet is none — left to size itself, the
     strip would drop a line and lift the chart under it every time the text
     changed. min-width does not clip, so a very stale figure still fits. */
  .chart-live .lp-age { min-width: 8ch; }
  .chart-live .lp-age.stale { color: var(--amber); }
  .chart-live .lp-status { margin-left: auto; color: var(--muted); font-size: 11px; }
  /* A healthy feed is a green dot, not a chip spelling out "live" next to a
     price that is visibly ticking. Only a degraded feed gets words. */
  .chart-live .lp-status.on { color: var(--green); }
  .chart-live .lp-status.err { color: var(--red); border: 1px solid var(--red); border-radius: 999px; padding: 1px 8px; }
  .chart-live .lp-status.warn { color: var(--amber); border: 1px solid var(--amber); border-radius: 999px; padding: 1px 8px; }
  /* The position you are actually in, above the chart that draws it. Boxed and
     tinted because everything around it is arithmetic about a trade that does
     not exist yet, and this is money already on the exchange. */
  /* Half a pixel under everything else in mono here, and it is bought with a
     measurement rather than a preference: at 393px — an iPhone 14 Pro, the
     phone this is read on — the strip has 351px of room, and side, size, entry
     and the whole stop (its price AND the dollars behind it) come to 353px at
     12px. Two pixels. So the stop broke off the line for the sake of them and
     left a third of the row empty above it. At 11.5px the same four groups are
     341px and the row is two lines instead of three. It does not fit every
     symbol — a six-figure BTC price is wider than any phone line — and the
     grouping below is what makes that case merely wrap rather than confuse.

     Two figures joined the row after that measurement — how far price has moved
     since the entry, and the stop's distance in percent — so on a phone it now
     wraps where those four groups used to fit. That is the trade the grouping
     was built to make survivable: a row that breaks between groups is still
     read, and the alternative was keeping two numbers off the one screen you
     look at while the position is open. */
  .chart-pos {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 6px;
    font: 11.5px var(--mono); margin: 0 0 8px; padding: 6px 9px; border-radius: 8px;
    background: color-mix(in srgb, var(--accent) 9%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
  }
  .chart-pos .ps-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 6px; width: 100%; }
  .chart-pos .ps-side { font-weight: 700; letter-spacing: .04em; }
  .chart-pos .ps-side.long { color: var(--green); }
  .chart-pos .ps-side.short { color: var(--red); }
  /* A label and its number are one flex item, not two. The row wraps — on a
     390px phone it always wraps — and a wrap between "stop" and 0.12734 leaves
     the word stranded at the end of one line with its number opening the next,
     which reads as two separate facts about the position. Grouping them costs
     nothing when the pair fits and is the whole point when it doesn't: the
     break moves to the space BETWEEN groups, where a break belongs.

     The group wraps internally only as a last resort — for the symbol whose
     price is so long that "stop 118420.5 −$38.85" is wider than the whole row.
     Then the tail drops inside the group instead of overflowing the strip, and
     the label still keeps its number. */
  .chart-pos .ps-kv {
    display: inline-flex; flex-wrap: wrap; align-items: baseline;
    gap: 0 4px; margin-left: 4px;
  }
  .chart-pos .ps-k { color: var(--muted); font-size: 10.5px; }
  .chart-pos .ps-v { white-space: nowrap; }
  .chart-pos .ps-q { color: var(--muted); white-space: nowrap; }
  /* The one number that moves on its own, kept at the far end of the row where
     the eye can find it without reading the rest. */
  .chart-pos .ps-pnl { margin-left: auto; font-size: 14px; }
  .chart-pos .ps-pnl.up { color: var(--green); }
  .chart-pos .ps-pnl.down { color: var(--red); }
  .chart-pos .ps-note { color: var(--muted); }
  .chart-pos .ps-note.warn { color: var(--amber); }
  /* A leveraged position with nothing behind it is the one thing on this strip
     worth shouting about. */
  .chart-pos .ps-note.stop {
    color: var(--red); border: 1px solid var(--red); border-radius: 999px; padding: 1px 8px;
  }
  /* How far it has run. Deliberately not green/red: MFE and MAE are a pair
     describing one range, and colouring the halves would read as profit and
     loss when neither has been realized. Amber is kept for the single case that
     is an event rather than a reading — a full R shown and handed back. */
  .chart-pos .ps-exc.warn { color: var(--amber); }

  /* What this ticker has already paid, under the position and above the chart.
     Quieter than the strip over it by a deliberate margin: that one is money at
     risk right now, this one is history, and history that shouts is history
     that gets traded on. */
  .chart-record {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px;
    font: 12px var(--mono); color: var(--muted); margin: 0 0 8px; padding: 0 2px;
  }
  .chart-record .rec-lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
  .chart-record .rec-wl { color: var(--text); }
  .chart-record .rec-wl b { font-weight: 700; }
  .chart-record .rec-wl b.up { color: var(--green); }
  .chart-record .rec-wl b.down { color: var(--red); }
  .chart-record .rec-rate { color: var(--text); }
  .chart-record .rec-r.up { color: var(--green); }
  .chart-record .rec-r.down { color: var(--red); }
  /* Too few trades behind it to colour. The number is still printed — the count
     beside it is the honest qualifier — but it is not dressed up as a finding. */
  .chart-record .rec-rate.quiet, .chart-record .rec-r.quiet { color: var(--muted); }
  .chart-record .rec-none { color: var(--muted); }
  .chart-record .rec-n { margin-left: auto; font-size: 11px; }

  /* The trade a journal row sent you here to see, pinned above the chart that
     is now drawing it instead of today's candles. Neither the accent tint of
     money-at-risk (.chart-pos) nor the quiet of a standing stat (.chart-record)
     — it is a deliberate detour, so it gets a border of its own and a way back
     out that is always in the same place. */
  .chart-hist {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px;
    font: 12px var(--mono); margin: 0 0 8px; padding: 6px 9px; border-radius: 8px;
    background: var(--panel2); border: 1px solid var(--border);
  }
  .chart-hist .ht-lbl { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
  .chart-hist .ht-side { font-weight: 700; letter-spacing: .04em; }
  .chart-hist .ht-side.long { color: var(--green); }
  .chart-hist .ht-side.short { color: var(--red); }
  .chart-hist .ht-k { color: var(--muted); font-size: 11px; margin-left: 4px; }
  .chart-hist .ht-q { color: var(--muted); }
  .chart-hist .ht-note { color: var(--muted); }
  .chart-hist .ht-pnl { font-weight: 700; }
  .chart-hist .ht-pnl.profit { color: var(--green); }
  .chart-hist .ht-pnl.loss { color: var(--red); }
  .chart-hist .ht-close {
    margin-left: auto; background: transparent; color: var(--muted); border: 1px solid var(--border);
    border-radius: 999px; width: 22px; height: 22px; line-height: 1; cursor: pointer; font-size: 12px;
  }
  .chart-hist .ht-close:hover { color: var(--text); border-color: var(--muted); }

  /* Legend: the three lines you are reading (entry, selected stop, target) are
     always out; the other stops fold away, because eight dashed labels is more
     chart legend than chart. */
  .chart-legend { display: flex; flex-wrap: wrap; gap: 4px 14px; font: 12px var(--mono); }
  .chart-legend .lg { display: inline-flex; align-items: center; gap: 6px; color: var(--text); }
  .chart-legend .lg.off { color: var(--muted); }
  .chart-legend .sw { width: 18px; height: 0; border-top-width: 2px; border-top-style: solid; display: inline-block; }
  .chart-legend .lg .px { color: var(--muted); }
  .legend-more { margin-top: 6px; }
  .legend-more > summary {
    list-style: none; cursor: pointer; color: var(--muted); font: 11px var(--mono);
    min-height: 32px; display: flex; align-items: center;
  }
  .legend-more > summary::-webkit-details-marker { display: none; }
  .legend-more[open] > summary { margin-bottom: 4px; }
  .legend-more .chart-legend { gap: 3px 14px; }

  /* ---- account / journal ---- */
  .acct-sub-h { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; margin: 16px 0 8px; }
  #acctOut > .acct-sub-h:first-child, #journalOut > .acct-sub-h:first-child { margin-top: 0; }
  .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); gap: 8px; }
  .stat { background: var(--panel2); border: 1px solid var(--border); border-radius: 9px; padding: 9px 11px; }
  .stat .lbl { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
  .stat .val { font: 15px var(--mono); margin-top: 3px; }
  /* ---- the stop / target replay ----
     Its own table rules rather than the journal's, because every cell here is a
     number over the evidence for it: a value line and, under it, the win rate
     and the n it came from. The journal's rows are one trade each and can be
     read across; these are one PLAN each and are read down a column, so the
     columns are what get the fixed width and the alignment. */
  .opt-tbl table { width: 100%; border-collapse: collapse; }
  .opt-tbl th, .opt-tbl td { padding: 7px 10px; text-align: right; white-space: nowrap; }
  .opt-tbl th { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; font-weight: 500; }
  .opt-tbl th:first-child, .opt-tbl td:first-child { text-align: left; }
  .opt-tbl td { font: 13px var(--mono); border-top: 1px solid var(--border); vertical-align: top; }
  .opt-tbl tbody tr:first-child td { border-top: none; }
  /* A withheld cell is deliberately quieter than a filled one — it is the
     absence of a finding, and it should not read as a zero. */
  .opt-tbl td.thin { color: var(--muted); }
  .opt-tbl .accent { color: var(--accent); }
  /* The plateau pick, and — only when they disagree — the lone peak beside it.
     The peak is amber rather than green on purpose: it is the cell most likely
     to be an artefact, and marking it as a runner-up would be exactly wrong. */
  .opt-tbl tbody tr.pick { background: color-mix(in srgb, var(--accent) 10%, transparent); }
  .opt-tbl tbody tr.pick td:first-child::before { content: "▸ "; color: var(--accent); }
  .opt-tbl tbody tr.peak td:first-child { color: var(--amber); }
  /* The trailing grid. Colour carries the expectancy so the SHAPE of the surface
     reads before any single cell does — a grid that comes out one flat colour is
     telling you these settings barely matter on your trades, which is worth
     seeing before you start picking a winner out of it. */
  .opt-heat-h { font-size: 11px; color: var(--muted); margin: 12px 0 6px; }
  .opt-tbl td.heat { text-align: center; border: 1px solid var(--border); border-radius: 0; }
  .opt-tbl td.heat.thin { background: none; }
  .opt-best { background: var(--panel2); border: 1px solid var(--border); border-radius: 10px; padding: 12px; margin-top: 8px; }
  .opt-best-h { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
  .opt-best-cfg { font: 14px var(--mono); margin: 4px 0 10px; }
  .opt-best .stats { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
  .opt-best .cell-sub { color: var(--muted); }
  #optFold button.act { margin-top: 10px; }

  .acct-check { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; white-space: nowrap; min-height: 38px; }
  .acct-check input { width: 20px; height: 20px; }
  .acct-h { font-size: 15px; margin: 0 0 4px; }
  /* The Balance heading carries how fresh the numbers under it are. Same
     convention as the chart's price readout: a working refresh is a green dot,
     and only a broken or stale one is worth words. */
  .acct-sub-h.row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
  .acct-live { font: 11px var(--mono); text-transform: none; letter-spacing: 0; color: var(--muted); }
  .acct-live.on { color: var(--green); }
  .acct-live.warn { color: var(--amber); }
  .acct-live.err { color: var(--red); }
  /* On a tab or a folded summary the value is all you can see of the panel — so
     if the balance on it has stopped being re-read, that has to show without
     opening anything. */
  .fold-v .stale, .tab-v .stale { color: var(--amber); }
  /* The screener's tab carries the coin moving hardest, and which way it moved
     is half of that news — the muted colour the rest of the label uses would
     throw it away. */
  .tab-v .profit { color: var(--green); }
  .tab-v .loss { color: var(--red); }

  /* ---- screener ---- */
  /* Eight columns of numbers whose whole purpose is to be re-ranked, so every
     heading is a button rather than a label. The header is the control here:
     there is no sort menu, and the arrow on the active column is the only state
     the table carries between repaints. */
  .screener-tbl th { padding: 0; }
  .screener-tbl th .scr-sort {
    width: 100%; min-height: 40px; padding: 8px; gap: 4px;
    display: flex; align-items: center; justify-content: flex-end;
    background: none; border: none; cursor: pointer;
    color: inherit; font: inherit; text-transform: inherit; letter-spacing: inherit;
  }
  .screener-tbl th:first-child .scr-sort { justify-content: flex-start; }
  /* The column you are sorted by is the one you chose; the rest stay muted so
     the choice is visible from across the table. */
  .screener-tbl th:not([aria-sort="none"]) .scr-sort { color: var(--text); }
  .scr-ar { font-size: 8px; line-height: 1; }
  /* The table is wider than a phone and scrolls sideways, which puts the coin's
     name off screen exactly when you need it to read the row. Pinned, the same
     way the variant table pins its trade button to the other edge. */
  .screener-tbl th:first-child, .screener-tbl td:first-child {
    position: sticky; left: 0; background: var(--panel);
    border-right: 1px solid var(--border);
  }
  .screener-tbl td:first-child { font-size: 12px; }
  .scr-quote { font-size: 9px; color: var(--muted); }
  .screener-tbl .mark.open {
    font: 9px/1 var(--mono); letter-spacing: .06em; color: var(--accent);
    text-transform: uppercase; vertical-align: 1px;
  }
  .screener-tbl td.quiet { color: var(--muted); }

  /* Six metric buttons are wider than a phone, and a segmented control does not
     shrink — it overflows. The overflow used to be the document's: the whole
     page grew a sideways scroll on the one tab that shows this switch. Now the
     row scrolls by itself, the way the sheet's tab bar does, and the page keeps
     its width. The scrollbar is hidden for the same reason as there: a bar
     under a row of buttons reads as a second control. */
  #scrMetricField .inline { overflow-x: auto; scrollbar-width: none; }
  #scrMetricField .inline::-webkit-scrollbar { display: none; }

  /* ---- the screener's MACD metric ---- */
  /* The table itself is the screener's, unchanged: the switch above it only
     changes what the four timeframe columns hold. What is new is the summary
     block above the table and the crossover mark inside a cell. */

  /* The per-cell crossover mark. Small and set slightly high so it reads as an
     annotation on the number rather than as part of it. */
  .macd-x { font-size: 8px; vertical-align: 2px; margin-left: 3px; }
  .macd-x.up { color: var(--green); }
  .macd-x.down { color: var(--red); }
  .screener-tbl.macd-on td, .screener-tbl.vol-on td,
  .screener-tbl.btc-on td, .screener-tbl.sig-on td, .screener-tbl.pred-on td { font-variant-numeric: tabular-nums; }

  /* ---- the screener's Signals metric ---- */
  /* Two readings of ONE bar per cell, on a line: the turnover multiple against
     the coin's own twenty-bar normal, and the reversal mark beside it. It is
     the watchboard's pair carried to this table — the multiple says how loud
     the bar was, the arrow says whether it turned — and the cell is one line
     tall because this table has four of them per row and a phone to fit.
     The slot glyphs (the muted ·) hold their width: a column that breathes
     every time an arrow appears is a column you cannot scan down. */
  .sig-cell { white-space: nowrap; }
  .sig-mult.quiet { color: var(--muted); }
  .sig-mult.spike { color: var(--accent); font-weight: 700; }
  .sig-rev { visibility: hidden; }
  .sig-rev.up { visibility: visible; color: var(--green); }
  .sig-rev.down { visibility: visible; color: var(--red); }

  /* Two of the eight confluence checks are shared rather than directional —
     volume and ADX gate whether the move is worth trusting, not which way it
     goes — so they are drawn as one cell across both columns. Centred, so the
     span reads as deliberate rather than as a row that lost a value. */
  .pred-both { text-align: center; font-size: 12px; }
  .pred-both.on { color: var(--muted); }
  .pred-tbl td small { display: block; font-size: 11px; opacity: .75; }

  /* ---- the screener's Predictor metric ---- */
  .pred-cell span { display: block; font-size: 12px; }
  .pred-cell small { display: block; color: var(--muted); font: 11px var(--mono); }
  .pred-cell.up span { color: var(--green); }
  .pred-cell.down span { color: var(--red); }

  /* ---- the Predictor's working, on the chart screen ---- */
  /* A shut panel by default: it answers "why", which is a question you ask
     about a number you have already seen on the board. The summary carries the
     reading itself so the panel is worth something before it is opened. */
  .pred-panel > summary { cursor: pointer; font-weight: 600; list-style: none; }
  .pred-panel > summary::-webkit-details-marker { display: none; }
  .pred-panel > summary::before { content: "▸ "; color: var(--muted); }
  .pred-panel[open] > summary::before { content: "▾ "; }
  .pred-panel > summary small { font-weight: 400; margin-left: 4px; }
  .pred-panel > summary span { float: right; font: 12px var(--mono); }
  .pred-tfs { margin: 10px 0; }
  .pred-block { margin-top: 14px; }
  .pred-block h4 { margin: 0 0 6px; font-size: 13px; }
  .pred-block h4 small { font-weight: 400; }
  .pred-tbl { width: 100%; border-collapse: collapse; font-size: 12px; }
  .pred-tbl th, .pred-tbl td { padding: 3px 8px 3px 0; text-align: left; white-space: nowrap; }
  .pred-tbl th { color: var(--muted); font-weight: 500; }
  .pred-tbl tfoot td { border-top: 1px solid var(--border); padding-top: 6px; }
  /* The grade bars are the one thing here meant to be read DOWN the column
     rather than across the row, so they are monospaced and the unfilled half
     keeps its width instead of collapsing. */
  .pred-bar { font: 11px/1 var(--mono); letter-spacing: -1px; }
  .pred-bar-off { color: var(--border); }
  .pred-side { font-variant-numeric: tabular-nums; }
  .pred-side.up b { color: var(--green); }
  .pred-side.down b { color: var(--red); }
  .pred-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 6px 14px; }
  .pred-grid > div { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; }
  .pred-grid b { font-variant-numeric: tabular-nums; }
  .pred-sets { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 4px 18px; }
  .pred-set-h { color: var(--muted); font-size: 11px; margin: 8px 0 4px; text-transform: uppercase; letter-spacing: .04em; }
  .pred-set { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 4px; font-size: 12px; }
  .pred-set input { width: 88px; }
  .pred-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 10px; }
  /* The chart panel's read-only echo of the same knobs. Two columns of
     label-and-value rather than fields: it is saying what the number above was
     scored with, not offering to change it. */
  .pred-sets-r { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 2px 18px; }
  .pred-set-r { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 12px; }
  .pred-set-r b { font: 12px var(--mono); }
  /* The range hint under each field in the settings sheet. */
  .pred-set small { flex: 0 0 auto; font-size: 11px; }

  /* ---- the screener's Volume metric ---- */
  /* Two figures per cell, stacked: how much turnover changed over that window,
     and what price did over the same one. Turnover leads because it is the
     metric you switched to and the one the column sorts by; price is the
     qualifier under it, because "+240%" is a breakout and a capitulation until
     something says which. */
  .vol-cell { line-height: 1.25; }
  .vol-cell span { display: block; }
  /* Turnover has no direction, so it does not get the green/red the other two
     metrics use: a volume surge is neither good news nor bad news, and painting
     expansion green would be telling you that a coin being dumped on triple
     volume is a coin doing well. The axis here is loudness. Expanding turnover is
     the thing worth your eye and wears the accent colour; a coin going quiet is
     the absence of news and falls back to the muted `quiet` every other dashed or
     inert cell in this table already uses. */
  .screener-tbl.vol-on td .vol-up { color: var(--accent); }
  /* The price line is subordinate by size, not by colour — it keeps the green
     and red it has in every other table on this screen, because there the
     convention means exactly what it always means. Small enough to read as the
     qualifier it is, large enough to read at arm's length. */
  .vol-cell .vol-px { font-size: 10px; }
  /* And the residual under it, at the same size and in the same green and red,
     because it is the same move — just the part of it BTC does not account for.
     Two lines wearing one convention on purpose: when their signs DISAGREE, that
     is the reading worth having (price up, residual negative is a coin that went
     up less than BTC was dragging it), and it only reads as a disagreement if
     both are speaking the same language. */
  .vol-cell .vol-solo { font-size: 10px; }
  /* And the same for the tab label, which carries the loudest hour on the list
     while this metric is showing. The ▲▼ beside it is the price move and keeps
     the green and red, exactly as the second line of a cell does — same split of
     the two scales, at the one size that has room for an arrow and not for a
     second percentage. */
  .tab-v .vol-up { color: var(--accent); }
  /* ---- the screener's vs-BTC metric ---- */
  /* Two figures per cell again, and stacked the same way, but the two lines
     swap jobs. The residual leads because it is the EVENT — what the coin did
     that BTC does not account for — and the index sits under it because it is a
     property of the coin rather than something that just happened. */
  .btc-cell { line-height: 1.25; }
  .btc-cell span { display: block; }
  /* The residual has a direction: the coin outran BTC's share of the move or it
     fell behind it. So it keeps the green and red every price move on this
     screen wears, because here that convention means exactly what it always
     means. */
  .btc-cell .btc-follow { font-size: 10px; }
  /* The index does NOT have a direction — following BTC is neither good news
     nor bad, it is a fact about the coin, and painting a low one green would be
     telling you a decoupled coin is a coin doing well. Its axis is independence:
     a coin genuinely off on its own business takes the accent colour, and
     everything else falls back to the muted `quiet` the rest of this table
     already uses for the inert. Same split as the volume cell, other way up. */
  .screener-tbl.btc-on td .btc-solo { color: var(--accent); }
  /* The key to the two lines, above the table rather than inside a tooltip.
     A `title` attribute does not exist on a touch screen and this app is read on
     a phone, so anything only a hover could reach was written for nobody. The
     samples are drawn in the cells' own colours and the cells' own font,
     because a key that merely DESCRIBES a green +2.4σ is one more thing to
     translate; a key that IS one is read in a glance. */
  .btc-key { display: grid; gap: 4px; margin: 10px 0 12px; }
  .btc-key > div { display: grid; grid-template-columns: 52px 1fr; gap: 10px; align-items: baseline; }
  .btc-key-eg {
    font: 12px/1.3 var(--mono); text-align: right;
    font-variant-numeric: tabular-nums;
  }
  /* The same class names the cells use, and the colours restated rather than
     inherited: the table's own rules are scoped to the table, and a key that
     silently lost its colour the day someone tightened a selector would be a
     key explaining a green number in grey. */
  .btc-key-eg.profit { color: var(--green); }
  .btc-key-eg.btc-solo { color: var(--accent); }
  .btc-key span:last-child { font-size: 11px; color: var(--muted); line-height: 1.35; }
  .btc-key b { color: var(--text); font-weight: 600; }
  /* "Traded" is the traded list's column — when did you last take one. On the
     market board there is no answer to that for most rows, and a column of
     dashes is worse than no column. */
  .screener-tbl.market-on .scr-traded { display: none; }

  /* ---- the summary block ---- */
  /* Three things four hundred rows cannot say for themselves: what they average,
     how they split, and where they stood before. */
  .macd-summary { margin-bottom: 14px; }

  /* The timeframe picker, which doubles as the glance across all four: each tab
     carries its own bullish share as a bar underneath, so choosing one to read
     closely does not cost you the comparison. */
  .macd-tabs { display: flex; gap: 6px; margin-bottom: 10px; }
  .macd-tab {
    flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 4px;
    background: none; border: none; cursor: pointer; padding: 5px 3px 4px;
    border-radius: 8px; font: 11px/1 var(--mono); color: var(--muted);
    text-transform: uppercase;
  }
  /* The chosen one is what the three cards below are describing, so it is given
     a panel of its own rather than a colour change alone — on a phone the cards
     are far enough down that "which tab is on" has to survive a glance. */
  .macd-tab.on { color: var(--text); background: color-mix(in srgb, var(--text) 7%, transparent); }
  .macd-tab-tf { font-size: 10px; }
  /* The number the tab exists to carry. Same figure as the card's headline, so
     the four timeframes can be compared without visiting each one. */
  .macd-tab-avg { font-size: 13px; font-variant-numeric: tabular-nums; }
  .macd-tab-split { font-size: 9px; color: var(--muted); text-transform: none; }
  .macd-tab-bar {
    display: block; height: 4px; border-radius: 2px; overflow: hidden;
    background: color-mix(in srgb, var(--red) 55%, transparent);
  }
  /* No answer yet is not the same as nought per cent bullish, so an unseeded
     timeframe shows a flat rule rather than a full red bar. */
  .macd-tab-bar[data-empty] { background: var(--border); }
  .macd-tab-bar > span { display: block; height: 100%; background: var(--green); }
  .macd-tab.on .macd-tab-bar { height: 5px; }

  /* The cards. One column on a phone, three across as soon as there is room —
     they are three readings of the same moment and reading them side by side is
     the point. */
  .macd-cards { display: grid; gap: 10px; }
  @media (min-width: 641px) { .macd-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
  .macd-card {
    background: color-mix(in srgb, var(--text) 4%, transparent);
    border: 1px solid var(--border); border-radius: 10px; padding: 12px;
  }
  /* The timeframe the card is about, stated on the card. The picker above says
     which is selected, but a card that names no period is a number with no
     question attached — and these three sit far enough below the picker on a
     phone that the tab is off screen while you read them. */
  .macd-card-tf {
    order: 1; margin-left: auto; padding: 2px 7px; border-radius: 999px;
    background: color-mix(in srgb, var(--text) 9%, transparent);
    color: var(--text); font-size: 10px; letter-spacing: .04em;
  }
  .macd-card-h {
    display: flex; align-items: center; gap: 6px; margin-bottom: 10px;
    font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em;
  }
  /* The "what is this" dot. A title attribute rather than a popover: the
     explanations are one sentence each and a phone shows them on long-press. */
  .macd-info {
    width: 13px; height: 13px; border-radius: 50%; border: 1px solid var(--border);
    display: inline-flex; align-items: center; justify-content: center;
    font: 9px/1 var(--mono); text-transform: none; cursor: help; flex: none;
  }

  .macd-big {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    font: 26px/1 var(--mono); margin-bottom: 14px;
  }
  .macd-badge {
    font: 10px/1 var(--mono); text-transform: uppercase; letter-spacing: .04em;
    padding: 5px 9px; border-radius: 999px; white-space: nowrap;
  }
  .macd-badge.up { color: var(--green); background: color-mix(in srgb, var(--green) 18%, transparent); }
  .macd-badge.down { color: var(--red); background: color-mix(in srgb, var(--red) 18%, transparent); }
  .macd-badge.flat { color: var(--muted); background: color-mix(in srgb, var(--text) 8%, transparent); }

  /* The gauge runs POSITIVE ON THE LEFT, which is the opposite of a number line
     — so both ends are labelled and the needle is never left to be read by
     position alone. */
  .macd-gauge-l {
    display: flex; justify-content: space-between; margin-bottom: 5px;
    font: 10px/1 var(--mono); color: var(--muted);
  }
  .macd-gauge {
    position: relative; height: 6px; border-radius: 3px;
    background: linear-gradient(to right, var(--green), var(--red));
  }
  .macd-needle {
    position: absolute; top: 50%; width: 13px; height: 13px; border-radius: 50%;
    background: var(--text); transform: translate(-50%, -50%);
    box-shadow: 0 0 0 2px var(--panel);
  }

  .macd-split-l, .macd-split-n { display: flex; justify-content: space-between; gap: 10px; }
  .macd-split-l { font: 11px/1 var(--mono); margin-bottom: 6px; }
  .macd-split-n { font: 20px/1 var(--mono); margin-bottom: 12px; }
  /* One bar, filled from the left by the positive share. Not a chart — a gauge:
     the question is "is this list mostly green", and a bare pair of numbers
     makes you compare them yourself. */
  .macd-bar {
    height: 6px; border-radius: 3px; overflow: hidden;
    background: color-mix(in srgb, var(--red) 70%, transparent);
  }
  .macd-bar > span { display: block; height: 100%; background: var(--green); }

  .macd-hist-row {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    font-size: 12px; padding: 3px 0;
  }
  .macd-pill {
    font: 11px/1 var(--mono); padding: 5px 9px; border-radius: 6px; min-width: 58px;
    text-align: center; white-space: nowrap;
  }
  .macd-pill.up { color: var(--green); background: color-mix(in srgb, var(--green) 18%, transparent); }
  .macd-pill.down { color: var(--red); background: color-mix(in srgb, var(--red) 18%, transparent); }
  .macd-pill.none { color: var(--muted); background: color-mix(in srgb, var(--text) 6%, transparent); }

  /* ---- receipt ---- */
  /* Not a ticket — the ticket is the card up in the report. This is what the
     exchange came back with, and it stays until dismissed. */
  #tradeResult:empty { display: none; }
  .receipt { border: 1px solid color-mix(in srgb, var(--green) 45%, transparent); }
  .receipt.aborted { border-color: color-mix(in srgb, var(--red) 55%, transparent); }
  .receipt-h { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
  .receipt-h h3 { margin: 0; font-size: 15px; margin-right: auto; }
  .receipt-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; font: 13px var(--mono); align-items: baseline; }
  .receipt-grid .k { color: var(--muted); }
  .receipt-grid .big { font: 15px var(--mono); }

  /* ---- market environment ---- */
  /* Production is the dangerous one, so it — not the testnet — carries the loud
     styling: a red rail down the page, and a red rail plus a red pill on the card
     the moment it is armed. You should be able to tell which exchange you are on
     from the corner of your eye. */
  /* Two taps deep now, so the switch itself can afford to say what each side
     means instead of relying on you remembering which word is the dangerous one. */
  #envSeg button { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; padding: 8px 12px; line-height: 1.2; }
  #envSeg button span { font: 11px var(--mono); opacity: .75; }
  #envSeg button.on[data-env="testnet"] { background: var(--amber); color: #241a00; }
  #envSeg button.on[data-env="production"] { background: var(--red); color: #fff; }
  /* The header pill: red on production, amber on the testnet — the same colours
     as the rail across the top of the page and the badge on the trade ticket. */
  #envTag { font: 600 10px/1.6 inherit; }

  /* ---- risk per trade ---- */
  /* Four choices on one line down to a 320px phone, so the comparison is a
     glance rather than a menu. Each carries the percentage it would mean at the
     deposit currently in the box — the number under the word is the whole
     reason to prefer one of them, and it changes as the account grows. */
  #riskSeg button { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; padding: 8px 6px; line-height: 1.2; }
  #riskSeg button span { font: 11px var(--mono); opacity: .75; }
  /* The custom ladder: one row per deposit band, label left, percentage right.
     Five rows rather than one box, because what is being customised is the
     shape of the de-escalation and not a single number. */
  .riskcustom { margin-top: 10px; }
  .rc-h { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 6px; }
  .rc-row { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
  .rc-row label { flex: 1; min-width: 0; font: 12px var(--mono); color: var(--muted); }
  .rc-row input { flex: none; width: 92px; height: 38px; text-align: right; }
  /* The ladder in force, as a shape. The tier the deposit is in now is lit;
     the rest are where it goes as the account grows, which is the whole
     argument for having a ladder at all. */
  .risk-rungs { display: flex; flex-wrap: wrap; gap: 4px 6px; margin: -2px 0 12px; }
  .risk-rungs span {
    font: 11px var(--mono); color: var(--muted); padding: 3px 7px;
    border: 1px solid var(--border); border-radius: 20px; white-space: nowrap;
  }
  .risk-rungs span.on { color: var(--text); border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
  /* What the account's own trades say the setting is worth. Boxed off from the
     arithmetic above it because it is a different kind of claim — that is what
     the percentage costs, this is what it has been doing. */
  .risk-mine { margin-top: 12px; padding: 10px 12px; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; }
  .rm-h { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 6px; }
  .rm-lead { margin: 0; font-size: 13px; line-height: 1.5; }
  .rm-lead .k { color: var(--muted); font-size: 11px; white-space: nowrap; }
  .rm-row { display: flex; flex-wrap: wrap; gap: 4px 6px; margin-top: 8px; }
  .rm-row span {
    font: 11px var(--mono); color: var(--muted); padding: 3px 7px;
    border: 1px solid var(--border); border-radius: 20px; white-space: nowrap;
  }
  .rm-row span.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
  .risk-mine .note.quiet { margin-top: 8px; }
  .risk-what { margin-top: 10px; }
  /* The setting restated in this account's dollars. Its own line, above the
     table, because it is the answer and the table is the consequence. */
  .risk-lead { margin: 0 0 10px; font-size: 13px; line-height: 1.5; }
  /* The tier and the leverage clause are each one fact; a wrap that leaves
     "leverage" alone at the end of a line splits one into two. */
  .risk-lead .k { color: var(--muted); font-size: 11px; white-space: nowrap; }
  /* A row per losing streak. Not clickable — the global tbody rule assumes the
     variants table, where a row is a trade you can take. */
  .risk-streak { font-size: 12px; }
  .risk-streak tbody tr { cursor: default; }
  .risk-streak th, .risk-streak td { padding: 7px 8px; }
  .risk-streak td.k { color: var(--muted); font-size: 11px; }
  .risk-streak tbody tr:last-child td { border-bottom: none; }
  /* The ten-in-a-row row is the one the whole section exists to show. */
  .risk-streak tbody tr:last-child td:first-child { font-weight: 600; }
  @media (max-width: 380px) {
    #riskSeg button { padding: 8px 2px; font-size: 12px; }
    .risk-streak th, .risk-streak td { padding: 7px 5px; }
  }
  body.env-production { border-top: 3px solid var(--red); }
  body.env-testnet { border-top: 3px solid var(--amber); }
  .env-tag { font: 600 10px/1.6 inherit; letter-spacing: .06em; padding: 1px 7px; border-radius: 20px; vertical-align: 2px; }
  .env-tag.testnet { background: color-mix(in srgb, var(--amber) 22%, transparent); color: var(--amber); border: 1px solid color-mix(in srgb, var(--amber) 50%, transparent); }
  .env-tag.prod { background: color-mix(in srgb, var(--red) 20%, transparent); color: var(--red); border: 1px solid color-mix(in srgb, var(--red) 55%, transparent); }
  /* Rides in the same bar but is not the same kind of fact: the others name the
     exchange this order fires at, this one names a rule it is crossing. Dashed
     and unfilled, so a glance never reads it as a third environment. */
  .env-tag.tight { background: none; color: var(--red); border: 1px dashed color-mix(in srgb, var(--red) 60%, transparent); }

  /* ---- accounts & API keys ---- */
  .userbar {
    display: flex; align-items: center; gap: 8px 10px; flex-wrap: wrap;
    background: var(--panel); border: 1px solid var(--border); border-radius: 12px;
    padding: 10px 12px; margin-bottom: 12px; font-size: 13px;
  }
  .userbar .hint { font-size: 11px; }
  .userbar-actions { margin-left: auto; display: flex; gap: 8px; }
  .keychip { font: 11px var(--mono); color: var(--muted); border: 1px solid var(--border); border-radius: 999px; padding: 2px 9px; white-space: nowrap; }
  .keychip.on { color: var(--green); border-color: color-mix(in srgb, var(--green) 45%, transparent); }
  .keychip.admin { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
  .stack { display: flex; flex-direction: column; gap: 10px; }
  .keycards { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 12px; margin-top: 10px; }
  .keycard { background: var(--panel2); border: 1px solid var(--border); border-radius: 10px; padding: 12px; }
  .keycard.prod { border-color: color-mix(in srgb, var(--red) 40%, transparent); }
  .keycard.test { border-color: color-mix(in srgb, var(--amber) 35%, transparent); }
  .keycard-h { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
  .keycard-h code { font: 11px var(--mono); color: var(--text); }
  .keycard-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
  .keycard-actions .act { width: auto; }

  /* ---- Touch ID / Face ID ---- */
  /* The unlock button is the primary way back in on a device that has been
     enrolled, so it sits above the password form and is styled quieter than a
     blue "act" only in that it carries the sensor glyph — it is still the
     action this panel expects. */
  button.act.passkey { background: var(--panel2); color: var(--text); border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); }
  button.act.passkey::before { content: "☝ "; }
  /* "or your password": a rule with the alternative sitting in the gap. */
  .pk-or { display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: 12px; margin: 12px 0; }
  .pk-or::before, .pk-or::after { content: ""; flex: 1; height: 1px; background: var(--border); }
  .pk-list { display: flex; flex-direction: column; gap: 8px; margin: 10px 0; }
  .pk-row {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    background: var(--panel2); border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px;
  }
  /* A device that can no longer open the vault is a repair job, not a warning
     about money — amber, and it says why in the line beneath its name. */
  .pk-row.stale { border-color: color-mix(in srgb, var(--amber) 45%, transparent); }
  .pk-what { display: flex; flex-direction: column; gap: 2px; margin-right: auto; min-width: 0; }
  .pk-what .hint { font-size: 11px; }
  .pk-actions { display: flex; gap: 8px; }

  /* ---- phones ---- */
  @media (max-width: 640px) {
    .userbar-actions { margin-left: 0; width: 100%; }
    .userbar-actions button { flex: 1; }
    .keycards { grid-template-columns: 1fr; }
    /* On a phone the enrolled-device row stacks: the name and its state on one
       line, the buttons full width underneath, same as every other control. */
    .pk-what { width: 100%; }
    .pk-actions { width: 100%; }
    .pk-actions button { flex: 1; }
    table { font-size: 12px; }
    th, td { padding: 8px 6px; }

    /* The variant table is the one thing on this page you actually decide from.
       Here each row becomes a card: name and its trade button on the top line,
       the numbers underneath in a labelled three-up grid, nothing beyond the
       right edge. It stays a <table> in the markup, so row selection, the
       recommended pick and the noise flags all keep working untouched. */
    .tbl-scroll.cards { overflow-x: visible; }
    .tbl-scroll.cards table, .tbl-scroll.cards tbody { display: block; }
    .tbl-scroll.cards thead { display: none; }
    /* Tight on purpose. Nine of these sit under a chart that is pinned to the
       top of the screen, so every pixel a card does not spend is another card
       you can compare without scrolling. The numbers keep their size — they are
       what you are reading — and the room comes out of the air around them:
       body leading on a one-line mono figure, and the padding between the two
       rows of three. */
    .tbl-scroll.cards tbody tr {
      display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
      gap: 6px 10px; align-items: start;
      background: var(--panel2); border: 1px solid var(--border);
      border-radius: 10px; padding: 8px 10px; margin-bottom: 6px;
    }
    .tbl-scroll.cards td {
      display: block; border: none; padding: 0; line-height: 1.25;
      text-align: left; white-space: normal; overflow-wrap: anywhere;
    }
    /* Each number carries its own column heading, since the header row is gone. */
    .tbl-scroll.cards td[data-k]::before {
      content: attr(data-k); display: block; margin-bottom: 1px;
      font: 10px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
      color: var(--muted); text-transform: uppercase; letter-spacing: .04em;
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .tbl-scroll.cards td:first-child { grid-column: 1 / span 2; grid-row: 1; font-size: 13px; line-height: 1.3; }
    .tbl-scroll.cards td.act-col {
      grid-column: 3; grid-row: 1; position: static; text-align: right;
      background: none; border-left: none; padding-left: 0;
    }
    /* With no trade button the name only spans two of the three columns, and the
       first number back-fills the empty corner beside it — the stop ends up in
       the top right in header-sized type. Letting the name take the whole first
       row keeps the six figures a tidy 3×2 block either way. */
    .tbl-scroll.cards tbody tr:not(:has(.act-col)) td:first-child { grid-column: 1 / -1; }
    .tbl-scroll.cards td.act-col button { min-height: 36px; padding: 0 14px; }
    .tbl-scroll.cards tbody tr.pick td:first-child::before { content: "★ "; color: var(--accent); }
    .tbl-scroll.cards tbody tr.pick td, .tbl-scroll.cards tbody tr.sel td,
    .tbl-scroll.cards tbody tr.heavy td { background: none; }
    .tbl-scroll.cards tbody tr.pick { background: color-mix(in srgb, var(--accent) 12%, var(--panel2)); }
    .tbl-scroll.cards tbody tr.heavy { border-color: color-mix(in srgb, var(--amber) 45%, transparent); }
    .tbl-scroll.cards tbody tr.sel {
      background: color-mix(in srgb, var(--green) 14%, var(--panel2));
      border-color: color-mix(in srgb, var(--green) 50%, transparent);
      box-shadow: inset 3px 0 0 var(--green);
    }
    .tbl-scroll.cards tbody tr.sel td:first-child { box-shadow: none; }
    /* A quarter of a phone is not much to fit a value into, and the alternative
       to a smaller one is an ellipsis eating the end of it — which costs a whole
       character however little the overflow was. This squeeze used to start at
       360px, back when three of the four tabs had anything to print; the Trade
       tab's PnL is the fourth, and it takes the row past what a phone has from
       about 420px down.
       The numbers, measured rather than guessed, at 390px: every label was
       ALREADY overflowing by about 2px with three values and the old spacing, so
       the balance has been reading "$190 · 1 op…" all along. At 9.5px, with 4px
       between the tabs and the settings button holding its pill a little
       tighter, four values fit — 62.9px wanted against 62.9px given, which is
       exact and not an accident: ten characters is what the row has, and the
       Trade tab's label was cut to that (whole dollars, R to one place). At
       420px it all fits at full size, so that is where the squeeze starts.
       Below 375px, or on a PnL that reaches three figures, the ellipsis comes
       back — and what it takes first is the end of the R. */
    @media (max-width: 420px) {
      .tab-v { font: 9.5px/1.2 var(--mono); }
      .seg.tabs.nav { gap: 4px; }
      .settingsbtn { gap: 4px; padding: 0 5px; }
    }
    @media (max-width: 360px) {
      .tbl-scroll.cards td[data-k]::before { font-size: 9.5px; letter-spacing: .01em; }
      /* Half a 320px column is not much to fit three unit buttons into. */
      .seg.units button { font-size: 11px; padding: 0 4px; }
    }

    /* Pin the chart to the top of the screen while everything beneath it
       scrolls, so a tap updates something you are already looking at instead of
       something you have to scroll back up to see. The host is what sticks, not
       the chart inside it: sticky lives inside its own parent's box, and the
       host is a direct child of the screen — so the chart now stays pinned past
       the side/entry/stop row and the sizing line as well as the variant cards,
       where before it came unstuck at the top of the results panel it lived in.
       The opaque background is what lets it paint over what passes underneath,
       and the negative margins take it to the edges of the phone through the
       body's 12px gutter. */
    #chartHost:not(:empty) {
      position: sticky; top: 0; z-index: 5;
      background: var(--bg);
      margin-left: calc(-12px - env(safe-area-inset-left));
      margin-right: calc(-12px - env(safe-area-inset-right));
      padding: 0 calc(12px + env(safe-area-inset-right)) 8px calc(12px + env(safe-area-inset-left));
      border-bottom: 1px solid var(--border);
      box-shadow: 0 6px 10px -8px rgba(0, 0, 0, .5);
    }
    /* The head wrapper has to stay transparent to layout on a phone, because
       sticky is constrained by the nearest block ancestor: left as .trade-head,
       the chart would unstick the moment the head's own short box scrolled
       past — the pins below it would push it off the screen. Dissolved, the
       ticker and the chart are laid out as .trade-cols' own children again and
       the chart's containing block is the whole tab, which is the shape it
       stuck through before the wrappers existed. */
    .trade-head { display: contents; }
  }

  @media (hover: hover) {
    button.mini:hover { border-color: var(--muted); }
    button.icon:hover { color: var(--text); border-color: var(--muted); }
    button.mini.trade:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
    button.mini.danger:hover { border-color: var(--red); }
    button.rec-tag:hover { color: var(--text); border-color: var(--muted); }
    .chart-tfs button:hover { color: var(--text); border-color: var(--muted); }
    tbody tr:hover td { background: color-mix(in srgb, var(--text) 6%, transparent); }
    /* The pinned symbol cell paints its own opaque background to cover the
       columns that scroll beneath it — which also outranked the hover wash, so
       a hovered row lit up from its second cell on. Restated here, and mixed
       into the panel rather than into transparency, because this cell cannot
       afford to let the scrolled columns show through. */
    .screener-tbl tbody tr:hover td:first-child,
    .tbl-sticky tbody tr:hover td:first-child {
      background: color-mix(in srgb, var(--text) 6%, var(--panel));
    }
    .tbl-scroll.cards tbody tr:hover td { background: none; }
    details.more > summary:hover { color: var(--accent); }
  }
