/* ToroTradez. Mobile-first, hand-written, no framework.
   Colours follow the mockups: near-black ground, neon green primary, red for DOWN/loss. */

:root {
  --bg:        #000000;
  --panel:     #0b0e0b;
  --panel-2:   #121712;
  --line:      #1f2a1f;
  --green:     #8fe215;
  --green-dim: #5c9a10;
  --green-glow: rgba(143, 226, 21, 0.35);
  --red:       #ff2d2d;
  --red-glow:  rgba(255, 45, 45, 0.3);
  --text:      #f2f5ef;
  --muted:     #8c9686;
  /* Neither green nor red: the colour for a state that is neither good nor bad, such as a
     symbol on its way out that is still carrying live trades. */
  --amber:     #e0a83c;
  --radius:    12px;

  --tabbar-h:  62px;
  --legal-h:   30px;
}

* { box-sizing: border-box; }

/* A display rule on a class beats the [hidden] attribute on specificity, so .field/.hint
   elements stayed visible when toggled. Restore the attribute as the final word. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Segoe UI", system-ui, -apple-system, Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

body {
  /* dvh, not vh: iOS Safari's collapsing URL bar makes 100vh taller than the visible area,
     which pushes the fixed tab bar off screen until the user scrolls. */
  min-height: 100dvh;
  /* manipulation removes the 300ms double-tap delay without a JS shim. */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

button, input { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }

.boot { padding: 3rem 1rem; text-align: center; color: var(--muted); }

/* ---------------------------------------------------------------- chrome */

.topbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: .5rem; padding: .7rem 1rem .2rem;
  /* The tagline is a header child now so the desktop layout can set it beside the wordmark
     without a second copy in the DOM. On a phone it wraps to its own full-width line, which is
     exactly where it was as a sibling -- measured identical before and after. */
  flex-wrap: wrap;
}
.topbar .tagline { flex: 1 0 100%; order: 3; }

/* The balance in the header. Hidden on a phone, where every screen prints its own. */
.head-bal { display: none; }

/* The phone header: one image, edge to edge, in place of the wordmark, the tagline and the
   bell. The artwork is 3:1, so it draws about 125px tall at 375 and 107 at 320.

   height:auto is load-bearing, not tidiness. The element carries height="214" so the box is
   reserved before the image arrives; without this that attribute beats width:100% and the
   banner renders squashed to 214px tall at every width.

   The negative margins undo .topbar's padding so the image touches all three edges. It is the
   header, not something sitting inside it. */
.brand-banner {
  display: block; width: calc(100% + 2rem); height: auto;
  margin: -.7rem -1rem .2rem; order: 1; flex: 1 0 100%;
}

/* The real wordmark, replacing the hand-set two-tone type it stood in for.
   width/height are on the element as well, so the header does not jump while it loads.
   Both it and the tagline are the banner's desktop halves, so a phone hides them -- along with
   the header bell, which balanceBar() reprints inside the PLAY CREDITZ strip instead. */
.brand { display: block; width: 156px; height: auto; }
@media (max-width: 767px) {
  .topbar .brand, .topbar .tagline, #navBell { display: none; }
  .topbar { padding-bottom: 0; }
}
.brand-lg { width: min(82vw, 344px); margin: 0 auto .9rem; }

.tagline {
  margin: .1rem 0 .8rem; text-align: center;
  font-size: .62rem; letter-spacing: .18em; color: var(--muted);
}

.icon-btn {
  background: none; border: 0; padding: .35rem .5rem;
  color: var(--green); font-size: 1.1rem; cursor: pointer; position: relative;
  min-width: 44px; min-height: 44px;   /* thumb target */
}
/* Matches the tab bar's icon treatment: outline, currentColor, same stroke weight. */
.icon-btn svg {
  display: block; width: 22px; height: 22px;
  fill: none; stroke: currentColor;
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
  filter: drop-shadow(0 0 5px var(--green-glow));
}
.bell-dot {
  position: absolute; top: 6px; right: 6px;
  width: 8px; height: 8px; border-radius: 50%; background: var(--green);
}

.legal {
  display: flex; align-items: center; justify-content: center;
  margin: 0; padding: .5rem .25rem .45rem;
  /* Fluid rather than a fixed size: all three phrases on one line is 397px of text, which does
     not fit a 375px phone at a fixed .58rem -- it was clipping at both ends. Scaling with the
     viewport keeps it on one line from 320px up, and the clamp stops it growing unreadably
     large in the 620px column. */
  font-size: clamp(.47rem, 2.3vw, .6rem);
  letter-spacing: .03em; color: var(--text);
  white-space: nowrap;
}
.legal span { padding: 0 .4rem; }
/* Separators as borders rather than pipe characters: they stay put when the text sizes change,
   and they can carry the green without colouring a glyph. */
.legal span + span { border-left: 1px solid var(--green-dim); }
.legal em { font-style: normal; font-weight: 700; color: var(--green); }

/* Below ~360px all three phrases will not fit on one line at a readable size. Dropping the
   middle one keeps the two that matter most legally. */
@media (max-width: 359px) {
  .legal span { padding: 0 .28rem; }
}
.legal-auth { display: block; margin-top: 1.6rem; line-height: 1.7; white-space: normal;
              font-size: .55rem; color: var(--muted); }

/* Clears the whole dock (nav + legal row), not just the nav. */
main { padding: 0 1rem calc(var(--tabbar-h) + var(--legal-h) + 1rem); }

/* ---------------------------------------------------------------- tab bar */

/* Legal line + nav, docked together. The disclaimer has to be on screen at all times, not
   only when a list happens to be scrolled to the end. */
.dock {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
  background: #000;
  /* The home indicator on modern iPhones overlaps a bottom-fixed bar without this. */
  padding-bottom: env(safe-area-inset-bottom);
}

.tabbar {
  /* position:relative is load-bearing. The lit rule below is an absolutely positioned ::before
     at top:0; without a positioning context on the nav it resolved against .dock instead and
     drew ABOVE the legal line rather than between it and the icons. */
  position: relative;
  /* Auto columns, not repeat(6, 1fr). The ADMIN item is a seventh child that is present in the
     markup and hidden for a Player, so a fixed six-column track would push it onto a second
     row for an admin. display:none takes a hidden child out of the flow entirely, so the count
     follows whoever is signed in. */
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  background: #000;
}

/* The lit rule above the bar, brightest under the centre where GAME sits. */
.tabbar::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg,
    rgba(143,226,21,.15) 0%, rgba(143,226,21,.85) 50%, rgba(143,226,21,.15) 100%);
  box-shadow: 0 0 10px rgba(143,226,21,.45);
}

.tabbar a {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 5px; height: 60px; padding-top: 4px;
  color: #eef1e9;                       /* inactive reads white in the mockup, not grey */
  -webkit-tap-highlight-color: transparent;
}

.tabbar a span {
  font-size: .5rem; font-weight: 600; letter-spacing: .07em; line-height: 1;
  white-space: nowrap;
}

.tabbar a svg {
  width: 23px; height: 23px; flex: none;
  fill: none; stroke: currentColor;
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}

/* LEADERBOARD is the longest label and would otherwise force the grid wider than the
   viewport on a 320px phone. Shrink just that one rather than shrinking all six. */
.tabbar a[data-tab="leaderboard"] span { font-size: .44rem; letter-spacing: .04em; }

.tabbar a.is-active { color: var(--green); }
.tabbar a.is-active svg { filter: drop-shadow(0 0 5px var(--green-glow)); }
.tabbar a:active { background: #0b0f0b; }

/* The candlestick bodies stay FILLED, so the glyph reads as candles rather than as three empty
   boxes -- but the fill comes from currentColor, so it is white on an unselected tab and green on
   the selected one like every other icon in the bar. It used to be green at all times with its
   own permanent glow, which made GAME look selected wherever you actually were. */
.tab-game svg path:last-of-type { fill: currentColor; fill-opacity: .22; }

@media (max-width: 359px) {
  .tabbar a svg { width: 21px; height: 21px; }
  .tabbar a span { font-size: .45rem; letter-spacing: .03em; }
  .tabbar a[data-tab="leaderboard"] span { font-size: .4rem; }
}

/* The bottom bar was cut for six items and an Admin sees seven, which takes about 7px off every
   cell at 320. LEADERBOARD is the one label with nothing to give -- measured at exactly its own
   cell width, so it read as cramped even without clipping. Scoped to a bar that is actually
   showing the ADMIN item, so a Player's six-item bar is untouched. The :has() argument carries
   an id, which is what lets this sit here rather than after the narrow-width block it corrects. */
@media (max-width: 767px) {
  .tabbar:has(#tabAdmin:not([hidden])) a[data-tab="leaderboard"] span {
    font-size: .4rem; letter-spacing: 0;
  }
}
@media (max-width: 359px) {
  .tabbar:has(#tabAdmin:not([hidden])) a[data-tab="leaderboard"] span { font-size: .36rem; }
}

/* ---------------------------------------------------------------- auth */

.auth-wrap { min-height: 100dvh; display: grid; place-items: center; padding: 1.25rem; }
.auth-card { width: 100%; max-width: 420px; text-align: center; }
.auth-title {
  margin: .8rem 0 1.4rem; font-size: 1.5rem; line-height: 1.2;
  font-weight: 800; letter-spacing: .01em;
}
.auth-title em { color: var(--green); font-style: normal; text-shadow: 0 0 14px var(--green-glow); }

.seg {
  display: grid; grid-template-columns: 1fr 1fr; gap: .4rem;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: .3rem; margin-bottom: 1.1rem;
}
.seg-btn {
  background: none; border: 0; border-radius: 9px; padding: .7rem .4rem;
  font-size: .72rem; font-weight: 700; letter-spacing: .1em; color: var(--muted); cursor: pointer;
}
.seg-btn.is-active { background: var(--green); color: #04140a; }

.field { display: block; text-align: left; margin-bottom: .85rem; }
.field > span {
  display: block; margin-bottom: .35rem;
  font-size: .58rem; letter-spacing: .14em; color: var(--muted);
}
.field input {
  width: 100%; padding: .85rem .9rem;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 10px;
  outline: none;
}
.field input:focus { border-color: var(--green-dim); box-shadow: 0 0 0 3px rgba(143,226,21,.12); }
.field input::placeholder { color: #55604f; }

.hint   { margin: -.35rem 0 .8rem; font-size: .68rem; color: var(--muted); text-align: left; }
.formerr {
  margin: .2rem 0 .8rem; padding: .6rem .7rem; text-align: left;
  background: rgba(255,45,45,.09); border: 1px solid rgba(255,45,45,.35);
  border-radius: 9px; color: #ff8b8b; font-size: .8rem;
}

.btn {
  display: block; width: 100%; padding: .95rem 1rem; margin-top: .3rem;
  border: 0; border-radius: 11px; cursor: pointer;
  font-weight: 800; letter-spacing: .1em; font-size: .85rem;
}
.btn:disabled { opacity: .55; cursor: default; }
.btn-primary { background: var(--green); color: #04140a; box-shadow: 0 0 20px var(--green-glow); }
.btn-primary:active:not(:disabled) { transform: translateY(1px); }

/* ---------------------------------------------------------------- panels */

.balance {
  border: 1px solid var(--green-dim); border-radius: var(--radius);
  background: linear-gradient(180deg, #0d150a, #070a06);
  padding: .8rem 1rem; text-align: center; margin-bottom: 1rem;
}
/* Three columns rather than a bell floated over the stack: the empty first column is the
   bell's width, so the numbers stay optically centred in the strip and a six-figure balance
   can never run under the button.
   Scoped to main, and that is load-bearing rather than tidy. The header's own balance carries
   this same class, and a bare `.balance { display: grid }` sits later in this file than
   `.head-bal { display: none }` at equal specificity -- so it won, and the desktop balance
   appeared above the banner on every phone screen. */
main .balance {
  display: grid; grid-template-columns: 40px 1fr 40px; align-items: center;
}
/* The two 40px columns are 80px the balance used to have. A grown account reaches eight
   figures, and V$12,345,678.00 has no break opportunity in it -- commas are not one -- so at
   320px it pushed the page 9px wide rather than wrapping. Measured: the middle column is 208px
   there, and 7.2vw brings the widest realistic balance to 187px. Above 356px the vw term is
   the larger of the two, so every ordinary phone still gets the full 1.6rem. */
main .balance strong { font-size: min(1.6rem, 7.2vw); white-space: nowrap; }
/* A grid track is min-width:auto by default, so an unbreakable number wider than its share
   grows the track and takes the whole page with it rather than being constrained by 1fr. This
   is what makes the column actually mean 1fr. */
.bal-stack { min-width: 0; }
.bal-bell { justify-self: end; margin-right: -.5rem; }
.balance small { display: block; font-size: .55rem; letter-spacing: .16em; color: var(--muted); }
.balance strong { display: block; font-size: 1.6rem; color: var(--green); letter-spacing: .02em; }
.balance .who { font-size: .7rem; color: var(--green-dim); }

.section-head {
  display: flex; align-items: center; gap: .6rem; margin: 1.2rem 0 .7rem;
  font-size: .62rem; letter-spacing: .18em; color: var(--muted);
}
.section-head::before, .section-head::after {
  content: ""; flex: 1; height: 1px; background: var(--line);
}

.card {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); padding: 1rem;
}
.muted { color: var(--muted); }
.stub { text-align: center; padding: 2.5rem 1rem; color: var(--muted); }
.stub h2 { color: var(--text); font-size: 1rem; letter-spacing: .1em; }

/* Symbol rows. Values are patched in place by app.js -- never re-rendered, so scroll position
   and :active states survive a price tick. */
/* Three columns: identity, sparkline, price.
   Both the sparkline AND the price column are FIXED width. The price column was previously
   auto, which sizes each row to its own content -- so a six-figure Bitcoin price pushed its
   sparkline left while a three-figure one did not, and the charts came out ragged down the
   list. Fixed widths are what make them line up.
   The identity column takes the slack and truncates, since the chart is the part that stops
   being readable when squeezed. */
.sym {
  display: grid; grid-template-columns: minmax(0, 1fr) 60px 122px; align-items: center; gap: .35rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); padding: .62rem .6rem; margin-bottom: .5rem;
}
.sym-id { min-width: 0; }
.sym-id .sym-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.spark { display: flex; align-items: center; justify-content: center; width: 60px; height: 38px; }
.sparksvg { display: block; }

/* Below ~340px the three columns stop fitting. The sparkline is the decoration, so it goes
   first rather than letting the price wrap. */
@media (max-width: 359px) {
  .sym { grid-template-columns: minmax(0, 1fr) 108px; }
  .spark { display: none; }
}
.sym:active { background: var(--panel-2); }
.sym-name { font-weight: 700; font-size: 1rem; line-height: 1.25; }
.sym-tick { font-size: .72rem; color: var(--green); letter-spacing: .06em; line-height: 1.3; }
.sym-px   { text-align: right; font-variant-numeric: tabular-nums; font-weight: 700; white-space: nowrap;
            font-size: 1.1rem; line-height: 1.25; }
.sym-ch   { text-align: right; font-size: .7rem; font-variant-numeric: tabular-nums; white-space: nowrap;
            line-height: 1.3; }
/* nowrap is load-bearing: "+4,099.76 (+5.32%)" wrapping made the crypto rows 15px taller
   than the equity rows, which is what broke the alignment down the list. */
.up   { color: var(--green); }
.down { color: var(--red); }

/* An arrow to the RIGHT of the price/change pair rather than a full-row flash. It shows the
   direction of the LAST move and stays there until the next one, so a busy symbol reads as
   steady information instead of a strobe -- Bitcoin ticks several times a second.

   It sits LAST in the flex row on purpose. Placed before the values its x-position would shift
   with the width of the price text, leaving the column of arrows ragged down the list; pinned
   to the trailing edge they all line up regardless of whether a price is $324.96 or $77,107.72.
   A fixed width rather than a content-sized one keeps that true even when a row has no arrow
   yet.

   It is sized to span both lines: align-self stretch makes it the full height of the value
   stack no matter how the two lines wrap. */
.sym-right { display: flex; align-items: stretch; gap: .3rem; min-width: 0; overflow: hidden; }
/* flex:1 pins the arrow to the RIGHT EDGE of its track. Without it the arrow sits wherever
   the value text happens to end, so the column of arrows comes out ragged down the list --
   the same defect the sparklines had, from the same cause. */
.sym-vals { display: flex; flex-direction: column; justify-content: center; flex: 1; min-width: 0; }

.tick-arrow {
  display: flex; align-items: center; justify-content: center;
  align-self: stretch; flex: none;
  width: .95rem; font-size: 1.15rem; line-height: 1;
  /* Opacity only. Transitioning COLOUR blends green to red through amber, and a fast-flipping
     symbol then spends much of its time showing an orange arrow -- which reads as a third
     state the game does not have. The direction must switch instantly. */
  opacity: 0; transition: opacity .18s ease-out;
}
.tick-arrow.is-up   { opacity: 1; color: var(--green); }
.tick-arrow.is-down { opacity: 1; color: var(--red); }

.banner {
  border-radius: 10px; padding: .6rem .8rem; margin-bottom: .9rem;
  font-size: .75rem; border: 1px solid;
}
.banner-warn { background: rgba(255,176,32,.08); border-color: rgba(255,176,32,.4); color: #ffca6b; }

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* A phone that happens to be wide, or a small window: centre the column rather than letting
   a 619px viewport stretch a layout drawn for 375. Still the phone shell -- bottom tab bar and
   all -- because the rail does not start until 768. */
@media (min-width: 620px) and (max-width: 767px) {
  main, .topbar { max-width: 620px; margin-inline: auto; }
  .dock { max-width: 620px; margin-inline: auto; }
  .tabbar { border-inline: 1px solid var(--line); }
}

/* ================================================================ desktop shell
   768 and up: the bottom tab bar becomes a left rail.

   The rail IS the tab bar, restyled. Nothing in the routing changes, because the active tab is
   set by querying .tabbar a and that query does not care which way the bar points.

   .dock already holds the legal line and the nav as one unit, in that DOM order.
   flex-direction: column-reverse puts the nav at the top of the rail and leaves the disclaimer
   at its foot -- with no markup change, and without the disclaimer ever leaving the screen.
   It is a compliance element; it does not get to disappear with the tab bar.
   ============================================================================================ */

@media (min-width: 768px) {
  :root {
    --head-h: 64px;
    --rail-w: 74px;         /* tablet: icons only */
    --content-max: 860px;   /* D1 is the shell; individual screens widen in D2-D4 */
  }

  /* Header and rail are both fixed. If only the rail were, scrolling would slide the header up
     and leave a gap above a rail pinned 64px down the viewport. */
  .topbar {
    position: fixed; top: 0; left: 0; right: 0; z-index: 21;
    height: var(--head-h); max-width: none; margin: 0;
    /* The base rule is space-between, which is right for a phone's two-item header. Here it
       shared the slack between EVERY pair, so the tagline drifted into the middle of the bar
       instead of sitting against the wordmark. Pack left; the balance's auto margin is what
       pushes the right-hand group over. */
    flex-wrap: nowrap; justify-content: flex-start; gap: 1rem;
    padding: 0 1.2rem 0 1rem;
    border-bottom: 1px solid var(--line);
    background: linear-gradient(180deg, #080b07, #000);
  }
  /* The DOM order is back, brand, bell, balance, tagline -- built for a phone. Desktop wants
     brand and tagline left, balance and bell right, so the row is reordered rather than the
     markup, which keeps the phone header exactly as it was. */
  /* The phone banner draws the wordmark and the tagline together. Up here they are two
     elements again, laid out beside the balance and the bell, so the one image goes away. */
  .brand-banner { display: none; }

  #navBack { order: 0; }
  .topbar .brand { order: 1; }
  .topbar .tagline {
    flex: 0 1 auto; order: 2; margin: 0; text-align: left;
    padding-left: 1rem; border-left: 1px solid var(--line);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .head-bal { order: 3; }
  #navBell { order: 4; flex: none; }

  .head-bal {
    display: flex; align-items: center; gap: .5rem;
    /* The auto left margin is what pushes the balance and the bell to the right edge, and it
       has to live in the same declaration as the rest of the margin -- a later 'margin: 0'
       silently reset it, which is why the header packed everything left. */
    margin: 0 0 0 auto; padding: .34rem .8rem; flex: none;
  }
  .head-bal small { display: inline; font-size: .52rem; }
  .head-bal strong { display: inline; font-size: 1.02rem; font-variant-numeric: tabular-nums; }
  .head-bal .who { padding-left: .55rem; margin-left: .1rem; border-left: 1px solid var(--green-dim); }

  /* Every screen prints its own balance panel for the phone. One on screen is enough. */
  main .balance { display: none; }

  .dock {
    top: var(--head-h); bottom: 0; left: 0; right: auto;
    width: var(--rail-w); padding-bottom: 0;
    display: flex; flex-direction: column-reverse;
    /* column-reverse, so flex-end is the TOP. At this width the legal line is taken out of
       flow into a footer strip, leaving the nav as the only item -- space-between would drop
       it to the bottom of an otherwise empty rail. */
    justify-content: flex-end;
    border-right: 1px solid var(--line);
  }

  .tabbar {
    /* Back to a row flow: the base rule flows into columns so the bottom bar can size itself
       to however many items the signed-in role sees. A rail stacks instead. */
    grid-auto-flow: row; grid-template-columns: 1fr;
    border-inline: 0;
  }
  /* The lit rule turns ninety degrees with the bar: it edges the rail instead of topping it,
     still brightest at the middle where GAME sits. */
  .tabbar::before {
    top: 0; bottom: 0; left: auto; right: 0; height: auto; width: 1px;
    background: linear-gradient(180deg,
      rgba(143,226,21,.10) 0%, rgba(143,226,21,.55) 50%, rgba(143,226,21,.10) 100%);
    box-shadow: 0 0 10px rgba(143,226,21,.28);
  }
  .tabbar a { height: auto; min-height: 58px; padding: .5rem .25rem; gap: 4px; border-radius: 0; }
  .tabbar a.is-active { background: #0c1207; }
  /* 74px will not hold LEADERBOARD on one line at any readable size, so the labels wrap here
     rather than being dropped. Six unlabelled glyphs is a thing to learn; two short lines is not. */
  .tabbar a span { white-space: normal; text-align: center; line-height: 1.15; }
  .tabbar a[data-tab="leaderboard"] span { font-size: .46rem; letter-spacing: .02em; }

  /* An icon-only rail has no room for three phrases, so the disclaimer becomes a strip across
     the foot of the content instead. It stays fixed, and main reserves the height. */
  .legal {
    position: fixed; left: var(--rail-w); right: 0; bottom: 0; z-index: 20;
    height: var(--legal-h); background: #000;
    border-top: 1px solid var(--line);
  }

  main {
    max-width: none; margin: 0 0 0 var(--rail-w);
    padding: calc(var(--head-h) + 1.1rem) 1.5rem calc(var(--legal-h) + 1.5rem);
  }
  /* The content column, centred in whatever room is left beside the rail.
     A grid track rather than max-width plus auto margins on the children: a child with its own
     margin rule -- .section-head sets 'margin: 1.2rem 0 .7rem' -- is a single class and wins
     the cascade against 'main > *', so half the blocks centred and half did not. The track
     places them without touching their margins at all. */
  main {
    display: grid; grid-template-columns: minmax(0, var(--content-max));
    justify-content: center; align-content: start;
  }

  .toasts { --toast-left: var(--rail-w); --toast-bottom: calc(var(--legal-h) + .75rem); }
  .toast { margin-inline: auto; max-width: 420px; }

  /* Running trades tile instead of stacking. auto-fill against a minimum card width rather than
     a named column count, so the number of columns follows the window and there is no width
     where a card is squeezed below what its countdown and prices need. */
  /* The cards live in a .pos-list inside #gameList, so the grid goes on the list -- putting it
     on the wrapper makes the list itself the only grid item and nothing tiles. */
  #gameList .pos-list {
    display: grid; gap: .6rem;
    grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
    align-items: start;
  }

  /* Rules read at a measure, not across a 980px column. */
  .rulez { max-width: 62ch; }
}

/* 1200 and up: the rail earns its labels, and the disclaimer moves into its foot. */
@media (min-width: 1200px) {
  :root { --rail-w: 236px; --content-max: 980px; }

  /* The legal line comes back into the rail's flow, so the nav goes to the top and the
     disclaimer to the foot -- which is what column-reverse plus space-between gives. */
  .dock { justify-content: space-between; }

  .tabbar a {
    flex-direction: row; justify-content: flex-start;
    gap: .72rem; height: 46px; padding: 0 .9rem;
  }
  .tabbar a span { font-size: .74rem; letter-spacing: .05em; white-space: nowrap; text-align: left; }
  .tabbar a[data-tab="leaderboard"] span { font-size: .74rem; letter-spacing: .05em; }
  .tabbar a svg { width: 20px; height: 20px; }
  .tabbar { padding: .6rem .5rem; }

  .legal {
    position: static; width: var(--rail-w); height: auto;
    flex-direction: column; gap: .1rem;
    border-top: 1px solid var(--line); border-bottom: 0;
    padding: .55rem .5rem calc(.55rem + env(safe-area-inset-bottom));
    font-size: .5rem; white-space: normal; text-align: center;
  }
  .legal span { padding: 0; }
  .legal span + span { border-left: 0; }

  main { padding-bottom: 2.5rem; }
  .toasts { --toast-bottom: 1.25rem; }

}

/* A closed market still shows its last traded price, so the row has to say so -- otherwise a
   frozen number is indistinguishable from a live one. Matters more now that the list mixes
   24/7 crypto with equities that shut at 16:00 ET. */
.closed-tag {
  display: inline-block; margin-left: .4rem; padding: .05rem .3rem;
  border: 1px solid #6b5a1f; border-radius: 4px;
  font-size: .5rem; letter-spacing: .1em; color: #c9a227; vertical-align: 1px;
}
.sym.is-shut .sym-px { opacity: .72; }

/* ---------------------------------------------------------------- trade screen */

/* Window tiers. The uiTier numeral and the actual payout sit together deliberately: the
   marketing "3X" is a points multiplier, so showing it without the real odds would mislead. */
.tiers { display: grid; grid-template-columns: repeat(3, 1fr); gap: .5rem; margin-bottom: 1rem; }
.tier {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); padding: .7rem .4rem; text-align: center; cursor: pointer;
}
.tier.is-selected { border-color: var(--green); background: #131c0c; box-shadow: 0 0 14px var(--green-glow) inset; }
.tier:disabled:not(.is-selected) { opacity: .38; cursor: default; }
.tier .tier-when { display: block; font-size: .55rem; letter-spacing: .1em; color: var(--muted); }
.tier .tier-x    { display: block; font-size: 1.35rem; font-weight: 800; color: var(--green); line-height: 1.1; }
.tier .tier-pay  { display: block; font-size: .58rem; color: var(--muted); }

.stakes { display: grid; grid-template-columns: repeat(4, 1fr); gap: .5rem; margin-bottom: .5rem; }
.chip {
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel-2);
  padding: .65rem .2rem; font-size: .78rem; font-weight: 700; cursor: pointer;
  font-variant-numeric: tabular-nums;
}
.chip.is-selected { border-color: var(--green); color: var(--green); background: #131c0c; }

.calls { display: grid; grid-template-columns: 1fr 1fr; gap: .6rem; margin: 1rem 0 .8rem; }
.call {
  border-radius: var(--radius); padding: 1.1rem .5rem; cursor: pointer;
  background: #08120a; border: 2px solid var(--green); color: var(--green);
  font-weight: 900; font-size: 1.45rem; letter-spacing: .04em;
}
.call small { display: block; font-size: .58rem; font-weight: 500; letter-spacing: .04em; color: var(--muted); }
.call-down { background: #150708; border-color: var(--red); color: var(--red); }
/* Selection is signalled by background and glow, NOT by a thicker border: changing
   border-width reflows the button by a pixel on each side, which reads as a jump. */
.call.is-selected      { background: #14300f; box-shadow: 0 0 22px var(--green-glow), inset 0 0 18px rgba(143,226,21,.18); }
.call-down.is-selected { background: #2a0b0c; box-shadow: 0 0 22px var(--red-glow),  inset 0 0 18px rgba(255,45,45,.18); }
.call:disabled:not(.is-selected) { opacity: .4; cursor: default; box-shadow: none; }

.activate { text-align: center; border: 1px solid var(--green-dim); border-radius: var(--radius);
            background: linear-gradient(180deg, #0d150a, #060906); padding: .9rem; }
.activate small { display: block; font-size: .6rem; letter-spacing: .16em; color: var(--green); }
.activate .clock { font-size: 2.6rem; font-weight: 800; color: var(--green);
                   font-variant-numeric: tabular-nums; letter-spacing: .04em;
                   text-shadow: 0 0 18px var(--green-glow); }
.activate .note { font-size: .62rem; color: var(--muted); }

/* ---------------------------------------------------------------- running positions */

/* A symbol takes any number of trades, so this is a list rather than a single panel, and each
   row owns its own countdown and its own cancel. Sits under the chart because the entry prices
   are read against the price above it. */
.positions { margin-bottom: 1rem; }
.pos-head {
  display: flex; justify-content: space-between; gap: .5rem;
  font-size: .55rem; letter-spacing: .16em; color: var(--muted); margin-bottom: .35rem;
}
.pos-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .4rem; }
.pos {
  display: grid; grid-template-columns: 3.4rem minmax(0, 1fr) auto 1.6rem;
  align-items: center; gap: .5rem;
  border: 1px solid var(--line); border-left-width: 3px; border-radius: 10px;
  background: var(--panel); padding: .5rem .55rem;
}
.pos.is-up   { border-left-color: var(--green); }
.pos.is-down { border-left-color: var(--red); }
.pos-dir { font-size: .68rem; font-weight: 800; letter-spacing: .04em; white-space: nowrap; }
.pos-win { display: block; font-size: .5rem; font-weight: 600; letter-spacing: .12em; color: var(--muted); }
.pos.is-up   .pos-dir, .pos.is-up   .pos-cd { color: var(--green); }
.pos.is-down .pos-dir, .pos.is-down .pos-cd { color: var(--red); }
.pos-info { min-width: 0; display: flex; flex-direction: column; gap: .1rem; }
.pos-l1 { font-size: .62rem; color: var(--muted); font-variant-numeric: tabular-nums;
          white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Ellipsis rather than overflow: without it a long entry price slides under the countdown and
   the two overlap, which looks like a rendering fault rather than a tight fit. */
.pos-l2 { font-size: .62rem; color: var(--muted); font-variant-numeric: tabular-nums;
          white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pos-l2 b { color: var(--text); font-weight: 700; }
.pos-cd { font-size: 1rem; font-weight: 800; font-variant-numeric: tabular-nums; letter-spacing: .03em; }
/* Quiet by default: cancelling costs the user their stake, so it should be reachable without
   inviting a tap. The tap target stays a full 44px tall even though the glyph is small. */
.pos-x {
  width: 1.6rem; height: 100%; min-height: 2.2rem; padding: 0; cursor: pointer;
  background: none; border: none; color: #6b4a49; font-size: .8rem; line-height: 1;
}
.pos-x:active { color: var(--red); }
.pos-x:disabled { opacity: .4; cursor: default; }

/* ---------------------------------------------------------------- result reveal */

.result {
  position: fixed; inset: 0; z-index: 40;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .5rem; padding: 1.5rem; text-align: center;
  background: radial-gradient(circle at 50% 38%, #0d1a06 0%, #000 70%);
  animation: fadeIn .22s ease-out;
}
.result.is-lose { background: radial-gradient(circle at 50% 38%, #1a0606 0%, #000 70%); }
.result.is-push { background: radial-gradient(circle at 50% 38%, #14140a 0%, #000 70%); }
@keyframes fadeIn { from { opacity: 0 } to { opacity: 1 } }

.result h2 {
  margin: 0; font-size: 2.6rem; font-weight: 900; letter-spacing: .02em;
  color: var(--green); text-shadow: 0 0 26px var(--green-glow);
}
.result.is-lose h2 { color: var(--red); text-shadow: 0 0 26px var(--red-glow); }
.result.is-push h2 { color: #e2c341; text-shadow: 0 0 26px rgba(226,195,65,.35); }
.result p.sub { margin: 0 0 .6rem; font-size: .85rem; color: var(--text); }

.result .amount {
  border: 1px solid var(--green-dim); border-radius: var(--radius);
  padding: .8rem 1.6rem; margin: .4rem 0 1rem;
  font-size: 2rem; font-weight: 800; color: var(--green);
  font-variant-numeric: tabular-nums;
}
.result.is-lose .amount { border-color: var(--red); color: var(--red); }
.result.is-push .amount { border-color: #6b5a1f; color: #e2c341; }
.result .amount small { display: block; font-size: .55rem; letter-spacing: .16em; color: var(--muted); font-weight: 500; }

.result .prices { font-size: .78rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.result .prices b { color: var(--text); }
.result .btn { max-width: 320px; }

/* ---------------------------------------------------------------- history & profile */

.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: .5rem; margin-bottom: 1rem; }
.stat {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); padding: .7rem .4rem; text-align: center;
}
/* nowrap matters: "-V$400.00" is wide enough to wrap in a third-of-screen column, and it
   breaks after the minus sign, leaving a stray "-" hanging on its own line above the number.
   Scaling with the viewport keeps it on one line down to a 320px phone. */
.stat b {
  display: block; font-variant-numeric: tabular-nums; white-space: nowrap;
  font-size: clamp(.82rem, 4.2vw, 1.25rem);
}
.stat small { display: block; font-size: .5rem; letter-spacing: .12em; color: var(--muted); margin-top: .15rem; }

/* One row per settled trade. Grid rather than flex so the outcome chip, the symbol and the
   money all line up down the list regardless of how long a symbol name is. */
.hrow {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: .7rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); padding: .7rem .85rem; margin-bottom: .5rem;
}
.hchip {
  width: 2.6rem; text-align: center; padding: .25rem 0; border-radius: 6px;
  font-size: .52rem; font-weight: 800; letter-spacing: .06em;
}
.hchip.win  { background: rgba(143,226,21,.16); color: var(--green); }
.hchip.lose { background: rgba(255,45,45,.14);  color: var(--red); }
.hchip.push { background: rgba(226,195,65,.14); color: #e2c341; }
.hchip.void { background: rgba(140,150,134,.14); color: var(--muted); }

.hrow .hsym  { font-weight: 700; font-size: .88rem; }
.hrow .hmeta { font-size: .62rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.hrow .hamt  { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
.hrow .hamt small { display: block; font-size: .58rem; font-weight: 400; color: var(--muted); }

/* Headings for the wide table. The row exists at every width so a resize never rebuilds the
   list; below 1200 it is simply not shown. */
.hhead { display: none; }

/* Layout wrappers, transparent until a screen is wide enough to use them. */
.me-main, .me-side, .trade-main, .trade-side, .home-main { display: contents; }
/* The home detail panel is empty below its breakpoint, so it takes no room and needs no rule. */

.empty { text-align: center; padding: 2rem 1rem; color: var(--muted); font-size: .85rem; }

/* Notifications drop over the page rather than replacing the view, so the bell never loses
   your place in the market list. */
.notif-wrap {
  position: fixed; inset: 0; z-index: 30; background: rgba(0,0,0,.72);
  display: flex; align-items: flex-start; justify-content: center; padding: 4.5rem 1rem 1rem;
}
.notif-panel {
  width: 100%; max-width: 460px; max-height: 75vh; overflow-y: auto;
  border: 1px solid var(--line); border-radius: var(--radius); background: #070a06;
}
.notif-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: .8rem 1rem; border-bottom: 1px solid var(--line);
  font-size: .62rem; letter-spacing: .16em; color: var(--muted);
}
.notif-head button {
  background: none; border: 0; color: var(--green); cursor: pointer;
  font-size: .6rem; letter-spacing: .1em;
}
.notif { padding: .75rem 1rem; border-bottom: 1px solid #131a12; }
.notif:last-child { border-bottom: 0; }
.notif.is-unread { background: rgba(143,226,21,.045); }
.notif b { display: block; font-size: .82rem; }
.notif p { margin: .2rem 0 0; font-size: .74rem; color: var(--muted); }
.notif time { display: block; margin-top: .25rem; font-size: .58rem; color: #5d6857; }

/* ---------------------------------------------------------------- trade flow state */

/* The screen no longer locks while a trade runs, so the old "fade the roads not taken" state
   is gone with it: a control is dimmed only when it is genuinely unavailable. */
.tier:disabled, .chip:disabled, .call:disabled { cursor: default; }

/* Custom wager. Presets cover the common cases; this covers the rest without a keypad shim. */
.custom-row {
  display: flex; align-items: center; gap: .5rem; margin-bottom: .6rem;
  border: 1px solid var(--green-dim); border-radius: 10px;
  background: var(--panel-2); padding: .2rem .2rem .2rem .8rem;
}
.custom-prefix { color: var(--green); font-weight: 700; }
.custom-row input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  padding: .7rem 0; font-size: 1.05rem; font-weight: 700; font-variant-numeric: tabular-nums;
}
.custom-row input::placeholder { color: #55604f; font-weight: 400; }
.custom-row .chip { flex: none; padding: .55rem .8rem; font-size: .62rem; }
.chip-custom { letter-spacing: .06em; }

/* ACTIVATE is a real button now, and becomes the countdown once a trade is live. */
button.activate {
  display: block; width: 100%; cursor: pointer;
  border: 1px solid var(--green-dim); border-radius: var(--radius);
  background: linear-gradient(180deg, #0d150a, #060906);
  padding: .9rem; text-align: center; color: inherit;
}
button.activate:disabled { cursor: default; }
/* Not .is-live: a disabled ACTIVATE means "you have not finished choosing", which should look
   inert. A live countdown is disabled too but must stay bright. */
button.activate:disabled:not(.is-live) { opacity: .45; }
button.activate:active:not(:disabled) { transform: translateY(1px); }

button.activate small { display: block; font-size: .6rem; letter-spacing: .16em; color: var(--green); }
button.activate .clock {
  display: block; font-size: 2.6rem; font-weight: 800; color: var(--green);
  font-variant-numeric: tabular-nums; letter-spacing: .04em; line-height: 1.15;
  text-shadow: 0 0 18px var(--green-glow);
}
button.activate .note { display: block; font-size: .62rem; color: var(--muted); }

.activate.is-live { border-color: var(--green); box-shadow: 0 0 26px var(--green-glow); }
.activate.is-live .clock { animation: pulse 1.6s ease-in-out infinite; }
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .72 } }

/* ---------------------------------------------------------------- price chart */

.chart-wrap {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); padding: .7rem .8rem .5rem; margin-bottom: 1rem;
}
.chart-head { display: flex; align-items: flex-start; justify-content: space-between; gap: .5rem; }

/* The chart header replaced the old price card, so it carries the name, the live price and the
   change. min-height reserves the third line: without it the panel jumps as the change text
   appears on the first tick. */
.chart-caption { display: flex; flex-direction: column; gap: 1px; min-height: 3.1rem; }
.chart-caption .cname  { font-size: .92rem; font-weight: 800; letter-spacing: .01em; }
.chart-caption .cname em { font-style: normal; font-weight: 600; font-size: .72rem; color: var(--green); }
.chart-caption .cprice { font-size: 1.3rem; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.15; }
.chart-caption .cchg   { font-size: .7rem; font-variant-numeric: tabular-nums; }

.chart-ranges { display: flex; gap: .25rem; flex: none; }
.crange {
  border: 1px solid var(--line); border-radius: 7px; background: var(--panel-2);
  padding: .3rem .5rem; font-size: .56rem; font-weight: 700; letter-spacing: .06em;
  color: var(--muted); cursor: pointer;
}
.crange[aria-pressed="true"] { border-color: var(--green); color: var(--green); background: #131c0c; }

/* IMMEDIATELY after the rules it overrides, which is this file's standing rule for a
   narrow-width block: equal specificity means source order decides, and a media query does not
   win for being the more specific context.

   The header is the caption beside the five ranges, and below about 354px those two want more
   room than there is: measured at 320, the caption needs 113 and the ranges 175 inside 262, so
   the row ran 6px past the viewport and scrolled the whole page sideways.

   The ranges take their own line rather than the buttons being shrunk to fit. They are already
   31px wide against the 44px thumb target this app uses everywhere else, so making them smaller
   at the smallest width is the wrong direction -- on their own line they can stretch instead,
   which makes them easier to hit here than they are at any other width. */
@media (max-width: 359px) {
  .chart-head { flex-wrap: wrap; }
  .chart-ranges { flex: 1 0 100%; margin-top: .45rem; }
  .crange { flex: 1 1 auto; text-align: center; }
}

.chart-box { position: relative; margin-top: .3rem; }

/* The SVG carries its own width/height attributes from the measured container, so it scales
   1:1 with no stretching -- the same drawing code will serve a desktop layout. touch-action
   none keeps a drag across the series from scrolling the page instead of scrubbing. */
.cchart { display: block; width: 100%; touch-action: none; }
/* Reserve the space before the first draw so the panel does not jump on load. */
.chart-box { min-height: 150px; }
.cgrid  { stroke: #1b241a; stroke-width: .5; }
.cax    { fill: #6c7565; font-size: 8px; font-family: inherit; }
.chair  { stroke: #8c9686; stroke-width: .7; stroke-dasharray: 2 2; }

.ctip {
  position: absolute; top: 2px; transform: translateX(-50%);
  background: #0c120a; border: 1px solid var(--green-dim); border-radius: 7px;
  padding: .25rem .5rem; font-size: .6rem; white-space: nowrap;
  font-variant-numeric: tabular-nums; pointer-events: none;
}
.chart-empty { padding: 3rem 1rem; text-align: center; color: var(--muted); font-size: .78rem; }
.chart-note  { margin: .2rem 0 0; text-align: right; font-size: .52rem; letter-spacing: .08em; color: #5d6857; }

/* Current-price pill, pinned to the right edge of the plot at the live price.
   Green or red by the direction of the last move, matching the arrows in the symbol list. */
.cpill-box  { fill: #8fe215; }
.cpill-tail { fill: #8fe215; }
.cpill-txt  { fill: #06140a; font-size: 8.5px; font-weight: 700; text-anchor: middle; font-family: inherit; }
.cpill.is-down .cpill-box, .cpill.is-down .cpill-tail { fill: var(--red); }
.cpill.is-down .cpill-txt { fill: #ffffff; }

/* ---------------------------------------------------------------- live trade badge */

/* Spans every column, so it does not have to fight the three-column budget above it. The row
   only grows when a trade is actually running -- the compact height is the normal state. */
.sym-live {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: .5rem;
  margin-top: .5rem; padding-top: .45rem;
  border-top: 1px solid var(--line);
  font-size: .68rem; letter-spacing: .04em;
}
.sym-live .live-dir   { font-weight: 800; letter-spacing: .06em; }
.sym-live .live-stake { color: var(--muted); font-variant-numeric: tabular-nums; }
/* Pushed to the trailing edge and tabular, so the digits do not shuffle as it counts down. */
.sym-live .live-cd {
  margin-left: auto; font-weight: 800; font-size: .88rem;
  font-variant-numeric: tabular-nums; letter-spacing: .04em;
}

/* The expand control. It carries the count, so it reads as the label it replaced rather than as
   a control bolted onto one -- and the whole thing is the tap target, not just the caret. */
.live-more {
  display: inline-flex; align-items: center; gap: .3rem;
  background: none; border: 0; padding: .1rem .2rem .1rem 0; margin: -.1rem 0;
  font: inherit; font-weight: 800; letter-spacing: .06em; color: inherit; cursor: pointer;
}
.live-caret { font-size: .7em; opacity: .85; }
.live-more:active { opacity: .6; }

/* Per-trade rows, revealed under the summary. Same .pos markup as the trade screen, so the two
   surfaces cannot drift apart; only the spacing is tightened for a list row. */
.sym-open {
  grid-column: 1 / -1;
  margin-top: .45rem;
}
.sym-open .pos { padding: .42rem .5rem; background: var(--panel-2); }
.sym-open .pos-cd { font-size: .9rem; }

/* These rows sit inside the panel's own padding, so they have ~10px less to work with than the
   same row on the trade screen, and at 320px the win amount was the part that clipped. It is
   the derived number of the four, and the trade screen still shows it in full. */
@media (max-width: 359px) {
  .sym-open .pos-w { display: none; }
}

/* Reserves the width the cancel button occupies, so rows without one still line up. */
.pos-x-gap { display: block; width: 1.6rem; }

/* ---------------------------------------------------------------- GAME tab cards */

/* The same row, one size up, with the symbol named. It appears here away from anything else
   that says which symbol it belongs to, so the header line is the only real addition. */
/* Wider first column than the compact row: the direction text is a size up here, and at 3.4rem
   it sat hard against the stake. */
.pos.is-card {
  grid-template-columns: 4.6rem minmax(0, 1fr) auto 1.6rem;
  padding: .7rem .7rem .6rem; row-gap: .45rem; border-left-width: 4px;
}
.pos-sym {
  grid-column: 1 / -1;
  display: flex; align-items: baseline; justify-content: space-between; gap: .5rem;
  padding-bottom: .45rem; border-bottom: 1px solid var(--line);
  font-size: .95rem; font-weight: 700; color: var(--text);
}
.pos-sym em { font-style: normal; font-size: .68rem; letter-spacing: .04em; color: var(--green-dim); }
/* Live price, so "am I ahead of my entry" is a glance rather than a trip to the trade screen. */
.pos-sym b { font-variant-numeric: tabular-nums; font-weight: 800; white-space: nowrap; }
.pos.is-card .pos-dir { font-size: .8rem; }
.pos.is-card .pos-l1, .pos.is-card .pos-l2 { font-size: .7rem; }
/* Stake and win get a line each rather than sharing one. An end-of-day countdown reads
   "13h 09m", which is wide enough that the shared line wrapped mid-phrase at every width -- and
   a wrap that lands after the word "win" reads as a mistake. Two short lines always fit. */
.pos.is-card .pos-l1 { white-space: normal; }
.pos.is-card .pos-w { display: block; }
.pos.is-card .pos-w i { display: none; }
.pos.is-card .pos-cd { font-size: 1.2rem; }
.pos-list .pos.is-card + .pos.is-card { margin-top: 0; }
.pos.is-card .pos-x { min-height: 2.6rem; }

/* Narrow phones. MUST come after the card rules above: same specificity, so source order is
   what decides, and sitting earlier in the file made these silently lose. The direction column
   gives back the width it borrowed and the clock drops a size, because an end-of-day countdown
   reads "13h 11m" and was taking 70px of a 288px card. */
@media (max-width: 359px) {
  .pos.is-card { grid-template-columns: 3.4rem minmax(0, 1fr) auto 1.6rem; }
  .pos.is-card .pos-w { display: none; }
  .pos.is-card .pos-dir { font-size: .72rem; }
  .pos.is-card .pos-cd { font-size: 1rem; }
}

/* A lit left edge makes a row with a live trade findable in the list without reading it. */
.sym.has-trade { border-color: var(--green-dim); }
.sym.is-up-trade   { box-shadow: inset 3px 0 0 var(--green); }
.sym.is-down-trade { box-shadow: inset 3px 0 0 var(--red); }
.sym.is-up-trade   .live-dir, .sym.is-up-trade   .live-cd { color: var(--green); }
.sym.is-down-trade .live-dir, .sym.is-down-trade .live-cd { color: var(--red); }
.sym.is-down-trade { border-color: rgba(255, 45, 45, .45); }
/* Both directions running on one symbol. It gets its own colour rather than borrowing green or
   red, because either of those would claim an exposure the row does not actually have. */
.sym.is-mixed-trade { border-color: rgba(226, 195, 65, .45); box-shadow: inset 3px 0 0 #e2c341; }
.sym.is-mixed-trade .live-dir, .sym.is-mixed-trade .live-cd { color: #e2c341; }

/* On a narrow phone the stake line is the first thing worth dropping: the direction and the
   clock are what the badge is for. */
@media (max-width: 359px) {
  .sym-live .live-stake { display: none; }
}

/* Entry price. It is the level the trade is judged against, so it reads as a value rather than
   as a label -- the number is emphasised and the word beside it is not. */
.sym-live .live-entry { color: var(--muted); font-variant-numeric: tabular-nums; }
.sym-live .live-entry b { color: var(--text); font-weight: 700; }
.sym-live .live-stake { flex: none; }

button.activate .note b.live-entry-px { color: var(--text); font-weight: 700; font-variant-numeric: tabular-nums; }
button.activate .note .note-sep { color: #4a5344; margin: 0 .35rem; }

/* On a narrow phone the stake is the first thing to go: direction, entry and the clock are
   what the badge exists to answer. */
@media (max-width: 359px) {
  .sym-live .live-stake { display: none; }
}

/* ---------------------------------------------------------------- cancel trade */

/* Slim, quiet, and outlined rather than filled. It is an escape hatch that costs the user
   their stake, so it should be findable without inviting a tap. */
.cancel-btn {
  display: block; width: 100%; margin-top: .5rem;
  padding: .55rem 1rem; cursor: pointer;
  background: none; border: 1px solid #4a2020; border-radius: 9px;
  color: #b9736f; font-size: .62rem; font-weight: 700; letter-spacing: .14em;
}
.cancel-btn:active:not(:disabled) { background: #1a0b0b; }
.cancel-btn:disabled { opacity: .5; cursor: default; }

.confirm-wrap {
  position: fixed; inset: 0; z-index: 50;
  display: flex; align-items: center; justify-content: center; padding: 1.25rem;
  background: rgba(0, 0, 0, .78);
}
.confirm-card {
  width: 100%; max-width: 380px;
  border: 1px solid var(--red); border-radius: var(--radius);
  background: #0b0808; padding: 1.2rem 1.1rem;
}
.confirm-card h3 { margin: 0 0 .5rem; font-size: 1.1rem; letter-spacing: .01em; }
.confirm-card p  { margin: 0 0 1.1rem; font-size: .82rem; line-height: 1.5; color: var(--muted); }
.confirm-card p b { color: var(--text); }

/* KEEP first and filled, FORFEIT second and outlined: the reflex tap should be the safe one. */
.confirm-actions { display: flex; flex-direction: column; gap: .5rem; }
.confirm-actions .btn { margin-top: 0; }
.btn-danger {
  background: none; border: 1px solid var(--red); color: var(--red);
  box-shadow: none;
}
.btn-danger:active { background: #1e0a0a; }

/* History chip for a cancelled trade -- a loss, but not a bad call. */
.hchip.cancel { background: rgba(185, 115, 111, .16); color: #b9736f; }

/* ---------------------------------------------------------------- settlement toasts */

/* The quiet reveal. A full-screen takeover is right when the settled trade was the only thing
   running on the symbol you are looking at; every other case gets one of these instead, so a
   result landing while you pick your next wager does not seize the screen.

   Sits ABOVE the dock rather than at bottom:0, or it would cover the legal line and the nav. */
.toasts {
  position: fixed; z-index: 35;
  left: var(--toast-left, 0); right: 0; bottom: var(--toast-bottom, calc(var(--tabbar-h) + var(--legal-h) + .5rem));
  display: flex; flex-direction: column; align-items: center; gap: .4rem;
  padding: 0 1rem; pointer-events: none;
}
.toast {
  pointer-events: auto; cursor: pointer;
  width: 100%; max-width: 380px;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: .5rem;
  border: 1px solid var(--green-dim); border-radius: 10px;
  background: #0b120a; padding: .6rem .7rem;
  box-shadow: 0 6px 24px rgba(0, 0, 0, .6);
  animation: toast-in .18s ease-out;
}
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .toast { animation: none; } }

.toast-head { font-size: .62rem; font-weight: 800; letter-spacing: .1em; color: var(--green); }
.toast-sym  { font-size: .62rem; color: var(--muted); white-space: nowrap;
              overflow: hidden; text-overflow: ellipsis; }
.toast-amt  { font-size: .95rem; font-weight: 800; color: var(--green);
              font-variant-numeric: tabular-nums; white-space: nowrap; }

.toast.is-lose { border-color: rgba(255, 45, 45, .5); background: #120a0a; }
.toast.is-lose .toast-head, .toast.is-lose .toast-amt { color: var(--red); }
.toast.is-push { border-color: rgba(226, 195, 65, .45); background: #12110a; }
.toast.is-push .toast-head, .toast.is-push .toast-amt { color: #e2c341; }

/* ---------------------------------------------------------------- the name editor (ME page) */

/* Read-only until CHANGE is tapped. The two rows occupy the same slot, so the card does not
   resize when it opens -- a card that grows under the thumb makes the SAVE button move. */
.namerow { display: flex; align-items: center; gap: .5rem; }
.namerow .btn { margin-top: 0; }
.nameval {
  flex: 1; min-width: 0; font-size: 1.05rem; font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#nameInput {
  flex: 1; min-width: 0; border: 1px solid var(--green-dim); border-radius: 10px;
  background: var(--panel-2); color: var(--text); outline: none;
  padding: .6rem .7rem; font-size: 1rem; font-weight: 700; font-family: inherit;
}
#nameInput:focus { border-color: var(--green); }

/* .btn is a full-width block by default, which is right for SIGN OUT and wrong beside a value. */
.btn-sm { display: inline-block; width: auto; flex: none; padding: .6rem .85rem; font-size: .66rem; }
.btn-ghost { background: none; border: 1px solid var(--line); color: var(--muted); box-shadow: none; }
.btn-ghost:active { background: var(--panel-2); }

/* The hint doubles as the error line, so a rejected name explains itself where the rule is.
   .hint carries a NEGATIVE top margin for the sign-in form, where it tucks under a field label;
   inside this card that pulled it up over the input. */
#nameHint { margin: .7rem 0 0; line-height: 1.5; }
.hint.is-error { color: #ff8b8b; }

/* Immediately after the rules it overrides -- same specificity, so source order decides.
   At 320px the input keeps only ~100px beside SAVE and CANCEL, which is not enough to read a
   24-character name back. Give it the full row and let the buttons sit under it. */
@media (max-width: 360px) {
  #nameEdit { flex-wrap: wrap; }
  #nameEdit #nameInput { flex: 1 0 100%; }
}


/* ================================================================ wide-screen screens (D2)

   AT THE END OF THE FILE ON PURPOSE. Every rule below overrides a base rule of the SAME
   specificity -- .hhead, .me-main, .me-side -- and those base rules are declared further down
   the file than the shell's media blocks are. Equal specificity means source order decides, so
   a media query sitting above them loses despite being the more specific *context*. This file
   has already lost a day to that once, with a narrow-width block that sat before the card rules
   it was written to override.
   ============================================================================================ */

@media (min-width: 1200px) {
/* ---- history becomes a table -------------------------------------------------------------
   Same markup as the phone. The two wrappers go transparent, which promotes their children to
   grid items of the row, so five columns line up down the list without a second renderer.
   One row builder, one place for a bug to live. */
.hmain, .hrow .hamt { display: contents; }
.hrow {
  grid-template-columns: 3.4rem minmax(8.5rem, 1fr) minmax(0, 1.6fr) 6.5rem 7rem;
  gap: .8rem; margin-bottom: .3rem; padding: .55rem .9rem;
}
.hhead {
  display: grid; background: none; border-color: transparent;
  padding-block: 0; margin-bottom: .1rem;
}
.hhead span {
  font-size: .53rem; letter-spacing: .16em; color: var(--muted);
}
.hhead span:nth-child(4), .hhead span:nth-child(5) { text-align: right; }

/* The MOVE cell holds the price move AND the spend button, side by side. That cell is the
   widest in the row and the move text uses about half of it, so the button costs no row height
   -- and, more to the point, the button stays INSIDE a cell. Promoted to a grid item of its own
   it became a sixth column: NET and RISKED each slid one place right and RISKED wrapped onto a
   second line, taking the row from 41px to 104px. The wrapper is emitted on every row so the
   rows without a button are laid out by the same rule. */
.hmove { display: flex; align-items: center; gap: .8rem; min-width: 0; }
/* Trimmed only here, and only enough: the phone's button keeps its full padding because it is a
   thumb target stacked under the move. At 1200 it sits in a line of text, and at its phone size
   it stood 24px against the row's 23px of content -- so every resettable row was 2px taller than
   every other one. The most generous padding that still measures 41px, matching them exactly. */
.hmove .btn-reset {
  flex: 0 0 auto; margin-top: 0;
  padding: .22rem .5rem; line-height: 1.1;
}
/* min-width:0 is what lets a flex item shrink below its content and ellipsise; without it the
   nowrap below would push the button out of the cell instead. */
.hrow .hmeta { font-size: .68rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
               flex: 1 1 auto; min-width: 0; }
.hrow .hnet  { text-align: right; font-weight: 700; }
.hrow .hamt small {
  display: block; text-align: right; color: var(--muted);
  font-size: .68rem; font-weight: 400;
}

/* ---- ME splits into record-and-history, then account -------------------------------------
   Two tracks instead of one, adding up to the same content measure. Nothing touches main's
   margins: the rail offset lives there, and margin-inline:auto would quietly undo it.
   Scoped with :has so only the screen carrying those wrappers gets a second column. */
main:has(.me-side) {
  /* A wider measure than the one-column screens use, because this one is two columns and the
     history table needs the room -- squeeze it and the price move is the column that loses. */
  --content-max: 1120px;
  grid-template-columns: minmax(0, calc(var(--content-max) - 336px)) 320px;
  column-gap: 1.6rem;
}
.me-main, .me-side { display: block; }
.me-main {
  grid-column: 1; grid-row: 1;
  padding-right: 1.6rem; border-right: 1px solid var(--line);
}
.me-side { grid-column: 2; grid-row: 1; }
.me-main > .section-head:first-child, .me-side > .section-head:first-child { margin-top: 0; }

/* ---- the trade screen: chart left, placement right --------------------------------------
   The numbered order the phone established -- boost, amount, call, activate -- survives intact
   in a narrow column, so nothing about the flow has to be relearned. The running trades stay
   under the chart rather than beside it, because they are read AGAINST the price above them. */
main:has(.trade-side) {
  --content-max: 1180px;
  grid-template-columns: minmax(0, 1fr) 316px;
  column-gap: 1.4rem;
}
.trade-main, .trade-side { display: block; }
.trade-main { grid-column: 1; grid-row: 2; }
.trade-side { grid-column: 2; grid-row: 2; }
/* A feed warning is about the whole screen, so it spans both columns and sits above them.
   It is usually hidden, and an empty auto row is zero tall, so this costs nothing when it is. */
main:has(.trade-side) > .banner { grid-column: 1 / -1; grid-row: 1; }

.trade-side > .section-head:first-child { margin-top: 0; }
/* 316px is enough for the flow but not for its full-size type. */
.trade-side .section-head { font-size: .58rem; letter-spacing: .14em; margin: 1rem 0 .6rem; }
.trade-side .tier-pay { font-size: .48rem; }
.trade-side .tier-x { font-size: 1.35rem; }

/* Running trades pair up under the chart once there is width for two. */
#positions .pos-list {
  display: grid; gap: .5rem; align-items: start;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}

/* ---- home: the list beside the selected symbol's chart -----------------------------------
   The list keeps its own width rather than sharing the slack, because a symbol row is a fixed
   composition -- name, sparkline, price, arrow -- and stretching it only pads the gaps. */
main:has(.home-side) {
  --content-max: 1180px;
  grid-template-columns: 430px minmax(0, 1fr);
  column-gap: 1.4rem;
}
.home-main { display: block; grid-column: 1; grid-row: 2; }
.home-side { grid-column: 2; grid-row: 2; }
main:has(.home-side) > .banner { grid-column: 1 / -1; grid-row: 1; }
.home-main > .section-head:first-child { margin-top: 0; }

/* The panel is a column: chart, whatever is running on this symbol, then the way in. */
.home-side { display: flex; flex-direction: column; gap: .7rem; }
.home-side .chart-wrap { margin: 0; }
.home-side .positions { margin: 0; }
.home-trade { margin-top: auto; text-align: center; }

/* The selected row. Same green edge the app uses for "this is the one", not a new idiom. */
.sym.is-sel { border-color: var(--green-dim); background: #0c1207; }
}

/* ---------------------------------------------------------------- admin

   A label/value list rather than more stat tiles. These are diagnostics an operator reads down
   the column, and a big-number tile each would claim they all matter equally. */
.admin-kv {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
  padding: .5rem 0; border-bottom: 1px solid var(--line);
}
.admin-kv:last-child { border-bottom: 0; }
.admin-kv span { color: var(--muted); font-size: .78rem; letter-spacing: .04em; }
.admin-kv b { font-size: .85rem; text-align: right; overflow-wrap: anywhere; }

/* ---- accounts ----------------------------------------------------------------------------
   Same three-column shape as a symbol row -- identity, numbers, a tag -- because it is read the
   same way: scanned down the identity column until something looks wrong. */
.urow {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center;
  gap: .6rem; padding: .6rem .8rem; margin-bottom: .4rem;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  color: var(--text);
}
.urow:active { background: var(--panel-2); }
/* A disabled account is dimmed but never hidden: it is still an account, and finding it again
   is the whole reason re-enabling exists. */
.urow.is-off { opacity: .55; border-style: dashed; }
.urow-id { min-width: 0; display: flex; flex-direction: column; gap: .15rem; }
.urow-id b { font-size: .95rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.urow-id small, .urow-amt small { font-size: .66rem; }
.urow-amt { text-align: right; display: flex; flex-direction: column; gap: .15rem; white-space: nowrap; }
.urow-amt b { font-size: .9rem; font-variant-numeric: tabular-nums; }
.urow-tag:empty { display: none; }

.tag {
  display: inline-block; padding: .2rem .45rem; border-radius: 6px;
  font-size: .55rem; font-weight: 700; letter-spacing: .08em; white-space: nowrap;
  border: 1px solid var(--line); color: var(--muted);
}
.tag-off { border-color: #4a1c1c; color: var(--red); }
.tag-admin { border-color: var(--green-dim); color: var(--green); }

.backlink { font-size: .68rem; letter-spacing: .08em; color: var(--muted); }

/* ---- an audit or ledger entry -------------------------------------------------------------
   Three lines, in the order the questions get asked: what changed, why, then who and when. */
.audit {
  padding: .55rem .8rem; margin-bottom: .4rem;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
}
.audit-l1 { display: flex; align-items: baseline; gap: .5rem; }
.audit-l1 b { font-size: .8rem; letter-spacing: .04em; }
.audit-l1 span { font-size: .7rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.audit-l2 { font-size: .74rem; margin-top: .15rem; overflow-wrap: anywhere; }
.audit-l3 { font-size: .64rem; margin-top: .15rem; }

/* ---- the action forms ---------------------------------------------------------------------
   Each action is its own block with its own required reason, rather than one form with a mode
   selector: the reason belongs to the thing being done, and sharing one field between three
   actions is how a disable ends up filed under a rename's explanation. */
.admin-act { padding: .7rem 0; border-bottom: 1px solid var(--line); }
.admin-act:last-child { border-bottom: 0; padding-bottom: 0; }
.admin-act > label {
  display: block; font-size: .6rem; font-weight: 700; letter-spacing: .1em;
  color: var(--muted); margin-bottom: .45rem;
}
.admin-act input { width: 100%; margin-top: .45rem; }
.admin-act .namerow input { margin-top: 0; }
.admin-act .namerow .btn { flex: none; }
.admin-act .btn { margin-top: .45rem; }
.admin-act .hint { margin-top: .35rem; }

@media (max-width: 359px) {
  /* Three controls on one line stops fitting; the buttons take their own row rather than the
     amount field shrinking to something nobody can read a number in. */
  .admin-act .namerow { flex-wrap: wrap; }
  .admin-act .namerow input { flex: 1 0 100%; }
}

/* Window toggles. Checkboxes rather than chips: these are independent on/off states, and the
   chip idiom in this app means "pick one of these", which is the wrong promise here. */
.setwins { display: flex; flex-wrap: wrap; gap: .5rem .9rem; margin-bottom: .5rem; }
.setwin { display: flex; align-items: center; gap: .35rem; font-size: .78rem; }
.setwin input { width: auto; margin: 0; accent-color: var(--green); }

/* ---- the account page's tab strip, header and filters -------------------------------------
   Declared at the end of the file for the same reason the wide-screen screen rules are: equal
   specificity means source order decides, and these sit over .card and .btn rules further up. */

/* One line of identity that survives every tab, so an admin reading a ledger never has to go
   back to check whose it is. Same three columns as a row in the account list, on purpose. */
.user-head { padding: .55rem .7rem; margin-bottom: .5rem; }
.uhead { display: grid; grid-template-columns: 1fr auto auto; gap: .5rem; align-items: center; }
.uhead-id { min-width: 0; }
.uhead-id b, .uhead-amt b { display: block; font-size: .95rem; }
.uhead-id b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.uhead-id small, .uhead-amt small { display: block; font-size: .66rem; }
.uhead-amt { text-align: right; white-space: nowrap; }

.atabs {
  display: flex;
  gap: .25rem;
  margin: 0 0 .6rem;
  border-bottom: 1px solid var(--line);
  /* Four short labels fit every width this app targets, but a scroller costs nothing and stops
     a longer label ever forcing the page sideways. */
  overflow-x: auto;
  scrollbar-width: none;
}
.atabs::-webkit-scrollbar { display: none; }
.atab {
  flex: 1 0 auto;
  padding: .5rem .7rem;
  text-align: center;
  font-size: .7rem;
  letter-spacing: .08em;
  color: var(--muted);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;                 /* sit the underline ON the strip's border, not under it */
  white-space: nowrap;
}
.atab:hover { color: var(--text); }
.atab.is-active { color: var(--green); border-bottom-color: var(--green); }

.filterbar { padding: .55rem .7rem; margin-bottom: .5rem; }
.frow {
  display: flex;
  align-items: center;
  gap: .4rem;
  flex-wrap: wrap;
  margin-bottom: .4rem;
}
.frow:last-child { margin-bottom: 0; }
.frow label {
  font-size: .6rem;
  letter-spacing: .1em;
  color: var(--muted);
  margin: 0;
}
.frow input[type="date"], .frow select {
  flex: 1 1 7rem;
  min-width: 0;                        /* a flex item will not shrink below its content without this */
  padding: .35rem .4rem;
  font-size: .78rem;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--text);
}
.frow-act { justify-content: flex-end; }

.morerow { text-align: center; margin: .6rem 0 0; }

/* The one thing a symbol filter over the ledger has to say for itself: it can only match rows
   with a trade behind them, so the column stops adding up to the balance. */
.filternote {
  margin: 0 0 .5rem;
  padding: .45rem .6rem;
  font-size: .68rem;
  line-height: 1.45;
  color: var(--muted);
  background: var(--panel-2);
  border-left: 2px solid var(--green-dim);
  border-radius: 0 6px 6px 0;
}

@media (min-width: 1200px) {
  /* Width the page already has: the filters go on one line and the tabs stop stretching. */
  .atab { flex: 0 0 auto; min-width: 8rem; }
  .filterbar .frow { flex-wrap: nowrap; }
  .frow input[type="date"], .frow select { flex: 0 1 11rem; }
}

@media (max-width: 359px) {
  /* Four labels plus .7rem of padding each side is 303px against about 290 of usable width, so
     TRADEZ sat under the scroller's edge. Tightening the padding fits all four flat. The strip
     stays scrollable as the backstop -- this buys the common case, it does not replace it.
     Declared after the base rule because equal specificity means source order decides, and a
     media query does not win for being the narrower context. */
  .atab { padding: .5rem .4rem; letter-spacing: .05em; }
}

@media (max-width: 599px) {
  /* Flex-wrapping the filter bar orphaned every second label: FROM sat beside its input, TO
     ended the line and its input dropped below it alone. A two-column grid auto-flows the
     label/input pairs into one row each, which is what the pairs actually are. */
  .filterbar .frow { display: grid; grid-template-columns: 4.6rem 1fr; gap: .35rem .45rem; }
  .filterbar .frow-act { display: flex; justify-content: flex-end; }
  .frow input[type="date"], .frow select { flex: none; width: 100%; }
}

/* ---- engagement stats on the account tab --------------------------------------------------
   A two-line row: what it is on top, the numbers underneath wrapping as they need to. Not a
   table, because the number of figures per row differs by section and a table would leave gaps
   in the short ones. */
.stat-row { padding: .5rem 0; border-bottom: 1px solid var(--line); }
.stat-row:last-child { border-bottom: 0; }
.stat-row:first-child { padding-top: 0; }
.stat-row-h { display: flex; justify-content: space-between; align-items: baseline; gap: .5rem; }
.stat-row-h b { font-size: .78rem; letter-spacing: .06em; }
.stat-row-h .muted { font-size: .68rem; white-space: nowrap; }
.stat-row-b {
  display: flex;
  flex-wrap: wrap;
  gap: .2rem .9rem;
  margin-top: .3rem;
  font-size: .72rem;
  color: var(--muted);
}
.stat-row-b b { color: var(--text); font-variant-numeric: tabular-nums; }

/* The hint under RESULTS is a paragraph, not a field note, so it needs the room a .hint next to
   an input does not. */
#userStats .hint { margin-top: .7rem; line-height: 1.5; }

/* ---- the admin symbols screen -------------------------------------------------------------
   Three stacked bands per row on a phone, three columns once there is width. Disabled rows are
   dimmed rather than hidden: finding one again is the whole reason re-enabling exists, and most
   of the shipped catalogue starts out disabled. */
.srow {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: .6rem .7rem;
  margin-bottom: .45rem;
  display: grid;
  gap: .4rem;
}
.srow.is-off { opacity: .55; }
.srow.is-off:hover { opacity: 1; }
/* A lit left edge, the same device the home list uses to make a row findable without reading it. */
.srow.is-live { border-left: 2px solid var(--green); }
.srow.is-retiring { border-left: 2px solid var(--amber); }

.srow-id b { display: block; font-size: .95rem; }
.srow-id small { display: block; font-size: .66rem; }
.srow-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem .5rem; }
.srow-meta small { font-size: .66rem; }
.srow-act { display: flex; gap: .4rem; align-items: center; }
.srow-act input {
  flex: 1 1 auto;
  min-width: 0;
  padding: .35rem .45rem;
  font-size: .75rem;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--text);
}
.srow-act .btn { flex: 0 0 auto; }

.tag-live { background: rgba(143, 226, 21, .15); color: var(--green); }
.tag-retiring { background: rgba(224, 168, 60, .18); color: var(--amber); }
.tag-off { background: var(--panel-2); color: var(--muted); }

#symAddCard select {
  width: 100%;
  padding: .45rem .5rem;
  font-size: .85rem;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--text);
}

@media (min-width: 900px) {
  /* The reason box and its button need room; below this they get their own band. */
  .srow { grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) minmax(0, 1.1fr); align-items: center; }
  .srow-act { justify-content: flex-end; }
}

/* ---- the engagement charts on the admin account page --------------------------------------
   Each figure measures its own container and draws one CSS pixel per SVG unit, so these rules
   set the box and never the drawing. */
.chartwrap { margin: 0 0 .8rem; }
.chartcap {
  font-size: .6rem;
  letter-spacing: .12em;
  color: var(--muted);
  margin-bottom: .3rem;
}
.minichart { width: 100%; min-height: 96px; }
.minichart svg { display: block; }

/* One part-to-whole bar. A 2px surface gap between segments rather than a stroke around each,
   so the bar still reads as one quantity split up. */
.mixbar { display: flex; gap: 2px; height: 26px; margin-bottom: .55rem; }
.mixseg {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: .68rem;
  /* Text on a coloured fill takes the darkest step of that same ramp, never black. */
  color: #173404;
  min-width: 3px;
  overflow: hidden;
}
.mixlegend {
  display: flex;
  flex-wrap: wrap;
  gap: .25rem .8rem;
  font-size: .66rem;
  color: var(--muted);
  margin-bottom: .7rem;
}
.mixkey { display: flex; align-items: center; gap: .3rem; }
.mixkey i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }

/* Ranked bars. Magnitude by identity, so one hue at one step -- a second colour here would
   imply a second dimension that is not in the data. */
.sbar {
  display: grid;
  grid-template-columns: minmax(0, 7rem) 1fr auto;
  align-items: center;
  gap: .5rem;
  padding: .3rem 0;
}
.sbar-l { font-size: .75rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sbar-t { height: 8px; background: var(--panel-2); border-radius: 4px; overflow: hidden; }
.sbar-t i { display: block; height: 100%; background: #93c93a; border-radius: 4px; }
.sbar-v {
  font-size: .72rem;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.sbar-v span { margin-left: .35rem; }
/* The figure a chart would otherwise print over its own marks. */
.capnote { color: var(--text); letter-spacing: 0; margin-left: .4rem; }

/* ---------- the leaderboards ---------- */

/* The period toggle sits on the section head's line rather than under it, so the two boards keep
   the same vertical rhythm as every other section on the phone. */
.lbhead { display: flex; align-items: baseline; justify-content: space-between; gap: .6rem; }
.lbhead .section-head { flex: 1 1 auto; }
.lbtoggle { display: flex; gap: .25rem; flex: 0 0 auto; }

/* Its own class rather than .hint, which is cut for a line tucked under a form label: tighter
   below and unwrapped. This note runs to two lines under a section head and needs the leading and
   the space beneath the card edge that .hint deliberately does not give it. */
.lbnote { margin: -.35rem 0 .55rem; font-size: .68rem; line-height: 1.45; color: var(--muted); }

.lbrow {
  display: grid;
  grid-template-columns: 1.6rem 1fr auto;
  align-items: center;
  gap: .55rem;
  padding: .5rem 0;
  border-bottom: 1px solid var(--line);
}
.lbrow:last-child { border-bottom: 0; }
.lbrank { font-size: .8rem; font-weight: 700; color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; }
/* min-width 0 is what lets a long name ellipsis instead of pushing the score out of the panel. */
.lbmid { min-width: 0; }
.lbname { font-size: .92rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lbsub { font-size: .66rem; color: var(--muted); margin-top: .1rem; }
.lbyou {
  font-size: .55rem; font-weight: 700; letter-spacing: .08em; vertical-align: .12em;
  color: var(--green); border: 1px solid var(--green); border-radius: 4px; padding: 0 .22rem;
}
/* nowrap for the same reason the ME stats row has it: a signed figure that wraps leaves the sign
   hanging on a line of its own. */
.lbscore { font-size: 1.05rem; font-weight: 700; white-space: nowrap; font-variant-numeric: tabular-nums; }
.lbscore small { font-size: .55rem; font-weight: 700; letter-spacing: .07em; color: var(--muted); margin-left: .22rem; }

.lbrow.is-me { background: rgba(147,201,58,.07); }

/* The pinned row is separated by a heavier rule, because it is the one row that is not part of
   the ranking above it -- it may be 1st or it may be 40th. */
.lbpin { margin-top: .2rem; border-top: 2px solid var(--line); }
.lbpin .lbrow { border-bottom: 0; }
.lbmiss { padding: .6rem 0 .2rem; font-size: .72rem; color: var(--muted); }

@media (max-width: 359px) {
  /* The score keeps its size; the name gives up a step, since the ticker-free row has no second
     identifier to fall back on and truncation is the visible cost. */
  .lbname { font-size: .86rem; }
  .lbrow { gap: .4rem; }
}
.lbpinlab {
  padding: .45rem 0 .1rem;
  font-size: .55rem; font-weight: 700; letter-spacing: .1em; color: var(--muted);
}

/* ---------- trade resets ---------- */

/* Its own colour rather than borrowing the void chip's. A void is something that happened TO the
   player; a reset is something they spent an item on, and the two should not read alike. */
.hchip.reset { background: rgba(94,193,255,.16); color: #5ec1ff; }

.btn-reset {
  margin-top: .35rem;
  padding: .3rem .55rem;
  font-size: .6rem; font-weight: 800; letter-spacing: .07em;
  color: #5ec1ff; background: rgba(94,193,255,.08);
  border: 1px solid rgba(94,193,255,.5); border-radius: 7px; cursor: pointer;
}
.btn-reset:hover { background: rgba(94,193,255,.16); }
.btn-reset:disabled { opacity: .45; cursor: default; }

/* Stacked, not side by side: at 320px two buttons on a row leave neither legible, and the
   takeover is the one screen with vertical room to spare. */
.result-actions { display: flex; flex-direction: column; gap: .6rem; margin-top: 1.4rem; width: min(320px, 80vw); }
.btn-reset-lg {
  color: #5ec1ff; background: rgba(94,193,255,.1);
  border: 1px solid rgba(94,193,255,.55);
}
.btn-reset-lg:hover { background: rgba(94,193,255,.2); }

.promo-row { display: flex; align-items: center; gap: .9rem; }
.promo-count { text-align: center; flex: 0 0 auto; min-width: 3.4rem; }
.promo-count b { display: block; font-size: 1.6rem; font-weight: 800; color: #5ec1ff; line-height: 1; }
.promo-count small { font-size: .5rem; font-weight: 700; letter-spacing: .08em; color: var(--muted); }
.promo-prog { flex: 1 1 auto; min-width: 0; }

/* One pip per win needed, filled as the run builds. A bar would imply a continuous quantity;
   this is a count of discrete results and the pips say how many are left at a glance. */
.promo-pips { display: flex; gap: .3rem; margin-bottom: .35rem; }
.promo-pips i {
  flex: 1 1 0; height: 6px; border-radius: 3px;
  background: var(--panel-2); border: 1px solid var(--line);
}
.promo-pips i.on { background: var(--green); border-color: var(--green); }
.promo-note { font-size: .7rem; color: var(--muted); }
.promo-note b { color: var(--text); }

/* The holding, once, above BOTH tracks. It belongs to the card rather than to either way of
   earning, and repeating it beside each would read as two separate balances. */
.promo-have { text-align: center; margin-bottom: .9rem; }
.promo-have b { display: block; font-size: 2rem; font-weight: 800; color: #5ec1ff; line-height: 1; }
.promo-have small { font-size: .5rem; font-weight: 700; letter-spacing: .12em; color: var(--muted); }
/* Two tracks, so they need separating. Not a border on the last one. */
.promo-row + .promo-row { margin-top: .8rem; padding-top: .8rem; border-top: 1px solid var(--line); }
/* .lbnote carries a negative top margin so it can tuck under a caption where it is used on the
   leaderboard. Following a track here it overlapped one by 6px -- measured, not eyeballed. The
   same trap this file hit with .hint inside the name card, and the fix is the same: override
   where it is reused rather than changing the rule its first caller depends on. */
.promo-row + .lbnote { margin-top: .9rem; }

/* ---- the same two tracks as a strip on HOME -----------------------------------------------
   Home is the busiest screen in the app, so this has to earn its height: the holding on the
   left, the two runs stacked in the middle, and a chevron saying the whole card opens ME. The
   scaled-down type and the tighter gaps are what keep it a strip rather than a second panel.

   These rules sit immediately after the ones they override, which is this file's standing rule
   and the one it has lost a day to before. */
.promo-strip {
  display: flex; align-items: center; gap: .9rem;
  padding: .62rem .8rem; text-decoration: none; color: inherit;
}
.promo-strip:hover { border-color: var(--green-dim); }
.promo-tracks { flex: 1 1 auto; min-width: 0; }
/* The holding is a column here rather than a banner over the tracks, so it loses its margin. */
.promo-strip .promo-have { margin-bottom: 0; flex: 0 0 auto; min-width: 3rem; }
.promo-strip .promo-have b { font-size: 1.5rem; }
/* No sentence in compact mode, so the pips sit tighter and the label carries the meaning. */
.promo-strip .promo-pips { margin-bottom: .25rem; }
.promo-strip .promo-pips i { height: 5px; }
.promo-strip .promo-count { min-width: 2.6rem; }
.promo-strip .promo-count b { font-size: 1rem; }
/* The small under each number is inline, so it sits in a line box sized by the INHERITED 1rem
   line-height rather than by its own .5rem type -- 10px of strut per label, and there are three
   of them. Blocks on a tight line-height are what turns this from a panel into a strip. */
.promo-strip .promo-have, .promo-strip .promo-count { line-height: 1; }
.promo-strip .promo-have small, .promo-strip .promo-count small { display: block; }
.promo-strip .promo-note { font-size: .56rem; font-weight: 700; letter-spacing: .07em; }
.promo-strip .promo-row { gap: .55rem; }
.promo-strip .promo-row + .promo-row { margin-top: .45rem; padding-top: .45rem; }
.promo-more { flex: 0 0 auto; color: var(--muted); font-size: 1.3rem; line-height: 1; }
/* The strip is the first thing in the column on a wide screen, where .home-main is a block and
   the heading below it is no longer :first-child. Its own margin would double that gap. */
.home-main > #homePromo { margin-bottom: 0; }

/* What an admin gave you. Same three-part shape as a history row -- chip, reason, amount -- so
   the two lists read as the same kind of thing rather than as two designs. */
.giftrow {
  display: grid; grid-template-columns: 4.6rem minmax(0, 1fr) auto;
  align-items: center; gap: .7rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); padding: .55rem .8rem; margin-bottom: .4rem;
}
.giftreason { font-size: .8rem; }
.giftamt { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
@media (max-width: 359px) {
  /* The chip column is the one with slack: RESETZ and CREDITZ are both six characters. */
  .giftrow { grid-template-columns: 4.2rem minmax(0, 1fr) auto; gap: .5rem; padding-inline: .6rem; }
}

/* ---- invite codes ---------------------------------------------------------------------------
   Three lines in the order the questions get asked: what the code is and what state it is in,
   who it was for, then what happened to it. Same shape as an audit row, because it is the same
   kind of reading. */
.inv-row {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: .55rem .8rem; margin-bottom: .4rem;
}
.inv-l1 { display: flex; align-items: center; flex-wrap: wrap; gap: .5rem; }
.inv-l2 { font-size: .78rem; margin-top: .2rem; overflow-wrap: anywhere; }
.inv-l3 { font-size: .64rem; margin-top: .15rem; overflow-wrap: anywhere; }
/* Monospaced and spaced out, because this is a string somebody reads aloud or types in. The
   generator already leaves out 0/O/1/I/L; tabular digits keep the rest from shifting. */
.inv-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .95rem; font-weight: 700; letter-spacing: .1em;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.inv-row .btn { margin-left: auto; }
.inv-available { border-color: var(--green-dim); color: var(--green); }
.inv-used { border-color: var(--line); color: var(--muted); }
.inv-expired { border-color: var(--line); color: var(--muted); }
.inv-revoked { border-color: #4a1c1c; color: var(--red); }
/* The freshly made batch, one per line so a drag selects exactly the codes and nothing else. */
.inv-fresh {
  margin: .4rem 0 0;
  padding: .5rem .7rem;
  background: var(--panel-2); border: 1px solid var(--green-dim); border-radius: 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .95rem; font-weight: 700; letter-spacing: .1em; line-height: 1.5;
  white-space: pre; overflow-x: auto;
}
@media (max-width: 359px) {
  /* At 320 the code, the chip and REVOKE do not share a line. The button takes its own, full
     width, which is a better thumb target than the squeezed one it would otherwise get. */
  .inv-code { font-size: .85rem; letter-spacing: .08em; }
  .inv-row .btn { margin-left: 0; width: 100%; }
}
