/* =========================================================================
 * Crypto Flex Mini App — theme
 *
 * Two palettes, picked by ``<html data-theme="light|dark">`` which is set
 * from JS using ``Telegram.WebApp.colorScheme``. Telegram also injects
 * ``--tg-theme-*`` vars on the root but they're inconsistent across
 * clients/themes, so we only pull the hero accent color from them; the
 * rest is controlled by us for predictable contrast.
 * ========================================================================= */

:root {
  /* Binance-inspired palette. Primary is Binance gold (#F0B90B) with a
     warmer secondary (#FCD535); success / danger are Binance's standard
     up-green / down-red so candlestick-style cues feel familiar. */
  --accent:      #F0B90B;
  --accent-text: #1E2329;   /* dark text reads best on yellow */
  --accent-2:    #FCD535;
  --gradient:    linear-gradient(135deg, #F0B90B 0%, #FCD535 100%);
  --danger:      #F6465D;   /* Binance down-red */
  --success:     #0ECB81;   /* Binance up-green */
  --warning:     #F8A808;

  --radius: 18px;
  --radius-sm: 12px;
  --pad: 16px;
  --header-h: 52px;
  /* Was 70px; trimmed to 56px (iOS / Material standard) so the nav
     stops eating the bottom of the viewport. The safe-area inset is
     still added on top for iPhone home-indicator clearance. */
  --nav-h: 56px;

  --font: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI",
    Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* -------------------------------- dark (default) ----------------------
   Mirrors Binance Dark: near-black canvas, #181A20 cards, #2B3139 lines.
   The ``html:not([data-theme])`` fallback makes dark the initial paint
   even if the inline pre-paint script in index.html fails (e.g. very
   strict CSP) -- product default is dark, so users see dark there too. */
html[data-theme="dark"], html:not([data-theme]) {
  --bg:         #0B0E11;
  --surface:    #181A20;
  --surface-2:  #1E2329;
  --surface-hi: #2B3139;
  --text:       #EAECEF;
  --text-muted: #B7BDC6;
  --text-soft:  #848E9C;
  --border:     #2B3139;
  --divider:    #2B3139;
  --shadow:     0 2px 12px rgba(0, 0, 0, 0.45);
  --shadow-lg:  0 18px 40px rgba(0, 0, 0, 0.6);
  --chip-bg:    #2B3139;
  --chip-text:  #EAECEF;
}

/* ---------------------------- light (opt-in) --------------------------
   Mirrors Binance Light: white surfaces, #EAECEF dividers, charcoal text. */
html[data-theme="light"] {
  --bg:         #FAFAFA;
  --surface:    #FFFFFF;
  --surface-2:  #F5F5F5;
  --surface-hi: #EFF2F5;
  --text:       #1E2329;
  --text-muted: #707A8A;
  --text-soft:  #929AA5;
  --border:     #EAECEF;
  --divider:    #EAECEF;
  --shadow:     0 2px 10px rgba(30, 35, 41, 0.06);
  --shadow-lg:  0 18px 40px rgba(30, 35, 41, 0.12);
  --chip-bg:    #F5F5F5;
  --chip-text:  #1E2329;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 15px;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
  /* 100dvh = dynamic viewport height, covers the whole screen when in
     Telegram's fullscreen mode. Fallback to 100vh for older clients. */
  min-height: 100dvh;
  overscroll-behavior-y: contain;
  /* Pinch-zoom + double-tap-zoom kill (CSS layer). ``manipulation``
     allows pan / scroll / single-tap but blocks the pinch and the
     300 ms double-tap-zoom delay. Paired with the viewport-meta
     lockout in index.html and the JS gesture / multi-touch guard
     in app.js -- defence in depth so any one layer holds the line
     even if another is silently ignored by a webview update. */
  touch-action: manipulation;
  -ms-touch-action: manipulation;
}
body {
  padding-top: env(safe-area-inset-top, 0);
}

button, input, select, textarea { font-family: inherit; color: inherit; }
.hidden { display: none !important; }

/* ================================================================ header */
.app-header {
  position: sticky;
  top: 0;
  z-index: 10;
  height: var(--header-h);
  padding: 0 var(--pad);
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--bg);
  border-bottom: 1px solid var(--divider);
  backdrop-filter: saturate(1.2);
}
.app-header h1 {
  flex: 1;
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  text-align: center;
  letter-spacing: 0.1px;
}

/* (Header-level glyph removed for now -- the Plan badge lives
   only on the Profile screen via .level-pill below. Add a
   header-side rule back here if we want a global pill again.) */
/* Brand mark when ``setTitle(..., { logo: true })`` — gradient wordmark +
   official TredFlex Telegram icon (vector, crisp at any DPI). */
.app-header h1.logo {
  --logo-gold-1: #F0B90B;
  --logo-gold-2: #FCD535;
  --logo-gradient: linear-gradient(135deg, var(--logo-gold-1) 0%, var(--logo-gold-2) 100%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.3px;
  background: var(--logo-gradient);
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  filter: drop-shadow(0 1px 1px rgba(240, 185, 11, 0.25));
}
.app-header h1.logo::before {
  content: "";
  flex: 0 0 auto;
  /* The SVG version carries a couple of ``feGaussianBlur`` glow filters
     that are fine at 512px but smear noticeably when downscaled to ~28px.
     Use the pre-rendered PNG and ask the browser for a crisp downscale. */
  width: 34px;
  height: 34px;
  border-radius: 9px;
  background: url("/static/brand/tredflex-telegram-icon.png") center / contain no-repeat;
  image-rendering: -webkit-optimize-contrast;
  image-rendering: crisp-edges;
  box-shadow: 0 6px 14px rgba(240, 185, 11, 0.35);
}
.icon-btn {
  background: transparent;
  border: 0;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  font-size: 18px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text);
  transition: background 0.15s;
}
.icon-btn:active { background: var(--surface-hi); }

/* Empty spacer on the right of the header that mirrors the back-button
   slot so the centered title stays optically centered regardless of
   whether the back button is visible. Also keeps the right edge clear
   for Telegram's native fullscreen controls. */
.header-spacer {
  display: inline-block;
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
}

/* Keep the back-button's 36px slot reserved when it's hidden so the
   ``flex:1`` title sits between equal 36px gutters on the left and
   right and stays perfectly centered (otherwise ``display:none`` on
   the back button collapses the left gutter and the title drifts
   ~18px to the left). The auth-screen rule a few sections down still
   uses ``display:none`` because it removes the whole header chrome,
   so this override only takes effect on regular app pages. */
#back-btn.hidden {
  display: inline-flex !important;
  visibility: hidden;
  pointer-events: none;
}

/* Privacy toggle (eye). Rendered inline inside cards that display
   money -- NOT in the header, because Telegram's native window chrome
   (close + minimize buttons in both top corners) overlays any
   header-edge controls. The two icons are stacked and cross-fade via
   ``body[data-privacy]`` so there's no JS re-paint on toggle. */
.balance-label-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.privacy-toggle {
  appearance: none;
  -webkit-appearance: none;
  background: rgba(0, 0, 0, 0.06);
  border: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text);
  position: relative;
  overflow: hidden;
  flex: 0 0 auto;
  padding: 0;
  transition: background 0.15s, color 0.15s;
  -webkit-tap-highlight-color: transparent;
}
.privacy-toggle:hover { background: rgba(0, 0, 0, 0.10); }
.privacy-toggle:active { transform: scale(0.94); }
.privacy-toggle .privacy-icon {
  position: absolute;
  inset: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.privacy-toggle .privacy-icon svg {
  width: 18px;
  height: 18px;
}
body:not([data-privacy="on"]) .privacy-icon--hide,
body[data-privacy="on"] .privacy-icon--show {
  opacity: 0;
  transform: scale(0.75);
  pointer-events: none;
}
body[data-privacy="on"] .privacy-toggle { color: var(--accent); }

/* ``.hero-gradient`` is actually a white card with a gradient BORDER
   (not a gradient fill), so the default dark icon reads perfectly --
   we just make the hover/background a little more prominent so the
   button is obviously tappable against the bright white card. */
.hero-gradient .privacy-toggle {
  background: rgba(0, 0, 0, 0.05);
}
.hero-gradient .privacy-toggle:hover {
  background: rgba(0, 0, 0, 0.09);
}

/* Pull-to-refresh indicator. Fixed just below the header, translated
   off-screen by default (-56px) and pulled into view by touch handlers
   in app.js. Rotates while armed / refreshing. */
.ptr {
  position: fixed;
  top: var(--header-h);
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  pointer-events: none;
  z-index: 12;
  transform: translateY(-56px);
  opacity: 0;
}
.ptr.animating {
  transition: transform 0.24s cubic-bezier(.2,.7,.2,1), opacity 0.24s linear;
}
.ptr-spinner {
  width: 40px;
  height: 40px;
  margin-top: -28px; /* overlap header so it peeks down as user pulls */
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--border));
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
  color: var(--accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.24s cubic-bezier(.2,.7,.2,1);
}
.ptr-spinner svg {
  width: 22px;
  height: 22px;
  transition: transform 0.18s ease;
}
.ptr.armed .ptr-spinner {
  transform: scale(1.06);
  border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
}
.ptr.armed .ptr-spinner svg {
  transform: rotate(180deg);
}
.ptr.refreshing .ptr-spinner svg {
  animation: ptr-spin 0.8s linear infinite;
  transform: none;
}
@keyframes ptr-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* ================================================================ main */
main#app {
  padding: var(--pad);
  padding-bottom: calc(var(--nav-h) + var(--pad) + env(safe-area-inset-bottom, 0));
  min-height: calc(100vh - var(--header-h) - var(--nav-h));
  /* Promote to its own compositor layer so the swipe-driven
     ``transform: translateX(...)`` stays buttery on mobile -- without
     this the iOS WebView falls back to repainting on every frame. */
  will-change: transform;
}

/* While the swipe-nav gesture is actively driving the page, suppress
   text selection (a long-ish horizontal drag would otherwise highlight
   text underneath the finger) and let the y-axis still scroll if the
   user changes their mind early. ``touch-action: pan-y`` tells the
   browser "y-scroll is yours, x-pan is mine" so vertical scrolling
   inside a swipe-active page still works normally. */
main#app.swipe-dragging {
  user-select: none;
  -webkit-user-select: none;
  touch-action: pan-y;
}

/* ============================================================ page transitions
   Top TMAs (Hamster Kombat, Notcoin, the Wallet apps) all share one
   trick that makes them feel fast even when they aren't: a quick
   slide+fade as the new page paints, so the user perceives motion
   instead of waiting for the network. We mirror that here.

   ``app-enter-forward`` runs on ``navigate(...)`` -- the new page
   eases in from slightly to the right of center, just like an iOS
   push. ``app-enter-back`` runs on ``goBack()`` -- mirrored so the
   incoming content arrives from the left, matching a pop. The
   distance is intentionally subtle (16 px) -- bigger swings start to
   feel like a carousel and fight with the existing horizontal-swipe
   carousel between sibling tabs.

   Duration is 180ms, snappy enough that even back-to-back taps don't
   stack up; the easing is a fast-out cubic-bezier so the motion
   "lands" decisively rather than coasting. Using translate3d
   (instead of translateX) keeps the transform on the GPU on
   WebKit-based engines including Telegram's iOS WebView -- avoids a
   compositor / paint round-trip on every navigation. */
@keyframes app-enter-forward {
  from { opacity: 0; transform: translate3d(16px, 0, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}
@keyframes app-enter-back {
  from { opacity: 0; transform: translate3d(-16px, 0, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}
main#app.app-enter-forward {
  animation: app-enter-forward 180ms cubic-bezier(0.2, 0, 0, 1);
}
main#app.app-enter-back {
  animation: app-enter-back 180ms cubic-bezier(0.2, 0, 0, 1);
}
/* Honour the user's reduce-motion preference. The navigation still
   happens; we just don't animate the transition. Doing so keeps the
   app accessible without changing the navigation model. */
@media (prefers-reduced-motion: reduce) {
  main#app.app-enter-forward,
  main#app.app-enter-back {
    animation: none;
  }
}

/* ============================================================ breadcrumbs
   Shown above each non-root view. Matches the form-card padding
   (var(--pad)) on the sides so the trail lines up with the cards
   below it; uses ``--surface`` for a subtle separation from the page
   background so the trail reads as part of the chrome rather than
   floating content. The whole strip is hidden via the ``.hidden``
   utility on root pages -- no padding is "leaked" when there's no
   trail to render. */
.breadcrumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 6px;
  padding: 8px var(--pad);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  line-height: 1.4;
  /* Sticky just below the header so the user keeps the trail in
     sight even after scrolling a long page. ``z-index`` sits one
     below the header so the header still wins if the two ever
     overlap mid-scroll. */
  position: sticky;
  top: var(--header-h);
  z-index: 9;
}
.breadcrumb-item {
  display: inline-flex;
  align-items: center;
  background: transparent;
  border: 0;
  padding: 2px 4px;
  margin: 0;
  border-radius: 6px;
  font: inherit;
  color: var(--text-soft);
  cursor: default;
  /* Some Safari builds add a default min-height on <button> -- reset
     it so a clickable crumb is the same height as a label. */
  min-height: 0;
  /* Keep long coin labels (e.g. "1000PEPEUSDT \u00b7 Emulator") on
     a single line if there's room, but allow the strip to wrap as
     a whole rather than overflowing horizontally. */
  white-space: nowrap;
}
.breadcrumb-item--link {
  color: var(--accent);
  cursor: pointer;
  font-weight: 600;
  /* Standard Telegram-Mini-App tap target: ~44px hit area without
     bloating the visual height. ``padding`` already gives 4px each
     side, so a transparent ``::after`` extends the hit zone. */
  position: relative;
}
.breadcrumb-item--link:active {
  background: var(--surface-hi);
}
.breadcrumb-item--current {
  color: var(--text);
  font-weight: 600;
}
.breadcrumb-sep {
  color: var(--text-soft);
  user-select: none;
  /* Bump the chevron a hair so it visually sits between the labels
     instead of riding their baseline. */
  line-height: 1;
  font-size: 13px;
  opacity: 0.7;
}
/* On the auth gate everything app-shell is hidden via
   ``body[data-screen="auth"]`` already; mirror that here so a stray
   route change to "auth" doesn't leave the trail visible above the
   gate while the rest of the chrome disappears. */
body[data-screen="auth"] .breadcrumbs { display: none; }

.loader {
  display: flex;
  justify-content: center;
  padding: 56px 0;
}

/* TredFlex-branded loader: four gold bars echoing the chart in the icon.
 * Each bar bounces with a staggered delay so the cluster reads like a
 * live price feed rather than a generic spinner. Pure CSS, respects
 * ``prefers-reduced-motion``, and scales with the accent var for both
 * light and dark themes. */
.tf-loader {
  display: inline-flex;
  align-items: flex-end;
  gap: 5px;
  height: 40px;
  padding: 2px 0;
}
.tf-loader .bar {
  width: 6px;
  height: 100%;
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
  border-radius: 3px;
  transform-origin: bottom center;
  transform: scaleY(0.25);
  box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 55%, transparent),
              0 0 2px color-mix(in srgb, var(--accent) 80%, transparent);
  animation: tf-bars 1.1s ease-in-out infinite;
}
.tf-loader .bar:nth-child(1) { animation-delay: 0s; }
.tf-loader .bar:nth-child(2) { animation-delay: 0.11s; }
.tf-loader .bar:nth-child(3) { animation-delay: 0.22s; }
.tf-loader .bar:nth-child(4) { animation-delay: 0.33s; }

@keyframes tf-bars {
  0%, 100% { transform: scaleY(0.22); opacity: 0.55; }
  50%      { transform: scaleY(1);    opacity: 1; }
}

/* ============================================================ skeletons
 * Loading placeholders shown while the SWR cache is cold (first-ever
 * load, post-logout, or after a WebView eviction with no IDB
 * hydration). Painted in the rough *shape* of the real content so the
 * page doesn't visually jump when the network response lands.
 *
 * Visual: a soft surface block with a slow horizontal shimmer. Tints
 * derived from ``--surface-hi`` so the skeleton blends with cards in
 * both light and dark themes without any per-theme overrides.
 *
 * Performance:
 *   - One ``background-position`` animation — GPU-cheap.
 *   - The whole block is ``pointer-events:none`` so a tap during a
 *     skeleton paint still hits the navigation underneath.
 *   - ``prefers-reduced-motion`` flattens the shimmer to a static
 *     fill so we don't strobe accessibility-conscious users.
 *
 * Usage from JS: see ``sk*`` helpers in app.js (skLine / skBox /
 * skCircle / skCard) and the page templates (``skDashboard``, etc.).
 */
.skeleton-page {
  pointer-events: none;
  user-select: none;
}
.skeleton {
  display: block;
  border-radius: 8px;
  background: color-mix(in srgb, var(--surface-hi) 75%, var(--surface));
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--text-soft) 14%, transparent) 50%,
    transparent 100%
  );
  background-size: 220% 100%;
  background-repeat: no-repeat;
  background-position: 220% 0;
  animation: skeleton-shimmer 1.4s ease-in-out infinite;
}
.skeleton.line { height: 14px; width: 100%; }
.skeleton.line.sm { height: 10px; }
.skeleton.line.lg { height: 20px; }
.skeleton.line.xl { height: 32px; border-radius: 10px; }
.skeleton.line.title { height: 12px; width: 30%; opacity: 0.85; }
.skeleton.circle { border-radius: 50%; width: 36px; height: 36px; }
.skeleton.box { height: 80px; border-radius: 12px; }
.skeleton.pill { height: 22px; width: 64px; border-radius: 999px; }
.skeleton.chip {
  display: inline-block; height: 26px; width: 72px;
  border-radius: 999px; margin-right: 8px; vertical-align: middle;
}
.skeleton.btn {
  height: 38px; border-radius: 12px; width: 100%;
}
/* Card-shaped skeleton: same padding / border / radius / shadow as the
 * real ``.card`` so the layout doesn't shift when content lands. */
.sk-card {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 18px;
  margin-bottom: 12px;
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
}
.sk-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}
.sk-row:last-child { margin-bottom: 0; }
.sk-col { display: flex; flex-direction: column; gap: 8px; flex: 1; }
.sk-grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.sk-grid-4 {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}
.sk-strip {
  display: flex;
  gap: 10px;
  overflow: hidden;
  margin-bottom: 12px;
}
.sk-strip .sk-card {
  min-width: 110px; margin-bottom: 0; flex-shrink: 0;
  padding: 12px;
}
@keyframes skeleton-shimmer {
  0%   { background-position: 220% 0; }
  100% { background-position: -120% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .skeleton {
    animation: none;
    background-image: none;
  }
  .tf-loader .bar {
    animation: none;
    transform: scaleY(0.75);
    opacity: 0.85;
  }
}

/* Legacy spinner — kept only for backwards compatibility with any
 * markup that still references ``.spinner`` (e.g. cached HTML pages
 * served while the new CSS propagates). The TredFlex bar loader is the
 * new default; see ``.tf-loader`` above. */
.spinner {
  width: 30px; height: 30px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ================================================================ nav
   Option C: glassmorphism bottom nav. A translucent frosted-glass
   surface floats over the content with a hairline top border; the
   active tab is signalled by its accent color + a tiny dot that
   slides in beneath the label. Icons are inline SVGs (``currentColor``
   stroke) so they recolor with the theme and stay crisp on HiDPI.

   Fallback: browsers that don't support ``backdrop-filter`` (old
   Android WebView) get a solid surface with slightly lower opacity
   so the bar never becomes unreadable. */
.bottom-nav {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: calc(var(--nav-h) + env(safe-area-inset-bottom, 0));
  padding-bottom: env(safe-area-inset-bottom, 0);
  display: flex;
  z-index: 10;
  /* Solid fallback; the --glass modifier layers translucency on top. */
  background: var(--surface);
  border-top: 1px solid var(--divider);
  box-shadow: 0 -4px 18px rgba(15, 26, 42, 0.04);
}
html[data-theme="dark"] .bottom-nav {
  box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.4);
}

/* Kept the ``--glass`` class name for markup stability but the visual
   treatment is now a solid surface -- the translucent/blurred variant
   felt too busy behind content. The hairline top border still gives
   the bar a subtle lift on scroll. */
.bottom-nav--glass {
  background: var(--surface);
  border-top: 1px solid var(--divider);
}

.nav-btn {
  position: relative;
  flex: 1;
  background: transparent;
  border: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  font-size: 10.5px;
  color: var(--text-muted);
  cursor: pointer;
  /* Compact vertical padding -- the dot sits inside the padding below
     the label, so we keep the bottom just big enough for it (5px dot
     + 3px gap). */
  padding: 6px 0 8px;
  transition: color 0.18s ease, transform 0.18s ease;
  -webkit-tap-highlight-color: transparent;
}
.nav-btn:active { transform: scale(0.96); }
.nav-btn.active {
  color: var(--accent);
  font-weight: 600;
}

.nav-btn .nav-icon {
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1px;
  transition: transform 0.18s ease;
}
.nav-btn .nav-icon svg {
  width: 20px;
  height: 20px;
  display: block;
}
.nav-btn.active .nav-icon { transform: translateY(-1px); }

.nav-btn .nav-label {
  letter-spacing: 0.1px;
  line-height: 1.1;
}

/* Tiny accent dot under the active tab -- the C-option signature.
   Sits inside the button's bottom padding so a bit of breathing room
   remains between label and dot without growing --nav-h. */
.nav-btn .nav-dot {
  position: absolute;
  left: 50%;
  bottom: 3px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--accent);
  transform: translate(-50%, 4px) scale(0.6);
  opacity: 0;
  transition: transform 0.22s cubic-bezier(.2,.7,.2,1), opacity 0.22s ease;
}
.nav-btn.active .nav-dot {
  transform: translate(-50%, 0) scale(1);
  opacity: 1;
  box-shadow: 0 0 7px color-mix(in srgb, var(--accent) 55%, transparent);
}

/* ================================================================ cards */
.card {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 18px;
  margin-bottom: 12px;
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
}
.card h2 {
  margin: 0 0 14px;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--text-soft);
}

/* hero card for the main balance */
.hero {
  background: var(--gradient);
  color: var(--accent-text);
  border: 0;
  padding: 22px;
  box-shadow: 0 14px 30px color-mix(in srgb, var(--accent) 35%, transparent);
}
.hero h2 { color: rgba(255, 255, 255, 0.85); margin-bottom: 6px; }
.hero .hero-value {
  font-size: 34px;
  font-weight: 700;
  letter-spacing: -0.5px;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.hero .hero-sub {
  color: rgba(255, 255, 255, 0.85);
  font-size: 13px;
  margin-top: 4px;
}

.stat-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.stat {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  min-width: 0;
}
.stat .label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.7px;
  color: var(--text-soft);
  margin-bottom: 6px;
  font-weight: 600;
}
.stat .value {
  font-size: 17px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.stat.big { grid-column: 1 / -1; }
.stat.big .value { font-size: 24px; }

.positive,
.stat .value.positive,
.kv .v.positive,
.hero .positive { color: var(--success); }

.negative,
.stat .value.negative,
.kv .v.negative,
.hero .negative { color: var(--danger); }

.muted {
  color: var(--text-muted);
  font-size: 12px;
}

/* ================================================================ lists */
.coin-list {
  display: grid;
  gap: 10px;
}
.coin-row {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: transform 0.12s, background 0.15s;
}
.coin-row:active { transform: scale(0.98); background: var(--surface-2); }

.coin-avatar {
  position: relative;
  width: 44px; height: 44px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-weight: 800;
  font-size: 12px;
  letter-spacing: 0.3px;
  flex-shrink: 0;
  color: #fff;
  background: linear-gradient(140deg, var(--avatar-a, #2ea6ff), var(--avatar-b, #6c5ce7));
  box-shadow: 0 4px 14px rgba(15, 26, 42, 0.15);
  overflow: hidden;
}
.coin-avatar .coin-icon {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #fff;          /* some PNGs are transparent — white disc looks cleanest */
  border-radius: inherit;
  z-index: 1;
}
/* When an icon is present the letter is behind it, so hide it to keep the
 * alt text from peeking through on slow connections. */
.coin-avatar:has(.coin-icon) { color: transparent; background: #fff; }
/* Explicit reset for when the img errored out — the JS adds .no-icon. */
.coin-avatar.no-icon {
  color: #fff;
  background: linear-gradient(140deg, var(--avatar-a, #2ea6ff), var(--avatar-b, #6c5ce7));
}
.coin-meta { flex: 1; min-width: 0; }
.coin-meta .name {
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.1px;
}
.coin-meta .sub {
  font-size: 12px;
  color: var(--text-muted);
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
}

.coin-status {
  font-size: 10px;
  padding: 5px 10px;
  border-radius: 999px;
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.coin-status.online {
  background: color-mix(in srgb, var(--success) 16%, transparent);
  color: var(--success);
}
.coin-status.offline {
  background: var(--chip-bg);
  color: var(--text-muted);
}

/* ---- Portfolio redesign: status dot + P&L/ROI right block -------- */
.status-dot {
  flex-shrink: 0;
  width: 9px; height: 9px;
  border-radius: 50%;
  display: inline-block;
  vertical-align: middle;
}
.status-dot.online {
  background: var(--success);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--success) 22%, transparent);
}
.status-dot.offline {
  background: var(--text-muted);
  opacity: 0.55;
}
.coin-right {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  text-align: right;
}
.coin-pnl {
  font-size: 15px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.coin-roi {
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

/* ---- Portfolio summary hero ------------------------------------- */
.pf-hero { text-align: left; }
.pf-hero.paper {
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--accent, #e0a83b) 7%, transparent),
    transparent), var(--surface);
}
.pf-hero-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.pf-hero-value {
  font-size: 30px;
  font-weight: 800;
  margin: 2px 0 6px;
  font-variant-numeric: tabular-nums;
}
.pf-hero-sub {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  font-size: 12px;
  align-items: center;
}
.pf-hero-stat { display: inline-flex; align-items: center; }
.pf-hero-stat .status-dot { margin-right: 6px; }

/* ---- Portfolio sort control ------------------------------------- */
.pf-sort {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 2px 12px;
  flex-wrap: wrap;
}
.pf-sort-label {
  font-size: 12px;
  margin-right: 2px;
}
.pf-sort-btn {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 12px;
  font-weight: 600;
  padding: 5px 12px;
  border-radius: 999px;
  cursor: pointer;
}
.pf-sort-btn.active {
  background: var(--accent, #e0a83b);
  border-color: var(--accent, #e0a83b);
  color: #1a1205;
}

.empty-state {
  text-align: center;
  padding: 56px 20px;
  color: var(--text-muted);
}
.empty-state .emoji { font-size: 44px; margin-bottom: 10px; }
.empty-state .title { color: var(--text); font-weight: 700; margin-bottom: 4px; }

.section-title {
  margin: 4px 4px 10px;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--text-soft);
}

.add-btn {
  background: var(--chip-bg);
  color: var(--accent);
  border: 0;
  border-radius: 999px;
  padding: 8px 14px;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  transition: transform 0.08s, background 0.15s;
  flex-shrink: 0;
}
.add-btn:active {
  transform: scale(0.94);
  background: color-mix(in srgb, var(--accent) 18%, var(--chip-bg));
}

/* ================================================================ buttons */
.btn {
  display: inline-flex;
  justify-content: center;
  align-items: center;
  gap: 8px;
  width: 100%;
  border: 0;
  background: var(--accent);
  color: var(--accent-text);
  font-weight: 600;
  padding: 14px 16px;
  border-radius: var(--radius-sm);
  font-size: 15px;
  cursor: pointer;
  transition: transform 0.08s, opacity 0.15s, background 0.15s;
  box-shadow: 0 6px 16px color-mix(in srgb, var(--accent) 28%, transparent);
}
.btn:active { transform: scale(0.98); opacity: 0.9; }
.btn.secondary {
  background: var(--surface-hi);
  color: var(--text);
  box-shadow: none;
}
.btn.danger {
  background: var(--danger);
  color: #fff;
  box-shadow: 0 6px 16px color-mix(in srgb, var(--danger) 30%, transparent);
}
.btn.success {
  background: var(--success);
  color: #fff;
  box-shadow: 0 6px 16px color-mix(in srgb, var(--success) 30%, transparent);
}
.btn.ghost {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  box-shadow: none;
}
.btn[disabled] { opacity: 0.5; cursor: default; box-shadow: none; }

.button-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 12px;
}

.button-stack {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 12px;
}
.button-stack .btn {
  width: 100%;
}

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.chip {
  background: var(--chip-bg);
  color: var(--chip-text);
  border: 0;
  border-radius: 999px;
  padding: 9px 14px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: transform 0.08s, background 0.15s;
}
.chip:active { transform: scale(0.95); }

/* ================================================================ forms */
.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 14px;
}
.field label {
  font-size: 11px;
  color: var(--text-soft);
  text-transform: uppercase;
  letter-spacing: 0.7px;
  font-weight: 700;
}
.field .hint {
  font-size: 12px;
  color: var(--text-muted);
  line-height: 1.35;
}
.field input,
.field select,
.field textarea {
  width: 100%;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 13px 14px;
  color: var(--text);
  font-size: 15px;
  font-family: inherit;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.field input:focus,
.field select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
}
.field input::placeholder { color: var(--text-soft); }

/* ================================================================ detail */
.detail-header {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 18px;
  padding: 4px 2px;
}
.detail-header .coin-avatar {
  width: 56px; height: 56px;
  font-size: 14px;
}
.detail-header .title {
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.2px;
  line-height: 1.2;
}
.detail-header .sub {
  color: var(--text-muted);
  font-size: 13px;
  margin-top: 3px;
  font-variant-numeric: tabular-nums;
}

.kv {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 11px 0;
  border-bottom: 1px solid var(--divider);
  font-size: 14px;
  gap: 12px;
}
.kv:last-child { border-bottom: 0; padding-bottom: 0; }
.kv:first-child { padding-top: 0; }
.kv .k {
  color: var(--text-muted);
  font-weight: 500;
}
.kv .v {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--text);
  text-align: right;
}

/* Stacked-label kv cell. Lets a row carry a small caption under
   its label (e.g. "ROI% (April)" / "on $60.01 · 4 trades") while
   keeping the value vertically centered against the whole stack
   -- so the row reads as a single line of data instead of the
   label + a separate full-width muted row below it. Used by the
   Asset profit card; designed so any future row can opt in by
   wrapping its label in ``.kv-k-stack``. */
.kv .k.kv-k-stack {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.kv-k-title {
  display: flex;
  align-items: center;
  gap: 4px;
  font-weight: 500;
  color: var(--text-muted);
  line-height: 1.25;
}
.kv-k-sub {
  font-size: 11px;
  line-height: 1.2;
  letter-spacing: 0.1px;
}

/* ================================================================ orders */
.orders-bulk-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  margin-bottom: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.orders-bulk-bar .btn.small {
  padding: 6px 14px;
  font-size: 12px;
  min-width: auto;
}
.order-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 10px;
  box-shadow: var(--shadow);
}
.order-card .row-1 {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}
.order-card .oid {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-muted);
}
.order-card .armed {
  font-size: 10px;
  padding: 4px 9px;
  border-radius: 999px;
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.armed.on  {
  background: color-mix(in srgb, var(--success) 18%, transparent);
  color: var(--success);
}
.armed.off {
  background: var(--chip-bg);
  color: var(--text-muted);
}

/* Open-order "Created" timestamp. The ``YYYY-MM-DD HH:MM`` UTC stamp
   is rendered as two stacked spans so the date never breaks across a
   line on narrow phones. The ``--hm`` qualifier dims the time a hair
   relative to the date so the eye reads the date first. */
.order-card .order-created-ts {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}
.order-created-ts-hm {
  color: var(--text-soft);
  font-size: 12px;
}

/* ================================================================ toast */
.toast-host {
  position: fixed;
  bottom: calc(var(--nav-h) + 16px + env(safe-area-inset-bottom, 0));
  left: 50%;
  transform: translateX(-50%);
  /* Must sit above .modal-host (z-index: 30) so confirmation/error
     toasts from modal submit handlers (Set PIN, Transfer, Change
     password, ...) are actually visible to the user. */
  z-index: 60;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.toast {
  background: var(--text);
  color: var(--surface);
  border-radius: var(--radius-sm);
  padding: 11px 18px;
  font-size: 14px;
  font-weight: 600;
  box-shadow: var(--shadow-lg);
  animation: toast-in 0.18s ease-out;
  max-width: 85vw;
  text-align: center;
}
.toast.error { background: var(--danger); color: #fff; }
.toast.ok    { background: var(--success); color: #fff; }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ---- success splash (center-screen "action landed" banner) -----------
 * Used for high-signal moments where a quiet toast doesn't cut it
 * (e.g. adding a bot, starting an emulation). Sits above modals and
 * the bottom nav, dims the backdrop lightly, then fades out. Pointer
 * events stay off so it never blocks the user from tapping through. */
.splash-host {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  background: color-mix(in srgb, #000 28%, transparent);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  animation: splash-bg-in 0.18s ease-out;
}
.splash-host.leaving {
  animation: splash-bg-out 0.22s ease-in forwards;
}
.splash-card {
  /* Translucent fill so the page behind shows through a little --
     softer than a solid white stamp while the card still reads as
     the main subject. The border picks up the same fade so its
     outline doesn't look disconnected from the fill. */
  background: color-mix(in srgb, var(--surface) 20%, transparent);
  color: var(--text);
  border: 1px solid color-mix(in srgb, var(--border) 22%, transparent);
  border-radius: 18px;
  padding: 20px 24px 18px;
  min-width: 180px;
  max-width: 78vw;
  text-align: center;
  box-shadow: 0 28px 64px rgba(5, 10, 18, 0.45);
  transform-origin: center;
  animation: splash-card-in 0.26s cubic-bezier(0.2, 1.2, 0.4, 1);
}
.splash-host.leaving .splash-card {
  animation: splash-card-out 0.22s ease-in forwards;
}
.splash-check {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  margin: 0 auto 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg,
    var(--success) 0%,
    color-mix(in srgb, var(--success) 55%, #ffffff) 100%);
  color: #fff;
  font-size: 34px;
  font-weight: 900;
  box-shadow: 0 12px 28px color-mix(in srgb, var(--success) 35%, transparent);
  animation: splash-check-pop 0.4s cubic-bezier(0.2, 1.4, 0.4, 1);
}
.splash-title {
  font-size: 20px;
  font-weight: 800;
  letter-spacing: 0.1px;
  color: var(--text);
}
.splash-sub {
  margin-top: 4px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-muted);
}
@keyframes splash-bg-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes splash-bg-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}
@keyframes splash-card-in {
  from { opacity: 0; transform: scale(0.82); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes splash-card-out {
  from { opacity: 1; transform: scale(1); }
  to   { opacity: 0; transform: scale(0.92); }
}
@keyframes splash-check-pop {
  0%   { transform: scale(0.2); opacity: 0; }
  60%  { transform: scale(1.12); opacity: 1; }
  100% { transform: scale(1); }
}

/* ================================================================ modal */
.modal-host {
  position: fixed;
  inset: 0;
  background: rgba(5, 10, 18, 0.55);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  z-index: 30;
  animation: fade-in 0.15s ease-out;
  padding-bottom: env(safe-area-inset-bottom, 0);
}
.modal {
  background: var(--surface);
  color: var(--text);
  border-radius: 20px 20px 0 0;
  padding: 22px 20px 28px;
  width: 100%;
  max-width: 500px;
  box-shadow: var(--shadow-lg);
  animation: slide-up 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.modal h3 {
  margin: 0 0 10px;
  font-size: 17px;
  font-weight: 700;
}
.modal .modal-body {
  color: var(--text-muted);
  margin-bottom: 18px;
  line-height: 1.4;
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes slide-up {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}

/* ---- Walkthrough overlay --------------------------------------------
 * Fullscreen variant of .modal-host that hosts the standalone
 * /static/walkthrough.html demo as an iframe. We deliberately use an
 * iframe (rather than rendering the scenes inline) so the walkthrough
 * page stays self-contained and screen-recordable for sharing as a GIF
 * on Telegram.
 *
 * The host pins itself to the viewport, the iframe fills it edge-to-
 * edge, and an "x" button on top of the iframe gives the user a
 * fallback close path even if walkthrough.html's own close button
 * fails to register postMessage. */
.walkthrough-host {
  position: fixed;
  inset: 0;
  background: rgba(5, 10, 18, 0.92);
  z-index: 60;
  animation: fade-in 0.18s ease-out;
  display: flex;
  align-items: stretch;
  justify-content: center;
}
.walkthrough-host iframe {
  width: 100%;
  height: 100%;
  border: 0;
  background: var(--bg);
  animation: slide-up 0.28s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.walkthrough-host .walkthrough-close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: rgba(20, 24, 32, 0.7);
  color: var(--text);
  border: 1px solid rgba(255, 255, 255, 0.06);
  font-size: 20px;
  font-weight: 600;
  cursor: pointer;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

/* "How it works" replay card on the Profile screen. Mirrors the
 * accent-tinted treatment of the Owner / Admin cards so the entry
 * point reads as a tour, not a setting. */
/* ---- Profile hub (header + tiles) -------------------------------- */
.hub-header {
  display: flex;
  align-items: center;
  gap: 14px;
}
.hub-avatar {
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  font-weight: 800;
  color: #1a1205;
  background: var(--accent, #e0a83b);
}
.hub-id { min-width: 0; }
.hub-name {
  font-size: 19px;
  font-weight: 800;
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.hub-sub {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 5px;
}
.hub-tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin: 12px 0;
}
.hub-tile {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 12px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 14px;
  background: var(--surface, #161a22);
  cursor: pointer;
  transition: transform .06s ease, border-color .12s ease;
}
.hub-tile:active { transform: scale(0.98); }
.hub-tile:hover { border-color: var(--accent, #e0a83b); }
.hub-tile-ico { font-size: 20px; flex: 0 0 auto; }
.hub-tile-body { min-width: 0; flex: 1 1 auto; }
.hub-tile-label { font-size: 14px; font-weight: 700; }
.hub-tile-val {
  font-size: 11px;
  margin-top: 2px;
  line-height: 1.35;
}
.hub-tile-chev {
  flex: 0 0 auto;
  color: var(--muted, #8b93a7);
  font-size: 18px;
}
.hub-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  margin: 18px 0 6px;
}
.hub-signout { width: 100%; }
.hub-about-link {
  background: none;
  border: 0;
  color: var(--muted, #8b93a7);
  font-size: 12px;
  cursor: pointer;
  padding: 4px 8px;
}

.howto-card {
  background: linear-gradient(135deg,
                rgba(240, 185, 11, 0.10),
                rgba(240, 185, 11, 0.02)) , var(--surface);
  border: 1px solid rgba(240, 185, 11, 0.22);
}
.howto-card .howto-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 8px;
}
.howto-card .howto-icon {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: linear-gradient(135deg, var(--accent), #ffd24a);
  color: #181a20;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  flex-shrink: 0;
}

/* ---- Level-change celebration / consolation modal ---------------------
 * Two flavours share the same shell so the structure (.level-modal__crest
 * / __title / __caps / __body / button-row) is reusable. Each variant
 * paints its own surface so the moment feels distinct: gradient + warm
 * glow for upgrades, calmer "soft surface" for downgrades.
 *
 * The crest is a circular emoji holder rather than a raw glyph so
 * different tier icons (🌱/📈/⚡/💠) all sit in the same visual frame
 * regardless of their natural box. ``::before`` paints a soft halo
 * behind the emoji on upgrades to give it that "you just earned this"
 * shine without needing a third element in the DOM. */
.level-modal {
  position: relative;
  text-align: center;
  overflow: hidden;
}
.level-modal__crest {
  margin: 4px auto 12px;
  width: 84px;
  height: 84px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 44px;
  line-height: 1;
  position: relative;
  z-index: 1;
}
.level-modal__crest-glyph {
  display: inline-block;
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.25));
}
.level-modal__title {
  margin: 0 0 4px;
  font-size: 22px !important;
  font-weight: 800;
  letter-spacing: -0.3px;
}
.level-modal__subtitle {
  color: var(--text-muted);
  font-size: 13px;
  margin-bottom: 14px;
  line-height: 1.4;
}
.level-modal__caps {
  display: inline-block;
  padding: 8px 12px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 600;
  margin: 0 auto 14px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
}
.level-modal__body {
  color: var(--text-muted);
  font-size: 14px;
  line-height: 1.5;
  margin-bottom: 18px;
  padding: 0 6px;
}

/* ----- upgrade variant ------------------------------------------------ */
.level-modal--up {
  background:
    radial-gradient(120% 80% at 50% -10%,
      color-mix(in srgb, #F0B90B 18%, transparent) 0%,
      transparent 60%),
    var(--surface);
  border-top: 1px solid color-mix(in srgb, #F0B90B 40%, var(--border));
}
.level-modal--up .level-modal__crest {
  background: radial-gradient(circle at 50% 35%,
    color-mix(in srgb, #F0B90B 28%, transparent) 0%,
    transparent 70%);
}
.level-modal--up .level-modal__crest::before {
  content: "";
  position: absolute;
  inset: -16px;
  border-radius: 50%;
  background: radial-gradient(circle,
    color-mix(in srgb, #F0B90B 22%, transparent) 0%,
    transparent 65%);
  filter: blur(6px);
  z-index: -1;
  animation: lvl-halo 2.4s ease-in-out infinite;
}
.level-modal--up .level-modal__caps {
  background: color-mix(in srgb, #F0B90B 12%, var(--surface-2));
  border-color: color-mix(in srgb, #F0B90B 35%, var(--border));
}
@keyframes lvl-halo {
  0%, 100% { transform: scale(1);   opacity: 0.85; }
  50%      { transform: scale(1.08); opacity: 1; }
}

/* ----- downgrade variant --------------------------------------------- */
.level-modal--down {
  background: var(--surface);
}
.level-modal--down .level-modal__crest {
  background: var(--surface-2);
  border: 1px solid var(--border);
}
.level-modal--down .level-modal__title {
  font-weight: 700;
}

/* ----- confetti ------------------------------------------------------- */
/* CSS-only confetti so we don't carry a dependency for one moment. The
 * pieces live in an absolutely-positioned layer behind the content so
 * they fall behind the title without affecting layout. ``pointer-events:
 * none`` keeps clicks landing on the CTA underneath. */
.level-modal__confetti {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}
.level-modal__confetti-piece {
  position: absolute;
  top: -14px;
  width: 8px;
  height: 14px;
  border-radius: 2px;
  opacity: 0.9;
  transform: translateY(-20%) rotate(0deg);
  animation-name: lvl-confetti-fall;
  animation-timing-function: cubic-bezier(0.2, 0.8, 0.4, 1);
  animation-fill-mode: forwards;
}
@keyframes lvl-confetti-fall {
  0%   { transform: translateY(-20%) rotate(0deg);    opacity: 0; }
  10%  { opacity: 1; }
  100% { transform: translateY(420px) rotate(540deg); opacity: 0; }
}

/* ---- "Show all months" breakdown ---------------------------------------
 * Entry button sits under the big PNL number on the Profit card when
 * the user has the All-time period selected. Kept low-emphasis (ghost)
 * so it doesn't compete with the headline figure.
 */
.monthly-breakdown-btn {
  margin-top: 14px;
  width: 100%;
  justify-content: center;
  font-weight: 600;
}

/* Modal body: vertical list of months, newest first. Auto-scrolls when
 * the user has a long history so the modal never grows past ~60vh. */
.monthly-modal .monthly-body {
  max-height: 60vh;
  overflow-y: auto;
  margin: 4px -4px 0;
  padding: 0 4px;
  -webkit-overflow-scrolling: touch;
}
.monthly-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 4px;
  border-bottom: 1px solid var(--border);
}
.monthly-row:last-child { border-bottom: none; }
.monthly-label {
  font-weight: 600;
  font-size: 14px;
  color: var(--text);
}
.monthly-values {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
}
.monthly-value {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 15px;
}
.monthly-paper {
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

/* ================================================================ utilities */
.row-gap { display: grid; gap: 10px; }


/* =========================================================================
 * Dashboard v2 components (inspired by the reference TredFlex-ish layout)
 * ========================================================================= */

/* ---- BTC / ETH ticker row at the top of the dashboard ------------------ */
.ticker-row {
  position: relative;
  margin: 0 calc(var(--pad) * -1) 12px;
  padding: 2px 0 6px;
  overflow: hidden;
  /* Soft fade at both edges so cards appear to slide in/out of view. */
  -webkit-mask-image: linear-gradient(90deg,
      transparent 0,
      #000 24px,
      #000 calc(100% - 24px),
      transparent 100%);
          mask-image: linear-gradient(90deg,
      transparent 0,
      #000 24px,
      #000 calc(100% - 24px),
      transparent 100%);
}
.ticker-track {
  display: flex;
  gap: 10px;
  width: max-content;
  padding-left: 10px;
  animation: ticker-marquee linear infinite;
  animation-duration: 40s; /* JS overrides this based on card count */
  will-change: transform;
}
/* Pause while the user is actively interacting so they can read a card. */
.ticker-row:hover .ticker-track,
.ticker-row:active .ticker-track {
  animation-play-state: paused;
}
/* Respect the user's reduced-motion preference. */
@media (prefers-reduced-motion: reduce) {
  .ticker-track { animation: none; }
  .ticker-row { overflow-x: auto; -webkit-overflow-scrolling: touch; }
}
@keyframes ticker-marquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
.ticker-card {
  flex: 0 0 auto;
  width: 150px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
  box-shadow: var(--shadow);
  min-width: 0;
}
.ticker-head {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.ticker-head .coin-avatar { box-shadow: none; }
.ticker-sym {
  font-weight: 800;
  font-size: 13px;
  letter-spacing: 0.2px;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ticker-chg {
  font-size: 11px;
  font-weight: 700;
  padding: 3px 7px;
  border-radius: 999px;
  background: var(--chip-bg);
}
.ticker-chg.positive {
  background: color-mix(in srgb, var(--success) 18%, transparent);
  color: var(--success);
}
.ticker-chg.negative {
  background: color-mix(in srgb, var(--danger) 18%, transparent);
  color: var(--danger);
}
.ticker-price {
  margin-top: 8px;
  font-size: 17px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.2px;
  color: var(--text);
}

/* ---- PNL hero card (gradient-bordered, with pill tabs) ----------------- */
.hero-gradient {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  overflow: hidden;
}
.hero-gradient::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 1.5px;
  border-radius: inherit;
  background: var(--gradient);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  opacity: 0.65;
}

.profit-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}
/* Push the privacy eye to the far end of the profit header so it
   reads as "hide this number" and doesn't crowd the FREE pill. */
.profit-head .privacy-toggle {
  margin-left: auto;
}
.profit-title {
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.3px;
}
.pill-badge {
  background: var(--gradient);
  color: var(--accent-text);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.8px;
  padding: 4px 10px;
  border-radius: 999px;
  text-transform: uppercase;
}

/* Pill tabs — unselected = chip-bg, active = gradient fill --------------- */
.tab-row {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  margin-bottom: 18px;
  padding: 2px;
}
.tab-row::-webkit-scrollbar { display: none; }
.tab {
  flex: 0 0 auto;
  border: 0;
  background: transparent;
  color: var(--text-muted);
  font-weight: 600;
  font-size: 13px;
  padding: 9px 16px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, transform 0.08s;
  white-space: nowrap;
}
.tab:active { transform: scale(0.96); }
.tab.active {
  background: var(--gradient);
  color: var(--accent-text);
  box-shadow: 0 6px 16px color-mix(in srgb, var(--accent) 30%, transparent);
}

.pnl-block {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.pnl-label {
  font-size: 12px;
  color: var(--text-muted);
  font-weight: 600;
}
.pnl-value {
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -0.5px;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}

/* ---- Balance card -------------------------------------------------------- */
.balance-card {
  text-align: center;
  padding-top: 24px;
  padding-bottom: 20px;
}
.balance-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-soft);
  margin-bottom: 8px;
}
.balance-value {
  font-size: 36px;
  font-weight: 800;
  letter-spacing: -0.6px;
  font-variant-numeric: tabular-nums;
  color: var(--success);
  margin-bottom: 18px;
  line-height: 1.1;
}
.balance-value .balance-unit {
  font-size: 18px;
  color: var(--text-muted);
  font-weight: 700;
  margin-left: 2px;
  letter-spacing: 0;
}

/* Gradient-filled primary button variant --------------------------------- */
.btn.btn-gradient {
  background: var(--gradient);
  color: var(--accent-text);
  box-shadow: 0 10px 24px color-mix(in srgb, var(--accent) 35%, transparent);
}

/* ------------------------------------------------------------- trade history */
/* Entry point card on the Portfolio screen */
.history-entry {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  margin: 0 0 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  cursor: pointer;
  transition: transform 0.08s, box-shadow 0.15s, border-color 0.15s;
}
.history-entry:hover {
  transform: translateY(-1px);
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
}
.history-entry-icon {
  width: 40px;
  height: 40px;
  flex: 0 0 40px;
  display: grid;
  place-items: center;
  font-size: 20px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
}
.history-entry-meta { flex: 1 1 auto; min-width: 0; }
.history-entry-meta .name { font-weight: 700; color: var(--text); }
.history-entry-meta .sub { color: var(--text-muted); font-size: 12px; margin-top: 2px; }
.history-entry-chev {
  color: var(--text-muted);
  font-size: 24px;
  line-height: 1;
  flex: 0 0 auto;
}

/* Hero summary on the history screen */
.history-hero {
  padding: 18px 18px 20px;
  margin: 0 0 14px;
  background: var(--gradient);
  color: var(--accent-text);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  text-align: center;
}
.history-hero .label {
  opacity: 0.85;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.history-hero .value {
  font-size: 30px;
  font-weight: 800;
  margin-top: 6px;
  letter-spacing: -0.5px;
  /* Override the gradient tint on the gold hero — we still want the
     green/red cue for profit direction, so force a readable contrast. */
  color: var(--accent-text) !important;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
}
.history-hero .value.positive { color: #065f46 !important; }
.history-hero .value.negative { color: #7f1d1d !important; }
.history-hero .sub { opacity: 0.85; font-size: 12px; margin-top: 4px; }

/* Day grouping */
.history-day-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin: 16px 4px 8px;
}
.history-day-header .day-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--text-soft);
}
.history-day-header .day-total {
  font-size: 13px;
  font-weight: 700;
}

.history-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.history-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow);
  cursor: pointer;
  transition: transform 0.08s, border-color 0.15s;
}
.history-row:hover {
  transform: translateY(-1px);
  border-color: color-mix(in srgb, var(--accent) 30%, var(--border));
}
.history-row .coin-avatar { flex: 0 0 auto; }
.history-main { flex: 1 1 auto; min-width: 0; }
.history-main .top,
.history-main .bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.history-main .top .coin { font-weight: 700; color: var(--text); }
.history-main .top .profit { font-weight: 700; font-size: 15px; }
.history-main .bottom {
  margin-top: 2px;
  font-size: 12px;
}
.history-main .bottom .time { color: var(--text-muted); }
.history-main .prices {
  margin-top: 4px;
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* TredFlex brand banner on the home dashboard */
/* Brand strip on the home dashboard — spans the full Mini App width
   (edge-to-edge inside the ``main`` content area). Natural 640×198
   aspect gives ~30% height of the available width. */
.brand-banner {
  display: flex;
  justify-content: center;
  align-items: center;
  margin: 4px 0 14px;
  padding: 0;
  overflow: hidden;
  border-radius: 14px;
  background: #111;
  box-shadow: 0 8px 18px color-mix(in srgb, var(--accent) 16%, transparent);
  width: 100%;
}
.brand-banner img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: contain;
}

/* About / demo card on the Profile screen */
.about-card .about-demo {
  margin-top: 6px;
  border-radius: 12px;
  overflow: hidden;
  background: #111;
  border: 1px solid var(--border);
}
.about-card .about-demo img {
  display: block;
  width: 100%;
  height: auto;
}

/* Market-cap + suggested-settings card on the Coin dashboard */
.market-card { padding-top: 14px; padding-bottom: 14px; }
.market-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}
.tier-badge {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.6px;
  padding: 4px 10px;
  border-radius: 999px;
  text-transform: uppercase;
}
.tier-badge.tier-large {
  background: color-mix(in srgb, var(--success) 18%, transparent);
  color: var(--success);
  border: 1px solid color-mix(in srgb, var(--success) 40%, transparent);
}
.tier-badge.tier-mid {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
}
.tier-badge.tier-small {
  background: color-mix(in srgb, var(--warning) 20%, transparent);
  color: var(--warning);
  border: 1px solid color-mix(in srgb, var(--warning) 45%, transparent);
}
.tier-badge.tier-micro {
  background: color-mix(in srgb, var(--danger) 20%, transparent);
  color: var(--danger);
  border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
}

.market-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-bottom: 10px;
}
.market-stat {
  background: color-mix(in srgb, var(--text) 3%, transparent);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 10px;
}
.market-stat .label {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-soft);
  margin-bottom: 2px;
}
.market-stat .value {
  font-size: 15px;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.market-stat .value.positive { color: var(--success); }
.market-stat .value.negative { color: var(--danger); }

.tier-desc {
  font-size: 12px;
  line-height: 1.45;
  margin-bottom: 10px;
}

.suggest-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.suggest-tile {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 20%, var(--border));
  border-radius: 10px;
  padding: 8px 10px;
  text-align: center;
}
.suggest-tile .title {
  font-size: 11px;
  font-weight: 700;
  color: var(--text);
  text-transform: uppercase;
  letter-spacing: 0.4px;
}
.suggest-tile .sub {
  font-size: 10px;
  margin-top: 1px;
  margin-bottom: 4px;
}
.suggest-tile .value {
  font-size: 16px;
  font-weight: 800;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.suggest-tile .range {
  font-size: 10px;
  margin-top: 2px;
}

/* Open-exposure card on the Coin dashboard */
.exposure-card { padding-top: 14px; padding-bottom: 14px; }
.exposure-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}
.exposure-badge {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.6px;
  padding: 4px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--success) 18%, transparent);
  color: var(--success);
  border: 1px solid color-mix(in srgb, var(--success) 40%, transparent);
}
.exposure-badge.near {
  background: color-mix(in srgb, var(--warning) 20%, transparent);
  color: var(--warning);
  border-color: color-mix(in srgb, var(--warning) 45%, transparent);
}
.exposure-badge.over {
  background: color-mix(in srgb, var(--danger) 20%, transparent);
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
}
.exposure-badge.muted {
  background: transparent;
  color: var(--text-muted);
  border-color: var(--border);
}
.exposure-amount {
  display: flex;
  align-items: baseline;
  gap: 4px;
  font-variant-numeric: tabular-nums;
  margin-bottom: 10px;
}
.exposure-amount .value {
  font-size: 26px;
  font-weight: 800;
  color: var(--text);
  letter-spacing: -0.4px;
}
.exposure-amount .cap {
  font-size: 14px;
  font-weight: 700;
  color: var(--text-muted);
}
.exposure-amount .cap.muted { color: var(--text-muted); opacity: 0.6; }
.exposure-bar {
  position: relative;
  height: 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--text) 8%, transparent);
  overflow: hidden;
  margin-bottom: 10px;
}
.exposure-bar .fill {
  height: 100%;
  background: var(--success);
  border-radius: inherit;
  transition: width 0.3s ease;
}
.exposure-bar.near .fill { background: var(--warning); }
.exposure-bar.over .fill { background: var(--danger); }
.exposure-foot { font-size: 12px; }

/* Per-coin USDT exposure readout on the Settings screen */
.limit-readout {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 10px 12px;
  margin: 2px 0 14px;
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--border));
  border-radius: 12px;
}
.limit-readout .label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-soft);
}
.limit-readout .value {
  font-size: 18px;
  font-weight: 800;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.limit-readout .cap {
  font-size: 12px;
  color: var(--text-muted);
  font-weight: 600;
  margin-left: auto;
}
.limit-readout .cap[data-over="true"] {
  color: var(--danger);
}

/* Sold-order detail view */
.sold-hero {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  margin-bottom: 12px;
}
.sold-hero-main { flex: 1 1 auto; min-width: 0; }
.sold-hero-main .symbol { font-size: 18px; font-weight: 700; color: var(--text); }
.sold-hero-main .oid { font-size: 12px; margin-top: 2px; }
.sold-hero-profit { flex: 0 0 auto; text-align: right; }
.sold-hero-profit .amount { font-size: 20px; font-weight: 800; letter-spacing: -0.3px; }
.sold-hero-profit .pct { font-size: 12px; margin-top: 2px; font-weight: 700; }

/* Tight secondary line under a kv row (used for fee breakdown). The
   parent ``.kv`` is flex with only two slots, so the sub-line lives
   alongside it inside ``.kv-group``. */
.kv-group .kv { border-bottom: none; padding-bottom: 2px; }
.kv-group {
  border-bottom: 1px solid var(--divider);
  padding-bottom: 8px;
}
.kv-sub {
  font-size: 11px;
  text-align: right;
  color: var(--text-muted);
}

/* ---------------------------------------------------------- Vortex filter */
/* Settings-form block for the opt-in Vortex entry filter. */
.vortex-block {
  margin-top: 16px;
  padding: 12px;
  border: 1px dashed color-mix(in srgb, var(--accent) 30%, var(--border));
  border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 4%, transparent);
}
.vortex-block .toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-weight: 700;
  color: var(--text);
}
.vortex-block .toggle input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: var(--accent);
}
.vortex-block .vortex-body {
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.field-row .field { margin: 0; }

/* Live VI+/VI- snapshot shown inside the settings block. */
.vortex-live {
  padding: 10px 12px;
  border-radius: 10px;
  font-variant-numeric: tabular-nums;
  background: color-mix(in srgb, var(--success) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--success) 30%, var(--border));
}
.vortex-live.blocked {
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  border-color: color-mix(in srgb, var(--danger) 30%, var(--border));
}
.vortex-live .label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-soft);
  margin-bottom: 4px;
}
.vortex-live .stats {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}
.vortex-live .verdict {
  margin-top: 4px;
  font-size: 12px;
  font-weight: 700;
  color: var(--success);
}
.vortex-live.blocked .verdict { color: var(--danger); }

/* Compact readout shown on the coin dashboard Strategy card. */
.vortex-inline {
  margin-top: 8px;
  padding: 8px 10px;
  border-radius: 10px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  background: color-mix(in srgb, var(--success) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--success) 26%, var(--border));
  color: var(--text);
}
.vortex-inline.blocked {
  background: color-mix(in srgb, var(--danger) 8%, transparent);
  border-color: color-mix(in srgb, var(--danger) 26%, var(--border));
}
.vortex-inline .verdict {
  margin-left: auto;
  font-weight: 700;
  color: var(--success);
}
.vortex-inline.blocked .verdict { color: var(--danger); }

/* =========================================================================
 * Paper trading / emulation mode
 *
 * We reuse the existing ``card``/``coin-row`` primitives and overlay a
 * distinct purple tint so simulated bots, orders and history rows can
 * never be mistaken for live ones. ``--paper-accent`` is defined here so
 * both themes can tweak it via ``data-theme`` if ever needed.
 * ========================================================================= */
:root {
  --paper-accent:     #8B5CF6;   /* violet 500 */
  --paper-accent-2:   #A78BFA;   /* violet 400 */
  --paper-tint:       rgba(139, 92, 246, 0.08);
  --paper-border:     rgba(139, 92, 246, 0.28);
}

.demo-badge {
  display: inline-block;
  padding: 2px 8px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.08em;
  color: #fff;
  background: linear-gradient(135deg, var(--paper-accent) 0%, var(--paper-accent-2) 100%);
  border-radius: 999px;
  vertical-align: middle;
  text-transform: uppercase;
  line-height: 1.3;
}

.paper-section {
  margin-top: 24px;
  padding-top: 14px;
  border-top: 1px dashed var(--paper-border);
}
.paper-section-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}
.paper-section-head .section-title { margin: 0; }

/* Portfolio Real/Paper toggle. The Paper tab swaps the accent gradient
 * for the violet paper palette when active so the user always knows at a
 * glance which lane they're looking at. */
.portfolio-tabs { margin-bottom: 14px; }
.portfolio-tabs .tab.paper { gap: 6px; }
.portfolio-tabs .tab.paper .demo-badge { opacity: 0.85; }
.portfolio-tabs .tab.paper.active {
  background: linear-gradient(135deg,
    var(--paper-accent) 0%,
    var(--paper-accent-2) 100%);
  color: #fff;
  box-shadow: 0 6px 16px color-mix(in srgb,
    var(--paper-accent) 35%, transparent);
}
.portfolio-tabs .tab.paper.active .demo-badge {
  background: rgba(255, 255, 255, 0.22);
  opacity: 1;
}

.portfolio-pane.paper .paper-banner { margin-top: 0; }

/* ----------------------------------------------------- coin-detail toggle
 * Same visual rules as the portfolio toggle so the pattern is familiar,
 * but with two additions specific to the coin-detail screen:
 *   (a) tighter margins so the switcher sits flush with the header,
 *   (b) a ``.missing`` state on inactive tabs whose bot doesn't exist
 *       yet -- dimmed and with a dashed border so the user sees at a
 *       glance that tapping it starts a create flow instead of just
 *       navigating. */
.coin-mode-tabs {
  margin: 8px 0 14px;
}
.coin-mode-tabs .tab.paper { gap: 6px; }
.coin-mode-tabs .tab.paper .demo-badge { opacity: 0.85; }
.coin-mode-tabs .tab.paper.active {
  background: linear-gradient(135deg,
    var(--paper-accent) 0%,
    var(--paper-accent-2) 100%);
  color: #fff;
  box-shadow: 0 6px 16px color-mix(in srgb,
    var(--paper-accent) 35%, transparent);
}
.coin-mode-tabs .tab.paper.active .demo-badge {
  background: rgba(255, 255, 255, 0.22);
  opacity: 1;
}
.coin-mode-tabs .tab.missing {
  opacity: 0.62;
  border-style: dashed;
  /* Subtle "plus" hint after the label so it's obvious a tap here
   * creates the bot rather than opening it. ::after is an inline
   * badge; stays accessible because the click-handler reads the
   * full label + badge anyway. */
}
.coin-mode-tabs .tab.missing::after {
  content: "+";
  display: inline-block;
  margin-left: 6px;
  width: 16px;
  height: 16px;
  line-height: 14px;
  text-align: center;
  border: 1px dashed currentColor;
  border-radius: 50%;
  font-size: 12px;
  font-weight: 700;
  vertical-align: middle;
}

.coin-row.paper {
  background:
    linear-gradient(90deg, var(--paper-tint) 0%, transparent 60%),
    var(--surface);
  border: 1px solid var(--paper-border);
}

.paper-card {
  background:
    linear-gradient(135deg, var(--paper-tint) 0%, transparent 70%),
    var(--surface);
  border: 1px solid var(--paper-border);
}
.paper-card-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

.paper-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  margin: 8px 0 12px;
  border-radius: var(--radius-sm);
  background: var(--paper-tint);
  border: 1px solid var(--paper-border);
  font-size: 12px;
  color: var(--text-soft, inherit);
}

.order-card.paper {
  border: 1px solid var(--paper-border);
}
.order-card.paper .row-1 {
  display: flex;
  align-items: center;
  gap: 8px;
}

.history-row.paper .coin {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.paper-dashboard {
  /* Extra top breathing room above the Emulation card on Home so it's
     visually separated from the preceding Binance-wallet entry card.
     The two share the same surface treatment, and at the default 12px
     card gap their headline labels ("Binance wallet" / "EMULATION")
     end up visually crowded. The +16px (28px total to the previous
     card) signals "new section" without needing a divider line. */
  margin-top: 16px;
  background:
    linear-gradient(135deg, var(--paper-tint) 0%, transparent 75%),
    var(--surface);
  border: 1px solid var(--paper-border);
}
.paper-dashboard-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}
.paper-dashboard-head .title {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--paper-accent);
}
.paper-dashboard-body {
  margin: 6px 0 12px;
}
.paper-dashboard-body .label { font-size: 12px; }
.paper-dashboard-body .value {
  font-size: 24px;
  font-weight: 800;
  margin-top: 2px;
}

/* -------------------------------------------------------------- auth gate
 * Login / Register screen styling. The gate is a full-bleed single-card
 * layout intentionally different from the rest of the app: no bottom
 * nav, no header buttons, no pull-to-refresh -- nothing is navigable until
 * the user authenticates. We achieve this with ``body[data-screen=auth]``
 * selectors rather than toggling individual elements, so adding a new
 * chrome element later just works.
 */
body[data-screen="auth"] .app-header #back-btn,
body[data-screen="auth"] .ptr,
body[data-screen="auth"] .bottom-nav {
  display: none !important;
}
body[data-screen="auth"] .app-header {
  /* Keep the header visible so the TredFlex title still reads, but
   * trim the spacing so the auth card can dominate the viewport. */
  box-shadow: none;
  border-bottom: none;
}
body[data-screen="auth"] #app {
  /* Nuke bottom padding reserved for the (now hidden) nav. */
  padding-bottom: 16px;
  min-height: calc(100dvh - 56px);
  display: flex;
  align-items: stretch;
  justify-content: center;
}

.auth-screen {
  width: 100%;
  max-width: 420px;
  padding: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.auth-card {
  width: 100%;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 28px 22px;
  box-shadow: 0 12px 38px rgba(0, 0, 0, 0.18);
  animation: auth-rise 0.38s cubic-bezier(.2, .8, .2, 1) both;
}

@keyframes auth-rise {
  0%   { opacity: 0; transform: translateY(14px); }
  100% { opacity: 1; transform: translateY(0); }
}

.auth-brand {
  text-align: center;
  margin-bottom: 22px;
}
.auth-logo, .auth-logo-fallback {
  width: 72px;
  height: 72px;
  margin: 0 auto 10px;
  display: block;
  border-radius: 20px;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  padding: 14px;
  box-shadow: 0 8px 22px color-mix(in srgb, var(--accent) 35%, transparent);
}
.auth-logo-fallback {
  line-height: 44px;
  font-size: 36px;
  text-align: center;
  color: #fff;
}
.auth-brand h1 {
  margin: 6px 0 4px;
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.3px;
}
.auth-subtitle {
  font-size: 14px;
  color: var(--muted);
}

.auth-form .field { margin-bottom: 14px; }
.auth-form input[type="password"] {
  width: 100%;
  box-sizing: border-box;
  padding: 13px 14px;
  font-size: 16px;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 12px;
  outline: none;
  transition: border-color 0.18s, box-shadow 0.18s;
}
.auth-form input[type="password"]:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
}

.auth-submit {
  width: 100%;
  margin-top: 8px;
  padding: 13px;
  font-size: 16px;
  font-weight: 700;
}
.auth-submit[disabled] {
  opacity: 0.7;
  cursor: progress;
}

.auth-switch {
  text-align: center;
  margin-top: 18px;
  font-size: 14px;
}
.auth-switch a {
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
}
.auth-switch a:hover { text-decoration: underline; }

/* --------------------------------------------- approval splash (waiting / rejected) */
/* Reuses .auth-screen and .auth-card; layered styles add the icon
   bubble, the rejection-reason callout, the meta line, the tip
   block, and the action stack. Light/dark theming flows from the
   shared variables, so no separate dark rules are needed. */
.approval-card .auth-brand { margin-bottom: 18px; }
.approval-icon {
  font-size: 44px;
  line-height: 1;
  width: 76px;
  height: 76px;
  margin: 0 auto 12px;
  display: grid;
  place-items: center;
  border-radius: 22px;
  background: var(--surface-2);
  border: 1px solid var(--border);
}

.approval-body { margin: 4px 0 14px; }
.approval-reason {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 14px;
  margin-bottom: 10px;
}
.approval-reason-label {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--muted);
  margin-bottom: 4px;
}
.approval-reason-text {
  font-size: 15px;
  color: var(--text);
  white-space: pre-wrap;
  word-break: break-word;
}
.approval-meta {
  font-size: 13px;
  color: var(--text-soft, var(--muted));
  text-align: center;
}

.approval-tip {
  background: var(--surface-2);
  border: 1px dashed var(--border);
  border-radius: 12px;
  padding: 12px 14px;
  font-size: 13px;
  line-height: 1.45;
  color: var(--text-soft, var(--text));
  margin: 6px 0 16px;
}
.approval-tip strong { color: var(--text); }

.approval-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 4px;
}
.approval-actions .btn { width: 100%; padding: 13px; font-size: 16px; }

/* --------------------------------------- owner dashboard: pending approvals card */
.owner-approvals-count {
  margin-left: 6px;
  font-size: 13px;
  font-weight: 700;
  color: var(--accent);
}
.owner-approval-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px dashed var(--border);
}
.owner-approval-row:last-child { border-bottom: 0; }
.owner-approval-meta {
  flex: 1 1 auto;
  min-width: 0;
}
.owner-approval-name {
  font-weight: 600;
  font-size: 14px;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.owner-approval-sub {
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 2px;
}
.owner-approval-actions {
  display: flex;
  gap: 6px;
  flex: 0 0 auto;
}

/* ------------------------------------------------------------- wallet page */
/* Entry card on the home screen linking to the wallet overview. Same
   shape as .history-entry so the two entries feel like siblings. */
.wallet-entry {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  margin: 14px 0 0;
  cursor: pointer;
  transition: transform 0.08s, box-shadow 0.15s, border-color 0.15s;
}
.wallet-entry:hover {
  transform: translateY(-1px);
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
}
.wallet-entry-icon {
  width: 40px;
  height: 40px;
  flex: 0 0 40px;
  display: grid;
  place-items: center;
  font-size: 20px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
}
.wallet-entry-meta { flex: 1 1 auto; min-width: 0; }
.wallet-entry-meta .name { font-weight: 700; color: var(--text); }
.wallet-entry-meta .sub {
  color: var(--text-muted);
  font-size: 12px;
  margin-top: 2px;
}
.wallet-entry-chev {
  color: var(--text-muted);
  font-size: 24px;
  line-height: 1;
  flex: 0 0 auto;
}

.wallet-total .balance-value { font-size: 28px; }

.wallet-section { padding: 14px 16px; }
.wallet-section-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-bottom: 10px;
  margin-bottom: 10px;
  border-bottom: 1px solid var(--border);
}
.wallet-section-emoji { font-size: 20px; }
.wallet-section-title {
  flex: 1 1 auto;
  font-weight: 700;
  color: var(--text);
}
.wallet-section-total {
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.wallet-section-body { display: grid; gap: 6px; }
.wallet-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
}
.wallet-row .label { color: var(--text-muted); font-size: 14px; }
.wallet-row .value {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
/* Small metadata chip sitting next to a row label -- used for the
   Simple Earn Flexible APR. Green-tinted because APR reads as
   "income" and it tracks the positive/gain colour used elsewhere. */
.wallet-row .row-badge {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 8px;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  border-radius: 999px;
  color: var(--success);
  background: color-mix(in srgb, var(--success) 14%, transparent);
  letter-spacing: 0.2px;
  white-space: nowrap;
  vertical-align: 1px;
}
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

/* Inline input + trailing affordance (e.g. Max, Use password). */
.input-with-suffix {
  display: flex;
  align-items: stretch;
  gap: 8px;
}
.input-with-suffix > input {
  flex: 1 1 auto;
  min-width: 0;
  /* Telegram's WebView on some Android builds renders type=number
     inputs with the system text colour (often near-transparent on a
     dark theme). Force visibility explicitly instead of relying on
     the inherited .field input rule. */
  color: var(--text) !important;
  -webkit-text-fill-color: var(--text);
  font-size: 16px;
  line-height: 1.3;
  background: var(--surface-2);
}
.input-with-suffix > input::placeholder {
  color: var(--text-soft);
  -webkit-text-fill-color: var(--text-soft);
  opacity: 1;
}
.input-with-suffix > .btn {
  /* Global .btn has width: 100% which, combined with flex: 0 0 auto,
     makes the inline button's base size the full row and collapses
     the input to a sliver. Reset width so the button only takes the
     space its content needs. */
  flex: 0 0 auto;
  width: auto;
  min-width: 64px;
}
/* Strip Chrome/Android's spinner arrows that can shove the value
   off-screen and sometimes overlap the Max button. */
.input-with-suffix > input[type="number"]::-webkit-outer-spin-button,
.input-with-suffix > input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.input-with-suffix > input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}
.btn.tiny {
  padding: 6px 10px;
  font-size: 12px;
  border-radius: 10px;
  align-self: stretch;
}

/* Transfer modal: From/To selects side-by-side with a swap button
   between them. Stacks vertically below 380px so the selects don't
   get squeezed and the swap button stays tappable. */
.transfer-modal .transfer-row {
  display: flex;
  align-items: flex-end;
  gap: 10px;
}
.transfer-modal .transfer-col {
  flex: 1 1 0;
  min-width: 0;
}
.transfer-modal .transfer-swap {
  flex: 0 0 auto;
  padding-bottom: 2px;
}
.transfer-modal .transfer-swap .btn {
  min-width: 34px;
  height: 34px;
  padding: 0 10px;
  font-size: 16px;
  line-height: 1;
}
.transfer-modal .transfer-label {
  font-size: 12px;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 4px;
}
.transfer-modal select.select {
  width: 100%;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 14px;
  appearance: none;
  -webkit-appearance: none;
}
@media (max-width: 380px) {
  .transfer-modal .transfer-row {
    flex-direction: column;
    align-items: stretch;
  }
  .transfer-modal .transfer-swap {
    align-self: center;
    padding: 0;
  }
}

/* ================================================================ portfolio
 * Portfolio "card" wrapper that hosts the owned-bots list, the bot
 * count, and the "+ Add" button in its header. Inspired by the
 * reference design where the user's portfolio lives inside a single
 * dark card rather than floating rows over the page background.
 *
 * Inside the card we use a list layout with thin dividers between
 * rows (not the usual ``.coin-list`` gap) so it reads as one cohesive
 * object.
 */
.portfolio-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) + 4px);
  padding: 18px 16px;
  box-shadow: var(--shadow);
}
.portfolio-card.paper {
  background:
    linear-gradient(135deg, var(--paper-tint) 0%, transparent 70%),
    var(--surface);
  border-color: var(--paper-border);
}
.portfolio-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}
.portfolio-card-title {
  font-size: 22px;
  font-weight: 800;
  color: var(--text);
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
}
.portfolio-card-count {
  font-size: 15px;
  font-weight: 600;
  color: var(--text-muted);
}
.portfolio-add-btn {
  background: transparent;
  color: var(--accent);
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  border-radius: 999px;
  padding: 8px 18px;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: transform 0.08s, background 0.15s, border-color 0.15s;
  flex-shrink: 0;
}
.portfolio-add-btn:active {
  transform: scale(0.96);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-color: var(--accent);
}

/* Rows inside the card sit flush with the card background and only
 * get a hairline divider between each other -- no individual card
 * chrome, which would duplicate the outer card's shadow. */
.portfolio-card-list {
  display: grid;
  gap: 0;
}
.portfolio-card-list .coin-row {
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  padding: 12px 2px;
  border-top: 1px solid var(--border);
}
.portfolio-card-list .coin-row:first-child { border-top: 0; }
.portfolio-card-list .coin-row:active {
  background: color-mix(in srgb, var(--surface-2) 70%, transparent);
}
.portfolio-card-list .coin-row.paper {
  background:
    linear-gradient(90deg,
      color-mix(in srgb, var(--paper-tint) 50%, transparent) 0%,
      transparent 70%);
}

.portfolio-card-empty {
  padding: 28px 8px 16px;
  text-align: center;
}
.portfolio-card-empty .emoji { font-size: 36px; margin-bottom: 6px; }
.portfolio-card-empty .title {
  color: var(--text);
  font-weight: 700;
  margin-bottom: 2px;
}
.portfolio-card-empty .sub {
  color: var(--text-muted);
  font-size: 13px;
}

/* ================================================================ add-token
 * Token picker behind the portfolio "+ Add" button. Single column:
 * title, search, category pills, list.
 */
.add-token-view {
  display: grid;
  gap: 16px;
}
.add-token-title {
  margin: 0;
  font-size: 28px;
  font-weight: 800;
  letter-spacing: -0.2px;
  color: var(--text);
}

.add-token-search {
  position: relative;
  display: flex;
  align-items: center;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 12px 14px;
  gap: 10px;
  transition: border-color 0.15s, background 0.15s;
}
.add-token-search:focus-within {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
  background: var(--surface);
}
.add-token-search .icon {
  color: var(--text-muted);
  font-size: 15px;
  line-height: 1;
}
.add-token-search-input {
  flex: 1;
  background: transparent;
  border: 0;
  outline: 0;
  color: var(--text);
  font-size: 15px;
  font-weight: 500;
  padding: 0;
  min-width: 0;
}
.add-token-search-input::placeholder {
  color: var(--text-muted);
  font-weight: 500;
}
/* Kill the Safari/Chromium "x" clear button so the search still looks
 * calm on mobile -- we rely on the soft keyboard's dismiss anyway. */
.add-token-search-input::-webkit-search-cancel-button,
.add-token-search-input::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
}

/* Horizontally-scrollable pill row so the category list never wraps
 * and the active pill can keep its richer layout (label + sub) without
 * blowing up the header. */
.add-token-cats {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  scrollbar-width: none;
  margin: 0 -4px;
  padding: 0 4px 2px;
}
.add-token-cats::-webkit-scrollbar { display: none; }

.add-token-cat {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  padding: 8px 16px;
  border-radius: 999px;
  border: 0;
  background: transparent;
  color: var(--text-muted);
  font-weight: 700;
  font-size: 14px;
  line-height: 1.15;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s, transform 0.08s;
  flex-shrink: 0;
}
.add-token-cat:active { transform: scale(0.96); }
.add-token-cat.active {
  background: linear-gradient(135deg,
    var(--accent) 0%,
    color-mix(in srgb, var(--accent) 50%, #c688ff) 100%);
  color: #fff;
  box-shadow: 0 6px 16px color-mix(in srgb, var(--accent) 30%, transparent);
}
.add-token-cat-label {
  font-weight: 800;
  letter-spacing: 0.1px;
}
.add-token-cat-sub {
  font-size: 10px;
  font-weight: 600;
  opacity: 0.9;
  margin-top: 1px;
}

.add-token-list {
  display: grid;
  gap: 2px;
}
.add-token-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 4px;
  border-radius: 10px;
  cursor: pointer;
  transition: background 0.15s, transform 0.08s;
}
.add-token-row:active {
  transform: scale(0.99);
  background: color-mix(in srgb, var(--surface-2) 80%, transparent);
}
.add-token-row-name {
  flex: 1;
  min-width: 0;
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.add-token-row-sym {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-muted);
  letter-spacing: 0.4px;
  flex-shrink: 0;
}

.add-token-empty {
  padding: 28px 12px;
  text-align: center;
  color: var(--text-muted);
  font-size: 14px;
}

/* ---- per-user bot-limit pill on the Add Token page ------------------
   Compact badge showing "Real bots  3 / 5" (or emulation variant) so
   users know how close they are to the cap BEFORE they tap a token.
   ``.at-limit`` swaps the chip to a warning palette + reveals the
   "Limit reached" hint inline. */
.add-token-limit {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 4px 0 10px;
  padding: 8px 12px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--surface) 70%, transparent);
  border: 1px solid var(--border);
  font-size: 12px;
  color: var(--text-muted);
}
.add-token-limit .k { font-weight: 600; color: var(--text); }
.add-token-limit .v {
  margin-left: auto;
  padding: 2px 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
  font-weight: 700;
  letter-spacing: 0.3px;
}
.add-token-limit .hint {
  flex-basis: 100%;
  font-size: 11.5px;
  color: var(--warning, #d9822b);
}
.add-token-limit.at-limit {
  border-color: color-mix(in srgb, var(--warning, #d9822b) 55%, var(--border));
  background: color-mix(in srgb, var(--warning, #d9822b) 12%, var(--surface));
}
.add-token-limit.at-limit .v {
  background: color-mix(in srgb, var(--warning, #d9822b) 22%, transparent);
  color: var(--warning, #d9822b);
}

/* ================================================================== admin
   Styling for the /admin dashboard. Kept intentionally minimal -- the
   page reuses the same card layout as the rest of the app so admins
   get a familiar frame, with a small shield accent to signal elevated
   privilege. */

.admin-pill {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 0.2px;
}

.admin-card h2 {
  display: flex;
  align-items: center;
  gap: 6px;
}

.admin-log {
  max-height: 280px;
  overflow: auto;
  background: color-mix(in srgb, var(--surface) 60%, transparent);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  line-height: 1.45;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text-muted);
}

/* The admin tab follows the same glass-nav pattern as the other tabs.
   When it's inactive we just ghost it slightly more than the others so
   admins can tell the elevated tab apart at a glance; when active, the
   shared ``.nav-btn.active`` rule takes over (full accent + dot). */
.nav-btn.admin-nav:not(.active) {
  color: color-mix(in srgb, var(--accent) 55%, var(--text-muted));
}

/* ================================================================== owner
   Owner is the top-level role. We use a gold / amber accent to
   distinguish it from the blue/teal accent used for Admin, so at a
   glance the hierarchy reads: Owner (gold) > Admin (accent) > User. */

:root {
  --owner-accent: #d9a441;            /* warm gold */
  --owner-accent-strong: #b9851d;
}

.owner-pill {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--owner-accent) 22%, transparent);
  color: var(--owner-accent-strong);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.2px;
}

.owner-card {
  border: 1px solid color-mix(in srgb, var(--owner-accent) 45%, var(--border));
  box-shadow: 0 2px 10px color-mix(in srgb, var(--owner-accent) 14%, transparent);
}
.owner-card h2 {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--owner-accent-strong);
}

/* User-management list on the /owner page. Each row is a compact
   three-column layout: identity -> role/key tags -> action button. */
.owner-user-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.owner-user-row {
  /* 2-column grid: left column owns the whole identity stack
     (name + id + tags), right column owns the Manage affordance.
     Long hex user_ids no longer push the layout sideways since
     the identity stack is constrained by ``min-width:0`` on
     ``.owner-user-main`` and the ID line uses ``overflow-wrap``. */
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: color-mix(in srgb, var(--surface) 80%, transparent);
}
.owner-user-main {
  /* ``min-width:0`` is the key bit -- without it the column
     refuses to shrink below the intrinsic width of the longest
     unbreakable token (the hex user_id), which is what caused
     the tags to overlap the id text in the previous layout. */
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.owner-user-name {
  font-weight: 600;
  font-size: 13.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.owner-user-id-line {
  /* Hex user_ids are unbreakable tokens; allow mid-token wraps
     so the line stays within the column instead of forcing the
     grid to widen. ``overflow-wrap: anywhere`` is supported on
     all the browsers Telegram WebView ships and degrades to
     ``break-word`` on the older ones. */
  font-size: 11px;
  line-height: 1.35;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.owner-user-tags {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  /* Tags now sit under the id line, so left-align them with the
     identity rather than pushing them to the right edge. */
  justify-content: flex-start;
  margin-top: 2px;
}
.owner-user-tags .tag {
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  border: 1px solid var(--border);
  color: var(--text-muted);
  background: var(--surface);
}
.owner-user-tags .tag.ok {
  color: var(--positive, #2a9d5a);
  border-color: color-mix(in srgb, var(--positive, #2a9d5a) 38%, var(--border));
}
.owner-user-tags .tag.warn {
  color: var(--warning, #d9822b);
  border-color: color-mix(in srgb, var(--warning, #d9822b) 38%, var(--border));
}
.owner-user-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  /* Reserve a clean tap target on the right edge that scales
     with the identity stack height. ``align-self: stretch`` on
     the grid item plus centred contents keeps Manage vertically
     centred no matter how many tag pills wrap below the id. */
  align-self: stretch;
  justify-content: flex-end;
  white-space: nowrap;
}
.owner-user-actions .btn {
  white-space: nowrap;
}

/* Manage entry button. The whole row is clickable (see
   .owner-user-row--clickable below); these elements just give the
   right-side affordance some visual weight so users discover the
   tap target. */
.owner-user-manage {
  letter-spacing: 0.3px;
  text-transform: uppercase;
  font-size: 11px;
}
.owner-user-chev {
  font-size: 22px;
  line-height: 1;
  color: var(--muted);
  transition: transform 0.15s ease, color 0.15s ease;
}

/* Clickable user row -- entire card opens the manage screen. The
   hover / focus states use ``color-mix`` against the accent token
   so they track the active theme; the chevron nudges right to
   reinforce the "tap to open" affordance. */
.owner-user-row--clickable {
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease,
              transform 0.05s ease;
}
.owner-user-row--clickable:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  background: color-mix(in srgb, var(--accent) 6%, var(--surface));
}
.owner-user-row--clickable:hover .owner-user-chev {
  transform: translateX(2px);
  color: var(--accent);
}
.owner-user-row--clickable:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.owner-user-row--clickable:active {
  transform: scale(0.997);
}
.owner-pill-inline,
.admin-pill-inline {
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}
.owner-pill-inline {
  background: color-mix(in srgb, var(--owner-accent) 22%, transparent);
  color: var(--owner-accent-strong);
}
.admin-pill-inline {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
}

/* ============================================================ user level
   Commercial tiers: Starter < Trader < Pro < Elite. Visually distinct
   from the operational role pills (Owner gold, Admin accent) so a
   user can tell at a glance "I'm Pro" without conflating it with
   staff-only badges. Same shape as the role pills (rounded chip) so
   they stack cleanly when both are present (e.g. a Pro who is also
   an Admin). */

:root {
  /* Cool slate -- "starting line", neutral and uncontroversial. */
  --level-starter-bg:   #94a3b8;
  --level-starter-fg:   #f1f5f9;
  /* Trader: confident green. Echoes the positive/profit colour the
     rest of the app uses, so "Trader" reads as "you're trading". */
  --level-trader-bg:    #16a34a;
  --level-trader-fg:    #ecfdf5;
  /* Pro: indigo -- distinct from the Admin teal/accent without
     stepping on the Owner gold. */
  --level-pro-bg:       #6366f1;
  --level-pro-fg:       #eef2ff;
  /* Elite: premium gradient (violet -> gold) with subtle glow.
     Reserved for the top tier so it feels earned, not generic. */
  --level-elite-grad:   linear-gradient(135deg, #7c3aed 0%, #d9a441 100%);
  --level-elite-fg:     #fffbeb;
  --level-elite-glow:   0 1px 8px color-mix(in srgb, #d9a441 35%, transparent);
}

.level-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.2px;
  white-space: nowrap;
  line-height: 1.2;
  border: 1px solid transparent;
}
.level-pill--sm {
  font-size: 11px;
  padding: 2px 8px;
}

.level-pill--starter {
  background: color-mix(in srgb, var(--level-starter-bg) 22%, transparent);
  color: var(--level-starter-bg);
  border-color: color-mix(in srgb, var(--level-starter-bg) 35%, transparent);
}
.level-pill--trader {
  background: color-mix(in srgb, var(--level-trader-bg) 20%, transparent);
  color: var(--level-trader-bg);
  border-color: color-mix(in srgb, var(--level-trader-bg) 40%, transparent);
}
.level-pill--pro {
  background: color-mix(in srgb, var(--level-pro-bg) 22%, transparent);
  color: var(--level-pro-bg);
  border-color: color-mix(in srgb, var(--level-pro-bg) 42%, transparent);
}
.level-pill--elite {
  /* Elite gets the gradient treatment so it feels like a "premium"
     badge rather than just another colour swatch. The text colour is
     near-white and we add a faint glow so it pops on dark themes. */
  background: var(--level-elite-grad);
  color: var(--level-elite-fg);
  border-color: color-mix(in srgb, #d9a441 60%, transparent);
  box-shadow: var(--level-elite-glow);
}

/* (The header badge moved to .level-glyph, defined near the
   .app-header rules -- it's an emoji-only accent rather than a
   full pill so the title doesn't get crowded. The full-pill
   variants above are still used by viewProfile() and the owner
   user-list, where there's room to spell out "Pro" / "Elite".) */

/* Plan dropdown shown in the owner user-list. Same compact footprint
   as the existing Promote/Demote button so the row's right column
   stays visually balanced. */
.owner-level-select {
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  font-size: 12px;
  padding: 5px 22px 5px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface)
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'><path d='M1 3 5 7 9 3' stroke='%23888' stroke-width='1.4' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>")
    no-repeat right 7px center;
  color: var(--text);
  cursor: pointer;
  min-width: 96px;
}
.owner-level-select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.owner-level-select:disabled {
  opacity: 0.55;
  cursor: progress;
}

/* ``btn.small`` is used inside the user-management list. Keep the
   button compact so it doesn't dominate the row. */
.btn.small {
  padding: 6px 12px;
  font-size: 12px;
  min-width: 78px;
}

/* ================================================================ privacy
 * Hide-balance mode. Toggled via the eye button in the app header.
 *
 * Design notes (mirrors Revolut / Monzo / Coinbase):
 *   - We blur the element instead of replacing text so the real value
 *     stays in the DOM for screen readers and copy-paste. Blur alone
 *     is enough to defeat shoulder-surfing at normal viewing distance.
 *   - The blur applies to a curated set of class names that we know
 *     only hold money / PnL / percentages. Things like coin names,
 *     quantities in coin units, market prices, bot statuses and
 *     timestamps stay visible -- matching the common app pattern.
 *   - ``.sensitive`` is a catch-all class for special cases (wallet
 *     section totals, transfer amounts) where the built-in blanket
 *     doesn't reach. Add it anywhere sensitive is rendered.
 *   - Tap-to-reveal: the JS side adds ``.revealed`` to an element
 *     for ~1.8s when tapped, temporarily dropping its blur. Stops the
 *     user from having to disarm privacy mode for a quick peek.
 */
body[data-privacy="on"] .balance-value,
body[data-privacy="on"] .pnl-value,
body[data-privacy="on"] .paper-dashboard-body .value,
body[data-privacy="on"] .pct,
body[data-privacy="on"] .wallet-section-total,
body[data-privacy="on"] .wallet-row .value,
body[data-privacy="on"] .kv .v.positive,
body[data-privacy="on"] .kv .v.negative,
body[data-privacy="on"] .sensitive {
  filter: blur(10px);
  transition: filter 0.22s ease;
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
}

/* Tap-to-reveal: .revealed clears the blur. JS removes the class
   after a short window so the hide-again is automatic. */
body[data-privacy="on"] .balance-value.revealed,
body[data-privacy="on"] .pnl-value.revealed,
body[data-privacy="on"] .paper-dashboard-body .value.revealed,
body[data-privacy="on"] .pct.revealed,
body[data-privacy="on"] .wallet-section-total.revealed,
body[data-privacy="on"] .wallet-row .value.revealed,
body[data-privacy="on"] .kv .v.positive.revealed,
body[data-privacy="on"] .kv .v.negative.revealed,
body[data-privacy="on"] .sensitive.revealed {
  filter: blur(0);
}

/* Buttons that contain money inside (e.g. Add token "Sell at market")
   shouldn't blur their entire label -- only any ``.sensitive`` children. */
body[data-privacy="on"] .btn .sensitive {
  filter: blur(6px); /* softer on button labels so layout doesn't jump */
}

/* ================================================================ leaderboard
 * Top Gainers. Dashboard teaser card + dedicated /leaderboard page.
 * Visual language: subtle gold accents on the #1 slot, muted-but-
 * confident podium for 2/3, dense ranked list below. Inspired by
 * eToro's "Popular Investors" cards and Bybit's copy-trading board,
 * tuned down for a Mini App form factor.
 */

/* ---- dashboard teaser card ---------------------------------------------- */
.top-gainers-card {
  cursor: pointer;
  transition: transform 120ms ease, box-shadow 120ms ease;
  /* Faint gold rim so the card reads as "trophy / rankings" at a
     glance without dominating the dashboard. */
  border: 1px solid color-mix(in srgb, #f5b301 18%, var(--border));
}
.top-gainers-card:active { transform: scale(0.995); }
.top-gainers-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}
.top-gainers-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  font-size: 15px;
}
.top-gainers-title .trophy { font-size: 18px; }
.top-gainers-chev {
  color: var(--text-muted);
  font-size: 22px;
  line-height: 1;
}
.top-gainers-row {
  display: grid;
  grid-template-columns: 28px 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
}
.top-gainers-row:last-child { border-bottom: none; }
.top-gainers-row .medal { font-size: 18px; text-align: center; }
.top-gainers-row .nickname {
  font-weight: 600;
  font-size: 14px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.top-gainers-row .roi {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 14px;
}
.top-gainers-you {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed var(--border);
  font-size: 13px;
}
.top-gainers-you .roi {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}
.top-gainers-you .top-gainers-cta {
  color: var(--accent);
  font-weight: 600;
}

/* ---- leaderboard page ---------------------------------------------------- */
.leaderboard-head {
  margin: 4px 2px 14px;
}
.lb-month {
  font-size: 20px;
  font-weight: 700;
  margin-bottom: 4px;
}

/* Podium row: 2-1-3 visual order with the #1 raised slightly. */
.podium-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  align-items: end;
  margin-bottom: 14px;
}
.podium-slot {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 14px 10px 12px;
  text-align: center;
  box-shadow: var(--shadow-sm, 0 1px 2px rgba(0,0,0,0.04));
  min-height: 118px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 2px;
  /* entrance flourish, cheap */
  animation: podium-pop 360ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.podium-slot.place-1 {
  min-height: 146px;
  border-color: color-mix(in srgb, #f5b301 55%, var(--border));
  background: linear-gradient(180deg,
    color-mix(in srgb, #f5b301 14%, var(--surface)) 0%,
    var(--surface) 70%);
}
.podium-slot.place-2 {
  border-color: color-mix(in srgb, #b9bec6 55%, var(--border));
}
.podium-slot.place-3 {
  border-color: color-mix(in srgb, #c88247 55%, var(--border));
}
.podium-slot.empty {
  opacity: 0.45;
  border-style: dashed;
}
.podium-slot .medal {
  font-size: 26px;
  margin-bottom: 2px;
}
.podium-slot .podium-name {
  font-weight: 700;
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.podium-slot .podium-roi {
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  font-size: 15px;
}
.podium-slot .podium-best {
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}
@keyframes podium-pop {
  from { transform: translateY(6px); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}

/* ---- ranked list (#4+) --------------------------------------------------- */
.lb-list {
  padding: 4px 12px;
}
.lb-row {
  display: grid;
  grid-template-columns: 44px 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.lb-row:last-child { border-bottom: none; }
.lb-rank {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 13px;
  color: var(--text-muted);
  text-align: center;
}
.lb-row-meta {
  min-width: 0;
}
.lb-name {
  font-weight: 600;
  font-size: 14px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lb-sub {
  font-size: 11px;
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
}
.lb-row-pnl {
  text-align: right;
  min-width: 84px;
}
.lb-row-pnl .roi {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 14px;
}
.lb-row-pnl .lb-usdt {
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  margin-top: 1px;
}
.lb-row-me {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border-radius: 10px;
  padding-left: 6px;
  padding-right: 6px;
}

/* ---- pinned "your rank" card + empty state ------------------------------ */
.lb-me-card {
  margin-top: 14px;
}
.lb-me-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  margin-bottom: 6px;
}
.lb-empty {
  text-align: center;
  padding: 24px 18px;
}
.lb-empty-icon {
  font-size: 36px;
  margin-bottom: 8px;
}
.lb-empty-title {
  font-weight: 700;
  font-size: 15px;
}

/* ---- nickname (profile card) -------------------------------------------- */
.nickname-card .nickname-handle {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  color: var(--accent);
  font-weight: 600;
}
.nickname-card .nickname-err {
  color: var(--danger, #e3412a);
  font-size: 12px;
  min-height: 16px;
  margin: -2px 0 8px;
}


/* ---- simulator dashboard ----------------------------------------------- */
.sim-coins {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.sim-coin-row {
  display: flex;
  gap: 8px;
  align-items: center;
}
.sim-coin-row select {
  flex: 1 1 auto;
  /* ``min-width: 0`` is critical -- without it the select inherits a
     content-based min-width (the longest option label) and refuses
     to shrink, which can push the remove button off the row on
     narrow viewports. */
  min-width: 0;
  width: 100%;
}
.sim-coin-remove {
  /* Override ``.btn { width: 100% }`` so the remove button shrinks to
     its icon instead of eating the row and squeezing the coin picker
     to nothing. */
  width: auto;
  flex: 0 0 auto;
  padding: 10px 14px;
  font-size: 14px;
  line-height: 1;
}
.sim-date-row {
  display: grid;
  /* Single column on narrow phones: a datetime-local input renders
     "YYYY-MM-DD HH:MM" plus chevrons, which doesn't reliably fit two
     side-by-side at 360px. We flip back to a 2-up grid once there's
     room. */
  grid-template-columns: 1fr;
  gap: 12px;
}
@media (min-width: 480px) {
  .sim-date-row {
    grid-template-columns: 1fr 1fr;
  }
}
/* iOS Safari's ``input[type="datetime-local"]`` keeps its native chrome
   at an intrinsic ~80px height regardless of any CSS we throw at it
   (``height``, ``padding``, ``min-height``, ``-webkit-min-logical-height``
   are all ignored as long as ``-webkit-appearance`` is the OS default).
   The only reliable way to get the same dimensions as the Label and
   Seed-Balance text inputs is to strip the native chrome with
   ``-webkit-appearance: none`` -- the picker UI still pops on tap (that
   behaviour is tied to ``type=`` not ``appearance``), but the input
   becomes a regular text-style box that honours author CSS.
   Trade-off: iOS no longer renders the formatted "14 Apr 2026 at 00:00"
   string and instead shows the raw ``YYYY-MM-DD HH:MM`` value, which is
   actually fine for a date+time field aimed at picking a precise UTC
   moment for backtesting. We inherit ``.field input``'s padding,
   border, font-size, etc. so the box matches the surrounding fields
   pixel-for-pixel. */
.sim-datetime-input {
  -webkit-appearance: none;
  appearance: none;
  -webkit-min-logical-height: 0;
  text-align: left;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}
/* Hide the native calendar / clock indicator that iOS still draws even
   after ``appearance: none`` on some versions -- it floats outside the
   text box and breaks the visual consistency. The picker is still
   reachable by tapping anywhere in the field. */
.sim-datetime-input::-webkit-calendar-picker-indicator,
.sim-datetime-input::-webkit-date-and-time-value {
  margin: 0;
}
.sim-datetime-input::-webkit-calendar-picker-indicator {
  opacity: 0.6;
  cursor: pointer;
}
.sim-copy-bot {
  width: 100%;
}
.sim-group {
  margin-top: 12px;
  padding: 10px 12px;
  border: 1px solid var(--border, #e5e7eb);
  border-radius: 10px;
  background: var(--surface-soft, rgba(0, 0, 0, 0.02));
}
.sim-group > summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--text, inherit);
  list-style: none;
  padding: 4px 0;
  user-select: none;
}
.sim-group > summary::-webkit-details-marker { display: none; }
.sim-group > summary::before {
  content: "›";
  display: inline-block;
  margin-right: 8px;
  transform: rotate(0deg);
  transition: transform .15s ease;
  color: var(--muted, #8a8f99);
}
.sim-group[open] > summary::before {
  transform: rotate(90deg);
}
.sim-group .field {
  margin-top: 10px;
}

/* Advanced-filters wrapper. Hosts the 11 less-commonly-used gates
   inside one collapsible so the simulator form isn't 13 nested
   accordions tall on first paint. We dial down the inner ``.sim-group``
   chrome to keep nested accordions readable -- without this every
   nested filter would be inset twice and the column would feel cramped
   on narrow screens. The outer wrapper keeps the standard chrome so
   it reads as the disclosure container. */
.sim-advanced-wrap {
  margin-top: 14px;
}
.sim-advanced-wrap > summary {
  font-weight: 700;
}
.sim-advanced-wrap > .sim-base-group {
  /* Inner accordions look lighter so the visual hierarchy reads as
     "here's a section of related filters", not "two competing
     containers". */
  background: transparent;
  border-color: var(--border-soft, var(--border, #e5e7eb));
  padding-left: 8px;
  padding-right: 8px;
}
.sim-advanced-wrap > .muted {
  /* Pull the descriptor closer to the summary so the disclosure
     reads as a single unit. */
  margin-top: 4px;
}

/* Sampling-mode panel: collapsed-by-default details panel that
   exposes the long "When to use / What it does / Result" copy on
   demand. The headline (emoji + best-for) stays always-visible
   above so the user still sees a useful hint without having to
   tap. */
.sim-sampling-more {
  margin-top: 10px;
  padding: 8px 10px;
  border: 1px solid var(--border-soft, var(--border, #e5e7eb));
  border-radius: 8px;
  background: var(--surface-soft, rgba(0, 0, 0, 0.02));
}
.sim-sampling-more > summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 13px;
  color: var(--muted, #6b7280);
  list-style: none;
  padding: 2px 0;
  user-select: none;
}
.sim-sampling-more > summary::-webkit-details-marker { display: none; }
.sim-sampling-more > summary::before {
  content: "›";
  display: inline-block;
  margin-right: 6px;
  transform: rotate(0deg);
  transition: transform .15s ease;
  color: var(--muted, #8a8f99);
}
.sim-sampling-more[open] > summary::before {
  transform: rotate(90deg);
}

/* Inline checkbox + wrap-friendly label used inside .hint rows.
   Uses block-level flex (not inline-flex) so the label fills the
   field width and the text can wrap on narrow screens instead of
   overflowing the card's border. */
.sim-inline-check {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  flex-wrap: wrap;
  line-height: 1.35;
  cursor: pointer;
}
.sim-inline-check > input[type="checkbox"] {
  width: auto;
  flex: 0 0 auto;
  margin: 2px 0 0 0;
}
.sim-inline-check > span {
  flex: 1 1 auto;
  min-width: 0;
  word-break: break-word;
}

.sim-pill {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  background: var(--surface-soft, #f2f3f5);
  color: var(--text, #222);
  white-space: nowrap;
}
.sim-pill.pending  { background: rgba(245, 158, 11, 0.12); color: #b45309; }
.sim-pill.queued   { background: rgba(124, 58, 237, 0.12); color: #6d28d9; }
.sim-pill.running  { background: rgba(59, 130, 246, 0.12); color: #1d4ed8; }
.sim-pill.success  { background: rgba(34, 197, 94, 0.14); color: #15803d; }
.sim-pill.failed   { background: rgba(239, 68, 68, 0.14); color: #b91c1c; }

.sim-runs {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.sim-run-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border, #e5e7eb);
  border-radius: 10px;
  background: var(--surface, #fff);
  cursor: pointer;
  text-align: left;
  font: inherit;
}
.sim-run-row:active { transform: scale(0.99); }
.sim-run-main { flex: 1 1 auto; min-width: 0; }
.sim-run-title {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sim-run-sub { font-size: 12px; margin-top: 2px; }

/* Side column for the past-runs row: stacks the headline metric
   (realized profit, color-coded) above the status pill so the
   user can scan a list of runs and immediately see "which were
   winners" without having to tap each one. The column is
   right-aligned to read like a results column. */
.sim-run-side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
  flex: 0 0 auto;
}
.sim-run-metric {
  font-weight: 700;
  font-size: 14px;
  white-space: nowrap;
}
.sim-run-metric.positive { color: var(--success, #16a34a); }
.sim-run-metric.negative { color: var(--danger,  #dc2626); }

/* Past-optimizations row: similar headline-metric treatment. The
   "best across all coins" profit goes above the status pill and
   the configs count, so a quick glance answers "did this sweep
   find anything?". */
.sim-opt-list-best {
  font-weight: 700;
  font-size: 14px;
  white-space: nowrap;
  margin-bottom: 2px;
}
.sim-opt-list-best.positive { color: var(--success, #16a34a); }
.sim-opt-list-best.negative { color: var(--danger,  #dc2626); }

/* Collapsible-card primitive for the Simulator landing page. Used by
   "Past runs" and "Past optimizations" so the user can toggle long
   history sections closed when they're focused on the form. The
   ``<summary>`` shows the title + a small count chip; tapping it
   flips a chevron via the ``[open]`` state. We keep the visual
   weight similar to the regular ``.card`` (same padding, same
   border) so the page rhythm doesn't change just because a
   section can collapse. */
.sim-collapsible-card {
  /* The default ``details`` element renders its summary inline; we
     override that so the summary spans the full card width. */
  display: block;
}
.sim-collapsible-card > .sim-collapsible-summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 4px 0;
  font-size: 18px;
  font-weight: 700;
  color: var(--text, inherit);
}
.sim-collapsible-card > .sim-collapsible-summary::-webkit-details-marker {
  display: none;
}
.sim-collapsible-card > .sim-collapsible-summary::after {
  content: "›";
  display: inline-block;
  margin-left: auto;
  font-size: 22px;
  font-weight: 400;
  color: var(--muted, #8a8f99);
  transform: rotate(0deg);
  transition: transform .15s ease;
}
.sim-collapsible-card[open] > .sim-collapsible-summary::after {
  transform: rotate(90deg);
}
.sim-collapsible-card > .sim-collapsible-summary > .sim-collapsible-title {
  flex: 1 1 auto;
  min-width: 0;
}
.sim-collapsible-card > .sim-collapsible-summary > .sim-collapsible-count {
  font-size: 13px;
  font-weight: 500;
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--surface-soft, rgba(0, 0, 0, 0.04));
  color: var(--muted, #6b7280);
}
/* Add a little breathing room between the summary and the body
   content so the title doesn't hug the first row. */
.sim-collapsible-card[open] > .sim-collapsible-summary {
  margin-bottom: 10px;
}

/* Hero P&L block for run-detail per-coin cards. Big colored number
   answers "did this make money?" at a glance, with a small ROI
   subtitle underneath. Sits right below the coin title so the
   first thing the eye lands on is the bottom-line answer, not a
   wall of kv rows. */
.sim-hero {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  margin: 6px 0 12px;
  padding: 10px 12px;
  border-radius: 12px;
  background: var(--surface-soft, rgba(0, 0, 0, 0.03));
  border: 1px solid var(--border-soft, var(--border, #e5e7eb));
}
.sim-hero-amount {
  font-size: 28px;
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.5px;
}
.sim-hero-amount.positive { color: var(--success, #16a34a); }
.sim-hero-amount.negative { color: var(--danger,  #dc2626); }
.sim-hero-meta {
  font-size: 12px;
  font-weight: 500;
}

/* Filter-activity collapsible: groups all the "how often did filter
   X block a trade" metric rows behind one disclosure. Closed by
   default because the typical run lands with zeros across the board
   and they'd just be visual noise. The chevron mirrors the
   ``.sim-group`` accordion style for visual continuity. */
.sim-filter-activity {
  margin-top: 8px;
  padding: 6px 10px;
  border: 1px solid var(--border-soft, var(--border, #e5e7eb));
  border-radius: 10px;
}
.sim-filter-activity > summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 13px;
  color: var(--muted, #6b7280);
  list-style: none;
  padding: 4px 0;
  user-select: none;
}
.sim-filter-activity > summary::-webkit-details-marker { display: none; }
.sim-filter-activity > summary::before {
  content: "›";
  display: inline-block;
  margin-right: 6px;
  transform: rotate(0deg);
  transition: transform .15s ease;
  color: var(--muted, #8a8f99);
}
.sim-filter-activity[open] > summary::before {
  transform: rotate(90deg);
}

/* Friendlier empty state for the Past Runs card -- gives first-time
   users a clear "what is this section?" pointer instead of a single
   muted line. Centered, with a soft icon and a one-sentence call
   to action. */
.sim-runs-empty {
  text-align: center;
}
.sim-empty-icon {
  font-size: 36px;
  margin: 8px 0 4px;
  line-height: 1;
}
.sim-empty-title {
  font-weight: 700;
  margin-bottom: 4px;
}
.sim-empty-sub {
  font-size: 13px;
  line-height: 1.5;
  max-width: 320px;
  margin: 0 auto;
}

.sim-run-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}

.sim-progress {
  margin-top: 10px;
  height: 6px;
  background: var(--surface-soft, #eef0f3);
  border-radius: 999px;
  overflow: hidden;
}
.sim-progress-bar {
  height: 100%;
  background: linear-gradient(90deg, #3b82f6, #22c55e);
  transition: width .4s ease;
  animation: sim-shimmer 1.6s linear infinite;
  background-size: 200% 100%;
}
/* Queued state: muted palette so the user can tell the bar is an
   "alive" indicator rather than a real progress bar. */
.sim-progress-bar.queued {
  background: linear-gradient(90deg, #a78bfa, #7c3aed);
  background-size: 200% 100%;
}
@keyframes sim-shimmer {
  0% { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}

.sim-section-title {
  margin-top: 14px;
  margin-bottom: 6px;
  font-weight: 600;
  font-size: 13px;
  color: var(--muted, #555);
}
.sim-months {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.sim-month-row {
  display: flex;
  justify-content: space-between;
  padding: 4px 0;
  font-size: 13px;
}
.sim-month-row .k { color: var(--muted, #555); }

/* ── Info hint (ⓘ button next to a label, with an inline panel) ────
   Used on the Simulator Summary so users can tap the icon to get a
   plain-English definition of a metric that's otherwise easy to
   misread (e.g. "Net profit" -> "Realized net profit" -> inline
   explanation). The panel lives on its own row of the parent ``.kv``
   grid so opening it doesn't displace the value column. */
.sim-info-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.sim-info-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--border, #d5d9df);
  background: var(--surface-soft, #f3f4f6);
  color: var(--muted, #555);
  font-family: Georgia, "Times New Roman", serif;
  font-size: 12px;
  font-style: italic;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  /* Touch-friendly: expands the hit area without changing layout so
     the button is easy to tap on mobile where the visual dot stays
     small. */
  position: relative;
}
.sim-info-btn::before {
  content: "";
  position: absolute;
  inset: -8px;
}
.sim-info-btn:hover,
.sim-info-btn:focus-visible {
  background: var(--surface, #fff);
  color: var(--text, #222);
  border-color: var(--muted, #9ca3af);
  outline: none;
}
/* Panel renders as a full-width block directly under its ``.kv`` row.
   Since ``.card`` is block-level the panel naturally spans the card
   width; we just pull it up slightly against the row's bottom
   divider so the two feel visually coupled. */
.sim-info-panel {
  margin: 6px 0 10px;
  padding: 10px 12px;
  background: rgba(59, 130, 246, 0.06);
  border-left: 3px solid rgba(59, 130, 246, 0.55);
  border-radius: 6px;
  color: var(--text-soft, #555);
  font-size: 12px;
  line-height: 1.45;
}
.sim-info-panel[hidden] { display: none; }

/* Bot settings: compact hint row + expandable filter explainer */
.settings-filter-explain-stack {
  margin: 2px 0 6px;
}
.settings-filter-hint-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
}
.settings-filter-hint-text {
  flex: 1;
  min-width: 0;
}
.settings-filter-explain.settings-filter-explain {
  margin-top: 4px;
}

/* Per-coin card title gets its own rule so the inline info button
   lines up with the heading baseline instead of stacking below it.
   Using flex (instead of inline-flex) lets the heading keep its
   block-level margin behaviour, which matches every other ``h2``
   on the simulator run view. */
.sim-coin-title {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.sim-coin-title .sim-info-btn {
  /* Nudged up to sit on the cap-height of the heading, which reads
     better than the default baseline-centred position. */
  align-self: center;
}

/* Dashboard profit/emulation label rows: make the label+info-button
   sit inline with a small gap, then let the hint panel flow naturally
   below the big PNL/Simulated value. Kept as separate rules (instead
   of reusing ``.sim-info-label``) so the label typography -- which
   differs between ``.pnl-label`` (muted, 12px) and
   ``.paper-dashboard-body .label`` (muted, 12px) -- stays exactly
   as it was before the ⓘ was added. */
.pnl-label-row,
.paper-label-row {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* Portfolio card title pairs a big 22px heading with the round info
   button; baseline alignment (set on ``.portfolio-card-title`` above)
   drops the button below the text, so we override to centre-align
   just the button without changing how the count pill sits. */
.portfolio-card-title .sim-info-btn {
  align-self: center;
}
/* Explanation panel on the Portfolio card spans the full card width
   under the header. Remove the default ``.sim-info-panel`` top margin
   so it sits flush against the header row's existing bottom margin. */
.portfolio-roi-panel {
  margin-top: 0;
  margin-bottom: 14px;
}

.sim-trades {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.sim-trade-row {
  display: grid;
  /* First column fits "YYYY-MM-DD HH:MM" at 11px without wrapping;
     ``minmax`` keeps it flexible enough that very narrow phones can
     still squeeze the price/profit columns rather than overflow. */
  grid-template-columns: minmax(96px, 110px) 1fr auto;
  gap: 10px;
  align-items: center;
  font-size: 12px;
  padding: 4px 0;
  border-bottom: 1px solid var(--border, #eef0f3);
}
.sim-trade-row:last-child { border-bottom: 0; }

/* Monthly-target harvester audit list. Each event is a <details>
   with a 3-col summary mirroring .sim-trade-row; expanded body
   reuses .sim-trade-row for the per-position lines. */
.sim-harvest-list { margin-top: 4px; }
.sim-harvest-ev {
  border: 1px solid var(--border, #eef0f3);
  border-radius: 10px;
  padding: 6px 10px;
  margin-bottom: 6px;
}
.sim-harvest-ev[open] {
  background: color-mix(in srgb, var(--warning, #e0a83b) 7%, transparent);
}
.sim-harvest-head {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 10px;
  align-items: center;
  cursor: pointer;
  list-style: none;
  font-size: 13px;
}
.sim-harvest-head::-webkit-details-marker { display: none; }
.sim-harvest-ev .sim-trade-row { font-size: 11px; }

/* Header row for the trades table: "Trades   3 of 87" with the
   counter aligned right in muted text so the user can tell at a
   glance whether they're looking at a subset. */
.sim-trades-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
}
/* "Show more / Show all / Show less" controls sit below the table.
   Buttons are inline so two fit side-by-side on wide screens and
   wrap gracefully on narrow ones. */
.sim-trades-more {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}
.sim-trades-toggle {
  width: auto;          /* override .btn width:100% */
  flex: 0 1 auto;
  padding: 6px 12px;
  font-size: 12px;
}

/* ============================================================
   Per-coin "Trade history" card on the bot dashboard.
   Visually a sibling of the open-orders/remove action row --
   sits right under it so open + closed orders share a locale.
   The row layout deliberately mirrors .sim-trade-row's 3-col
   grid (date | prices | profit) so the simulator and live bot
   share a familiar shape, but uses its own classes so the two
   surfaces can diverge (clickability, manual-trigger tag, qty
   subline) without cross-impact.
   ============================================================ */
/* ============================================================
   Strategy card -- collapsible <details> on the coin page. The
   summary row mimics a regular .card h2 (same size + weight) but
   adds a rotating chevron on the right so the toggle affordance
   is obvious. Default-collapsed; open state is remembered in
   ``_botStrategyExpanded`` so the live-poll repaint doesn't
   close it on the user.
   ============================================================ */
.strategy-collapse {
  /* details is a block element; reuse .card padding/radius from
   * the base .card rule. Override list-style markers so the
   * native triangle disappears on every browser. */
}
.strategy-collapse > summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
}
.strategy-collapse > summary::-webkit-details-marker { display: none; }
.strategy-collapse > summary::marker { content: ""; }
.strategy-summary-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0;
  margin: 0;
}
.strategy-summary-row h2 { margin: 0; }
.strategy-summary-chev {
  font-size: 14px;
  color: var(--text-soft);
  transition: transform 200ms ease;
  transform: rotate(-90deg);
}
.strategy-collapse[open] > summary .strategy-summary-chev {
  transform: rotate(0);
}
/* Body padding -- when collapsed, .card's bottom padding still
 * applies so the summary doesn't look cramped. When open, the
 * children flow naturally below. */
.strategy-collapse[open] > summary {
  margin-bottom: 12px;
}

/* ============================================================
   Open position card -- live "what does the bot currently hold"
   snapshot at the top of the coin page. Mirrors the walkthrough
   design: a green "Bot running" pill (or muted "Bot paused")
   and a 2x2 grid of Entry / Now / P&L / Change. Background is
   tinted with the success color so the card visually reads as
   "live + healthy" without competing with the yellow accent
   used elsewhere on the page.
   ============================================================ */
/* Default tint = the running-state green. ``.is-running`` is set by
 * renderOpenPositionCard so the rule still wins when both classes
 * collide. Paused state overrides below with an amber tint. */
.open-position-card {
  background:
    linear-gradient(135deg,
      rgba(14, 203, 129, 0.10),
      rgba(14, 203, 129, 0.02)) ,
    var(--surface);
  border: 1px solid rgba(14, 203, 129, 0.22);
}
.open-position-card.is-paused {
  background:
    linear-gradient(135deg,
      rgba(246, 70, 93, 0.10),
      rgba(246, 70, 93, 0.02)) ,
    var(--surface);
  border-color: rgba(246, 70, 93, 0.28);
}
.open-pos-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
}
.open-pos-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 700;
  border-radius: 999px;
  letter-spacing: 0.2px;
}
.open-pos-pill.is-online {
  background: rgba(14, 203, 129, 0.18);
  color: var(--success);
}
.open-pos-pill.is-paused {
  background: rgba(246, 70, 93, 0.18);
  color: var(--danger);
}
.open-pos-pill-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}
.open-pos-pill.is-online .open-pos-pill-dot {
  /* The pulse signals "live" without being noisy -- 1.6s, low
   * amplitude. Reduced-motion users get a static dot. */
  animation: openPosDotPulse 1.6s ease-in-out infinite;
}
@keyframes openPosDotPulse {
  0%, 100% { opacity: 0.5; transform: scale(1); }
  50%      { opacity: 1;   transform: scale(1.3); }
}
@media (prefers-reduced-motion: reduce) {
  .open-pos-pill.is-online .open-pos-pill-dot { animation: none; opacity: 1; }
}
.open-pos-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.open-pos-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.open-pos-label {
  font-size: 11px;
  color: var(--text-muted);
  font-weight: 600;
  letter-spacing: 0.2px;
}
.open-pos-value {
  font-size: 16px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.open-pos-value.positive { color: var(--success); }
.open-pos-value.negative { color: var(--danger); }
.open-pos-sub {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.2px;
  margin-top: 1px;
}
.open-pos-empty {
  font-size: 12px;
  padding: 4px 0 2px;
}

/* ============================================================
   Recent activity card -- compact always-visible feed of the
   bot's last few BUY / SELL events. Sits above the (collapsed)
   Trade history card on the coin page so users always see the
   bot is doing something without expanding anything. The row
   layout is denser than .bot-trades-row -- one line per event
   with an inline pill -- because we only show ~6 entries.
   ============================================================ */
.recent-activity-card h2 { margin: 0 0 10px; }
.recent-activity-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.activity-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  background: var(--surface-2);
  border-radius: 10px;
}
.activity-pill {
  flex: 0 0 auto;
  padding: 3px 8px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.4px;
}
.activity-buy {
  background: rgba(14, 203, 129, 0.18);
  color: var(--success);
}
.activity-sell {
  background: rgba(240, 185, 11, 0.18);
  color: var(--accent);
}
.activity-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.activity-price {
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.activity-meta {
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.activity-pnl {
  flex: 0 0 auto;
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.activity-pnl.positive { color: var(--success); }
.activity-pnl.negative { color: var(--danger); }
.recent-activity-loading,
.recent-activity-empty,
.recent-activity-error {
  font-size: 12px;
  padding: 8px 4px;
}

/* ============================================================
   Coin-page action hierarchy ----------------------------------
   Three visual tiers:
     1. Hero primary (Start / Stop)  -- full-width, dominant.
     2. Action tiles (Settings, Orders) -- equal ghost weight.
     3. Danger link (Remove)          -- small, separated.
   This mirrors the pattern used by Coinbase, Robinhood, and
   most production trading apps: one obvious "do it now" CTA,
   safe config tiles below, irreversible action quarantined.
   ============================================================ */
.coin-action-hero {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 16px 18px;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 0.4px;
  border-radius: 14px;
  margin: 12px 0 10px;
  text-transform: uppercase;
}
.coin-action-hero .icon {
  flex-shrink: 0;
}

.coin-action-tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-bottom: 10px;
}
.coin-action-tile {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 14px;
  font-size: 13px;
  font-weight: 700;
  border-radius: 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  transition: background 160ms ease, border-color 160ms ease, transform 140ms ease;
}
.coin-action-tile:hover {
  background: var(--surface-hi);
  border-color: rgba(255, 255, 255, 0.08);
}
.coin-action-tile:active {
  transform: scale(0.98);
}

.coin-action-remove {
  display: flex;
  justify-content: center;
  margin: 18px 0 6px;
}
/* Solid red danger button -- universal destructive pattern. We keep
 * it visibly smaller than the hero Stop button (auto width + slightly
 * smaller padding) so it can't be confused for the primary action,
 * but the red fill + white text leaves zero ambiguity that tapping
 * it removes the bot. Border-radius is intentionally rounded-corner
 * (not pill) so it reads as a button rather than a navigation chip. */
.coin-action-remove-btn {
  background: linear-gradient(135deg, var(--danger), #ff7088);
  border: 0;
  color: #fff;
  font-size: 13px;
  font-weight: 800;
  padding: 11px 22px;
  border-radius: 12px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: 0 6px 14px rgba(246, 70, 93, 0.28),
              inset 0 1px 0 rgba(255, 255, 255, 0.18);
  transition: filter 140ms ease, transform 140ms ease, box-shadow 200ms ease;
}
.coin-action-remove-btn:hover {
  filter: brightness(1.05);
  box-shadow: 0 8px 18px rgba(246, 70, 93, 0.34),
              inset 0 1px 0 rgba(255, 255, 255, 0.22);
}
.coin-action-remove-btn:active {
  transform: translateY(1px) scale(0.985);
  box-shadow: 0 2px 6px rgba(246, 70, 93, 0.22),
              inset 0 1px 0 rgba(255, 255, 255, 0.10);
}

/* ============================================================
   Inline SVG icon helper -- aligns SVGs with adjacent text so
   they read as a single line. Card headings that mix an icon
   and a label use ``.h2-with-icon`` to space them properly.
   The status-chip variant replaces the legacy 🟢 / 🔴 emoji
   in the coin-detail header with a pulsing colored dot + text
   (no emoji), matching the other "live" indicators.
   ============================================================ */
.icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: -0.18em;
  line-height: 1;
}
.icon svg { display: block; }
.h2-with-icon {
  display: flex;
  align-items: center;
  gap: 8px;
}
.h2-with-icon .icon { vertical-align: baseline; }

.status-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 8px 2px 7px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.2px;
  vertical-align: -1px;
}
.status-chip.is-online {
  background: rgba(14, 203, 129, 0.16);
  color: var(--success);
}
.status-chip.is-offline {
  background: rgba(246, 70, 93, 0.16);
  color: var(--danger);
}
.status-chip .status-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}
.status-chip.is-online .status-dot {
  animation: statusDotPulse 1.6s ease-in-out infinite;
}
@keyframes statusDotPulse {
  0%, 100% { opacity: 0.55; transform: scale(1); }
  50%      { opacity: 1;    transform: scale(1.3); }
}
@media (prefers-reduced-motion: reduce) {
  .status-chip.is-online .status-dot { animation: none; opacity: 1; }
}

/* Owner / Admin pills inherit the role-card color but pick up
 * .icon vertical alignment from the rule above. The pills are
 * inline-flex so the gem / shield SVG sits cleanly next to
 * "Owner" / "Admin" text. */
.owner-pill, .admin-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.bot-trades-card .bot-trades-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 10px;
}
.bot-trades-count {
  font-size: 11px;
  font-weight: 400;
}
.bot-trades-toggle {
  width: 100%;
  font-size: 13px;
}
.bot-trades-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 10px;
}
.bot-trades-body.hidden,
.bot-trades-more.hidden {
  display: none;
}
.bot-trades-empty,
.bot-trades-loading {
  font-size: 13px;
  padding: 8px 0;
}
.bot-trades-error {
  color: #f47171;
  font-size: 12px;
  margin-bottom: 4px;
}
.bot-trade-row {
  display: grid;
  /* First col holds the *stacked* date / time (YYYY-MM-DD over HH:MM)
     so the column can shrink to the width of just the date string
     instead of the full one-line "YYYY-MM-DD HH:MM"; this frees
     ~40px for the prices column on narrow phones. Price column
     flexes -- the ``minmax(0, 1fr)`` is critical: a bare ``1fr``
     defaults to ``minmax(auto, 1fr)`` where ``auto`` = min-content,
     so a nowrap price like ``0.014420`` would refuse to shrink and
     overflow into the profit column. Profit hugs right. */
  grid-template-columns: minmax(80px, 96px) minmax(0, 1fr) auto;
  gap: 10px;
  /* Top-align the row so the profit number renders on the *first*
     line of the price stack rather than getting vertically centered
     against a now-three-line price column (date+time stack is two
     lines too) and visually colliding with the wrapped second
     price. */
  align-items: start;
  font-size: 12px;
  padding: 8px 4px;
  border-bottom: 1px solid var(--border, #eef0f3);
  border-radius: 6px;
}
.bot-trade-row:last-child { border-bottom: 0; }
.bot-trade-row.clickable {
  cursor: pointer;
  transition: background-color 0.12s ease;
}
.bot-trade-row.clickable:hover,
.bot-trade-row.clickable:focus-visible {
  background: rgba(255, 255, 255, 0.04);
  outline: none;
}
.bot-trade-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.bot-trade-time {
  font-size: 11px;
  white-space: nowrap;
}
/* Time-of-day sits on its own line directly under the date so the
   column stays narrow. Slightly dimmer than the date line so the
   eye reads "date, then time" not "two equal labels". */
.bot-trade-time-hm {
  font-size: 11px;
  opacity: 0.78;
}
.bot-trade-tag {
  display: inline-block;
  align-self: flex-start;
  font-size: 10px;
  padding: 1px 6px;
  border-radius: 8px;
  background: rgba(247, 178, 50, 0.18);
  color: #f7b232;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.bot-trade-prices {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
/* Buy / sell prices used to live in a single nowrap+ellipsis span,
   which on narrow phones (microcap pairs like 0.010518 -> 0.010570
   alongside a long qty) collapsed everything into "0.01...". The
   pair is now a flex container with column-gap and wrap enabled, so
   when both prices fit on one line they stay there, otherwise the
   second one drops to its own line. Each individual price keeps
   ``nowrap`` so the number itself is never broken mid-digit. */
.bot-trade-pricepair {
  display: flex;
  flex-wrap: wrap;
  column-gap: 4px;
  row-gap: 2px;
}
.bot-trade-price {
  white-space: nowrap;
}
.bot-trade-qty {
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bot-trade-profit {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  font-weight: 600;
}
.bot-trades-more {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}
.bot-trades-more-btn {
  width: auto;
  flex: 0 1 auto;
  padding: 6px 12px;
  font-size: 12px;
}
.bot-trades-end {
  font-size: 11px;
  text-align: center;
  width: 100%;
  padding: 4px 0 0;
}

@media (max-width: 360px) {
  /* Cramped phone layout: drop the qty subline so the price pair
     still fits without ellipsing aggressively. */
  .bot-trade-row {
    grid-template-columns: minmax(96px, 116px) 1fr auto;
    gap: 8px;
  }
  .bot-trade-qty { display: none; }
}

/* Past-runs card header: "Past runs" on the left, a small "Clear all"
   button on the right. We keep h2 flush with the normal card
   heading style so the row visually matches the other cards on the
   page. */
.sim-past-runs-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}
.sim-delete-all,
.sim-compare-toggle {
  width: auto;          /* override .btn width:100% */
  flex: 0 0 auto;
  padding: 6px 12px;
  font-size: 12px;
}
.sim-compare-toggle.active {
  background: rgba(59, 130, 246, 0.12);
  color: #1d4ed8;
  border-color: rgba(59, 130, 246, 0.35);
}

/* Compare mode: selection row. We re-use .sim-run-row's base and
   add a left gutter for the checkbox + a "selected" highlight. */
.sim-run-row-select {
  padding-left: 10px;
  gap: 8px;
}
.sim-run-row-select.selected {
  border-color: rgba(34, 197, 94, 0.55);
  background: rgba(34, 197, 94, 0.06);
}
.sim-run-row-select.disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.sim-run-select-box {
  flex: 0 0 20px;
  width: 20px;
  height: 20px;
  border-radius: 4px;
  border: 1.5px solid var(--border, #d1d5db);
  background: var(--surface, #fff);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 13px;
  color: #16a34a;
}
.sim-run-row-select.selected .sim-run-select-box {
  border-color: #16a34a;
  background: #16a34a;
  color: #fff;
}
.sim-run-hint {
  font-size: 11px;
  margin-top: 3px;
}

/* Action bar docked under the Past Runs header while in compare
   mode. Keeps the "Pick 2" hint and the Compare→ button visually
   grouped so the user always knows what step they're on. */
.sim-compare-actionbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  margin: 2px 0 10px;
  border-radius: 10px;
  background: var(--surface-soft, #f4f6f9);
  border: 1px dashed var(--border, #d1d5db);
}
.sim-compare-actionbar .btn { width: auto; padding: 6px 14px; }
.sim-compare-hint { font-size: 12px; flex: 1 1 auto; }

/* --- Compare view (side-by-side A vs B) ------------------------ */
.sim-compare-header .sim-compare-labels {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 10px;
  margin: 4px 0 10px;
  padding: 10px;
  border-radius: 10px;
  background: var(--surface-soft, #f4f6f9);
}
.sim-compare-col-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  min-width: 0;
}
.sim-compare-col-label > div:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sim-compare-col-label.b { justify-content: flex-end; text-align: right; }
.sim-compare-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px; height: 22px;
  border-radius: 999px;
  background: rgba(59, 130, 246, 0.12);
  color: #1d4ed8;
  font-weight: 700;
  font-size: 12px;
  flex: 0 0 auto;
}
.sim-compare-chip.b {
  background: rgba(217, 70, 239, 0.12);
  color: #a21caf;
}
.sim-compare-vs { font-size: 12px; }

/* Row layout: label | A value | B value | delta. Uses CSS grid so
   the columns stay aligned across the summary + per-coin cards. */
.sim-cmp-row,
.sim-cmp-header-row {
  display: grid;
  grid-template-columns: 1.5fr 1fr 1fr 0.9fr;
  align-items: center;
  gap: 8px;
  padding: 7px 0;
  border-bottom: 1px solid var(--border-soft, #f1f3f6);
  font-size: 13px;
}
.sim-cmp-header-row { font-weight: 600; font-size: 11px; }
.sim-cmp-row:last-child { border-bottom: none; }
.sim-cmp-label { color: var(--text-muted, #6b7280); }
.sim-cmp-a, .sim-cmp-b { font-variant-numeric: tabular-nums; }
.sim-cmp-b { color: var(--text, #1f2937); }
.sim-cmp-d {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: 12px;
}
.sim-cmp-delta.positive { color: #16a34a; font-weight: 600; }
.sim-cmp-delta.negative { color: #dc2626; font-weight: 600; }
.sim-cmp-delta.diff     { color: #6b7280; font-weight: 700; }
.sim-cmp-changed { font-weight: 600; }

.sim-cmp-coin-heading {
  font-size: 13px;
  margin: 12px 0 4px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.sim-cmp-tag {
  font-size: 10px;
  font-weight: 600;
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(245, 158, 11, 0.15);
  color: #b45309;
  text-transform: uppercase;
}
.sim-cmp-equal-note { font-size: 12px; padding: 4px 0 8px; }

@media (max-width: 420px) {
  /* Tight screens: drop the "Δ" column since the A/B numbers on
     their own already reveal direction. Keeps every row readable
     without horizontal scroll on a phone. */
  .sim-cmp-row,
  .sim-cmp-header-row {
    grid-template-columns: 1.3fr 1fr 1fr;
  }
  .sim-cmp-d { display: none; }
}

/* --- Simulator: "Find best config" mode ------------------------- */

/* Mode switcher at the top of the form. Two pill-shaped tabs share
   the row; the active tab gets a soft accent fill so it doesn't fight
   with the form's other call-to-action buttons. */
/* The track previously used ``var(--surface-soft, #f4f6f9)`` -- but
   ``--surface-soft`` isn't defined in either theme, so the white-ish
   fallback was rendered unconditionally and the segmented control read
   as a permanent white pill in dark mode. Switching to the theme-aware
   ``--surface-2`` makes the track flip with the rest of the UI. We use
   ``--surface-hi`` for the active pill so it stays visually distinct
   from the track in *both* themes (slightly lighter than track in dark
   mode, slightly darker than track in light mode). */
.sim-mode-tabs {
  display: flex;
  gap: 6px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 4px;
  margin-bottom: 4px;
}
.sim-mode-tab {
  flex: 1 1 0;
  border: none;
  background: transparent;
  padding: 8px 12px;
  font-weight: 600;
  font-size: 13px;
  color: var(--text-muted);
  border-radius: 9px;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.sim-mode-tab:hover { color: var(--text); }
.sim-mode-tab.active {
  background: var(--surface-hi);
  color: var(--text);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}

/* Sweep editor card. Shown only in optimize mode; hidden by an inline
   ``display: none`` from JS when the user is on "Single run". */
/* The fallback values used to be hardcoded near-whites
   (``#fafbfc`` / ``#f9fafb`` / ``#fff``) keyed off CSS variables that
   don't actually exist in either theme (``--surface-soft`` /
   ``--muted``). Result: dark mode rendered the search-space card on
   a permanent white background. We now use the theme-aware tokens
   that ARE defined (``--surface``, ``--surface-2``, ``--surface-hi``,
   ``--text-soft``) so the card flips with the rest of the UI. */
.sim-sweep-wrap {
  border: 1px dashed var(--border);
  border-radius: 12px;
  padding: 12px;
  background: var(--surface-2);
  margin: 4px 0;
  color: var(--text);
}
.sim-sweep-title {
  margin: 0 0 6px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
}
.sim-sweep-intro {
  font-size: 12px;
  line-height: 1.45;
  margin-bottom: 10px;
}
.sim-sweep-sections {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 10px;
}
.sim-sweep-group {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  padding: 0;
  overflow: hidden;
}
.sim-sweep-group > summary {
  padding: 10px 12px;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  list-style: none;
  background: var(--surface-hi);
  color: var(--text);
  border-bottom: 1px solid transparent;
  user-select: none;
}
.sim-sweep-group[open] > summary {
  border-bottom-color: var(--border);
}
.sim-sweep-group > summary::-webkit-details-marker { display: none; }
.sim-sweep-group > summary::before {
  content: "▸";
  display: inline-block;
  margin-right: 6px;
  transition: transform 120ms ease-in-out;
  font-size: 11px;
  color: var(--text-soft);
}
.sim-sweep-group[open] > summary::before {
  transform: rotate(90deg);
}
.sim-sweep-group > .sim-sweep-row {
  margin: 8px 12px;
}
.sim-sweep-group > .sim-sweep-row:last-child {
  margin-bottom: 12px;
}
.sim-sweep-row {
  display: grid;
  grid-template-columns: 180px 1fr;
  grid-template-rows: auto auto;
  column-gap: 10px;
  row-gap: 2px;
  align-items: center;
}
.sim-sweep-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  user-select: none;
  grid-column: 1 / 2;
  grid-row: 1 / 2;
  flex-wrap: wrap;
}
.sim-sweep-type-tag {
  display: inline-block;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-soft);
  background: rgba(127, 135, 152, 0.16);
  padding: 1px 6px;
  border-radius: 4px;
}
.sim-sweep-toggle input[type="checkbox"] {
  width: 16px;
  height: 16px;
  cursor: pointer;
}
.sim-sweep-knob { color: var(--text, #111827); }
.sim-sweep-values {
  width: 100%;
  padding: 7px 10px;
  border: 1px solid var(--border, #d1d5db);
  border-radius: 8px;
  background: var(--surface, #fff);
  font-size: 13px;
  grid-column: 2 / 3;
  grid-row: 1 / 2;
}
.sim-sweep-hint {
  grid-column: 2 / 3;
  grid-row: 2 / 3;
  font-size: 11px;
  line-height: 1.3;
}
.sim-sweep-summary {
  margin-top: 6px;
  padding-top: 8px;
  border-top: 1px solid var(--border, #e5e7eb);
}
.sim-sweep-badge {
  display: inline-block;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  background: rgba(59, 130, 246, 0.12);
  color: #1d4ed8;
}
.sim-sweep-badge.warn {
  background: rgba(245, 158, 11, 0.14);
  color: #b45309;
}
.sim-sweep-badge.danger {
  background: rgba(239, 68, 68, 0.15);
  color: #b91c1c;
}

@media (max-width: 480px) {
  .sim-sweep-row {
    grid-template-columns: 1fr;
  }
  .sim-sweep-toggle,
  .sim-sweep-values,
  .sim-sweep-hint {
    grid-column: 1 / 2;
  }
  .sim-sweep-toggle  { grid-row: 1; }
  .sim-sweep-values  { grid-row: 2; }
  .sim-sweep-hint    { grid-row: 3; }
}

/* Past optimizations sidebar list (mirrors past-runs styling) */
.sim-opt-list { display: flex; flex-direction: column; gap: 8px; }
.sim-opt-list-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border, #e5e7eb);
  border-radius: 10px;
  background: var(--surface, #fff);
  cursor: pointer;
  transition: border-color 120ms ease, transform 120ms ease;
}
.sim-opt-list-row:hover {
  border-color: rgba(59, 130, 246, 0.45);
  transform: translateY(-1px);
}
.sim-opt-list-main { flex: 1 1 auto; min-width: 0; }
.sim-opt-list-title {
  font-weight: 600;
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sim-opt-list-meta { font-size: 11px; line-height: 1.35; }
.sim-opt-list-side { text-align: right; flex: 0 0 auto; }

/* Optimization detail: leaderboard card */
.sim-opt-card { display: flex; flex-direction: column; gap: 10px; }
.sim-opt-controls {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.sim-opt-metric-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted, #6b7280);
}
.sim-opt-metric-select {
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font-size: 13px;
}

/* Coin tabs above the leaderboard. Horizontal scroll on overflow so
   a 10-coin sweep doesn't break the layout. */
.sim-opt-coin-tabs {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding-bottom: 4px;
  margin: 0 -4px;
}
.sim-opt-coin-tab {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  border-radius: 999px;
  padding: 5px 12px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.sim-opt-coin-tab.active {
  background: color-mix(in srgb, var(--accent) 14%, var(--surface));
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  color: var(--accent);
}

/* Headline winner block */
.sim-opt-summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 0 6px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
}
.sim-opt-winner-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted, #6b7280);
}
.sim-opt-winner-value {
  font-size: 20px;
  font-weight: 700;
  color: var(--success);
}

/* Leaderboard table. Grid layout so the columns align without
   touching <table>; rows are clickable to expand a details panel
   below them. */
.sim-opt-table-host {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.sim-opt-row-group {
  display: flex;
  flex-direction: column;
}
.sim-opt-row {
  display: grid;
  grid-template-columns: 38px 110px 1fr 90px 28px;
  align-items: center;
  gap: 8px;
  padding: 8px 6px;
  border-radius: 8px;
  font-size: 13px;
  color: var(--text);
  border-bottom: 1px solid var(--border);
}
.sim-opt-row.best {
  background: rgba(34, 197, 94, 0.08);
  border-color: rgba(34, 197, 94, 0.18);
}
.sim-opt-row-head {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  color: var(--text-muted);
  letter-spacing: 0.04em;
  border-bottom: 1px solid var(--border);
}
.sim-opt-row-clickable {
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.sim-opt-row-clickable:hover,
.sim-opt-row-clickable:focus-visible {
  background: color-mix(in srgb, var(--accent) 8%,
                        var(--surface));
}
.sim-opt-row-clickable:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent) 60%, transparent);
  outline-offset: -2px;
}
.sim-opt-row-clickable.expanded {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  border-bottom-color: transparent;
}
.sim-opt-row-clickable.expanded.best {
  background: rgba(34, 197, 94, 0.14);
}
.sim-opt-cell.rank   { font-weight: 700; color: var(--text-muted); }
.sim-opt-cell.metric { font-weight: 700; color: var(--text); }
.sim-opt-cell.knobs  { font-size: 12px; line-height: 1.35;
                       overflow-wrap: anywhere; color: var(--text-soft); }
.sim-opt-cell.trades { font-size: 11px; }

/* Chip-based config column. Each chip wraps as a unit so the row
   stays scannable on narrow screens even with multiple swept knobs.
   Colors are deliberately calm (subtle background + muted label,
   prominent value) so the chips don't compete with the metric cell
   for attention. */
/* Sweeping-knobs chip list on the optimization-detail header card.
 * The previous ``"a, b, c"`` join produced an unreadable wall of
 * comma-separated labels that wrapped mid-name on narrow viewports.
 * Chips wrap naturally and stay scannable. The .kv layout switches
 * to column when ``.kv-sweep`` so the chips can claim full width. */
.kv.kv-sweep {
  display: block;
}
.kv.kv-sweep .k {
  display: block;
  margin-bottom: 6px;
  color: var(--text-soft);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
}
.kv.kv-sweep .sim-sweep-count {
  text-transform: none;
  letter-spacing: 0;
  color: var(--text-muted);
  font-weight: 500;
}
.sim-sweep-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}
.sim-sweep-chip {
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
  letter-spacing: 0.1px;
}

.sim-opt-knob-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
}
.sim-opt-knob-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 3px;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--surface-soft, rgba(0, 0, 0, 0.04));
  border: 1px solid var(--border-soft, var(--border, #e5e7eb));
  font-size: 11px;
  white-space: nowrap;
}
.sim-opt-knob-chip .k {
  color: var(--muted, #6b7280);
  font-weight: 500;
}
.sim-opt-knob-chip .v {
  color: var(--text, inherit);
  font-weight: 700;
}

/* "Show more" button bar below the leaderboard. Centered, subtle --
   the table itself is the story; these buttons exist only when the
   user wants to peek deeper. */
.sim-opt-overflow-meta {
  font-size: 11px;
  text-align: center;
  margin-top: 8px;
}
/* One-line hint above the leaderboard rows -- reinforces the chevron
 * affordance for the rows beyond the auto-expanded winner. Sits
 * between the column header and the first row, so it reads as part
 * of the table chrome, not a banner. */
.sim-opt-tap-hint {
  font-size: 11px;
  text-align: center;
  padding: 6px 0 4px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 4px;
}
.sim-opt-more-bar {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 8px;
  flex-wrap: wrap;
}
.sim-opt-more-btn {
  padding: 6px 14px;
  font-size: 12px;
}
.sim-opt-cell.chevron {
  text-align: center;
  color: var(--text-muted, #6b7280);
  font-size: 14px;
  line-height: 1;
  transition: transform 0.15s ease;
}
.sim-opt-row-clickable[aria-expanded="true"] .sim-opt-cell.chevron {
  transform: rotate(180deg);
}

/* Expandable details panel revealed under a row. Reuses the same
   .kv / .sim-section-title primitives as the Past Runs coin card so
   the tone matches; the config block uses a small tile grid so all
   five strategy sections fit on a phone without horizontal scroll. */
.sim-opt-details {
  border: 1px solid var(--border);
  border-top: 0;
  border-radius: 0 0 8px 8px;
  background: var(--surface);
  color: var(--text);
  margin-bottom: 6px;
}
.sim-opt-details[hidden] { display: none; }
.sim-opt-details-inner {
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.sim-opt-details-section { display: block; color: var(--text); }
.sim-opt-details-section .sim-section-title {
  margin-top: 0;
  margin-bottom: 8px;
}
.sim-opt-cfg-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
/* "Enabled / disabled" status text in the per-gate config tile.
 * Coloured so the user can scan the expanded panel and immediately
 * see which gates were active for the trial -- the "yes / no" text
 * version didn't pop visually. */
.sim-opt-cfg-status {
  font-weight: 700;
  letter-spacing: 0.2px;
}
.sim-opt-cfg-status.is-on  { color: var(--success); }
.sim-opt-cfg-status.is-off { color: var(--text-soft); }
/* Disabled gates collapse to just the enable marker. We dim the
 * whole tile so the panel reads as "these aren't part of this
 * trial's strategy" without making the user squint at the values. */
.sim-opt-cfg-group.dim {
  opacity: 0.62;
}
/* When the user hasn't expanded the disabled-gates section, hide
 * every dim tile so the grid reads at a glance as just the active
 * config. Toggle button below the grid flips this class. */
.sim-opt-cfg-grid.collapse-disabled .sim-opt-cfg-group.dim {
  display: none;
}
/* Toggle button styling -- centred under the grid, low visual
 * weight so it doesn't compete with the action buttons (Run as
 * full simulation / Apply to bot). */
.sim-opt-cfg-toggle {
  display: block;
  width: max-content;
  margin: 10px auto 0;
  font-size: 12px;
  padding: 6px 14px;
}
.sim-opt-cfg-group {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
  background: var(--surface-2);
  color: var(--text);
}
.sim-opt-cfg-group-title {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-soft);
  margin-bottom: 6px;
}
.sim-opt-cfg-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  font-size: 12px;
  padding: 2px 0;
}
.sim-opt-cfg-row .k { color: var(--text-muted); }
.sim-opt-cfg-row .v {
  font-weight: 600;
  text-align: right;
  color: var(--text);
  overflow-wrap: anywhere;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
/* "swept" badge stamped onto config rows whose value came from the
   optimizer's sweep (rather than the user's base config). Helps the
   user understand why a row's value differs from what they typed in
   the form -- e.g. "Vortex mode: confirm  [swept]" makes it obvious
   the optimizer chose that value from the sweep set, NOT that the
   simulator silently overrode the user's setting. The pill is small
   and uses the brand accent so it reads as informational, not a
   warning. ``title`` on the pill (set in JS) lists the full sweep
   values for a tap-to-reveal hint on mobile. */
.sim-opt-cfg-pill {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 1px 6px;
  border-radius: 999px;
  white-space: nowrap;
  line-height: 1.4;
}
.sim-opt-cfg-pill.swept {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent);
}

/* Compact hint mounted at the top of the "Find best config" sweep
   editor. Explains in one line where un-swept knob values come from
   ("the Single run tab"), with an inline button that jumps straight
   to that tab. The button reads as a text link so it doesn't compete
   visually with the search-space card below. */
.sim-sweep-fixed-hint {
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-soft);
  margin: 0 0 10px;
  padding: 8px 10px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--accent) 6%, var(--surface-2));
  border: 1px solid color-mix(in srgb, var(--accent) 18%, var(--border));
}
.sim-sweep-fixed-hint-link {
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: var(--accent);
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.sim-sweep-fixed-hint-link:hover,
.sim-sweep-fixed-hint-link:focus {
  text-decoration: none;
  outline: none;
}
.sim-opt-details-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.sim-opt-action-btn {
  flex: 1 1 auto;
  min-width: 160px;
}
.sim-opt-deploy-header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding-top: 8px;
  border-top: 1px dashed var(--border, #e5e7eb);
  margin-top: 4px;
}
.sim-opt-deploy-title {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text, #111827);
}
.sim-opt-deploy-hint {
  font-size: 11px;
  line-height: 1.35;
  text-align: right;
}

@media (max-width: 600px) {
  /* Phones: collapse the trades column so the metric + knobs columns
     can breathe; full numbers still show in the expanded panel. */
  .sim-opt-row,
  .sim-opt-row-head {
    grid-template-columns: 30px 90px 1fr 24px;
  }
  .sim-opt-cell.trades { display: none; }
  .sim-opt-cfg-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ============================================================
 * Legacy CryptoFlex migration UI
 *
 * Two surfaces:
 *   - ``.legacy-migrate-banner``  -- Profile entry-point card.
 *   - ``.leg-mig-*`` / ``.leg-group-*`` / ``.leg-order-*``
 *     -- the dedicated /migration screen.
 *
 * Whole subsystem is intended to be deleted along with the backend
 * once everyone has migrated, so we keep selectors local-prefixed
 * and avoid touching shared utility classes.
 * ============================================================ */

/* Banner ---------------------------------------------------- */
/* The element is a real <button> so iOS Telegram WebView reliably
   dispatches click events (a styled <div role="button"> sometimes
   silently swallows taps on iOS). All the button defaults that
   would clash with the card styling -- min-width, alignment, font
   inheritance, background, border, color -- are reset here so the
   visual is identical to the surrounding ``.card`` look. */
.legacy-migrate-banner {
  display: grid;
  grid-template-columns: 44px 1fr 24px;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  cursor: pointer;
  border: 1px solid rgba(120, 175, 255, 0.30);
  background:
    linear-gradient(180deg,
      rgba(120, 175, 255, 0.13),
      rgba(120, 175, 255, 0.05));
  transition: transform 120ms ease, border-color 120ms ease;
  /* button reset */
  width: 100%;
  text-align: left;
  font: inherit;
  color: inherit;
  -webkit-appearance: none;
          appearance: none;
  -webkit-tap-highlight-color: rgba(120, 175, 255, 0.18);
}
.legacy-migrate-banner:hover,
.legacy-migrate-banner:focus-visible {
  border-color: rgba(120, 175, 255, 0.55);
  outline: none;
}
.legacy-migrate-banner:active {
  transform: scale(0.99);
}
.legacy-migrate-icon {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  background: rgba(120, 175, 255, 0.18);
}
.legacy-migrate-body {
  min-width: 0;
}
.legacy-migrate-title {
  font-weight: 600;
  font-size: 15px;
}
.legacy-migrate-sub {
  font-size: 12.5px;
  margin-top: 2px;
  line-height: 1.4;
}
.legacy-migrate-cta {
  font-size: 22px;
  opacity: 0.55;
  text-align: right;
}

/* Migration screen ----------------------------------------- */
.leg-mig-header h2 {
  margin-bottom: 6px;
}
.leg-mig-totals-card {
  padding: 10px 14px;
}
.leg-mig-totals {
  font-size: 12.5px;
}

.leg-groups {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Per-coin group ------------------------------------------- */
.leg-group-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.leg-group-head {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.leg-group-title {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
.leg-group-coin {
  font-weight: 600;
  font-size: 18px;
}
.leg-group-counts {
  font-size: 12.5px;
}
.leg-group-bot {
  font-size: 12.5px;
  line-height: 1.4;
}
.leg-group-orders {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.leg-group-actions {
  display: flex;
  justify-content: stretch;
  margin-top: 4px;
}
/* The primary action lives at the top of the group card, full-width
   on phone-size screens so the tap target is unmistakable -- much
   easier to find than a small button buried below dozens of rows. */
.leg-group-actions .btn {
  width: 100%;
  padding: 12px 16px;
  font-size: 14px;
  font-weight: 600;
}
/* Disclosure for the per-row detail. Ghost-styled so it's clearly
   secondary to the action above it. ``.hidden`` is the existing
   utility class used elsewhere; we just wire it onto the order list. */
.leg-group-toggle {
  align-self: flex-start;
  font-size: 12.5px;
}

/* Per-order row -------------------------------------------- */
.leg-order-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
  padding: 10px 12px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.025);
  border: 1px solid rgba(255, 255, 255, 0.06);
}
.leg-order-main {
  min-width: 0;
}
.leg-order-top {
  display: flex;
  align-items: baseline;
  gap: 4px;
  flex-wrap: wrap;
  font-size: 14px;
}
.leg-order-qty {
  font-weight: 600;
}
.leg-order-sym {
  font-size: 12.5px;
  margin-right: 2px;
}
.leg-order-sep {
  font-size: 12px;
}
.leg-order-price {
  font-feature-settings: "tnum" 1;
}
.leg-order-sub {
  font-size: 12px;
  margin-top: 3px;
  line-height: 1.4;
  overflow-wrap: anywhere;
}
.leg-order-note {
  font-size: 11.5px;
  margin-top: 3px;
  font-style: italic;
}

/* Status pills --------------------------------------------- */
.leg-pill {
  display: inline-block;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
  letter-spacing: 0.02em;
}
.leg-pill--live {
  color: #6cf3a4;
  background: rgba(108, 243, 164, 0.14);
  border: 1px solid rgba(108, 243, 164, 0.30);
}
.leg-pill--gone {
  color: #ffb05a;
  background: rgba(255, 176, 90, 0.12);
  border: 1px solid rgba(255, 176, 90, 0.28);
}
.leg-pill--done {
  color: #98a2b3;
  background: rgba(152, 162, 179, 0.12);
  border: 1px solid rgba(152, 162, 179, 0.25);
}
.leg-pill--unknown {
  color: #d0d4dc;
  background: rgba(208, 212, 220, 0.10);
  border: 1px solid rgba(208, 212, 220, 0.22);
}

/* Warn-card variant for missing API keys / Binance error --- */
.warn-card {
  border: 1px solid rgba(255, 176, 90, 0.32);
  background:
    linear-gradient(180deg,
      rgba(255, 176, 90, 0.10),
      rgba(255, 176, 90, 0.03));
}

@media (max-width: 600px) {
  /* Phones: collapse the order row so the status pill drops below
     the price line instead of squeezing it. Keeps the price/quantity
     line legible at narrow widths. */
  .leg-order-row {
    grid-template-columns: minmax(0, 1fr);
  }
  .leg-order-row .leg-pill {
    justify-self: flex-start;
  }
}

/* =========================================================================
 * Owner-published "best config" recommendation card.
 *
 * Three surfaces share a few visual primitives -- the bot dashboard's
 * recommendation card, the diff modal, and the owner panel's active
 * list -- so they share class prefixes (``rec-*`` for the user side,
 * ``owner-rec-*`` for the owner panel, ``rec-pub-*`` for the publish
 * modal).
 * ========================================================================= */

/* ---- Bot dashboard recommendation card -------------------------------- */
.rec-card {
  /* Subtle accent glow so the card reads as "owner-curated" without
     looking like a banner. Uses the same brand gradient used by other
     hero accents elsewhere in the app for a consistent feel. */
  border: 1px solid rgba(124, 58, 237, 0.28);
  background:
    linear-gradient(180deg,
      rgba(124, 58, 237, 0.08),
      rgba(124, 58, 237, 0.02));
}
.rec-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  margin-bottom: 4px;
}
.rec-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  font-size: 16px;
  color: var(--text, #e8edf5);
}
.rec-icon { font-size: 18px; }
.rec-symbol {
  font-size: 12px;
  margin-bottom: 12px;
  /* Allow the "· Tested <date> → <date>" suffix to wrap onto a
     second line on narrow screens instead of being truncated; the
     date range is too wide to share one line with the symbol on
     small phones. */
  line-height: 1.4;
  word-break: break-word;
}

/* Expiry pill: amber by default, red when < 24h to draw the eye. */
.rec-expiry {
  font-size: 11px;
  font-weight: 600;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(255, 176, 90, 0.14);
  border: 1px solid rgba(255, 176, 90, 0.32);
  color: #ffb05a;
  white-space: nowrap;
}
.rec-expiry.urgent {
  background: rgba(244, 113, 113, 0.16);
  border-color: rgba(244, 113, 113, 0.36);
  color: #f47171;
}

.rec-metrics {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
  margin: 10px 0 8px;
}
/* Second-tier metrics row (trade activity). Identical layout to the
   headline row but rendered in a slightly subdued tile so the eye
   reads it as supporting detail rather than parallel information. */
.rec-metrics.rec-metrics-sub {
  margin-top: 0;
  margin-bottom: 10px;
}
.rec-metrics.rec-metrics-sub .rec-metric {
  background: rgba(255, 255, 255, 0.02);
}
.rec-metrics.rec-metrics-sub .rec-metric .value {
  font-size: 13px;
}
.rec-metric {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 10px;
  padding: 8px 10px;
  min-width: 0;
  /* Stretch tiles to a uniform height per row by pushing the value
     to the bottom; otherwise tiles whose label wraps onto 2 lines
     would be visibly taller than their single-line neighbours. */
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
.rec-metric .label {
  font-size: 11px;
  color: var(--muted, #98a2b3);
  margin-bottom: 4px;
  /* Allow long labels like "Realized net profit" or
     "Total P&L (if sold now)" to wrap onto a second line instead
     of being truncated. The min-height reserves space for two lines
     so single-line labels in the same row stay vertically aligned
     with their multi-line siblings. */
  line-height: 1.25;
  white-space: normal;
  overflow-wrap: anywhere;
  min-height: calc(11px * 1.25 * 2);
}
.rec-metric .value {
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rec-metric .value.positive { color: #29c46f; }
.rec-metric .value.negative { color: #f47171; }

/* Value-stack -- used by the "Realized net profit" tile to show $
   on top and the derived % beneath it. The wrapper keeps both lines
   together at the bottom of the tile so flex's space-between still
   lifts the label to the top. */
.rec-metric .rec-value-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  min-width: 0;
}
.rec-metric .value-sub {
  font-size: 11px;
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0.85;
}
.rec-metric .value-sub.positive { color: #29c46f; }
.rec-metric .value-sub.negative { color: #f47171; }
/* ``warn`` is reserved for the "Still held" tile when at least one
   position remained open at the end of the test window -- amber, not
   red, because some held positions are normal in trend-following
   strategies; we only want to draw attention. */
.rec-metric .value.warn { color: #ffb05a; }
/* Tiny line under the metric grids: fees + equity context. Stays
   inline-muted so it doesn't compete with the tiles. */
.rec-footnote {
  font-size: 11px;
  margin: -4px 0 12px;
  text-align: right;
  letter-spacing: 0.01em;
}

.rec-note {
  font-size: 13px;
  font-style: italic;
  color: var(--text, #e8edf5);
  background: rgba(255, 255, 255, 0.04);
  border-left: 3px solid rgba(124, 58, 237, 0.5);
  padding: 8px 12px;
  margin: 4px 0 12px;
  border-radius: 0 8px 8px 0;
  line-height: 1.4;
}
.rec-note-quote {
  color: rgba(124, 58, 237, 0.7);
  font-weight: 700;
  font-size: 16px;
  font-style: normal;
}

.rec-section-title {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted, #98a2b3);
  margin: 8px 0 6px;
}
.rec-diff {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 10px;
}
.rec-diff-row {
  display: grid;
  grid-template-columns: minmax(120px, 1fr) auto;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  font-size: 13px;
}
.rec-diff-label {
  color: var(--muted, #98a2b3);
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rec-diff-values {
  display: flex;
  align-items: center;
  gap: 6px;
  font-variant-numeric: tabular-nums;
}
.rec-diff-from {
  color: var(--muted, #98a2b3);
  text-decoration: line-through;
  text-decoration-color: rgba(244, 113, 113, 0.5);
}
.rec-diff-arrow {
  color: var(--muted, #98a2b3);
}
.rec-diff-to {
  color: #29c46f;
  font-weight: 600;
}

.rec-actions-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 4px;
  flex-wrap: wrap;
}
.rec-link-btn {
  background: none;
  border: none;
  color: rgba(124, 58, 237, 0.95);
  font-size: 13px;
  padding: 4px 6px;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.rec-link-btn:hover { opacity: 0.85; }
.rec-apply-btn {
  min-height: 36px;
  font-weight: 600;
}
.rec-full {
  margin-top: 10px;
  padding: 10px;
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 10px;
}
.rec-full.hidden { display: none; }
.rec-full-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.rec-full-group-title {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted, #98a2b3);
  margin-bottom: 4px;
}
.rec-full-row {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  padding: 2px 0;
}
.rec-full-row .k { color: var(--muted, #98a2b3); }
.rec-full-row .v { font-variant-numeric: tabular-nums; }

/* ---- Monthly realized-profit breakdown ------------------------------ */
/* Sits between the metric grids and the diff/note/apply block. The
   toggle button reuses the .rec-link-btn underline-link style for
   consistency with "Show full config". The expanded panel uses a
   row-per-month bar visualization so users can spot streaks of
   profit / loss at a glance without parsing numbers. */
.rec-monthly {
  margin: 6px 0 4px;
}
.rec-monthly-toggle {
  /* Left-align so the toggle reads as a section header, not buried
     in an action cluster. */
  padding-left: 0;
  display: inline-block;
}
.rec-monthly-panel {
  margin-top: 8px;
  padding: 10px 12px;
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 10px;
}
.rec-monthly-panel.hidden { display: none; }
.rec-month-summary,
.rec-month-extremes {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 8px;
  font-size: 12px;
  align-items: baseline;
}
.rec-month-summary { font-weight: 500; }
.rec-month-extremes { margin-top: 2px; opacity: 0.95; }
.rec-month-summary .sep,
.rec-month-extremes .sep { opacity: 0.4; }
.rec-month-summary .positive,
.rec-month-extremes .positive { color: #29c46f; }
.rec-month-summary .negative,
.rec-month-extremes .negative { color: #f47171; }

.rec-month-rows {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 10px;
  /* A 12-month sweep is comfortable; cap height with a scroll if a
     future feature ever produces longer windows. */
  max-height: 280px;
  overflow-y: auto;
}
.rec-month-row {
  display: grid;
  grid-template-columns: 70px 1fr 78px;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.rec-month-label {
  color: var(--muted, #98a2b3);
  white-space: nowrap;
}
.rec-month-bar-wrap {
  height: 10px;
  background: rgba(255, 255, 255, 0.04);
  border-radius: 5px;
  overflow: hidden;
}
.rec-month-bar {
  height: 100%;
  border-radius: 5px;
  transition: width 200ms ease-out;
}
.rec-month-bar-wrap.positive .rec-month-bar { background: #29c46f; }
.rec-month-bar-wrap.negative .rec-month-bar { background: #f47171; }
.rec-month-value {
  text-align: right;
  font-weight: 600;
  white-space: nowrap;
}
.rec-month-value.positive { color: #29c46f; }
.rec-month-value.negative { color: #f47171; }
@media (max-width: 600px) {
  .rec-month-row {
    /* On phones tighten the columns so the bar still has room to
       communicate magnitude. */
    grid-template-columns: 58px 1fr 72px;
    gap: 6px;
  }
}

.rec-disclaimer {
  font-size: 11px;
  margin-top: 10px;
  line-height: 1.5;
  text-align: center;
}

/* ---- Apply modal (diff confirm) -------------------------------------- */
.rec-apply-modal { max-width: 480px; }

/* ---- Publish modal (owner side) -------------------------------------- */
.rec-pub-modal { max-width: 480px; }
.rec-pub-summary {
  font-size: 13px;
  margin-bottom: 14px;
  padding: 8px 10px;
  background: rgba(255, 255, 255, 0.03);
  border-radius: 8px;
  border: 1px solid var(--border, #2a2f3a);
}
.rec-pub-section {
  margin-top: 12px;
}
.rec-pub-section-title {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted, #98a2b3);
  margin-bottom: 6px;
}
.rec-pub-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.rec-pub-chip {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border, #2a2f3a);
  color: var(--text, #e8edf5);
  font-size: 12px;
  padding: 6px 12px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.rec-pub-chip:hover { background: rgba(255, 255, 255, 0.07); }
.rec-pub-chip.active {
  background: rgba(124, 58, 237, 0.18);
  border-color: rgba(124, 58, 237, 0.55);
  color: #fff;
}
.rec-pub-scopes {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.rec-pub-scope {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  cursor: pointer;
}
.rec-pub-modal textarea {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 13px;
  color: var(--text, #e8edf5);
  font-family: inherit;
}

/* ---- Owner panel: active recommendations list ------------------------ */
.owner-recs-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.owner-rec-row {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 10px;
  padding: 10px 12px;
}
.owner-rec-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}
.owner-rec-pair {
  font-weight: 600;
  font-size: 14px;
  color: var(--text, #e8edf5);
}
.owner-rec-expiry {
  font-size: 11px;
  font-weight: 600;
  padding: 3px 8px;
  border-radius: 999px;
  background: rgba(255, 176, 90, 0.14);
  border: 1px solid rgba(255, 176, 90, 0.32);
  color: #ffb05a;
  white-space: nowrap;
}
.owner-rec-expiry.urgent {
  background: rgba(244, 113, 113, 0.16);
  border-color: rgba(244, 113, 113, 0.36);
  color: #f47171;
}
.owner-rec-metrics { font-size: 12px; }
.owner-rec-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 6px;
}

@media (max-width: 600px) {
  /* Phones: stack the metrics 2x2 so the value column doesn't truncate. */
  .rec-metrics {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .rec-full-grid {
    grid-template-columns: minmax(0, 1fr);
  }
  .rec-actions-row {
    flex-direction: column;
    align-items: stretch;
  }
  .rec-apply-btn { width: 100%; }
}

/* ============================================================== appearance
 * 3-way segmented control on the Profile -> Appearance card.
 * Visual: Auto / Light / Dark, evenly distributed. Active button uses
 * the accent gradient so the chosen palette pops; inactive buttons
 * blend into the card surface. Resets the inherited ``.btn`` width so
 * the three sit side-by-side instead of stacking.
 * ========================================================================= */
.theme-pick-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.theme-pick-btn {
  /* Override .btn defaults so the three siblings line up neatly. */
  width: auto;
  margin: 0;
  padding: 10px 8px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.2px;
  transition: background 0.15s ease, border-color 0.15s ease,
              color 0.15s ease, transform 0.08s ease;
}
.theme-pick-btn:active { transform: scale(0.97); }
.theme-pick-btn.active {
  /* Filled accent for the chosen mode. ``--accent-text`` is dark on
     yellow which keeps contrast comfortable in both palettes. */
  background: var(--gradient);
  color: var(--accent-text);
  border-color: transparent;
  box-shadow: 0 4px 14px rgba(240, 185, 11, 0.25);
}


/* ============================================================
   API Keys page — Binance setup help card.
   Renders above the actual key/secret form so first-time users
   read the prerequisites before scrolling to the inputs. The
   visual goal: a single card with three labelled blocks
   (enable / disable / IP) tight enough that everything fits on
   one phone screen without scrolling between sections.
   ============================================================ */
.apikeys-page { display: contents; }
.apikeys-help-intro {
  margin: 0 0 14px;
  font-size: 13px;
  line-height: 1.4;
}
.apikeys-section {
  margin-top: 14px;
}
.apikeys-section:first-of-type { margin-top: 8px; }
.apikeys-section-title {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.4px;
  margin-bottom: 6px;
  color: var(--text);
}
.apikeys-perm-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.apikeys-perm-list li {
  font-size: 13px;
  line-height: 1.35;
  padding: 6px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.02);
}
/* Tint the two checklists differently so a quick glance tells
   the user which permissions are go vs. no-go without having to
   re-read the section headers. */
.apikeys-perm-enable li {
  border-color: rgba(46, 188, 137, 0.35);
  background: rgba(46, 188, 137, 0.08);
}
.apikeys-perm-disable li {
  border-color: rgba(244, 113, 113, 0.30);
  background: rgba(244, 113, 113, 0.06);
}
.apikeys-disable-hint {
  font-size: 11px;
  margin-top: 6px;
  line-height: 1.4;
}
.apikeys-help-text {
  margin: 0 0 8px;
  font-size: 13px;
  line-height: 1.4;
}
/* The IP itself: monospaced + copy button, sized so a 15-char
   IPv4 (``255.255.255.255``) fits without truncating on a 360px
   phone. The button hugs the right side and stays narrow so the
   IP keeps its prominence in the row. */
.apikeys-ip-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  background: rgba(240, 185, 11, 0.08);
  border: 1px solid rgba(240, 185, 11, 0.35);
  border-radius: 10px;
  padding: 10px 12px;
}
.apikeys-ip-value {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: 0.4px;
  flex: 1 1 auto;
  min-width: 0;
  word-break: break-all;
  background: transparent;
  padding: 0;
}
.apikeys-ip-copy {
  flex: 0 0 auto;
  padding: 6px 12px;
  font-size: 12px;
}
.apikeys-ip-fallback {
  font-size: 12px;
  line-height: 1.4;
  padding: 10px 12px;
  border-radius: 10px;
  background: rgba(244, 113, 113, 0.08);
  border: 1px solid rgba(244, 113, 113, 0.30);
}

/* ----------------------------------------------------------------
   Simulator -- "Sampling realism" info panel
   ----------------------------------------------------------------
   Shown directly under the sampling-mode <select> on the simulator
   form. Re-renders in place when the user changes the dropdown;
   replaces the older one-line muted hint with a structured panel
   (Best for / When to use / What it does / Result).

   Tinted with the brand accent so it visually links to the picker
   above without competing with the strategy-knob cards below.
   ---------------------------------------------------------------- */
.sim-sampling-panel {
  margin-top: 10px;
  padding: 12px 14px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 6%, var(--surface-2));
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--border));
  font-size: 13px;
  line-height: 1.5;
  color: var(--text);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.sim-sampling-panel-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  font-size: 14px;
  color: var(--text);
}
.sim-sampling-emoji {
  font-size: 16px;
  line-height: 1;
}
/* "Best for" headline -- a single sentence presented like a tag +
   highlighted answer so the user can decide at a glance whether to
   keep reading. */
.sim-sampling-best {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  padding: 8px 10px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
}
.sim-sampling-best-tag {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--accent);
  flex: 0 0 auto;
}
.sim-sampling-best-text {
  font-size: 13px;
  color: var(--text);
  flex: 1 1 auto;
}
.sim-sampling-section + .sim-sampling-section {
  margin-top: 2px;
}
.sim-sampling-section-title {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-soft);
  margin-bottom: 4px;
}
.sim-sampling-text {
  font-size: 13px;
  color: var(--text);
  line-height: 1.5;
}
.sim-sampling-list {
  margin: 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.sim-sampling-list li {
  font-size: 13px;
  line-height: 1.5;
  color: var(--text);
}

/* ---- owner read-only "view as user" banner ----------------------------
 * Docked at the BOTTOM, directly above the app's bottom nav. A top-fixed
 * bar collided with Telegram's own header / OS status-bar chrome (a
 * ``position:fixed`` element ignores the ``body`` safe-area padding the
 * rest of the app relies on), leaving the Exit button unreachable under
 * Telegram's controls. The bottom strip is always inside the app's own
 * safe area -- Telegram never overlays the bottom -- so Exit is always
 * tappable. High-contrast amber so the owner always knows they're
 * impersonating. */
.tf-imperso-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(var(--nav-h) + env(safe-area-inset-bottom, 0));
  height: 44px;
  z-index: 30;            /* above content + bottom-nav (10), below modals */
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 0 12px;
  background: var(--warning);
  color: #1E2329;
  font-size: 13px;
  font-weight: 600;
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.35);
}
.tf-imperso-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tf-imperso-exit {
  flex: 0 0 auto;
  background: rgba(0, 0, 0, 0.20);
  color: #1E2329;
  border: 0;
  border-radius: 8px;
  padding: 8px 18px;
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
}
.tf-imperso-exit:active { background: rgba(0, 0, 0, 0.32); }
/* Keep the last cards clear of the docked bar while impersonating. */
body.tf-impersonating main#app {
  padding-bottom: calc(
    var(--nav-h) + var(--pad) + env(safe-area-inset-bottom, 0) + 52px
  );
}
