@charset "utf-8";
/* ==========================================================================
   orchexai arena - console stylesheet
   ==========================================================================

   Constraints this file is built under, all deliberate:

   * NO build step, NO CDN, NO remote request of any kind. This has to render
     byte-for-byte the same on an air-gapped VPS behind a strict CSP. The one
     webfont is SELF-HOSTED from /static/fonts and declared in section 0 with
     a same-origin path; there is no font-provider link and no imported
     stylesheet. webapp/tests/test_integration.py asserts that - it greps this
     file for absolute URLs and for an import rule, so do not add either, and
     do not name them in a comment here either.
   * Dark is the DEFAULT. People stare at this screen while a position is open.
     A light theme comes from `prefers-color-scheme: light` only - there is no
     toggle, because a toggle needs JS and state we do not want to own.
   * The dark palette hexes below are the same values hard-coded inline in
     templates/login.html and templates/register.html. Those two pages are
     deliberately standalone (they must render even if /static is broken), so
     if you change a core hex here, change it there too or the product stops
     reading as one thing. The four that matter most:
         page        #090514
         card        #130D26
         ink         #EEEAF7
         accent      #A78BFA   (brand violet, lifted for a dark surface)
   * Density over decoration. This is a trading console: a user should be able
     to see the kill-switch state, the open position and the last failure
     without scrolling and without hunting.

   BRAND
   -----
   The identity is a flat lockup: an orchestration glyph (two signal paths
   converging on a hub) plus a lowercase, single-weight wordmark - "orchexai"
   in ink, "arena" in the accent. Enterprise, not startup: one accent colour,
   no boxed icon tile, no gloss. The stylesheet carries that through the token
   layer only - see section 1. Change the three --brand-* values there and the
   entire console retints.

   ACCESSIBILITY RULES ENCODED HERE
   -------------------------------
   * Numbers are tabular (`font-variant-numeric: tabular-nums`) everywhere they
     can be compared column-wise. Digits must line up or you cannot scan them.
   * Gain/loss and long/short are NEVER signalled by colour alone. `.pnl-pos`,
     `.pnl-neg`, `.side-long` and `.side-short` all carry a glyph prefix, so
     they survive greyscale, a colour-blind user and a printed page.
   * Destructive controls (kill switch, rotate token, delete key, delete
     account) use `.btn-danger` / `.btn-danger-ghost`, which are visually
     distinct in SHAPE and WEIGHT, not just hue.
   * Focus is always visible, and it is the ONE place the secondary brand
     cyan is allowed to shout: a 2px cyan ring, offset from the control, so it
     stays visible even on top of a violet accent button (an accent-coloured
     focus ring on an accent-coloured button is invisible).
   * The brand accent replaces the interactive/primary colour ONLY. --ok stays
     green, --warn stays amber, --danger stays red. A trader must never read a
     branded number as a profit.

   THE SHELL
   ---------
   Navigation is a LEFT SIDEBAR, not a top bar. The page is a two-column flex
   row - a fixed-width sticky rail and a content column - and everything the
   content column owns (status row, ribbon, page, footer) shares ONE horizontal
   gutter token, so the right edge of the status row lines up with the right
   edge of the cards below it. That alignment is the whole point of the token;
   see section 3.

     .shell           flex row, min-height 100vh
       .sidebar       flex: none, width --sidebar-w, sticky, 100vh tall
       .shell-main    flex: 1, min-width: 0   <- load-bearing, see section 3
         .statusbar   sticky, right-aligned chips, all at --chip-h
         .page        max-width --shell, padding 0 --gutter
         .footer

   ORGANISATION
   ------------
     0  typeface        8  notices / flash / ribbons
     1  tokens          9  forms
     2  reset + base   10  buttons
     3  layout shell   12  tables
     4  navigation     13  numeric + market semantics
        (sidebar +     14  console widgets (tiles, kill switch, checklist)
         status row)   15  utilities
     5  cards          17  responsive / motion / print
     6  kv lists
     7  badges
   ========================================================================== */


/* ==========================================================================
   0. TYPEFACE
   --------------------------------------------------------------------------
   Space Grotesk, self-hosted, and it is the wordmark's own typeface - the top
   bar and the page are set in the same letterforms as the logo.

   These are VARIABLE fonts: ONE file per unicode subset covering the whole
   300-700 range, which is why each @font-face declares a weight RANGE rather
   than a single weight. That matters here because the console leans on odd
   in-between weights (520, 570, 630, 650) for hierarchy - with a variable
   font those are real instances, not the browser faking a bold.

   `swap` because a trading console must paint its numbers immediately; a
   FOIT on a 22KB local file is a bad trade either way, but swap makes the
   worst case "wrong font for 100ms", never "no price for 100ms".
   ========================================================================== */

@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("/static/fonts/SpaceGrotesk-variable-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("/static/fonts/SpaceGrotesk-variable-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}


/* ==========================================================================
   1. TOKENS
   ========================================================================== */

:root {
  color-scheme: dark light;

  /* -- BRAND ------------------------------------------------------------- */
  /* PLACEHOLDERS. These three stand in for the user's real brand constants -
     the theme's COLORS.violet / COLORS.cyan and the wordmark ink - and they
     are expected to be corrected to the canonical values later. They are
     defined exactly ONCE, here, and every accent, ring, tint and gradient in
     the rest of this file derives from them: swap these three lines and the
     entire console retints, with no other edit anywhere.

     They are deliberately NOT used raw as UI colours. #7C3AED is a fine brand
     colour and a poor link colour on a near-black page (3.4:1). So each theme
     below derives its own working accent from the brand hue - the light theme
     uses the brand violet unchanged, the dark theme uses a lifted tint of it -
     and both are contrast-checked. Keep that discipline when you swap them. */
  --brand-ink:    #0F0828;   /* wordmark ink - deep charcoal/navy            */
  --brand-violet: #7C3AED;   /* primary accent - "arena", the hub, actions   */
  --brand-cyan:   #06B6D4;   /* secondary - focus, the signal-path highlight */

  /* Per-theme companions to the two brand hues, because neither raw value is
     legible on both a near-black page and white paper. Each is the brand hue
     mixed toward that theme's extreme, worked out once and written down as a
     literal - see the regeneration formula on each line.

     Why literals and not color-mix():
       * static/chart.js reads --accent with getComputedStyle and passes the
         string straight to the canvas charting library. var() IS substituted
         in a custom property's computed value, but color-mix() is NOT
         evaluated - the library would receive the text "color-mix(...)" and
         fail to parse it. Tokens that chart.js reads must stay literal:
         --accent, --ok, --danger, --ok-soft, --danger-soft, --line,
         --line-soft, --bg-raised, --text-dim.
       * --focus draws the focus ring. If color-mix() were unsupported the
         token would be invalid at computed-value time and the outline would
         vanish entirely - the one failure mode not worth risking. (The usual
         "fallback declaration first" trick does not rescue a custom property:
         color-mix() is not a parse-time error inside one, so the earlier
         declaration is already gone by the time it fails.)

     If you change a brand hue, regenerate its two companions. */
  --brand-violet-lift: #AE85F4;  /* = brand-violet 62% + #FFFFFF - on dark   */
  --brand-cyan-lift:   #47C9DF;  /* = brand-cyan   74% + #FFFFFF - on dark   */
  --brand-cyan-deep:   #097493;  /* = brand-cyan   62% + brand-ink - on paper*/

  /* -- surfaces ---------------------------------------------------------- */
  /* Dark surfaces are the brand ink taken down and desaturated, so the page
     reads as the same family as the wordmark rather than neutral slate. */
  --bg:        #090514;   /* page                                            */

  /* The two brand glows painted across the page surface by `body`. Kept as
     tokens so each theme can tune them: a wash that reads as "depth" on a
     near-black page reads as "smudge" on white, so light uses roughly half
     the alpha. Decorative only - nothing is ever encoded in them. */
  --page-glow-a: rgba(124, 58, 237, 0.20);   /* violet, top-left  */
  --page-glow-b: rgba(6, 182, 212, 0.13);    /* cyan,   top-right */

  --bg-raised: #130D26;   /* cards - one step toward the viewer              */
  --bg-nav:    #100A22;   /* the sidebar - its own surface, between the two  */
  --bg-sunken: #0D0820;   /* code blocks, card footers, read-only fields     */
  --bg-input:  #0D0820;
  --bg-hover:  #1C1436;
  --line:      #322A5E;   /* structural border  (1.46:1 on --bg-raised)      */
  --line-soft: #221A44;   /* internal hairline                               */

  /* THE INTERACTIVE BOUNDARY, and it is a different job from --line.
     --line draws structure a reader looks THROUGH: table hairlines, card
     edges, dividers. It is deliberately quiet (1.46:1) and that is correct
     for decoration - WCAG puts no floor on it.

     But a text input's border is the only thing on the screen that says
     "type here". Nothing else distinguishes an empty field from the card it
     sits on, so it is a user interface component boundary and WCAG 1.4.11
     wants 3:1. At --line it measured 1.52:1 against the field's own fill and
     1.46:1 against the card - i.e. an empty input was, strictly, invisible.

     Same hue as --line (identical HSL h/s, lightness only), so the console
     still reads as one family; used ONLY where a control's outline carries
     that meaning - input/select/textarea and the outlined buttons.
     3.18:1 at worst here (against --bg-hover, i.e. a hovered ghost button);
     3.15:1 at worst on paper. */
  --line-strong: #6A5CB6;

  /* -- ink --------------------------------------------------------------- */
  --text:        #EEEAF7;  /* 15.9:1 on --bg-raised                          */
  --text-strong: #FFFFFF;  /* headings                                       */
  --text-dim:    #A79ECB;  /*  7.5:1 on --bg-raised                          */
  --text-faint:  #8C83B4;  /*  5.4:1 on --bg-raised - still AA at 11px       */

  /* -- accent ------------------------------------------------------------ */
  /* The brand violet lifted until it clears 4.5:1 as link text on the page
     (7.2:1). Because the fill is now light, its label is the brand ink, not
     white - ink on this violet is 6.9:1, white on it would be 2.3:1. */
  --accent:      var(--brand-violet-lift);
  --accent-text: var(--brand-ink);
  --accent-soft: color-mix(in srgb, var(--accent) 16%, transparent);
  --accent-line: color-mix(in srgb, var(--accent) 45%, transparent);

  /* -- focus (the secondary accent, used sparingly) ----------------------- */
  /* Cyan, not violet, so the ring never disappears into the accent button it
     is drawn around.

     The brand cyan lifted for a dark surface: 10.2:1 on the page, 9.6:1 on a
     card, against the 3:1 a focus indicator needs. */
  --focus:      var(--brand-cyan-lift);
  --focus-soft: color-mix(in srgb, var(--focus) 30%, transparent);

  /* -- semantic ---------------------------------------------------------- */
  /* NOT branded. Green is profit, red is loss, amber is halted - these carry
     meaning a trader acts on, so they stay where the eye already expects.
     Brightened slightly from the old palette to hold their contrast against
     the deeper page these surfaces became.

     The *-soft pairs must stay literal rgba(): chart.js reads --ok-soft and
     --danger-soft and hands them to the charting library. Do not "tidy" them
     into color-mix(). */
  --ok:          #3FB950;
  --ok-soft:     rgba(63, 185, 80, 0.14);
  --warn:        #E3B341;
  --warn-soft:   rgba(227, 179, 65, 0.14);
  --danger:      #F85149;
  --danger-soft: rgba(248, 81, 73, 0.14);

  /* Label colour on a filled ok/warn/danger button. On dark, those fills are
     bright enough that white text fails (2.9:1 on --danger); near-black
     passes (6.0:1) and matches the ink-on-accent button beside it. */
  --on-solid: #0B0616;

  /* -- geometry ---------------------------------------------------------- */
  /* ONE corner radius for the whole console. Cards, tiles, inputs, buttons and
     nav items all derive from these two so nothing looks bolted on. */
  --radius:    10px;
  --radius-sm: 6px;
  --radius-pill: 99px;
  --shell: 1180px;

  /* The shell. --gutter is shared by .page, .statusbar-inner, .ribbon and
     .footer: change it once and every horizontal edge in the content column
     moves together. That is what keeps the status row aligned with the cards
     under it instead of floating a few pixels off. */
  --sidebar-w:   256px;
  --sidebar-pad: 18px;
  --gutter:      26px;
  --statusbar-h: 56px;

  /* Height of the sidebar lockup. MIRRORS the `size` argument _nav.html passes
     to _logo.html (36) - the SVG's own attributes do the sizing; this token
     only exists so .brand-sub can indent itself to the wordmark's left edge,
     which starts at x=50 in the logo's 470x80 box (= 0.625 x the height). */
  --logo-h: 36px;

  /* Every chip in the status row is exactly this tall, so the testnet badge,
     the kill-switch lamp, the email and the sign-out button share one optical
     line rather than three slightly different ones. */
  --chip-h: 28px;

  /* -- motion ------------------------------------------------------------ */
  /* Short enough to feel immediate on a console where people click fast;
     long enough to read as a transition. The reduced-motion block in section
     17 zeroes every duration in the file, this one included. */
  --speed: 140ms;
  --ease:  cubic-bezier(0.4, 0, 0.2, 1);

  /* -- depth ------------------------------------------------------------- */
  --shadow-1: 0 1px 2px rgba(4, 2, 12, 0.50);
  --shadow-2: 0 6px 24px rgba(4, 2, 12, 0.55);
  --ring: 0 0 0 3px var(--focus-soft);

  /* A 1px highlight along the top edge of a raised surface. On a near-black
     page this is what makes a card look lit rather than drawn; on paper there
     is no such thing, so the light theme sets it to nothing. */
  --sheen: rgba(255, 255, 255, 0.05);

  /* The sidebar is one step off the page and casts to the right. */
  --shadow-nav: 6px 0 24px rgba(4, 2, 12, 0.38);

  /* -- the brand gradient, rationed ---------------------------------------
     Violet into cyan, echoing the two signal paths of the glyph converging on
     the hub. It appears in exactly THREE places in this file and nowhere else:
     the sidebar's outer edge, the bottom edge of the collapsed mobile strip
     and of the signed-out bar, and the 3px rail on the active nav item. Never
     as a filled area, never behind text. The first two are pure decoration
     carrying no text and no state, so they have no contrast floor and can show
     the true brand hues.

     --brand-edge / --brand-edge-v settle INTO --line rather than fading to
     `transparent`, which some engines fade through grey: past the two thirds
     mark each one is simply the border it replaces.

     --brand-rail is different: it marks which page you are on, so it is a UI
     indicator and does have a floor (3:1). It is built from the per-theme
     companions, not the raw hues - raw #06B6D4 is 2.4:1 on white and would
     fail. On dark: 6.9:1 violet / 9.7:1 cyan against the sidebar. On paper:
     5.7:1 / 5.3:1. It is also never the only signal - the active item carries
     aria-current, a raised surface and a heavier weight as well. */
  --brand-edge: linear-gradient(90deg,
                  var(--brand-violet) 0%,
                  var(--brand-cyan) 30%,
                  var(--line) 72%);

  --brand-edge-v: linear-gradient(180deg,
                    var(--brand-violet) 0%,
                    var(--brand-cyan) 26%,
                    var(--line) 68%);

  --brand-rail: linear-gradient(180deg,
                  var(--brand-violet-lift) 0%,
                  var(--brand-cyan-lift) 100%);

  /* -- type -------------------------------------------------------------- */
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "JetBrains Mono", Menlo,
          Consolas, "Liberation Mono", monospace;
  --sans: "Space Grotesk", system-ui, -apple-system, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;
}

@media (prefers-color-scheme: light) {
  :root {
    /* Paper is tinted a few points toward the brand ink's hue - enough that
       the page and the wordmark look related, not enough to read as purple. */
    --bg:        #F4F3F9;

    /* Half the dark theme's alpha: the same wash that gives a near-black page
       depth would look like a printing fault on paper. Enough to lift the
       corners and stop the console reading as a blank white sheet. */
    --page-glow-a: rgba(124, 58, 237, 0.11);
    --page-glow-b: rgba(6, 182, 212, 0.09);

    --bg-raised: #FFFFFF;
    --bg-nav:    #FFFFFF;
    --bg-sunken: #EEECF6;
    --bg-input:  #FFFFFF;
    --bg-hover:  #E9E6F4;
    --line:      #CBC5DE;
    --line-soft: #E4E0EF;
    --line-strong: #8577B2;   /* 3.98:1 on a white field, 3.24:1 at its worst */

    /* Body ink is the brand ink lightened just enough to stay comfortable at
       14px; headings get the brand ink itself (17.5:1 on the page). */
    --text:        #1D1836;
    --text-strong: var(--brand-ink);
    --text-dim:    #55506B;   /* 7.7:1 on white */
    --text-faint:  #615C77;   /* 6.4:1 on white */

    /* On paper the brand violet needs no help and is used exactly as given:
       5.7:1 on white as link text, and 5.7:1 the other way for a white label
       on the filled button. This is the one surface where the console shows
       the true brand colour. */
    --accent:      var(--brand-violet);
    --accent-text: #FFFFFF;
    --accent-soft: color-mix(in srgb, var(--accent) 10%, transparent);
    --accent-line: color-mix(in srgb, var(--accent) 38%, transparent);

    /* The brand cyan raw is only 2.4:1 on white - far too weak to mark focus.
       Deepened toward the brand ink instead: 5.3:1 on a card, 4.8:1 on the
       page, still unmistakably the brand's cyan. */
    --focus:      var(--brand-cyan-deep);
    --focus-soft: color-mix(in srgb, var(--focus) 25%, transparent);

    /* Each one step deeper than it used to be, and the reason is compositing,
       not taste. These inks are painted ON TOP OF their own *-soft tint - the
       status-row chips, the badges, the notices are all "ink on a wash of the
       same hue" - and the tint eats contrast that a measurement against plain
       white never sees. Measured on the rendered pixels of the real console:

         .kswitch-halted  amber on its own wash   4.41:1   FAILED AA
         .envchip-live    red   on its own wash   4.50:1   FAILED AA (on the line)
         .badge-danger    red   in a flagged row  4.28:1   FAILED AA

       Deepened along HSL LIGHTNESS ONLY - hue and saturation are byte-identical
       to the values above them, so green is the same green, amber the same
       amber, red the same red. A trader's colour vocabulary does not move; only
       the luminance does. Every other pairing improves at the same time (white
       on the filled button, the ink on the page, the ink on a card).

       If you ever lighten these back, re-run the tint composite, not the
       against-white one - against-white is the number that lied. */
    --ok:          #126C35;   /* was #137539 */
    --ok-soft:     rgba(18, 108, 53, 0.10);
    --warn:        #7B5300;   /* was #8A5D00 */
    --warn-soft:   rgba(123, 83, 0, 0.12);
    --danger:      #B51D28;   /* was #C7202C */
    --danger-soft: rgba(181, 29, 40, 0.09);

    /* On paper every filled button is dark enough for a white label. */
    --on-solid: #FFFFFF;

    --shadow-1: 0 1px 2px rgba(15, 8, 40, 0.07);
    --shadow-2: 0 8px 28px rgba(15, 8, 40, 0.12);
    --shadow-nav: 4px 0 22px rgba(15, 8, 40, 0.07);
    --ring: 0 0 0 3px var(--focus-soft);

    /* No lit top edge on paper - a white card on a white rail does not glow,
       it just needs its border. */
    --sheen: rgba(255, 255, 255, 0);

    /* The rail must clear 3:1 on white, so it uses the deepened companions.
       See the note beside --brand-rail above. */
    --brand-rail: linear-gradient(180deg,
                    var(--brand-violet) 0%,
                    var(--brand-cyan-deep) 100%);
  }
}

/* Windows high-contrast / forced colours: stop fighting the OS. */
@media (forced-colors: active) {
  .badge, .btn, button, .card, .tile { border: 1px solid ButtonBorder; }
}


/* ==========================================================================
   2. RESET + BASE
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scrollbar-color: var(--line) transparent;
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  /* A flat --bg made every page read as a blank sheet with boxes on it. Two
     very soft brand glows, anchored top-left and top-right, give the surface
     depth and tie it to the lockup without putting colour behind any text.
     `fixed` so it does not slide as a long orders table scrolls, and the
     alphas are low enough that body text keeps its full contrast ratio - the
     wash never crosses the reading column at any meaningful strength. */
  background:
    radial-gradient(52% 46% at 8% -6%,  var(--page-glow-a), transparent 70%),
    radial-gradient(46% 40% at 96% -8%, var(--page-glow-b), transparent 68%),
    var(--bg);
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a {
  color: var(--accent);
  text-decoration: none;
}
a:hover { text-decoration: underline; }

code, kbd, samp, pre {
  font-family: var(--mono);
  font-size: 0.92em;
  font-variant-ligatures: none;
}

code {
  background: var(--bg-sunken);
  border: 1px solid var(--line-soft);
  border-radius: 4px;
  padding: 0.05em 0.35em;
  overflow-wrap: anywhere;
}
pre code { background: none; border: 0; padding: 0; }

hr {
  border: 0;
  border-top: 1px solid var(--line-soft);
  margin: 18px 0;
}

img, svg { max-width: 100%; }

::selection { background: var(--accent-soft); color: var(--text); }

/* One focus treatment for the whole app, and the one place the secondary
   brand cyan is used at full strength.

   The ring is cyan rather than accent-violet on purpose: the primary button
   IS accent-violet, and an accent ring around an accent fill is invisible.
   `outline-offset: 2px` also matters for contrast - it lifts the ring off the
   control so the colour it has to beat is the page or card behind it
   (11.1:1 dark / 4.9:1 light), not the fill it surrounds. */
:where(a, button, .btn, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  box-shadow: var(--ring);
  border-radius: var(--radius-sm);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--accent);
  color: var(--accent-text);
  padding: 9px 15px;
  border-radius: 0 0 var(--radius-sm) 0;
  font-weight: 600;
}
.skip-link:focus { left: 0; top: 0; text-decoration: none; }


/* ==========================================================================
   3. LAYOUT SHELL
   --------------------------------------------------------------------------
   Two columns: a sticky sidebar and everything else. See the diagram in the
   file header and the structural notes in templates/base.html.
   ========================================================================== */

.shell {
  flex: 1 0 auto;
  display: flex;
  /* flex-start, not stretch: the sidebar sets its own 100vh height so it can
     be sticky. Stretched to the row height it would have nowhere to stick. */
  align-items: flex-start;
  width: 100%;
}

.shell-main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  /* The footer sits at the bottom of a short page; .page's `flex: 1 0 auto`
     below pushes it there. */
  min-height: 100vh;

  /* LOAD-BEARING. A flex item's default min-width is `auto`, i.e. its content.
     One wide order table or one un-broken webhook token would then widen this
     column, widen the row, and push the sidebar off the left of the screen -
     with no horizontal scrollbar to get it back. Every overflow-x: auto
     wrapper inside the console depends on this being 0. */
  min-width: 0;
}

.page {
  width: 100%;
  max-width: var(--shell);
  margin: 0 auto;
  padding: 24px var(--gutter) 64px;
  flex: 1 0 auto;

  /* The status row is sticky, so "skip to content" would otherwise land the
     first heading underneath it. */
  scroll-margin-top: calc(var(--statusbar-h) + 10px);
}

.page-head {
  margin-bottom: 20px;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 8px 16px;
}
.page-head > div { min-width: 0; }
.page-head h1 {
  margin: 0 0 4px;
  font-size: 21px;
  font-weight: 650;
  letter-spacing: -0.02em;
  line-height: 1.25;
  color: var(--text-strong);
}
.page-head p {
  margin: 0;
  color: var(--text-dim);
  max-width: 74ch;
}
.page-head .spacer { flex: 1 1 auto; }

/* Headings carry the wordmark's ink. In the light theme that is --brand-ink
   itself, so a heading and the "orchexai" half of the logo are the same
   colour and the page reads as one identity instead of generic grey on grey.
   Tracking is tightened toward the lockup's own (-0.5 at 38px). */
h1, h2 { color: var(--text-strong); }

h1 { font-size: 21px; letter-spacing: -0.02em; }

h2 {
  margin: 0;
  font-size: 15px;
  font-weight: 630;
  letter-spacing: -0.005em;
}

h3 {
  margin: 0 0 8px;
  font-size: 12px;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
}

.footer {
  flex-shrink: 0;
  border-top: 1px solid var(--line-soft);
  padding: 18px var(--gutter) 30px;
  background: var(--bg);
}
.footer-inner {
  max-width: var(--shell);
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
  align-items: baseline;
}
.footer p {
  margin: 0;
  color: var(--text-faint);
  font-size: 12px;
  line-height: 1.55;
  max-width: 88ch;
  flex: 1 1 420px;
}
.footer-meta {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-faint);
  white-space: nowrap;
  letter-spacing: 0.02em;
}


/* ==========================================================================
   4. NAVIGATION  (left sidebar + status row, see templates/_nav.html)
   ==========================================================================

   Two pieces with one contract between them:

     .sidebar     identity, and everywhere you can go
     .statusbar   what state you are in, and who you are

   The split is deliberate. Navigation is stable and belongs at the edge of the
   screen where it can be a fixed target; STATE changes under you and belongs
   at the top of the thing you are reading. It also means the kill-switch
   indicator can stay sticky on a narrow screen after the sidebar has collapsed
   into a strip and scrolled away - the one thing that must never scroll out of
   reach is whether inbound alerts are being turned into real orders.
   ========================================================================== */

/* -- the rail ------------------------------------------------------------ */

.sidebar {
  position: sticky;
  top: 0;
  /* The row is `align-items: flex-start`, so this sets its own height and has
     somewhere to stick to. */
  align-self: flex-start;
  z-index: 25;
  flex: none;
  width: var(--sidebar-w);

  /* vh first for engines without dynamic viewport units, dvh second so a
     mobile browser's collapsing address bar cannot crop the last nav item.
     An engine that does not understand dvh simply drops the second line. */
  height: 100vh;
  height: 100dvh;

  display: flex;
  flex-direction: column;
  padding: 20px var(--sidebar-pad) 18px;
  background: var(--bg-nav);
  border-right: 1px solid var(--line);
  box-shadow: var(--shadow-nav);
}

/* The brand signature: a 2px violet-into-cyan edge down the OUTSIDE of the
   rail, settling into the ordinary border colour a third of the way down - the
   glyph's two signal paths, stood on end. Decoration only: no text sits on it
   and no state is encoded in it, so it is free to show the raw brand hues.

   It is an absolutely positioned child rather than a border-image because the
   sidebar's own box deliberately does not scroll (only .sidebar-nav does),
   which is what keeps a child at `right: -1px` from being clipped. */
.sidebar::after {
  content: "";
  position: absolute;
  top: 0;
  right: -1px;
  width: 2px;
  height: 100%;
  background: var(--brand-edge-v);
  pointer-events: none;
}

/* -- the lockup -----------------------------------------------------------
   Glyph + lowercase wordmark, flat, single weight, one accent, with "trading
   bot" set directly beneath it as the lockup's descriptor line. The wordmark
   is 500 - NOT bold - because the identity's weight comes from the tracking
   and the accent, not from heft. If a page renders the lockup as text rather
   than as the inline SVG, the .brand-* rules below produce the same treatment
   in live type. */

.brand {
  display: block;
  min-width: 0;
  color: var(--text-strong);
  font-weight: 500;
  letter-spacing: -0.015em;   /* the lockup's -0.5 at 38px, in em */
  font-size: 15px;
  flex: none;
}
.brand:hover { text-decoration: none; color: var(--text-strong); }

/* DO NOT put a height on this SVG. The `size` argument in _nav.html is the
   single source of truth for how large the lockup renders (36 in the sidebar,
   26 in the signed-out bar) and a height here would outrank the SVG's own
   rule, which is exactly the bug that pinned every call site to 22px. Width is
   capped only so a narrow rail can never overflow. */
.brand .oa-logo { max-width: 100%; }

/* No horizontal padding: the lockup is sized to the rail's inner width and
   needs every pixel of it, or `max-width: 100%` quietly scales the wordmark
   down and the "make the logo bigger" goes away again. */
.sidebar-brand {
  flex: none;
  padding: 0 0 18px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--line-soft);
}

.brand-sub {
  display: block;
  margin-top: 8px;

  /* Indented to the WORDMARK's left edge, not the glyph's: the wordmark starts
     at x=50 in the logo's 470x80 box, i.e. 0.625 x its rendered height. That
     alignment is what makes this read as part of the lockup rather than as a
     caption somebody dropped underneath it. */
  padding-left: calc(var(--logo-h) * 0.625);

  font-size: 13px;
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.01em;   /* the wordmark's own tightness, at 13px */

  /* Lowercase is the identity, so it is enforced here rather than left to
     whatever string the template passes. The DOM text stays "Trading Bot",
     which is what a screen reader announces. */
  text-transform: lowercase;

  /* Chosen over violet-at-low-opacity: the wordmark already spends both brand
     tokens - ink on "orchexai", violet on "arena" - and a third brand colour
     underneath would give the eye three signals to rank. The ink at reduced
     emphasis reads as one lockup with a descriptor under it, and it clears AA
     comfortably on both rails (7.7:1 dark, 7.7:1 light). */
  color: var(--text-dim);
}

/* Fallback mark for a text-only lockup: the hub of the glyph, a filled dot
   with its halo - a circle, never a boxed icon tile. */
.brand-mark {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  flex: none;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.brand-text {
  overflow: hidden;
  text-overflow: ellipsis;
  text-transform: lowercase;
}

/* The second word of the wordmark - "arena" - in the accent. */
.brand-accent { color: var(--accent); }

/* -- the nav list ---------------------------------------------------------
   The rail itself does not scroll; this does, and only when a short viewport
   makes it. That keeps .sidebar::after (the brand edge) out of an overflow
   clip and keeps the lockup pinned while the list moves. */

.sidebar-nav {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

.sidenav-title {
  margin: 0 0 9px;
  padding: 0 13px;   /* on the nav items' own left padding */
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--text-faint);   /* 5.5:1 on the rail */
}

.sidenav-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.sidenav-link {
  position: relative;
  display: flex;
  align-items: center;
  gap: 11px;
  /* 13px of left padding on every state, active or not, so the 3px rail below
     drops into space that already existed and nothing shifts on navigation. */
  padding: 9px 12px 9px 13px;
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  font-size: 13.5px;
  font-weight: 540;
  letter-spacing: -0.005em;
  line-height: 1.25;
  transition: background var(--speed) var(--ease),
              color var(--speed) var(--ease),
              box-shadow var(--speed) var(--ease);
}
.sidenav-link:hover {
  background: var(--bg-hover);
  color: var(--text);
  text-decoration: none;
}

.sidenav-label { min-width: 0; overflow-wrap: anywhere; }

.sidenav-ico {
  flex: none;
  width: 18px;
  height: 18px;
  color: var(--text-faint);
  transition: color var(--speed) var(--ease);
}
.sidenav-link:hover .sidenav-ico { color: var(--text-dim); }

/* THE ACTIVE PAGE, marked four ways so it never depends on hue: the ARIA
   state itself, a raised surface, a heavier weight, and the violet rail. The
   attribute is the styling hook on purpose - the look and the accessible state
   cannot drift apart if one selector drives both. */
.sidenav-link[aria-current="page"] {
  background: var(--accent-soft);
  color: var(--text-strong);
  font-weight: 640;
  box-shadow: inset 0 0 0 1px var(--accent-line), var(--shadow-1);
}
.sidenav-link[aria-current="page"] .sidenav-ico { color: var(--accent); }
/* The generic :hover above is the same specificity and loses to the rule
   above, so the current item needs its own or it would go dead under the
   pointer. One step deeper into the accent, not a different colour. */
.sidenav-link[aria-current="page"]:hover {
  background: color-mix(in srgb, var(--accent) 24%, transparent);
}
.sidenav-link[aria-current="page"]::before {
  content: "";
  position: absolute;
  left: 0;
  top: 5px;
  bottom: 5px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--brand-rail);
}

/* The rail lives inside an `overflow-y: auto` box, so the global focus ring -
   2px OUTSIDE the control plus a 3px halo - would be clipped at the list's
   edges. Drawn inside instead: same colour, same 2px, guaranteed visible.
   Cyan on the rail is 9.6:1 dark / 5.3:1 light, well past the 3:1 floor. */
.sidenav-link:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -2px;
  box-shadow: inset 0 0 0 4px var(--focus-soft);
}

/* Hand the palette to the OS rather than fight it. */
@media (forced-colors: active) {
  .sidenav-link[aria-current="page"] {
    outline: 2px solid Highlight;
    outline-offset: -2px;
  }
  .sidenav-link[aria-current="page"]::before { background: Highlight; }
}

/* -- the status row -------------------------------------------------------
   Top of the CONTENT column, right-aligned, ONE row. It shares --gutter and
   --shell with .page, so its right edge lands exactly on the right edge of the
   cards below it - the alignment the old wrapped top bar never had. */

.statusbar {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--bg);
  border-bottom: 1px solid var(--line-soft);
}

/* Frosted where the engine can do it cheaply: a card scrolling underneath
   stays faintly present, which reads as depth for free. Both features are
   guarded, and the flat --bg above is what stands if either is missing. */
@supports ((backdrop-filter: blur(4px)) or (-webkit-backdrop-filter: blur(4px)))
      and (background: color-mix(in srgb, red, blue)) {
  .statusbar {
    background: color-mix(in srgb, var(--bg) 82%, transparent);
    -webkit-backdrop-filter: blur(12px) saturate(140%);
    backdrop-filter: blur(12px) saturate(140%);
  }
}

/* Signed out there is no sidebar to carry the brand edge, so the bar carries
   it instead - the same hairline, laid flat. */
.shell-anon .statusbar { border-bottom-color: var(--line); }
.shell-anon .statusbar::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  background: var(--brand-edge);
  pointer-events: none;
}

.statusbar-inner {
  width: 100%;
  max-width: var(--shell);
  margin: 0 auto;
  padding: 0 var(--gutter);
  min-height: var(--statusbar-h);
  display: flex;
  align-items: center;
  gap: 10px;
}
.statusbar .spacer { flex: 1 1 auto; min-width: 12px; }

.statusbar-brand {
  flex: none;
  min-width: 0;
  display: flex;
  align-items: center;
}

/* Every child is fixed at --chip-h and vertically centred by the flex line, so
   the badge, the lamp, the email and the button share ONE optical baseline.
   Nothing wraps: the email is the single shrinkable item and it truncates. */
.statusbar-right {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 1 auto;
  min-width: 0;
}

.statusbar-form { margin: 0; display: flex; flex: none; }
.statusbar-right .btn { flex: none; height: var(--chip-h); padding-top: 0; padding-bottom: 0; }

.statusbar-sep {
  flex: none;
  width: 1px;
  height: 18px;
  background: var(--line);
}

/* The email is the flexible member of the row. It shrinks and ellipses rather
   than pushing the row past the gutter - which is what used to force the whole
   thing onto a second line. */
.who {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 24ch;
  height: var(--chip-h);
  line-height: var(--chip-h);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-dim);       /* 8.0:1 on the page */
  font-size: 12.5px;
}

/* -- kill-switch indicator ------------------------------------------------
   Always visible in the status row on every authenticated page. Distinct class
   names (`kswitch-*`) so it never collides with the big `.ks` panel that
   templates/_console.html injects on the dashboard and settings screens.

   Colour is backed up by a glyph and by the word itself: LIVE / HALTED. */

.kswitch {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: none;
  height: var(--chip-h);
  padding: 0 12px 0 9px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: var(--bg-sunken);
  color: var(--text-dim);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: filter var(--speed) var(--ease),
              border-color var(--speed) var(--ease);
}
.kswitch:hover { text-decoration: none; filter: brightness(1.12); }

/* The lamp differs in SHAPE, not only hue: armed is a filled dot with a glow,
   halted is a hollow ring. Readable in greyscale and in peripheral vision. */
.kswitch-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: none;
  box-sizing: border-box;
}

.kswitch-live {
  color: var(--ok);
  border-color: color-mix(in srgb, var(--ok) 45%, transparent);
  background: var(--ok-soft);
}
.kswitch-live .kswitch-dot {
  background: var(--ok);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 30%, transparent);
}

.kswitch-halted {
  color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 45%, transparent);
  background: var(--warn-soft);
}
.kswitch-halted .kswitch-dot {
  background: transparent;
  border: 2px solid var(--warn);
}

/* Host chip: testnet vs real money. Unknown/live hosts read as danger. */
.envchip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: none;
  height: var(--chip-h);
  padding: 0 11px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: var(--bg-sunken);
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 660;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: filter var(--speed) var(--ease),
              border-color var(--speed) var(--ease);
}
.envchip:hover { text-decoration: none; filter: brightness(1.1); }
.envchip-live {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
  background: var(--danger-soft);
}
.envchip-test {
  color: var(--text-dim);
}
.envchip-none {
  color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 45%, transparent);
  background: var(--warn-soft);
}


/* ==========================================================================
   5. CARDS
   ========================================================================== */

/* The drop shadow gives the card its footprint; the 1px --sheen along the top
   edge gives it a lit face. On a near-black page that pairing is what reads as
   "raised" - a border alone reads as "drawn". The light theme sets --sheen to
   nothing, because white paper does not glow. */
.card {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-1), inset 0 1px 0 var(--sheen);
  transition: border-color var(--speed) var(--ease),
              box-shadow var(--speed) var(--ease);
}

.card-head {
  padding: 12px 16px;
  border-bottom: 1px solid var(--line-soft);
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.card-head .spacer { flex: 1 1 auto; }

.card-body { padding: 16px; }
.card-body > *:first-child { margin-top: 0; }
.card-body > *:last-child { margin-bottom: 0; }

.card-foot {
  padding: 12px 16px;
  border-top: 1px solid var(--line-soft);
  background: var(--bg-sunken);
}

/* Status accent down the left edge. 3px so it is legible at a glance without
   shifting the content grid. */
.card-ok    { border-left: 3px solid var(--ok); }
.card-warn  { border-left: 3px solid var(--warn); }
.card-alert { border-left: 3px solid var(--danger); }
.card-info  { border-left: 3px solid var(--accent); }

/* A whole card that frames a destructive action. */
.card-danger { border-color: color-mix(in srgb, var(--danger) 45%, var(--line)); }
.card-danger .card-head { background: var(--danger-soft); }


/* ==========================================================================
   6. KEY / VALUE LISTS
   ========================================================================== */

.kv {
  display: grid;
  grid-template-columns: minmax(120px, 190px) 1fr;
  gap: 9px 16px;
  margin: 0;
}
.kv dt {
  color: var(--text-dim);
  font-size: 12.5px;
  padding-top: 1px;
}
.kv dd {
  margin: 0;
  word-break: break-word;
  min-width: 0;
}

@media (max-width: 560px) {
  .kv { grid-template-columns: 1fr; gap: 2px 0; }
  .kv dt { font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.05em; }
  .kv dd { margin-bottom: 10px; }
  .kv dd:last-child { margin-bottom: 0; }
}


/* ==========================================================================
   7. BADGES
   ========================================================================== */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  font-size: 11.5px;
  font-weight: 620;
  letter-spacing: 0.02em;
  line-height: 1.6;
  white-space: nowrap;
  vertical-align: 1px;

  /* A badge is a COMPONENT WITH ITS OWN SURFACE, not a wash, so it gets an
     opaque base and the tint is layered on top of that base rather than on
     whatever happens to be behind it. See the note on the modifiers below -
     this line is the fix for a real, measured AA failure. */
  background-color: var(--bg-raised);
}

/* WHY THESE ARE `linear-gradient(tint, tint) <opaque base>` AND NOT `tint`
   -----------------------------------------------------------------------
   A translucent fill composites against whatever is underneath it, and in this
   app something of the same hue often IS underneath it. `tr.row-bad td` paints
   --danger-soft across a failed order's row, and the FAILED badge inside that
   row then painted --danger-soft again. Two 0.14 layers of red are not one:

     dark   one layer #33172B = 4.83:1     stacked #4F1F2F = 3.99:1   FAIL
     light  one layer #FAEBEC = 4.95:1     stacked #F5D9DB = 4.31:1   FAIL

   Measured off the rendered screenshot, not modelled: the FAILED badge on
   /orders came out at 4.07:1 dark and 4.28:1 light. The audit trail's single
   most important word was the least legible thing on the page.

   `background: <image> <color>` paints the tint as a one-stop gradient over an
   OPAQUE base, so the badge composites against a known surface and the stack
   is arithmetically impossible - on a flagged row, on a hovered row, inside a
   tinted notice, anywhere. It also stabilises the translucent border, which is
   drawn over the element's own background box.

   The row wash still marks the row; it just no longer double-dips. Note this
   cannot be fixed at `tr.row-bad` instead: templates/_console.html re-declares
   that rule in an inline <style> that loads after this file. */
.badge-ok {
  background: linear-gradient(var(--ok-soft), var(--ok-soft)) var(--bg-raised);
  color: var(--ok);
  border-color: color-mix(in srgb, var(--ok) 35%, transparent);
}
.badge-warn {
  background: linear-gradient(var(--warn-soft), var(--warn-soft)) var(--bg-raised);
  color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 35%, transparent);
}
.badge-danger {
  background: linear-gradient(var(--danger-soft), var(--danger-soft)) var(--bg-raised);
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 35%, transparent);
}
.badge-info {
  background: linear-gradient(var(--accent-soft), var(--accent-soft)) var(--bg-raised);
  color: var(--accent);
  border-color: var(--accent-line);
}
.badge-neutral {
  background: var(--line-soft);
  color: var(--text-dim);
  border-color: var(--line);
}


/* ==========================================================================
   8. NOTICES / FLASH / RIBBONS
   ========================================================================== */

.flash, .notice {
  border: 1px solid var(--line);
  border-left-width: 3px;
  border-radius: var(--radius-sm);
  padding: 11px 14px;
  margin-bottom: 16px;
  background: var(--bg-raised);
}
.notice > *:first-child { margin-top: 0; }
.notice > *:last-child  { margin-bottom: 0; }

.flash-ok,    .notice-ok     { border-left-color: var(--ok);     background: var(--ok-soft); }
.flash-error, .notice-danger { border-left-color: var(--danger); background: var(--danger-soft); }
.flash-warn,  .notice-warn   { border-left-color: var(--warn);   background: var(--warn-soft); }
.flash-info,  .notice-info   { border-left-color: var(--accent); background: var(--accent-soft); }

.notice-title { margin: 0 0 5px; font-weight: 650; }

/* Flash messages carry a glyph so "saved" and "failed" differ without colour. */
.flash { display: flex; gap: 9px; align-items: flex-start; }
.flash::before {
  flex: none;
  font-weight: 700;
  line-height: 1.5;
}
.flash-ok::before    { content: "\2713"; color: var(--ok); }      /* check   */
.flash-error::before { content: "\2715"; color: var(--danger); }  /* cross   */
.flash-warn::before  { content: "\26A0"; color: var(--warn); }    /* warning */
.flash-info::before  { content: "\2139"; color: var(--accent); }  /* info    */

/* Full-bleed strip under the status row. Used for the dev-keys warning. */
.ribbon {
  padding: 9px var(--gutter);
  font-size: 12.5px;
  line-height: 1.5;
  text-align: center;
  border-bottom: 1px solid var(--line);
}
.ribbon-danger {
  background: var(--danger-soft);
  color: var(--text);
  border-bottom-color: color-mix(in srgb, var(--danger) 40%, transparent);
}
.ribbon-warn {
  background: var(--warn-soft);
  color: var(--text);
  border-bottom-color: color-mix(in srgb, var(--warn) 40%, transparent);
}

.steps { margin: 10px 0 0; padding-left: 20px; }
.steps li { margin-bottom: 4px; }
.steps li:last-child { margin-bottom: 0; }


/* ==========================================================================
   9. FORMS
   ========================================================================== */

.field { margin-bottom: 14px; }
.field:last-child { margin-bottom: 0; }

label {
  display: block;
  margin-bottom: 5px;
  font-size: 13px;
  font-weight: 570;
}

.hint {
  margin: 5px 0 0;
  color: var(--text-faint);
  font-size: 12px;
  line-height: 1.45;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="search"],
input[type="url"],
select,
textarea {
  width: 100%;
  background: var(--bg-input);
  /* --line-strong, not --line: an empty field's border is the only thing that
     says a field is there, so it is a UI component boundary at 3:1, not a
     decorative hairline. At --line this measured 1.52:1 against the field's own
     fill on dark and 1.67:1 on paper. See the token's note in section 1. */
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  color: var(--text);
  padding: 8px 10px;
  font: inherit;
  font-size: 13.5px;
}
input::placeholder, textarea::placeholder { color: var(--text-faint); }

input:hover:not([readonly]):not(:disabled),
select:hover:not(:disabled),
textarea:hover:not([readonly]) { border-color: var(--text-faint); }

/* Secrets and identifiers are read character by character - never proportional. */
input[type="password"],
input[type="number"],
.field-mono input,
.field-mono select,
.field-mono textarea {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}

input[readonly] {
  color: var(--text-dim);
  background: var(--bg-sunken);
  cursor: text;
}
input:disabled, select:disabled, textarea:disabled, button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

textarea { min-height: 90px; resize: vertical; }

select {
  appearance: none;
  padding-right: 30px;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--text-dim) 50%),
    linear-gradient(135deg, var(--text-dim) 50%, transparent 50%);
  background-position: calc(100% - 17px) 52%, calc(100% - 12px) 52%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

.check {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  margin-bottom: 0;
  font-weight: 400;
  cursor: pointer;
}
.check input[type="checkbox"],
.check input[type="radio"] {
  flex: none;
  margin: 2px 0 0;
  width: 15px;
  height: 15px;
  accent-color: var(--accent);
}

fieldset {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  margin: 0 0 14px;
}
legend { padding: 0 6px; font-size: 12px; font-weight: 650; color: var(--text-dim); }

.field-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px;
}


/* ==========================================================================
   10. BUTTONS
   --------------------------------------------------------------------------
   Three intents, and they differ in more than hue:

     .btn            filled accent   - the safe, expected action
     .btn-ghost      outlined        - secondary / navigational
     .btn-danger     filled red      - irreversible, or it moves money
     .btn-danger-ghost outlined red  - irreversible but reversible-ish

   A destructive button is filled and heavier than the ghost next to it, so it
   is distinguishable in greyscale and at a glance in peripheral vision.
   ========================================================================== */

button, .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: var(--accent);
  color: var(--accent-text);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: 8px 14px;
  font: inherit;
  font-size: 13px;
  font-weight: 570;
  line-height: 1.3;
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  transition: filter var(--speed) var(--ease),
              background var(--speed) var(--ease),
              border-color var(--speed) var(--ease),
              transform var(--speed) var(--ease);
}
button:hover, .btn:hover { filter: brightness(1.12); text-decoration: none; }
button:active, .btn:active { filter: brightness(0.93); transform: translateY(1px); }

.btn-sm  { padding: 5px 10px; font-size: 12.5px; }
.btn-big { padding: 11px 20px; font-size: 14px; font-weight: 640; }
.btn-block { width: 100%; }

/* Outlined, so the outline IS the button - same 3:1 argument as the inputs. */
.btn-ghost {
  background: transparent;
  color: var(--text);
  border-color: var(--line-strong);
}
.btn-ghost:hover { background: var(--bg-hover); filter: none; }

/* `--on-solid` and the brand ink replace the hard-coded #fff / #000 these two
   used to carry. That is what makes the palette swappable: on a dark page the
   danger fill is bright and takes a near-black label (6.0:1); on paper it is
   deep and takes white (5.7:1). Neither theme has to special-case a rule. */
.btn-danger {
  background: var(--danger);
  color: var(--on-solid);
  font-weight: 640;
  border-color: color-mix(in srgb, var(--danger) 70%, var(--brand-ink));
}

.btn-danger-ghost {
  background: transparent;
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 45%, var(--line-strong));
  font-weight: 620;
}
.btn-danger-ghost:hover { background: var(--danger-soft); filter: none; }

.btn-ok {
  background: var(--ok);
  color: var(--on-solid);
  font-weight: 640;
  border-color: color-mix(in srgb, var(--ok) 70%, var(--brand-ink));
}


/* ==========================================================================
   11. COPY FIELDS + CODE
   ========================================================================== */

.copyfield {
  display: flex;
  gap: 8px;
  align-items: stretch;
}
.copyfield input {
  font-family: var(--mono);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text);
  background: var(--bg-sunken);
}
.copyfield button { flex: none; }
.copy-done { align-self: center; color: var(--ok); font-size: 12.5px; font-weight: 600; }

.codeblock {
  margin: 0;
  padding: 11px 13px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow-x: auto;
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.5;
  white-space: pre;
  color: var(--text);
}
.codeblock-sm { font-size: 11.5px; padding: 8px 10px; }
.payload {
  max-height: 260px;
  overflow: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.secret-slot {
  font-family: var(--mono);
  letter-spacing: 0.14em;
  color: var(--text-faint);
}

details.disclose > summary {
  cursor: pointer;
  padding: 6px 0;
  font-weight: 570;
  color: var(--text-dim);
}
details.disclose > summary:hover { color: var(--text); }
details.disclose[open] > summary { margin-bottom: 8px; }


/* ==========================================================================
   12. TABLES
   --------------------------------------------------------------------------
   The order and signal logs are the audit trail. They must be dense, scannable
   and honest: numbers right-aligned and tabular, failures marked with a red
   left edge AND a badge, never with a row tint alone.
   ========================================================================== */

.table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

table.data {
  width: 100%;
  min-width: 340px;
  border-collapse: collapse;
  font-size: 13px;
}
table.data th {
  padding: 8px 12px;
  text-align: left;
  font-size: 11px;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  white-space: nowrap;
  border-bottom: 1px solid var(--line);
  background: var(--bg-raised);
}
table.data td {
  padding: 8px 12px;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: top;
}
table.data tbody tr:last-child td { border-bottom: none; }
table.data tbody tr:hover td { background: var(--bg-hover); }

/* Numeric cells: right-aligned, monospaced, tabular. Digits must stack. */
table.data th.num, table.data td.num, .num {
  text-align: right;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  white-space: nowrap;
}

/* A failed row. Tint + inset red edge + (in the markup) a danger badge. */
tr.row-bad td { background: var(--danger-soft); }
tr.row-bad td:first-child { box-shadow: inset 3px 0 0 var(--danger); }
tr.row-bad:hover td { background: color-mix(in srgb, var(--danger) 18%, transparent); }

.empty {
  padding: 34px 16px;
  text-align: center;
  color: var(--text-dim);
}
.empty p { margin: 0 auto 10px; max-width: 56ch; }
.empty p:last-child { margin-bottom: 0; }

.stamp {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  color: var(--text-faint);
  white-space: nowrap;
}

.err-text {
  color: var(--danger);
  font-size: 12.5px;
  overflow-wrap: anywhere;
}


/* ==========================================================================
   13. MARKET SEMANTICS  (never colour alone)
   --------------------------------------------------------------------------
   `.pnl-pos` / `.pnl-neg` and `.side-long` / `.side-short` are applied by the
   dashboard, positions and orders templates. Each gets a directional glyph in
   front of it, so the meaning survives greyscale printing, a colour-blind
   reader and a low-quality screen. The colour is a bonus, not the signal.
   ========================================================================== */

/* Tabular figures here too, not only in `.num`: unrealised P&L ticks several
   times a second, and with proportional digits the number would visibly
   breathe on every update while the trader is trying to read it. */
.pnl-pos, .pnl-neg {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
.pnl-pos { color: var(--ok); }
.pnl-neg { color: var(--danger); }
.pnl-pos::before { content: "\25B2\00A0"; font-size: 0.78em; }   /* up   */
.pnl-neg::before { content: "\25BC\00A0"; font-size: 0.78em; }   /* down */

.side-long, .side-short { font-weight: 640; white-space: nowrap; }
.side-long  { color: var(--ok); }
.side-short { color: var(--danger); }
.side-long::before  { content: "\25B2\00A0"; font-size: 0.78em; }
.side-short::before { content: "\25BC\00A0"; font-size: 0.78em; }

.t-ok     { color: var(--ok); }
.t-warn   { color: var(--warn); }
.t-danger { color: var(--danger); }


/* ==========================================================================
   14. CONSOLE WIDGETS
   --------------------------------------------------------------------------
   templates/_console.html injects its own copy of these rules in a <style>
   block for the four trading screens. They are repeated here so any page that
   uses a tile, a tab strip or the kill-switch panel is styled correctly from
   the base stylesheet alone. The inline block loads after this file, so it
   still wins on ties - nothing regresses.
   ========================================================================== */

/* -- stat tiles ---------------------------------------------------------- */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: 12px;
}
.tile {
  min-width: 0;
  padding: 12px 14px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-left-width: 3px;
  border-radius: var(--radius);
  box-shadow: var(--shadow-1), inset 0 1px 0 var(--sheen);
}
.tile-label {
  margin: 0 0 6px;
  font-size: 11px;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
}
.tile-value {
  font-size: 19px;
  font-weight: 650;
  line-height: 1.2;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  color: var(--text-strong);
  overflow-wrap: anywhere;
}
.tile-sub { margin: 5px 0 0; font-size: 12px; color: var(--text-dim); }
.tile-ok     { border-left-color: var(--ok); }
.tile-warn   { border-left-color: var(--warn); }
.tile-danger { border-left-color: var(--danger); }
.tile-info   { border-left-color: var(--accent); }
.tile-muted  { border-left-color: var(--line); }

/* -- kill-switch panel --------------------------------------------------- */
.ks {
  display: flex;
  gap: 16px;
  align-items: center;
  flex-wrap: wrap;
  padding: 16px;
  border: 2px solid var(--line);
  border-radius: var(--radius);
}
.ks-on  { border-color: var(--ok);   background: var(--ok-soft); }
.ks-off { border-color: var(--warn); background: var(--warn-soft); }
.ks-state { display: flex; align-items: center; gap: 11px; min-width: 0; }
.ks-lamp {
  flex: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--warn);
  box-shadow: 0 0 0 4px var(--warn-soft);
}
.ks-on .ks-lamp { background: var(--ok); box-shadow: 0 0 0 4px var(--ok-soft); }
.ks-word {
  font-size: 21px;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: 0.04em;
}
.ks-on  .ks-word { color: var(--ok); }
.ks-off .ks-word { color: var(--warn); }
.ks-note { margin: 3px 0 0; font-size: 12.5px; color: var(--text-dim); }
.ks form { margin-left: auto; }
.ks .btn-big { padding: 11px 20px; font-size: 14px; font-weight: 640; }

/* -- setup checklist ----------------------------------------------------- */
.checklist { list-style: none; margin: 0; padding: 0; }
.checklist li {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 12px 0;
  border-bottom: 1px solid var(--line-soft);
}
.checklist li:last-child { border-bottom: none; }
.step-mark {
  flex: none;
  width: 22px;
  height: 22px;
  margin-top: 1px;
  border-radius: 50%;
  border: 1px solid var(--line);
  color: var(--text-faint);
  font-size: 11.5px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}
.step-done .step-mark { border-color: var(--ok); background: var(--ok-soft); color: var(--ok); }
.step-next .step-mark { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.step-body { flex: 1 1 auto; min-width: 0; }
.step-title { font-weight: 600; }
.step-done .step-title { color: var(--text-dim); }
.step-detail { margin: 2px 0 0; font-size: 12.5px; color: var(--text-dim); }
.step-action { flex: none; align-self: center; }

/* -- tabs ---------------------------------------------------------------- */
.tabs {
  display: flex;
  gap: 4px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tabs a {
  padding: 8px 14px;
  margin-bottom: -1px;
  color: var(--text-dim);
  border-bottom: 2px solid transparent;
  font-weight: 570;
  white-space: nowrap;
  transition: color var(--speed) var(--ease),
              border-color var(--speed) var(--ease);
}
.tabs a:hover { color: var(--text); text-decoration: none; }
.tabs a.is-active { color: var(--text); border-bottom-color: var(--accent); }

/* -- pager --------------------------------------------------------------- */
.pager {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 12.5px;
  color: var(--text-dim);
}
.pager .spacer { flex: 1 1 auto; }


/* ==========================================================================
   15. UTILITIES
   ========================================================================== */

.stack > * + * { margin-top: 16px; }
.stack-sm > * + * { margin-top: 8px; }

.cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 16px;
  align-items: start;
}

.split {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

.row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
}
.row-end { justify-content: flex-end; }
.spacer { flex: 1 1 auto; }

.inline  { display: inline; }
.muted   { color: var(--text-dim); }
.faint   { color: var(--text-faint); }
.small   { font-size: 12.5px; }
.nowrap  { white-space: nowrap; }
.mono    { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.break   { overflow-wrap: anywhere; word-break: break-word; }
.mt-0    { margin-top: 0; }
.mb-0    { margin-bottom: 0; }
.hidden  { display: none !important; }

.sep {
  border: 0;
  border-top: 1px solid var(--line-soft);
  margin: 18px 0;
}

/* Visible only to screen readers. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


/* ==========================================================================
   16. ERROR PAGE  (rendered from webapp/main.py)
   ========================================================================== */

.errpage {
  max-width: 620px;
  margin: 6vh auto 0;
  text-align: center;
}
.errpage-code {
  font-family: var(--mono);
  font-size: 58px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  color: var(--text-faint);
}
.errpage h1 { margin: 14px 0 8px; font-size: 20px; font-weight: 640; }
.errpage p { margin: 0 auto 14px; color: var(--text-dim); max-width: 56ch; }
.errpage .row { justify-content: center; }
.errpage .codeblock { text-align: left; margin-top: 18px; white-space: pre-wrap; }


/* ==========================================================================
   17. RESPONSIVE / MOTION / PRINT
   ========================================================================== */

/* Laptop. A 1280px screen still has ~1000px of content next to a 256px rail,
   which is comfortable, so the rail is only trimmed - not removed - here. This
   console is used on laptops and must not become unusable at 1280 or below. */
@media (max-width: 1180px) {
  :root {
    /* 236 less 32px of padding leaves 204px of inner width. The 36px lockup
       wants 211.5, so `max-width: 100%` scales it to 204 x 34.7 - still inside
       the 34-40px band the sidebar lockup is meant to hold. */
    --sidebar-w:   236px;
    --sidebar-pad: 16px;
    --gutter:      20px;
  }
  .sidenav-link { padding: 8px 10px 8px 12px; font-size: 13px; }
  .sidenav-link[aria-current="page"]::before { top: 4px; bottom: 4px; }
}

/* Tablet: the two-column split becomes one column before it gets cramped. */
@media (max-width: 860px) {
  .split { grid-template-columns: 1fr; }
}

/* -------------------------------------------------------------------------
   Below 900px the rail would start crushing the content, so it UNFOLDS into a
   full-width strip at the top of the page: lockup on the first line, the same
   eight items as a horizontal scroll list on the second. Same markup, same
   order, same aria-current - only the flex axis changes, so this needs no JS,
   no toggle and no state to get out of sync.

   The strip scrolls away with the page and the STATUS ROW stays sticky in its
   place. That is the deliberate trade: on a small screen the thing that must
   remain pinned is not the navigation, it is whether trading is armed.
   ------------------------------------------------------------------------- */
@media (max-width: 900px) {
  .shell {
    flex-direction: column;
    align-items: stretch;
  }

  .sidebar {
    /* relative, NOT static: ::after below is absolutely positioned and would
       otherwise fall back to the initial containing block and paint a 2px
       brand line across the bottom of the viewport. */
    position: relative;
    width: 100%;
    height: auto;
    z-index: auto;
    padding: 12px var(--gutter) 0;
    border-right: 0;
    border-bottom: 1px solid var(--line);
    box-shadow: none;
  }
  /* The same brand hairline, laid flat along the bottom of the strip. */
  .sidebar::after {
    top: auto;
    bottom: -1px;
    left: 0;
    right: 0;
    width: auto;
    height: 2px;
    background: var(--brand-edge);
  }

  .sidebar-brand {
    padding: 0 0 10px;
    margin-bottom: 0;
    border-bottom: 0;
  }
  .brand-sub { margin-top: 5px; font-size: 12px; }

  .sidebar-nav {
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .sidebar-nav::-webkit-scrollbar { display: none; }

  /* `<nav aria-label="Main">` already names the region, so the visible group
     heading is redundant once the list is a strip. */
  .sidenav-title { display: none; }

  .sidenav-list {
    flex-direction: row;
    gap: 4px;
    padding-bottom: 2px;
  }
  .sidenav-link {
    padding: 8px 12px;
    white-space: nowrap;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  }
  /* The rail rotates with the axis: an underline, not a left edge. */
  .sidenav-link[aria-current="page"]::before {
    left: 8px;
    right: 8px;
    top: auto;
    bottom: 0;
    width: auto;
    height: 3px;
    border-radius: 3px 3px 0 0;
  }

  .shell-main { min-height: 0; }
}

/* Phone. */
@media (max-width: 720px) {
  :root {
    --gutter: 15px;
    --statusbar-h: 50px;
  }

  body { font-size: 14px; }

  /* Let the chips wrap among themselves rather than pushing the row wider than
     the viewport. The kill-switch state must never be the thing that clips. */
  .statusbar-inner {
    flex-wrap: wrap;
    gap: 8px;
    padding-top: 8px;
    padding-bottom: 8px;
  }
  .statusbar-right {
    gap: 8px;
    flex: 0 1 auto;
    min-width: 0;
    flex-wrap: wrap;
    justify-content: flex-end;
  }
  .who, .statusbar-sep { display: none; }   /* the email is on /account anyway */

  .page { padding: 16px var(--gutter) 48px; }
  .card-body, .card-head, .card-foot { padding-left: 13px; padding-right: 13px; }

  .page-head h1 { font-size: 19px; }

  .copyfield { flex-wrap: wrap; }
  .copyfield input { flex: 1 1 100%; font-size: 14px; }

  .ks form { margin-left: 0; width: 100%; }
  .ks .btn-big { width: 100%; }

  .footer-inner { flex-direction: column; }
}

@media (max-width: 420px) {
  :root {
    /* _logo.html clamps itself to 176px wide here, which is 30px tall - keep
       --logo-h in step or "trading bot" indents past the wordmark's edge. */
    --logo-h: 30px;
  }
  .brand-text { display: none; }   /* the text-lockup fallback only */
  .tiles { grid-template-columns: 1fr 1fr; }
  .errpage-code { font-size: 44px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  button:active, .btn:active { transform: none; }
}

/* Print is a third theme, so it is expressed the same way as the other two -
   by overriding the tokens, not by re-styling components. Ink on paper: no
   surface tints to waste toner, borders dark enough to survive a mono laser,
   and the semantic colours kept (they still print as distinguishable greys,
   and the P&L glyphs carry the meaning regardless). */
@media print {
  :root {
    --bg:        #FFFFFF;
    /* No decorative wash on paper - it is pure toner for zero information. */
    --page-glow-a: transparent;
    --page-glow-b: transparent;
    --bg-raised: #FFFFFF;
    --bg-sunken: #FFFFFF;
    --bg-input:  #FFFFFF;
    --bg-hover:  #FFFFFF;
    --line:      #949494;
    --line-soft: #C7C7C7;
    /* On paper a field's boundary has to survive a mono laser, so it goes to
       near-ink rather than to the toner-saving structural grey. */
    --line-strong: #4A4A4A;

    --text:        #000000;
    --text-strong: #000000;
    --text-dim:    #333333;
    --text-faint:  #444444;

    --accent:      #4B189B;   /* the brand violet, darkened for toner */
    --accent-text: #FFFFFF;
    --accent-soft: transparent;
    --accent-line: #949494;

    --ok:     #0B5A2A;
    --warn:   #6B4800;
    --danger: #9B1620;
    --on-solid: #FFFFFF;

    --shadow-1: none;
    --shadow-2: none;
    --shadow-nav: none;
    --sheen: transparent;
  }

  .sidebar, .statusbar, .footer, .skip-link, .ribbon, .pager, form { display: none !important; }
  body { background: var(--bg); color: var(--text); }
  /* With the rail gone the shell is a single column again. */
  .shell { display: block; }
  .shell-main { display: block; min-height: 0; }
  .card, .tile { border: 1px solid var(--line); box-shadow: none; break-inside: avoid; }
  .page { max-width: none; padding: 0; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 10px; }
}
