/* ───────────────────────────────────────────────────────────────
   Self-hosted Inter (variable, weights 100–900). Replaces the Google
   Fonts CDN link so the font is served from /static/vendor with the
   long-lived immutable cache (no extra DNS/TLS to fonts.gstatic.com).
   Latin + Cyrillic subsets — the UI is Russian. unicode-range lets the
   browser fetch only the subsets a glyph actually needs.
   ─────────────────────────────────────────────────────────────── */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url(vendor/inter-latin-wght-normal.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url(vendor/inter-latin-ext-wght-normal.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url(vendor/inter-cyrillic-wght-normal.woff2) format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url(vendor/inter-cyrillic-ext-wght-normal.woff2) format("woff2");
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

/* ───────────────────────────────────────────────────────────────
   Mini App theme — dark + orange (mining-app inspired).
   Chart colours (--bg/--grid/--border/--up/--down/--tv) are read by
   app.js (lightweight-charts + canvas thumbs), so keep those var NAMES.
   ─────────────────────────────────────────────────────────────── */
:root {
  /* surfaces */
  --bg: #141414;           /* chart area + base (read by app.js) */
  --bg-top: #1e1e1e;       /* page gradient top */
  --surface: #1c1c1c;      /* cards */
  --surface-2: #242424;    /* chips / lifted controls */
  --grid: #1b1b1b;         /* chart grid — low-contrast so candles carry the eye (read by app.js) */
  --border: #2a2a2a;       /* hairline strokes (read by app.js) */
  --border-soft: #212121;

  /* brand */
  --accent: #ff8a00;       /* orange */
  --accent-2: #ffa224;     /* orange highlight (gradient top) */
  --accent-press: #e67c00;
  --on-accent: #ffffff;

  /* candles (read by app.js) */
  --up: #2bd47e;
  --down: #ff5466;

  /* text */
  --title: #ffffff;
  --text: #e8e8e8;
  --neutral: #d2d2d2;
  --muted: #8a8a8a;        /* axis ticks / labels (read by app.js) */
  --stat-header: #6a6a6a;
  --level: #b0b3c0;

  /* measure tool accent → orange (read by app.js as --tv) */
  --tv: #ff8a00;

  /* One radius scale used across the whole app: panels/cards · inputs/tiles ·
     controls/rows · small tags/tooltips · pills(999). Every border-radius in the
     app resolves to one of these — no off-scale one-offs. */
  --radius: 1rem;
  --radius-sm: 0.75rem;
  --radius-xs: 0.625rem;
  --radius-2xs: 0.5rem;

  /* glass surfaces — translucent + blur, reference-inspired */
  --glass: rgba(38, 38, 38, 0.55);          /* lifted controls (chips/buttons) */
  --glass-strong: rgba(26, 26, 26, 0.72);   /* menus / floating nav */
  --border-glass: rgba(255, 255, 255, 0.07);/* hairline light edge on glass */
  --shadow-soft: 0 10px 30px rgba(0, 0, 0, 0.45);

  /* ── Safe areas — Telegram fullscreen (Bot API 8.0) ──────────────────────────
     In fullscreen the client drops its own header and hands the app the WHOLE
     screen, drawing the ✕/⋮ controls straight over the page. Two independent
     insets have to be cleared and they STACK:
       --tg-safe-area-inset-*          device chrome (status bar, notch, gesture bar)
       --tg-content-safe-area-inset-*  Telegram's floating controls
     Both are published by the client as CSS vars; app.js mirrors the SDK objects
     into the same names so this formula stays the single source of truth on every
     client. Old clients leave them unset → every term falls back to 0px, i.e. the
     pre-fullscreen layout, byte for byte. env() keeps iOS correct when the page is
     opened outside Telegram (browser testing). */
  --sa-top: max(var(--tg-safe-area-inset-top, 0px), env(safe-area-inset-top, 0px));
  --sa-bottom: max(var(--tg-safe-area-inset-bottom, 0px), env(safe-area-inset-bottom, 0px));
  --sa-left: max(var(--tg-safe-area-inset-left, 0px), env(safe-area-inset-left, 0px));
  --sa-right: max(var(--tg-safe-area-inset-right, 0px), env(safe-area-inset-right, 0px));
  /* Floor for the Telegram-controls inset. Telegram DESKTOP reports 0 here yet still
     floats ✕/⋮ over the top-right — exactly where the screener parks its tool row —
     so fullscreen reserves a minimum strip regardless of what the client reports.
     max() means a client that reports a real (larger) inset still wins. */
  --fs-floor: 0px;
  --chrome-top: max(var(--tg-content-safe-area-inset-top, 0px), var(--fs-floor));
  --pad-top: calc(var(--sa-top) + var(--chrome-top));
}

/* Set by app.js from tg.isFullscreen (and kept in sync via fullscreenChanged). */
html[data-fullscreen="1"] { --fs-floor: 2.75rem; }

* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
  /* Kill the mobile long-press callout ("Открыть в браузере / Сохранить
     изображение …") across the whole app — a long press on any image, svg,
     canvas chart or label must never yank the user out into the browser. */
  -webkit-touch-callout: none;
}

/* A Mini App should feel native: no accidental text/image selection or drag on
   a long press. Selection is re-enabled only where the user actually types. */
#app, #app * {
  -webkit-user-select: none;
  user-select: none;
  -webkit-user-drag: none;
}
img, svg, canvas {
  -webkit-user-drag: none;
  user-drag: none;
}
input, textarea, [contenteditable="true"] {
  -webkit-user-select: text;
  user-select: text;
}

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow: hidden;
  /* ── Cross-device proportional scaling ──────────────────────────────────────
     The whole layout is expressed in rem, so this ONE fluid root font-size scales
     every element together as a single unit. It is keyed to viewport HEIGHT: the
     app is a full-height flex column with a greedy flex:1 chart, so on a taller
     device the fixed chrome would otherwise occupy a smaller share while the chart
     ballooned. Growing the root with height keeps the chrome/chart proportions
     constant across phones, tablets and desktop — independent of pixel density or
     resolution (CSS px already normalise DPR; rem removes the remaining aspect
     drift). The 16px FLOOR is the reference base: the Telegram *desktop* webview is
     short (~600px), where 2.3vh would fall below 16 — pinning the min at 16px keeps
     the PC render pixel-identical to the original, and nothing ever renders smaller
     than the reference. Taller phones scale UP toward the 19px cap so the fixed
     chrome grows and the greedy flex chart can't dominate. text-size-adjust pins
     text so Android's display-size/font-scale settings can't inflate the UI
     independently of the layout.
     ⚠️ The live `2.3vh` here is only the FIRST-PAINT value. Android's Telegram WebView
     RESIZES the window when the on-screen keyboard opens, so every viewport unit shrinks
     with it and this clamp would collapse onto its 16px floor mid-typing — the search
     field, the columns and every row redraw ~6% smaller the moment the keyboard appears.
     app.js latches the height-derived size into --root-fs and re-latches only on a REAL
     layout change (rotation, a taller viewport), never on a keyboard shrink. Keep the
     clamp as the fallback: it is what paints before the deferred script runs. */
  font-size: var(--root-fs, clamp(16px, 2.3vh, 19px));
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

#app {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  /* Darker page gradient: --bg-top (== native header/bottom-bar chrome) fades into the
     --bg chart body. The top edge equals the native header colour so header→app is
     seamless; the body stays dark. Telegram's setBackgroundColor is set to the SAME
     --bg-top so the thin seam it paints under the header (and the desktop @bot strip)
     matches this gradient top instead of showing as a darker line. */
  /* Top edge == --bg-top == the native Telegram chrome tone (setBackgroundColor), so the
     header→app join is seamless; it fades into the darker --bg body. This exact match is
     load-bearing — do NOT lighten the 0% stop or a seam appears under the native header. */
  background: linear-gradient(180deg, var(--bg-top) 0%, var(--bg) 38%);
  /* Fullscreen: the app owns the status bar and the strip under Telegram's floating
     ✕/⋮ controls, so it has to inset itself — nothing else reserves that space. The
     gradient still paints across the padding box, which is what keeps the reserved
     strip the --bg-top chrome tone instead of a bare band. Horizontal insets matter
     in landscape fullscreen (notch on the side). Outside fullscreen every term is
     0px and this is a no-op. */
  padding-top: var(--pad-top);
  padding-left: var(--sa-left);
  padding-right: var(--sa-right);
}

.view {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.hidden { display: none !important; }

/* ── Top bar (feed) ──────────────────────────────────────── */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.875rem 1rem 0.75rem;
}

.topbar-title {
  font-size: 1.25rem;
  font-weight: 800;
  color: var(--title);
  letter-spacing: 0.0125rem;
}

/* ── Generic round icon button ───────────────────────────── */
.icon-btn {
  flex: 0 0 auto;
  width: 2.375rem;
  height: 2.375rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.25rem;
  line-height: 1;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, transform 0.08s;
}
.icon-btn:active { background: #303030; transform: scale(0.94); }

/* Back is secondary navigation — a borderless chevron, not a heavy 38px square that
   out-weighs the ticker. Tap target kept at 34px; the glyph is optically pulled toward
   the 16px page gutter since it no longer has a box. */
#back-btn {
  width: 2.125rem;
  height: 2.125rem;
  margin-left: -0.4375rem;
  font-size: 1.625rem;
  padding-bottom: 0.1875rem;
  background: transparent;
  border-color: transparent;
  color: var(--muted);
}
#back-btn:active { background: transparent; color: var(--text); }

/* ── Detail header ───────────────────────────────────────── */
/* Back chevron pinned to the left gutter, the ticker CENTERED in the row (like before).
   Nothing else lives here — the AI-разбор pill floats over the chart and the timeframe
   sits on the snapshot-strip tools row. */
.header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  padding: 0.25rem 1rem 0;
  background: transparent;
}
/* Centered ticker title — plain, no chip frame. Takes the middle column; a spacer the
   same width as the back chevron balances the right so the symbol is TRULY centered.
   Truncates with «…» only if a very long symbol would otherwise collide with the edges. */
.detail-sym {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  overflow: hidden;
}
/* Zero the gutter-pull margin here so the back box is a clean 34px, symmetrical with the
   spacer below → the centred ticker lands on the true page centre. */
.header #back-btn { margin-left: 0; }
/* Invisible right-hand counterweight = back-btn box, so «justify-content:center» inside
   .detail-sym lands the ticker on the page's true centre. */
.header::after {
  content: "";
  flex: 0 0 auto;
  width: 2.125rem;
}
.detail-tools { display: flex; align-items: center; gap: 0.3125rem; flex: 0 0 auto; }

.symbol {
  font-size: 1.1875rem;
  font-weight: 800;
  letter-spacing: 0.0125rem;
  color: var(--title);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Role + outcome + recommendation badges — a tight LEFT-aligned row under the header,
   flat like the screener's chips. Its own horizontal padding now that it sits outside
   the header. */
/* All badges stay on ONE row — never wrap a pill down to a second line. If the set is
   wider than the screen it scrolls sideways (hidden scrollbar) instead of stacking. */
.title-badges {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  /* Badges sit CENTERED as a group — no empty gap stretched across the row. «safe center»
     keeps them centered when they fit and falls back to left-anchored (nothing clipped)
     when the set is wider than the screen, where it scrolls sideways instead. */
  justify-content: safe center;
  gap: 0.3125rem;
  padding: 0.375rem 1rem 0;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.title-badges::-webkit-scrollbar { display: none; }
/* Each pill keeps its natural width and shows its full text — never truncated with «…».
   If the whole set overflows, the row scrolls (above) rather than clipping any label. */
.title-badges .badge {
  flex: 0 0 auto;
  white-space: nowrap;
}

.badge {
  font-size: 0.6875rem;
  font-weight: 600;
  padding: 0.25rem 0.625rem;
  border-radius: 62.4375rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);
  white-space: nowrap;
}
/* Detail header badges: slimmer, lighter pills (less «box») — equal height, a single
   pill radius and a soft hairline so the row reads as one clean, modern set. Kept compact
   so the plate stays small and the two pills sit evenly on one tidy line. */
.title-badges .badge {
  display: inline-flex;
  align-items: center;
  line-height: 1;
  font-size: 0.625rem;
  padding: 0.1875rem 0.4375rem;
  border-color: var(--border-soft);
}
.badge-dir { color: var(--text); }
/* Density outcome badge: green when the wall held (отскок), red on a пробой. */
.badge.up { color: var(--up); }
.badge.down { color: var(--down); }
.badge.skip { color: var(--accent); }
/* «Вход через 0:37» — не исход, а ПРЕДУПРЕЖДЕНИЕ, что цифры на экране ещё поедут. Тот же
   акцентный тон, что у «Лучше не заходить»: оба говорят «подожди», а не «получилось». */
.badge.pending { color: var(--accent); }

/* Badge hierarchy in the detail title row: the role (Уровень сопротивления) is a QUIET
   ghost chip — transparent fill, just a hairline — while the recommendation and the
   resolved outcome carry a soft tinted fill so the eye lands on the conclusion first. */
.title-badges .badge-dir {
  color: var(--neutral);
  background: transparent;
}
.title-badges #rec-call,
.title-badges #density-outcome { font-weight: 700; }
.title-badges #rec-call.up,
.title-badges #density-outcome.up {
  color: var(--up);
  background: rgba(43, 212, 126, 0.12);
  border-color: rgba(43, 212, 126, 0.26);
}
.title-badges #rec-call.down,
.title-badges #density-outcome.down {
  color: var(--down);
  background: rgba(255, 84, 102, 0.12);
  border-color: rgba(255, 84, 102, 0.28);
}
.title-badges #rec-call.skip {
  color: var(--accent);
  background: rgba(255, 138, 0, 0.12);
  border-color: rgba(255, 138, 0, 0.28);
}

/* ── Timeframe selector (segmented pills) ────────────────── */
.tfbar {
  display: flex;
  gap: 0.4375rem;
  margin-top: 0.875rem;
}

/* Detail bar: equal-width buttons filling the row. */
#tfbar .tf-btn {
  flex: 1 1 0;
  min-width: 0;
  padding-left: 0;
  padding-right: 0;
  text-align: center;
}

.tf-btn {
  flex: 0 0 auto;
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 600;
  padding: 0.4375rem 0.8125rem;
  border-radius: 62.4375rem;
  background: var(--glass);
  border: 1px solid var(--border-glass);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--muted);
  cursor: pointer;
  transition: background 0.14s, color 0.14s, border-color 0.14s;
}
.tf-btn:hover { color: var(--text); }

.tf-btn.active {
  color: var(--on-accent);
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
  border-color: transparent;
  box-shadow: 0 3px 12px rgba(255, 138, 0, 0.28);
}

/* ── Timeframe dropdown (compact selector) ───────────────── */
.tf-dd { position: relative; flex: 0 0 auto; }

.tf-dd-btn {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  font-family: inherit;
  font-size: 0.8125rem;
  font-weight: 700;
  padding: 0.4375rem 0.8125rem;
  border-radius: 62.4375rem;
  background: var(--glass);
  border: 1px solid var(--border-glass);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--title);
  cursor: pointer;
  transition: border-color 0.14s;
}
.tf-dd-btn .tf-dd-caret {
  font-size: 0.5625rem;
  color: var(--accent);
  transition: transform 0.16s;
}
.tf-dd.open .tf-dd-btn { border-color: var(--accent); }
.tf-dd.open .tf-dd-caret { transform: rotate(180deg); }

.tf-dd-menu {
  display: none; /* opened by toggling .tf-dd.open (see below) */
  position: absolute;
  right: 0;
  top: calc(100% + 0.4375rem);
  z-index: 30;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.375rem;
  padding: 0.5625rem;
  min-width: 10.75rem;
  background: var(--glass-strong);
  border: 1px solid var(--border-glass);
  border-radius: var(--radius);
  box-shadow: var(--shadow-soft);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}
.tf-dd.open .tf-dd-menu { display: grid; }

.tf-dd-item {
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 600;
  padding: 0.5625rem 0;
  border-radius: var(--radius-xs);
  background: var(--glass);
  border: 1px solid var(--border-glass);
  color: var(--text);
  cursor: pointer;
  text-align: center;
}
.tf-dd-item.active {
  color: var(--on-accent);
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
  border-color: transparent;
}

/* «Статистика» period picker — a dedicated calendar-aware panel, not the flat TF grid.
   Flat + solid per the design system (no glass): quick presets that show their real dates,
   then a past-month grid. The panel animates open with opacity/translate (display can't
   transition), gated on .open; visibility keeps it out of the tab order when closed. */
#stats-range .stats-picker {
  display: block;
  grid-template-columns: none;
  right: 0;
  left: auto;
  width: min(21rem, calc(100vw - 1.5rem));
  min-width: 0;
  max-height: 74vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 0.875rem;
  gap: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  visibility: hidden;
  opacity: 0;
  transform: translateY(-0.375rem);
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
}
#stats-range.tf-dd.open .stats-picker {
  visibility: visible;
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
  #stats-range .stats-picker {
    transition: opacity 0.12s ease;
    transform: none;
  }
}

.stats-pick-eyebrow {
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--stat-header);
  margin-bottom: 0.5rem;
}
.stats-pick-eyebrow ~ .stats-pick-eyebrow,
.stats-pick-presets + .stats-pick-eyebrow {
  margin-top: 0.875rem;
}

.stats-pick-presets {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.5rem;
}
.stats-pick-tile {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.1875rem;
  padding: 0.625rem 0.6875rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  cursor: pointer;
  text-align: left;
  transition: border-color 0.14s, background 0.14s;
}
.stats-pick-name {
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--title);
}
.stats-pick-range {
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--muted);
}
.stats-pick-tile.active {
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
  border-color: transparent;
  box-shadow: 0 3px 12px rgba(255, 138, 0, 0.28);
}
.stats-pick-tile.active .stats-pick-name { color: var(--on-accent); }
.stats-pick-tile.active .stats-pick-range { color: rgba(255, 255, 255, 0.85); }

.stats-pick-year {
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--muted);
  margin: 0.625rem 0 0.375rem;
}
.stats-pick-months {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.375rem;
}
.stats-pick-mchip {
  padding: 0.4375rem 0;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  cursor: pointer;
  text-align: center;
  transition: border-color 0.14s, background 0.14s;
}
.stats-pick-mchip.active {
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
  border-color: transparent;
  color: var(--on-accent);
  box-shadow: 0 3px 12px rgba(255, 138, 0, 0.28);
}

.stats-pick-hint {
  margin-top: 0.875rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border);
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--stat-header);
  line-height: 1.35;
}

@media (hover: hover) {
  .stats-pick-tile:not(.active):hover,
  .stats-pick-mchip:not(.active):hover { border-color: var(--accent); }
}

/* ── Stats / alert-moment snapshot ───────────────────────── */
/* Detail «Данные на момент алерта» — a slim strip flush above the chart, connected by
   a bottom hairline exactly like the screener's market-stats row. A quiet eyebrow
   caption sits on the left, the ruler + center controls on the right; the metric row
   below spans the full width (reusing the global .sstat typography). No card box. */
/* Structurally mirrors the screener head+stats: the caption/tools row is the "header"
   (hairline below it), and the metric row sits between two hairlines exactly like the
   screener's .screener-head → .screener-stats → chart stack. No outer box. */
.snap-strip {
  flex: 0 0 auto;
}
.snap-strip-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.625rem 1rem;
  border-bottom: 1px solid var(--border-soft);
}
.snap-strip-cap {
  font-size: 0.5625rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.0375rem;
  color: var(--stat-header);
  white-space: nowrap;
  /* The tools group on the right is fixed-width; let the caption shrink/ellipsize on
     narrow screens instead of pushing the tools off the row. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.snap-tools { display: flex; align-items: center; gap: 0.3125rem; flex: 0 0 auto; }
/* TF pill on the snapshot strip: compact + solid so it sits level with the ruler square,
   matching the screener's flat timeframe pill. */
.snap-tools .tf-dd-btn {
  font-size: 0.75rem;
  padding: 0.375rem 0.6875rem;
  background: var(--surface-2);
  border-color: var(--border-soft);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.snap-tools .tf-dd.open .tf-dd-btn { border-color: var(--accent); }

/* The metric row is styled identically to the screener's .screener-stats: same padding,
   same bottom hairline (so it meets the chart exactly like the screener), 4 readouts
   spread evenly, tabular digits, horizontal-scroll fallback with no visible scrollbar. */
.stats {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 0.375rem;
  padding: 0.5rem 1rem;
  border-bottom: 1px solid var(--border-soft);
  font-variant-numeric: tabular-nums;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.stats::-webkit-scrollbar { display: none; }

/* Feed-card snapshot — a quiet eyebrow caption + the SAME flat .sstat metric row used
   on the screener and the detail strip (no box/frame). */
.snap-cap {
  font-size: 0.5625rem;
  text-transform: uppercase;
  letter-spacing: 0.05rem;
  color: var(--stat-header);
  margin-bottom: 0.5625rem;
}
/* The metric row itself is the SAME .sstat readout the screener header uses (shared
   markup), so the plate looks identical everywhere; here it's spread evenly inside the
   snapshot card. Reuses the global .sstat / .sstat-l / .sstat-v typography. */
.snap-stats {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.375rem;
  font-variant-numeric: tabular-nums;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.snap-stats::-webkit-scrollbar { display: none; }

/* ── Chart ───────────────────────────────────────────────── */
/* ⚠️ `touch-action` MUST sit on the base rule, never on a gesture-time class. The browser
   LATCHES it when the finger lands, so a `none` applied inside pointerdown (as .editing
   used to) arrives too late for the gesture already in flight: the scroll/fling detector
   is still arbitrating, the drag crosses the touch slop, the browser claims the touch and
   fires `pointercancel` — the line moves ~16px and detaches. Measured on a 390×740 touch
   stand, dragging a shape with no pause after touchdown: latched `auto` cancelled 5/5
   runs (4 of 25 moves delivered), preset `none` cancelled 0/5 (25 of 25). It only bit
   touch — a mouse has no gesture arbitration, so the PC was always smooth — and only the
   drag, because LWC pans from its own touch listeners, which the browser never cancels.
   Holding the finger still first «fixed» it by letting the detector settle: that is the
   tell for this bug, not a workaround to keep. .measuring/.drawing arm their class BEFORE
   the gesture and so were never broken; they no longer need their own declaration. */
.chart { flex: 1; min-height: 0; position: relative; touch-action: none; }
.chart.editing { cursor: grabbing; }
.chart a { display: none !important; }

/* The ruler + center controls in .snap-tools reuse the screener's .tool-btn / .tool-ico
   styling verbatim, so they look identical to the screener's header tools. */

/* ── «ИИ-анализ»: floating pill + bottom sheet ───────────── */
/* «AI-разбор» — a small accent pill floating over the chart's top-right corner (like a
   charting app's tools button). Compact so it barely covers any candles; the solid accent
   fill alone carries it as the primary action — flat, no shadow (see Elevation in DESIGN.md). */
.ai-btn {
  position: absolute;
  top: 0.625rem;
  right: 0.625rem;
  z-index: 20;
  height: 1.625rem;
  padding: 0 0.625rem 0 0.5rem;
  border-radius: 62.4375rem;
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  color: var(--on-accent);
  background: var(--accent);
  border: 1px solid transparent;
  touch-action: manipulation;
}
.ai-btn:active { background: var(--accent-press); }
.ai-btn-ico { width: 0.8125rem; height: 0.8125rem; stroke-width: 1.9; }

/* Every glyph in the «ИИ-анализ» surface comes from the sprite (#ico-*) in ONE stroke
   language — the emoji they replaced (✨ 🎯 💰 🚫) rendered differently on every platform
   and read as chat, not instrumentation. This is the shared stroke setup for all of them. */
.ai-btn svg,
.ai-panel svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  flex: 0 0 auto;
}

.ai-panel {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 8;
  /* Tall enough that the whole card (Рекомендация…Сумма входа) fits without an
     inner scroll on desktop, where the viewport runs a bit shorter than a phone. */
  max-height: 94%;
  display: flex;
  flex-direction: column;
  /* Flat solid sheet, one tone up from the chart base (see Elevation in DESIGN.md):
     the top hairline carries the separation — no shadow, no blur, no translucency. */
  background: var(--surface);
  border-top: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
  animation: ai-slide-up 0.18s ease-out;
}
@keyframes ai-slide-up {
  from { transform: translateY(14px); opacity: 0; }
  to { transform: none; opacity: 1; }
}

/* The «ИИ-анализ» title is a quiet eyebrow — the recommendation below is the hero,
   not this header. A hairline separates the header from the content. */
.ai-panel-head {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  padding: 0.75rem 0.75rem 0.625rem 1rem;
  border-bottom: 1px solid var(--border);
}
/* Слот иконки в шапке панели. Держит и статичную звёздочку, и Lottie-искры —
   обе рисуются в одном боксе, поэтому подмена не двигает заголовок.

   ⚠️ Бокс НАМЕРЕННО крупнее рисунка. Холст Lottie 300×300 больше самих искр:
   замерено по объединённому bbox всех 65 кадров — полезны ~70% ширины, мёртвое
   поле слева 16.6% бокса. Отсюда оба числа: бокс 1.5rem даёт искру 16×18 px
   (статичная иконка была 15×15, то есть тот же вес в строке заголовка), а
   отрицательные margin вычитают мёртвое поле с обеих сторон: без левого искра
   вставала на 1.5 px правее поля шапки и колонка получалась рваной, без правого
   зазор до заголовка расходился со штатным gap (11 px против 7.4). */
.ai-panel-ico {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem; height: 1.5rem;
  margin-left: -0.36rem;
  margin-right: -0.21rem;
  color: var(--accent);
}
/* Статичная иконка и мишень Lottie — РАЗНЫЕ узлы, наложенные друг на друга.
   ⚠️ Разводить их обязательно: animation.destroy() чистит innerHTML своего
   контейнера, так что общий узел означал бы, что при закрытии панели вместе с
   искрами стирается и запасная звёздочка. */
.ai-panel-ico-static,
.ai-panel-ico-anim {
  position: absolute;
  display: block;
}
.ai-panel-ico-static { width: 0.875rem; height: 0.875rem; }
.ai-panel-ico-anim { inset: 0; display: none; }
.ai-panel-ico-anim svg { display: block; width: 100%; height: 100%; }
/* Подмена — одним классом, чтобы иконка не пропадала между двумя состояниями. */
.ai-panel-ico.ready .ai-panel-ico-static { display: none; }
.ai-panel-ico.ready .ai-panel-ico-anim { display: block; }
.ai-panel-title {
  flex: 1 1 auto;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.03125rem;
  text-transform: uppercase;
  color: var(--muted);
}
/* Round icon button. The 2rem box is the visible chip; ::after grows the tap target to the
   44px platform minimum without pushing the header taller. */
.ai-close {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem; height: 2rem;
  border-radius: 62.4375rem;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--muted);
  cursor: pointer;
  touch-action: manipulation;
  transition: color 0.14s, border-color 0.14s;
}
.ai-close svg { width: 0.875rem; height: 0.875rem; stroke-width: 2; }
.ai-close::after {
  content: "";
  position: absolute;
  inset: -0.375rem;
  border-radius: inherit;
}
.ai-close:active { color: var(--text); border-color: var(--muted); }

.ai-body {
  overflow-y: auto;
  padding: 0.875rem 1rem 1.125rem;
  -webkit-overflow-scrolling: touch;
}
.ai-block { margin-bottom: 0; }

/* ── Recommendation hero — the visual center of the whole card ──
   A tone-tinted arrow badge + the direction word. No eyebrow above it: the sheet header
   already says «ИИ-анализ», so a second «Рекомендация» label was a caption on a caption. */
.ai-rec {
  display: flex;
  align-items: baseline;
  gap: 0.4375rem;
  margin-top: 0.125rem;
}
.ai-rec-badge {
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.125rem; height: 2.125rem;
  margin-right: 0.1875rem;
  border-radius: 62.4375rem;
  /* The tone wash is the badge's whole definition — flat fill, no ring, no shadow. */
  background: rgba(255, 255, 255, 0.05);
}
.ai-rec-badge svg { width: 1.125rem; height: 1.125rem; stroke-width: 2.1; }
.ai-rec-word { font-size: 1.25rem; font-weight: 800; line-height: 1; letter-spacing: -0.025rem; }
.ai-rec-sub { font-size: 0.8125rem; font-weight: 600; color: var(--muted); }
.ai-rec-up .ai-rec-word, .ai-rec-up .ai-rec-badge { color: var(--up); }
.ai-rec-up .ai-rec-badge { background: rgba(43, 212, 126, 0.14); }
.ai-rec-down .ai-rec-word, .ai-rec-down .ai-rec-badge { color: var(--down); }
.ai-rec-down .ai-rec-badge { background: rgba(255, 84, 102, 0.14); }
.ai-rec-mid .ai-rec-word, .ai-rec-mid .ai-rec-badge { color: var(--accent); }
.ai-rec-mid .ai-rec-badge { background: rgba(255, 138, 0, 0.14); }

/* Reason line ("Пробой вниз — шорт.") sits under the hero direction */
.ai-lead { margin: 0.625rem 0 0; font-size: 0.875rem; line-height: 1.45; font-weight: 600; color: var(--text); }

/* Reliability — an ordinal label, so it gets an ordinal shape: three segments, filled
   1 / 2 / 3 for низкая / средняя / высокая. The word stays, the meter makes it glanceable. */
.ai-reliab {
  display: inline-flex; align-items: center; gap: 0.4375rem;
  margin-top: 0.625rem;
  padding: 0.3125rem 0.5625rem;
  border-radius: 62.4375rem;
  background: var(--surface-2);
  font-size: 0.75rem;
}
.ai-reliab-k { color: var(--muted); font-weight: 600; }
.ai-reliab b { font-weight: 700; }
.ai-reliab-meter { display: inline-flex; gap: 0.125rem; }
.ai-reliab-meter i {
  width: 0.5rem; height: 0.25rem;
  border-radius: 62.4375rem;
  background: rgba(255, 255, 255, 0.14);
}
.ai-reliab-up b { color: var(--up); }
.ai-reliab-up .ai-reliab-meter i.on { background: var(--up); }
.ai-reliab-mid b { color: var(--accent); }
.ai-reliab-mid .ai-reliab-meter i.on { background: var(--accent); }
.ai-reliab-down b { color: var(--down); }
.ai-reliab-down .ai-reliab-meter i.on { background: var(--down); }

/* ── The trade plan — ONE plate split by hairlines, not three floating tiles ──
   Вход is the price you act at, so it gets a full-width row; цель/стоп are a
   reward/risk pair below it, and the meter under them draws that pair to scale. */
.ai-plan {
  margin-top: 0.875rem;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border: 1px solid var(--border);
  overflow: hidden;
}
.ai-plan-entry {
  display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem;
  padding: 0.625rem 0.75rem;
  border-bottom: 1px solid var(--border-soft);
}
.ai-plan-k { font-size: 0.75rem; font-weight: 600; color: var(--muted); }
.ai-plan-v { font-size: 1rem; font-weight: 800; letter-spacing: -0.01875rem; color: var(--title); }

.ai-plan-pair { display: flex; }
.ai-tgt {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.25rem;
  padding: 0.625rem 0.75rem 0.6875rem;
}
.ai-plan-pair .ai-tgt + .ai-tgt { border-left: 1px solid var(--border-soft); }
.ai-tgt-k { font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.0125rem; color: var(--muted); }
.ai-tgt-v { font-size: 1rem; font-weight: 800; letter-spacing: -0.01875rem; }
.ai-tgt-p {
  font-size: 0.6875rem; font-weight: 700;
  padding: 0.0625rem 0.375rem;
  border-radius: 62.4375rem;
}
.ai-tgt-up .ai-tgt-v { color: var(--up); }
.ai-tgt-up .ai-tgt-p { color: var(--up); background: rgba(43, 212, 126, 0.14); }
.ai-tgt-down .ai-tgt-v { color: var(--down); }
.ai-tgt-down .ai-tgt-p { color: var(--down); background: rgba(255, 84, 102, 0.14); }

/* Risk-to-reward drawn to scale: the red segment is 1/(1+rr) of the bar, the green the
   rest — the ratio the line below states in words, made legible in one glance. */
.ai-rr {
  padding: 0.6875rem 0.75rem 0.75rem;
  border-top: 1px solid var(--border-soft);
}
.ai-rr-bar {
  display: flex;
  gap: 0.125rem;
  height: 0.25rem;
  transform-origin: left center;
  animation: ai-rr-draw 0.42s cubic-bezier(0.16, 1, 0.3, 1) 0.1s both;
}
.ai-rr-bar i { min-width: 0.25rem; border-radius: 62.4375rem; }
.ai-rr-risk { background: var(--down); }
.ai-rr-rew { background: var(--up); }
@keyframes ai-rr-draw {
  from { transform: scaleX(0); }
  to { transform: none; }
}
/* Equal-flex side labels so the ratio lands in the OPTICAL centre of the bar — with
   space-between it drifted to wherever the left word happened to end. */
.ai-rr-legend {
  display: flex; align-items: baseline;
  margin-top: 0.4375rem;
  font-size: 0.6875rem; font-weight: 600; color: var(--muted);
}
.ai-rr-legend span { flex: 1 1 0; min-width: 0; }
.ai-rr-legend span:last-child { text-align: right; }
.ai-rr-legend b { flex: 0 0 auto; font-size: 0.75rem; font-weight: 700; color: var(--text); }

/* ── ПРЕДВАРИТЕЛЬНЫЙ план (импульс, бар после выноса ещё не открылся) ──────────────────
   Вход импульса — это ОТКРЫТИЕ следующей свечи, которого ещё нет; на экране стоит оценка
   по текущей цене, и она доедет. Разметка обязана сказать это ДО того, как цифра поедет:
   пунктирная рамка, приглушённые числа, шапка с обратным отсчётом и строка-пояснение под
   метром R/R.
   ⚠️ Метр R/R в этом состоянии ВИДЕН (31.08). До этого он прятался, и это была не
   осторожность, а источник подмены: спрятанное число не стоит на месте — у импульса цель
   держится в абсолюте, вход переезжает на открытие следующей свечи, и отношение падает с
   ~1:2.5 до ~1:1, о чём человек узнавал только после перезагрузки. Показанная оценка тикает
   и доезжает. Отменяет ⛔ от 23.08; замер, на котором стояло прятанье, никуда не делся —
   именно он и объясняет, почему число обязано быть на виду.
   Все узлы лежат в разметке ВСЕГДА и переключаются классом: пересборка плашки снесла бы
   калькулятор суммы вместе с введённым депозитом. */
.ai-prelim-head,
.ai-plan-hint,
.ai-rr-wait { display: none; }

.ai-plan.is-prelim { border-style: dashed; }
.ai-plan.is-prelim .ai-prelim-head { display: flex; }
/* ⚠️ Именно block. Под flex строка «R/R будет известен на входе — на открытии следующей
   свечи» разваливалась на два флекс-элемента (текст и <b>), и точка после них уезжала на
   отдельную строку — поймано на стенде. */
.ai-plan.is-prelim .ai-rr-wait { display: block; }
.ai-plan.is-prelim .ai-plan-hint { display: inline; }
/* Числа приглушены, но НЕ обесцвечены до нечитаемости: это всё ещё рабочая оценка, по
   которой человек прикидывает сделку, а не выключенный контрол. */
.ai-plan.is-prelim .ai-plan-num,
.ai-plan.is-prelim .ai-rr-legend b,
.ai-plan.is-prelim .ai-tgt-v { opacity: 0.62; }

.ai-prelim-head {
  align-items: center;
  gap: 0.375rem;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px dashed var(--border);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.03rem;
  text-transform: uppercase;
  color: var(--accent);
}
.ai-prelim-dot {
  width: 0.375rem; height: 0.375rem;
  border-radius: 50%;
  background: var(--accent);
  flex: 0 0 auto;
}
.ai-plan-v { display: inline-flex; align-items: baseline; gap: 0.4375rem; min-width: 0; }
/* Пока вход — оценка, число меняется на каждом кадре WS. Без табличных цифр строка
   дёргалась бы по ширине на каждой смене разряда — движение читалось бы как сбой, а не
   как живая цена. Цель и стоп едут вместе с входом, поэтому правило общее. */
.ai-plan-num, .ai-tgt-v { font-variant-numeric: tabular-nums; }
.ai-plan-hint {
  font-size: 0.625rem; font-weight: 600; letter-spacing: 0;
  color: var(--muted);
}
.ai-rr-wait {
  padding: 0.6875rem 0.75rem 0.75rem;
  border-top: 1px solid var(--border-soft);
  font-size: 0.75rem; font-weight: 600; line-height: 1.35;
  color: var(--muted);
}
.ai-rr-wait b { color: var(--text); font-weight: 700; }

/* ── «Сумма входа» — informational block, part of the design system (neutral surface,
   no heavy tinted fill) ── */
.ai-size {
  margin-top: 0.875rem;
  padding: 0.75rem 0.75rem 0.8125rem;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border: 1px solid var(--border);
}
.ai-size-head { display: flex; align-items: center; gap: 0.4375rem; }
.ai-size-ico { width: 0.9375rem; height: 0.9375rem; color: var(--accent); }
.ai-size-title { font-size: 0.875rem; font-weight: 800; color: var(--title); }
.ai-size-lev {
  margin-left: auto;
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--accent);
  background: rgba(255, 138, 0, 0.14);
  padding: 0.125rem 0.5rem;
  border-radius: 62.4375rem;
}
/* «без плеча» is the calm state, but --muted on this lighter wash measured 3.9:1 at 11px —
   below the 4.5 floor. --neutral clears it and still reads quieter than the accent pill. */
.ai-size-lev-off { color: var(--neutral); background: rgba(255, 255, 255, 0.06); }
.ai-size-text {
  margin-top: 0.5rem;
  font-size: 0.8125rem; line-height: 1.5; color: var(--neutral);
}
.ai-size-text b { font-weight: 800; color: var(--title); }
.ai-size-text .ai-accent { color: var(--accent); }
.ai-size-text .ai-neg { color: var(--down); }

/* «Посчитать в деньгах» — a DISCLOSURE row, not a second CTA: the % rule above already
   answers the question, this only converts it to the user's own deposit. Icon · label ·
   chevron, at the 44px touch height. Accent-tinted (never solid orange) so it stays calm
   inside the surface-2 plate instead of shouting over the numbers. */
.ai-calc-toggle {
  margin-top: 0.75rem;
  width: 100%;
  height: 2.75rem;
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0 0.75rem;
  border-radius: var(--radius-xs);
  border: 1px solid rgba(255, 138, 0, 0.28);
  background: rgba(255, 138, 0, 0.1);
  color: var(--accent);
  font-size: 0.8125rem; font-weight: 700;
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
  transition: background 0.14s, border-color 0.14s;
}
.ai-calc-toggle span { flex: 1 1 auto; min-width: 0; }
.ai-calc-toggle:active { background: rgba(255, 138, 0, 0.2); border-color: rgba(255, 138, 0, 0.5); }
.ai-calc-ico { width: 1.0625rem; height: 1.0625rem; }
.ai-calc-chev { width: 1rem; height: 1rem; opacity: 0.7; }

/* Calculator (revealed on tap): deposit input + quick presets + result rows */
.ai-calc { margin-top: 0.625rem; }
.ai-calc-field {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5625rem 0.75rem;
  border-radius: var(--radius-xs);
  background: var(--bg);
  border: 1px solid var(--border);
}
.ai-calc-field:focus-within { border-color: var(--accent); }
.ai-calc-cur { font-size: 0.875rem; font-weight: 700; color: var(--muted); }
.ai-calc-input {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  color: var(--title);
  font-size: 1rem; /* ≥16px keeps iOS from zooming on focus */
  font-weight: 700;
}
.ai-calc-input::placeholder { color: var(--muted); font-weight: 500; font-size: 0.875rem; }
/* Hide the number-input spinners (Chrome/Safari + Firefox). */
.ai-calc-input::-webkit-outer-spin-button,
.ai-calc-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.ai-calc-input { -moz-appearance: textfield; }

/* The deposit presets ARE the system's chip component — surface-2 + hairline at rest, the
   lit accent gradient when selected (see Chips in DESIGN.md), so they read as the same
   control family as the timeframe chips over the chart. */
.ai-calc-presets { display: flex; gap: 0.375rem; margin-top: 0.5rem; }
.ai-calc-preset {
  flex: 1;
  height: 2rem;
  border-radius: var(--radius-2xs);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--neutral);
  font-size: 0.75rem; font-weight: 700;
  cursor: pointer;
  touch-action: manipulation;
  transition: background 0.14s, color 0.14s, border-color 0.14s;
}
.ai-calc-preset:active,
.ai-calc-preset.active {
  border-color: transparent;
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
  color: var(--on-accent);
  box-shadow: 0 3px 12px rgba(255, 138, 0, 0.28);
}

/* Result rows: the 1px gaps show the container's own border colour as hairlines, so the
   readout is one plate with rules in it rather than two bordered boxes. */
.ai-calc-out {
  margin-top: 0.625rem;
  display: flex; flex-direction: column; gap: 1px;
  border-radius: var(--radius-xs);
  overflow: hidden;
  background: var(--border);
  border: 1px solid var(--border);
}
.ai-calc-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem;
  padding: 0.5625rem 0.75rem;
  background: var(--surface);
}
.ai-calc-row-k { font-size: 0.75rem; color: var(--muted); }
.ai-calc-row-v { font-size: 0.875rem; font-weight: 800; letter-spacing: -0.0125rem; color: var(--title); }
.ai-calc-row-v.ai-neg { color: var(--down); }
.ai-calc-row-v.ai-accent { color: var(--accent); }

/* Keyboard focus. :focus-visible never fires on a tap, so this costs the phone nothing and
   gives the Telegram desktop webview a visible ring the app otherwise has nowhere. */
.ai-close:focus-visible,
.ai-calc-toggle:focus-visible,
.ai-calc-preset:focus-visible,
.ai-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.ai-foot { margin-top: 0.875rem; font-size: 0.6875rem; color: var(--muted); line-height: 1.45; }
/* Legal footnote — hairline above it so it reads as a footer, not another content line. */
.ai-disclaimer {
  margin-top: 0.875rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-soft);
  font-size: 0.6875rem; color: var(--muted); line-height: 1.45;
}

/* ── Статистика отработки ────────────────────────────────── */
.stats-page-title { font-size: 1.25rem; font-weight: 700; letter-spacing: 0.0125rem; color: var(--title); }
.stats-section-hd {
  font-size: 0.9375rem;
  font-weight: 700;
  color: var(--title);
  margin: 0.375rem 0.125rem 0.625rem;
}
.stats-page {
  flex: 1;
  overflow-y: auto;
  padding: 0.75rem 1rem 1.5rem;
  -webkit-overflow-scrolling: touch;
}
.stats-summary {
  text-align: center;
  padding: 0.875rem 0.75rem 1.25rem;
  margin-bottom: 0.5rem;
}
.stats-big { font-size: 2.875rem; font-weight: 800; line-height: 1; letter-spacing: -0.02em; }
.stats-sub { margin-top: 0.5rem; font-size: 0.75rem; color: var(--muted); }

.stats-pct-up { color: var(--up); }
.stats-pct-down { color: var(--down); }
.stats-pct-mid { color: var(--muted); }

/* The headline % is the hero by size and weight alone (2.875rem/800). It carries the
   same --up/--down as every other price figure: one green in the app, not two. */

.stats-meta { flex: 0 0 auto; font-size: 0.75rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* ── Headline facts strip ───────────────────────────────────────────────────
   One unified block under the profit %, split into three segments by hairlines
   (winrate · trades · тейк/стоп). Extra top margin gives the profit room to breathe
   and stay the single accent. */
.stats-facts {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-items: center;
  margin-top: 1.5rem;
  padding: 0.75rem 0.5rem;
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border-soft);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.035),
    0 0.375rem 1rem rgba(0, 0, 0, 0.22);
}
.stats-fact {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
  padding: 0.125rem 0.5rem;
}
.stats-fact + .stats-fact { border-left: 1px solid var(--border); }
.stats-fact-v {
  font-size: 1rem;
  font-weight: 800;
  color: var(--title);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.stats-fact-l { font-size: 0.6875rem; color: var(--muted); }
.stats-fact-wl { display: inline-flex; align-items: center; gap: 0.75rem; }
.stats-wl { display: inline-flex; align-items: center; gap: 0.25rem; color: var(--title); }
.stats-wl .stats-ic { width: 0.875rem; height: 0.875rem; }
.stats-wl-tp .stats-ic { color: var(--up); }
.stats-wl-sl .stats-ic { color: var(--down); }

.stats-note {
  text-align: center;
  font-size: 0.8125rem;
  color: var(--muted);
  padding: 1.25rem 0.75rem;
}

/* ── Per-strategy card (minimal, TradingView/Linear-flavoured) ──────────────
   Quiet surface, no border, generous vertical rhythm. Only three accent colours
   in play: green (profit), red (loss), muted grey (secondary); everything else
   is white. Blocks are separated by whitespace, not rules or boxes. */
.stats-row {
  padding: 1rem;
  margin-bottom: 0.5rem;
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border-soft);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.035),
    0 0.375rem 1rem rgba(0, 0, 0, 0.22);
}
.stats-row-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
}
/* Left identity block: rank medal + name + the «N сделок» count all on ONE line
   («Импульсы  33 сделки»), so the header reads as a single tidy leaderboard line
   instead of stacking the count as a subtitle below the name. */
.stats-id { display: flex; align-items: center; min-width: 0; flex: 1 1 auto; }
.stats-name-wrap { display: inline-flex; align-items: center; gap: 0.5rem; min-width: 0; }
.stats-name {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--title);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 0 1 auto;
}
/* Rank: an SVG medal (gold/silver/bronze) for the top 3, a quiet #N below that. */
.stats-medal { display: inline-flex; line-height: 0; }
.stats-medal-ic { width: 1.1875rem; height: 1.1875rem; display: block; }
.stats-medal-1 { color: #f3c04a; }   /* gold   */
.stats-medal-2 { color: #c2c7d0; }   /* silver */
.stats-medal-3 { color: #cd8a54; }   /* bronze */
.stats-rank {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.1875rem;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* The hero figure of the card: net result, coloured by sign. Anchored on the RIGHT of
   the header row (opposite the name) — a leaderboard line, so the number stays big and
   prominent without floating on its own lonely row. */
.stats-net {
  flex: 0 0 auto;
  /* Nudged up so the big figure's cap-height lines up with the strategy name to its left
     (the row is flex-start), reading as one leaderboard line rather than a floating number. */
  margin-top: -0.125rem;
  font-size: 1.75rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

/* Metrics grid spans the full card width, evenly distributed. Tighter top gap now that
   the number sits inline with the name (the card reads as one compact block). */
.stats-metrics {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.625rem;
  /* Faint hairline splits the header from the metrics — a quiet in-card divider «для
     красоты», sitting on the tighter 12px rhythm rather than a big empty gap. */
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-glass);
}
.stats-metric { display: flex; flex-direction: column; gap: 0.1875rem; min-width: 0; }
.stats-metric-val {
  font-size: 0.9375rem;
  font-weight: 700;
  color: var(--title);
  font-variant-numeric: tabular-nums;
}
.stats-metric-lbl {
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}

/* Outcome tally — decode of the winrate: тейк / стоп / без результата. Each item is an
   icon+count on top over a faint label below, the three spread evenly across the card so
   the counts are legible (not the cramped «✔14✖18⏳1») and self-explanatory. */
.stats-tally {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.625rem;
  /* Same faint in-card divider + tighter rhythm, pulling the тейк/стоп tally up snug
     under the metrics instead of floating far below. */
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-glass);
  font-variant-numeric: tabular-nums;
}
.stats-tl {
  display: flex;
  flex-direction: column;
  gap: 0.1875rem;
  min-width: 0;
}
.stats-tl-top {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
}
.stats-tl-n {
  font-size: 0.9375rem;
  font-weight: 700;
  color: var(--title);
}
.stats-tl-lbl {
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.stats-ic { width: 1rem; height: 1rem; display: block; flex: none; }
.stats-tl-tp .stats-ic { color: var(--up); }
.stats-tl-sl .stats-ic { color: var(--down); }
.stats-tl-to .stats-ic { color: var(--muted); }

/* ── Equity curve (рост депозита по советам) ─────────────────────────────── */
.stats-equity-wrap {
  margin: 0 2px 14px;
  padding: 12px 12px 10px;
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border);
}
.eq-plot { position: relative; height: 150px; }
.eq-canvas {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  right: 38px;
  touch-action: pan-y;        /* horizontal drag scrubs the crosshair, vertical still scrolls */
  cursor: crosshair;
}
.stats-equity { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.eq-up { color: var(--up); }
.eq-down { color: var(--down); }
.eq-line {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.eq-area { fill: currentColor; opacity: 0.13; }
.eq-grid { stroke: rgba(255, 255, 255, 0.06); stroke-width: 1; vector-effect: non-scaling-stroke; }
.eq-grid.eq-zero { stroke: var(--muted); stroke-dasharray: 3 4; opacity: 0.55; }

.eq-ylabel {
  position: absolute;
  right: 0;
  width: 36px;
  transform: translateY(-50%);
  padding-left: 5px;
  font-size: 10px;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}
.eq-xaxis { position: relative; margin: 6px 38px 0 0; height: 13px; }
.eq-xlabel {
  position: absolute;
  top: 0;
  font-size: 10px;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}

.eq-cursor {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  margin-left: -0.5px;
  background: rgba(255, 255, 255, 0.32);
  pointer-events: none;
}
.eq-dot {
  position: absolute;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: currentColor;
  border: 2px solid var(--bg);
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.eq-tip {
  position: absolute;
  top: 4px;
  z-index: 3;
  pointer-events: none;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-2xs);
  padding: 5px 8px;
  white-space: nowrap;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
}
.eq-tip-val { font-size: 12px; font-weight: 800; }
.eq-tip-date { margin-top: 1px; font-size: 10px; color: var(--muted); }

.stats-equity-cap {
  margin-top: 10px;
  text-align: center;
  font-size: 12px;
  font-weight: 500;
  color: var(--muted);
  line-height: 1.45;
  padding: 0 6px;
}

/* ── Portfolio metrics grid ──────────────────────────────────────────────── */
.stats-overview {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.5rem;
  margin: 0 0.125rem 1rem;
}
.stats-ov-cell {
  padding: 0.75rem 0.875rem;
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border);
}
.stats-ov-val { font-size: 1.25rem; font-weight: 800; color: var(--title); line-height: 1.1; }
.stats-ov-lbl {
  margin-top: 0.1875rem;
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--muted);
}
.stats-ov-note {
  margin: 0.125rem 0.25rem 1.125rem;
  font-size: 0.75rem;
  color: var(--muted);
  line-height: 1.45;
}

.measure-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 4;
  pointer-events: none;
  overflow: visible;
}
.m-cross {
  stroke: rgba(255, 255, 255, 0.45);
  stroke-width: 1;
  stroke-dasharray: 4 4;
}
.m-rect {
  fill: rgba(255, 138, 0, 0.12);
  stroke: rgba(255, 138, 0, 0.5);
  stroke-width: 1;
}
.m-arrow { stroke: var(--tv); stroke-width: 1.5; }

.measure-label {
  position: absolute;
  z-index: 6;
  pointer-events: none;
  transform: translate(-50%, calc(-100% - 8px));
  text-align: center;
  line-height: 1.32;
  font-size: 0.625rem;
  font-weight: 700;
  padding: 0.1875rem 0.4375rem;
  border-radius: var(--radius-2xs);
  white-space: nowrap;
  color: var(--on-accent);
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
  box-shadow: 0 3px 10px rgba(255, 138, 0, 0.3);
}
.measure-label.below { transform: translate(-50%, 8px); }
.measure-label .ml-sub {
  font-weight: 600;
  color: rgba(255, 255, 255, 0.9);
  font-size: 0.5625rem;
}

/* ── Overlays / spinner ──────────────────────────────────── */
.overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.875rem;
  /* Loading/error screen fills the chart area with the same dark chart tone (--bg) the
     canvas uses, so it flows into the page gradient. Its top connects to the native
     header via the gradient (no black seam now that setBackgroundColor == --bg-top). */
  background: var(--bg);
  z-index: 10;
}
.overlay-text { color: var(--muted); font-size: 0.8125rem; max-width: 80%; text-align: center; }
.overlay-icon { font-size: 2.5rem; }
.overlay-hint { margin-top: 0.5rem; color: var(--accent); font-size: 0.75rem; }
#detail-error { cursor: pointer; }

/* Retry affordance shared by every failed-load state (home / stats / screener /
   detail): a message plus a solid orange «Обновить» button, so a slow or failed
   server never dead-ends the user without a way back. */
.load-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.875rem;
  padding: 2.5rem 1rem;
  text-align: center;
}
.load-error-msg { color: var(--muted); font-size: 0.8125rem; max-width: 20rem; }
.retry-btn {
  appearance: none;
  border: 0;
  cursor: pointer;
  background: var(--accent);
  color: var(--on-accent);
  font: inherit;
  font-weight: 700;
  font-size: 0.8125rem;
  padding: 0.625rem 1.375rem;
  border-radius: 62.4375rem;
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  transition: background 0.15s ease, transform 0.08s ease;
}
.retry-btn:active { background: var(--accent-press); transform: scale(0.97); }
.retry-btn svg {
  width: 1rem;
  height: 1rem;
  stroke: currentColor;
  stroke-width: 2;
  fill: none;
}

.spinner {
  width: 2.125rem;
  height: 2.125rem;
  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); } }

/* ── Launch splash ──────────────────────────────────────────────────────────
   Brand logo centred on the app's dark gradient (+ a soft brand glow), shown from
   the first painted frame and faded out by app.js (.hiding → .gone) once the first
   view renders. Gives the Mini App a real "launching" moment instead of a bare
   spinner on black. z-index sits above every in-app overlay (loader/error z:10). */
#splash {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.75rem;
  background:
    radial-gradient(90% 55% at 50% 42%, rgba(255, 138, 0, 0.10) 0%, rgba(255, 138, 0, 0) 60%),
    linear-gradient(180deg, var(--bg-top) 0%, var(--bg) 70%);
  transition: opacity 0.45s ease;
}
#splash.hiding { opacity: 0; }
#splash.gone { display: none; }
.splash-logo {
  width: min(62vw, 15rem);
  max-width: 74%;
  height: auto;
  filter: drop-shadow(0 0.5rem 1.5rem rgba(0, 0, 0, 0.5));
  animation: splash-pop 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
/* Text wordmark shown only if the logo image is missing (onerror). */
.splash-word {
  font-size: 2rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: var(--title);
  animation: splash-pop 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
.splash-word b { color: var(--accent); font-weight: 800; }
.splash-spin { opacity: 0.9; animation: spin 0.8s linear infinite, splash-fade-in 0.4s ease 0.2s both; }
@keyframes splash-pop {
  from { transform: scale(0.92); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}
@keyframes splash-fade-in { from { opacity: 0; } to { opacity: 0.9; } }

/* The «Загружаем историю…» pill that used to sit here is gone: older pages are now
   prefetched before the view reaches the edge, so history just appears as you pan or
   zoom out and there is nothing to announce. */

/* ── Feed (list of situations) ───────────────────────────── */
.feed-tfbar {
  padding: 0.25rem 0.875rem 0.875rem;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.feed-tfbar::-webkit-scrollbar { display: none; }

.feed-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 0.25rem 0.625rem 0.875rem 0.875rem;
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  /* Visible thin scrollbar so it's obvious the coin list scrolls (not only wheel). */
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.22) transparent;
}
.feed-list::-webkit-scrollbar { width: 0.375rem; }
.feed-list::-webkit-scrollbar-track { background: transparent; }
.feed-list::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.22);
  border-radius: 62.4375rem;
}
.feed-list::-webkit-scrollbar-thumb:active { background: rgba(255, 255, 255, 0.35); }

.card {
  flex: 0 0 auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  transition: border-color 0.14s, transform 0.08s;
}
.card:active { border-color: var(--accent); transform: scale(0.992); }

/* The chart is the hero of the card: taller, and set on the darker chart base so it
   reads as its own panel — no divider rule needed to separate it from the plate. */
.card-preview {
  position: relative;
  width: 100%;
  height: 14rem;
  background: var(--bg);
}
.card-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.card-spin { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.card.loaded .card-spin { display: none; }

/* Outcome pill overlaid on the preview corner — absolute, so it never reflows the card. */
.card-outcome {
  position: absolute;
  top: 0.625rem;
  left: 0.625rem;
  z-index: 2;
  max-width: calc(100% - 1.25rem);
  padding: 0.25rem 0.625rem;
  border-radius: var(--radius-2xs);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.0125rem;
  line-height: 1.4;
  color: var(--neutral);
  background: rgba(10, 14, 19, 0.82);
  border: 1px solid var(--border-glass);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}
.card-outcome.oc-win { color: var(--up); border-color: rgba(43, 212, 126, 0.3); background: rgba(15, 33, 25, 0.85); }
.card-outcome.oc-loss { color: var(--down); border-color: rgba(255, 84, 102, 0.3); background: rgba(35, 16, 20, 0.85); }
.card-outcome.oc-skip { color: var(--accent); border-color: rgba(255, 138, 0, 0.3); background: rgba(35, 25, 12, 0.85); }
.card-outcome.oc-no_fill { color: var(--muted); }
.card-outcome.oc-timeout { color: var(--muted); }
.card-outcome.oc-pending { color: var(--muted); opacity: 0.85; }
.card-outcome.oc-no_data { color: var(--muted); opacity: 0.7; }

.card-plate {
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.card-row { display: flex; align-items: center; gap: 0.625rem; }
/* Coin name is the primary accent of the header row. */
.card-symbol {
  font-size: 1.0625rem;
  font-weight: 800;
  color: var(--title);
  letter-spacing: 0.0125rem;
}
/* Time meta on one baseline, evenly spaced and quiet: 1m · 09.07 09:03 · 4ч назад. */
.card-meta { font-size: 0.6875rem; color: var(--muted); align-items: center; gap: 0.625rem; }
.card-tf {
  padding: 0.125rem 0.5rem;
  border-radius: var(--radius-2xs);
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);
  font-weight: 600;
}
.card-date { color: var(--muted); }
.card-ago { margin-left: auto; color: var(--muted); }

/* Signal type is the secondary badge — pushed to the far side of the coin name. */
.card .badge-dir { margin-left: auto; }

/* Alert-moment snapshot — mirrors the detail page's .snap-strip 1:1 so the plate reads
   pixel-for-pixel like the chart page. Full-bleed to the card edges; a top hairline
   separates it from the meta row, then a padded 9px eyebrow caption sits over its OWN
   hairline (like .snap-strip-top), then the flat .sstat readout. 16px gutters line the
   numbers up under the ticker; the four readouts spread evenly and only fall back to a
   hidden horizontal scroll in the rare over-long case (same as the detail .stats row). */
.card-alert {
  /* Full-bleed left/right AND bottom (negative bottom margin cancels the plate's 16px
     bottom padding) → the snapshot becomes a clean footer strip flush to the card's
     bottom edge, with no big empty gap under the numbers. No fill: it reads as the same
     dark card surface, separated only by the thin top hairline. */
  margin: 0.25rem -1rem -1rem;
  border-top: 1px solid var(--border-soft);
}
.card-alert .snap-cap {
  margin: 0;
  padding: 0.4375rem 1rem 0.375rem;
  border-bottom: 1px solid var(--border-soft);
  letter-spacing: 0.0375rem;
}
/* Four readouts spread evenly edge-to-edge (16px gutters) so the row is balanced and
   centred across the width, not huddled to one side. Values a touch smaller so all four
   fit without overflow-scrolling. */
.card-alert .snap-stats {
  padding: 0.5rem 1rem 0.625rem;
  gap: 0.375rem;
  align-items: baseline;
  justify-content: space-between;
}
.card-alert .snap-stats .sstat-v { font-size: 0.6875rem; }

/* ── Схлопнутая карточка «Пролив» ─────────────────────────
   Одна запись = одно СОБЫТИЕ, а не монета, поэтому у неё нет ни строки снимка
   рынка (NATR/объём/OI относятся к монете), ни бейджа исхода: исход тут не один,
   он у каждой из 13 строк свой и живёт на карточке внутри. Отличается карточка
   ровно одним — левой полосой в цвет стороны: 🔻 красная, 🟢 зелёная. */
.card-flush { border-left: 3px solid var(--down); }

/* Список монет — тот же подвал-полоска, что и снимок рынка у обычной карточки:
   на всю ширину, отделён волоском, прижат к нижнему краю. Тикеры переносятся, а не
   уезжают в горизонтальный скролл — карточку читают взглядом, а не пальцем. */
.flush-coins {
  margin: 0.25rem -1rem -1rem;
  padding: 0.5rem 1rem 0.625rem;
  border-top: 1px solid var(--border-soft);
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--muted);
  overflow-wrap: anywhere;
}

/* ── Экран одной стороны события ──────────────────────────
   Шапка как у detail: шеврон «назад» + заголовок, без нижней навигации в фокусе. */
.flush-topbar { justify-content: flex-start; gap: 0.5rem; }
.flush-topbar .icon-btn {
  width: 2.125rem;
  height: 2.125rem;
  margin-left: -0.4375rem;
  background: transparent;
  border-color: transparent;
}
.flush-head { display: flex; flex-direction: column; gap: 0.125rem; min-width: 0; }
.flush-title {
  font-size: 1.0625rem;
  font-weight: 800;
  color: var(--title);
  letter-spacing: 0.0125rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.flush-range { font-size: 0.6875rem; color: var(--muted); }

.feed-loader { display: flex; justify-content: center; padding: 0.5rem 0 1rem; }
.spinner-sm { width: 1.375rem; height: 1.375rem; border-width: 2px; }
.feed-empty { text-align: center; color: var(--muted); font-size: 0.8125rem; padding: 2.5rem 1rem; }

/* ── Home («Главная») — top take-profit wins over 24h ────── */
/* The view scrolls as ONE column. The header is content, not chrome: on a 740px Telegram
   viewport a pinned banner spent a third of the screen restating what the user already
   read, so it now yields the screen to the cards as soon as the user moves. */
.home-view {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.22) transparent;
}
.home-view::-webkit-scrollbar { width: 0.375rem; }
.home-view::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.22); border-radius: 62.4375rem; }

/* ── AI-assistant hero ──────────────────────────────────────────────
   The mascot is the emotional anchor of the landing view. Its glow, aura and
   accent copy all read from a single --mood set on [data-mood]; the whole card
   cross-fades between mood palettes. Eyes/accents recolour via hue-rotate of the
   baked-orange PNG (isolating just the eyes in a raster is impossible), so the
   robot appears to "glow the mood colour". */
.ai-hero {
  --mood: #3aa0ff;                        /* neutral (blue) default */
  --mood-soft: rgba(58, 160, 255, 0.22);
  --bot-hue: 180deg;                      /* orange (~35°) → mood hue */
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0.875rem 1rem 0.875rem;
  padding: 1.25rem 1.25rem 1.25rem 1.375rem;
  min-height: 9.5rem;
  border-radius: var(--radius);
  overflow: hidden;
  /* Directional light: the card is darkest at the left edge and lifts toward the bottom-right,
     where the robot stands — a mood-tinted wash plus a soft white bloom read as the mascot
     spilling light onto the panel, rather than a flat, evenly-lit rectangle. */
  background:
    radial-gradient(90% 95% at 100% 100%, var(--mood-soft) 0%, transparent 55%),
    radial-gradient(72% 82% at 93% 86%, rgba(255, 255, 255, 0.055) 0%, transparent 60%),
    linear-gradient(100deg, #131313 0%, #1a1a1a 52%, #242424 100%);
  border: 1px solid var(--border);
  /* Lifted off the page: a soft cast shadow for depth + a hairline top highlight so the
     panel catches light like a physical card. */
  box-shadow: 0 12px 28px -14px rgba(0, 0, 0, 0.7), inset 0 1px 0 rgba(255, 255, 255, 0.05);
  transition: background 0.6s ease;
  animation: cardRise 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}
/* Mood → palette + hue-rotate amount. bull=green, bear=red, flat=yellow, neutral=blue. */
.ai-hero[data-mood="bull"]    { --mood: #2bd47e; --mood-soft: rgba(43, 212, 126, 0.24); --bot-hue: 108deg; }
.ai-hero[data-mood="bear"]    { --mood: #ff5466; --mood-soft: rgba(255, 84, 102, 0.24); --bot-hue: -42deg; }
.ai-hero[data-mood="flat"]    { --mood: #ffcf3a; --mood-soft: rgba(255, 207, 58, 0.22); --bot-hue: 16deg; }
.ai-hero[data-mood="neutral"] { --mood: #3aa0ff; --mood-soft: rgba(58, 160, 255, 0.22); --bot-hue: 180deg; }

/* Ambient wash behind the robot. Kept low-opacity, wide, and heavily blurred so it
   reads as light spilling off the mascot into the card — never a hard-edged disc that
   sits on top of the background. Anchored to the bot's corner (bottom-right), not the
   vertical centre. */
.ai-hero-aura {
  position: absolute;
  right: -5rem;
  bottom: -5.5rem;
  width: 21rem;
  height: 21rem;
  /* Wider, softer and fainter than before so it reads as ambient light spilling off the robot
     that melts into the card's own bottom-right lift — not a coloured disc laid on top of the
     background. The gradient fades to transparent very early (48%) and the whole layer is heavily
     blurred, so the glow dissolves gradually into the background with no perceptible edge. */
  background: radial-gradient(circle at 55% 55%, var(--mood) 0%, transparent 48%);
  opacity: 0.10;
  filter: blur(64px);
  pointer-events: none;
  animation: auraPulse 4.2s ease-in-out infinite;
  transition: background 0.6s ease;
}
.ai-hero-copy {
  position: relative;
  z-index: 1;
  flex: 1 1 auto;
  min-width: 0;
  /* Clearance for the mascot. The robot PNG's visible silhouette (measured: 19.5%–79.4% of its
     square box) reaches ~9.4rem in from the card's right edge, while the text column only used to
     reserve 7rem — so any headline that filled the clamp ran under the robot, and the collision
     grew on wider (desktop) viewports where the copy column stretches further right. This reserve
     is in rem and the robot is rem-sized against the right edge, so one value clears it at every
     width. */
  padding-right: 9.25rem;
}
.ai-hero-kicker {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  white-space: nowrap;   /* «AI-ассистент» is one token — never let the narrow column break it */
  color: var(--mood);
  transition: color 0.6s ease;
}
.ai-hero-dot {
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: 50%;
  background: var(--mood);
  box-shadow: 0 0 8px var(--mood);
  animation: dotPulse 2s ease-in-out infinite;
  transition: background 0.6s ease, box-shadow 0.6s ease;
}
.ai-hero-headline {
  margin: 0.5rem 0 0;
  font-size: 0.875rem;
  font-weight: 800;
  line-height: 1.34;
  letter-spacing: -0.005em;
  color: var(--title);
  text-wrap: balance;
  /* A HARD-fixed 3-line box: the rotating phrase (reads of varying length) is clamped so the
     card NEVER resizes as the assistant «speaks» — and 3 lines keeps the card compact rather than
     reading like a paragraph of an article. Anything longer than 3 lines trails off with an
     ellipsis; the spoken copy is kept short enough that this is rare.
     height is locked, not just min-height, so a short line and a long one occupy the same space. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* min-height reserves the full 3-line box (a hair above 3×line-height) so a short spoken line
     occupies the same space as a long one and the card never resizes — while staying just clear
     of the clamp boundary, so WebKit doesn't inject a phantom ellipsis on a phrase that fits. */
  min-height: 3.55rem;
  transition: opacity 0.22s ease;    /* soft crossfade as the spoken line rotates */
}
.ai-hero-headline.is-swapping { opacity: 0; }
.ai-hero-mood {
  display: inline-block;
  margin-top: 0.5625rem;
  font-size: 0.78125rem;
  font-weight: 700;
  letter-spacing: 0.005em;
  color: var(--mood);
  transition: color 0.6s ease;
  /* Две строки зарезервированы ВСЕГДА — тот же приём, что у .ai-hero-headline выше, и по той
     же причине: карточка не имеет права менять высоту, когда приезжают данные.
     Плейсхолдер «Рынок нейтральный» влезает в одну строку, а боевая подпись «Рынок во флэте
     +1.6%» в колонку 190px (остаток после padding-right под руку робота) переносится на две
     — карточка вырастала на 14.31px и толкала вниз весь экран ровно в момент загрузки
     (замер 390×740: 161.69px → 176.00px). */
  line-height: 1.2;
  min-height: 2.4em;
}
/* The mascot is enlarged and seated into the card's bottom-right corner so it reads as if it
   is standing BEHIND the panel: the card's own `overflow: hidden` gives a clean straight cut
   across the hips (no mask, no fade/blur), and the box is sized + dropped far enough that the
   legs and feet fall below that edge and never show. Motion is layered for a natural, calm idle
   and runs on every device: it is NOT gated behind prefers-reduced-motion (users wanted the
   desktop robot alive without touching the Windows animation setting). */
.ai-hero-bot {
  position: absolute;
  right: -1.5rem;              /* seated a little further into the right corner so its raised arm clears the copy */
  bottom: -4.5rem;            /* dropped a touch less than before so the head gains a little top breathing room and the bust reads centred, not jammed at the top edge — hip-cut still hides the legs */
  z-index: 1;
  width: 13.5rem;            /* slightly smaller so it sits centred with headroom, still enough presence and clear of the text column */
  height: 13.5rem;
  /* Layer 1 — slow "breathing": a small vertical drift + micro-scale. */
  animation: botBreath 6s cubic-bezier(0.45, 0, 0.55, 1) infinite;
  will-change: transform;
}
.ai-bot-inner {
  position: relative;
  width: 100%;
  height: 100%;
  /* Layer 2 — a slow, tiny sway, out of phase with the breath so the compound motion never
     looks like one mechanical loop. */
  animation: botSway 9s ease-in-out infinite;
  transform-origin: 58% 46%;   /* pivot near the face so the head barely moves, body settles */
}
.ai-bot-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center top;   /* square image in a square box → exact fill, face is deterministic */
  filter:
    hue-rotate(var(--bot-hue)) saturate(1.12)
    /* grounding cast shadow (neutral) + a WIDE, low-opacity mood glow that dissolves into the
       card instead of hugging the silhouette as a hard coloured outline. */
    drop-shadow(0 5px 13px rgba(0, 0, 0, 0.5))
    drop-shadow(0 0 34px var(--mood-soft));
  transition: filter 0.6s ease;
  -webkit-user-drag: none;
  user-select: none;
  /* Purely decorative — nothing is bound to it. Taking it out of hit-testing means a
     long press lands on the hero card instead of on an <img>, so the webview has no
     image to offer a "save / copy link" menu for even if it ignores the
     contextmenu preventDefault in app.js. */
  pointer-events: none;
}
@keyframes botBreath {
  0%, 100% { transform: translateY(0) scale(1); }
  50%      { transform: translateY(-0.24rem) scale(1.012); }
}
@keyframes botSway {
  0%, 100% { transform: rotate(-0.9deg); }
  50%      { transform: rotate(0.9deg); }
}
@keyframes auraPulse {
  0%, 100% { opacity: 0.11; transform: scale(1); }
  50%      { opacity: 0.18; transform: scale(1.06); }
}
/* Panels ease up + fade in on first paint — a calm, staggered arrival that reads as
   "the page composing itself" rather than snapping in. Runs on desktop too. */
@keyframes cardRise {
  from { opacity: 0; transform: translateY(0.625rem); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes dotPulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.5; transform: scale(0.85); }
}
/* ⚠️ СВОИ кадры для точки дня, а не общий dotPulse, и это не копипаста. `transform` в
   кадрах ЗАМЕЩАЕТ значение из правила целиком: на .day-dot, который центрируется
   `translate(-50%, -50%)`, общий dotPulse со `scale(1)` стирал этот перенос и всю анимацию
   держал точку на 3px правее и ниже своего значения. Перенос обязан стоять В КАЖДОМ кадре. */
@keyframes dayDotPulse {
  0%, 100% { opacity: 1;    transform: translate(-50%, -50%) scale(1); }
  50%      { opacity: 0.62; transform: translate(-50%, -50%) scale(0.86); }
}

/* Day header — the section label above the top-winners board (hero now carries the
   day's mood). Smaller than the old page-title since the assistant leads the view. */
.home-head {
  flex: 0 0 auto;
  padding: 0.25rem 1rem 0.75rem;
}
.home-head-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
}
.home-title {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 800;
  letter-spacing: 0.0125rem;
  color: var(--title);
  text-wrap: balance;
}
/* Лид доски — РОВНО одна строка (требование владельца 25.08). Кегль опущен с 0.8125rem
   до 0.75rem замером на стенде 390×740 (root 17.02px, полезная ширина 356px): прежним
   набором строка «Самые прибыльные тейки: расчёт при риске 1%» не влезала.
   `nowrap` здесь несущий, а не косметика: без него строка ломается на две ровно в тот
   день, когда риск станет двузначным («10%» шире «1%»), — а это дефект, который увидит
   владелец, а не тест. Многоточие оставлено осознанно: если строка всё же не влезет,
   пусть её обрежет край, а не второй ряд, толкающий доску вниз. */
.home-lede {
  margin: 0.25rem 0 0;
  font-size: 0.75rem;
  line-height: 1.35;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Имя лида чуть светлее хвоста — двоеточие делит строку на «что» и «как», и без разницы
   в тоне она читается одним серым полотном. */
.home-lede-key {
  font-weight: 700;
  color: var(--stat-header);
}
.home-updated {
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--stat-header);
  white-space: nowrap;
}
/* ── Итог дня ────────────────────────────────────────────────────────────────────
   Раскладка по эталону владельца (D:\Загрузки\Дашборд.png, 25.08): герой-число и чип знака
   в одной строке, под числом ТИХАЯ подпись знака, дальше полоса кривой во всю ширину с
   пунктирной сеткой, ряд из трёх счётчиков и — под волосяным разделителем — сноска
   «Расчёт: N% риска на каждую сделку».
   ⚠️ КАРТОЧКА ВЫШЕ .ai-hero, И ЭТО РЕШЕНИЕ ВЛАДЕЛЬЦА, А НЕ НЕДОСМОТР. 25.08 сноска стояла
   пилюлей в строке героя ровно ради высоты (175px у карточки ассистента против 201px со
   сноской); владелец вернул сноску вниз явным требованием и с точным текстом. Прежде чем
   «чинить» высоту — спроси, а не режь сноску снова.
   ⛔ У КАРТОЧКИ НЕТ ФОНОВОГО СВЕЧЕНИЯ. Слой .day-glow (размытый диск --day в правом нижнем
   углу) и тёплый угол #201c17 в градиенте фона сняты 25.08 по прямому замечанию владельца
   («у нас нет на карточке справа какого-то свечения на фоне»). Фон нейтральный. Светится
   ТОЛЬКО кривая — и слабо.
   --day — ОДНА ручка цвета, и её читают ровно три вещи: число, кривая с точкой и стрелка
   чипа. Иконки счётчиков и щит сноски НЕЙТРАЛЬНЫ по требованию владельца («чтоб не менялся
   каждый раз») — это хромировка, а не показание. Три состояния ставит app.js на карточке
   (.up / .down / ни одного). Фирменный оранжевый — это «вердикта нет»: день, который никуда
   не вошёл или закрылся ровно в ноль, не победа и не поражение. */
.day-card {
  --day: var(--accent);
  position: relative;
  flex: 0 0 auto;
  margin: 0 1rem 0.875rem;
  padding: 0.875rem 1rem 0.8125rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* overflow скрыт ради ореола кривой: он обязан срезаться по скруглению карточки, а не
     торчать прямоугольником за её углом. */
  overflow: hidden;
  /* Нейтральный подъём к верхнему левому углу, где стоит число. Ни одного тёплого тона:
     тёплый угол справа внизу и читался как «свечение на фоне». */
  background: linear-gradient(158deg, #1b1b1b 0%, #161616 58%, #131313 100%);
  box-shadow: 0 12px 28px -14px rgba(0, 0, 0, 0.7), inset 0 1px 0 rgba(255, 255, 255, 0.045);
  animation: cardRise 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.06s both;
}
.day-card.hidden { display: none; }
.day-card.up { --day: var(--up); }
.day-card.down { --day: var(--down); }
.day-body {
  position: relative;
  z-index: 1;
}
.day-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
}
.day-head { min-width: 0; }
.day-sum {
  display: block;
  font-size: 2.125rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.035em;
  color: var(--title);
  font-variant-numeric: tabular-nums;
}
.day-sum.up { color: var(--up); }
.day-sum.down { color: var(--down); }
/* Подпись знака словами. Цвет — не сообщение: «−1.4%» обязано читаться как убыток и тем, кто
   не различает два зелёных, и тем, кто скользнул взглядом.
   ⚠️ Она ТИХАЯ и МЕЛКАЯ намеренно (0.6875rem против 2.125rem у числа — эталонное отношение
   ≈1:3). До 25.08 стояло 0.8125rem/700, и владелец: «текст намного меньше, а у тебя какой-то
   большой». Подпись объясняет число, а не спорит с ним. */
.day-label {
  margin: 0.3125rem 0 0;
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: 0.005em;
  color: var(--neutral);
}
/* Чип знака в правом верхнем углу — единственное, что уравновешивает герой-число, и
   единственный элемент хромировки, которому МОЖНО менять цвет: он и есть знак. */
.day-tag {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  margin: 0.25rem 0 0;
  padding: 0.3125rem 0.5625rem;
  border: 1px solid var(--border);
  border-radius: 62.4375rem;
  background: var(--surface-2);
  font-size: 0.625rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.02em;
  color: var(--neutral);
  white-space: nowrap;
}
.day-tag.hidden { display: none; }
.day-tag-ico {
  width: 0.8125rem;
  height: 0.8125rem;
  flex: 0 0 auto;
  fill: none;
  stroke: var(--day);
  stroke-width: 2.1;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Один глиф на оба направления — см. #ico-arrow-dn в спрайте. */
.day-tag-ico.up { transform: rotate(180deg); }
/* Полоса кривой — собственная строка во всю ширину. Высота фиксированная: пустой день
   (кривой нет) обязан занимать ровно столько же, иначе карточка прыгает между «—» и первой
   сделкой.
   Пунктирная сетка живёт в CSS, а не в SVG кривой, и это не вкусовщина: dayCurve() возвращает
   ПУСТОЕ на дне без двух точек (деление на ноль дало бы path из NaN), а вместе с ним исчезала
   бы и сетка — на пустом дне полоса читалась как ДЫРА между подписью и счётчиками, а не как
   пустой график.
   ⚠️ ТРИ линии, а не четыре, и тон 0.035, а не 0.07 — требование владельца 25.08 («сетку
   прозрачнее и меньше 4 штук, а то не красиво»). Сетка обрамляет полосу (верх · середина ·
   низ) и не имеет права спорить с кривой над ней. */
.day-curve {
  --day-grid: rgba(255, 255, 255, 0.035);
  position: relative;
  margin-top: 0.5625rem;
  height: 2.625rem;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(to right, var(--day-grid) 0 3px, transparent 3px 10px),
    repeating-linear-gradient(to right, var(--day-grid) 0 3px, transparent 3px 10px),
    repeating-linear-gradient(to right, var(--day-grid) 0 3px, transparent 3px 10px);
  background-size: 100% 1px;
  background-repeat: no-repeat;
  background-position: 0 0.0625rem, 0 50%, 0 calc(100% - 0.0625rem);
}
/* Три слоя друг на друге: заливка · размытый ореол · чёткая линия.
   ⚠️ ГАШЕНИЕ КРАЁВ — CSS-МАСКАМИ, а не градиентными масками внутри SVG, и у заливки маска
   СВОЯ. Разводит их одно требование: заливка обязана раствориться и СПРАВА (иначе у полосы
   стенка на самом краю), а конец линии обязан быть САМЫМ ЯРКИМ — там «где мы сейчас».
   Общая маска этих двух вещей не делает: замерено 25.08, на дне +14.2% она оставляла
   вертикальную кромку заливки у правого края.
   Слева гашение общее и КОРОТКОЕ (в половину уже к 4% ширины, ≈13px): разгон от нуля на трети
   ширины съедал бы начало РЕАЛЬНОГО дня. */
.day-curve svg {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.day-curve-fill {
  -webkit-mask-image: linear-gradient(to right, transparent 0%, rgba(0, 0, 0, 0.45) 4%, #000 40%, #000 86%, transparent 100%);
          mask-image: linear-gradient(to right, transparent 0%, rgba(0, 0, 0, 0.45) 4%, #000 40%, #000 86%, transparent 100%);
}
.day-curve-glow,
.day-curve-line {
  -webkit-mask-image: linear-gradient(to right, transparent 0%, rgba(0, 0, 0, 0.5) 4%, #000 42%);
          mask-image: linear-gradient(to right, transparent 0%, rgba(0, 0, 0, 0.5) 4%, #000 42%);
}
/* ⚠️ РАЗМЫТИЕ ЖИВЁТ ЗДЕСЬ, А НЕ В SVG, и это замер, а не стиль. viewBox кривой растянут
   неравномерно (100×100 → ≈324×45px), поэтому SVG-фильтр внутри него анизотропен: тот же
   `drop-shadow(0 0 4px)` давал ≈13px по горизонтали против ≈1.8px по вертикали — резко
   обрезанную сверху и снизу кляксу. `filter` в CSS применяется к уже свёрстанной коробке
   слоя, то есть считает радиус в ЭКРАННЫХ пикселях и одинаково по обеим осям.
   Стопка полупрозрачных обводок эту анизотропию тоже снимала, но давала СТУПЕНИ — по кольцу
   на слой; владелец увидел их 25.08 («видно переходы»). Гаусс ступеней не имеет.
   ⛔ Не поднимать opacity: свечение по требованию владельца СЛАБОЕ. */
.day-curve-glow {
  filter: blur(4px);
  opacity: 0.5;
}
/* «Мы сейчас здесь» — последняя точка дня, настоящим кругом в CSS, а не внутри SVG: тот
   viewBox растянут неравномерно (preserveAspectRatio="none") и раздавил бы <circle> в овал.
   Проценты приходят из app.js в той же системе координат.
   ⚠️ Ореол — псевдоэлементом с РАДИАЛЬНЫМ градиентом, а не box-shadow-кольцом: у кольца
   ровный край на границе радиуса, ровно та «резкость», на которую пожаловался владелец. */
.day-dot {
  position: absolute;
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 50%;
  background: var(--day);
  transform: translate(-50%, -50%);
  animation: dayDotPulse 2.4s ease-in-out infinite;
}
.day-dot::before {
  content: "";
  position: absolute;
  inset: -0.5rem;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, var(--day) 0%, transparent 70%);
  opacity: 0.34;
}
/* Три счётчика дня. Разделители — волосяная линия между колонками, а не цветная полоса
   слева у карточки: это сетка, а не акцент. */
.day-metrics {
  display: flex;
  align-items: center;
  margin-top: 0.5625rem;
}
.day-metric {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}
.day-metric + .day-metric {
  padding-left: 0.625rem;
  border-left: 1px solid var(--border-soft);
}
/* Плитка иконки. ⛔ НЕ КРАСИТСЯ В --day: 25.08 владелец потребовал нейтральные иконки
   («чтоб не менялся каждый раз»). Плитка и глиф — постоянная хромировка карточки; знак дня
   несут число, кривая и стрелка чипа, и трёх носителей знака достаточно. */
.day-mtile {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 1.6875rem;
  height: 1.6875rem;
  border-radius: var(--radius-2xs);
  border: 1px solid var(--border-soft);
  background: var(--surface-2);
}
.day-mtile svg {
  width: 0.9375rem;
  height: 0.9375rem;
  fill: none;
  stroke: var(--neutral);
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.day-mtext { min-width: 0; }
.day-mtext b {
  display: block;
  font-size: 0.9375rem;
  font-weight: 800;
  line-height: 1;
  color: var(--title);
}
.day-mtext i {
  display: block;
  margin-top: 0.125rem;
  font-style: normal;
  font-size: 0.625rem;
  font-weight: 600;
  line-height: 1.25;
  color: var(--muted);
  white-space: nowrap;   /* «в работе» держится одной строкой на 390px */
}
/* Сноска единицы под волосяным разделителем — низ карточки на эталоне. Щит НЕЙТРАЛЬНЫЙ по
   тому же требованию, что и плитки счётчиков. */
.day-note {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0.5625rem 0 0;
  padding-top: 0.5625rem;
  border-top: 1px solid var(--border-soft);
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1.3;
  color: var(--muted);
}
.day-note.hidden { display: none; }
.day-note-ico {
  width: 0.875rem;
  height: 0.875rem;
  flex: 0 0 auto;
  fill: none;
  stroke: var(--neutral);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Daily insights — deterministic reads of the screener's own stats (no LLM). Icons are
   stroked glyphs from the sprite; the emoji bullets they replace rendered differently on
   every platform and read as chat, not instrumentation. */
.insights {
  flex: 0 0 auto;
  margin: 0 1rem 0.875rem;
  padding: 1.125rem 1.125rem 1.25rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 10px 24px -16px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.035);
  animation: cardRise 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.12s both;
}
.insights.hidden { display: none; }
/* Header doubles as the collapse toggle (button, reset to look like the old row). */
.insights-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0;
  background: none;
  border: 0;
  text-align: left;
  color: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.insights-title {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 0.875rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--title);
  white-space: nowrap;   /* keep «Что говорит ассистент» on one line — the updated chip shrinks first */
  overflow: hidden;
  text-overflow: ellipsis;
}
/* «За последние сутки» — the panel's WINDOW, on its own quiet line above the bullets.
   NOT in the title: at 390px the suffix overflows the title's single nowrap line by 58px and
   the ellipsis eats the heading (measured on the stand 2026-08-24). Quieter than the bullets
   it introduces — a qualifier, not a heading. Its whole reason to exist is that the «Итог дня»
   card right above runs on the CALENDAR day while these bullets run on a trailing 24 h. */
.insights-window {
  margin: 0 0 0.5rem;
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--muted);
}

/* «обновлено …» — a quiet live chip: a soft accent dot + muted text on a faint surface pill.
   More noticeable than the old bare grey text, still calm enough not to compete with the title. */
.insights-updated {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  padding: 0.1875rem 0.4375rem;
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
  background: var(--surface-2);
  border-radius: 62.4375rem;
}
.insights-updated:empty { display: none; }
.insights-updated::before {
  content: "";
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 6px rgba(255, 138, 0, 0.6);
  animation: dotPulse 2s ease-in-out infinite;
}
.insights-chev {
  flex: 0 0 auto;
  width: 1.125rem;
  height: 1.125rem;
  stroke: var(--muted);
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 0.3s ease;
}
.insights.collapsed .insights-chev { transform: rotate(-90deg); }
/* Smooth "tab" open/close via grid-rows (0fr↔1fr) — no layout-thrashing height
   animation, and no arbitrary max-height cap that could clip a longer summary. */
.insights-body {
  display: grid;
  grid-template-rows: 1fr;
  opacity: 1;
  transition: grid-template-rows 0.35s ease, opacity 0.25s ease;
}
.insights.collapsed .insights-body {
  grid-template-rows: 0fr;
  opacity: 0;
}
/* ⚠️ ЕДИНСТВЕННЫЙ ребёнок сетки, и на нём же обрезка. Трек 0fr гасит только ЯВНУЮ строку:
   стоило телу получить второго ребёнка (строка окна «За последние сутки», 24.08), как тот
   встал в НЕЯВНУЮ строку grid-auto-rows: auto и сохранил полную высоту — панель уходила в
   opacity: 0, но занимала прежнее место, и свёртка читалась как «текст просто пропал».
   Новый блок внутри панели кладём СЮДА, а не рядом. */
.insights-inner {
  min-height: 0;
  overflow: hidden;
}
.insights-list {
  list-style: none;
  margin: 0;
  padding: 0.875rem 0 0;
  min-height: 0;      /* let the grid track collapse to 0 */
  overflow: hidden;   /* clip bullets as the row closes */
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
}
.insights-item {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1.45;
  color: var(--text);
}
/* Icon sits in a soft round chip so each read is a discrete recommendation, not a flat
   bullet in a paragraph. content-box keeps the glyph a fixed size inside the padded chip. */
.insights-ico {
  flex: 0 0 auto;
  box-sizing: content-box;
  width: 0.9375rem;
  height: 0.9375rem;
  padding: 0.375rem;
  border-radius: 50%;
  background: var(--surface-2);
  stroke: var(--muted);
  fill: none;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Orange marks the one insight worth acting on (the freshest live setup). Its chip picks up a
   faint accent wash so the eye lands there first. */
.insights-ico.accent {
  stroke: var(--accent);
  background: rgba(255, 138, 0, 0.12);
}
.insights-ico.warn { stroke: var(--stat-header); }

/* The coin token inside «самый интересный сейчас» is a live link into its отработка. Kept as
   inline accent text with a soft underline — reads as tappable at a glance without the loud
   filled chip that used to shout over the rest of the calm insight list. The soft accent wash
   only appears on press, to confirm the tap. */
.insight-link {
  display: inline;
  padding: 0.02rem 0.12rem;
  border: none;
  border-radius: 0.375rem;
  font: inherit;
  font-weight: 800;
  letter-spacing: 0.01em;
  color: var(--accent);
  background: transparent;
  text-decoration: underline;
  text-decoration-color: rgba(255, 138, 0, 0.45);
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.15em;
  cursor: pointer;
  transition: background 0.15s ease, text-decoration-color 0.15s ease;
  -webkit-tap-highlight-color: transparent;
}
.insight-link:hover,
.insight-link:active {
  background: var(--accent-soft, rgba(255, 138, 0, 0.14));
  text-decoration-color: var(--accent);
}

.home-list {
  flex: 0 0 auto;
  padding: 0 1rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.home-card {
  flex: 0 0 auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  transition: border-color 0.14s, transform 0.08s;
  /* Entrance: a short staggered rise. --i is set per card by buildHomeCard. */
  animation: home-card-in 0.34s cubic-bezier(0.22, 1, 0.36, 1) both;
  animation-delay: calc(var(--i, 0) * 55ms);
}
@keyframes home-card-in {
  from { opacity: 0; transform: translateY(0.5rem); }
  to { opacity: 1; transform: none; }
}
.home-card:active { border-color: var(--accent); transform: scale(0.992); }

.home-top {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.875rem 1rem 0.75rem;
}
/* Rank marker: a numbered chip. #1 is lit orange («look here»); the rest stay muted —
   the medal emoji it replaces rendered as three different illustrations per platform. */
.home-rank {
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 62.4375rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 800;
  line-height: 1;
}
.home-rank.rank-1 {
  background: var(--accent);
  border-color: transparent;
  color: var(--on-accent);
}
.home-info { flex: 1 1 auto; min-width: 0; }
.home-sym {
  font-size: 1.0625rem;
  font-weight: 800;
  color: var(--title);
  letter-spacing: 0.0125rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.home-sub {
  margin-top: 0.125rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.home-pnl {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.1875rem;
}
.home-pnl-v {
  font-size: 1.25rem;
  font-weight: 800;
  line-height: 1;
  color: var(--up);
  letter-spacing: 0.0125rem;
}
.home-pnl-tag {
  display: flex;
  align-items: center;
  gap: 0.1875rem;
  font-size: 0.625rem;
  font-weight: 700;
  color: var(--up);
}
.home-pnl-ico {
  width: 0.6875rem;
  height: 0.6875rem;
  display: block;
  stroke: currentColor;
  fill: none;
  stroke-width: 2.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The chart strip — the card's hero. It sits on the chart base, full-bleed to the card
   edges, and is tall enough for real candles: at 5.5rem the 690-bar outcome window
   collapsed into a 1px smear, which is why «отработку не видно». See fitCandles(). */
.home-preview {
  position: relative;
  width: 100%;
  height: 8.5rem;
  background: var(--bg);
  border-top: 1px solid var(--border-soft);
}
/* Skeleton sweep while the thumbnail fetches — the strip reads as loading, not broken.
   ОДИН приём на весь экран «Главной»: та же полоса света бежит и по графику карточки, и
   по каждой заглушке скелетона ниже (.skel-bar / .skel-strip). --s сдвигает фазу, чтобы
   соседние заглушки не мигали синхронно — иначе экран пульсирует как один блок. */
.home-preview::after,
.skel-bar::after,
.skel-strip::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.035), transparent);
  transform: translateX(-100%);
  animation: home-skeleton 1.5s ease-in-out infinite;
  animation-delay: calc(var(--s, 0) * 140ms);
}
@keyframes home-skeleton { to { transform: translateX(100%); } }
.home-card.loaded .home-preview::after { display: none; }
.home-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  transition: opacity 0.24s ease-out;
}
.home-card.loaded .home-canvas { opacity: 1; }

/* Empty state teaches the board instead of just saying «nothing here». */
.home-blank {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 2rem 1.5rem 2.5rem;
}
.home-blank-art { width: 6.5rem; height: auto; opacity: 0.5; margin-bottom: 0.875rem; }
.home-blank-t { font-size: 0.9375rem; font-weight: 800; color: var(--text); }
.home-blank-s {
  margin-top: 0.375rem;
  font-size: 0.8125rem;
  line-height: 1.4;
  color: var(--muted);
  max-width: 17rem;
}

/* ── Скелетон «Главной» ───────────────────────────────────────────────────────
   Заглушки повторяют геометрию настоящих блоков (те же margin/padding/radius и та же
   высота полосы графика 8.5rem), чтобы подмена содержимого не двигала экран. Размеры
   полосок взяты по САМОМУ ЧАСТОМУ содержимому, а не по максимуму: заглушка шире реального
   текста читается как «данные не влезли».
   ⚠️ Ни одна анимация в этом блоке НЕ гасится под prefers-reduced-motion — см. NOTE в
   том медиазапросе ниже. */
.home-skel { flex: 0 0 auto; }
.home-skel.hidden { display: none; }

.skel-bar {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: 62.4375rem;
  background: var(--surface-2);
}

/* «Итог дня» — та же рамка и та же сетка, что у .day-card: строка «число + пилюля», полоса
   кривой, ряд из трёх плиток. Высота собрана из тех же rem, а не прибита min-height'ом —
   так реальная карточка встаёт на то же место и не сдвигает доску победителей. */
.skel-day {
  margin: 0 1rem 0.875rem;
  padding: 0.875rem 1rem 0.8125rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.skel-day-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.75rem; }
.skel-day-sum { width: 6.75rem; height: 2.125rem; border-radius: var(--radius-2xs); }
.skel-day-tag { width: 4.25rem; height: 1.375rem; margin-top: 0.25rem; border-radius: 62.4375rem; --s: 3; }
.skel-day-label { width: 8.25rem; height: 0.6875rem; margin-top: 0.4375rem; --s: 1; }
.skel-day-curve { width: 100%; height: 2.625rem; margin-top: 0.5625rem; border-radius: var(--radius-2xs); --s: 2; }
.skel-day-counts { display: flex; align-items: center; gap: 0.625rem; margin-top: 0.5625rem; }
.skel-day-tile { flex: 1 1 0; height: 1.6875rem; border-radius: var(--radius-2xs); --s: 2; }
/* Нижняя сноска карточки. Отступ 1.125rem подменяет собой margin + разделитель + padding
   настоящей строки: без неё скелетон стал бы на 33px ниже карточки, ради чего он и есть. */
.skel-day-note { display: block; width: 12.5rem; height: 0.6875rem; margin-top: 1.125rem; --s: 3; }

/* Панель «Что говорит ассистент» — рамка .insights один-в-один, и внутри та же сетка
   «кружок-иконка + строка», потому что высоту панели задают именно эти четыре ряда. */
.skel-insights {
  margin: 0 1rem 0.875rem;
  padding: 1.125rem 1.125rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.skel-ins-title { width: 11rem; height: 0.875rem; }
.skel-ins-window { width: 7.5rem; height: 0.6875rem; margin-top: 0.5rem; --s: 1; }
.skel-ins-row {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  margin-top: 0.875rem;      /* = gap у .insights-list */
}
.skel-insights .skel-ins-row:first-of-type { margin-top: 1.25rem; }
.skel-insights .skel-ins-row:nth-of-type(2) { --s: 1; }
.skel-insights .skel-ins-row:nth-of-type(3) { --s: 2; }
.skel-insights .skel-ins-row:nth-of-type(4) { --s: 3; }
.skel-ins-ico { flex: 0 0 auto; width: 1.6875rem; height: 1.6875rem; }
/* Разная длина строк: полоски одной ширины читаются как таблица, а не как текст. */
.skel-ins-line { flex: 1 1 auto; height: 0.75rem; }
.skel-insights .skel-ins-row:nth-of-type(2) .skel-ins-line { max-width: 84%; }
.skel-insights .skel-ins-row:nth-of-type(3) .skel-ins-line { max-width: 68%; }
.skel-insights .skel-ins-row:nth-of-type(4) .skel-ins-line { max-width: 78%; }

/* Карточки победителей — геометрия .home-card / .home-top / .home-preview. */
.home-skel .skel-card + .skel-card { margin-top: 0.75rem; }
.home-skel .skel-card:nth-child(2) { --s: 1; }
.home-skel .skel-card:nth-child(3) { --s: 2; }
.skel-card {
  margin: 0 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}
.skel-card-top {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.875rem 1rem 0.75rem;
}
.skel-rank { flex: 0 0 auto; width: 1.5rem; height: 1.5rem; }
.skel-card-info { flex: 1 1 auto; min-width: 0; }
/* Высоты полосок подогнаны ПО ЗАМЕРУ, а не на глаз: с 0.9375/0.6875/0.375rem карточка-заглушка
   давала 208.31px против 213.42px у настоящей (390×740, root 17.02px), и доска дёргалась на
   5px × 3 карточки в момент подмены. Текущие числа дают 213.4px. */
.skel-sym { width: 5.5rem; height: 1.0625rem; }
.skel-sub { width: 8.5rem; height: 0.75rem; margin-top: 0.5rem; }
.skel-pnl { flex: 0 0 auto; width: 4rem; height: 1.375rem; border-radius: var(--radius-xs); }
/* Полоса графика на месте .home-preview — тот же фон и та же высота 8.5rem. */
.skel-strip {
  position: relative;
  overflow: hidden;
  height: 8.5rem;
  background: var(--bg);
  border-top: 1px solid var(--border-soft);
}

@media (prefers-reduced-motion: reduce) {
  /* ⚠️⚠️ ЧТО ЗДЕСЬ НЕ ДОЛЖНО ПОЯВЛЯТЬСЯ — и почему это правило стоило двух заходов.
     На Windows «Показывать анимацию» выключено ПО УМОЛЧАНИЮ, и WebView2/Chrome (то есть
     Telegram Desktop и Telegram Web) репортят из-за этого `prefers-reduced-motion: reduce`.
     Замерено на машине владельца: `matchMedia('(prefers-reduced-motion: reduce)').matches
     === true`. Значит ЛЮБАЯ строка, попавшая сюда, гасится не «у пользователя с особыми
     настройками», а у ВСЕХ на ПК — при живой анимации на телефоне. Ровно этот перекос
     владелец и принёс: «на телефоне видно что загружаются, а на пк этого нет».
     Маскот, точка-кикер и точка кривой были вынесены отсюда по той же причине ещё
     2026-07-18; карточки «Лучшее за сутки» и мерцание скелетонов тогда пропустили.

     Здесь остаётся только ОКРУЖАЮЩАЯ декорация — то, что ничего не сообщает:
     широкий пульс ауры, пульс точки «обновлено», разовая отрисовка R/R-шкалы.
     ⛔ НЕ добавлять сюда индикаторы ЗАГРУЗКИ: застывшая серая полоска читается как
     «данных нет», а не «данные едут», — это уже не полировка, а другое сообщение. */
  /* The "updated" live dot is motion-for-polish, not content. */
  .insights-updated::before { animation: none; }
  /* The risk/reward meter draws itself once when the sheet opens — polish, not content;
     the bar's final proportions carry the meaning on their own. */
  .ai-panel, .ai-rr-bar { animation: none; }
  .ai-hero-aura { animation: none; }
}

/* ── Global bottom navigation (app sections) ─────────────── */
/* A floating, rounded, translucent glass panel detached from the screen edges
   (reference-inspired). Icon-over-label items; the active one glows orange with
   a small pill indicator. Stays in flow so it reserves its own height. */
.bottomnav {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-around;
  gap: 0.5rem;
  /* Flat, solid bar with a single top hairline — no glass blur, no drop shadow. */
  /* margin-top:auto keeps the bar glued to the bottom even before any view is
     un-hidden on first load — otherwise it's the only flex child of #app and
     floats up to the top during the initial loading frames. */
  margin: auto 0 0;
  /* Bottom padding clears the gesture bar: --sa-bottom is env() on iOS OUTSIDE
     Telegram and the client-reported inset in fullscreen, whichever is larger. */
  padding-top: 0.375rem;
  padding-bottom: calc(0.375rem + var(--sa-bottom));
  padding-left: 0.5rem;
  padding-right: 0.5rem;
  border-radius: var(--radius) var(--radius) 0 0;
  /* --bg-top so the nav matches the native Telegram bottom bar (@bot), which
     applyThemeChrome() paints the same #1e1e1e → the two read as one seamless strip. */
  background: var(--bg-top);
  border-top: 1px solid var(--border);
}

.nav-item {
  /* Four tabs now (Главная · Скринер · Отработка · Статистика) — share the width
     equally so they always fit on narrow phones instead of overflowing. */
  flex: 1 1 0;
  min-width: 0;
  position: relative;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4375rem;
  padding: 0.25rem 0.375rem 0.3125rem;
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-family: inherit;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.00625rem;
  cursor: pointer;
  transition: color 0.14s;
}
.nav-ico { width: 1.5rem; height: 1.5rem; display: block; stroke: currentColor; fill: none; transition: transform 0.18s cubic-bezier(0.16, 1, 0.3, 1); }
.nav-item span { line-height: 1; white-space: nowrap; }

.nav-item.active { color: var(--accent); font-weight: 700; }
/* Active tab reads at a glance: the icon lifts a hair and a solid accent pill sits under it. */
.nav-item.active .nav-ico { transform: translateY(-0.0625rem); }
.nav-item.active::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 1.375rem;
  height: 0.1875rem;
  border-radius: 62.4375rem;
  background: var(--accent);
  box-shadow: 0 0 8px rgba(255, 138, 0, 0.45);
}
.nav-item:active { color: var(--text); }

/* ── Feed TF-group tabs (Все / Быстрые / Среднесрок / Долгосрок) ── */
.feed-groups {
  flex: 0 0 auto;
  display: flex;
  gap: 0.375rem;
  padding: 0 1rem;
  overflow-x: auto;
  /* No visible scroll indicator — it read like a system scrollbar and cheapened the
     bar. The row still scrolls (touch/wheel) if the chips ever overflow. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.feed-groups::-webkit-scrollbar { display: none; }

/* When the chips share the topbar row with the strategy dropdown they take the
   remaining width and scroll horizontally (min-width:0 lets the flex child
   shrink so overflow-x works); the topbar already supplies side padding. */
.topbar .feed-groups { flex: 1 1 auto; min-width: 0; padding: 0; }

/* Strategy picker — a compact dropdown pinned at the left of the feed header row.
   Its menu opens left-aligned (the shared tf-dd default aligns right) as a single
   vertical column listing every strategy. */
/* Top-align the strategy pill with the chip row: the chips' scroll container
   reserves horizontal-scrollbar height at its bottom, making it taller than the
   pill, so center-alignment never lines them up cleanly. Anchoring both to the top
   puts the pill and the chips on the same line (the scrollbar sits below both). */
/* Scrollbar gone → the chips and the strategy pill are the same height, so centre-align
   them cleanly on one line. */
.feed-topbar { padding-top: 0.75rem; padding-bottom: 0.75rem; align-items: center; }

/* Filter controls (strategy pill + group chips): solid surfaces, no glass blur, equal
   height, roomier horizontal padding, tighter inter-button gap. */
.feed-topbar #feed-strategy-dd .tf-dd-btn,
.feed-groups .tf-btn {
  font-size: 0.75rem;
  padding: 0.5rem 0.9375rem;
  background: var(--surface-2);
  border-color: var(--border);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* Selected filter: a solid, confident orange fill (no gradient, no glow) — clearly the
   active tab without the neon shimmer. */
.feed-groups .tf-btn.active {
  color: var(--on-accent);
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: none;
}
/* Strategy picker menu — flat solid panel per the design system (no glass/blur/shadow),
   matching the «Статистика» picker: solid --surface, 1px hairline, one orange accent on
   the active row. Rows are a single readable column with roomy tap targets. */
#feed-strategy-dd .tf-dd-menu {
  left: 0;
  right: auto;
  grid-template-columns: 1fr;
  gap: 0.25rem;
  min-width: 11.5rem;
  padding: 0.375rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
#feed-strategy-dd .tf-dd-item {
  text-align: left;
  padding: 0.625rem 0.75rem;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-xs);
  transition: background 0.14s, color 0.14s;
}
/* Active row = the system's lit-switch: orange gradient + accent glow, exactly like an
   active chip. Only one row is lit at a time. */
#feed-strategy-dd .tf-dd-item.active {
  color: var(--on-accent);
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
  border-color: transparent;
  box-shadow: 0 3px 12px rgba(255, 138, 0, 0.28);
}
@media (hover: hover) {
  #feed-strategy-dd .tf-dd-item:not(.active):hover { background: var(--surface-2); }
}

/* ── Screener (home): coin strip + live chart ────────────── */
/* Top bar: a pinned search pill (always visible) + a horizontal, finger-
   scrollable coin strip (like the pair chips in binary-options apps). */
.screener-stripbar {
  flex: 0 0 auto;
  display: flex;
  align-items: stretch;
  gap: 0.5rem;
  padding: 0.625rem 1rem 0.375rem;
}

/* Search magnifier: a SQUARE that matches the coin-chip height (aspect-ratio 1 + stretch),
   not the old tall 38px-wide vertical bar. Sits level with the ticker chips. */
.coin-search-pill {
  flex: 0 0 auto;
  align-self: stretch;
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  border: 1px solid var(--border-soft);
  color: var(--muted);
  cursor: pointer;
  transition: border-color 0.14s, background 0.14s, color 0.14s, transform 0.08s;
}
.coin-search-pill:active { transform: scale(0.94); border-color: var(--accent); color: var(--accent); }
.search-ico { width: 1.125rem; height: 1.125rem; display: block; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; }

.screener-strip {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  /* No scroll-snap: it made wheel/drag scrolling stutter as it tugged chips to a
     stop. Free scrolling reads smoother. */
  scroll-behavior: smooth;
  /* Scrollbar hidden — the chips are obviously swipeable, and the visible bar read like a
     system scrollbar. Drag/swipe still works. */
  scrollbar-width: none;
}
.screener-strip::-webkit-scrollbar { display: none; }
/* Favourite star pinned to each strip chip's top-right corner. Tapping it flips
   the favourite state in place (handled by the chip's click handler). */
.coin-fav-btn {
  position: absolute;
  top: 0.125rem;
  right: 0.125rem;
  width: 1.25rem;
  height: 1.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  line-height: 1;
  color: var(--muted);
  transition: color 0.12s, transform 0.08s;
}
.coin-fav-btn:active { transform: scale(0.85); }
.coin-fav-btn.on { color: var(--accent); }
.strip-hint {
  display: flex;
  align-items: center;
  font-size: 0.75rem;
  color: var(--muted);
  padding: 0.375rem 0.25rem;
  white-space: nowrap;
}

.coin-chip {
  position: relative; /* anchor for the top-right favourite star */
  scroll-snap-align: start;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.125rem;
  min-width: 4.625rem;
  padding: 0.375rem 1.375rem 0.375rem 0.625rem; /* extra right room so the star clears the ticker */
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  border: 1px solid var(--border-soft);
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
  transition: background 0.14s, border-color 0.14s, transform 0.08s;
}
.coin-chip:active { transform: scale(0.96); }
.coin-chip.active {
  border-color: rgba(255, 138, 0, 0.5);
  background: rgba(255, 138, 0, 0.1);
}
.coin-sym { font-size: 0.75rem; font-weight: 800; color: var(--title); letter-spacing: 0.0125rem; }
/* tabular-nums = every digit the same width, so live price/percent updates don't
   reflow the chip (see fmtStripPrice, which also keeps the string length fixed). */
.coin-row { display: flex; align-items: baseline; gap: 0.375rem; font-variant-numeric: tabular-nums; }
.coin-px { font-size: 0.6875rem; font-weight: 600; color: var(--neutral); }
.coin-chg { font-size: 0.6875rem; font-weight: 700; color: var(--muted); }
.coin-chg.up { color: var(--up); }
.coin-chg.down { color: var(--down); }

.screener-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  /* Tightened top padding pulls this row up closer to the coin strip above (the tools
     buttons already give it height), so the header block reads as one tight stack. */
  padding: 0.25rem 1rem 0.5rem;
  border-bottom: 1px solid var(--border-soft);
}
/* Symbol + price take the space the tools leave; the symbol truncates first so the
   price (котировка) is never clipped or pushed onto the tool squares. */
.screener-sym {
  display: flex;
  align-items: baseline;
  gap: 0.4375rem;
  margin-right: auto;
  min-width: 0;
  flex: 0 1 auto;
  overflow: hidden;
  /* Small right nudge so the bold ticker optically lines up with the stats numbers in
     the row below (24Н · Объём …) instead of jutting out past them to the left. */
  padding-left: 0.25rem;
}
.screener-symbol {
  font-size: 1rem;
  font-weight: 800;
  color: var(--title);
  letter-spacing: 0.0125rem;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.screener-price { font-size: 0.875rem; font-weight: 700; color: var(--neutral); flex: 0 0 auto; white-space: nowrap; }
.screener-price.up { color: var(--up); }
.screener-price.down { color: var(--down); }

/* Timeframe pill on the screener header: solid to match the flat tool squares
   (the shared glass .tf-dd-btn is only for the detail view). */
.screener-head .tf-dd-btn {
  font-size: 0.75rem;
  padding: 0.4375rem 0.75rem;
  background: var(--surface-2);
  border-color: var(--border-soft);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.screener-head .tf-dd.open .tf-dd-btn { border-color: var(--accent); }

.screener-feed-btn {
  flex: 0 0 auto;
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 700;
  padding: 0.4375rem 0.75rem;
  border-radius: 62.4375rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--accent);
  cursor: pointer;
  transition: background 0.14s, border-color 0.14s;
}
.screener-feed-btn:active { border-color: var(--accent); background: rgba(255, 138, 0, 0.14); }

/* Chart tool buttons (ruler / fit) grouped next to the timeframe selector. */
.tool-btn {
  flex: 0 0 auto;
  width: 1.875rem;
  height: 1.875rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.875rem;
  line-height: 1;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  border: 1px solid var(--border-soft);
  color: var(--muted);
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, color 0.12s, transform 0.08s;
}
.tool-btn:active { transform: scale(0.94); }
.tool-btn.active {
  color: var(--on-accent);
  border-color: transparent;
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
}

/* `touch-action: none` belongs on the base rule, not on .editing — see the comment on
   `.chart` for the measurement. Latched at touchdown, so a gesture-time class is too late
   and the browser cancels the drag mid-flight. */
.screener-chart { flex: 1; min-height: 0; position: relative; touch-action: none; }
.screener-chart a { display: none !important; }
/* While dragging a drawn line: grab cursor. */
.screener-chart.editing { cursor: grabbing; }

/* User-drawing overlay: trendlines projected in pixel space so their slope is exactly
   what the user drew (re-projected on every chart move). pointer-events:none so the
   chart still handles pan/zoom; we hit-test the shapes ourselves. */
.draw-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 3;
  pointer-events: none;
  overflow: hidden;
}
.draw-line { stroke: var(--accent); stroke-width: 2; stroke-linecap: round; }
.draw-rect { fill: rgba(255, 138, 0, 0.10); stroke: var(--accent); stroke-width: 1.5; }
/* Endpoint handles for the selected shape (TradingView-style): white dot with an
   accent ring. Purely visual — hit-testing is done in JS on pixel coords. */
.draw-handle { fill: #fff; stroke: var(--accent); stroke-width: 2; }

/* Volume-profile overlay: horizontal volume-by-price bars, below the drawings
   overlay so user shapes sit on top. pointer-events:none — non-interactive. */
.vp-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
  pointer-events: none;
  overflow: hidden;
}
.vp-bar { fill: rgba(120, 144, 180, 0.28); }
.vp-bar.poc { fill: rgba(255, 138, 0, 0.42); }

/* Side readout panels — plain-language plates that replace the on-chart indicator
   curves so beginners read a verdict, not a plot. Stacked top-left in .chart-panels
   (clear of the right price axis and bottom time axis), non-interactive so they never
   steal a pan/draw gesture. Two of them: «Поток ордеров» (CVD) and «Индикаторы».
   Each is shown only while its source is on (.show is toggled in JS). */
.chart-panels {
  position: absolute;
  left: 8px;
  top: 8px;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: 66%;
  pointer-events: none;
}
.of-panel {
  min-width: 118px;
  padding: 6px 9px 7px;
  border-radius: var(--radius-2xs);
  background: rgba(18, 18, 18, 0.82);
  border: 1px solid var(--border);
  font-variant-numeric: tabular-nums;
  display: none;
}
.of-panel.show { display: block; }
.of-panel-title {
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 5px;
}
.of-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  line-height: 1.55;
}
.of-key { font-size: 10px; font-weight: 600; color: var(--muted); }
.of-val { font-size: 11px; font-weight: 700; white-space: nowrap; }
.of-val.pos { color: var(--up); }
.of-val.neg { color: var(--down); }
.of-val.neu { color: var(--title); }
/* The combined verdict row — set apart from the raw metric rows: it's the «куда лучше»
   headline, so it reads a touch bigger with a divider under it. */
.of-row.of-signal {
  margin-bottom: 3px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--border);
}
.of-row.of-signal .of-key { color: var(--title); }
.of-row.of-signal .of-val { font-size: 12px; }

/* Центрировать — small labelled control tucked into the chart corner, clear of the
   right price axis and the bottom time axis so it never covers quotes or candles. */
.chart-fit-corner {
  position: absolute;
  left: 10px;      /* bottom-left: fully clear of the right price axis (the quotes) */
  bottom: 38px;    /* clear of the bottom time axis */
  z-index: 5;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 3px;
  padding: 2px 5px;
  border-radius: var(--radius-2xs);
  background: rgba(28, 28, 28, 0.7);
  border: 1px solid var(--border);
  color: var(--muted);
  opacity: 0.7;
  cursor: pointer;
  touch-action: manipulation;
  transition: opacity 0.12s, color 0.12s, border-color 0.12s;
}
.chart-fit-corner svg {
  width: 10px;
  height: 10px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.chart-fit-corner .fit-label {
  font-size: 7px;
  font-weight: 700;
  letter-spacing: 0.01em;
  line-height: 1;
  white-space: nowrap;
}
.chart-fit-corner:active { opacity: 1; color: var(--title); border-color: var(--accent); background: rgba(255, 138, 0, 0.16); }

/* Header chart tools: standalone square buttons (Линейка · Магнит) + a pencil
   dropdown for the drawing tools. All share .tool-btn; the pencil menu opens like
   the timeframe selector — a vertical list of icon+label rows. */
.screener-tools { display: flex; align-items: center; gap: 0.3125rem; flex: 0 0 auto; }
.tools-dd { position: relative; flex: 0 0 auto; }
.tool-ico { width: 1.125rem; height: 1.125rem; display: block; stroke: currentColor; fill: none; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.tool-ico circle { fill: currentColor; stroke: none; }
/* Pencil button glows on open; the shared .tool-btn.active handles the armed state. */
.tools-dd.open .tool-btn { border-color: var(--accent); color: var(--accent); }
.tools-menu {
  display: none; /* opened by toggling .tools-dd.open */
  position: absolute;
  right: 0;
  top: calc(100% + 0.4375rem);
  z-index: 30;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.4375rem;
  min-width: 13.25rem;
  max-height: 72vh;
  overflow-y: auto;
  background: var(--glass-strong);
  border: 1px solid var(--border-glass);
  border-radius: var(--radius);
  box-shadow: var(--shadow-soft);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}
.tools-dd.open .tools-menu { display: flex; }
/* The chart-type menu (Свечи / Линия) is the leftmost right-aligned tool, so the
   full 212px width opening leftward runs off the left edge of the screen. Its two
   labels are short — shrink it so it stays on-screen. */
#screener-charttype-dd .tools-menu,
#detail-charttype-dd .tools-menu { min-width: 8.25rem; }
.tools-item {
  display: flex;
  align-items: center;
  gap: 0.6875rem;
  width: 100%;
  font-family: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  padding: 0.625rem 0.75rem;
  border-radius: var(--radius-xs);
  background: transparent;
  border: 1px solid transparent;
  color: var(--text);
  cursor: pointer;
  text-align: left;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.tools-item:active { background: var(--glass); }
.tools-item.active { color: var(--accent); background: rgba(255, 138, 0, 0.10); border-color: rgba(255, 138, 0, 0.30); }
.tools-ico { width: 1.125rem; height: 1.125rem; flex: 0 0 auto; display: block; stroke: currentColor; fill: none; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.tools-ico circle { fill: currentColor; stroke: none; }
/* «?» help badge at the far right of each indicator row — a small round chip that opens
   the plain-language card. margin-left:auto pins it right of the label. */
.ind-help {
  margin-left: auto;
  flex: 0 0 auto;
  width: 1.125rem;
  height: 1.125rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1;
  color: var(--muted);
  background: var(--glass);
  border: 1px solid var(--border-glass);
  border-radius: 50%;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.ind-help:active { color: var(--accent); border-color: var(--accent); background: rgba(255, 138, 0, 0.14); }

/* Indicator help card — a centered modal opened by the «?» badge. Explains an indicator
   in plain words (same voice as the on-chart plates). Above everything, dismissed by the
   backdrop or the «Понятно» button. */
.ind-help-modal {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(0, 0, 0, 0.6);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  align-items: center;
  justify-content: center;
  padding: 1.25rem;
}
.ind-help-modal.show { display: flex; }
.ind-help-card {
  width: 100%;
  max-width: 20rem;
  max-height: 80vh;
  overflow-y: auto;
  padding: 1.125rem 1.25rem 1rem;
  background: var(--glass-strong);
  border: 1px solid var(--border-glass);
  border-radius: var(--radius);
  box-shadow: var(--shadow-soft);
}
.ind-help-title {
  font-size: 1rem;
  font-weight: 800;
  color: var(--title);
  margin-bottom: 0.5rem;
}
.ind-help-body { font-size: 0.8125rem; line-height: 1.5; color: var(--text); }
.ind-help-body p { margin: 0 0 0.4rem; }
.ind-help-body p:last-child { margin-bottom: 0; }
.ind-help-body p.sp { height: 0.35rem; margin: 0; }
.ind-help-close {
  margin-top: 0.9rem;
  width: 100%;
  padding: 0.625rem;
  font-family: inherit;
  font-size: 0.8125rem;
  font-weight: 700;
  color: #141414;
  background: var(--accent);
  border: none;
  border-radius: var(--radius-xs);
  cursor: pointer;
}
.ind-help-close:active { opacity: 0.85; }

/* Market-stats readout — a slim row ABOVE the chart (no longer floating over the
   candles): 24h change · 24h volume · NATR, divider-spaced, tabular digits. */
.screener-stats {
  flex: 0 0 auto;
  display: flex;
  flex-direction: row;
  align-items: center;
  /* Spread the 4 readouts (24ч · Объём · NATR · OI) evenly across the width so the
     plate reads as an aligned row rather than a left-huddled cluster. */
  justify-content: space-between;
  gap: 0.375rem;
  padding: 0.5rem 1rem;
  border-bottom: 1px solid var(--border-soft);
  font-variant-numeric: tabular-nums;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.screener-stats::-webkit-scrollbar { display: none; }
.screener-stats:empty { display: none; }
.sstat {
  display: flex;
  align-items: baseline;
  gap: 0.3125rem;
  white-space: nowrap;
}
.sstat-l {
  color: var(--stat-header);
  font-size: 0.5625rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.sstat-v {
  font-weight: 700;
  font-size: 0.75rem;
  color: var(--title);
  font-variant-numeric: tabular-nums;
}
.sstat-v.up { color: var(--up); }
.sstat-v.down { color: var(--down); }
/* Значение, которое ещё ДОПИСЫВАЕТСЯ (свежий импульс: 24h/объём/OI приезжают через
   несколько секунд после алерта — см. statsPending). Прочерк на этом месте значил бы
   «данных нет», а они будут. Полоска, а не слово: в колонку шириной под «+12.34%» слово
   «загружается» не влезает, а четыре колонки обязаны остаться на одной строке.
   Ширина взята по самому длинному живому значению этой строки («$123.4M»), чтобы строка не
   дёргалась, когда цифра приедет. */
.sstat-v.is-loading {
  display: inline-block;
  width: 2.375rem;
  height: 0.6875rem;
  border-radius: 62.4375rem;
  background: linear-gradient(
    90deg, var(--surface-2) 25%, var(--border) 50%, var(--surface-2) 75%
  );
  background-size: 200% 100%;
  animation: sstat-shimmer 1.4s linear infinite;
}
@keyframes sstat-shimmer {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}

.screener-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 8;
  color: var(--muted);
  font-size: 0.8125rem;
}

/* Home (‹) button sits at the far left of the feed top bar. */
.topbar-home { font-size: 1.625rem; padding-bottom: 0.1875rem; }

/* ── Screener search / filter panel (overlay) ────────────── */
.search-panel {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  /* position:fixed puts this OUTSIDE #app, so it inherits none of #app's safe-area
     padding and has to inset itself — otherwise the search field lands under the
     status bar and Telegram's ✕/⋮ in fullscreen. */
  padding-top: var(--pad-top);
  padding-bottom: var(--sa-bottom);
  padding-left: var(--sa-left);
  padding-right: var(--sa-right);
}

.search-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.875rem 1rem 0.625rem;
}
.search-input {
  flex: 1 1 auto;
  min-width: 0;
  font-family: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  padding: 0.6875rem 0.875rem 0.6875rem 2.5rem;
  border-radius: var(--radius-sm);
  background: var(--surface) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%238a8a8a' stroke-width='2' stroke-linecap='round'><circle cx='7' cy='7' r='5'/><line x1='10.5' y1='10.5' x2='15' y2='15'/></svg>") no-repeat 14px center;
  background-size: 16px 16px;
  border: 1px solid var(--border);
  color: var(--title);
  outline: none;
  transition: border-color 0.14s;
}
.search-input::placeholder { color: var(--muted); font-weight: 500; }
.search-input:focus { border-color: var(--accent); }
.search-close {
  flex: 0 0 auto;
  width: 2.375rem;
  height: 2.375rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  cursor: pointer;
}
.search-close:active { background: #303030; }

/* NB: the «Добавить на главный экран» pill that used to sit between the search field and
   the sort chips is gone (markup + JS removed too). Search is a find-a-coin surface. */

.search-sorts {
  flex: 0 0 auto;
  display: flex;
  gap: 0.375rem;
  padding: 0.125rem 1rem 0.75rem;
}
.sort-chip {
  /* All four filters share the row equally so they fit in one line — no scrolling. */
  flex: 1 1 0;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.3125rem;
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 600;
  padding: 0.5rem 0.375rem;
  border-radius: 62.4375rem;
  background: var(--surface-2);
  border: 1px solid var(--border-soft);
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.14s, color 0.14s, border-color 0.14s;
}
.sort-lbl {
  min-width: 0;
  line-height: 1;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sort-chip:active { transform: scale(0.96); }
/* Icon carries a tone tint (green up / red down / amber star) so the row is
   scannable even before you read the labels. */
.sort-ico {
  flex: 0 0 auto;
  display: inline-flex;
  width: 0.875rem;
  height: 0.875rem;
  color: var(--muted);
}
.sort-ico svg {
  width: 0.875rem;
  height: 0.875rem;
  display: block;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.sort-ico.up { color: var(--up); }
.sort-ico.down { color: var(--down); }
.sort-ico.star { color: var(--accent-2); }
.sort-ico.star svg { fill: currentColor; stroke: none; }
.sort-chip.active {
  color: var(--on-accent);
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: none;
}
/* On the active (orange) chip the tinted icon would clash — flip it to white. */
.sort-chip.active .sort-ico { color: var(--on-accent); }

.search-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 0 0.375rem 0.75rem;
}
.search-list::-webkit-scrollbar { display: none; }

.srow {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.6875rem 0.5rem;
  border-bottom: 1px solid var(--border-soft);
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition: background 0.1s;
}
.srow:active { background: var(--surface); }
.srow.active { background: rgba(255, 138, 0, 0.08); }

.srow-star {
  flex: 0 0 auto;
  width: 1.875rem;
  height: 1.875rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  line-height: 1;
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
}
.srow-star.on { color: var(--accent); }

/* Exchange-style watchlist row: ticker + venue/volume subtitle on the left,
   price over coloured %change stacked on the right. */
.srow-meta {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1875rem;
}
.srow-line1 { display: flex; align-items: center; gap: 0.4375rem; min-width: 0; }
.srow-line2 { display: flex; align-items: center; gap: 0.4375rem; min-height: 0.9375rem; }
.srow-sym { font-size: 0.9375rem; font-weight: 800; color: var(--title); letter-spacing: 0.0125rem; }
.srow-vol { font-size: 0.75rem; font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; }
.srow-right {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.125rem;
  text-align: right;
}
.srow-px { font-size: 0.875rem; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.srow-chg { font-size: 0.8125rem; font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums; }
.srow-chg.up { color: var(--up); }
.srow-chg.down { color: var(--down); }
/* Mini exchange logos under the ticker: which venues list this coin. */
.srow-ex { display: flex; align-items: center; gap: 0.3125rem; min-height: 0.9375rem; }
.ex-badge {
  display: inline-flex;
  width: 0.9375rem;
  height: 0.9375rem;
  line-height: 0;
  border-radius: 0.25rem;
  overflow: hidden;
}
.ex-badge svg { display: block; width: 0.9375rem; height: 0.9375rem; }
/* Bybit shows its full wordmark → a wider pill than the square venue tiles. */
.ex-badge--bybit { width: auto; }
.ex-badge--bybit svg { width: 2.375rem; height: 0.9375rem; }

.search-empty {
  flex: 0 0 auto;
  text-align: center;
  color: var(--muted);
  font-size: 0.8125rem;
  padding: 1.875rem 1rem;
}

/* ── Access denial screen (closed-channel gate) ──────────────────────────────
   Covers the whole app when the server refuses a session: the shell may already
   have painted, and a half-rendered screener behind a refusal reads as a bug.
   One card, one action — the same visual language as the rest of the app. */
.access-screen {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: linear-gradient(180deg, var(--bg-top) 0%, var(--bg) 38%);
}
.access-screen.hidden { display: none; }
.access-card {
  width: 100%;
  max-width: 22rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.75rem 1.25rem 1.5rem;
  text-align: center;
}
.access-icon { font-size: 2rem; line-height: 1; margin-bottom: 0.875rem; }
.access-title {
  color: var(--title);
  font-size: 1.0625rem;
  font-weight: 700;
  margin-bottom: 0.5rem;
}
.access-text {
  color: var(--muted);
  font-size: 0.875rem;
  line-height: 1.45;
}
.access-btn {
  display: block;
  width: 100%;
  margin-top: 1.25rem;
  padding: 0.8125rem 1rem;
  border: 0;
  border-radius: var(--radius);
  background: linear-gradient(180deg, var(--accent-2) 0%, var(--accent) 100%);
  color: var(--on-accent);
  font-size: 0.9375rem;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
}
.access-btn:active { background: var(--accent-press); }
.access-btn:disabled { opacity: 0.6; }

/* Terms splash — the access card with a readable list instead of a paragraph.
   Left-aligned on purpose: the centred access copy is two lines, this is four
   bullets, and centred multi-line text gives every line a different left edge
   for the eye to find. */
.terms-card { text-align: left; }
.terms-card .access-icon,
.terms-card .access-title { text-align: center; }
.terms-list {
  margin: 0.875rem 0 0;
  padding-left: 1.125rem;
  color: var(--muted);
  font-size: 0.875rem;
  line-height: 1.45;
}
.terms-list li { margin-bottom: 0.5rem; }
.terms-link {
  display: block;
  margin-top: 0.875rem;
  color: var(--accent);
  font-size: 0.875rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}
.terms-note {
  margin-top: 0.75rem;
  color: var(--muted);
  font-size: 0.75rem;
  line-height: 1.4;
  text-align: center;
}
