/* ═══ Pulse trading terminal ═══ */
/* Amber/flame accent on a blue-black ground. Two square corners, two round. */

:root {
  --bg: #0c0c11;
  --panel: #15151c;
  --panel-light: #1d1d26;
  --border: #272733;
  --border-light: #383846;
  --green: #22d3a0;
  --red: #ff5c7a;
  --yellow: #ffb020;
  --text: #eceaf2;
  --muted: #8b8b9c;

  /* accent system — amber into flame, used for anything actionable */
  --amber: #ffb020;
  --flame: #ff7a45;
  --violet: #a78bfa;
  --ice: #5fd8f5;
  --dim: #585868;
  --grad: linear-gradient(135deg, #ffb020, #ff7a45);
  /* the house shape: two square corners, two generously rounded */
  --r: 20px 4px 20px 4px;
  --r-sm: 14px 3px 14px 3px;
  --font: 'Inter', -apple-system, sans-serif;
  --mono: 'JetBrains Mono', 'Courier New', monospace;

  /* Layout chrome heights, referenced by the mobile block at the end of this file */
  --topbar-h: 50px;
  --footer-h: 40px;
  --tabbar-h: 56px;
}

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

html, body {
  background: var(--bg);
  background-image:
    radial-gradient(760px 420px at 88% -12%, rgba(255,122,69,.10), transparent 62%),
    radial-gradient(680px 380px at 4% 8%, rgba(167,139,250,.07), transparent 60%);
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--font);
  font-size: 13px;
  line-height: 1.5;
  height: 100%;
  /* Kept deliberately: this clips anything wider than the viewport rather than
     letting the whole page scroll sideways. Removing it would trade silent
     clipping for a page-wide horizontal rubber-band on iOS the moment any one
     of the innerHTML call sites emits something too wide. Overflow sources are
     fixed individually in the mobile block instead. */
  overflow-x: hidden;
  /* Stop iOS rotating portrait->landscape from inflating font sizes */
  -webkit-text-size-adjust: 100%;
}

/* Removes the 300ms double-tap-zoom delay on controls without disabling
   pinch-zoom (which iOS ignores anyway, and which harms accessibility).
   No-op for mouse input. */
button, a, .tab, .tf-pill, .nav-icon, .coin-pill, .toggle-slider {
  touch-action: manipulation;
}

/* ═══ NAV BAR ═══ */
#topbar {
  display: flex;
  align-items: center;
  gap: 16px;
  height: 60px;
  background: rgba(12,12,17,.86);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--border);
  padding: 0 20px;
  position: sticky;
  top: 0;
  z-index: 1000;
}

.logo-link {
  display: flex;
  align-items: center;
  margin-right: 10px;
  text-decoration: none;
}
.logo-img {
  height: 28px;
  width: auto;
}

.nav-links { display: flex; gap: 4px; }
.nav-links a {
  color: var(--muted);
  text-decoration: none;
  padding: 7px 15px;
  font-size: 13px;
  font-weight: 500;
  border-radius: 999px;
  transition: all .16s;
}
.nav-links a:hover { color: var(--text); background: var(--panel-light); }
.nav-links a.active {
  color: #1a1206;
  font-weight: 600;
  background: var(--grad);
}

.nav-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 14px;
}

/* ═══ STATUS CLUSTER ═══
   Heartbeat, state word, mode. No housing: the footer is already a rail, so
   the status joins it rather than sitting in a box on it. The trace scrolls
   while the engine polls and FLATLINES when it stops — no LED to misread.

   The canvas carries no size here on purpose. drawBeat() reads its dimensions
   from data-w/data-h and sets both the backing store and the CSS box, because
   a CSS-shrunk canvas would be redrawn at the old width every frame and come
   out squashed. placeStatusCluster() owns those attributes. */
.vitals {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}
.vitals canvas { display: block; }
.vitals-word {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--green);
  white-space: nowrap;
}
.vitals-word.off { color: var(--dim); }
.vitals-hair { width: 1px; height: 15px; background: var(--border); flex: none; }

/* Mode stays OUTSIDE the status reading — it is not a measurement, it is the
   stakes — but shares the rail. Neutral housing, coloured text: the border
   never competes with the trace, and the word carries the whole signal.
   The bare .modew (no paper/live yet) is deliberately colourless: until the
   backend answers, the badge must not name either mode. */
.modew {
  display: flex;
  align-items: center;
  height: 24px;
  padding: 0 11px;
  border: 1px solid var(--border);
  border-radius: 12px 3px 12px 3px;
  background: rgba(29,29,38,.5);
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--dim);
  white-space: nowrap;
  flex: none;
}
.modew.paper { color: var(--violet); }
.modew.live  { color: var(--green); border-color: rgba(34,211,160,.42); }

.langsw { display: flex; gap: 1px; background: var(--border); border-radius: 999px; overflow: hidden; flex: none; }
.langsw button {
  background: var(--panel-light);
  border: none;
  color: var(--muted);
  font-family: var(--font);
  font-size: 11px;
  font-weight: 600;
  padding: 6px 10px;
  cursor: pointer;
  transition: .15s;
}
.langsw button.on { background: var(--grad); color: #1a1206; }

/* ═══ GATE MONITOR ═══
   No card header. "Gate monitor" named the widget instead of telling you
   anything, and spent a 44px row plus a divider doing it — on the first card of
   the page, where nothing needs a label to be found. The row survives in form
   but is now a READING: a tinted strip stating the state and what is held, which
   recolours with the gate. The card's identity is its condition.

   The three-bar ladder is gone. It sized each bar as `value / max * 100%` from a
   $0 baseline, so three prices within 12% of each other drew as three
   near-identical full bars and the gap that decides everything read as "about the
   same". The rail below replaces it: one axis, true proportions. */
.gate-card { margin-bottom: 10px; }

.gate-strip {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 11px 16px;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .18em;
  text-transform: uppercase;
  background: rgba(255,176,32,.09);
  border-bottom: 1px solid rgba(255,176,32,.22);
  color: var(--amber);
}
.gate-strip.on {
  background: rgba(34,211,160,.09);
  border-bottom-color: rgba(34,211,160,.22);
  color: var(--green);
}
.gate-strip .gate-sep { color: currentColor; opacity: .45; }
/* The as-of has to survive the header's removal: without it a correct daily
   reading is indistinguishable from a frozen page. */
.gate-asof {
  margin-left: auto; letter-spacing: .1em; text-transform: none;
  color: var(--dim); font-weight: 400; white-space: nowrap;
}

.gate-body { display: flex; align-items: flex-start; gap: 26px; padding: 20px 16px 16px; flex-wrap: wrap; }
.gate-headline { flex: none; }
.gate-big {
  font-family: var(--mono); font-weight: 700; font-size: 52px; line-height: .95;
  letter-spacing: -.03em; color: var(--amber); font-variant-numeric: tabular-nums;
}
.gate-big.on { color: var(--green); }
.gate-lb { color: var(--muted); font-size: 13px; margin-top: 9px; }
.gate-col { flex: 1 1 250px; min-width: 0; display: grid; gap: 12px; align-content: start; }
.gate-rail { display: block; width: 100%; }
.gate-why { font-size: 13px; color: var(--muted); line-height: 1.7; }
.gate-why b { color: var(--text); font-weight: 600; }

.gate-pill {
  display: inline-flex; align-items: center; padding: 7px 15px; border-radius: 999px;
  font-size: 12.5px; font-weight: 600;
}
.gate-pill.off { background: var(--grad); color: #1a1206; }
.gate-pill.on { background: rgba(34,211,160,.16); color: var(--green); border: 1px solid rgba(34,211,160,.42); }

.gate-foot {
  padding: 12px 16px; border-top: 1px solid var(--border);
  font-size: 11px; color: var(--dim);
}

@media (max-width: 900px) {
  /* Stacked, with the chart shortened rather than folded away. A <details> would
     have to be opened by JS on desktop, and nothing re-renders this card on
     rotation — so the fold would strand the chart hidden behind a summary that is
     itself hidden at that width. */
  .gate-body { display: block; padding: 16px 16px 6px; }
  .gate-big { font-size: 44px; }
  .gate-col { margin-top: 14px; }
  .gate-chart { height: 130px; }
  .gate-strip { padding: 10px 14px; letter-spacing: .12em; }
  .gate-strip .gate-asof { flex-basis: 100%; margin-left: 0; }
}

/* ═══ DERIVED BASKET ═══ */
.basket-leg {
  display: grid; grid-template-columns: 38px 1fr 90px auto; align-items: center; gap: 12px;
  padding: 12px 13px; background: var(--panel-light); border: 1px solid var(--border);
  border-radius: var(--r-sm); margin-bottom: 8px;
}
.basket-sym {
  width: 38px; height: 38px; border-radius: 13px 3px 13px 3px; display: grid; place-items: center;
  background: var(--grad); color: #1a1206; font-weight: 700; font-size: 11.5px;
}
.basket-meta { display: flex; flex-direction: column; gap: 2px; }
.basket-meta b { font-size: 13px; font-weight: 600; }
.basket-meta span { font-size: 11.5px; color: var(--muted); }
.basket-bar { height: 6px; background: var(--panel); border-radius: 999px; overflow: hidden; }
.basket-bar span { display: block; height: 100%; background: var(--grad); border-radius: 999px; }
.basket-pct { font-family: var(--mono); font-size: 13px; color: var(--amber); font-weight: 600; }
.basket-note { font-size: 12.5px; color: var(--muted); line-height: 1.65; padding: 6px 2px 0; }

.gate-chart { display: block; width: 100%; height: 172px; margin-top: 14px; border-top: 1px solid var(--border); }

/* ═══ HISTORY ═══ */
.ta-r { text-align: right; }
.hist-row { cursor: pointer; transition: background .14s; }
.hist-row:hover { background: rgba(255,176,32,.04); }
.hist-when { color: var(--muted); font-family: var(--mono); font-size: 12px; }
.hist-pair b { font-weight: 600; }
.hist-held { color: var(--muted); font-family: var(--mono); font-size: 12px; }
.hist-pnl { font-family: var(--mono); }
.hist-pnl b { display: block; font-size: 13.5px; font-weight: 600; }
.hist-pnl span { font-size: 11.5px; opacity: .72; }
.reason-chip {
  display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 11.5px;
  background: var(--panel-light); border: 1px solid var(--border); color: var(--muted);
}
.reason-chip.r-trailing_tp, .reason-chip.r-take_profit { color: var(--green); border-color: rgba(34,211,160,.32); }
.reason-chip.r-stop_loss, .reason-chip.r-circuit_breaker { color: var(--red); border-color: rgba(255,92,122,.32); }
.reason-chip.r-gate_off, .reason-chip.r-gate_on { color: var(--amber); border-color: rgba(255,176,32,.32); }
.hist-detail { display: none; }
.hist-detail.open { display: table-row; }
.hist-detail td { background: var(--bg); border-bottom: 1px solid var(--border); }
.hd-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; padding: 4px 0; }
.hd-grid div { display: flex; flex-direction: column; gap: 3px; }
.hd-grid span { font-size: 11.5px; color: var(--dim); }
.hd-grid b { font-family: var(--mono); font-size: 12.5px; font-weight: 500; }

.recent-trade { display:flex; align-items:center; gap:8px; padding:7px 0; font-size:12.5px; border-bottom:1px solid rgba(39,39,51,.5); }
.recent-trade:last-child { border-bottom:none; }
.recent-trade strong { font-weight:600; }
.rt-qty { color:var(--dim); font-family:var(--mono); font-size:11.5px; }
.rt-reason { margin-left:auto; color:var(--muted); font-size:11.5px; }

#chart-live { position: relative; }
#price-canvas { display: block; width: 100%; height: 100%; }
.chart-empty { color: var(--muted); text-align: center; padding: 40px 20px; font-size: 13px; }

/* ═══ USERS ═══ */
.account-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; align-items: start; }
/* .solo: the People card is admin-only and hidden outright for a member, and a
   hidden grid child is removed from placement — so Your account would sit in
   column 1 with column 2 empty beside it. Set by loadUsersPage() as a class
   rather than an inline style, or it would beat the query below. */
.account-grid.solo { grid-template-columns: 1fr; }
@media (max-width: 900px) { .account-grid { grid-template-columns: 1fr; } }
.fold { border-top: 1px solid var(--border); margin-top: 14px; padding-top: 12px; }
.fold summary {
  cursor: pointer; font-size: 13px; font-weight: 600; color: var(--text);
  list-style: none; padding: 2px 0;
}
.fold summary::-webkit-details-marker { display: none; }
.fold summary::before { content: '\25b8  '; color: var(--amber); }
.fold[open] summary::before { content: '\25be  '; }
.fold-note { font-size: 12.5px; color: var(--muted); line-height: 1.65; margin: 8px 0; }

/* ═══ CHANNEL STATE — read-only ═══
   States what the bot is configured to do. The per-user toggles below it decide
   who a configured channel reaches; nothing here is clickable, because the
   switching belongs in Settings and used to appear to live in both places. */
.chan-state {
  margin: 10px 0 16px;
  padding: 12px 13px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--bg);
}
.chan-row { display: flex; align-items: center; gap: 9px; font-size: 12.5px; }
.chan-row + .chan-row { margin-top: 7px; }
.chan-dot {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--dim);
}
.chan-row.on .chan-dot { background: var(--green); }
.chan-name { color: var(--text); }
.chan-val { margin-left: auto; color: var(--dim); font-family: var(--mono); font-size: 11px; }
.chan-row.on .chan-val { color: var(--green); }
.chan-note {
  margin-top: 11px; padding-top: 10px; border-top: 1px solid var(--border);
  font-size: 11.5px; line-height: 1.6; color: var(--muted);
}
.chan-note b { color: var(--text); font-weight: 600; }
.fold-note b { color: var(--text); font-weight: 600; }
.invite-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
.invite-row .dark-input { flex: 1 1 160px; }
.pw-stack { display: grid; gap: 10px; margin-top: 8px; }

.user-row { display: flex; align-items: center; gap: 12px; }

/* ═══ ONE-TIME INVITE CODE ═══
   It arrived in a native alert() before, which on a phone cannot be selected or
   copied — the code was displayed and unusable. It now sits under the row of the
   person it belongs to, selectable, with a Copy button. */
.invite-code {
  margin: 8px 0 4px;
  padding: 11px 13px;
  border: 1px solid rgba(255,176,32,.34);
  border-radius: var(--r-sm);
  background: rgba(255,176,32,.06);
}
.invite-code-lb {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--amber);
  margin-bottom: 7px;
}
.invite-code-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.invite-code-val {
  flex: 1 1 220px;
  min-width: 0;
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 7px 9px;
  /* One tap selects the whole code rather than a word of it. */
  user-select: all;
  -webkit-user-select: all;
  overflow-wrap: anywhere;
}
.invite-copy { flex: none; }
.invite-code-note {
  margin-top: 8px;
  font-size: 11px;
  line-height: 1.6;
  color: var(--muted);
}
.user-av {
  width: 34px; height: 34px; border-radius: 12px 3px 12px 3px; display: grid; place-items: center;
  background: var(--grad); color: #1a1206; font-weight: 700; font-size: 13px; flex: none;
}
.user-av.muted { background: var(--panel-light); color: var(--muted); border: 1px solid var(--border-light); }
.user-meta { display: flex; flex-direction: column; gap: 2px; }
.user-meta b { font-size: 13px; font-weight: 600; }
.user-meta span { font-size: 11.5px; color: var(--dim); }
.role-chip {
  display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 11.5px;
  background: var(--panel-light); border: 1px solid var(--border); color: var(--muted);
}
.role-chip.owner { color: var(--amber); border-color: rgba(255,176,32,.35); }
.fa-chip { display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 11.5px; border: 1px solid; }
.fa-chip.on { color: var(--green); border-color: rgba(34,211,160,.4); background: rgba(34,211,160,.08); }
.fa-chip.off { color: var(--red); border-color: rgba(255,92,122,.35); background: rgba(255,92,122,.07); }

/* ═══ CONNECTION CARDS ═══ */
.conn-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; }
.conn-card {
  background: var(--panel-light); border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 16px; cursor: pointer; display: flex; flex-direction: column; gap: 9px;
  text-align: left; font-family: var(--font); color: inherit; transition: .18s;
}
.conn-card:hover { border-color: var(--amber); transform: translateY(-3px); }
.conn-t { display: flex; align-items: center; gap: 10px; }
.conn-n { font-size: 13.5px; font-weight: 600; color: var(--text); }
.conn-st {
  font-size: 11px; padding: 3px 10px; border-radius: 999px;
  border: 1px solid var(--border-light); color: var(--dim);
}
.conn-st.is-on { color: var(--green); border-color: rgba(34,211,160,.4); background: rgba(34,211,160,.08); }
.conn-card p { font-size: 12.5px; color: var(--muted); line-height: 1.65; }
.conn-go { font-size: 12.5px; color: var(--amber); margin-top: auto; font-weight: 500; }

/* ═══ WIZARD ═══ */
.wiz-card {
  width: min(600px, 100%); background: var(--panel); border: 1px solid var(--border-light);
  border-radius: 24px 4px 24px 4px; overflow: hidden;
}
.wiz-head { display: flex; align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--border); font-weight: 600; font-size: 14px; }
.wiz-x { margin-left: auto; background: none; border: none; color: var(--muted); font-size: 15px; cursor: pointer; font-family: var(--font); }
.wiz-steps { display: flex; padding: 0 20px; border-bottom: 1px solid var(--border); }
.wiz-steps div { flex: 1; padding: 12px 0; font-size: 12px; color: var(--dim); text-align: center; border-bottom: 2px solid transparent; }
.wiz-steps div.on { color: var(--amber); border-bottom-color: var(--amber); }
.wiz-steps div.done { color: var(--green); }
.wiz-body { padding: 22px 20px; min-height: 200px; }
.wiz-body h4 { font-size: 14.5px; margin-bottom: 9px; color: var(--text); }
.wiz-body p { font-size: 12.5px; color: var(--muted); line-height: 1.7; margin-bottom: 15px; }
.wiz-body b { color: var(--text); }
.wiz-body code { font-family: var(--mono); color: var(--amber); font-size: 12px; }
.wiz-note { font-size: 12px; color: var(--dim); }
.wiz-fld { margin-bottom: 13px; }
.wiz-fld label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
.wiz-fld input {
  width: 100%; background: var(--bg); border: 1px solid var(--border-light); color: var(--text);
  font-family: var(--mono); font-size: 12.5px; padding: 10px 13px; border-radius: 12px 3px 12px 3px;
}
.wiz-fld input:focus { outline: none; border-color: var(--amber); }
.wiz-segs { margin-bottom: 12px; }
.wiz-ok, .wiz-bad, .wiz-pending { font-size: 12.5px; padding: 12px 14px; border-radius: var(--r-sm); }
.wiz-ok { color: var(--green); border: 1px solid rgba(34,211,160,.3); background: rgba(34,211,160,.07); }
.wiz-bad { color: var(--red); border: 1px solid rgba(255,92,122,.3); background: rgba(255,92,122,.07); }
.wiz-pending { color: var(--muted); border: 1px solid var(--border); }
.wiz-qr { display: grid; place-items: center; padding: 8px 0; }
.wiz-qr img { width: 168px; height: 168px; border-radius: 12px 3px 12px 3px; background: #fff; padding: 8px; }
.wiz-foot { display: flex; gap: 10px; padding: 16px 20px; border-top: 1px solid var(--border); }
.wiz-sp { margin-left: auto; }

@media (max-width: 760px) {
  .gate-body { grid-template-columns: 1fr; gap: 18px; }
}

/* ═══ LOGIN ═══ */
.overlay {
  position: fixed; inset: 0; z-index: 2000;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg);
  background-image:
    radial-gradient(760px 420px at 78% -10%, rgba(255,122,69,.12), transparent 62%),
    radial-gradient(680px 380px at 8% 108%, rgba(167,139,250,.10), transparent 60%);
  padding: 24px;
}
.login-card {
  width: min(392px, 100%);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 28px 5px 28px 5px;
  padding: 38px 34px 32px;
  text-align: center;
}
.login-logo { height: 34px; width: auto; margin-bottom: 22px; }
.login-card h2 { font-size: 19px; font-weight: 600; letter-spacing: -.01em; margin-bottom: 6px; }
.login-subtitle { font-size: 13px; color: var(--muted); margin-bottom: 22px; }
.login-input {
  width: 100%; margin-bottom: 11px; padding: 12px 14px;
  background: var(--bg); border: 1px solid var(--border-light); color: var(--text);
  border-radius: 14px 3px 14px 3px; font-family: var(--font); font-size: 13.5px;
}
.login-input:focus { outline: none; border-color: var(--amber); }
.login-btn { width: 100%; margin-top: 12px; padding: 12px 24px; font-size: 13.5px; }
.login-error { color: var(--red); font-size: 12.5px; min-height: 18px; margin-top: 4px; }
.login-card .pw-wrap { position: relative; }
/* right only. This used to also set `top: 11px`, which beat the generic rule's
   `top: 50%` on specificity but NOT its `transform: translateY(-50%)` — so the
   icon was placed 11px down and then pulled half its own height back up,
   landing 4.5px above the field's centre. Letting the generic centring stand
   is the fix; a hand-tuned offset would only re-break at the next font change. */
.login-card .pw-eye { right: 10px; }

.legacy-mk {
  display: inline-grid;
  place-items: center;
  width: 17px;
  height: 17px;
  border-radius: 6px 2px 6px 2px;
  background: var(--panel-light);
  border: 1px solid var(--border-light);
  fill: var(--muted);
  margin-left: 7px;
  vertical-align: -3px;
  cursor: help;
}
.legacy-mk svg { display: block; }
.hist-note { font-size: 12.5px; color: var(--muted); line-height: 1.65; padding: 0 16px 12px; }
.hist-note b { color: var(--text); font-weight: 600; }

/* ═══ SETTINGS KNOBS ═══ */
.knob {
  background: var(--panel-light);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 11px;
  transition: border-color .16s;
}
.knob:hover { border-color: var(--border-light); }
.knob-t { display: flex; align-items: baseline; gap: 10px; }
.knob-n { font-size: 13.5px; font-weight: 600; color: var(--text); }
.knob-v {
  font-size: 30px;
  font-weight: 700;
  font-family: var(--mono);
  letter-spacing: -.03em;
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.knob-v small {
  font-size: 12.5px;
  font-weight: 400;
  margin-left: 6px;
  -webkit-text-fill-color: var(--muted);
  color: var(--muted);
}
.knob-step, .knob-segs {
  display: flex;
  border: 1px solid var(--border-light);
  border-radius: 999px;
  width: max-content;
  overflow: hidden;
}
.knob-step button {
  background: var(--panel);
  border: none;
  color: var(--amber);
  font-family: var(--mono);
  font-size: 15px;
  width: 34px;
  height: 30px;
  cursor: pointer;
  transition: .15s;
}
.knob-step button:hover { background: var(--grad); color: #1a1206; }
.knob-m {
  padding: 0 15px;
  line-height: 30px;
  font-family: var(--mono);
  font-size: 12.5px;
  border-inline: 1px solid var(--border-light);
  min-width: 74px;
  text-align: center;
  color: var(--text);
}
.knob-segs button {
  background: var(--panel);
  border: none;
  color: var(--muted);
  font-family: var(--font);
  font-size: 12.5px;
  padding: 7px 16px;
  cursor: pointer;
  transition: .15s;
}
.knob-segs button.on { background: var(--grad); color: #1a1206; font-weight: 600; }
.knob-x { font-size: 12.5px; color: var(--muted); line-height: 1.65; }
.knob-x b { color: var(--text); font-weight: 600; }

.save-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 4px 16px 16px; }
.save-state { font-size: 12.5px; color: var(--dim); }
.staged-warn { color: var(--amber); }
.staged-ok { color: var(--green); }

@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes toastIn {
  from { opacity: 0; transform: translateX(-50%) translateY(20px); }
  to { opacity: 1; transform: translateX(-50%) translateY(0); }
}

.nav-icon {
  color: var(--muted);
  cursor: pointer;
  transition: color 0.2s;
  display: flex;
  align-items: center;
}
.nav-icon .material-symbols-outlined { font-size: 20px; }
.nav-icon:hover { color: var(--text); }

/* Material icons — global vertical centering */
.material-symbols-outlined {
  vertical-align: middle;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.pw-eye .material-symbols-outlined { font-size: 18px; }
.coin-remove .material-symbols-outlined { font-size: 16px; }
.btn-sm .material-symbols-outlined { font-size: 16px; }

/* ═══ CARDS ═══ */
.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r);
  overflow: hidden;
  transition: border-color .16s;
}

.card-header {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  padding: 14px 16px 10px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 4px;
}
.card-header + .card-header,
.card-body .card-header {
  border-bottom: none;
  border-top: 1px solid var(--border);
  margin-top: 12px;
  padding-top: 14px;
}

.card-body { padding: 8px 16px 16px; }

.card-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border);
  padding: 9px 14px;
  align-items: center;
}

.tab {
  background: none;
  border: none;
  color: var(--muted);
  font-family: var(--font);
  font-size: 13px;
  font-weight: 500;
  padding: 7px 15px;
  cursor: pointer;
  border-radius: 999px;
  transition: all .16s;
}
.tab:hover { color: var(--text); background: var(--panel-light); }
.tab.active {
  color: #fff;
  background: var(--grad);
  font-weight: 600;
  box-shadow: 0 2px 8px rgba(14,203,129,0.3);
}

.tab-content { display: none; }
.tab-content.active { display: block; padding: 8px; }

/* ═══ CHART TOOLBAR ═══ */
.chart-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 8px;
  border-bottom: 1px solid var(--border);
}

.tf-pills {
  display: flex;
  gap: 6px;
  margin-left: auto;
  padding: 4px 0;
}

.tf-pill {
  background: var(--panel-light);
  border: none;
  color: var(--muted);
  font-family: var(--font);
  font-size: 12.5px;
  font-weight: 500;
  padding: 7px 14px;
  border-radius: 999px;
  cursor: pointer;
  transition: all .15s;
}
.tf-pill:hover { color: var(--text); background: var(--border); }
.tf-pill.active {
  color: #1a1206;
  background: var(--grad);
  font-weight: 700;
}

/* ═══ METRICS ═══ */
.metrics-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 11px;
  margin-bottom: 12px;
}

.metric {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 14px 15px;
  transition: border-color .16s, transform .16s;
}
.metric:hover { border-color: var(--border-light); transform: translateY(-2px); }

.metric-label {
  font-size: 11.5px;
  color: var(--muted);
  margin-bottom: 6px;
}

.metric-value {
  font-size: 22px;
  font-weight: 600;
  color: var(--text);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}

.metric-delta { font-size: 11.5px; margin-top: 3px; font-family: var(--mono); color: var(--dim); }
.metric-delta.positive { color: var(--green); }
.metric-delta.negative { color: var(--red); }
.metric-delta.muted { color: var(--dim); font-family: var(--font); }
.metric-value.amber { color: var(--amber); }

/* ═══ PAGE LAYOUTS ═══ */
.page { display: none; padding: 14px 12px; }
.page.active { display: block; }

.dash-grid {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 10px;
}

.trade-grid {
  display: grid;
  grid-template-columns: 65fr 35fr;
  gap: 10px;
}

/* NOTE: a .settings-grid (3-col) rule used to live here. No markup ever used it
   — both the Settings and Users pages use .settings-grid-2x2 — and the 1200px
   media query below was overriding the dead class while the live one went
   untouched, which is why neither page had any responsive behaviour. Removed. */
/* Settings is two independent stacks, not a 2x2 grid: grid rows align, so the
   tall Strategy Parameters card was dictating where the second row began and
   burying Connections a screen down. Columns that flow separately keep every
   card packed against the one above it. */
.settings-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  align-items: start;
}
.set-col { display: flex; flex-direction: column; gap: 10px; min-width: 0; }

/* ═══ NOTIFICATIONS ═══ */
.notif-section { }
.notif-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}
.notif-label {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}
.notif-fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.notif-fields .param-item { margin-bottom: 0; }

/* Toggle switch */
.toggle {
  position: relative;
  display: inline-block;
  width: 42px;
  height: 22px;
}
.toggle input { opacity: 0; width: 0; height: 0; }
.toggle-slider {
  position: absolute;
  cursor: pointer;
  top: 0; left: 0; right: 0; bottom: 0;
  background: var(--border);
  border-radius: 22px;
  transition: 0.3s;
}
.toggle-slider::before {
  content: "";
  position: absolute;
  height: 16px;
  width: 16px;
  left: 3px;
  bottom: 3px;
  background: var(--muted);
  border-radius: 50%;
  transition: 0.3s;
}
.toggle input:checked + .toggle-slider {
  background: var(--green);
}
.toggle input:checked + .toggle-slider::before {
  transform: translateX(20px);
  background: #fff;
}
/* ═══ TICKER BAR ═══ */
.ticker-bar {
  display: flex;
  align-items: center;
  gap: 0;
  padding: 14px 18px;
  margin-bottom: 12px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r);
}

.ticker-left {
  display: flex;
  align-items: center;
  gap: 14px;
  padding-right: 24px;
  margin-right: 24px;
  border-right: 1px solid var(--border);
}

.ticker-price-badge {
  font-size: 30px;
  font-weight: 700;
  color: var(--green);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  white-space: nowrap;
}

.ticker-symbol-wrap {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.ticker-symbol-select {
  background: transparent;
  border: none;
  color: var(--text);
  font-family: var(--font);
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  padding: 0;
  outline: none;
  -webkit-appearance: none;
}
.ticker-symbol-select option { background: var(--panel); }

.ticker-interval-select {
  display: none; /* Hidden — using timeframe pills in chart toolbar instead */
}

.ticker-stats {
  display: flex;
  align-items: flex-start;
  gap: 34px;
  flex: 1;
}

.ticker-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ticker-stat-label {
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
}

.ticker-stat-value {
  font-size: 14px;
  color: var(--text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  font-family: var(--mono);
  white-space: nowrap;
}

/* ═══ ORDER BOOK ═══ */
.ob-row {
  display: flex;
  justify-content: space-between;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 11.5px;
  padding: 3px 14px;
  position: relative;
  transition: background .12s;
}
.ob-row:hover { background: rgba(255,255,255,.03); }

/* Depth bars removed: at four levels a side they were decoration, and they
   competed with the one figure that matters here -- the spread. */

.ob-row span { position: relative; z-index: 1; }
.ob-ask .price { color: var(--red); }
.ob-bid .price { color: var(--green); }
.ob-row .size { color: var(--muted); }

/* The spread is the one number in this panel that matters to a bot placing
   market orders, so it gets a rule of its own rather than blending in. */
.ob-spread {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 6px 0;
  padding: 9px 12px;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--amber);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  border-block: 1px solid var(--border);
  background: rgba(255,176,32,.05);
}

.ob-label {
  font-size: 11.5px;
  color: var(--muted);
  padding: 10px 14px 4px;
}

.orderbook-card {
  min-height: 540px;
}

.orderbook-card .tab-content {
  padding: 4px 0;
}

/* ═══ INPUTS ═══ */
.dark-select, .dark-input {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-family: var(--font);
  font-size: 12px;
  padding: 6px 12px;
  outline: none;
}
.dark-select:focus, .dark-input:focus {
  border-color: var(--green);
}
.dark-select option { background: var(--panel); }

/* ═══ BUTTONS ═══ */
.btn-green {
  background: var(--grad);
  color: #1a1206;
  border: none;
  border-radius: 999px;
  padding: 9px 20px;
  font-family: var(--font);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: all .16s;
}
.btn-green:hover { filter: brightness(1.08); transform: translateY(-1px); }

.btn-sm {
  background: var(--panel-light);
  border: 1px solid var(--border-light);
  border-radius: 999px;
  color: var(--text);
  font-family: var(--font);
  font-size: 11px;
  padding: 4px 12px;
  cursor: pointer;
}
.btn-sm:hover { background: var(--border); }

.input-row { display: flex; gap: 8px; }
.input-row .dark-input { flex: 1; }

/* ═══ TABLES ═══ */
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}
.data-table th {
  text-align: left;
  padding: 8px 12px;
  color: var(--muted);
  font-weight: 500;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  border-bottom: 1px solid var(--border);
}
.data-table td {
  padding: 6px 12px;
  border-bottom: 1px solid rgba(30,35,41,0.5);
  font-family: var(--mono);
  font-size: 11px;
}
.data-table tr:hover { background: rgba(255,255,255,0.02); }

.pnl-pos { color: var(--green); }
.pnl-neg { color: var(--red); }

/* ═══ PARAMS GRID ═══ */
.param-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  padding: 0 16px 16px;
}

.param-item label {
  display: block;
  font-size: 10px;
  color: var(--muted);
  text-transform: uppercase;
  margin-bottom: 4px;
}

.param-item input {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  font-family: var(--mono);
  font-size: 12px;
  padding: 6px 10px;
  outline: none;
}

.param-section-header {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text);
  border-top: 1px solid var(--border);
  padding-top: 10px;
  margin-top: 4px;
}
.param-section-header:first-child {
  border-top: none;
  padding-top: 0;
  margin-top: 0;
}

/* ═══ COINS LIST ═══ */
.coins-lock-banner {
  padding: 10px 14px;
  margin-bottom: 12px;
  background: rgba(240, 185, 11, 0.08);
  border: 1px solid rgba(240, 185, 11, 0.4);
  border-left: 3px solid #f0b90b;
  border-radius: 6px;
  color: #f0b90b;
  font-size: 12px;
  line-height: 1.5;
}
.coin-row-locked { opacity: 0.65; }

.coins-subheader {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text);
  margin-bottom: 8px;
}
.coins-subheader-hint {
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  color: var(--muted);
  font-size: 11px;
}

.coins-whitelist {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.coin-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  font-family: inherit;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  cursor: default;
  transition: background 0.15s, border-color 0.15s;
}
.coin-pill-active {
  color: var(--green);
  border-color: rgba(14, 203, 129, 0.4);
  background: rgba(14, 203, 129, 0.08);
}
.coin-pill-add {
  cursor: pointer;
  color: var(--muted);
}
.coin-pill-add:hover {
  color: var(--text);
  border-color: var(--text);
  background: #161a1c;
}
.coin-pill-disabled {
  opacity: 0.4;
}
.coin-pill-orphan {
  color: #f0b90b;
  border-color: rgba(240, 185, 11, 0.4);
  background: rgba(240, 185, 11, 0.08);
}

.coins-grid {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 14px;
}

.coin-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  transition: background 0.15s;
}
.coin-row:hover { background: #161a1c; }

.coin-row-left {
  display: flex;
  align-items: center;
  gap: 10px;
}

.coin-icon {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--green), #0ba369);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  color: #0e1113;
}

.coin-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}

.coin-pair {
  font-size: 11px;
  color: var(--muted);
}

.coin-remove {
  background: none;
  border: 1px solid transparent;
  color: var(--muted);
  font-size: 16px;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 6px;
  transition: all 0.15s;
}
.coin-remove:hover {
  color: var(--red);
  background: rgba(246,70,93,0.1);
  border-color: rgba(246,70,93,0.3);
}

.coins-add-bar {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
}
.coins-input { flex: 1; }
.btn-add-coin { white-space: nowrap; padding: 8px 20px; display: inline-flex; align-items: center; gap: 6px; }
.btn-add-coin:disabled { opacity: 0.6; cursor: wait; }

.spinner {
  display: inline-block;
  width: 14px; height: 14px;
  border: 2px solid rgba(255,255,255,0.3);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.coins-quick {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.coins-quick-label {
  font-size: 11px;
  color: var(--muted);
}
.coins-quick-list {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.coin-quick-btn {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--muted);
  font-family: var(--font);
  font-size: 11px;
  padding: 4px 10px;
  cursor: pointer;
  transition: all 0.15s;
}
.coin-quick-btn:hover {
  color: var(--green);
  border-color: var(--green);
  background: rgba(14,203,129,0.05);
}

/* ═══ LOG BOX ═══ */

/* Phone-only trade panel switcher; revealed by the phone media query below.
   .mobile-hidden intentionally has no desktop rule — the class is applied by
   setTradeMobileView() at any width, and only resolves to display:none inside
   the phone query, so the state cannot leak onto the desktop layout. */
#trade-segmented { display: none; }

/* ═══ BOTTOM TAB BAR — phones only ═══
   Hidden here by default and revealed by the phone media query at the end of
   this file, gated on body.authed so it can never appear over the login screen.
   Visibility is class-driven rather than an inline style specifically so the
   media query stays authoritative — the footer's inline display:flex, set in
   app.js, is exactly the problem being avoided. */
#tabbar { display: none; }

/* ═══ FOOTER — fixed at bottom ═══
   Height lives in --footer-h and NOWHERE else. It used to be stated three
   times — 38px here, 32px in the token, and two competing body paddings of
   42px and 36px — so the clearance under the page never matched the bar. */
#footer {
  display: flex;
  align-items: center;
  height: var(--footer-h);
  background: rgba(12,12,17,.86);
  backdrop-filter: blur(14px);
  border-top: 1px solid var(--border);
  padding: 0 20px;
  font-size: 11.5px;
  color: var(--muted);
  gap: 16px;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 999;
}
.footer-right { color: var(--dim); white-space: nowrap; }
#footer-version {
  font-family: var(--mono);
  color: var(--dim);
  font-size: 11px;
  white-space: nowrap;
}
body { padding-bottom: calc(var(--footer-h) + 4px); }

/* ═══ GATE RAIL ═══
   The strategy is one decision — is BTC above the band around its long-term
   average — so the footer draws that decision at scale.

   The notch is the level that would FLIP the gate (the entry trigger while the
   gate is shut, the exit level while it is open), NOT the plain average: put
   the notch on the average and the fill would cross it several percent before
   anything actually happened, which is a picture that lies. The marker is
   BTC's last daily close and the fill is the margin between the two, so the
   marker reaching the notch is exactly the gate flipping.

   Green when open (margin you can afford to lose), amber when shut (the gap
   you are waiting to close). Not red — sitting in cash is this strategy
   working as designed, not a fault. */
.grail {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1 1 0%;
  min-width: 90px;
}
/* REQUIRED, not belt-and-braces. The `hidden` attribute works through the UA
   stylesheet's `[hidden] { display: none }`, and ANY author display declaration
   beats the UA origin whatever its specificity — so the rule above would render
   the rail with an engine that is down, a gate still warming up, or simply
   before the first /api/gate resolves: bed and notch, no marker, no labels. */
.grail[hidden] { display: none; }
.grail-state {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--dim);
  white-space: nowrap;
  flex: none;
}
.grail-num {
  font-family: var(--mono);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  flex: none;
}
.grail-track { position: relative; flex: 1 1 0%; min-width: 40px; height: 14px; }
.grail-bed {
  position: absolute; left: 0; right: 0; top: 6px; height: 2px;
  background: var(--border); border-radius: 1px;
}
.grail-fill {
  position: absolute; top: 5px; height: 4px; border-radius: 2px;
  background: var(--green);
  transition: left .3s, width .3s;
}
.grail.shut .grail-fill { background: var(--amber); }
.grail.shut .grail-state { color: var(--amber); }
.grail-notch {
  position: absolute; left: 50%; top: 1px; width: 2px; height: 12px;
  margin-left: -1px; background: var(--border-light); border-radius: 1px;
}
.grail-mk {
  position: absolute; top: 2px; width: 2px; height: 10px; margin-left: -1px;
  background: var(--text); border-radius: 1px;
  transition: left .3s;
}
.green { color: var(--green); }
.red { color: var(--red); }

/* NOTE: a second copy of the .overlay/.login-card/.login-* rules used to live
   here — the pre-redesign green panel with uniform 16px corners. Same class
   names, same specificity, further down the file, so it beat the redesigned
   block above and the login screen was the one surface that never actually
   changed. Removed; the rules at the top of this file are now the only ones.
   The .depth-chart rules that preceded it went with the Depth pane. */

/* Password eye toggle */
.pw-wrap {
  position: relative;
  width: 100%;
  margin-bottom: 10px;
}
/* margin-bottom: 0 is what makes the eye centre. The wrapper is the icon's
   positioning context, so any bottom margin on the field inflates the box that
   `top: 50%` measures against and pushes the icon down by half of it. .pw-wrap
   supplies the spacing below itself, so the inner margin was double anyway. */
.pw-wrap input { width: 100%; padding-right: 40px; margin-bottom: 0; }
.pw-eye {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 16px;
  padding: 2px 4px;
  line-height: 1;
}
.pw-eye:hover { color: var(--text); }

/* TOTP QR code */
.totp-qr { margin: 16px auto; border-radius: 12px; }
.totp-secret { font-family: var(--mono); font-size: 12px; color: var(--muted); word-break: break-all; margin: 8px 0; }

/* User rows */
.user-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  margin-bottom: 4px;
}
.user-row:hover { background: #161a1c; }
.user-badge {
  font-size: 10px;
  padding: 2px 8px;
  border-radius: 4px;
  font-weight: 600;
  text-transform: uppercase;
}
.user-badge.admin { background: rgba(14,203,129,0.15); color: var(--green); }
.user-badge.user { background: rgba(132,142,156,0.15); color: var(--muted); }
.user-totp { font-size: 11px; color: var(--muted); }

/* ═══ RESPONSIVE ═══════════════════════════════════════════════════════════ */

/* Tablet / narrow desktop */
@media (max-width: 1200px) {
  .trade-grid { grid-template-columns: 1fr; }
  .dash-grid { grid-template-columns: 1fr; }
  /* The copyright is the one thing in the footer that carries no reading, so
     it yields first and the gate rail keeps a width worth looking at. */
  .footer-right { display: none; }
  /* Settings/Users stay two-up here — a 2x2 grid is perfectly readable on a
     1024px laptop. They collapse to one column in the phone block below, which
     is what the dead .settings-grid line was failing to do. */
}

/* ═══ PHONE ════════════════════════════════════════════════════════════════
   Composite query, not a plain max-width. The first arm covers portrait; the
   second catches iPhone *landscape*, whose viewports are 844-932px wide — a
   plain `max-width: 768px` (or even 900px) would silently restore the desktop
   layout the moment the phone is rotated. The landscape arm keys off height
   (375-430px on iPhone) so it does not catch iPad or a merely narrow window.

   There is no build step, so this query string cannot live in a variable: every
   phone rule belongs in this single block rather than a second copy. */
@media (max-width: 900px), (orientation: landscape) and (max-height: 500px) {

  /* — Chrome — */
  .page { padding: 10px 8px; }

  /* — One column —
     .settings-grid-2x2 is the single biggest win here: it is what the Settings
     and Users pages actually use, and until now it had no responsive rule at
     all, staying locked two-up down to 320px. */
  .settings-cols,
  .param-grid,
  .notif-fields { grid-template-columns: 1fr; }
  /* auto-fit's 140px minimum yields cramped 2-3 up columns; pin it to 2 */
  .metrics-row { grid-template-columns: repeat(2, 1fr); gap: 8px; }

  /* — Nothing may exceed the viewport —
     max-width is resolved at a different stage of CSS sizing than width, not by
     specificity, so this also caps the inline width:440px (welcome wizard) and
     width:420px (about modal) without editing that markup. */
  .login-card {
    width: 100%;
    max-width: calc(100vw - 24px);
    padding: 32px 20px;
  }
  #login-overlay .login-logo { height: 52px; }
  .alloc-body { gap: 14px; padding: 14px 12px; }

  /* These flex rows default to nowrap with fixed gaps and overflow at 390px */
  .ticker-bar,
  .ticker-stats,
  .chart-toolbar,
  .coins-add-bar,
  .notif-row { flex-wrap: wrap; }
  .ticker-stats { gap: 14px; }

  /* — Chart heights —
     Same max-height mechanism: constrains the inline height:300px/520px in
     index.html and the height app.js injects for #trades-plotly. */
  #equity-chart { max-height: 240px; }
  #chart-live,
  #chart-trades,
  #trades-plotly { max-height: 380px; }

  /* min-height wins over max-height per spec, so it must be reset directly */
  .orderbook-card { min-height: 0; }
  /* — iOS input zoom —
     Safari zooms the entire page when focusing any field whose computed
     font-size is under 16px. Every input here is 12-14px, so without this,
     tapping any field on Login or Settings jerks the viewport. */
  .dark-input,
  .dark-select,
  .login-input,
  .param-item input,
  .param-item select { font-size: 16px; }

  /* — Touch targets — */
  .btn-green,
  .btn-sm,
  .btn-add-coin { min-height: 44px; }

  /* — Bottom tab bar —
     This is the phone's primary navigation, so it wears the same clothes as the
     top bar it replaces: the translucent blurred ground, var(--border), and the
     amber->flame ramp for "you are here". It kept a flat #0c0f11 panel, a hard
     #1e2329 rule and a green active state through the whole redesign because it
     was the one surface never open on a desktop screenshot. */
  body.authed #tabbar {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1500;
    background: rgba(12,12,17,.86);
    backdrop-filter: blur(14px);
    border-top: 1px solid var(--border);
    /* Clears the iPhone home indicator. Non-zero only because the viewport meta
       carries viewport-fit=cover. */
    padding-bottom: env(safe-area-inset-bottom);
  }
  #tabbar a {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    min-height: var(--tabbar-h);
    padding-top: 7px;
    color: var(--muted);
    text-decoration: none;
    font-size: 10.5px;
    font-weight: 500;
    letter-spacing: .2px;
    transition: color .16s;
  }
  /* The icon sits in the house shape — two square corners, two round — the same
     chip used by the avatar and the traded-pair badges. Filling that rather than
     recolouring the whole item keeps the labels evenly weighted across the bar. */
  #tabbar a .material-symbols-outlined {
    font-size: 21px;
    width: 42px;
    height: 27px;
    display: grid;
    place-items: center;
    border-radius: 12px 3px 12px 3px;
    transition: background .16s, color .16s;
  }
  #tabbar a.active { color: var(--text); font-weight: 600; }
  #tabbar a.active .material-symbols-outlined {
    background: var(--grad);
    color: #1a1206;
  }

  /* Top bar slims down: the page links move to the tab bar. Everything else it
     used to hide here (#btn-engine, #nav-username) no longer exists — the
     account menu holds the identity and there is no engine control at all. */
  #topbar { padding: 0 10px; gap: 8px; }
  .nav-links { display: none; }
  .nav-right { gap: 8px; min-width: 0; }

  /* The full lockup, mark AND wordmark. It used to crop to a 40px window on the
     mark alone, which dropped the product's name from every phone screen. The
     200x50 viewBox is ~96px wide at 24px tall, and the bar has that to spare now
     that the strategy name is gone: 96 + trace + badge + avatar comes to about
     250px of the 340px available at 360px wide. */
  .logo-img { height: 24px; width: auto; object-fit: fill; object-position: center; }
  .logo-link { margin-right: 0; }
  /* placeStatusCluster() moves #status-cluster into .nav-right at this width and
     shrinks the trace to 32x12. What used to overflow a 360px bar was the
     strategy name, which no longer exists — so nothing here needs to ellipsize
     or yield.

     The mode drops to a single word (Demo / Paper / Live) and the state word and
     its hairline go entirely: at 32x12 the flatline is still unmistakable, and
     the ~52px the word costs buys nothing the trace is not already saying. */
  #status-cluster { gap: 7px; }
  .vitals-word, .vitals-hair { display: none; }
  .modew { height: 22px; padding: 0 9px; font-size: 10px; }

  /* Moved here by placeLangSwitcher() at this breakpoint. */
  #acct-lang-slot:empty { display: none; }

  /* The pill-strip idiom belongs in a dense top bar, not in a vertical menu of
     house-shaped rows — and inside the menu it was actively broken. `.acct-menu
     button` styles EVERY button in the menu: display:block, width:100%,
     text-align:left and a 10px/2px radius. The old override reset only flex,
     padding and font-size, so each language button kept left-aligned text with
     zero horizontal padding, pressed against the strip's 999px curve, which
     `overflow: hidden` then clipped — the "E" of EN was sliced off, and the
     active button's 10px/2px corners fought the pill they sat inside.

     Fixed by construction rather than by tuning the clip: drop the strip
     entirely and give each language its own chip in the menu's own shape, so
     they read as three more rows of the same list. Nothing overflows because
     nothing is being clipped. */
  .acct-menu #langsw {
    display: flex;
    gap: 4px;
    width: calc(100% - 8px);
    margin: 8px 4px 2px;
    background: none;
    border-radius: 0;
    overflow: visible;
  }
  .acct-menu #langsw button {
    flex: 1;
    width: auto;
    padding: 8px 0;
    font-size: 12px;
    text-align: center;
    border-radius: 10px 2px 10px 2px;
    background: var(--panel-light);
  }
  .acct-menu #langsw button.on { background: var(--grad); color: #1a1206; }

  /* app.js sets an inline display:flex on the footer, which outranks a plain
     rule — !important is required to hide it. */
  #footer { display: none !important; }

  /* Clear the tab bar rather than the now-hidden footer */
  body { padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom)); }

  /* showToast() sets bottom:50px inline, which would land behind the tab bar */
  #toast-container {
    bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 12px) !important;
  }

  /* — History: 9-column table becomes stacked cards —
     Scoped to #history-table rather than the shared .data-table class, so a
     future table without data-label attributes is not silently mangled.
     Each <tr> becomes a two-column grid; every cell is placed explicitly so
     auto-placement cannot reorder them. DOM cell order is:
     1 Symbol, 2 Qty, 3 Entry, 4 Exit, 5 P&L $, 6 P&L %, 7 Reason,
     8 Duration, 9 Closed. */
  #history-table thead { display: none; }
  #history-table,
  #history-table tbody { display: block; }

  #history-table tr {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    gap: 2px 10px;
    margin-bottom: 8px;
    padding: 10px 12px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 8px;
  }
  #history-table td { border: none; padding: 0; }

  /* Headline: symbol and net P&L, the two things worth scanning */
  #history-table td:nth-child(1) {
    grid-row: 1; grid-column: 1;
    font-weight: 600; font-size: 14px;
  }
  #history-table td:nth-child(5) {
    grid-row: 1; grid-column: 2;
    font-weight: 600; font-size: 14px; text-align: right;
  }
  /* Subhead: exit reason and P&L % */
  #history-table td:nth-child(7) {
    grid-row: 2; grid-column: 1;
    color: var(--muted); font-family: var(--mono); font-size: 11px;
  }
  #history-table td:nth-child(6) {
    grid-row: 2; grid-column: 2;
    font-size: 12px; text-align: right;
  }

  /* Detail lines: label on the left, value on the right */
  #history-table td:nth-child(3) { grid-row: 3; }
  #history-table td:nth-child(4) { grid-row: 4; }
  #history-table td:nth-child(2) { grid-row: 5; }
  #history-table td:nth-child(2),
  #history-table td:nth-child(3),
  #history-table td:nth-child(4) {
    grid-column: 1 / -1;
    display: flex;
    justify-content: space-between;
    font-family: var(--mono);
    font-size: 11px;
  }
  #history-table td:nth-child(3) {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--border);
  }
  #history-table td:nth-child(2)::before,
  #history-table td:nth-child(3)::before,
  #history-table td:nth-child(4)::before {
    content: attr(data-label);
    color: var(--muted);
    font-family: var(--font);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
  }

  /* Footer: duration and close time */
  #history-table td:nth-child(8) {
    grid-row: 6; grid-column: 1;
    margin-top: 6px; color: var(--muted); font-size: 11px;
  }
  #history-table td:nth-child(9) {
    grid-row: 6; grid-column: 2;
    margin-top: 6px; color: var(--muted); font-size: 11px; text-align: right;
  }

  /* — Trade page: one panel at a time — */
  .mobile-hidden { display: none !important; }

  #trade-segmented {
    display: flex;
    gap: 4px;
    margin-bottom: 8px;
  }
  .mseg {
    flex: 1;
    min-height: 40px;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 8px;
    color: var(--muted);
    font-family: var(--font);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
  }
  /* Was var(--green) — the pre-redesign accent, the same one the tab bar kept.
     The phone switcher is a primary control, so it takes the house ramp. */
  .mseg.active {
    background: var(--grad);
    border-color: transparent;
    color: #1a1206;
    font-weight: 700;
  }
  /* The segmented control replaces these; the buttons stay in the DOM because
     setTradeMobileView() proxy-clicks them to reuse the existing tab handler. */
  .chart-card .card-tabs,
  .orderbook-card .card-tabs { display: none; }
  /* Timeframe pills stay, and scroll rather than wrap */
  .tf-pills {
    margin-left: 0;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .tf-pills::-webkit-scrollbar { display: none; }

  /* — Engine control — */

  /* — Users / Account — */
  .user-row {
    flex-wrap: wrap;
    gap: 8px;
    padding: 12px;
  }
  /* Let a long username wrap instead of squeezing the action buttons */
  .user-row > div:first-child {
    flex: 1 1 auto;
    min-width: 0;
  }
  .user-row .btn-sm,
  .user-row .coin-remove {
    min-height: 44px;
    min-width: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .coin-row { padding: 10px 12px; }
  /* 2FA QR is a fixed-size image and would otherwise overflow a narrow card */
  .totp-qr { max-width: 100%; height: auto; }
}

/* ═══ TIMEFRAME PILLS (in the ticker card) ═══ */
.ticker-bar .tf-pills { margin-left: auto; margin-right: 14px; }
.chart-toolbar { justify-content: flex-start; }

.ob-spread b { font-size: 17px; color: var(--amber); font-weight: 700; letter-spacing: -.02em; }
.ob-spread span { font-size: 12px; color: var(--muted); font-weight: 400; font-family: var(--mono); }
.ob-row { padding: 5px 16px; font-size: 12.5px; }
.ob-note { font-size: 12.5px; color: var(--muted); line-height: 1.65; padding: 14px 16px 16px; }
.ob-note b { color: var(--ice); font-weight: 600; }

/* ═══ ACCOUNT MENU ═══ */
.acct { position: relative; flex: none; }
.avatar {
  width: 34px; height: 34px; border: none; cursor: pointer;
  border-radius: 12px 3px 12px 3px; background: var(--grad);
  color: #1a1206; font-weight: 700; font-size: 13px; font-family: var(--font);
}
.avatar:hover { filter: brightness(1.08); }
.avatar:focus-visible { outline: 2px solid var(--ice); outline-offset: 2px; }
.acct-menu {
  display: none; position: absolute; right: 0; top: calc(100% + 10px);
  min-width: 210px; background: var(--panel); border: 1px solid var(--border-light);
  border-radius: 16px 3px 16px 3px; padding: 6px; z-index: 1200;
  box-shadow: 0 18px 44px rgba(0,0,0,.55);
}
.acct-menu.open { display: block; }
.acct-who { padding: 10px 12px 8px; display: flex; flex-direction: column; gap: 2px; }
.acct-who b { font-size: 13px; font-weight: 600; }
.acct-who span { font-size: 11.5px; color: var(--dim); }
.acct-menu button {
  display: block; width: 100%; text-align: left; background: none; border: none;
  color: var(--text); font-family: var(--font); font-size: 13px;
  padding: 9px 12px; border-radius: 10px 2px 10px 2px; cursor: pointer;
}
.acct-menu button:hover { background: var(--panel-light); }
.acct-menu button.danger { color: var(--red); }
.acct-sep { height: 1px; background: var(--border); margin: 6px 4px; }

.chart-title { font-size: 13.5px; font-weight: 600; color: var(--text); }
.chart-legend { display: flex; align-items: center; gap: 7px; margin-left: 16px; font-size: 11.5px; color: var(--muted); }
.chart-legend span:not([class]) { margin-right: 6px; }
.lg-entry, .lg-win, .lg-loss { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }
.lg-entry { background: var(--ice); }
.lg-win { background: var(--green); }
.lg-loss { background: var(--red); }

/* ═══ LOGIN BACKDROP ═══
   Painted by login.js. Gated on body:not(.authed) so it can never sit behind
   the app: showMainApp() adds the class, which hides this AND stops the rAF. */
#fx { display: none; }
body:not(.authed) #fx {
  display: block;
  position: fixed;
  inset: 0;
  /* Explicit, and not redundant with inset:0. A canvas is a REPLACED element:
     width:auto resolves to its intrinsic width attribute (the device-pixel
     bitmap), which over-constrains left+right and makes the browser drop
     `right`. Without these the CSS box was 2880x1920 on a 1440x960 viewport,
     so everything painted into the top-left quarter and the price layer fell
     off-screen entirely. */
  width: 100%;
  height: 100%;
  z-index: 1990;
  pointer-events: none;
}
/* The overlays must not paint over the canvas. Scoped by id rather than by
   .overlay, because #wiz-overlay still needs its scrim. */
#login-overlay, #setup-overlay, #welcome-overlay {
  background: transparent;
  background-image: none;
  flex-direction: column;
  gap: 18px;
}

.login-card {
  position: relative;
  isolation: isolate;
  background: rgba(21,21,28,.72);
  backdrop-filter: blur(18px) saturate(1.2);
  box-shadow: 0 30px 90px rgba(0,0,0,.6);
}
/* A hairline that travels around the card. The conic gradient is masked to the
   1px border box, so it reads as light moving along the edge rather than a
   glowing rectangle. */
.login-card::before {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1px;
  background: conic-gradient(from var(--a, 0deg),
    transparent 0deg, transparent 250deg,
    var(--amber) 300deg, var(--flame) 330deg, transparent 360deg);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  animation: edge 5.5s linear infinite;
  z-index: -1;
}
@property --a { syntax: '<angle>'; initial-value: 0deg; inherits: false; }
@keyframes edge { to { --a: 360deg; } }

.login-logo { animation: beat 2.6s ease-in-out infinite; transform-origin: center; }
@keyframes beat {
  0%, 72%, 100% { transform: scale(1); }
  76% { transform: scale(1.07); }
  82% { transform: scale(.99); }
  88% { transform: scale(1.03); }
}

.login-card h2 {
  background: linear-gradient(92deg, var(--text) 30%, var(--amber));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.login-input {
  font-family: var(--mono);
  font-size: 13px;
  transition: border-color .18s, box-shadow .18s, background .18s;
}
.login-input:focus {
  border-color: var(--amber);
  box-shadow: 0 0 0 3px rgba(255,176,32,.14);
  background: rgba(12,12,17,.9);
}
.login-input::placeholder { color: var(--dim); }

/* Sheen sweeps across on hover, and again while a sign-in is in flight. */
.login-btn { position: relative; overflow: hidden; font-weight: 700; }
.login-btn::after {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(105deg, transparent 35%, rgba(255,255,255,.42) 50%, transparent 65%);
  transform: translateX(-120%);
}
.login-btn:hover::after { transition: transform .7s ease; transform: translateX(120%); }
.login-btn.busy { pointer-events: none; }
.login-btn.busy::after { animation: sheen 1.1s linear infinite; }
@keyframes sheen { from { transform: translateX(-120%); } to { transform: translateX(120%); } }

/* Live market, before sign-in. Real data, not ornament. */
#login-ticks {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 10px;
  position: relative; z-index: 2001; min-height: 30px;
}
#login-ticks .lt {
  display: inline-flex; align-items: baseline; gap: 7px;
  padding: 6px 12px; border-radius: 12px 3px 12px 3px;
  background: rgba(21,21,28,.66);
  border: 1px solid var(--border);
  backdrop-filter: blur(10px);
  font-size: 11.5px;
}
#login-ticks b { color: var(--muted); font-weight: 600; letter-spacing: .04em; }
#login-ticks i { font-style: normal; font-family: var(--mono); color: var(--text); }
#login-ticks u { text-decoration: none; font-family: var(--mono); font-size: 11px; }
#login-ticks u.up { color: var(--green); }
#login-ticks u.dn { color: var(--red); }

@media (prefers-reduced-motion: reduce) {
  .login-card::before, .login-logo, .login-btn::after { animation: none; }
}

/* Sign-in drops its heading: the mark already says Pulse, and a wordmark plus
   "Pulse Trading Terminal" plus "Sign in to continue" said it three times.
   Scoped to #login-overlay — setup and welcome keep their own headings, so
   their logo stays at the smaller size that sits above one. */
#login-overlay .login-logo { height: 62px; margin-bottom: 18px; }

/* A level that actually moved gets one flash. Restores the sense of a live
   book that the depth bars used to carry, without reinstating the bars — the
   spread is still the only number here that can cost the bot money. */
@keyframes ob-flash { from { background: var(--ob-f); } to { background: transparent; } }
.ob-row.chg { animation: ob-flash .5s ease-out; }
.ob-ask.chg { --ob-f: rgba(255,92,122,.20); }
.ob-bid.chg { --ob-f: rgba(34,211,160,.18); }
@media (prefers-reduced-motion: reduce) { .ob-row.chg { animation: none; } }

/* ═══ ALLOCATION ═══ */
.dash-side { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.alloc-body { display: flex; align-items: center; gap: 20px; padding: 18px 16px; }
.alloc-ring { flex: none; position: relative; width: 128px; height: 128px; }
.alloc-ring canvas { display: block; width: 128px; height: 128px; }
.alloc-mid {
  position: absolute; inset: 0; display: grid; place-content: center;
  text-align: center; line-height: 1.15;
}
.alloc-mid b { font-size: 21px; font-family: var(--mono); letter-spacing: -.03em; }
.alloc-mid span { font-size: 11px; color: var(--muted); }
.alloc-legend { flex: 1; min-width: 0; display: grid; gap: 11px; }
.alloc-row { display: grid; grid-template-columns: 14px 1fr auto; align-items: center; gap: 10px; font-size: 13px; }
.alloc-dot { width: 11px; height: 11px; border-radius: 4px 1px 4px 1px; }
.alloc-name { color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.alloc-row.flat .alloc-name { color: var(--muted); }
.alloc-pct { font-family: var(--mono); font-variant-numeric: tabular-nums; font-weight: 600; font-size: 12.5px; }
.alloc-row.flat .alloc-pct { color: var(--dim); font-weight: 400; }
.alloc-note {
  font-size: 12.5px; color: var(--muted); line-height: 1.7;
  padding: 0 16px 16px;
}
.alloc-note b { color: var(--ice); font-weight: 600; }

/* ═══ LIVE vs REGISTERED ═══ */
.cmp-table { width: 100%; border-collapse: collapse; }
.cmp-table th, .cmp-table td { padding: 13px 16px; text-align: right; }
.cmp-table th:first-child, .cmp-table td:first-child { text-align: left; }
.cmp-table thead th {
  font-size: 11.5px; font-weight: 500; color: var(--muted);
  border-bottom: 1px solid var(--border);
}
.cmp-table tbody tr + tr td { border-top: 1px solid var(--border); }
.cmp-table td { font-size: 13px; }
.cmp-table td:first-child { color: var(--text); }
.cmp-num { font-family: var(--mono); font-variant-numeric: tabular-nums; font-weight: 600; }
.cmp-num.pos { color: var(--green); }
.cmp-num.neg { color: var(--red); }
.cmp-none { color: var(--dim); }
.cmp-note {
  font-size: 12.5px; color: var(--muted); line-height: 1.7;
  padding: 14px 16px 16px; border-top: 1px solid var(--border);
}
.cmp-note b { color: var(--ice); font-weight: 600; }

/* ═══ Binance account card (Settings) ═══
   Real exchange balance and the bot's notional capital sit adjacent, so the
   VALUES must be visually separable from the labels at a glance — conflating
   them is the failure mode this card exists to prevent. */
.acct-row { display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,.06); }
.acct-row:last-of-type { border-bottom: 0; }
.acct-lbl { color: var(--muted); font-size: 12px; }
.acct-val { font-family: 'JetBrains Mono', monospace; font-size: 14px;
  font-weight: 500; color: var(--text); }
.acct-val.is-on { color: var(--green); }
.acct-note { color: var(--muted); font-size: 11px; line-height: 1.5;
  margin: 10px 0 0; }
.acct-holdings { margin-top: 12px; }
.acct-h { display: grid; grid-template-columns: 1fr 1fr auto; gap: 8px;
  font-family: 'JetBrains Mono', monospace; font-size: 11px;
  color: var(--muted); padding: 3px 0; }
.acct-h span:last-child { color: var(--text); text-align: right; }


/* Why the gate panel looks static: it is a DAILY system. Stated in the panel
   itself, because "correct but unchanging" and "broken" look identical. */


/* ═══ Pending values ═══
   Any element awaiting data gets .is-loading. The rule is that a dynamic field
   must never render a concrete default: the exchange chip was hardcoded to
   "Simulated", so before the config arrived it asserted the wrong venue as
   fact. A shimmer is obviously "not yet"; a plausible wrong value is not. */
.is-loading {
  display: inline-block;
  min-width: 62px;
  min-height: 1em;
  border-radius: 5px;
  background: linear-gradient(90deg,
    rgba(139,139,156,.10) 25%, rgba(139,139,156,.22) 37%, rgba(139,139,156,.10) 63%);
  background-size: 400% 100%;
  animation: pulse-shimmer 1.4s ease-in-out infinite;
  color: transparent !important;
}
@keyframes pulse-shimmer {
  0%   { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}
@media (prefers-reduced-motion: reduce) {
  .is-loading { animation: none; }
}
