/* ========================================
   Tradeiz - Main Stylesheet
   ======================================== */

/* ========== Web Fonts ========== */

/* ========== CSS Variables (Light Theme) ========== */
:root {
    --bg-primary: #ffffff;
    --bg-secondary: #ffffff;
    --bg-tertiary: #f5f5f5;
    /* [FIX] 2026-05-18 — `--bg-quaternary` was referenced by several
     * AI panel hover states (.ai-icon-btn:hover, .ai-btn:hover, table th,
     * code-block toolbar :hover) without ever being defined. The CSS
     * fallback `#2a2e39` (dark slate) was therefore applied universally,
     * including under the light theme, causing dark chips with black text
     * — see the `.ai-icon-btn` block farther down for the full diagnosis.
     * Define it explicitly here as one notch darker than --bg-tertiary
     * so :hover surfaces stand out on the f5f5f5 chrome. */
    --bg-quaternary: #e0e3eb;
    --bg-input: #f5f5f5;
    --bg-hover: rgba(0, 0, 0, 0.05);
    --text-primary: #181818;
    --text-secondary: #666666;
    --icon-color: var(--text-primary);
    --border-color: #f5f5f5;
    --border-primary: var(--border-color);
    --border-soft: var(--border-color);
    /* [ADD] 2026-05-24 r33 — dedicated resizer (split-bar) color token.
     * Default in light theme inherits the panel-border grey so the bar
     * blends with adjacent panel chrome (white background = light-grey
     * bar). Dark theme overrides to #1a1a1a (one notch lighter than the
     * pure-black panel background) so the bar reads as a deliberate
     * divider rather than disappearing into the surrounding void.
     * Used by .resizer / .resizer-vertical / .bottom-resizer /
     * .yes-console-resizer (all chart-page split bars). */
    --resizer-color: #f5f5f5;
    --price-up: #00C896;
    --price-down: #FF4D6A;
    /* [ADD] 2026-05-24 r32 — compatibility aliases used by Symbol Insights.
     * The chart stylesheet historically uses --text-primary/--price-up/down.
     * Recent SI widgets reused web_app landing tokens (--tt-fg-0/up/down),
     * but those were undefined on chart pages, causing decorative layers
     * (gauge bars / ticks / split bars) to resolve as invalid and vanish.
     * Keep aliases here so both token families are always available. */
    --tt-fg-0: var(--text-primary);
    --tt-up: var(--price-up);
    --tt-down: var(--price-down);
    /* Scripts row hover outline (light theme): thin dark hairline that is
     * clearly visible on white background without looking heavy. */
    --sc-row-hover-outline: rgba(0, 0, 0, 0.18);
    --accent-color: #2962ff;
    --state-active: #2962ff;
    --text-muted: #555555;
    --font-price: -apple-system, BlinkMacSystemFont, "Trebuchet MS", Roboto, Ubuntu, sans-serif;
    --toolbar-btn-size: 42px;
    /* Chart icon tokens — change here only; do not inline SVG width/height. */
    --chart-icon-size: 24px;
    --chart-icon-stroke: 0.8;
    --chart-screenshot-icon-size: 26px;
    --chart-type-shortcut-icon-size: 18px;
    --chart-toolbar-icon-size: 32px;
    --chart-toolbar-icon-stroke: 0.6;
    --chart-icon-chevron-width: 10px;
    --chart-icon-chevron-height: 6px;
    /* Legacy aliases (prefer --chart-* names in new code) */
    --toolbar-icon-size: var(--chart-toolbar-icon-size);
    --toolbar-icon-stroke: var(--chart-toolbar-icon-stroke);
    --topbar-icon-size: var(--chart-toolbar-icon-size);
    /* [ADD] 2026-05-20 — Popover/Dropdown/Dialog elevation tokens.
     * Design intent (Bloomberg Terminal / TradingView Pro / Apple HIG):
     * elevated floating surfaces (dropdown, popover, confirm dialog,
     * context menu, modal, tooltip) keep the SAME background colour as
     * the page surface and gain "lift" purely from a 1px outline +
     * outer drop shadow. Painting the panel a different shade of grey
     * on top of pure-black/pure-white looks out-of-spec on professional
     * trading UIs — the reference dark themes (Bloomberg, TradingView
     * Pro, Linear) all retain the deep black panel and rely on shadow.
     *
     * Light theme:
     *   --popover-bg #ffffff matches the page surface.
     *   --popover-border #d6dae3 — soft cool-grey 1px outline.
     *   --popover-shadow — Material 3 elevation-3 style soft outer glow
     *     that visibly lifts the panel off a white background. */
    --chart-popup-bg: #ffffff;
    --chart-popup-border: #d6dae3;
    --drawer-head-bg: var(--chart-popup-bg);
    --popover-bg: var(--chart-popup-bg);
    --popover-border: var(--chart-popup-border);
    --popover-shadow: 0 12px 32px rgba(0, 0, 0, 0.18), 0 2px 6px rgba(0, 0, 0, 0.08);
    /* [ADD] 2026-05-22 — Dedicated separator colour for chart left/right
     * toolbar inner dividers + top-bar vertical dividers. Cannot reuse
     * --border-color (#f5f5f5 / #0a0a0a) because that variable is
     * INTENTIONALLY near-invisible to keep chart panels "frameless"
     * (see the dark-theme L104 comment) — but the same colour applied
     * to toolbar separators made them disappear entirely, which users
     * perceive as "missing dividers" (2026-05-22 UX report).
     *
     * SCOPE: Only `.toolbar-divider` (horizontal, inside left/right
     * toolbars), `.toolbar-divider-v` (vertical, top-bar) and
     * `.yes-toolbar-sep` (YeScript editor). The OUTER edges of the
     * left/right toolbars (border-right/border-left) intentionally
     * stay on --border-color so they keep the project's signature
     * frameless aesthetic at the page-level boundary (user explicitly
     * asked to keep those two unchanged on 2026-05-22 r2).
     *
     *   Light: #d6dae3 — same as --popover-border. Contrast 1.4:1 vs
     *     #fff; a soft hairline that is perceptible but doesn't read
     *     as a heavy shelf. Initial r1 value (#b8bdc8) was reported by
     *     the user as "线条过于清楚 要暗一些" — dialed back here.
     *   Dark:  #2a2e39 — contrast 1.4:1 vs #000. Same visual weight
     *     as the light theme; sits well below --popover-border
     *     (#3a3f4d) which is reserved for actual interactive outlines.
     *   Reference: TradingView/Bloomberg toolbar dividers run ~1.3-1.5:1
     *     to stay decorative without competing with button icons. */
    --toolbar-sep: #d6dae3;
}

/* ========== Dark Theme ==========
 * [UPDATE] 2026-05-11 — pure black background per user request.
 * Was #141414 (a near-black grey); changed to #000 to align with the
 * site-wide --tt-bg-0 update and to maximise contrast on OLED panels.
 * Reason: Chart page is a primary trading surface; pure black gives
 * the cleanest separation between data ink (candles / orderbook /
 * heatmap) and chrome surfaces, matches Bloomberg / TradingView Pro
 * dark themes, and removes the dark-on-dark "shelf" gap between the
 * site nav (now #000) and the chart container.
 * Border/separator colours also darkened to #0a0a0a (see --border-color
 * below) so chart panel separators stay subtle on the new #000 surface
 * instead of reading as bright grey lines. */
body.dark-theme {
    --bg-primary: #000000;
    --bg-secondary: #000000;
    --bg-tertiary: #000000;
    /* [FIX] 2026-05-18 — matching dark-theme definition of --bg-quaternary
     * (see the :root block above). One notch lighter than the pure-black
     * surfaces so :hover states read as a subtle highlight rather than a
     * dark-on-dark hole. */
    --bg-quaternary: #1c1f2a;
    --bg-input: #000000;
    --bg-hover: rgba(255, 255, 255, 0.1);
    --text-primary: #d0d0d0;
    --text-secondary: #999999;
    --icon-color: var(--text-primary);
    /* [UPDATE 2026-05-25] dark-theme unified divider baseline.
     * User requested all dark-theme split/border/menu/modal lines to use
     * the chart module separator color #1a1a1a for consistent contrast. */
    --border-color: #1a1a1a;
    /* [ADD] 2026-05-24 r33 — dark-theme resizer (split-bar) override.
     * #1a1a1a is one notch lighter than the panel border (#0a0a0a) and
     * the pure-black panel background (#000), so the 5px split bar reads
     * as a deliberate, grabbable divider on dark theme without looking
     * like a heavy black bar. Applied to .resizer / .resizer-vertical /
     * .bottom-resizer / .yes-console-resizer. */
    --resizer-color: #1a1a1a;
    --price-up: #00C896;
    --price-down: #FF4D6A;
    --tt-fg-0: var(--text-primary);
    --tt-up: var(--price-up);
    --tt-down: var(--price-down);
    /* Scripts row hover outline (dark theme): thin light hairline so rows
     * get an explicit interaction state on pure-black surfaces. */
    --sc-row-hover-outline: rgba(255, 255, 255, 0.14);
    --accent-color: #2962ff;
    --state-active: #2962ff;
    --text-muted: #aaaaaa;
    --font-price: -apple-system, BlinkMacSystemFont, "Trebuchet MS", Roboto, Ubuntu, sans-serif;
    /* [ADD] 2026-05-20 — Dark-theme popover elevation tokens.
     * Design intent (Bloomberg Terminal / TradingView Pro / Apple HIG
     * dark mode): keep the popover background PURE BLACK to match the
     * page surface, and create separation purely via a 1px light-grey
     * outline + a soft outer glow shadow. Lighter-grey panels on top
     * of pure-black look "muddy" and read as out-of-spec Material
     * surfaces; pro trading UIs always preserve the deep black surface
     * and lift via border + shadow only.
     * --popover-bg #000000: same as page surface, no shift in colour
     *   temperature, preserves OLED contrast on the chart canvas.
     * --popover-border #2b2b2b: menu/modal border token in dark theme.
     * --popover-shadow uses heavy rgba(0,0,0,.7) + a small dark glow
     *   ring. Even on a #000 backdrop the alpha .7 darkens the
     *   surrounding chart pixels enough to read as a real drop-shadow
     *   halo, giving the elevated surface "lift" without changing its
     *   colour. */
    --chart-popup-bg: #0d0d0d;
    --chart-popup-border: #2b2b2b;
    --drawer-head-bg: #181818;
    --popover-bg: var(--chart-popup-bg);
    --popover-border: var(--chart-popup-border);
    --popover-shadow: 0 12px 32px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(0, 0, 0, 0.4);
    /* [UPDATE] 2026-05-25 — unified with dark-theme module separator
     * per user request: menu/tool dividers should match #1a1a1a. */
    --toolbar-sep: #1a1a1a;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, "Trebuchet MS", Roboto, Ubuntu, sans-serif;
    background: var(--bg-primary);
    color: var(--text-primary);
    height: 100vh;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    transition: background 0.3s, color 0.3s;
}

/* ========== Top Bar ========== */
/* FIX: Top bar background color matches left/right toolbars for consistency */
/* [CHG] 2026-05-24 r35 — bottom edge unified with the chart split bars.
 * Was 4px solid --border-color (light theme #f5f5f5 / dark theme #0a0a0a);
 * now 5px solid --resizer-color so the top-bar separator, the .resizer
 * splits, and the .left-toolbar right edge all read as the SAME divider
 * (5px, dark theme #1a1a1a / light theme #f5f5f5). */
.top-bar {
    height: 40px;
    background: var(--bg-secondary);
    border-bottom: 5px solid var(--resizer-color);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1px 0;
    flex-shrink: 0;
    min-width: 0;
    overflow: visible;
}

/* Top bar utility buttons — same 42px hit target as #menuBtn. */
.top-bar .toolbar-btn {
    width: var(--toolbar-btn-size);
    height: var(--toolbar-btn-size);
}

.top-bar .toolbar-btn svg {
    width: var(--chart-toolbar-icon-size);
    height: var(--chart-toolbar-icon-size);
}

.top-bar #screenshotBtn svg {
    width: var(--chart-toolbar-icon-size);
    height: var(--chart-toolbar-icon-size);
}

/* Left menu button with dropdown (opens to right) */
.chart-menu-wrap {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    width: 45px;
    height: 100%;
    flex-shrink: 0;
    padding-left: 3px;
    padding-right: 2px;
    box-sizing: border-box;
}
.chart-menu-dropdown {
    display: none;
    position: absolute;
    top: 0;
    left: 100%;
    margin-left: 6px;
    min-width: 280px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 10px;
    box-shadow: var(--popover-shadow);
    list-style: none;
    padding: 6px 0;
    z-index: 9999;
}
.chart-menu-wrap.open .chart-menu-dropdown { display: block; }
.chart-menu-wrap::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    width: 10px;
    transform: translateX(100%);
}

/* Submenu parent with arrow */
.nud-submenu-parent {
    position: relative;
}
.nud-submenu-parent > a {
    display: flex !important;
    align-items: center;
    justify-content: space-between;
}
.nud-submenu-parent .submenu-arrow {
    width: 14px;
    height: 14px;
    margin-left: auto;
    flex-shrink: 0;
}

/* Submenu (language list) */
.nud-submenu {
    display: none;
    position: absolute;
    top: 0;
    left: 100%;
    margin-left: 0;
    min-width: 240px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 10px;
    box-shadow: var(--popover-shadow);
    list-style: none;
    padding: 6px 0;
    z-index: 10000;
}
.nud-submenu-parent:hover > .nud-submenu { display: block; }
/* [FIX] 2026-07-23 — .open must show language submenu on all devices.
   Reason: @media (hover:none) disables hover-open; JS click toggle only ran
     on isTouchPrimary, leaving Electron/desktop-shell in a dead zone.
   Warning: keep in sync with bindTouchSubmenus() gate in tt.app.js. */
.nud-submenu-parent.open > .nud-submenu { display: block; }
.nud-submenu-parent::after {
    content: '';
    position: absolute;
    top: -8px;
    bottom: -8px;
    right: 0;
    width: 14px;
    transform: translateX(100%);
}
.nud-submenu .lang-option a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 16px;
    color: var(--text-primary);
    text-decoration: none;
    font-size: 14px;
    transition: background 0.15s, color 0.15s;
}
.nud-submenu .lang-option .lang-name {
    flex: 1;
    min-width: 0;
}
.nud-submenu .lang-option .lang-check {
    flex-shrink: 0;
    font-size: 13px;
    line-height: 1;
    color: var(--text-primary);
    display: none;
}
.nud-submenu .lang-option a:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
}
.nud-submenu .lang-option.active a {
    color: var(--accent-color, #2962ff);
    background: var(--bg-hover);
    font-weight: 600;
}
.nud-submenu .lang-option.active .lang-check {
    display: inline;
    color: var(--text-primary);
}

.top-bar-left {
    height: 100%;
    display: flex;
    align-items: center;
    gap: 0;
    padding-left: 0;
    position: relative;
    flex: 1;
    min-width: 0;
    overflow: visible;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.top-bar-left::-webkit-scrollbar {
    display: none;
}

.tt-price-reminders-float {
    position: fixed;
    inset: 0;
    z-index: 1200;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    pointer-events: none;
    background: transparent;
}

.tt-price-reminder-popover {
    display: none;
}

.tt-price-reminders-panel {
    width: min(440px, 100%);
    max-height: min(620px, 90vh);
    overflow: auto;
    padding: 16px;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    background: var(--bg-primary);
    color: var(--text-primary);
    box-shadow: var(--popover-shadow, 0 16px 48px rgba(0, 0, 0, 0.35));
}

.tt-price-reminders-stack {
    width: min(520px, calc(100vw - 32px));
    max-height: min(78vh, 720px);
    padding: 16px 20px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    pointer-events: auto;
    border: 1px solid var(--popover-border);
    border-radius: 12px;
    background: var(--bg-primary);
    box-shadow: var(--popover-shadow, 0 16px 48px rgba(0, 0, 0, 0.35));
}

.tt-price-reminders-stack-body {
    display: flex;
    flex-direction: column;
    flex: 0 0 auto;
    gap: 0;
}

.tt-price-reminders-section-heading {
    margin: 0 0 12px;
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
}

.tt-price-reminders-form-section {
    flex-shrink: 0;
    padding-bottom: 16px;
    border-bottom: 1px solid var(--border-color);
}

.tt-price-reminders-form-heading-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 12px;
}

.tt-price-reminders-form-heading-row .tt-price-reminders-section-heading {
    margin: 0;
    flex: 1;
    min-width: 0;
}

.tt-price-reminders-back {
    flex-shrink: 0;
    margin-left: auto;
}

.tt-price-reminders-back.is-hidden {
    display: none !important;
}

.tt-price-reminder-field-row--triple {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.tt-price-reminder-field--price {
    flex: 1.4 1 140px;
    min-width: 100px;
}

.tt-price-reminder-field--cond,
.tt-price-reminder-field--freq {
    flex: 1 1 90px;
    min-width: 80px;
}

.tt-price-reminders-compound {
    display: grid;
    gap: 10px;
    margin-top: 12px;
}

.tt-price-reminders-compound.is-hidden {
    display: none;
}

.tt-price-reminders-compound > span {
    font-size: 12px;
    color: var(--text-secondary);
}

.tt-price-reminders-compound .tt-price-reminder-icon-btn {
    width: auto;
    padding: 0 10px;
    gap: 6px;
}

.tt-price-reminders-edit-actions {
    display: block;
}

.tt-price-reminders-save-btn {
    width: 100%;
}

.tt-price-reminders-panel button.ca-toggle {
    min-width: 36px;
    width: 36px;
    height: 20px;
    min-height: 20px;
    padding: 0;
    border-radius: 10px;
    border: 1px solid var(--text-secondary);
    background: var(--bg-quaternary);
}

.tt-price-reminders-panel button.ca-toggle.active {
    background: var(--accent-color);
    border-color: var(--accent-color);
}

.tt-price-reminder-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 1px solid var(--border-color);
    border-radius: 4px;
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
    min-height: 32px;
}

.tt-price-reminder-icon-btn svg {
    width: 16px;
    height: 16px;
}

.tt-price-reminder-icon-btn--danger {
    color: #ef5350;
    border-color: rgba(239, 83, 80, 0.45);
}

.tt-price-reminder-delete-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 32px;
    min-height: 32px;
    padding: 8px;
    border: 0;
    border-radius: 0;
    background: transparent;
    color: #ef5350;
    cursor: pointer;
}

.tt-price-reminder-delete-btn svg {
    width: 16px;
    height: 16px;
}

.tt-price-reminders-panel button.tt-price-reminder-delete-btn {
    min-height: 32px;
    border-radius: 0;
}

.tt-price-reminder-row-actions {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 8px;
}

.tt-price-reminders-form-section .tt-price-reminders-create-form {
    display: grid;
    gap: 14px;
    align-content: start;
}

.tt-price-reminders-form-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 4px;
}

.tt-price-reminders-form-actions.is-hidden {
    display: none !important;
}

.tt-price-reminders-list-section {
    flex: 0 0 auto;
    display: block;
    padding-top: 16px;
}

.tt-price-reminders-list-section .tt-price-reminders-list {
    overflow-y: auto;
    max-height: min(40vh, 360px);
    padding-right: 4px;
    scrollbar-width: thin;
    scrollbar-color: var(--sb-thumb-vis, rgba(100, 100, 110, 0.7)) transparent;
}

.tt-price-reminders-list.is-empty {
    max-height: none;
    overflow: visible;
    padding: 4px 0 0;
    font-size: 12px;
    color: var(--text-secondary);
}

.tt-price-reminders-list-section .tt-price-reminders-list::-webkit-scrollbar {
    width: 4px;
}

.tt-price-reminders-list-section .tt-price-reminders-list::-webkit-scrollbar-thumb {
    background: var(--sb-thumb-vis, rgba(100, 100, 110, 0.7));
    border-radius: 4px;
}

.tt-price-reminders-stack header {
    margin-bottom: 12px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--border-color);
}

.tt-price-reminders-panel header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 14px;
}

.tt-price-reminders-panel-title {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-weight: 600;
    color: var(--text-primary);
}

.tt-price-reminders-panel-title svg {
    width: var(--chart-toolbar-icon-size);
    height: var(--chart-toolbar-icon-size);
    flex-shrink: 0;
    fill: none;
    stroke: none;
    color: var(--text-primary);
}

.tt-price-reminders-panel-title svg :is(path, line, circle) {
    fill: none;
    stroke: var(--text-primary);
    stroke-width: var(--chart-toolbar-icon-stroke);
    stroke-linecap: round;
    stroke-linejoin: round;
}

.tt-price-reminders-panel header button {
    min-width: 32px;
    padding: 0 8px;
    border: 0;
    background: transparent;
    color: var(--text-secondary);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
}

.tt-price-reminders-create {
    display: grid;
    gap: 10px;
    margin-bottom: 14px;
    padding-bottom: 14px;
    border-bottom: 1px solid var(--border-color);
}

.tt-price-reminder-field {
    display: grid;
    gap: 6px;
}

.tt-price-reminder-field > span {
    font-size: 12px;
    color: var(--text-secondary);
}

.tt-price-reminders-panel input,
.tt-price-reminders-panel select,
.tt-price-reminders-panel button:not(.ca-toggle):not(.tt-price-reminder-delete-btn) {
    min-height: 32px;
    border-radius: 4px;
}

.tt-price-reminders-panel input,
.tt-price-reminders-panel select {
    width: 100%;
    padding: 0 10px;
    border: 1px solid var(--border-color);
    background: var(--bg-primary);
    color: var(--text-primary);
}

.tt-price-reminders-create-btn {
    width: 100%;
}

.tt-price-reminders-list {
    display: grid;
    gap: 0;
}

.tt-price-reminder-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 8px;
    border-top: 1px solid var(--border-color);
    cursor: pointer;
    border-radius: 4px;
    background: transparent;
    outline: 1px solid transparent;
    outline-offset: -1px;
}

.tt-price-reminder-row.is-selected {
    background: transparent;
    outline-color: var(--border-color);
}

.tt-price-reminder-row:hover {
    background: transparent;
    outline-color: var(--border-color);
}

.tt-price-reminder-row:first-child {
    border-top: 0;
}

.tt-price-reminder-main {
    display: grid;
    gap: 4px;
    min-width: 0;
    flex: 1;
}

.tt-price-reminder-symbol {
    font-weight: 600;
    color: var(--text-primary);
}

.tt-price-reminder-meta {
    color: var(--text-secondary);
    font-size: 12px;
    line-height: 1.4;
    word-break: break-word;
}

.tt-price-reminder-row-actions,
.tt-price-reminder-editor-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.tt-price-reminder-editor-actions {
    margin-top: 4px;
}

.tt-btn-primary,
.tt-btn-secondary,
.tt-btn-danger {
    padding: 0 12px;
    border: 1px solid var(--border-color);
    background: var(--bg-secondary, var(--bg-primary));
    color: var(--text-primary);
    cursor: pointer;
    font-size: 13px;
}

.tt-btn-primary {
    border-color: #2962ff;
    background: #2962ff;
    color: #ffffff;
}

.tt-btn-danger {
    border-color: rgba(239, 83, 80, 0.45);
    color: #ef5350;
}

/* Dividers between top bar buttons (except first menu button) */
.top-bar-left > *:not(:first-child)::before {
    content: '';
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 1px;
    height: 20px;
    background: var(--border-color);
}

.top-bar-dropdown,
.top-bar-ind-settings-btn {
    position: relative;
}

/* ========== Chart Page: Auth UI (user avatar + dropdown in top-bar-left) ========== */
/* nav-auth is hidden by default; JS sets display based on localStorage */
.nav-auth { display: none; }

/* Guest: user icon link */
.chart-user-guest {
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--toolbar-btn-size);
    height: var(--toolbar-btn-size);
    color: var(--text-primary);
    transition: color 0.2s;
    text-decoration: none;
    margin-right: 12px;
}
.chart-user-guest:hover { color: var(--text-primary); }
.chart-user-guest svg {
    width: var(--topbar-icon-size);
    height: var(--topbar-icon-size);
    stroke-width: var(--chart-icon-stroke);
}

/* Auth: avatar container */
.chart-user-auth {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-right: 12px;
}

/* Avatar circle button */
/* [FIX] 2026-08-08 — img avatar: drop letter-avatar fill; clip circle on button. */
.nav-user-avatar {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: transparent;
    border: none;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    flex-shrink: 0;
    padding: 0;
    overflow: hidden;
}
.nav-user-avatar:focus {
    outline: none;
}
.nav-user-avatar:focus-visible {
    outline: 2px solid var(--tt-fg-0, var(--text-primary));
    outline-offset: 2px;
}
.nav-user-avatar-img {
    width: 100%;
    height: 100%;
    border-radius: inherit;
    object-fit: cover;
    display: block;
}

/* Dropdown panel.
   margin-top provides visual gap; ::after on the container bridges the gap
   so the hover chain is never broken while moving mouse to the panel.
   [UPDATE] 2026-05-20 r10 — unified onto --popover-* tokens (was a
   hard-coded #2a2a2a dark / #ffffff light split that fought every other
   dropdown style on the page). Now reads the same surface treatment as
   chart-menu-dropdown / market-hub-popover / confirm-dialog. */
.nav-user-dropdown {
    display: none;
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 6px;
    min-width: 280px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 10px;
    box-shadow: var(--popover-shadow);
    list-style: none;
    padding: 6px 0;
    z-index: 9999;
}
/* Invisible hover bridge fills the margin-top gap below avatar */
.chart-user-auth::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    height: 10px;
    transform: translateY(100%);
}
.chart-user-auth:hover .nav-user-dropdown { display: block; }

/* ========================================================================
   [DEL] 2026-05-30 (Slice 2) — removed the top-bar notification bell
   stylesheet block (.nav-notif-bell / .nav-notif-badge / .nav-notif-panel
   / .nav-notif-head / .nav-notif-title / .nav-notif-mark-all /
   .nav-notif-list / .nav-notif-empty / .nav-notif-item* / .nav-notif-footer).

   Reason: the in-site notification surface was relocated from a top-bar
   dropdown to a right-toolbar side drawer (#notifDrawer). The new visual
   contract reuses the .sc-drawer base (slide-in from right, resizable
   left edge handle, mobile bottom-sheet) and adds .notif-drawer-* +
   .notif-drawer__tab* selectors further down in this file. The toolbar
   trigger button reuses .toolbar-btn / .toolbar-btn__badge from the same
   pattern as the DM bell (#dmToolbarBtn / #dmToolbarBadge).

   The old dropdown DOM was removed in chart.ejs (#notifBell /
   #notifBellPanel / #notifBadge / #notifBellList / #notifMarkAllRead);
   any future audit grep for .nav-notif-* should return zero hits.
   ====================================================================== */

/* Identity block.
   [FIX] 2026-07-02 — two-row grid: row1 = avatar + username, row2 = tier pill
   from the avatar's LEFT edge (col 1). Pill is a direct child of .nud-identity
   (no longer nested in .nud-identity-text) so it owns its own row. */
.nud-identity {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: center;
    column-gap: 10px;
    row-gap: 8px;
    padding: 14px !important;
    border-bottom: 1px solid var(--popover-border);
    margin-bottom: 6px !important;
}

.nud-avatar-sm {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    display: block;
    overflow: hidden;
    object-fit: cover;
}
/* Username occupies row 1 / column 2 (next to the avatar). */
.nud-identity-text {
    min-width: 0;
    align-self: center;
}
.nud-username {
    min-width: 0;
    font-size: 15px;
    font-weight: 500;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Membership tier pill — row 2 / column 1, left edge aligned to the avatar.
   Compact ~20px pill with an optional leading accent dot; per-tier colour is
   decorative (dot/border/background), matching chart design language. */
.nud-tier {
    grid-column: 1 / -1;
    justify-self: start;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 20px;
    padding: 0 9px;
    border-radius: 999px;
    border: 1px solid var(--popover-border);
    background: var(--bg-secondary);
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: 0.02em;
    color: var(--text-secondary);
    white-space: nowrap;
}
/* Guests: hidden pill must stay hidden despite the inline-flex above. */
.nud-tier[hidden] { display: none; }
/* Per-tier accent dot + border tint + subtle background wash (all decorative).
   Free = neutral (no dot). Higher tiers read progressively richer. */
.nud-tier--basic::before,
.nud-tier--plus::before,
.nud-tier--pro::before,
.nud-tier--ultimate::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    flex-shrink: 0;
}
.nud-tier--basic::before { background: #3b82f6; }
.nud-tier--basic { border-color: rgba(59,130,246,0.45); background: rgba(59,130,246,0.10); }
.nud-tier--plus::before { background: #a855f7; }
.nud-tier--plus { border-color: rgba(168,85,247,0.45); background: rgba(168,85,247,0.10); }
.nud-tier--pro::before { background: #f59e0b; }
.nud-tier--pro { border-color: rgba(245,158,11,0.50); background: rgba(245,158,11,0.12); }
.nud-tier--ultimate::before { background: linear-gradient(135deg,#fde68a 0%,#f59e0b 100%); box-shadow: 0 0 4px rgba(245,158,11,0.65); }
.nud-tier--ultimate {
    border-color: rgba(245,158,11,0.55);
    background: linear-gradient(135deg, rgba(253,230,138,0.12) 0%, rgba(245,158,11,0.12) 100%);
}

/* Group labels */
.nud-group-label {
    padding: 8px 14px 4px !important;
    font-size: 11px !important;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-secondary);
    cursor: default;
    line-height: 1.2 !important;
}

/* Menu items */
.nud-item a {
    display: flex;
    align-items: center;
    gap: 10px;
    height: 36px;
    padding: 0 14px;
    font-size: 14px;
    color: var(--text-primary);
    text-decoration: none;
    transition: background-color 0.15s;
}
.nud-item a svg {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    flex-shrink: 0;
    color: inherit;
}
.nud-item a:hover { background-color: var(--bg-hover); color: var(--text-primary); }
.nud-item a:hover svg { color: var(--text-primary); }

/* Menu divider */
.nud-divider {
    height: 1px;
    margin: 6px 0;
    background-color: var(--popover-border);
}

/* Sign out
   [EXCEPTION] color uses --price-down (red) — destructive-action semantic
   colour, follows the same convention as confirm-btn-ok type=danger. */
.nud-signout { border-top: 1px solid var(--popover-border); margin-top: 6px; }
.nud-signout a {
    display: flex;
    gap: 10px;
    align-items: center;
    height: 36px;
    padding: 0 14px;
    font-size: 14px;
    color: var(--price-down);
    text-decoration: none;
    transition: background-color 0.15s;
}
.nud-signout a svg {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    color: inherit;
}
.nud-signout a:hover { background-color: var(--bg-hover); }

.top-bar-right {
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    padding-right: 4px;
    margin-left: auto;
    flex-shrink: 0;
}

/* ========== Server Select Dropdown ========== */
.server-select-wrap {
    position: relative;
    display: inline-flex;
    align-items: center;
    height: 100%;
}
.server-select-btn {
    display: flex;
    align-items: center;
    gap: 2px;
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 4px 4px 4px 6px;
    border-radius: 4px;
    height: 28px;
    transition: color .15s, background .15s;
}
.server-select-btn:hover {
    color: var(--text-primary);
    background: var(--bg-tertiary);
}
.ss-btn-icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    transition: color .3s;
}
.server-select-btn.ss-good .ss-btn-icon {
    color: #22c55e;
    filter: drop-shadow(0 0 3px rgba(34,197,94,.5));
    animation: ss-pulse 2s ease-in-out infinite;
}
.server-select-btn.ss-medium .ss-btn-icon {
    color: #f59e0b;
    filter: drop-shadow(0 0 3px rgba(245,158,11,.5));
    animation: ss-pulse 1.5s ease-in-out infinite;
}
.server-select-btn.ss-bad .ss-btn-icon {
    color: #ef4444;
    filter: drop-shadow(0 0 3px rgba(239,68,68,.5));
    animation: ss-pulse 1s ease-in-out infinite;
}
@keyframes ss-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: .45; }
}
.server-select-arrow {
    width: 8px;
    height: 8px;
    flex-shrink: 0;
    opacity: .6;
    transition: transform .2s;
}
.server-select-wrap.open .server-select-arrow {
    transform: rotate(180deg);
}
.server-select-dropdown {
    display: none;
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    min-width: 220px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 8px;
    box-shadow: var(--popover-shadow);
    z-index: 10000;
    padding: 6px 0;
}
.server-select-wrap.open .server-select-dropdown {
    display: block;
}
.ss-group-label {
    padding: 6px 12px 4px;
    font-size: 11px;
    font-weight: 600;
    color: var(--text-tertiary);
    text-transform: uppercase;
    letter-spacing: .5px;
    user-select: none;
}
.ss-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 12px;
    font-size: 13px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background .15s;
    user-select: none;
}
.ss-item:hover { background: var(--bg-hover); }
.ss-item.active { color: var(--primary-color); }
.ss-item .ss-icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}
.ss-item .ss-check {
    width: 14px;
    height: 14px;
    margin-left: auto;
    flex-shrink: 0;
    opacity: 0;
}
.ss-item.active .ss-check { opacity: 1; color: var(--primary-color); }
.ss-divider {
    height: 1px;
    background: var(--border-color);
    margin: 4px 0;
}
.ss-item.disabled {
    opacity: .4;
    cursor: default;
    pointer-events: none;
}
.ss-latency-section {
    padding: 6px 12px 8px;
}
.ss-latency-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 6px;
}
.ss-latency-label {
    font-size: 11px;
    color: var(--text-tertiary);
}
.ss-latency-chart {
    width: 100%;
    height: 70px;
    border-radius: 4px;
    background: var(--bg-primary);
    overflow: hidden;
}
.ss-latency-chart canvas {
    width: 100%;
    height: 100%;
}

/* FIX: Theme toggle icon visibility and styling
   Show moon in light mode, sun in dark mode
   Ensures icons maintain consistent size and position */
.icon-sun {
    display: none;
}

.icon-moon {
    display: block;
}

body.dark-theme .icon-sun {
    display: block;
}

body.dark-theme .icon-moon {
    display: none;
}


#pwaInstallBtn {
    cursor: default;
}

/* ========== Main Container ========== */
.main-container {
    flex: 1;
    display: flex;
    overflow: hidden;
    min-width: 0;
}

/* ========== Toolbars ========== */
.left-toolbar,
.right-toolbar {
    width: 45px;
    background: var(--bg-secondary);
    display: flex;
    flex-direction: column;
    align-items: center;
    flex-shrink: 0;
}

/* [CHG] 2026-05-24 r35 — left-toolbar right edge now matches the chart
 * split-bar look: 5px solid --resizer-color (dark #1a1a1a / light #f5f5f5).
 * The previous 1px symmetry-with-right-toolbar decision (r3) was rolled
 * back per user request — the drawing toolbar / chart boundary is the
 * SAME visual divider as the layout splitters, so it should read at the
 * same 5px thickness.
 * IMPORTANT: content width MUST stay 45px because .toolbar-btn inside is
 * 42px (var(--toolbar-btn-size)) plus 2*1px padding, and box-sizing:
 * border-box would shrink the content area to 38px and clip every
 * drawing-tool icon by 4px. So we KEEP the default content-box and let
 * the total occupied width grow from 46px (45 + 1px border) to 50px
 * (45 + 5px border) — a 4px chart shift to the right that's part of
 * "split bars are now 5px thick everywhere". The .right-toolbar deliberately
 * keeps its 1px border to match user spec. */
.left-toolbar {
    width: 45px;
    border-right: 5px solid var(--resizer-color);
    padding: 0 1px;
    position: relative;
    overflow-x: hidden;
    overflow-y: hidden;
    display: flex;
    flex-direction: column;
}

.toolbar-fixed-top {
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 8px 0;
    gap: 4px;
}

.toolbar-scrollable {
    flex: 1;
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 4px 0;
    gap: 4px;
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.toolbar-scrollable::-webkit-scrollbar {
    display: none;
}

.toolbar-scroll-btn {
    width: var(--toolbar-btn-size);
    height: var(--toolbar-btn-size);
    min-height: var(--toolbar-btn-size);
    background: var(--bg-secondary);
    border: none;
    border-radius: 6px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.2s;
    flex-shrink: 0;
    color: var(--text-primary);
}

.toolbar-scroll-btn svg {
    width: var(--chart-toolbar-icon-size);
    height: var(--chart-toolbar-icon-size);
    fill: none;
    stroke: none;
    stroke-linecap: round;
    stroke-linejoin: round;
    color: var(--text-primary);
    transition: stroke 0.2s;
}

.toolbar-scroll-btn svg :is(path, line, polyline, circle, rect, ellipse, polygon) {
    fill: none;
    stroke: var(--text-primary);
    stroke-width: var(--chart-toolbar-icon-stroke);
    stroke-linecap: round;
    stroke-linejoin: round;
}


.toolbar-scroll-btn:hover {
    background: var(--bg-tertiary);
}

.toolbar-scroll-btn.hidden {
    display: none;
}

/* Left Toolbar Scroll Buttons (vertical scroll) */
.left-toolbar > .toolbar-scroll-btn {
    display: flex;
    width: 100%;
    height: 28px;
    min-height: 28px;
    background: var(--bg-secondary);
    border: none;
    border-radius: 0;
}

.left-toolbar > .toolbar-scroll-btn.scroll-up {
    border-bottom: 1px solid var(--border-color);
}

.left-toolbar > .toolbar-scroll-btn.scroll-down {
    border-top: 1px solid var(--border-color);
}

.left-toolbar > .toolbar-scroll-btn.hidden {
    display: none;
}

/* [HISTORY] 2026-05-22 r1 → r2 — see .left-toolbar note above. The
 * outer left edge of the right toolbar stays on --border-color to
 * preserve the frameless boundary. Only inner .toolbar-divider rows
 * use --toolbar-sep. */
/* [CHG] 2026-05-24 r35 — right-toolbar left edge: KEEP 1px width per user
 * spec (this is the asymmetric-by-design boundary that anchors the chart's
 * right edge), only the colour switches to --resizer-color so it matches
 * the .top-bar bottom edge + .left-toolbar right edge tonally. */
.right-toolbar {
    border-left: 1px solid var(--resizer-color);
    padding: 0 1px;
    gap: 4px;
}


.toolbar-btn {
    width: var(--toolbar-btn-size);
    height: var(--toolbar-btn-size);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    border-radius: 6px;
    transition: background 0.2s;
    background: transparent;
    border: none;
    flex-shrink: 0;
    color: var(--text-primary);
}

.toolbar-btn:hover {
    background: var(--border-color);
}

.toolbar-btn.active {
    background: var(--bg-tertiary);
}

.toolbar-btn svg {
    width: var(--chart-toolbar-icon-size);
    height: var(--chart-toolbar-icon-size);
    fill: none;
    stroke: none;
    stroke-linecap: round;
    stroke-linejoin: round;
    color: var(--text-primary);
    transition: stroke 0.2s;
}

.toolbar-btn svg :is(path, line, polyline, circle, rect, ellipse, polygon) {
    fill: none;
    stroke: var(--text-primary);
    stroke-width: var(--chart-toolbar-icon-stroke);
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Chart functional icons (drawers, menus) — 24px; top-bar triggers use 32px block below. */
.drawer-head-title__icon,
.nud-item a svg,
.bp-tab-icon,
.add-exchange-btn svg,
.symbol-columns-btn svg,
.data-control-btn svg,
.ai-icon-btn svg,
.dropdown-icon {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
}

/* Top-bar trigger icons — 32px / 0.6, same as #menuBtn and left-toolbar. */
.top-bar-left .chart-menu-wrap .toolbar-btn {
    width: var(--toolbar-btn-size);
    height: var(--toolbar-btn-size);
    transform: translateX(-2px);
}

.top-bar-left .chart-menu-wrap .toolbar-btn svg,
.top-bar-left .chart-icon,
.top-bar-left .core-icon,
.top-bar-left .btn-icon,
.top-bar-left .top-bar-ind-gear-btn > svg,
.top-bar-left .pt-toolbar-btn > svg,
.top-bar-left .top-bar-overlay-dropdown > .toolbar-dropdown-btn .btn-icon,
.top-bar .toolbar-btn > svg,
.pt-toolbar-btn > svg {
    width: var(--chart-toolbar-icon-size);
    height: var(--chart-toolbar-icon-size);
    fill: none;
    stroke: none;
    stroke-linecap: round;
    stroke-linejoin: round;
    color: var(--text-primary);
}

.top-bar-left .chart-menu-wrap .toolbar-btn svg :is(path, line, polyline, circle, rect, ellipse, polygon),
.top-bar-left .chart-icon :is(path, line, polyline, circle, rect, ellipse, polygon),
.top-bar-left .core-icon :is(path, line, polyline, circle, rect, ellipse, polygon),
.top-bar-left .btn-icon :is(path, line, polyline, circle, rect, ellipse, polygon),
.top-bar-left .top-bar-ind-gear-btn > svg :is(path, line, polyline, circle, rect, ellipse, polygon),
.top-bar-left .pt-toolbar-btn > svg :is(path, line, polyline, circle, rect, ellipse, polygon),
.top-bar-left .top-bar-overlay-dropdown > .toolbar-dropdown-btn .btn-icon :is(path, line, polyline, circle, rect, ellipse, polygon),
.top-bar .toolbar-btn > svg :is(path, line, polyline, circle, rect, ellipse, polygon),
.pt-toolbar-btn > svg :is(path, line, polyline, circle, rect, ellipse, polygon) {
    fill: none;
    stroke: var(--text-primary);
    stroke-width: var(--chart-toolbar-icon-stroke);
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Top-bar dropdown menus — item icons 24px (triggers stay 32px / 0.6). */
.top-bar-left :is(
    .chart-type-option .chart-type-icon,
    .ma-type-item .ma-cat-icon,
    .ma-option .ma-cat-icon,
    .ind-type-item .ind-cat-icon,
    .ind-settings-icon,
    .heatmap-layer-icon,
    .fp-btn-icon,
    .dropdown-slider .dropdown-icon
) {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    flex-shrink: 0;
    color: var(--text-primary);
}

.top-bar-left #chartSettingsDropdown .layout-icon,
.top-bar-left #chartSettingsDropdown .layout-icon svg {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
}

.layout-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
}

.layout-icon svg {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
}

#chartSettingsDropdown .layout-icon svg :is(rect, line, polyline, circle, path),
#chartSettingsDropdown .dropdown-slider .dropdown-icon :is(circle, path, line) {
    stroke-width: var(--chart-icon-stroke);
}

#chartSettingsDropdown .dropdown-slider {
    display: grid;
    grid-template-columns: var(--chart-icon-size) minmax(72px, 108px) auto 1fr;
    align-items: center;
    column-gap: 8px;
    padding: 8px 12px;
}

#chartSettingsDropdown .dropdown-slider input[type="range"] {
    flex: unset;
    width: 100%;
    min-width: 0;
}

#chartSettingsDropdown .dropdown-slider span {
    min-width: 0;
    width: auto;
    text-align: left;
}

#chartSettingsDropdown .layout-item::before {
    display: none;
    content: none;
}

#chartSettingsDropdown .layout-item > span:not(.layout-icon) {
    flex: 1;
    min-width: 0;
}

#chartSettingsDropdown .layout-item::after {
    content: '';
    width: 14px;
    flex-shrink: 0;
    font-size: 12px;
    text-align: center;
}

#chartSettingsDropdown .layout-item.active::after {
    content: '\2713';
    color: var(--primary-color);
}

#footprintDropdown .fp-btn-group {
    padding: 4px 6px;
}

#footprintDropdown .fp-btn {
    padding: 6px 8px;
    gap: 6px;
}

#footprintDropdown .fp-btn-icon :is(path, line, polyline, rect, polygon, circle) {
    stroke-width: var(--chart-icon-stroke);
}

/* FIX: Divider width 36px matches toolbar content width (52px - 4px border - 12px padding).
   Parent align-items:center ensures horizontal centering. */
/* [FIX] 2026-05-22 — switched background from --border-color to
 * --toolbar-sep. --border-color is intentionally near-invisible to keep
 * chart panels frameless, but that same colour made the toolbar dividers
 * literally disappear under both light AND dark themes. --toolbar-sep is
 * the new dedicated 1px separator token for the chart toolbars only
 * (see :root block for the rationale + contrast numbers).
 * Warning: do NOT revert to --border-color — it will reintroduce the
 * "missing dividers" UX bug the user reported on 2026-05-22. */
.toolbar-divider {
    width: 36px;
    height: 1px;
    background: var(--toolbar-sep);
    margin: 4px 0;
}

.toolbar-group {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
}

/* Drawing Tool Dropdown */
.drawing-tool-dropdown {
    position: relative;
    display: flex;
    justify-content: center;
    z-index: 9999;
}

.drawing-tool-dropdown .dropdown-btn {
    width: 36px;
    height: 36px;
}

.drawing-tool-dropdown-menu {
    position: fixed;
    left: 48px;
    top: 12px;
    width: 272px;
    max-height: calc(100vh - 24px);
    overflow-y: auto;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 8px;
    box-shadow: var(--popover-shadow);
    z-index: 9999;
    display: none;
    padding: 8px 0;
    scrollbar-width: thin;
    scrollbar-color: #555 transparent;
}

.drawing-tool-dropdown-menu::-webkit-scrollbar {
    width: 6px;
}

.drawing-tool-dropdown-menu::-webkit-scrollbar-track {
    background: transparent;
}

.drawing-tool-dropdown-menu::-webkit-scrollbar-thumb {
    background: #555;
    border-radius: 3px;
}

.drawing-tool-dropdown-menu::-webkit-scrollbar-thumb:hover {
    background: #666;
}

.drawing-tool-dropdown.open .drawing-tool-dropdown-menu {
    display: block;
}

.tool-group-title {
    font-size: 11px;
    font-weight: 600;
    color: var(--text-tertiary);
    padding: 8px 12px 4px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    margin-top: 8px;
}

.tool-group-title:first-child {
    margin-top: 0;
}

.drawing-tool-option {
    display: flex;
    align-items: center;
    padding: 8px 12px;
    cursor: pointer;
    transition: background 0.2s;
    gap: 10px;
}

.drawing-tool-option:hover {
    background: var(--bg-hover);
}

.drawing-tool-option .tool-icon {
    width: var(--toolbar-icon-size);
    height: var(--toolbar-icon-size);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.drawing-tool-option .tool-icon svg {
    width: var(--chart-toolbar-icon-size);
    height: var(--chart-toolbar-icon-size);
    fill: none;
    stroke: none;
    color: var(--text-primary);
}

.drawing-tool-option .tool-icon svg :is(path, line, polyline, circle, rect, ellipse, polygon) {
    fill: none;
    stroke: var(--text-primary);
    stroke-width: var(--chart-toolbar-icon-stroke);
    stroke-linecap: round;
    stroke-linejoin: round;
}

.drawing-tool-option span {
    flex: 1;
    font-size: 13px;
    color: var(--text-primary);
}

.drawing-tool-option .star-icon {
    width: 16px;
    height: 16px;
    fill: none;
    stroke: var(--text-primary);
    stroke-width: 1.2;
    cursor: pointer;
    transition: all 0.2s;
    flex-shrink: 0;
    opacity: 0.5;
    opacity: 0.5;
}

.drawing-tool-option .star-icon:hover {
    stroke: var(--text-primary);
    transform: scale(1.1);
    opacity: 1;
}

.drawing-tool-option .star-icon.filled,
.drawing-tool-option.favorite .star-icon {
    fill: var(--text-primary);
    stroke: var(--text-primary);
    opacity: 1;
}

/* ========== Content Area ========== */
.content-area {
    flex: 1;
    display: flex;
    overflow: hidden;
    min-width: 0;
}

/* ========== Symbol List (now in right-panel-top) ========== */
/* Legacy left-panel styles removed - now using right-panel-top */

/* [CHG] 2026-05-24 r33 — width 4 → 5 (easier grab target on dense layouts)
 * and colour switches to --resizer-color (light theme = #f5f5f5 unchanged,
 * dark theme = #1a1a1a). */
.resizer {
    width: 5px;
    background: var(--resizer-color);
    cursor: col-resize;
    flex-shrink: 0;
    transition: background 0.2s;
    touch-action: none;
}

.resizer:hover,
.resizer.active {
    background: var(--text-secondary);
}

/* Vertical Resizer (for top/bottom split in right panel) */
.resizer-vertical {
    height: 5px;
    width: 100%;
    background: var(--resizer-color);
    cursor: ns-resize;
    flex-shrink: 0;
    transition: background 0.2s;
    touch-action: none;
}

.resizer-vertical:hover,
.resizer-vertical.active {
    background: var(--text-secondary);
}

.exchange-selector {
    position: relative;
    padding: 0 12px 0 12px;
    border-bottom: none;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    gap: 8px;
    height: 45px;
    box-sizing: border-box;
}

.exchange-selector .panel-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
    white-space: nowrap;
    margin-left: 0;
}

.exchange-selector label {
    font-size: 10px;
    color: var(--text-secondary);
    font-weight: 500;
    flex-shrink: 0;
}

/* Custom Select Component */
.custom-select {
    position: relative;
    width: fit-content;
    font-size: 14px;
}

.custom-select-trigger {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 0 10px;
    height: 40px;
    box-sizing: border-box;
    background: transparent;
    border: none;
    border-radius: 6px;
    color: var(--text-primary);
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.2s;
}

.custom-select-trigger:hover {
    background: var(--bg-hover);
}

/* Exchange icon (left side) */
.custom-select-trigger .exchange-icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

/* Text in the middle */
.custom-select-trigger span {
    flex: 1;
    line-height: 1.2;
}

/* Dropdown arrow — chevron SVG */
.custom-select-trigger .dropdown-arrow {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    margin-left: 2px;
    color: var(--text-secondary);
    transition: transform 0.2s;
}

.custom-select.open .custom-select-trigger .dropdown-arrow {
    transform: rotate(180deg);
}

.custom-select-options {
    position: absolute;
    top: 100%;
    right: 0;
    min-width: 200px;
    margin-top: 4px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 8px;
    box-shadow: var(--popover-shadow);
    z-index: 9999;
    display: none;
    overflow: hidden;
}

.custom-select.open .custom-select-options {
    display: block;
}

.custom-select-option {
    padding: 10px 12px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background 0.15s;
    display: flex;
    align-items: center;
    gap: 8px;
}

.custom-select-option:hover {
    background: var(--bg-hover);
}

/* Option icon in dropdown */
.custom-select-option .option-icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

.custom-select-option .exchange-name {
    flex: 1;
    min-width: 0;
}

.custom-select-option.selected {
    background: var(--bg-tertiary);
}

/* Highlight spot-only exchanges with active Spot label when selected */
.custom-select-option[data-has-types="false"].selected .type-label {
    background: var(--state-active);
    color: #fff;
    font-weight: 500;
    padding: 2px 8px;
    border-radius: 8px;
    font-size: 10px;
}

/* Exchange type toggle buttons */
.exchange-type-toggle {
    display: flex;
    gap: 2px;
    background: var(--bg-primary);
    border-radius: 8px;
    padding: 2px;
    margin-left: auto;
}

.exchange-type-toggle .type-btn {
    padding: 2px 8px;
    font-size: 12px;
    border: none;
    background: transparent;
    color: var(--text-primary);
    border-radius: 3px;
    cursor: pointer;
    transition: all 0.15s;
    white-space: nowrap;
}

body.dark-theme .exchange-type-toggle .type-btn {
    color: var(--text-primary);
}

.exchange-type-toggle .type-btn:hover {
    color: var(--text-primary);
}

body.dark-theme .exchange-type-toggle .type-btn:hover {
    color: var(--text-primary);
}

.exchange-type-toggle .type-btn.active,
body.dark-theme .exchange-type-toggle .type-btn.active {
    background: var(--state-active);
    color: #fff;
    font-weight: 500;
}

/* For exchanges with only one type */
.custom-select-option .type-label {
    font-size: 12px;
    color: var(--text-primary);
    padding: 2px 8px;
    background: var(--bg-primary);
}

body.dark-theme .custom-select-option .type-label {
    color: var(--text-primary);
}

/* Add Exchange Button - Same size as right toolbar buttons */
.exchange-selector-actions {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 0;
}

.add-exchange-btn,
.symbol-columns-btn {
    width: 36px;
    height: 36px;
    border: none;
    border-radius: 6px;
    background: transparent;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.2s;
    color: var(--text-primary);
}

.add-exchange-btn:hover,
.symbol-columns-btn:hover {
    background: var(--border-color);
    color: var(--text-primary);
}

.add-exchange-btn svg,
.symbol-columns-btn svg,
.data-control-btn svg {
    stroke: currentColor;
    fill: none;
    stroke-linecap: butt;
    stroke-linejoin: miter;
    transition: stroke 0.2s, color 0.2s;
}

.data-control-btn {
    width: 36px;
    height: 36px;
    border: none;
    border-radius: 6px;
    background: transparent;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.2s;
    color: var(--text-primary);
}
.data-control-btn:hover {
    background: var(--border-color);
    color: var(--text-primary);
}

/* Symbol Columns Menu & Data Control Menu */
.symbol-columns-menu {
    display: none;
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 4px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 6px;
    padding: 8px 0;
    width: 272px;
    box-shadow: var(--popover-shadow);
    z-index: 1000;
}
.data-control-menu {
    display: none;
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 4px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 6px;
    padding: 8px 0;
    width: 320px;
    box-shadow: var(--popover-shadow);
    z-index: 1000;
}

body.dark-theme .symbol-columns-menu,
body.dark-theme .data-control-menu {
    /* [REMOVED 2026-05-20 r10] dark-theme shadow override no longer
     * needed — base selector now uses var(--popover-shadow) which is
     * already tuned for the dark-theme black background. Kept the rule
     * body empty rather than dropping the selector entirely because
     * other dark-theme-only properties may be appended here later. */
    box-shadow: var(--popover-shadow);
}

.symbol-columns-menu.show,
.data-control-menu.show {
    display: block;
}

.symbol-columns-title {
    padding: 10px 12px 8px;
    font-size: 11px;
    font-weight: 600;
    color: var(--text-tertiary);
    letter-spacing: 0.5px;
    text-transform: uppercase;
}
.symbol-columns-title:not(:first-child) {
    margin-top: 8px;
    border-top: 1px solid var(--border-color);
    padding-top: 12px;
}
/* [ADD] 2026-05-27 — Modifier for section headers that host an inline
   help tooltip-icon (.tt-help) next to the label. Keeps the base
   .symbol-columns-title block rule intact for the other five headers
   (DISPLAY CONTROL / CUSTOMIZE COLUMNS / FAVORITES / SYMBOLS / LOADING)
   so this is a strictly additive change. The .tt-help icon is pushed
   to the RIGHT edge of the header (margin-left: auto on the icon) so
   that the tooltip popup — which always opens with `right: 0` relative
   to the icon, mirroring the "Load all enabled exchanges" tooltip
   exactly — can expand leftward without overflowing the menu's left
   edge. This matches the geometry of the canonical tooltip (icon at
   right, popup grows leftward). */
.symbol-columns-title--with-help {
    display: flex;
    align-items: center;
    gap: 6px;
}
/* Push the help icon to the right edge of the section header. We use
   a descendant selector + .tt-help so this only triggers when a
   .tt-help icon is actually present, not for other future inline
   elements that might sit in the header. */
.symbol-columns-title--with-help .tt-help {
    margin-left: auto;
}

.symbol-menu-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    cursor: pointer;
    font-size: 14px;
    color: var(--text-primary);
    transition: background 0.15s;
}
.symbol-menu-item:hover {
    background: var(--bg-hover);
}
.symbol-menu-item svg {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    flex-shrink: 0;
}

.symbol-column-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    cursor: pointer;
    font-size: 14px;
    color: var(--text-primary);
    transition: background 0.15s;
}
.symbol-column-item:hover {
    background: var(--bg-hover);
}
.symbol-column-item svg {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    flex-shrink: 0;
    color: var(--text-primary);
}

.symbol-column-item input[type="checkbox"] {
    width: 16px;
    height: 16px;
    cursor: pointer;
    margin: 0;
    appearance: none;
    -webkit-appearance: none;
    border: 1.5px solid var(--text-primary);
    border-radius: 3px;
    background: transparent;
    position: relative;
    transition: border-color 0.15s, background 0.15s;
}

.symbol-column-item input[type="checkbox"]:checked {
    background: var(--text-primary);
    border-color: var(--text-primary);
}

.symbol-column-item input[type="checkbox"]:checked::after {
    content: '';
    position: absolute;
    left: 4.5px;
    top: 1.5px;
    width: 4px;
    height: 8px;
    border: solid var(--bg-secondary);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

.symbol-column-item input[type="checkbox"]:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.symbol-column-item span {
    flex: 1;
    font-size: 14px;
    color: var(--text-primary);
}

/* Data Control Menu Specific */
.data-control-section {
    padding: 4px 12px 8px;
}
.data-control-label {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    color: var(--text-primary);
    margin-bottom: 8px;
}
.data-control-label svg {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
}
.data-control-options {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
}
.data-control-radio {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 6px 0;
    border-radius: 6px;
    cursor: pointer;
    font-size: 14px;
    font-weight: 400;
    color: var(--text-primary);
    background: var(--bg-tertiary);
    transition: background 0.15s, font-weight 0.15s;
}
.data-control-radio span {
    font-size: 14px;
    color: var(--text-primary);
}
.data-control-radio:hover {
    background: color-mix(in srgb, var(--text-primary) 15%, var(--bg-tertiary));
}
.data-control-radio input[type="radio"] {
    display: none;
}
.data-control-radio:has(input:checked) {
    background: color-mix(in srgb, var(--text-primary) 20%, var(--bg-tertiary));
    font-weight: 600;
}
.data-control-radio:has(input:checked):hover {
    background: color-mix(in srgb, var(--text-primary) 25%, var(--bg-tertiary));
}

.data-control-toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
}
.data-control-toggle > svg {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    color: var(--text-primary);
}
.data-control-toggle .ex-toggle-label {
    flex: 1;
}
.data-control-toggle .ex-toggle-label > span:first-child {
    font-size: 14px;
    color: var(--text-primary);
}

/* Exchange Manager Dialog (non-modal) */
.exchange-dialog-overlay {
    display: none;
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    z-index: 10000;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

.exchange-dialog-overlay.open {
    display: flex;
}

.exchange-dialog {
    width: 800px;
    max-width: 90vw;
    max-height: 80vh;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 12px;
    box-shadow: var(--popover-shadow);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    pointer-events: auto;
    position: absolute;
}

.exchange-dialog-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 20px;
    border-bottom: 1px solid var(--popover-border);
    cursor: move;
    user-select: none;
}

.exchange-dialog-title {
    font-size: 16px;
    font-weight: 600;
    color: var(--text-primary);
}

.exchange-dialog-close {
    width: 32px;
    height: 32px;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--text-primary);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s, color 0.15s;
}

.exchange-dialog-close svg {
    width: 22px;
    height: 22px;
}

.exchange-dialog-close:hover {
    background: var(--bg-tertiary);
    color: var(--text-primary);
}

.exchange-dialog-search {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 20px;
    border-bottom: 1px solid var(--border-color);
}

.exchange-dialog-search .search-icon {
    width: 24px;
    height: 24px;
    color: var(--text-primary);
    flex-shrink: 0;
}

.exchange-search-input {
    flex: 1;
    border: none;
    background: transparent;
    color: var(--text-primary);
    font-size: 14px;
    outline: none;
}

.exchange-search-input::placeholder {
    color: var(--text-secondary);
}

.exchange-dialog-list {
    overflow-y: auto;
    padding: 8px 0;
    flex: 1;
}

.exchange-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 20px;
    transition: background 0.15s;
}

.exchange-row:hover {
    background: var(--bg-hover);
}

.exchange-row .ex-icon {
    width: 28px;
    height: 28px;
    border-radius: 6px;
    flex-shrink: 0;
}

.exchange-row .ex-info {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.exchange-row .ex-name {
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
}

.exchange-row .ex-desc {
    font-size: 12px;
    line-height: 1.35;
    color: var(--text-secondary);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Toggle switch */
.ex-toggle-label {
    display: flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
    user-select: none;
}

.ex-toggle-label > span:first-child {
    font-size: 11px;
    color: var(--text-secondary);
    min-width: 38px;
}

.ex-toggle {
    display: none;
}

.ex-toggle-slider {
    width: 32px;
    height: 18px;
    background: var(--bg-tertiary);
    border-radius: 9px;
    position: relative;
    transition: background 0.2s;
    flex-shrink: 0;
}

.ex-toggle-slider::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 14px;
    height: 14px;
    background: var(--text-primary);
    border-radius: 50%;
    transition: transform 0.2s, background 0.2s;
}

.ex-toggle:checked + .ex-toggle-slider {
    background: var(--text-primary);
}

.ex-toggle:checked + .ex-toggle-slider::after {
    transform: translateX(14px);
    background: var(--bg-primary);
}

/* [ADD] 2026-06-17 — exchange-manager dialog rows use one single toggle per
   brand (logo + name + description + switch). Reuses the .ex-toggle-slider
   visual above. */
.exchange-row .ex-info {
    justify-content: flex-start;
}
.exchange-row .ex-toggle-label {
    flex-shrink: 0;
    gap: 0;
}

/* Exchange Dialog Footer */
.exchange-dialog-footer {
    display: flex;
    justify-content: flex-end;
    padding: 12px 20px;
    border-top: 1px solid var(--border-color);
}

.exchange-dialog-footer-option {
    display: flex;
    align-items: center;
    gap: 8px;
}

.exchange-dialog-footer-option .ex-toggle-label > span:first-child {
    font-size: 14px;
    color: var(--text-primary);
    min-width: auto;
}

/* ==========================================================================
   [ADD] 2026-05-27 — .tt-help: unified help-tooltip utility
   --------------------------------------------------------------------------
   THIS IS THE ONLY APPROVED tooltip pattern for the chart page going forward
   (see .cursor/rules/web_app_styling.mdc 约束 6 - 提示 Tooltip 统一 .tt-help).

   Visual contract (must stay 1:1 with the "Load all enabled exchanges"
   question-mark tooltip that already shipped in the Loading Settings popover,
   per user 2026-05-27 directive):

     icon wrapper (.tt-help):
       18×18 flex container, position: relative, color: --text-secondary,
       cursor: help, transition: color 0.15s.
       On :hover or :focus-within → color: --text-primary.

     icon SVG (.tt-help > svg):
       14×14, inherits currentColor (no explicit color rule — keeps the
       wrapper's --text-secondary → --text-primary transition working).

     tooltip popup (.tt-help__text):
       absolute, width 220px, padding 8px, font-size 12px, line-height 1.5,
       color --text-secondary, background --popover-bg,
       border 1px solid --popover-border, border-radius 4px,
       box-shadow --popover-shadow, z-index 1001, display: none by default.
       Shown on .tt-help:hover / :focus / :focus-within / .tt-help__text:hover
       (the last bridge prevents flicker when the mouse moves from icon to
       tooltip — CSS-only tooltip anti-flicker, same trick GitHub / Stripe /
       Linear use).

   Direction variants (pick exactly one per icon — direction matters because
   the icon's vertical position inside its containing popover determines
   which way the tooltip can open without occluding sibling content):

     .tt-help--up   → opens UPWARD,   right-aligned (bottom:100%; right:0).
                       Default; use when the icon sits near the BOTTOM of
                       its containing popover (the "Load all exchanges"
                       icon is at the bottom of the Loading menu).
     .tt-help--down → opens DOWNWARD, right-aligned (top:100%; right:0).
                       Use when the icon sits near the TOP/MIDDLE of its
                       containing popover (e.g. the MODE section header
                       in the markets columns menu).

   Optional row layout inside the popup (.tt-help__row + .tt-help__label
   + .tt-help__desc): a "term + definition" stack for multi-section help
   popups (used by the MODE tooltip to describe Watchlist vs Scanner).
   For single-paragraph help text, omit .tt-help__row and just put the
   localized text directly inside .tt-help__text — that matches the
   existing "Load all exchanges" tooltip exactly.

   Usage example (single paragraph, upward — the original pattern):
     <span class="tt-help tt-help--up" tabindex="0">
       <svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
            stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"
            aria-hidden="true">
         <circle cx="12" cy="12" r="10"/>
         <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
         <line x1="12" y1="17" x2="12.01" y2="17"/>
       </svg>
       <span class="tt-help__text" role="tooltip"
             data-i18n="markets.reloadSymbols">…</span>
     </span>

   Usage example (multi-row, downward — the MODE pattern):
     <span class="tt-help tt-help--down" tabindex="0"
           aria-label="Mode help"
           data-i18n="markets.modeHelp" data-i18n-attr="aria-label">
       <svg …></svg>
       <span class="tt-help__text" role="tooltip">
         <span class="tt-help__row">
           <strong class="tt-help__label" data-i18n="markets.modeWatchlist">Watchlist</strong>
           <span class="tt-help__desc" data-i18n="markets.modeTooltipWatchlistDesc">…</span>
         </span>
         <span class="tt-help__row">
           <strong class="tt-help__label" data-i18n="markets.modeScanner">Scanner</strong>
           <span class="tt-help__desc" data-i18n="markets.modeTooltipScannerDesc">…</span>
         </span>
       </span>
     </span>

   Anti-pitfalls baked in:
     1. Color uses currentColor on the SVG so a single `color:` change on
        the wrapper drives both icon stroke and the hover transition —
        no parallel `fill:`/`stroke:` updates needed.
     2. NO vertical margin between icon and popup → mouse can cross from
        icon to popup without losing :hover state (classic CSS tooltip
        flicker bug). Vertical breathing room is the popup's own padding.
     3. NO undefined CSS tokens (var(--text-tertiary) is referenced
        elsewhere in this file but never defined; we avoid it here).
     4. position:relative is on .tt-help (the icon wrapper), so the
        popup anchors to the icon — wrappers DO NOT need to be
        position:relative themselves. Drop .tt-help anywhere.
   ========================================================================== */
.tt-help {
    width: 18px;
    height: 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: help;
    color: var(--text-secondary);
    transition: color 0.15s;
    position: relative;
    /* Outline is added explicitly on :focus-visible so keyboard users
       see a focus ring; mouse-driven focus stays clean. */
    outline: none;
}
.tt-help:hover,
.tt-help:focus-visible {
    color: var(--text-primary);
}
.tt-help:focus-visible {
    outline: 2px solid var(--accent-color, #2962ff);
    outline-offset: 1px;
    border-radius: 2px;
}
.tt-help > svg {
    width: 14px;
    height: 14px;
    /* SVG strokes track wrapper color via currentColor (set on <svg> by
       stroke="currentColor" in the markup) — single source of truth. */
}

.tt-help__text {
    display: none;
    position: absolute;
    width: 220px;
    padding: 8px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 4px;
    box-shadow: var(--popover-shadow);
    font-size: 12px;
    line-height: 1.5;
    color: var(--text-secondary);
    z-index: 1001;
    /* Reset inherited typography that some popover containers apply
       (e.g. .symbol-columns-title sets text-transform: uppercase /
       letter-spacing on its children — without resetting these the
       MODE tooltip body text would be uppercased and tracking-spaced,
       making "Your personal symbol list…" unreadable). */
    text-transform: none;
    letter-spacing: normal;
    font-weight: normal;
    text-align: left;
    /* margin defaults to 0 — variants below decide direction-specific
       offsets without ever inserting a gap that would break hover. */
    margin: 0;
}

/* Direction variant: UPWARD (default for tooltips at the bottom of a
   container, mirrors "Load all enabled exchanges" exactly). */
.tt-help--up .tt-help__text {
    bottom: 100%;
    right: 0;
    top: auto;
    left: auto;
}

/* Direction variant: DOWNWARD (used when the icon sits at the top or
   middle of its container — popping upward would occlude the content
   above it). Same right-alignment so it visually rhymes with the
   default direction. */
.tt-help--down .tt-help__text {
    top: 100%;
    right: 0;
    bottom: auto;
    left: auto;
}

/* Show on hover / keyboard focus. The .tt-help__text:hover bridge
   prevents the popup from disappearing when the mouse leaves the icon
   and enters the popup (the icon ⇆ popup gap is 0 by design — see the
   anti-pitfall #2 note in the section header). flex column is used so
   the optional .tt-help__row children stack vertically with `gap`.
   For single-paragraph popups (no .tt-help__row), flex column with a
   single child renders identically to display: block. We deliberately
   avoid the :has() selector here for older Edge/Safari compatibility
   — a single uniform `display: flex` covers both single and multi-row
   variants without conditional rules. */
.tt-help:hover > .tt-help__text,
.tt-help:focus > .tt-help__text,
.tt-help:focus-within > .tt-help__text,
.tt-help__text:hover {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.tt-help__row {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
/* Label inherits the popup's font-size (12px), color
   (--text-secondary) and line-height (1.5) from .tt-help__text — the
   only visual difference is font-weight so the two rows still read as
   "term + definition" without breaking the tooltip's color/size
   contract with the canonical "Load all enabled exchanges" popup
   (per 2026-05-27 user directive: visual must be 1:1 with that
   tooltip; semantic hierarchy comes from weight alone).
   .tt-help__desc has NO rule — all properties are inherited from
   .tt-help__text. The class is kept in the markup purely as a
   structural marker so future tweaks (e.g. color emphasis under a
   future high-contrast theme) have an obvious hook. */
.tt-help__label {
    font-weight: 600;
    letter-spacing: 0.2px;
}
/* ==========================================================================
   End .tt-help
   ========================================================================== */

/* Exchange Group Styles */
.symbol-group {
    border-bottom: 1px solid var(--border-color);
}

.symbol-group:last-child {
    border-bottom: none;
}

.symbol-group-header {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    background: transparent;
    cursor: pointer;
    user-select: none;
    transition: background 0.15s;
}

.symbol-group-header:hover {
    background: rgba(255, 255, 255, 0.03);
}
body:not(.dark-theme) .symbol-group-header:hover {
    background: rgba(0, 0, 0, 0.03);
}

.symbol-group-header .group-toggle {
    width: 16px;
    height: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-secondary);
    transition: transform 0.2s;
    flex-shrink: 0;
}

.symbol-group-header .group-toggle svg {
    width: 12px;
    height: 12px;
}

.symbol-group.collapsed .symbol-group-header .group-toggle {
    transform: rotate(-90deg);
}

.symbol-group-header .group-icon {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}

.symbol-group-header .group-name {
    flex: 1;
    font-size: 13px;
    font-weight: 500;
    color: var(--text-primary);
}



.symbol-group-header .group-actions {
    display: flex;
    align-items: center;
    gap: 2px;
    opacity: 0;
    transition: opacity 0.15s;
}

.symbol-group-header:hover .group-actions {
    opacity: 1;
}

.symbol-group-header .group-action-btn {
    width: 22px;
    height: 22px;
    border: none;
    border-radius: 4px;
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s, color 0.15s;
}

.symbol-group-header .group-action-btn:hover:not(:disabled) {
    background: var(--bg-secondary);
    color: var(--text-primary);
}

.symbol-group-header .group-action-btn:disabled {
    opacity: 0.25;
    cursor: default;
    pointer-events: none;
}

.symbol-group-header .group-action-btn.delete:hover {
    color: #ef4444;
}

.symbol-group-header .group-action-btn svg {
    width: 14px;
    height: 14px;
}

.symbol-group-items {
    display: block;
}

.symbol-group.collapsed .symbol-group-items {
    display: none;
}

/* ============================================
   [FIX 2026-03-15] Two-level Exchange Hierarchy
   Exchange > Spot/Futures > Symbols
   ============================================ */

/* Exchange Level (Top) */
.symbol-exchange {
    border-bottom: 1px solid var(--border-color);
}

.symbol-exchange:last-child {
    border-bottom: none;
}

.symbol-exchange-header {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    background: transparent;
    cursor: pointer;
    user-select: none;
    transition: background 0.15s;
}

.symbol-exchange-header:hover {
    background: rgba(255, 255, 255, 0.03);
}

body:not(.dark-theme) .symbol-exchange-header:hover {
    background: rgba(0, 0, 0, 0.03);
}

.symbol-exchange-header .exchange-toggle {
    width: 16px;
    height: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-secondary);
    transition: transform 0.2s;
    flex-shrink: 0;
}

.symbol-exchange-header .exchange-toggle svg {
    width: 12px;
    height: 12px;
}

.symbol-exchange.collapsed .symbol-exchange-header .exchange-toggle {
    transform: rotate(-90deg);
}

.symbol-exchange-header .exchange-icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

/* [FIX] 2026-06-17 — exchange name hugs its content so the GMX chain switcher
   renders immediately to its right (instead of flex:1 pushing the switcher all
   the way to the action buttons). Long names still ellipsize. The action group
   is floated to the row's right edge with margin-left:auto. */
.symbol-exchange-header .exchange-name {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
}

.symbol-exchange-header .exchange-actions {
    display: flex;
    align-items: center;
    gap: 2px;
    margin-left: auto;
    opacity: 0;
    transition: opacity 0.15s;
}

.symbol-exchange-header:hover .exchange-actions {
    opacity: 1;
}

/* ==== [ADD] 2026-06-22 — inline market-type text buttons (现货/合约) ====
   Replaces the old two-level spot/futures sub-expanders. Sit to the right of
   the exchange name, hidden by default, revealed on row hover (mirrors the
   move/delete action buttons). A button stays visible + highlighted while its
   symbol list is expanded (.active), even when the row is not hovered. */
.symbol-exchange-header .exchange-market-types {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
    margin-left: 8px;
}

.symbol-exchange-header .market-type-btn {
    border: none;
    background: transparent;
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 12px;
    line-height: 1.4;
    font-weight: 500;
    color: var(--text-secondary);
    cursor: pointer;
    white-space: nowrap;
    opacity: 0;
    transition: opacity 0.15s, background 0.15s, color 0.15s;
}

.symbol-exchange-header:hover .market-type-btn {
    opacity: 1;
}

/* [CHG] 2026-06-22 (Hu Ge) — once ANY market of this exchange is expanded
   (exchange is not .collapsed), reveal BOTH market-type buttons (现货 + 合约)
   so the user can switch to the other one without hovering. */
.symbol-exchange:not(.collapsed) .market-type-btn {
    opacity: 1;
}

.symbol-exchange-header .market-type-btn:hover {
    background: var(--bg-secondary);
    color: var(--text-primary);
}

/* Expanded market: keep its button highlighted at all times — theme accent
   background + rounded pill so the active market type reads at a glance. */
.symbol-exchange-header .market-type-btn.active {
    opacity: 1;
    color: #fff;
    background: var(--accent-color);
    border-radius: 6px;
}

.symbol-exchange-header .market-type-btn.active:hover {
    color: #fff;
    background: var(--accent-color);
}

.symbol-exchange-header .exchange-action-btn {
    width: 22px;
    height: 22px;
    border: none;
    border-radius: 4px;
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s, color 0.15s;
}

.symbol-exchange-header .exchange-action-btn:hover:not(:disabled) {
    background: var(--bg-secondary);
    color: var(--text-primary);
}

.symbol-exchange-header .exchange-action-btn:disabled {
    opacity: 0.25;
    cursor: default;
    pointer-events: none;
}

.symbol-exchange-header .exchange-action-btn.delete:hover {
    color: #ef4444;
}

.symbol-exchange-header .exchange-action-btn svg {
    width: 14px;
    height: 14px;
}

/* ==== [ADD] 2026-06-15 — GMX chain switcher (sidebar exchange header) ====
   Multi-chain DEX brands (gmx) render a compact chain-switch dropdown next to
   the exchange name. Visual language mirrors the existing sidebar / server
   picker: --text-secondary idle -> --text-primary active, popover on
   --bg-secondary / --border-color. Single-venue brands never render this. */
.symbol-exchange-header .exchange-chain-switch {
    position: relative;
    flex-shrink: 0;
    margin-right: 4px;
}
.symbol-exchange-header .exchange-chain-btn {
    display: flex;
    align-items: center;
    gap: 3px;
    height: 22px;
    padding: 0 6px;
    border: 1px solid var(--border-color);
    border-radius: 4px;
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 12px;
    line-height: 1;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.symbol-exchange-header .exchange-chain-btn:hover {
    background: var(--bg-secondary);
    color: var(--text-primary);
    border-color: var(--text-secondary);
}
.symbol-exchange-header .exchange-chain-switch.open .exchange-chain-btn {
    background: var(--bg-secondary);
    color: var(--text-primary);
}
.symbol-exchange-header .exchange-chain-current {
    font-weight: 500;
    white-space: nowrap;
}
.symbol-exchange-header .exchange-chain-caret {
    width: 12px;
    height: 12px;
    transition: transform 0.2s;
}
.symbol-exchange-header .exchange-chain-switch.open .exchange-chain-caret {
    transform: rotate(180deg);
}
.symbol-exchange-header .exchange-chain-menu {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    min-width: 130px;
    padding: 4px;
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    border-radius: 6px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.28);
    z-index: 60;
    display: none;
}
.symbol-exchange-header .exchange-chain-switch.open .exchange-chain-menu {
    display: block;
}
.symbol-exchange-header .exchange-chain-option {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    height: 28px;
    padding: 0 8px;
    border-radius: 4px;
    color: var(--text-secondary);
    font-size: 13px;
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}
.symbol-exchange-header .exchange-chain-option:hover {
    background: var(--bg-tertiary);
    color: var(--text-primary);
}
.symbol-exchange-header .exchange-chain-option.active {
    color: var(--text-primary);
}
.symbol-exchange-header .exchange-chain-option-name {
    white-space: nowrap;
}
.symbol-exchange-header .exchange-chain-check {
    width: 13px;
    height: 13px;
    opacity: 0;
    flex-shrink: 0;
}
.symbol-exchange-header .exchange-chain-option.active .exchange-chain-check {
    opacity: 1;
}

.symbol-exchange-markets {
    display: block;
}

.symbol-exchange.collapsed .symbol-exchange-markets {
    display: none;
}

/* [CHG] 2026-06-22 — market level no longer renders its own header row; the
   spot/futures expanders moved to inline text buttons in the exchange header.
   The container just holds the symbol list and collapses by data flag. */
.symbol-market .symbol-group-items {
    display: block;
}

.symbol-market.collapsed .symbol-group-items {
    display: none;
}

/* Dragging state */
.symbol-group.dragging {
    opacity: 0.5;
}

.symbol-group.drag-over {
    border-top: 2px solid var(--accent-color);
}

/* Loading state for group */
.symbol-group-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
}

.symbol-group-loading::before {
    content: '';
    width: 16px;
    height: 16px;
    border: 2px solid rgba(128, 128, 128, 0.2);
    border-top-color: rgba(128, 128, 128, 0.8);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* Error state for group */
.symbol-group-error {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    color: #ef4444;
    font-size: 12px;
}

.symbol-list-header {
    display: grid;
    /* [FIX] 2026-06-24 — symbol column widened from 1.5fr to 2.5fr to fit long
       symbols (e.g. NCCOXAUJP, NCCOGOLD2USDUSDT, NCCO1OILWT on BingX) without
       ellipsis truncation. 2.5fr was sized for the default 5-column layout
       (symbol + price + change + changePercent + volume, gap 4px) so that the
       symbol cell holds ~130px in a 360px sidebar — enough for a 16-char
       symbol at the existing font-size:14px. Keep in sync with .symbol-item
       below and the JS grid templates (renderGroupSymbolsHtml /
       renderFavoritesFlatHtml / applySymbolColumnVisibility in tt.app.js):
       all four sites MUST share the same first-column unit. */
    grid-template-columns: 2.5fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
    padding: 4px 12px 4px 18px;
    font-size: 14px;
    color: var(--text-secondary);
    font-weight: 500;
    gap: 4px;
}

.symbol-list-header > div:not(:first-child) {
    display: flex;
    justify-content: flex-end;
}

.symbol-list-header .sortable {
    cursor: pointer;
    user-select: none;
    display: flex;
    align-items: center;
    gap: 4px;
    transition: color 0.2s;
}

.symbol-list-header > div:nth-child(1) .sortable {
    padding-left: 0;
}

.symbol-list-header .sortable:hover {
    color: var(--text-primary);
}

.symbol-list-header .sortable.active {
    color: var(--text-primary);
}

.symbol-list-header .sort-arrow {
    display: inline-block;
    width: 0;
    height: 0;
    opacity: 0;
    transition: opacity 0.2s;
}

.symbol-list-header .sortable.active .sort-arrow {
    opacity: 1;
}

.symbol-list-header .sortable.asc .sort-arrow {
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-bottom: 5px solid currentColor;
}

.symbol-list-header .sortable.desc .sort-arrow {
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 5px solid currentColor;
}

.symbol-tabs {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    margin: 0 12px 8px 12px;
}

.symbol-tabs-left {
    display: flex;
    gap: 6px;
    padding: 0;
    background: transparent;
    border-radius: 0;
    width: fit-content;
}

.symbol-tab {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    color: var(--text-primary);
    opacity: 0.5;
    background: transparent;
    border: none;
    cursor: pointer;
    border-radius: 4px;
    transition: opacity 0.2s ease, background 0.2s ease;
}
.symbol-tab svg {
    width: 13px;
    height: 13px;
}

/* [ADD] 2026-06-21 — Favorites (bookmark) tab icon a touch smaller than the
   Markets list icon (Hu Ge). Keeps the 16:15 bookmark aspect ratio. */
.symbol-tab[data-tab="favorites"] svg {
    width: 11px;
    height: 11px;
}

.symbol-tab:hover {
    opacity: 0.8;
    background: var(--border-color);
}

/* [CHG] 2026-06-22 (Hu Ge) — active tab uses the theme accent background +
   white icon (rounded via the base .symbol-tab border-radius), matching the
   active spot/futures market-type button. */
.symbol-tab.active {
    background: var(--accent-color);
    color: #fff;
    opacity: 1;
}

.symbol-tab.active:hover {
    background: var(--accent-color);
    color: #fff;
    opacity: 1;
}

/* Symbol Search Box */
.symbol-search-box {
    position: relative;
    display: flex;
    align-items: center;
    width: 170px;
    height: 28px;
    background: var(--bg-secondary);
    border-radius: 6px;
    padding: 0 10px;
    gap: 6px;
    overflow: visible;
}

.symbol-search-box .search-icon {
    color: var(--text-primary);
    flex-shrink: 0;
}

.symbol-search-input {
    flex: 1;
    min-width: 0;
    background: transparent;
    border: none;
    outline: none;
    color: var(--text-primary);
    font-size: 11px;
    padding: 0;
    text-overflow: ellipsis;
    overflow: hidden;
}

.symbol-search-input::placeholder {
    color: var(--text-secondary);
    opacity: 0.6;
}

.symbol-search-input:focus {
    outline: none;
}

.symbol-search-box:focus-within {
    background: var(--bg-tertiary);
    box-shadow: 0 0 0 1px var(--border-color);
}

.search-clear-icon {
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 14px;
    line-height: 1;
    flex-shrink: 0;
    display: none;
    margin-left: 4px;
}

.symbol-search-input:not(:placeholder-shown) ~ .search-clear-icon {
    display: block;
}

.search-clear-icon:hover {
    color: var(--text-primary);
}

.symbol-list-container {
    flex: 1;
    overflow: hidden;
    position: relative;
}

.symbol-list {
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 4px 0;
}

.symbol-list::-webkit-scrollbar {
    width: 3px;
    opacity: 0;
}

.symbol-list-container:hover .symbol-list::-webkit-scrollbar-thumb {
    background: #c8ccd8;
}

.symbol-list::-webkit-scrollbar-track {
    background: transparent;
}

.symbol-list::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 2px;
    transition: background 0.2s;
}

.symbol-list::-webkit-scrollbar-thumb:hover {
    background: #a0a4b0;
}

.symbol-item {
    padding: 8px 12px 8px 18px;
    cursor: pointer;
    transition: background 0.15s;
    display: grid;
    /* [FIX] 2026-06-24 — see .symbol-list-header above for 2.5fr rationale. */
    grid-template-columns: 2.5fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
    align-items: center;
    gap: 4px;
    position: relative;
    border-bottom: 1px solid var(--border-color);
}

.symbol-item:hover {
    background: var(--bg-tertiary);
}

.symbol-item.selected {
    background: var(--bg-tertiary);
}

.symbol-item.active {
    background: transparent;
}

.symbol-name {
    font-size: 14px;
    font-weight: 400;
    color: var(--text-primary) !important;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    display: flex;
    align-items: center;
    gap: 4px;
}

/* [ADD] 2026-06-21 — App-parity flat Favorites row: pair name stacked over a
   small `Exchange · Type` venue label (10px). Only the Favorites tab uses the
   `.symbol-name-text` wrapper; the All tab keeps the plain inline name. */
.symbol-name-text {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-width: 0;
    line-height: 1.2;
}

.symbol-name-pair {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.symbol-venue {
    font-size: 10px;
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.symbol-item--fav {
    align-items: center;
}

@media (pointer: fine) {
    .symbol-item--fav {
        cursor: grab;
    }
}

body.watchlist-dragging {
    user-select: none;
}

.symbol-item--fav.symbol-item--dragging {
    cursor: grabbing;
    opacity: 0.35;
}

.symbol-item--fav.symbol-item--drop-before {
    box-shadow: inset 0 2px 0 var(--accent-color);
}

.symbol-item--fav.symbol-item--drop-after {
    box-shadow: inset 0 -2px 0 var(--accent-color);
}

/* [FIX] 2026-06-21 — keep the flat Favorites-row bookmark identical to the
   Markets-tab one. The two-line row (pair + venue) is taller, so the default
   `top:10px;bottom:10px` stretch would elongate the bookmark. Pin it to a
   fixed height, vertically centred, so it matches the Markets list glyph. */
.symbol-item--fav .favorite-icon {
    top: 50%;
    bottom: auto;
    height: 14px;
    transform: translateY(-50%);
}

.coin-icon-wrap {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
}

.coin-icon {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    object-fit: contain;
}

.coin-icon-fallback {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #a78bfa;
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

/* Bookmark: rectangle with right-side concave triangle notch via clip-path */
.favorite-icon {
    position: absolute;
    left: 0;
    top: 10px;
    bottom: 10px;
    width: 16px;
    cursor: pointer;
    z-index: 1;
    opacity: 0;
    transition: opacity 0.15s;
}

/* Bookmark with smooth circular concave on the right side using radial-gradient mask */
.favorite-icon::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    width: 11px;
    height: 100%;
    background: var(--text-secondary);
    /* V-notch pointing left at right edge center */
    clip-path: polygon(0 0, 100% 0, 100% 5%, 65% 50%, 100% 95%, 100% 100%, 0 100%);
    transition: background 0.15s;
}

.symbol-item:hover .favorite-icon {
    opacity: 0.4;
}

.favorite-icon.active {
    opacity: 1 !important;
}

.favorite-icon.active::before {
    background: var(--state-active);
}

.symbol-item:hover .favorite-icon.active {
    opacity: 1;
}

.symbol-price {
    font-family: var(--font-price);
    font-size: 14px;
    font-weight: 400;
    text-align: right;
}

.symbol-price.up {
    color: var(--price-up);
}

.symbol-price.down {
    color: var(--price-down);
}

.symbol-change {
    font-family: var(--font-price);
    font-size: 14px;
    font-weight: 500;
    text-align: right;
}

.symbol-change.up {
    color: var(--price-up);
}

.symbol-change.down {
    color: var(--price-down);
}

.symbol-volume {
    font-family: var(--font-price);
    font-size: 14px;
    font-weight: 400;
    text-align: right;
    color: var(--text-primary);
}

.symbol-deriv {
    font-family: var(--font-price);
    font-size: 13px;
    font-weight: 500;
    text-align: right;
    color: var(--text-primary);
}
.symbol-deriv.up { color: var(--price-up); }
.symbol-deriv.down { color: var(--price-down); }
.symbol-deriv.neutral { color: var(--text-secondary); }

/* ========== Center Column: Chart + Bottom Panel ========== */
.center-column {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    overflow: hidden;
}

/* ========== Center Panel: Chart ========== */
.center-panel {
    flex: 1;
    background: var(--bg-primary);
    display: flex;
    flex-direction: column;
    position: relative;
    min-width: 0;
    overflow: hidden;
}

/* ========== Bottom Resizer ========== */
/* [CHG] 2026-05-24 r33 — height 4 → 5 + --resizer-color (see :root). */
.bottom-resizer {
    height: 5px;
    cursor: row-resize;
    background: var(--resizer-color);
    flex-shrink: 0;
    display: none;
}
.bottom-resizer:hover,
.bottom-resizer.dragging { background: var(--text-secondary); }

/* ========== Bottom Panel ========== */
.bottom-panel {
    display: none;
    flex-direction: column;
    height: 200px;
    min-height: 80px;
    max-height: calc(100vh - 50px);
    background: var(--bg-primary);
    border-top: 1px solid var(--border-color);
    overflow: hidden;
    flex-shrink: 0;
}
.bottom-panel.active { display: flex; }
.bottom-resizer.active { display: block; }

.bp-tabs {
    display: flex;
    align-items: stretch;
    gap: 0;
    padding: 0 12px 0 0;
    height: 38px;
    min-height: 38px;
    border-bottom: 1px solid var(--border-color);
    background: color-mix(in srgb, var(--border-color) 50%, var(--bg-primary));
    flex-shrink: 0;
}
.bp-tab {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 0 14px;
    font-size: 14px;
    font-weight: 500;
    color: var(--text-secondary);
    background: none;
    border: none;
    border-radius: 6px 6px 0 0;
    cursor: pointer;
    white-space: nowrap;
    margin-bottom: -1px;
    transition: color .15s, background .15s;
}
.bp-tab-icon {
    flex-shrink: 0;
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
}
.bp-tab:hover { color: var(--text-primary); background: color-mix(in srgb, var(--border-color) 70%, var(--bg-primary)); }
.bp-tab.active { color: var(--text-primary); background: var(--bg-primary); border-bottom: 1px solid var(--bg-primary); }
.bp-tab.bp-tab-hidden { display: none; }
.bp-tab.bp-tab-dragging { opacity: .4; }
.bp-tab.bp-tab-drag-over { border-left: 2px solid var(--text-primary); }
.bp-tab-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    margin-left: 6px;
    font-size: 14px;
    line-height: 1;
    border-radius: 3px;
    opacity: 0;
    transition: opacity .15s, background .12s;
    vertical-align: middle;
}
.bp-tab:hover .bp-tab-close,
.bp-tab.active .bp-tab-close { opacity: .5; }
.bp-tab-close:hover { opacity: 1 !important; background: var(--bg-input); }

.bp-tabs-actions {
    margin-left: auto;
    display: flex; align-items: center; gap: 2px;
}
.bp-action-btn {
    width: 28px; height: 28px;
    display: flex; align-items: center; justify-content: center;
    background: none; border: none; border-radius: 4px;
    color: var(--text-primary); cursor: pointer;
    transition: background .15s;
}
.bp-action-btn:hover { background: var(--bg-input); }

.bottom-panel.bp-minimized {
    height: auto !important; min-height: 0 !important; max-height: none !important;
}
.bottom-panel.bp-minimized .bp-content,
.bottom-panel.bp-minimized .bp-sub-tabs { display: none !important; }

.bottom-panel.bp-maximized {
    height: auto !important; min-height: 0 !important; max-height: none !important;
    flex: 1 !important;
}
.center-column.bp-chart-hidden > .center-panel { display: none !important; }
.center-column.bp-chart-hidden > .bottom-resizer { display: none !important; }

.bp-content { flex: 1; overflow: hidden; display: flex; flex-direction: column; }
.bp-pane { display: none; flex: 1; flex-direction: column; overflow: hidden; }
.bp-pane.active { display: flex; }

.bp-sub-tabs {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 4px 12px 0;
    min-height: 48px;
    background: var(--bg-secondary, var(--bg-primary));
    flex-shrink: 0;
}
.bp-sub-tab {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 5px 12px;
    font-size: 14px;
    font-weight: 400;
    color: var(--text-secondary);
    background: none;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    white-space: nowrap;
    transition: color .15s, background .15s;
}
.bp-sub-tab:hover { color: var(--text-primary); background: var(--bg-input); }
.bp-sub-tab.active { color: var(--text-primary); background: var(--bg-tertiary); font-weight: 500; }
.bp-sub-tab svg { width: 14px; height: 14px; flex-shrink: 0; }

.bp-sub-content { flex: 1; overflow: hidden; display: flex; flex-direction: column; }
.bp-sub-pane { display: none; flex: 1; overflow: auto; padding: 0; }
.bp-sub-pane.active { display: block; }
.bp-sub-pane::-webkit-scrollbar { width: 3px; height: 3px; }
.bp-sub-pane::-webkit-scrollbar-track { background: transparent; }
.bp-sub-pane::-webkit-scrollbar-thumb { background: transparent; border-radius: 2px; }
.bp-sub-pane:hover::-webkit-scrollbar-thumb { background: #c8ccd8; }
.bp-sub-pane::-webkit-scrollbar-thumb:hover { background: #a0a4b0; }

/* ========== Scripts Panel ========== */
.sc-panel { display: flex; flex-direction: column; flex: 1; min-width: 0; overflow: hidden; }
.sc-panel .bp-sub-content { min-width: 0; }
.sc-panel .bp-sub-pane { min-width: 0; overflow-x: clip; }
.sc-panel #scMyList,
.sc-panel #scFavList,
.sc-panel #scInvitedList,
.sc-panel #scRunningList { min-width: 0; max-width: 100%; }
.sc-toolbar-spacer { flex: 1; }
.sc-actions { display: flex; align-items: center; gap: 6px; }
.sc-btn {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 10px;
    font-size: 14px;
    color: var(--text-primary);
    background: none;
    border: 1px solid var(--border-color);
    border-radius: 4px;
    cursor: pointer;
    transition: color .15s, background .15s, border-color .15s;
    white-space: nowrap;
}
.sc-btn:hover { background: var(--bg-input); }
.sc-btn-primary { border-color: var(--text-secondary); }
.sc-btn-primary:hover { background: var(--bg-input); }
/* [REMOVED] 2026-05-30 v20260530.1 — .sc-new-wrap / .sc-new-menu /
 * .sc-new-menu-open / .sc-new-item rules deleted together with the New ▾
 * button DOM (虎哥 2026-05-30 12:58). The Scripts Library row no longer
 * has any creation entry point — new scripts come exclusively from the
 * editor topbar Templates ▼. Floating-portal selector
 * (.sc-new-menu.sc-dropdown--floating) was also pruned below. */

.sc-search-wrap {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 4px 8px;
    border: 1px solid var(--border-color);
    border-radius: 4px;
    background: var(--bg-secondary);
    max-width: 200px;
}
.sc-search-wrap svg { opacity: .7; flex-shrink: 0; }
.sc-search {
    border: none;
    background: transparent;
    color: var(--text-primary);
    font-size: 14px;
    outline: none;
    width: 100%;
}
.sc-search::placeholder { color: var(--text-secondary); opacity: .6; }

.sc-filter-dropdown { position: relative; }
.sc-filter-trigger {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 4px 10px; font-size: 14px; font-weight: 500;
    color: var(--text-primary); background: none; border: 1px solid var(--border-color);
    border-radius: 6px; cursor: pointer; transition: color .15s, background .15s;
}
.sc-filter-trigger:hover { background: var(--bg-input); }
.sc-filter-arrow { transition: transform .2s; }
.sc-filter-dropdown.open .sc-filter-arrow { transform: rotate(180deg); }
/* [FIX] 2026-05-29 — YeScript UI Redesign §5.2 — Sort menu right-aligns
 * to the trigger. The Sort trigger sits at the right edge of .sc-toolbar
 * (after the search input and type filter pills), so a left-anchored
 * menu used to clip off the right side of the panel. Mirrors the New
 * menu fix above. */
.sc-filter-menu {
    display: none; position: absolute; top: calc(100% + 4px); right: 0; z-index: 100;
    min-width: 150px; padding: 4px 0;
    background: var(--popover-bg); border: 1px solid var(--popover-border);
    border-radius: 6px; box-shadow: var(--popover-shadow);
}
.sc-filter-dropdown.open .sc-filter-menu { display: block; }

/* [CHG] 2026-05-19 v3 — floating modifier for scripts panel popovers
 *   (#scFilterMenu and any future portaled popover). When
 *   .sc-dropdown--floating is added by JS (tt.scripts.js
 *   _floatDropdownToBody), the menu is portalled into <body> with inline
 *   position:fixed + top/left from trigger rect, so it can never be
 *   clipped by an ancestor's overflow:hidden. This is primarily for
 *   mobile where .bottom-panel / .bp-pane / .sc-panel all clip; on
 *   desktop the absolute-positioned default works fine.
 *
 *   Override `display: none` so the portalled element is actually
 *   visible regardless of the trigger's `.sc-filter-dropdown.open`
 *   class chain (the old chained selectors no longer apply once the
 *   element is moved to <body>).
 *
 *   [CHG] 2026-05-30 v20260530.1 — #scNewMenu selector removed; the New
 *   ▾ button was deleted (虎哥 2026-05-30 12:58).
 */
.sc-filter-menu.sc-dropdown--floating {
    display: block;
    z-index: 9700;
    min-width: 180px;
    max-height: 60vh;
    overflow-y: auto;
}
/* [ADD] 2026-05-29 — YeScript UI Redesign (实施规范 §3.2.2) — same portal
   safety net for the per-row "···" menu when _floatDropdownToBody moves
   it to <body> on mobile. Without max-height a long menu (8 actions for
   a published-invite-only Strategy script) could overflow the viewport
   bottom; overflow-y:auto lets the user scroll inside the popover. */
.sc-more-menu.sc-dropdown--floating {
    display: block;
    z-index: 9700;
    /* [FIX] 2026-05-29 v20260529.16 — match the narrowed base rule
     * (min 180 / max 220) so portaled menus on mobile/tablet stay in
     * the same 180-220 band as desktop. */
    min-width: 180px;
    max-width: 220px;
    max-height: 60vh;
    overflow-y: auto;
}
.sc-filter-option {
    display: flex; align-items: center; gap: 8px; width: 100%;
    padding: 7px 12px; font-size: 14px;
    color: var(--text-primary); background: none; border: none;
    cursor: pointer; text-align: left; transition: background .1s;
}
.sc-filter-option:hover { background: var(--bg-hover); }
.sc-filter-option.active { font-weight: 500; }

/* [ADD] 2026-05-27 — Phase 0 / P0-2 + §0-C mobile contract:
 * Filter dropdown options must clear the iOS HIG 44pt minimum touch
 * target on mobile / coarse-pointer devices. Desktop keeps the
 * compact 30px row for visual density. Same breakpoint family as
 * .ai-panel--mobile / .news-drawer mobile / strategy-mobile-switcher
 * (chart.ejs L4216) to avoid creating a new responsive boundary.
 * Reason: the filter menu now has 4 options (was 3); on a 320px-wide
 * mobile sheet a sub-44px row is genuinely hard to tap accurately. */
@media (max-width: 768px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    .sc-filter-option {
        min-height: 44px;
        padding: 10px 14px;
        font-size: 14px;
    }
}

.sc-list {
    flex: 1;
    overflow-y: auto;
    padding: 4px 12px;
}
.sc-list::-webkit-scrollbar { width: 3px; }
.sc-list::-webkit-scrollbar-track { background: transparent; }
.sc-list::-webkit-scrollbar-thumb { background: transparent; border-radius: 2px; }
.sc-list:hover::-webkit-scrollbar-thumb { background: #c8ccd8; }
.sc-list::-webkit-scrollbar-thumb:hover { background: #a0a4b0; }

/* [FIX] 2026-05-29 — Empty state copy 13→14px (虎哥反馈). */
.sc-empty {
    display: none;
    padding: 32px 16px;
    text-align: center;
    color: var(--text-secondary);
    font-size: 14px;
}
.sc-empty.visible { display: block; }

/* [FIX] 2026-08-03 — Community grid uses the same explicit pager UX as the
   public YeScript community page while keeping Chart's compact styling. */
.sc-community-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 12px 10px 16px;
}
.sc-community-pager[hidden] { display: none; }
.sc-community-pager button {
    min-width: 34px;
    height: 32px;
    padding: 0 10px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    background: transparent;
    color: var(--text-primary);
    font-size: 14px;
    cursor: pointer;
}
.sc-community-pager button:hover:not(:disabled) {
    border-color: var(--accent-color);
    background: var(--bg-input);
}
.sc-community-pager button:disabled {
    cursor: not-allowed;
    opacity: 0.45;
}
.sc-community-pager__numbers {
    display: flex;
    align-items: center;
    gap: 6px;
}
.sc-community-pager__page.is-current {
    border-color: var(--accent-color);
    background: var(--bg-input);
    opacity: 1;
}
.sc-community-pager__gap,
.sc-community-pager__label {
    color: var(--text-primary);
    font-size: 14px;
}
.sc-community-pager__gap {
    min-width: 16px;
    text-align: center;
    opacity: 0.72;
}
.sc-community-pager__label {
    min-width: 64px;
    text-align: center;
    opacity: 0.82;
}

.sc-card {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-radius: 5px;
    cursor: pointer;
    transition: background .12s;
}
.sc-card:hover { background: var(--bg-input); }
.sc-card-info { flex: 1; min-width: 0; }
.sc-card-name {
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* [CHG] 2026-05-28 — Phase 2 / P2-13k — meta row bumped 11→12px so the
   author / timestamp / version line is readable at desktop view distance.
   Tradeiz finance UI standard is min 12px for non-primary descriptive
   text (see tradeiz-home / aggregations rule). Letter spacing kept tight
   so the row doesn't visually compete with the 14px script name. */
/* [FIX] 2026-05-29 — legacy v1 card meta 12→14px to match Scripts Library
 * unified font (虎哥反馈). v3 grid card uses .sc-gc-meta (already 14px). */
.sc-card-meta {
    font-size: 14px;
    color: var(--text-secondary);
    margin-top: 3px;
    display: flex;
    align-items: center;
    gap: 8px;
}

/* [ADD] 2026-05-28 — Phase 2 / P2-13k — left-side 18px type indicator.
   Coloured boxes that group scripts by kind at a glance. Sits BEFORE
   `.sc-card-info` inside `.sc-card`. Uses tinted background +
   coloured stroke so the glyph reads even in the dense list. */
.sc-type-icon {
    flex: 0 0 28px;
    width: 28px;
    height: 28px;
    border-radius: 5px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid transparent;
}
.sc-type-icon--strategy  {
    color: #4caf50;
    background: rgba(76,175,80,0.10);
    border-color: rgba(76,175,80,0.30);
}
.sc-type-icon--indicator {
    color: #2196f3;
    background: rgba(33,150,243,0.10);
    border-color: rgba(33,150,243,0.30);
}
.sc-type-icon--library   {
    color: #ab47bc;
    background: rgba(171,71,188,0.10);
    border-color: rgba(171,71,188,0.30);
}
/* [ADD] 2026-05-27 — Phase 1 / P1-5: clickable author name in card meta.
   No href on the <a> since we delegate to JS; tabindex=0 + role=button is
   wrong here because semantically this navigates ("Show this author's
   page"), so we keep it as a plain inline-link with hover/focus chrome
   that matches the industry norm (TradingView script-card author links).
   Mobile-friendly: 44×44 hit area is in the surrounding cell padding,
   the visible text just inherits font-size from sc-card-meta. */
.sc-author-link {
    color: var(--text-secondary);
    text-decoration: underline;
    text-decoration-color: rgba(255, 255, 255, 0.18);
    text-underline-offset: 2px;
    cursor: pointer;
    transition: color 120ms ease, text-decoration-color 120ms ease;
}
.sc-author-link:hover,
.sc-author-link:focus-visible {
    color: var(--accent-color);
    text-decoration-color: var(--accent-color);
    outline: none;
}
body:not(.dark-theme) .sc-author-link {
    text-decoration-color: rgba(0, 0, 0, 0.18);
}
.sc-tag {
    display: inline-block;
    padding: 1px 6px;
    font-size: 10px;
    border-radius: 3px;
    background: var(--bg-input);
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: .3px;
}
.sc-tag-strategy { color: #4caf50; }
.sc-tag-indicator { color: #2196f3; }
/* [FIX] 2026-05-16 — library tag colour (P0-2.2). Purple distinguishes it
   from indicator (blue) and strategy (green). */
.sc-tag-library { color: #ab47bc; }

.sc-card-actions {
    display: flex;
    align-items: center;
    gap: 2px;
    opacity: 0;
    transition: opacity .15s;
}
.sc-card:hover .sc-card-actions { opacity: 1; }
/* [BUMP] 2026-05-28 — Phase 2 / P2-13c — bounding box 26x26 → 28x28
   to leave a 5px radial gutter around the new 18px SVG icons (was
   5.5px gutter for 15px icons). Keeps the row visually balanced and
   matches TradingView/Binance action-row button density. */
.sc-action-btn {
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    border-radius: 4px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background .15s;
}
.sc-action-btn:hover { background: var(--bg-secondary); }
.sc-action-btn.sc-favorited { color: var(--state-active); }
.sc-action-btn.sc-favorited:hover { color: var(--state-active); opacity: .8; }

.sc-card-fav-count {
    font-size: 11px;
    color: var(--text-secondary);
    min-width: 20px;
    text-align: center;
}

.chart-header {
    padding: 6px 16px;
    border-bottom: 1px solid var(--border-color);
    display: flex;
    justify-content: space-between;
    align-items: center;
    height: 32px;
    box-sizing: border-box;
}

.chart-symbol-info {
    display: flex;
    align-items: center;
    gap: 16px;
    flex: 1;
}

/* Indicator Toolbar (below chart header) */
.indicator-toolbar {
    padding: 0;
    display: flex;
    align-items: center;
    height: 36px;
    box-sizing: border-box;
    background: var(--bg-primary);
    border-bottom: 1px solid var(--border-color);
    flex-shrink: 0;
    position: relative;
}

/* [2026-03-09] Unified chart toolbar typography */
.indicator-toolbar,
.indicator-toolbar *:not(svg):not(path):not(circle):not(rect):not(line):not(polyline):not(polygon) {
    font-size: 12px !important;
}

/* Toolbar Scroll Buttons */
.toolbar-scroll-btn {
    display: none;
    width: 24px;
    height: 36px;
    padding: 0;
    border: none;
    background: var(--bg-primary);
    color: var(--text-secondary);
    cursor: pointer;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    z-index: 5;
    transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
}

.toolbar-scroll-btn:hover {
    color: var(--text-primary);
    background: var(--bg-secondary);
}

.toolbar-scroll-btn svg {
    width: 16px;
    height: 16px;
}

.toolbar-scroll-btn.visible {
    display: flex;
}

.toolbar-scroll-left {
    border-right: 1px solid var(--border-color);
}

.toolbar-scroll-right {
    border-left: 1px solid var(--border-color);
}

/* Stronger hover feedback for chart-toolbar left/right scroll buttons */
.indicator-toolbar .toolbar-scroll-left:hover,
.indicator-toolbar .toolbar-scroll-right:hover {
    background: color-mix(in srgb, var(--bg-tertiary) 78%, var(--bg-primary));
    box-shadow: none;
}

/* Scrollable Toolbar Container - FIX: Changed to flex-start after removing indicator-toolbar-right */
.indicator-toolbar-scroll {
    display: flex;
    justify-content: flex-start;
    align-items: center;
    flex: 1;
    min-width: 0;
    overflow-x: auto;
    overflow-y: visible;
    -webkit-overflow-scrolling: touch;
    padding: 4px 12px;
    gap: 8px;
    scroll-behavior: smooth;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.indicator-toolbar-scroll::-webkit-scrollbar {
    display: none;
}

/* [FIX] 2026-05-19 v8 — iOS Safari overlap prevention.
 * Same root cause as .yes-toolbar (flex children get compressed to 0 px
 * inside overflow-x:auto on iOS). Lock every direct child to its
 * content width. */
.indicator-toolbar-scroll > * {
    flex: 0 0 auto;
    min-width: max-content;
}

.indicator-toolbar-left {
    display: flex;
    align-items: center;
    gap: 0;
    flex-shrink: 0;
}

/* Small type label before each chart-toolbar module */
.toolbar-type-tag {
    font-size: 10px !important;
    font-weight: 500;
    color: color-mix(in srgb, var(--text-tertiary) 65%, transparent);
    line-height: 1;
    padding: 0 6px 0 2px;
    white-space: nowrap;
    flex-shrink: 0;
    letter-spacing: 0.2px;
    text-shadow: 0 1px 0 color-mix(in srgb, var(--bg-primary) 70%, transparent);
    opacity: 0.8;
    transform: translateY(-2px);
}

/* Keep module titles (Time / Chart Type / MA / Indicators) smaller than toolbar text */
.indicator-toolbar .toolbar-type-tag {
    font-size: 10px !important;
}

/* Toolbar Vertical Divider */
/* [FIX] 2026-05-22 — switched background from --border-color to
 * --toolbar-sep. Same rationale as .toolbar-divider — --border-color is
 * intentionally near-invisible so the vertical separators in the top
 * bar between button groups were unreadable in both themes. */
.toolbar-divider-v {
    width: 1px;
    height: 20px;
    background: var(--toolbar-sep);
    margin: 0 8px;
    flex-shrink: 0;
}

/* Chart Toolbar (Interval Selector) */
.chart-toolbar {
    display: flex;
    align-items: center;
    gap: 4px;
}

.interval-btn {
    padding: 3px 10px;
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
    background: transparent;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    transition: all 0.2s;
}

.interval-btn:hover {
    background: var(--bg-tertiary);
}

.interval-btn.active {
    background: var(--bg-tertiary);
    color: var(--text-primary);
    font-weight: 600;
}

/* Toolbar Dropdown Arrow (common style) */
.indicator-toolbar .dropdown-arrow,
.top-bar-chart-btn .dropdown-arrow,
.top-bar-core-btn .dropdown-arrow,
.sub-ind-dropdown-btn .dropdown-arrow,
.top-bar-left .top-bar-overlay-dropdown > .toolbar-dropdown-btn .dropdown-arrow,
.toolbar-dropdown-btn .dropdown-arrow,
.pt-toolbar-split-arrow .dropdown-arrow {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    color: var(--text-primary);
    stroke-width: var(--chart-icon-stroke);
    transition: transform 0.2s;
}

.dropdown-arrow :is(polyline, path, line) {
    stroke-width: var(--chart-icon-stroke);
}

/* Interval Dropdown */
.interval-dropdown {
    position: relative;
}

.interval-dropdown-btn {
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    transition: background 0.2s;
}

.interval-dropdown-btn svg {
    width: 14px;
    height: 14px;
    color: var(--text-primary);
}

.interval-dropdown-btn:hover {
    background: var(--bg-tertiary);
}

.interval-dropdown-menu {
    display: none;
    /* FIX: Use position:fixed to prevent clipping by parent overflow:hidden */
    position: fixed;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 8px;
    box-shadow: var(--popover-shadow);
    z-index: 10000;
    width: 120px;
    max-height: calc(100vh - 120px);
    overflow-y: auto;
}

.interval-dropdown-menu::-webkit-scrollbar {
    width: 3px;
    opacity: 0;
}

.interval-dropdown-menu::-webkit-scrollbar-track {
    background: transparent;
}

.interval-dropdown-menu::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 2px;
    transition: background 0.2s;
}

.interval-dropdown-menu:hover::-webkit-scrollbar-thumb {
    background: #c8ccd8;
}

.interval-dropdown-menu::-webkit-scrollbar-thumb:hover {
    background: #a0a4b0;
}

.interval-dropdown.open .interval-dropdown-menu {
    display: block;
}

.interval-option {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 12px;
    font-size: 14px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background 0.2s;
}

.interval-option:hover {
    background: var(--bg-hover);
}

.interval-option span {
    flex: 1;
}

.interval-option .star-icon {
    width: 14px;
    height: 14px;
    fill: none;
    stroke: var(--text-primary);
    stroke-width: 1.2;
    transition: all 0.2s;
    opacity: 0.5;
}

.interval-option.favorite .star-icon.filled,
.interval-option .star-icon.filled,
.interval-option.favorite .star-icon {
    fill: var(--text-primary);
    stroke: var(--text-primary);
    opacity: 1;
}

.interval-option:hover .star-icon {
    stroke: var(--text-primary);
}

/* Chart Type Selector */
.chart-type-selector {
    display: flex;
    align-items: center;
    gap: 2px;
}

.chart-type-btn {
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    background: transparent;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    transition: all 0.2s;
}

.chart-type-btn svg {
    width: var(--chart-type-shortcut-icon-size);
    height: var(--chart-type-shortcut-icon-size);
    color: var(--text-primary);
}

.chart-type-btn:hover svg,
.chart-type-btn.active svg {
    color: var(--text-primary);
}

.chart-type-btn:hover {
    background: var(--bg-tertiary);
}

.chart-type-btn.active {
    background: var(--bg-tertiary);
}

/* Chart Type Dropdown */
.chart-type-dropdown {
    position: relative;
}

.chart-type-dropdown-btn {
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    transition: background 0.2s;
}

.chart-type-dropdown-btn svg {
    width: 14px;
    height: 14px;
    color: var(--text-primary);
}

.chart-type-dropdown-btn:hover {
    background: var(--bg-tertiary);
}

.chart-type-dropdown-menu {
    display: none;
    /* FIX: Use position:fixed to prevent clipping by parent overflow:hidden */
    position: fixed;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 8px;
    box-shadow: var(--popover-shadow);
    z-index: 10000;
    width: 220px;
    max-height: calc(100vh - 120px);
    overflow-y: auto;
    padding: 6px 0;
}

.chart-type-dropdown-menu::-webkit-scrollbar {
    width: 3px;
    opacity: 0;
}

.chart-type-dropdown-menu::-webkit-scrollbar-track {
    background: transparent;
}

.chart-type-dropdown-menu::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 2px;
    transition: background 0.2s;
}

.chart-type-dropdown-menu:hover::-webkit-scrollbar-thumb {
    background: #c8ccd8;
}

.chart-type-dropdown-menu::-webkit-scrollbar-thumb:hover {
    background: #a0a4b0;
}

.chart-type-dropdown-menu.show {
    display: block;
}

.chart-type-option {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 12px;
    cursor: pointer;
    transition: background 0.2s;
}

.chart-type-option:hover {
    background: var(--bg-hover);
}

.chart-type-option .chart-type-icon {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    flex-shrink: 0;
    color: var(--text-primary);
    margin-right: 10px;
}

.chart-type-option span {
    flex: 1;
    font-size: 14px;
    color: var(--text-primary);
}


.chart-type-option .star-icon {
    width: 14px;
    height: 14px;
    fill: none;
    stroke: var(--text-primary);
    stroke-width: 1.2;
    cursor: pointer;
    transition: all 0.2s;
    opacity: 0.5;
}

.chart-type-option.favorite .star-icon.filled,
.chart-type-option .star-icon.filled,
.chart-type-option.favorite .star-icon {
    fill: var(--text-primary);
    stroke: var(--text-primary);
    opacity: 1;
}

.chart-type-option:hover .star-icon {
    stroke: var(--text-primary);
}

/* ============================================
   MA Selector Styles
   ============================================ */
.ma-selector {
    display: flex;
    align-items: center;
    gap: 2px;
}

.ma-btn {
    padding: 3px 10px;
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
    background: transparent;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    transition: all 0.2s;
    position: relative;
}

.ma-btn:hover {
    background: var(--bg-tertiary);
}

/* MA toolbar buttons are stateless by design (no active visual state). */

.ma-dropdown {
    position: relative;
}

.ma-dropdown-btn {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 4px 8px;
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
    background: transparent;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    transition: all 0.2s;
    white-space: nowrap;
}

.ma-dropdown-btn svg {
    width: 12px;
    height: 12px;
    flex-shrink: 0;
}

.ma-dropdown-btn:hover {
    background: var(--bg-tertiary);
}

.ma-dropdown-menu {
    display: none;
    /* FIX: Use position:fixed to prevent clipping by parent overflow:hidden */
    position: fixed;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 8px;
    box-shadow: var(--popover-shadow);
    z-index: 10000;
    width: 220px;
    max-height: calc(100vh - 120px);
    overflow-y: auto;
    padding: 6px 0;
}

.ma-dropdown-menu::-webkit-scrollbar {
    width: 3px;
}

.ma-dropdown-menu::-webkit-scrollbar-track {
    background: transparent;
}

.ma-dropdown-menu::-webkit-scrollbar-thumb {
    background: rgba(200, 204, 216, 0.5);
    border-radius: 2px;
    transition: background 0.2s;
}

.ma-dropdown-menu::-webkit-scrollbar-thumb:hover {
    background: #a0a4b0;
}

.ma-dropdown-menu.show {
    display: block;
}

.ma-option {
    display: flex;
    align-items: center;
    padding: 8px 12px;
    font-size: 14px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background 0.2s;
}

.ma-option span {
    flex: 1;
}

.ma-option:hover {
    background: var(--bg-hover);
}

.ma-option .star-icon {
    width: 14px;
    height: 14px;
    fill: none;
    stroke: var(--text-primary);
    stroke-width: 1.2;
    transition: fill 0.2s, stroke 0.2s;
    flex-shrink: 0;
    margin-left: auto;
    opacity: 0.5;
    opacity: 0.5;
}

.ma-option.favorite .star-icon.filled,
.ma-option .star-icon.filled,
.ma-option.favorite .star-icon {
    fill: var(--text-primary);
    stroke: var(--text-primary);
    opacity: 1;
}

.ma-option:hover .star-icon {
    stroke: var(--text-primary);
}

/* Custom MA actions */
.ma-option-actions {
    display: flex;
    align-items: center;
    gap: 6px;
}

.ma-option .delete-icon {
    width: 14px;
    height: 14px;
    fill: none;
    stroke: var(--text-primary);
    stroke-width: 1.2;
    opacity: 0;
    transition: opacity 0.2s, stroke 0.2s;
    cursor: pointer;
}

.ma-option .ma-cat-icon {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    flex-shrink: 0;
    margin-right: 8px;
    color: var(--text-primary);
}

.ma-option.custom-ma:hover .delete-icon {
    opacity: 1;
}

.ma-option .delete-icon:hover {
    stroke: var(--price-down);
}

/* MA Type Item with Submenu */
.ma-type-item {
    display: flex;
    align-items: center;
    padding: 8px 12px;
    font-size: 14px;
    color: var(--text-primary);
    cursor: pointer;
    position: static;
    transition: background 0.2s;
}

.ma-type-item:hover {
    background: var(--bg-hover);
}

.ma-type-item .ma-cat-icon {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    flex-shrink: 0;
    margin-right: 8px;
    opacity: 0.7;
}

.ma-type-item:hover .ma-cat-icon {
    opacity: 1;
}

.ma-type-item span {
    flex: 1;
}

.ma-type-item .submenu-arrow {
    width: 12px;
    height: 12px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.2;
    flex-shrink: 0;
    margin-left: auto;
}

.ma-submenu {
    display: none;
    position: fixed;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 8px;
    box-shadow: var(--popover-shadow);
    width: 220px;
    z-index: 1002;
    max-height: calc(100vh - 100px);
    overflow-y: auto;
    padding: 6px 0;
}

.ma-submenu .ma-option {
    white-space: nowrap;
}

/* Indicator Type Item with Submenu (same as MA) */
.ind-type-item {
    display: flex;
    align-items: center;
    padding: 8px 12px;
    font-size: 14px;
    color: var(--text-primary);
    cursor: pointer;
    position: static;
    transition: background 0.2s;
}

.ind-type-item:hover {
    background: var(--bg-hover);
}

.ind-type-item span {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ind-type-item .ind-cat-icon {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    flex-shrink: 0;
    margin-right: 8px;
    color: var(--text-primary);
}

.ind-type-item .submenu-arrow {
    width: 12px;
    height: 12px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.2;
    flex-shrink: 0;
    margin-left: auto;
}

/* Indicator Settings Button at bottom of dropdown */
.ind-settings-item {
    display: flex;
    align-items: center;
    padding: 8px 10px;
    font-size: 14px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background 0.2s;
}

.ind-settings-item:hover {
    background: var(--bg-hover);
}

.ind-settings-icon {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    flex-shrink: 0;
    margin-right: 8px;
    color: var(--text-primary);
}

.ind-settings-item:hover .ind-settings-icon {
    color: var(--text-primary);
}

.ind-submenu {
    display: none;
    position: fixed;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 8px;
    box-shadow: var(--popover-shadow);
    /* [FIX] 2026-05-21 — width grows to fit bilingual indicator labels
       (e.g. "BOS/CHoCH 结构突破/特征转变" / Russian / Thai variants).
       min-width preserves the previous 220px baseline so short English
       labels render the same. max-width caps growth so the panel cannot
       overflow the viewport on narrow desktops (1280×800). overflow-x
       hidden prevents the long-text horizontal scrollbar the user
       observed; nowrap keeps each row on a single line. */
    min-width: 220px;
    max-width: 420px;
    width: max-content;
    z-index: 1002;
    max-height: calc(100vh - 100px);
    overflow-x: hidden;
    overflow-y: auto;
    padding: 6px 0;
}

.ind-submenu .sub-ind-option {
    white-space: nowrap;
}

.ind-submenu .sub-ind-option .ind-icon {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    color: var(--text-primary);
}

.ind-submenu-divider {
    height: 1px;
    background: var(--border-color);
    margin: 4px 8px;
}

/* Indicator Search Box */
.ind-search-box {
    display: flex;
    align-items: center;
    padding: 6px 8px;
    border-bottom: 1px solid var(--border-color);
    position: sticky;
    top: 0;
    background: var(--bg-secondary);
    z-index: 1;
}

.ind-search-icon {
    width: 14px;
    height: 14px;
    color: var(--text-primary);
    flex-shrink: 0;
}

.ind-search-input {
    flex: 1;
    border: none;
    background: transparent;
    padding: 4px 6px;
    font-size: 13px;
    color: var(--text-primary);
    outline: none;
}

.ind-search-input::placeholder {
    color: var(--text-secondary);
}

/* Search Results */
.ind-search-results {
    display: none;
}

.ind-search-results.show {
    display: block;
}

.ind-search-results .sub-ind-option {
    display: flex;
    align-items: center;
    padding: 6px 10px;
    font-size: 14px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background 0.2s;
}

.ind-search-results .sub-ind-option:hover {
    background: var(--bg-hover);
}

.ind-search-results .sub-ind-option .ind-icon {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    color: var(--text-primary);
}

.ind-search-results .sub-ind-option span {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ind-search-results .sub-ind-option .star-icon {
    width: 14px;
    height: 14px;
    fill: none;
    stroke: var(--text-primary);
    stroke-width: 1.2;
    flex-shrink: 0;
    margin-left: auto;
    opacity: 0.5;
}

.ind-search-results .sub-ind-option.favorite .star-icon.filled,
.ind-search-results .sub-ind-option.favorite .star-icon {
    fill: var(--text-primary);
    stroke: var(--text-primary);
    opacity: 1;
}

.ind-search-no-results {
    padding: 12px 10px;
    text-align: center;
    color: var(--text-secondary);
    font-size: 13px;
}

/* Categories container */
.ind-categories {
    display: block;
}

.ind-categories.hidden {
    display: none;
}

/* MA Custom Input */
.ma-custom-input {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 6px 10px;
}

.ma-type-select {
    flex: 1;
    padding: 4px 6px;
    font-size: 12px;
    color: var(--text-primary);
    background: var(--bg-tertiary);
    border: 1px solid var(--border-color);
    border-radius: 3px;
    outline: none;
    cursor: pointer;
}

.ma-period-input {
    width: 50px;
    padding: 4px 6px;
    font-size: 12px;
    color: var(--text-primary);
    background: var(--bg-tertiary);
    border: 1px solid var(--border-color);
    border-radius: 3px;
    outline: none;
    text-align: center;
}

.ma-period-input:focus,
.ma-type-select:focus {
    border-color: var(--primary-color);
}

.ma-add-btn {
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    font-weight: 600;
    color: var(--text-primary);
    background: var(--primary-color);
    border: none;
    border-radius: 3px;
    cursor: pointer;
    transition: opacity 0.2s;
}

.ma-add-btn:hover {
    opacity: 0.8;
}

.ma-style-input {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 0 10px 6px;
}

.ma-style-label {
    font-size: 12px;
    color: var(--text-secondary);
}

.ma-color-input {
    width: 28px;
    height: 22px;
    padding: 0;
    background: var(--bg-tertiary);
    border: 1px solid var(--border-color);
    border-radius: 3px;
    cursor: pointer;
}

.ma-width-select {
    padding: 4px 6px;
    font-size: 12px;
    color: var(--text-primary);
    background: var(--bg-tertiary);
    border: 1px solid var(--border-color);
    border-radius: 3px;
    outline: none;
    cursor: pointer;
}

.ma-width-select:focus {
    border-color: var(--primary-color);
}

.ma-alma-input {
    display: none;
}

.ma-alma-input.show {
    display: flex;
}

/* MA Clear Button */
.ma-clear-btn {
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    color: var(--text-secondary);
    background: transparent;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    transition: all 0.2s;
}

.ma-clear-btn svg {
    width: 14px;
    height: 14px;
}

.ma-clear-btn:hover {
    background: var(--bg-hover);
    color: var(--price-down);
}

/* ============================================
   Sub-Chart Indicator Selector Styles
   ============================================ */
.sub-indicator-selector {
    display: flex;
    align-items: center;
    gap: 2px;
}

.sub-ind-btn {
    padding: 3px 10px;
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
    background: transparent;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    transition: all 0.2s;
}

.sub-ind-btn:hover {
    background: var(--bg-tertiary);
}

.sub-ind-btn.active {
    color: var(--text-primary);
}

/* Sub-Indicator Dropdown */
.sub-ind-dropdown {
    position: relative;
}

.sub-ind-dropdown-btn {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 4px 8px;
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
    background: transparent;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    transition: all 0.2s;
    white-space: nowrap;
}

.sub-ind-dropdown-btn .dropdown-arrow {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
}

.sub-ind-dropdown-btn .btn-icon {
    width: var(--topbar-icon-size);
    height: var(--topbar-icon-size);
    margin-right: 6px;
}

.sub-ind-dropdown-btn:hover {
    background: var(--bg-tertiary);
}

/* Paper Trading toggle in top-bar */
.pt-toolbar-btn {
    position: relative;
    display: flex;
    align-items: center;
    gap: 2px;
    height: 100%;
    padding: 0 8px;
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
    background: transparent;
    border: none;
    cursor: pointer;
    transition: color 0.15s, background 0.15s;
    white-space: nowrap;
    flex-shrink: 0;
}

.pt-toolbar-btn svg {
    margin-right: 4px;
    flex-shrink: 0;
}

.pt-toolbar-btn:hover {
    background: var(--bg-tertiary);
}

/* [ADD] 2026-05-29 v20260529.17 — Topbar split button (YeScript + ▼).
 * Wraps the main label-bearing button and a dedicated arrow trigger
 * inside one logical control so the pair reads as a single "primary
 * action + alternate action" widget — same idiom as Gmail "Send ▼",
 * VS Code "Run ▼", GitHub "Code ▼". The main button still opens the
 * YeScript editor; the arrow opens the Templates picker.
 * No background on the split itself — the constituent .pt-toolbar-btn
 * hover styles already provide the affordance per half, which makes
 * the boundary between the two halves obvious without extra chrome. */
.pt-toolbar-split {
    display: inline-flex;
    align-items: center;
    height: 100%;
    flex-shrink: 0;
    position: relative;
}
.pt-toolbar-btn.pt-toolbar-split-main {
    padding-right: 4px;
}
.pt-toolbar-btn.pt-toolbar-split-arrow {
    padding: 0 8px;
    height: 100%;
    align-self: stretch;
    color: var(--text-secondary);
}
.pt-toolbar-btn.pt-toolbar-split-arrow .dropdown-arrow {
    width: 14px;
    height: 14px;
    margin-right: 0;
    flex-shrink: 0;
    stroke-width: var(--chart-icon-stroke);
}
.pt-toolbar-btn.pt-toolbar-split-arrow:hover,
.pt-toolbar-btn.pt-toolbar-split-arrow[aria-expanded="true"] {
    color: var(--text-primary);
    background: var(--bg-tertiary);
}

.sub-ind-dropdown-menu {
    display: none;
    /* FIX: Use position:fixed to prevent clipping by parent overflow:hidden */
    position: fixed;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 8px;
    box-shadow: var(--popover-shadow);
    z-index: 10000;
    /* [FIX] 2026-05-22 — bilingual category labels ("Levels & Lines 水平与线段")
       overflow the old fixed 220px and wrap to a 2nd line. Allow the menu to
       size to its widest row but cap at 360px so it never dominates the
       viewport. Mobile breakpoint already forces width:auto via !important. */
    min-width: 220px;
    max-width: 360px;
    width: max-content;
    max-height: calc(100vh - 120px);
    overflow-y: auto;
    overflow-x: hidden;
    padding: 6px 0;
}

.sub-ind-dropdown-menu::-webkit-scrollbar {
    width: 3px;
    opacity: 0;
}

.sub-ind-dropdown-menu::-webkit-scrollbar-track {
    background: transparent;
}

.sub-ind-dropdown-menu::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 2px;
    transition: background 0.2s;
}

.sub-ind-dropdown-menu:hover::-webkit-scrollbar-thumb {
    background: #c8ccd8;
}

.sub-ind-dropdown-menu::-webkit-scrollbar-thumb:hover {
    background: #a0a4b0;
}

.sub-ind-dropdown-menu.show {
    display: block;
}

.sub-ind-option {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    font-size: 14px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background 0.2s;
}

.sub-ind-option span {
    flex: 1;
    /* Defensive: even though .ind-submenu max-width:420px covers every
       current bilingual label, future translators may add longer text.
       Ellipsis prevents horizontal overflow / scroll regardless. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.sub-ind-option .star-icon {
    flex-shrink: 0;
    margin-left: auto;
}

.sub-ind-option:hover {
    background: var(--bg-hover);
}

.sub-ind-option .star-icon {
    width: 14px;
    height: 14px;
    fill: none;
    stroke: var(--text-primary);
    stroke-width: 1.2;
    transition: fill 0.2s, stroke 0.2s;
    opacity: 0.5;
}

.sub-ind-option.favorite .star-icon.filled,
.sub-ind-option .star-icon.filled,
.sub-ind-option.favorite .star-icon {
    fill: var(--text-primary);
    stroke: var(--text-primary);
    opacity: 1;
}

.sub-ind-option:hover .star-icon {
    stroke: var(--text-primary);
}

/* ============================================
   Generic Toolbar Dropdown Styles
   ============================================ */
.toolbar-dropdown {
    position: relative;
}

.toolbar-dropdown-btn {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 4px 8px;
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
    background: transparent;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    transition: all 0.2s;
    white-space: nowrap;
}

.toolbar-dropdown-btn svg {
    width: 12px;
    height: 12px;
    flex-shrink: 0;
}

.toolbar-dropdown-btn .btn-icon {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    stroke-width: var(--chart-icon-stroke);
    margin-right: 6px;
}

.toolbar-dropdown-btn:hover {
    background: var(--bg-tertiary);
}

.toolbar-dropdown-menu {
    display: none;
    /* FIX: Use position:fixed to prevent being clipped by parent overflow:hidden */
    position: fixed;
    width: 158px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 8px;
    box-shadow: var(--popover-shadow);
    z-index: 10000;
    max-height: calc(100vh - 120px);
    overflow-y: auto;
}

.toolbar-dropdown-menu::-webkit-scrollbar {
    width: 3px;
    opacity: 0;
}

.toolbar-dropdown-menu::-webkit-scrollbar-track {
    background: transparent;
}

.toolbar-dropdown-menu::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 2px;
    transition: background 0.2s;
}

.toolbar-dropdown-menu:hover::-webkit-scrollbar-thumb {
    background: #c8ccd8;
}

.toolbar-dropdown-menu::-webkit-scrollbar-thumb:hover {
    background: #a0a4b0;
}

.toolbar-dropdown-menu-wide {
    min-width: 200px;
}

.toolbar-dropdown.open .toolbar-dropdown-menu {
    display: block;
}

.dropdown-category {
    padding: 6px 12px 4px;
    font-size: 10px;
    font-weight: 600;
    color: var(--text-tertiary);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.dropdown-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    font-size: 14px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background 0.2s;
}

.dropdown-item:hover {
    background: var(--bg-hover);
}

.dropdown-icon {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    flex-shrink: 0;
    color: var(--text-primary);
}

.dropdown-item:hover .dropdown-icon {
    color: var(--text-primary);
}

.dropdown-slider .dropdown-icon {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    flex-shrink: 0;
    color: var(--text-primary);
}

.dropdown-item.active {
    color: var(--primary-color);
}

.dropdown-item.active::before {
    content: '\2713';
    margin-right: 4px;
    font-size: 10px;
}

/* [2026-03-09] Toggle items with indicator */
.dropdown-item.toggle-item {
    justify-content: flex-start;
}

.dropdown-item.toggle-item::before {
    display: none;
}

.dropdown-item.toggle-item .toggle-indicator {
    margin-left: auto;
}

.toggle-indicator {
    width: 32px;
    height: 18px;
    background: var(--bg-tertiary);
    border-radius: 9px;
    position: relative;
    transition: background 0.2s;
}

.toggle-indicator::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 14px;
    height: 14px;
    background: var(--text-primary);
    border-radius: 50%;
    transition: transform 0.2s, background 0.2s;
}

.dropdown-item.toggle-item.active .toggle-indicator {
    background: #333;
}

.dropdown-item.toggle-item.active .toggle-indicator::after {
    transform: translateX(14px);
    background: #fff;
}

.light-theme .dropdown-item.toggle-item.active .toggle-indicator {
    background: #222;
}

.dropdown-divider {
    height: 1px;
    background: var(--border-color);
    margin: 4px 0;
}

.dropdown-slider {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
}

.dropdown-slider input[type="range"] {
    flex: 1;
    height: 6px;
    background: linear-gradient(to right, var(--state-active) 0%, var(--state-active) var(--value, 100%), #5c6370 var(--value, 100%), #5c6370 100%);
    border-radius: 3px;
    -webkit-appearance: none;
    appearance: none;
    cursor: pointer;
    outline: none;
    border: none;
}

.dropdown-slider input[type="range"]::-webkit-slider-runnable-track {
    height: 6px;
    border-radius: 3px;
    background: transparent;
}

.dropdown-slider input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 16px;
    height: 16px;
    background: var(--state-active);
    border: 2px solid #fff;
    border-radius: 50%;
    cursor: pointer;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
    margin-top: -5px;
    transition: transform 0.15s, box-shadow 0.15s;
}

.dropdown-slider input[type="range"]::-webkit-slider-thumb:hover {
    transform: scale(1.15);
    box-shadow: 0 2px 8px rgba(41, 98, 255, 0.4);
}

.dropdown-slider input[type="range"]::-moz-range-track {
    height: 6px;
    border-radius: 3px;
    background: #5c6370;
}

.dropdown-slider input[type="range"]::-moz-range-progress {
    height: 6px;
    border-radius: 3px;
    background: var(--state-active);
}

.dropdown-slider input[type="range"]::-moz-range-thumb {
    width: 16px;
    height: 16px;
    background: var(--state-active);
    border: 2px solid #fff;
    border-radius: 50%;
    cursor: pointer;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
    transition: transform 0.15s, box-shadow 0.15s;
}

.dropdown-slider input[type="range"]::-moz-range-thumb:hover {
    transform: scale(1.15);
    box-shadow: 0 2px 8px rgba(41, 98, 255, 0.4);
}

.dropdown-slider span {
    font-size: 12px;
    color: var(--text-secondary);
    min-width: 36px;
    text-align: right;
}

/* Layout Icon — size via --chart-icon-size (see block near .toolbar-btn svg) */

/* Colormap Preview */
.colormap-item {
    justify-content: space-between;
}

.colormap-preview {
    width: 40px;
    height: 10px;
    border-radius: 2px;
}

/* Heatmap Dropdown */
/* [FEAT] 2026-05-13 — Widened from 260px to 320px to fit "Predicted
   Liquidation" + EST badge in English / German / Romance languages
   without wrapping. EST is a regulatory disclaimer ("estimate, not a
   guaranteed liquidation amount") and cannot be removed.
   Sizing math: 320 - 24(padding) = 296px row width.
   Fixed cost per Predicted row =
     icon 18 + gap 8 + gap 8 + EST ~32 + gap 8 + color preview 32 + gap 8 + arrow 12 = 126px
   Text width = 296 - 126 = 170px → fits "Predicted Liquidation" (~155px @14px).
   Warning: if a future locale produces a longer label (e.g. ~190px),
     widen further or break label across two lines instead of relying on
     viewport scroll. */
#heatmapDropdown .toolbar-dropdown-menu {
    width: 320px;
    padding: 6px 0;
    /* [FIX] 2026-07-20 — Suppress horizontal scrollbar on Heatmap menu.
       Reason: overflow-y:auto makes overflow-x compute to auto; the preview
         ::after hover bridge (chart.ejs) and flex nowrap labels can nudge
         scrollWidth past 320px by a few pixels.
       Change: clip horizontal overflow; bridge is capped at 32px so visible
         rows (lookback, layers, EST badge, color preview, arrow) stay intact.
       Warning: keep overflow-y:auto for long layer lists; do not set overflow
         hidden (both axes) or vertical scroll would break. */
    overflow-x: hidden;
}

/* Heatmap Layer Item with Submenu */
/* [FIX] 2026-05-22 — heatmap row labels include single-language
   translations ("Estimated Liquidation Heatmap" in en, "预估爆仓热力图"
   in zh-CN, "Carte thermique des liquidations prédites" in fr, etc.).
   The longest row across all 12 locales is fr at ~449px, so cap at
   480px to leave a comfortable margin and prevent runaway in
   pathological cases. Mobile breakpoint already forces width:auto via
   !important above. */
.heatmap-menu {
    min-width: 240px;
    max-width: 480px;
    width: max-content;
}

.heatmap-layer-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    cursor: pointer;
    position: static;
    transition: background 0.12s;
}

.heatmap-layer-item:hover {
    background: var(--bg-hover);
}

/* [FEAT] 2026-05-13 — Click feedback for heatmap rows.
   Reason: with the toggle slider hidden (.hm-switch display:none) and the
     .active class permanently disabled (see _syncHeatmapButtonsFromState),
     a click had no visual confirmation. :active gives the user the
     transient "I pressed it" feedback while the underlying enable() loads.
   Warning: keep this on :active (not on .active class) — the .active
     class is intentionally never added per the 8-chart UX rule. */
.heatmap-layer-item:active {
    background: rgba(41, 98, 255, 0.18);
}

/* Heatmap row icon — 24px (matches chart toolbar dropdown convention). */
.heatmap-layer-icon {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    flex-shrink: 0;
    color: var(--text-primary);
}

/* [FIX] 2026-05-11 — disabled state for spot-only / unsupported layers
   (D2 from CHART走读 2026-05-11).
   Reason: Liquidation and Predicted-Liquidation overlays require futures
   data; on a spot exchange the toggle was clickable and falsely switched
   on, while the adapter silently refused the request URL.
   Change: when JS adds `.disabled` to the row (see updateLiquidationToggleState
   in tt.app.js) drop the opacity, swallow pointer events, and keep the
   row out of hover highlight.
   Warning: keep `pointer-events: none` so child clicks (switch + label)
   never reach the dropdown handler. */
.heatmap-layer-item.disabled,
.heatmap-layer-item.disabled:hover {
    opacity: 0.4;
    cursor: not-allowed;
    pointer-events: none;
    background: transparent;
}

/* [FEAT] 2026-05-13 — Heatmap rows are no longer toggle switches.
   Reason: with up to 8 charts the toolbar must not carry per-slot "is this
     on?" memory. The .hm-switch slider falsely advertised an off path that
     only ever existed in the in-chart top-left legend "×". Hiding the
     slider while keeping the row clickable makes the menu match its actual
     enable-only behaviour. The slider's "on/off" CSS rules were removed
     because they will never match again — see tt.app.js sync helpers.
   Warning: do NOT delete the .hm-switch DOM in chart.ejs — it remains a
     valid click target in
     $('.hm-switch, .heatmap-layer-icon, .heatmap-layer-name') so removing
     the element would alter the click selector contract. */
.hm-switch {
    display: none;
}

.heatmap-layer-name {
    flex: 1;
    font-size: 14px;
    color: var(--text-primary);
    /* [FIX] 2026-05-22 — keep label on one line. With .heatmap-menu now
       sized via max-content up to 680px, every locale's bilingual label
       fits in full, so ellipsis is no longer needed (and was explicitly
       removed per user request). */
    white-space: nowrap;
}

.heatmap-color-preview {
    width: 32px;
    height: 8px;
    border-radius: 2px;
}

.heatmap-layer-item .submenu-arrow {
    width: 12px;
    height: 12px;
    flex-shrink: 0;
    color: var(--text-secondary);
}

/* Heatmap Color Submenu */
.heatmap-color-submenu {
    display: none;
    position: fixed;
    min-width: 260px;
    /* [FIX] 2026-07-14 — match ma/ind submenu viewport cap; fixed 400px
       forced scroll before content reached panel bottom after L2 controls grew. */
    max-height: calc(100vh - 100px);
    overflow-x: hidden;
    overflow-y: auto;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 6px;
    box-shadow: var(--popover-shadow);
    padding: 6px 0;
    z-index: 10002;
    pointer-events: auto;
}

body > .heatmap-color-submenu.heatmap-color-submenu--portaled {
    position: fixed;
    z-index: 10002;
    pointer-events: auto;
}

.heatmap-color-submenu::-webkit-scrollbar {
    width: 4px;
}

.heatmap-color-submenu::-webkit-scrollbar-track {
    background: transparent;
}

.heatmap-color-submenu::-webkit-scrollbar-thumb {
    background: var(--border-color);
    border-radius: 2px;
}

.heatmap-layer-item:hover > .heatmap-color-submenu {
    display: block;
}

.heatmap-colormap-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 12px;
    cursor: pointer;
    transition: background 0.2s;
}

.heatmap-colormap-item:hover {
    background: var(--bg-hover);
}

.heatmap-colormap-item span:first-child {
    font-size: 13px;
    color: var(--text-primary);
}

.heatmap-colormap-item.active span:first-child::before {
    content: '\2713';
    margin-right: 6px;
    font-size: 10px;
}

/* [ADD] 2026-05-18 — Order book "L2 view" auto-zoom controls.
   Reason: order book depth on a single venue only spans ±0.03~0.19% of
     mid-price, so by default the Y-axis (sized for the full visible
     K-line range, typically ±1~5%) shows the heatmap as a thin band
     glued to each candle. Bookmap and TradingView Pro solve this by
     auto-zooming Y to a tighter window when the depth layer is active.
   Warning: keep the inline elements small (toggle 28px, slider full row)
     so the menu height stays within the viewport-capped max-height. */
.hm-l2-toggle-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 12px;
}

.hm-l2-toggle-label {
    font-size: 13px;
    color: var(--text-primary);
}

.hm-l2-switch {
    position: relative;
    width: 28px;
    height: 16px;
    flex-shrink: 0;
}

.hm-l2-switch input {
    opacity: 0;
    width: 0;
    height: 0;
}

.hm-l2-switch-slider {
    position: absolute;
    inset: 0;
    background: var(--border-color);
    border-radius: 16px;
    transition: background 0.2s;
    cursor: pointer;
}

.hm-l2-switch-slider::before {
    content: '';
    position: absolute;
    width: 12px;
    height: 12px;
    top: 2px;
    left: 2px;
    background: #fff;
    border-radius: 50%;
    transition: transform 0.2s;
}

.hm-l2-switch input:checked + .hm-l2-switch-slider {
    background: var(--accent-color, #2962ff);
}

.hm-l2-switch input:checked + .hm-l2-switch-slider::before {
    transform: translateX(12px);
}

.hm-l2-range-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 12px 8px;
}

.hm-l2-range-row.is-disabled {
    opacity: 0.55;
}

.hm-l2-range-label {
    font-size: 12px;
    color: var(--text-secondary);
    flex-shrink: 0;
}

.hm-l2-range-row input[type="range"] {
    flex: 1;
    height: 4px;
    -webkit-appearance: none;
    appearance: none;
    background: var(--border-color);
    border-radius: 2px;
    outline: none;
}

.hm-l2-range-row input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--accent-color, #2962ff);
    cursor: pointer;
}

.hm-l2-range-row input[type="range"]::-moz-range-thumb {
    width: 12px;
    height: 12px;
    border: none;
    border-radius: 50%;
    background: var(--accent-color, #2962ff);
    cursor: pointer;
}

.hm-l2-range-row input[type="range"]:disabled {
    cursor: not-allowed;
}

.hm-l2-range-row input[type="range"]:disabled::-webkit-slider-thumb {
    cursor: not-allowed;
    background: var(--border-color);
}

.hm-l2-range-row input[type="range"]:disabled::-moz-range-thumb {
    cursor: not-allowed;
    background: var(--border-color);
}

.hm-l2-range-value {
    font-size: 12px;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
    min-width: 44px;
    text-align: right;
}

.hm-intensity-control {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 4px 12px 10px;
}

.hm-intensity-control-label {
    font-size: 12px;
    color: var(--text-secondary);
    line-height: 1.2;
}

.hm-intensity-control-stack {
    display: flex;
    flex-direction: column;
    gap: 4px;
    width: 100%;
}

.hm-intensity-band-track {
    --hm-intensity-gradient: linear-gradient(90deg, #1a1a1a 0%, #ffffff 100%);
    --hm-intensity-min-thumb: #2f8fff;
    --hm-intensity-max-thumb: #f4a338;
    --min-pct: 0%;
    --max-pct: 100%;
    position: relative;
    width: 100%;
    height: 4px;
    border-radius: 999px;
    overflow: hidden;
    background: var(--hm-intensity-gradient);
}

.hm-intensity-band-track::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: var(--min-pct);
    right: calc(100% - var(--max-pct));
    background: rgba(255, 255, 255, 0.28);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35);
}

.hm-intensity-range-row input[type="range"] {
    background: var(--hm-intensity-gradient);
}

.hm-intensity-dual-slider {
    position: relative;
    width: 100%;
    height: 22px;
    margin: 0;
    overflow: visible;
    background: transparent;
}

.hm-intensity-dual-slider::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 9px;
    height: 4px;
    border-radius: 999px;
    background: var(--hm-intensity-gradient);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
}

.hm-intensity-dual-slider::after {
    content: '';
    position: absolute;
    top: 9px;
    height: 4px;
    left: var(--min-pct);
    right: calc(100% - var(--max-pct));
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.26);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.32);
    pointer-events: none;
}

.hm-intensity-thumb {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    width: 100%;
    height: 22px;
    margin: 0;
    background: transparent !important;
    -webkit-appearance: none;
    appearance: none;
    pointer-events: none;
    z-index: 2;
}

.hm-intensity-thumb::-webkit-slider-runnable-track {
    height: 22px;
    background: transparent;
    border: 0;
}

.hm-intensity-thumb::-moz-range-track {
    height: 22px;
    background: transparent;
    border: 0;
}

.hm-intensity-thumb::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 3px;
    height: 16px;
    border-radius: 1px;
    border: 1px solid rgba(0, 0, 0, 0.45);
    margin-top: 3px;
    cursor: ew-resize;
    pointer-events: auto;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(235, 235, 235, 0.96) 100%);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.35), 0 1px 4px rgba(0, 0, 0, 0.35);
}

.hm-intensity-thumb::-moz-range-thumb {
    width: 3px;
    height: 16px;
    border-radius: 1px;
    border: 1px solid rgba(0, 0, 0, 0.45);
    cursor: ew-resize;
    pointer-events: auto;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(235, 235, 235, 0.96) 100%);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.35), 0 1px 4px rgba(0, 0, 0, 0.35);
}

.hm-intensity-thumb-min {
    z-index: 3;
}

.hm-intensity-thumb-min::-webkit-slider-thumb {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.35), 0 0 0 2px color-mix(in srgb, var(--hm-intensity-min-thumb) 55%, transparent), 0 1px 4px rgba(0, 0, 0, 0.35);
}

.hm-intensity-thumb-min::-moz-range-thumb {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.35), 0 0 0 2px color-mix(in srgb, var(--hm-intensity-min-thumb) 55%, transparent), 0 1px 4px rgba(0, 0, 0, 0.35);
}

.hm-intensity-thumb-max {
    z-index: 4;
}

.hm-intensity-thumb-max::-webkit-slider-thumb {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.35), 0 0 0 2px color-mix(in srgb, var(--hm-intensity-max-thumb) 55%, transparent), 0 1px 4px rgba(0, 0, 0, 0.35);
}

.hm-intensity-thumb-max::-moz-range-thumb {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.35), 0 0 0 2px color-mix(in srgb, var(--hm-intensity-max-thumb) 55%, transparent), 0 1px 4px rgba(0, 0, 0, 0.35);
}

.hm-intensity-dual-labels {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 8px;
}

.hm-intensity-dual-label {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 12px;
    color: var(--text-secondary);
    line-height: 1.3;
}

.hm-intensity-dual-label .tt-help {
    flex-shrink: 0;
}

.hm-intensity-dual-label--right {
    justify-content: flex-end;
    text-align: right;
}

.hm-intensity-dual-values {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 16px;
}

.hm-intensity-dual-value {
    font-size: 12px;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
    min-width: 34px;
    text-align: center;
}

.hm-intensity-dual-sep {
    font-size: 12px;
    min-width: auto;
    color: var(--text-muted);
}

.predliq-lev-control {
    padding: 4px 12px 8px;
}

.predliq-lev-control.is-hidden {
    display: none;
}

.predliq-lev-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-bottom: 6px;
}

.predliq-lev-label {
    font-size: 12px;
    color: var(--text-secondary);
}

.predliq-lev-buttons {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.predliq-lev-btn {
    border: 1px solid var(--border-color);
    border-radius: 4px;
    background: transparent;
    color: var(--text-secondary);
    font-size: 12px;
    line-height: 1;
    padding: 4px 8px;
    cursor: pointer;
    transition: background 0.2s, color 0.2s, border-color 0.2s;
}

.predliq-lev-btn:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
}

.predliq-lev-btn.active {
    border-color: var(--accent-color, #2962ff);
    color: var(--text-primary);
    background: rgba(41, 98, 255, 0.14);
}

.liq-bubble-min-range-row {
    padding: 2px 0 8px;
}

.liq-bubble-min-presets {
    margin-top: 2px;
}

/* [ADD] 2026-06-23 — overlay lookback selector (global, top of heatmap dropdown) */
.overlay-lookback-control {
    padding: 4px 12px 8px;
}

.overlay-lookback-buttons {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.lookback-btn {
    border: 1px solid var(--border-color);
    border-radius: 4px;
    background: transparent;
    color: var(--text-secondary);
    font-size: 12px;
    line-height: 1;
    padding: 4px 8px;
    cursor: pointer;
    transition: background 0.2s, color 0.2s, border-color 0.2s;
}

.lookback-btn:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
}

.lookback-btn.active {
    border-color: var(--accent-color, #2962ff);
    color: var(--text-primary);
    background: rgba(41, 98, 255, 0.14);
}

.heatmap-colormap-item .colormap-preview {
    width: 40px;
    height: 10px;
    border-radius: 2px;
}

.heatmap-color-submenu .dropdown-category {
    padding: 4px 12px 2px;
    font-size: 9px;
}

.heatmap-color-submenu .dropdown-divider {
    margin: 4px 0;
}

/* Chart Settings Dropdown - Position: top-bar left side */
.top-bar-dropdown {
    position: relative;
    display: flex;
    align-items: center;
    height: 100%;
}

.top-bar-chart-btn {
    display: flex;
    align-items: center;
    gap: 4px;
    height: 100%;
    padding: 0 10px;
    background: transparent;
    border: none;
    cursor: pointer;
    color: var(--text-primary);
    font-size: 14px;
    font-weight: 500;
    transition: color 0.2s, background 0.2s;
}

.top-bar-chart-btn:hover {
    background: var(--bg-tertiary);
}

.top-bar-chart-btn .chart-icon {
    width: var(--topbar-icon-size);
    height: var(--topbar-icon-size);
    color: var(--text-primary);
}

.top-bar-chart-btn .chart-text {
    color: var(--text-primary);
}

.top-bar-chart-btn .dropdown-arrow {
    width: 14px;
    height: 14px;
    color: var(--text-primary);
    margin-left: 2px;
}

/* Top bar dropdown menu position */
.top-bar-dropdown .toolbar-dropdown-menu {
    top: 100%;
    left: 0;
    margin-top: 4px;
}

/* Indicator Settings Button in top bar */
.top-bar-ind-settings-btn {
    display: flex;
    align-items: center;
    gap: 4px;
    height: 100%;
    padding: 0 10px;
    background: transparent;
    border: none;
    cursor: pointer;
    color: var(--text-primary);
    font-size: 14px;
    font-weight: 500;
    transition: color 0.2s, background 0.2s;
}

.top-bar-ind-settings-btn:hover {
    background: var(--bg-tertiary);
}

.top-bar-ind-settings-btn svg {
    width: var(--topbar-icon-size);
    height: var(--topbar-icon-size);
    color: var(--text-primary);
}

.top-bar-ind-settings-btn span {
    color: var(--text-primary);
}

/* Independent gear button after Indicators dropdown */
.top-bar-ind-gear-btn {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: auto;
    min-width: var(--toolbar-btn-size);
    height: 100%;
    padding: 0 8px;
    background: transparent;
    border: none;
    cursor: pointer;
    color: var(--text-primary);
}

.top-bar-left #chartAppearanceBtn > svg {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
}

.top-bar-left #yescriptEditorToggle > svg {
    width: var(--chart-toolbar-icon-size);
    height: var(--chart-toolbar-icon-size);
}

.top-bar-ind-gear-btn:hover {
    background: var(--bg-tertiary);
}

/* Gear should be attached to Indicators dropdown (no divider between them). */
.top-bar-left > .top-bar-ind-gear-btn::before {
    display: none !important;
}

/* Top-bar indicator dropdown (moved from chart toolbar) */
.top-bar-sub-ind-dropdown {
    display: flex;
    align-items: center;
    height: 100%;
}

.top-bar-sub-ind-dropdown .sub-ind-dropdown-btn {
    height: 100%;
    padding: 0 10px;
    font-size: 14px;
    font-weight: 500;
    gap: 4px;
}

/* Top-bar moved core dropdowns: Time Interval / Chart Type / MA */
.top-bar-core-dropdown {
    position: relative;
    display: flex;
    align-items: center;
    height: 100%;
    flex-shrink: 0;
}

/* Core dropdown uses its own left/right separators; disable global left separator. */
.top-bar-left > .top-bar-core-dropdown::before {
    display: none !important;
}

.top-bar-core-btn {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 4px;
    height: 100%;
    width: auto;
    min-width: 0;
    padding: 0 12px;
    background: transparent;
    border: none;
    border-left: 1px solid var(--border-color);
    border-right: 1px solid var(--border-color);
    cursor: pointer;
    color: var(--text-primary);
    font-size: 14px;
    font-weight: 500;
    white-space: nowrap;
    margin: 0;
}

.top-bar-core-btn:hover {
    background: var(--bg-tertiary);
}

.top-bar-core-btn .core-icon {
    width: var(--topbar-icon-size);
    height: var(--topbar-icon-size);
    color: var(--text-primary);
    flex-shrink: 0;
}

.top-bar-core-btn .core-label {
    color: var(--text-primary);
    white-space: nowrap;
}

.top-bar-core-btn .dropdown-arrow {
    width: 14px;
    height: 14px;
    color: var(--text-primary);
}

/* [FIX 2026-03-09] Overlay dropdowns moved to top bar after indicator settings */
.top-bar-left .top-bar-overlay-dropdown {
    position: relative;
    display: flex;
    align-items: center;
    height: 100%;
}

.top-bar-left .top-bar-overlay-dropdown > .toolbar-dropdown-btn {
    display: flex;
    align-items: center;
    gap: 4px;
    height: 100%;
    padding: 0 10px;
    background: transparent;
    border: none;
    cursor: pointer;
    color: var(--text-primary);
    font-size: 14px;
    font-weight: 500;
    transition: color 0.2s, background 0.2s;
}

.top-bar-left .top-bar-overlay-dropdown > .toolbar-dropdown-btn:hover {
    background: var(--bg-tertiary);
}

.top-bar-left .top-bar-overlay-dropdown > .toolbar-dropdown-btn .btn-icon {
    width: var(--topbar-icon-size);
    height: var(--topbar-icon-size);
    color: var(--text-primary);
}

.top-bar-left .top-bar-overlay-dropdown > .toolbar-dropdown-btn .dropdown-arrow {
    width: 14px;
    height: 14px;
    color: var(--text-primary);
    margin-left: 2px;
}

/* [FIX 2026-03-09] Top-bar moved overlay menus must anchor to button, not viewport-fixed. */
.top-bar-left .top-bar-overlay-dropdown > .toolbar-dropdown-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
}

#chartSettingsDropdown .toolbar-dropdown-menu {
    width: 220px;
    padding: 6px 0;
}

/* Layout items in Chart Settings Dropdown */
#chartSettingsDropdown .layout-item {
    display: flex;
    align-items: center;
    gap: 10px;
}

#chartSettingsDropdown .layout-item.active::before {
    display: none;
    content: none;
}

#chartSettingsDropdown .layout-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    color: var(--text-primary);
}

#chartSettingsDropdown .layout-item.active {
    color: var(--primary-color);
}

#chartSettingsDropdown .layout-item.active .layout-icon {
    color: var(--primary-color);
}

#chartSettingsDropdown .chart-sync-item {
    width: 100%;
    border: 0;
    background: transparent;
    font-size: 14px;
    text-align: left;
}

#chartSettingsDropdown .chart-sync-item > span:first-child {
    flex: 1;
    min-width: 0;
}

/* [FEAT] 2026-05-13 — viewport-gated layout (e.g. 8-grid on small screens).
   Refuse interaction at CSS level so even keyboard/tap activation no-ops, and
   visually mute icon + label so the disabled state is unambiguous. */
#chartSettingsDropdown .layout-item.layout-item-disabled {
    opacity: 0.4;
    cursor: not-allowed;
    pointer-events: auto;
}

#chartSettingsDropdown .layout-item.layout-item-disabled .layout-icon {
    color: var(--text-tertiary);
}

#chartSettingsDropdown .layout-item.layout-item-disabled:hover {
    background: transparent;
    color: inherit;
}

/* Footprint Dropdown */
#footprintDropdown {
    margin-left: 8px;
}

#footprintDropdown .toolbar-dropdown-menu {
    width: 220px;
    padding: 6px 0;
}

/* Footprint button group */
.fp-menu {
    width: 200px;
    padding: 6px 0;
}
.fp-btn-group {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 4px 10px;
}
.fp-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    /* [FIX] 2026-05-22 — User requested no border on footprint menu
       buttons. Keep `border: 1px solid transparent` (not `border:none`)
       so the :active state below — which switches border-color to the
       accent color — can still show a 1px highlight without causing
       sub-pixel layout shift on click. */
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    color: var(--text-primary);
    font-size: 13px;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
}
.fp-btn:hover {
    background: var(--bg-hover);
}
/* [FEAT] 2026-05-13 — Click feedback for footprint buttons.
   Reason: with the .active class permanently disabled (per the 8-chart UX
     rule, see _syncFootprintButtonsFromState), :active is the only
     visual confirmation that the click was registered while the
     enable() loads.
   Warning: keep this on :active (not on .active class). */
.fp-btn:active {
    background: rgba(41, 98, 255, 0.18);
    border-color: var(--accent-color);
}
.fp-btn-icon {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    flex-shrink: 0;
    color: var(--text-primary);
}
.fp-btn-label {
    flex: 1;
    text-align: left;
}
.fp-btn-color {
    width: 24px;
    height: 12px;
    border-radius: 2px;
    flex-shrink: 0;
}

/* ============================================
   Liquidation Heatmap Selector Styles
   FIX 2026-01-29: Added dropdown for three liquidation heatmap view types
   ============================================ */
.liquidation-heatmap-selector {
    position: relative;
    margin-left: 8px;
}

.heatmap-btn {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 3px 8px 3px 6px;
    font-size: 14px;
    font-weight: 500;
    color: var(--text-secondary);
    background: transparent;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    transition: all 0.2s;
}

.heatmap-btn svg {
    width: 16px;
    height: 16px;
}

.heatmap-btn span {
    font-size: 14px;
}

.heatmap-btn .dropdown-arrow {
    width: 14px;
    height: 14px;
    transition: transform 0.2s;
}

.heatmap-btn:hover {
    background: var(--bg-tertiary);
    color: var(--text-primary);
}

.heatmap-btn.active {
    background: var(--bg-tertiary);
    color: var(--text-primary);
    font-weight: 600;
}

.heatmap-btn.active .dropdown-arrow {
    transform: rotate(180deg);
}

.heatmap-dropdown-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    min-width: 240px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 6px;
    box-shadow: var(--popover-shadow);
    padding: 6px 0;
    z-index: 1000;
    display: none;
    max-height: calc(100vh - 120px);
    overflow-y: auto;
}

.heatmap-dropdown-menu::-webkit-scrollbar {
    width: 3px;
    opacity: 0;
}

.heatmap-dropdown-menu::-webkit-scrollbar-track {
    background: transparent;
}

.heatmap-dropdown-menu::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 2px;
    transition: background 0.2s;
}

.heatmap-dropdown-menu:hover::-webkit-scrollbar-thumb {
    background: #c8ccd8;
}

.heatmap-dropdown-menu::-webkit-scrollbar-thumb:hover {
    background: #a0a4b0;
}

.heatmap-dropdown-menu.show {
    display: block;
}

.heatmap-option {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    cursor: pointer;
    transition: background 0.15s;
}

.heatmap-option svg {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    color: var(--text-primary);
}

.heatmap-option span {
    display: block;
}

.heatmap-option span:first-of-type {
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
}

.heatmap-option .heatmap-desc {
    font-size: 10px;
    color: var(--text-secondary);
    margin-top: 1px;
}

.heatmap-option:hover {
    background: var(--bg-hover);
}

.heatmap-option.active {
    background: var(--bg-tertiary);
}

.heatmap-option.active svg {
    color: var(--accent-color);
}

.heatmap-option.active span:first-of-type {
    color: var(--accent-color);
    font-weight: 600;
}

.stat-item {
    display: inline-flex;
    align-items: baseline;
    gap: 2px;
    white-space: nowrap;
    margin-right: 3px;
}

.stat-label {
    font-size: 12px;
    color: var(--text-secondary);
    font-weight: 400;
    margin-right: 1px;
}

.stat-value {
    font-family: var(--font-price);
    font-size: 14px;
    font-weight: 700;
    color: var(--text-primary);
}

.stat-value.up {
    color: var(--price-up);
}

.stat-value.down {
    color: var(--price-down);
}

.symbol-title {
    font-size: 14px;
    font-weight: 700;
    color: var(--text-primary);
}

.current-price {
    font-family: var(--font-price);
    font-size: 14px;
    font-weight: 600;
    color: var(--price-up);
}

/* Price change display - no background */
.price-change {
    font-family: var(--font-price);
    font-size: 11px;
    padding: 2px 6px;
    color: var(--price-up);
    background: none !important; /* Force remove any background */
}

.price-change.down {
    color: var(--price-down);
    background: none !important; /* Force remove any background */
}

.price-change.up {
    color: var(--price-up);
    background: none !important; /* Force remove any background */
}

.chart-container {
    flex: 1;
    position: relative;
    padding: 0;
    overflow: hidden;
    width: 100%;
    min-height: 300px;
}

/* Chart Layout Container (Multi-chart splits) */
.chart-layout-container {
    flex: 1;
    position: relative;
    width: 100%;
    min-height: 300px;
    display: grid;
    gap: 2px;
    background: var(--border-color);
}

.chart-layout-container.layout-single {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr;
}

.chart-layout-container.layout-2h {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr;
}

.chart-layout-container.layout-2v {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr 1fr;
}

.chart-layout-container.layout-4 {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr 1fr;
}

/* [FEAT] 2026-05-13 — 6-chart layout (3 cols x 2 rows, equal-split).
   Same rationale as layout-8 below: equal split, no resizer. */
.chart-layout-container.layout-6 {
    grid-template-columns: 1fr 1fr 1fr;
    grid-template-rows: 1fr 1fr;
}

/* [FEAT] 2026-05-12 — 8-chart layout (4 cols x 2 rows, equal-split).
   Reason: user-requested capability bump from max 4 to max 8 charts.
   Change: equal 1fr split, no resizer (resizer infra was 2x2-only and
     adding multi-line resizer would require turning AppState.chartLayoutRatio
     into per-track arrays; out of scope for this feature).
   Warning: do not add resizer divs for layout-8 in addChartResizers — the
     drag handler assumes single-axis ratio + 2x2 center, expanding it
     would reintroduce the legacy 2x2-only assumption everywhere. */
.chart-layout-container.layout-8 {
    grid-template-columns: 1fr 1fr 1fr 1fr;
    grid-template-rows: 1fr 1fr;
}

/* Chart Resizers */
.chart-resizer {
    position: absolute;
    z-index: 10;
    background: transparent;
    touch-action: none;
}

.chart-resizer-v {
    width: 9px;
    height: 100%;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    cursor: col-resize;
}

.chart-resizer-h {
    width: 100%;
    height: 9px;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    cursor: row-resize;
}

/* [CHG] 2026-05-24 r33 — center grab handle uses --resizer-color (same
 * token as the linear resizers). The 9px .chart-resizer-v / -h hot zones
 * stay transparent on purpose (multi-chart layout intentionally uses the
 * natural slot gap as the visual seam, the handles are pure hit-targets
 * — making them visible would clutter the 2x2 / 2x3 / 2x4 grids). Size
 * stays 14×14 — the centre handle is a fixed-size grab puck, not a
 * stretchable bar, so the 5px width convention doesn't apply. */
.chart-resizer-center {
    position: absolute;
    width: 14px;
    height: 14px;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 11;
    cursor: move;
    background: var(--resizer-color);
    border-radius: 3px;
    touch-action: none;
}

.chart-resizer:hover,
.chart-resizer.active {
    background: rgba(255, 255, 255, 0.1);
}

.chart-resizer-center:hover,
.chart-resizer-center.active {
    background: var(--text-secondary);
}

.chart-slot {
    position: relative;
    overflow: hidden;
    background: var(--bg-primary);
    border: 2px solid transparent;
    border-radius: 4px;
}

/* Only show active border when multiple charts exist */
.chart-layout-container:not(.layout-single) .chart-slot.active {
    border-width: 2px;
    border-color: #444;
}

/* Pending (empty) slot waiting for symbol selection - same as active */
.chart-layout-container:not(.layout-single) .chart-slot.pending {
    border-width: 2px;
    border-color: #444;
}

.chart-slot .chart-container {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
}

/* Empty slot with + button */
.chart-slot-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-secondary);
    cursor: pointer;
}

.chart-slot-add {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 20px 40px;
    border: 2px dashed var(--border-color);
    border-radius: 8px;
    color: var(--text-tertiary);
    transition: all 0.2s;
}

.chart-slot-empty:hover .chart-slot-add {
    border-color: var(--primary-color);
    color: var(--primary-color);
}

.chart-slot-add svg line {
    stroke: currentColor;
}

.chart-slot-add span {
    font-size: 12px;
}

/* Drawing tool cursor styles */
.chart-container.drawing-mode {
    cursor: crosshair !important;
}

.chart-container.drawing-mode * {
    cursor: crosshair !important;
}

/* Chart cursor styles for axis dragging */
.chart-container.cursor-ns-resize,
.chart-container.cursor-ns-resize *,
.chart-container.cursor-ns-resize canvas,
.chart-container.cursor-ns-resize div {
    cursor: ns-resize !important;
}

.chart-container.cursor-ew-resize,
.chart-container.cursor-ew-resize *,
.chart-container.cursor-ew-resize canvas,
.chart-container.cursor-ew-resize div {
    cursor: ew-resize !important;
}

/* K-line OHLC Info is now displayed inside the chart using TradeizChart */

/* ========== Middle Panel B: Liquidations + Orderbook ========== */
.dock-module {
    position: relative;
    display: flex;
    flex-direction: column;
    flex: 1 1 0;
    min-height: 140px;
    overflow: hidden;
}

.dock-module.hidden {
    display: none !important;
}

.dock-module.right-panel-top,
.dock-module.right-panel-bottom,
.dock-module.middle-panel-top,
.dock-module.middle-panel-bottom {
    height: auto;
    min-height: 140px;
}

.dock-module-header {
    user-select: none;
    gap: 1px;
}

.dock-module.module-stack-divider {
    border-top: 1px solid var(--border-color);
}

.module-header-inline {
    border-bottom: 1px solid var(--border-color);
}

.module-drag-handle {
    width: 18px;
    height: 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: -2px;
    color: var(--text-secondary);
    cursor: grab;
    border-radius: 4px;
    transition: background 0.2s, color 0.2s;
    flex-shrink: 0;
}

.module-drag-handle:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
}

.module-drag-handle:active {
    cursor: grabbing;
}

.module-drag-handle svg {
    width: 14px;
    height: 14px;
    pointer-events: none;
}

.dock-module.module-dragging {
    opacity: 0.55;
}

.panel-drop-target {
    outline: 1px dashed rgba(240, 185, 11, 0.55);
    outline-offset: -3px;
}

.module-drop-indicator {
    position: fixed;
    height: 2px;
    border-radius: 2px;
    background: var(--state-active);
    box-shadow: 0 0 0 1px rgba(41, 98, 255, 0.2);
    pointer-events: none;
    z-index: 9999;
    opacity: 0;
    transition: opacity 0.08s ease;
}

.module-drop-indicator.show {
    opacity: 1;
}

/* [CHG] 2026-05-24 r33 — height 2 → 5 + --resizer-color (matches the other
 * chart-page resizers). Note: this sits between stacked modules in the
 * middle/right panel; if 5px proves too thick on dense module stacks,
 * the right call is to drop it to 4 here rather than diverge the colour. */
.module-stack-resizer {
    height: 5px;
    width: 100%;
    background: var(--resizer-color);
    cursor: ns-resize;
    flex-shrink: 0;
    transition: background 0.15s;
    touch-action: none;
}

.module-stack-resizer:hover,
.module-stack-resizer.active {
    background: var(--text-secondary);
}

.middle-panel {
    width: 390px;
    min-width: 30px;
    max-width: 40vw;
    background: var(--bg-primary);
    display: flex;
    flex-direction: column;
    position: relative;
    flex-shrink: 1;
    border-left: none;
}

.middle-panel.hidden {
    display: none;
}

.middle-panel-top {
    height: 40%;
    min-height: 120px;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.middle-panel-top.hidden {
    display: none;
}

.middle-panel-bottom {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    min-height: 200px;
}

.middle-panel-bottom.hidden {
    display: none;
}

/* Liquidation Panel Header */
.panel-header {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 8px;
    padding: 0 12px 0 12px;
    height: 48px;
    box-sizing: border-box;
    border-bottom: none;
    flex-shrink: 0;
}

.panel-header .panel-title {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
}

.panel-header .panel-title svg {
    width: 16px;
    height: 16px;
}

/* [REMOVED] 2026-05-23 — .liquidation-stats span and the
 * inline `.panel-header > #liqSettingsGear` margin override
 * were dropped when the Liquidation summary moved out of the
 * header into a dedicated `.liq-stats-bar` row (4-column
 * Total/Long/Short/Largest + gear), mirroring the Large
 * Trades summary. The header is now just [drag][icon][title],
 * and the gear lives in the new stats bar via `.liq-stats-bar
 * .tt-module-gear { margin-left: auto; }` below. */

/* Liquidation List */
/* [CHG] 2026-05-23 — 6 columns: Time / Exchange / Side / Price /
 * Qty / USDT. Symbol column dropped per product rule
 * "每个交易对只专注自己的交易对" — the chart already shows the
 * symbol at the top and the Symbol Details card repeats it, so a
 * third copy in every liquidation row is pure noise. The chart
 * page is single-symbol so we don't lose information. */
.liquidation-list {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    --liquidation-grid-columns: 70fr 100fr 55fr 100fr 80fr 95fr;
}

.liquidation-header {
    display: grid;
    grid-template-columns: var(--liquidation-grid-columns);
    gap: 4px;
    padding: 8px 12px;
    font-size: 14px;
    color: var(--text-secondary);
    border-bottom: 1px solid var(--border-color);
}

.liquidation-header div {
    text-align: right;
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
    transition: color 0.15s;
}

.liquidation-header div:hover {
    color: var(--text-primary);
}

.liquidation-header div.sort-active {
    color: var(--text-primary);
}

/* [CHG] 2026-05-23 — first 2 cols (Time / Exchange) left-aligned. */
.liquidation-header > div:nth-child(1),
.liquidation-header > div:nth-child(2) {
    text-align: left;
}

.sort-arrow {
    font-size: 10px;
    opacity: 0.9;
}

.liquidation-body {
    flex: 1;
    overflow-y: auto;
    overflow-x: hidden;
}

.liquidation-body::-webkit-scrollbar {
    width: 3px;
    opacity: 0;
}

.liquidation-body::-webkit-scrollbar-track {
    background: transparent;
}

.liquidation-body::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 2px;
}

.liquidation-list:hover .liquidation-body::-webkit-scrollbar-thumb {
    background: #c8ccd8;
}

.liquidation-row {
    display: grid;
    grid-template-columns: var(--liquidation-grid-columns);
    gap: 4px;
    padding: 6px 12px;
    font-size: 14px;
    color: var(--text-primary);
    border-bottom: 1px solid var(--border-color);
}

.liquidation-row div {
    text-align: right;
}

/* [CHG] 2026-05-23 — first 2 columns (Time / Exchange) are text-y
 * identifiers and read better left-aligned; the remaining numeric
 * columns (Side / Price / Qty / USDT) keep the default right-align.
 * Matches the Large Trades convention. */
.liquidation-row > div:nth-child(1),
.liquidation-row > div:nth-child(2) {
    text-align: left;
}

.liquidation-row .side-long {
    color: var(--price-up);
}

.liquidation-row .side-short {
    color: var(--price-down);
}

.liquidation-empty {
    padding: 20px;
    text-align: center;
    color: var(--text-secondary);
    font-size: 13px;
}

/* ========== Right Panel A: Markets + Symbol Details ========== */
.right-panel {
    width: 480px;
    min-width: 30px;
    max-width: 50vw;
    background: var(--bg-primary);
    display: flex;
    flex-direction: column;
    position: relative;
    flex-shrink: 1;
    border-left: none;
}

.right-panel.hidden {
    display: none;
}

.right-panel-top.hidden,
.right-panel-bottom.hidden {
    display: none;
}

/* ========== Symbol Detail Panel ========== */
.symbol-detail-panel {
    padding: 0 12px 12px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    scrollbar-gutter: stable;
}

/* [FIX] 2026-05-25 — Symbol Detail children must NEVER be flex-shrunk.
 * Reason: default flex-shrink: 1 caused #siWhaleKpi (a 2-row grid) to be
 * compressed to a 1-row height when the dock-module height was tight.
 * Combined with `.si-kpi-strip { overflow: hidden }`, the second row of
 * KPI cells (Buy Ratio / Whale # / Largest Buy / Largest Sell) was clipped
 * AND, because clipped content doesn't grow scrollHeight, the user could
 * not scroll to reveal them either — they appeared permanently invisible.
 * Forcing flex-shrink: 0 makes the strip keep its full 2-row height; the
 * scroll panel (`.symbol-detail-panel { overflow-y: auto }`) takes over.
 * Warning: do NOT revert — this will reintroduce the "下面四个 KPI 不显示" bug. */
.symbol-detail-panel > * {
    flex-shrink: 0;
}

.symbol-detail-panel::-webkit-scrollbar {
    width: 3px;
    opacity: 0;
}

.symbol-detail-panel::-webkit-scrollbar-track {
    background: transparent;
}

.symbol-detail-panel::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 2px;
    transition: background 0.2s;
}

.symbol-detail-panel:hover::-webkit-scrollbar-thumb {
    background: #c8ccd8;
}

.symbol-detail-panel::-webkit-scrollbar-thumb:hover {
    background: #a0a4b0;
}

.symbol-detail-header {
    display: flex;
    align-items: center;
    gap: 0;
    padding: 12px 0 4px 0;
}

.symbol-detail-header .symbol-detail-drag-handle {
    margin-left: -2px;
    margin-right: 0;
}

.symbol-detail-header .symbol-detail-drag-handle + .symbol-detail-icon {
    margin-left: 1px;
    margin-right: 10px;
}

.symbol-detail-icon {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    object-fit: contain;
    flex-shrink: 0;
}

.symbol-detail-name {
    display: flex;
    align-items: baseline;
    gap: 4px;
    flex: 1;
}

.symbol-detail-base {
    font-size: 14px;
    color: var(--text-primary);
}

.symbol-detail-quote {
    font-size: 14px;
    color: var(--text-secondary);
}

.symbol-detail-exchange {
    font-size: 12px;
    color: var(--text-secondary);
    background: var(--bg-tertiary);
    padding: 4px 8px;
    border-radius: 8px;
}

.symbol-detail-price {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 0 0 0 10px;
}

.symbol-detail-current {
    font-size: 22px;
    color: var(--text-primary);
}

.symbol-detail-current.up {
    color: var(--price-up);
}

.symbol-detail-current.down {
    color: var(--price-down);
}

.symbol-detail-current.neutral {
    color: var(--text-primary);
    font-family: var(--font-price);
}

.symbol-detail-change {
    font-size: 14px;
    color: var(--text-primary);
}

.symbol-detail-change.up {
    color: var(--price-up);
}

.symbol-detail-change.down {
    color: var(--price-down);
}

.symbol-detail-change.neutral {
    color: var(--text-primary);
}

.symbol-detail-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0;
    padding: 8px 0;
}

.detail-item {
    display: flex;
    flex-direction: column;
    padding: 8px 10px;
    border-right: 1px solid var(--border-color);
    border-bottom: 1px solid var(--border-color);
}

.detail-item:nth-child(4n) {
    border-right: none;
}

.detail-item:nth-last-child(-n+4) {
    border-bottom: none;
}

.detail-label {
    font-size: 12px;
    color: var(--text-secondary);
    margin-bottom: 4px;
    white-space: nowrap;
}

.detail-value {
    font-size: 14px;
    color: var(--text-primary);
    font-family: var(--font-price);
}

.detail-value.up {
    color: var(--price-up);
}

.detail-value.down {
    color: var(--price-down);
}

.detail-value.neutral {
    color: var(--text-primary);
}

.futures-metrics-section {
    border-top: 1px solid var(--border-color);
    margin-top: 0px;
    padding-top: 12px;
}

.futures-metrics-title {
    font-size: 12px;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 8px;
    padding: 0 12px;
}

.futures-metrics-container {
    padding-top: 0;
}

/* ========== TA Recommendation (Chart Detail) ========== */
.ta-rec-section {
    margin: 0 0 10px;
    padding: 10px 10px 10px;
    border: 1px solid var(--border-color);
    border-radius: 4px;
}
.ta-rec-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 6px;
}
.ta-rec-title {
    font-size: 12px;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.ta-rec-interval {
    font-size: 12px;
    color: var(--text-primary);
    font-family: var(--font-price);
}
.ta-rec-meter {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
}
.ta-rec-gauge {
    position: relative;
    width: 100%;
    max-width: 200px;
    margin: 0 auto;
}
/* Shared half-dial centre readout — viewBox 0 0 160 96 calibrated.
   Greed interior mid-y = (25.7 + 86) / 2 / 96 ≈ 58.2%. */
.tt-half-gauge-meta,
.si-gauge-meta {
    position: absolute;
    left: 50%;
    top: 58.2%;
    transform: translate(-50%, -50%);
    text-align: center;
    line-height: 1.1;
    pointer-events: none;
}
.tt-half-gauge-meta--ta {
    top: 61%;
}
.ta-rec-gauge .ta-rec-score {
    display: block;
    font-size: 22px;
    font-weight: 700;
    color: var(--text-primary);
    font-family: var(--font-price);
}
.ta-rec-gauge .ta-rec-label {
    display: block;
    margin-top: 2px;
}
.ta-rec-gauge-svg {
    width: 100%;
    height: auto;
    display: block;
    color: var(--text-primary);
}
.ta-rec-scale {
    width: 100%;
    max-width: 200px;
    margin: -2px auto 2px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
}
.ta-rec-scale span {
    font-size: 11px;
    font-weight: 500;
    line-height: 1.2;
    white-space: nowrap;
}
.ta-rec-scale-strong-sell { color: var(--price-down); }
.ta-rec-scale-sell { color: var(--price-down); }
.ta-rec-scale-neutral { color: var(--text-secondary); }
.ta-rec-scale-buy { color: var(--price-up); }
.ta-rec-scale-strong-buy { color: var(--price-up); }
.ta-rec-meta {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    text-align: center;
}
.ta-rec-score {
    font-size: 14px;
    color: var(--text-primary);
    font-family: var(--font-price);
}
.ta-rec-label {
    font-size: 12px;
    font-weight: 600;
}
.ta-rec-label.up { color: var(--price-up); }
.ta-rec-label.down { color: var(--price-down); }
.ta-rec-label.neutral { color: var(--text-primary); }
.ta-rec-meta .ta-rec-btn {
    margin-top: 2px;
}
.ta-rec-counts {
    display: flex;
    gap: 6px;
    margin-top: 8px;
    flex-wrap: wrap;
    align-items: center;
}
.ta-rec-count {
    font-size: 12px;
    border: 1px solid var(--border-color);
    border-radius: 999px;
    padding: 2px 8px;
    color: var(--text-secondary);
}
.ta-rec-count b {
    color: var(--text-primary);
    font-family: var(--font-price);
}
.ta-rec-count--buy b { color: var(--price-up); }
.ta-rec-count--sell b { color: var(--price-down); }
.ta-rec-btn {
    border: 1px solid var(--border-color);
    background: transparent;
    color: var(--text-primary);
    border-radius: 4px;
    padding: 4px 10px;
    font-size: 12px;
    cursor: pointer;
}
.ta-rec-btn:hover { border-color: var(--accent-color); }
.ta-rec-updated {
    margin-left: auto;
    font-size: 12px;
    color: var(--text-secondary);
    white-space: nowrap;
}

/* ========== Panel Close Handle ========== */
.panel-close-handle {
    position: absolute;
    left: 0;
    bottom: 50px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 12px;
    height: 20px;
    background: transparent;
    border: none;
    cursor: pointer;
    transition: background 0.2s;
    z-index: 10;
}

.panel-close-handle:hover {
    background: var(--state-active);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
    border-radius: 2px;
}

.panel-close-handle svg {
    width: 10px;
    height: 10px;
    stroke: var(--text-secondary);
    transition: stroke 0.2s;
}

.panel-close-handle:hover svg {
    stroke: var(--text-primary);
}

/* Ensure parent panels have position relative */
.middle-panel-top,
.middle-panel-bottom,
.right-panel-top,
.right-panel-bottom {
    position: relative;
}

/* Toolbar button disabled state */
.toolbar-btn.disabled {
    opacity: 0.4;
    cursor: not-allowed;
    pointer-events: none;
}

/* Right Panel Top Section (Symbol List) */
/* overflow: visible on container so exchange dropdown is not clipped */
.right-panel-top {
    height: 40%;
    min-height: 120px;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    overflow: visible;
}

.right-panel-top.hidden {
    display: none;
}

/* When bottom panel is hidden, top panel should expand */
.right-panel-top.full-height {
    flex: 1;
    height: auto;
}

/* Right Panel Bottom Section (Orderbook & Trades) */
.right-panel-bottom {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    min-height: 200px;
}

.right-tabs {
    display: flex;
    border-bottom: none;
    padding: 0 12px 0 18px;
    gap: 16px;
    align-items: center;
    height: 48px;
    box-sizing: border-box;
    flex-shrink: 0;
}

/* [CHANGED] 2026-05-23 — parent gap raised from 0 to 5px.
 *
 * User invariant: "the two non-active tabs must always sit exactly
 * 5px apart, regardless of which tab is active". With the previous
 * `gap: 0` design we relied on a divider span + per-side 12px
 * margin to space the trailing pair, but the divider's fixed HTML
 * position broke the invariant in the largeTrades-active state.
 *
 * Pure flex `gap: 5px` guarantees a constant 5px between *every*
 * adjacent flex item (including the drag-handle ↔ leading tab pair
 * — which is fine because we additionally pull the leading tab
 * 1px closer via the per-tab override below, matching the 1px
 * symbol-detail handle↔icon canonical distance). */
.right-tabs.dock-module-header {
    gap: 5px;
    padding: 0 12px 0 12px;
}

/* drag-handle hugs the first tab with the canonical 1px gap used by
 * `.symbol-detail-header .symbol-detail-drag-handle + .symbol-detail-
 * icon`. We use a negative right-margin to subtract 4px from the
 * parent `gap: 5px`, so visually the handle ↔ first-tab spacing
 * equals 1px exactly. */
.right-tabs.dock-module-header .module-drag-handle {
    margin-right: -4px;
}

.panel-header.dock-module-header {
    gap: 0;
}

.panel-header.dock-module-header .module-drag-handle {
    margin-right: 1px;
}

/* [ADD] 2026-05-23 — unify the leading drag-handle ↔ first-child
 * (icon for module titles, label for tab strips) distance across
 * every dock-module header to the canonical 1px used by the Symbol
 * Details header. Two headers need their parent `gap` clamped to 0
 * here because they win the cascade fight against `.dock-module-
 * header`: `.exchange-selector` (Markets) wins via source-order but
 * gap:8 was already replaced by .dock-module-header gap:1, leaving
 * a stray 1px; `.pt-header` (Paper Trading) wins with gap:6.
 *
 * After this rule all four headers share the formula:
 *   parent-gap (0) + drag-handle margin-right (1px) = 1px
 *   matching `.symbol-detail-header` exactly.
 */
.exchange-selector.dock-module-header,
.pt-header.dock-module-header {
    gap: 0;
}
.exchange-selector.dock-module-header > .module-drag-handle,
.pt-header.dock-module-header > .module-drag-handle {
    margin-right: 1px;
}

.right-tab {
    padding: 0;
    background: transparent;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 14px;
    font-weight: 500;
    order: 1;
    display: flex;
    align-items: center;
    /* [CHANGED] 2026-05-23 — Inactive tabs use a tight 3px icon↔label
     * gap so the small 16px discs sit close to their wording. The
     * active tab is overridden below to gap: 8px so its 24px disc
     * matches the spacing of the Markets / Liquidation module-title
     * icons (which use `.dock-module-header > .module-icon { margin-
     * right: 8px }`). Users requested:
     *   "订单簿 激活的标签页的 图标文字距离要和爆仓/市场的一样" → 8px
     *   "两个非激活标签 图标和文字距离近一些"                  → 3px */
    gap: 3px;
}

.right-tab svg {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

.right-tab:hover {
    color: var(--text-primary);
}

.right-tab.active {
    color: var(--text-primary);
    font-size: 14px;
    font-weight: 600;
    order: 0;
    margin-right: auto;
    /* [ADD] 2026-05-23 — active tab carries module-identity weight,
     * so its 24px disc uses the same 8px icon↔label gap as the
     * Markets / Liquidation module-title icons. Inactive tabs
     * stay tight at the parent's 3px. */
    gap: 8px;
}

/* [REMOVED] 2026-05-23 — `.right-tab-divider` styles deleted.
 * The 1px vertical rule between Recent Trades and Large Trades was
 * replaced by a pure flex `gap: 5px` on `.right-tabs.dock-module-
 * header` (see above). See chart.ejs around the largeTrades button
 * for the matching DOM cleanup. Reason: the divider's fixed HTML
 * position broke the user-required invariant that the two non-
 * active tabs sit exactly 5px apart in all three active-state
 * permutations. */

.right-content {
    flex: 1;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.tab-pane {
    height: 100%;
    display: none;
    flex-direction: column;
}

.tab-pane.active {
    display: flex;
}

/* ========== Orderbook ========== */
.orderbook {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    cursor: default;
}

.tab-pane[data-pane="orderbook"].gmx-liquidity-mode .stats-bar,
.tab-pane[data-pane="orderbook"].gmx-liquidity-mode .orderbook-controls,
.tab-pane[data-pane="orderbook"].gmx-liquidity-mode .orderbook {
    display: none;
}

.gmx-liq-panel {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 8px 12px 12px 18px;
    color: var(--text-primary);
    overflow-y: auto;
    min-height: 0;
}

.gmx-liq-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.gmx-liq-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
}

.gmx-liq-disabled {
    display: none;
    align-items: center;
    padding: 2px 8px;
    border-radius: 999px;
    border: 1px solid rgba(239, 83, 80, 0.6);
    color: var(--price-down);
    background: rgba(239, 83, 80, 0.14);
    font-size: 12px;
    font-weight: 600;
}

.gmx-liq-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
}

.gmx-liq-item,
.gmx-liq-section {
    border: 1px solid color-mix(in srgb, var(--border-color) 75%, transparent);
    border-radius: 8px;
    background: color-mix(in srgb, var(--surface-elevated, var(--bg-primary)) 92%, transparent);
    padding: 8px 10px;
}

.gmx-liq-label,
.gmx-liq-section-title,
.gmx-liq-meta {
    color: var(--text-secondary);
    font-size: 12px;
}

.gmx-liq-section-title {
    margin-bottom: 6px;
}

.gmx-liq-value {
    color: var(--text-primary);
    font-size: 14px;
    font-family: var(--font-price);
    font-weight: 600;
}

.gmx-liq-item .gmx-liq-value {
    display: block;
    margin-top: 2px;
}

.gmx-liq-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.gmx-liq-row + .gmx-liq-row {
    margin-top: 4px;
}

.gmx-liq-side {
    font-size: 12px;
    font-weight: 500;
}

.gmx-liq-side--long {
    color: var(--price-up);
}

.gmx-liq-side--short {
    color: var(--price-down);
}

.gmx-liq-oi-bar {
    display: flex;
    width: 100%;
    height: 8px;
    border-radius: 999px;
    overflow: hidden;
    margin-bottom: 6px;
    background: color-mix(in srgb, var(--border-color) 60%, transparent);
}

.gmx-liq-oi-bar-long {
    background: rgba(38, 166, 154, 0.8);
    width: 50%;
    transition: width 0.2s ease;
}

.gmx-liq-oi-bar-short {
    background: rgba(239, 83, 80, 0.8);
    width: 50%;
    transition: width 0.2s ease;
}

.orderbook-header {
    padding: 8px 12px 6px 18px;
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 6px;
    font-size: 14px;
    color: var(--text-secondary);
    border-bottom: none;
    flex-shrink: 0;
}

.orderbook-header div {
    text-align: right;
}

.orderbook-header div:first-child {
    text-align: left;
}

.orderbook-body {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    min-height: 0; /* Important: Allow the body to shrink */
    position: relative;
}

.market-empty {
    display: none;
    align-items: center;
    justify-content: center;
    text-align: center;
    pointer-events: none;
    z-index: 2;
}

.market-empty.visible {
    display: flex;
}

.market-empty--chart {
    position: absolute;
    inset: 0;
}

.market-empty--orderbook,
.market-empty--trades {
    position: absolute;
    inset: 0;
}

.orderbook-asks-wrapper,
.orderbook-bids-wrapper {
    flex: 1 1 0; /* flex-grow:1, flex-shrink:1, flex-basis:0 */
    min-height: 0; /* Allow shrinking below content size */
    max-height: 100%; /* Don't exceed available space */
    overflow: hidden;
    position: relative;
    transition: flex 0.2s ease;
}

/* When hidden, set flex to 0 to give space to the other */
.orderbook-asks-wrapper[style*="display: none"],
.orderbook-bids-wrapper[style*="display: none"] {
    flex: 0 0 0;
    min-height: 0;
    overflow: hidden;
}

/* CRITICAL: .orderbook-asks and .orderbook-bids share identical overflow/scrollbar rules below — keep selector lists paired */
.orderbook-asks,
.orderbook-bids {
    height: 100%;
    overflow-y: auto;  /* CRITICAL: Use 'auto' to show scrollbar only when needed - BOTH asks and bids */
    overflow-x: hidden;
    padding: 2px 12px 2px 18px;
    display: flex;
    flex-direction: column;
}

/* Asks should stick to bottom using ::before spacer - allows proper overflow */
.orderbook-asks::before {
    content: '';
    flex-grow: 1;  /* Pushes content to bottom when content < container height */
    flex-shrink: 1;  /* Shrinks when content > container height, allowing overflow */
}

/* Bids use default flex-start behavior (no special styles needed) */

/* ==================================================================================
   CRITICAL - DO NOT MODIFY
   Orderbook asks (.orderbook-asks) and bids (.orderbook-bids) scrollbar styling must
   stay symmetric. Any rule that targets one side must include the other with the same
   specificity so their scrollbars match (width, track, thumb, hover colors).
   1. Keep .orderbook-asks and .orderbook-bids selector lists paired in every rule below.
   2. Scrollbar width: 3px for both.
   3. Track: transparent for both.
   4. Thumb: transparent by default; #c8ccd8 on wrapper hover; #a0a4b0 on thumb hover.
   5. overflow-y: auto on both containers (scrollbar only when content overflows).
   ================================================================================== */

/* Scrollbar width - BOTH asks and bids must be identical */
.orderbook-asks::-webkit-scrollbar,
.orderbook-bids::-webkit-scrollbar {
    width: 3px;
}

/* Scrollbar track - BOTH asks and bids must be transparent */
.orderbook-asks::-webkit-scrollbar-track,
.orderbook-bids::-webkit-scrollbar-track {
    background: transparent;
}

/* Scrollbar thumb default - BOTH asks and bids are hidden (transparent) */
.orderbook-asks::-webkit-scrollbar-thumb,
.orderbook-bids::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 2px;
    transition: background 0.2s;
}

/* Scrollbar appears when hovering the wrapper - BOTH asks and bids */
.orderbook-asks-wrapper:hover .orderbook-asks::-webkit-scrollbar-thumb,
.orderbook-bids-wrapper:hover .orderbook-bids::-webkit-scrollbar-thumb {
    background: #c8ccd8;
}

/* Scrollbar darker when hovering thumb itself - BOTH asks and bids */
.orderbook-asks::-webkit-scrollbar-thumb:hover,
.orderbook-bids::-webkit-scrollbar-thumb:hover {
    background: #a0a4b0;
}

/* [FIX] 2026-05-19 — Touch devices have no hover, so the hover-only
 * scrollbar above is permanently invisible on mobile / iPad — users
 * literally don't know the asks list is scrollable and end up only seeing
 * whichever slice loads first. Make the asks/bids scrollbars always visible
 * on coarse pointers (touchscreens). Keep the desktop hover-only behaviour
 * so the trader's mouse-driven workflow stays uncluttered.
 *
 * The thumb colour mirrors `--tt-fg-2` weight on dark / light themes,
 * matching the rest of the chart-page scrollbar treatment. */
@media (pointer: coarse) {
    .orderbook-asks,
    .orderbook-bids {
        scrollbar-width: thin; /* Firefox */
        scrollbar-color: rgba(160, 164, 176, 0.6) transparent; /* Firefox */
    }
    .orderbook-asks::-webkit-scrollbar,
    .orderbook-bids::-webkit-scrollbar {
        width: 4px;
    }
    .orderbook-asks::-webkit-scrollbar-thumb,
    .orderbook-bids::-webkit-scrollbar-thumb {
        background: rgba(160, 164, 176, 0.6);
    }
    .orderbook-asks::-webkit-scrollbar-thumb:active,
    .orderbook-bids::-webkit-scrollbar-thumb:active {
        background: rgba(160, 164, 176, 0.9);
    }
}

/* ================================================================================== */

.orderbook-row {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 6px;
    padding: 2px 0;
    font-family: var(--font-price);
    flex-shrink: 0;
    font-size: 14px;
    position: relative;
    cursor: pointer;
}

.orderbook-row > div {
    position: relative;
    z-index: 1;
    text-align: right;
}

.orderbook-row > div:first-child {
    text-align: left;
}

.price-bid {
    font-family: var(--font-price);
    color: var(--price-up);
    font-weight: 500;
}

.price-ask {
    font-family: var(--font-price);
    color: var(--price-down);
    font-weight: 500;
}

.amount {
    font-family: var(--font-price);
    color: var(--text-primary);
}

.total {
    font-family: var(--font-price);
    color: var(--text-secondary);
}

.spread-info {
    padding: 6px 12px;
    background: transparent;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-shrink: 0; /* Never shrink, always visible */
    min-height: 28px; /* Ensure minimum height */
    z-index: 1; /* Stay on top */
}

.spread-value {
    display: flex;
    align-items: baseline;
    gap: 6px;
    /* No transition initially to avoid flash on first load */
}

.spread-value .spread-price {
    font-family: var(--font-price);
    font-size: 16px;
    font-weight: 700;
    /* No transition initially to avoid flash on first load */
}

.spread-value .spread-percent {
    font-family: var(--font-price);
    font-size: 11px;
    font-weight: 400;
    /* No transition initially to avoid flash on first load */
}

/* Enable smooth transition after page is loaded */
.spread-value.loaded,
.spread-value.loaded .spread-price,
.spread-value.loaded .spread-percent {
    transition: color 0.3s ease;
}

.spread-value .percent-sign {
    display: inline-block;
    width: 8px; /* Fixed width for +/- sign to prevent layout shift */
    text-align: center;
}

.spread-value.up,
.spread-value.up .spread-price,
.spread-value.up .spread-percent {
    color: var(--price-up);
}

.spread-value.down,
.spread-value.down .spread-price,
.spread-value.down .spread-percent {
    color: var(--price-down);
}


/* ========== Trades ========== */
.trades {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Trade Flow Section */
.trade-flow-section {
    border-bottom: none;
    flex-shrink: 0;
    margin-bottom: 8px;
}

.trade-flow-header {
    padding: 6px 12px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 12px;
    color: var(--text-secondary);
}

.trade-flow-title {
    font-weight: 500;
}

.trade-flow-stats {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 12px;
    font-weight: 600;
}

.trade-flow-stats .trade-flow-count {
    color: var(--text-secondary);
}

.trade-flow-stats .buy-ratio {
    color: var(--price-up);
    font-weight: 700;
}

.trade-flow-stats .sell-ratio {
    color: var(--price-down);
    font-weight: 700;
}

.trade-flow-stats .stats-divider {
    color: var(--text-secondary);
    opacity: 0.5;
}

.trade-flow-container {
    height: 60px;
    width: 100%;
    padding: 0 8px;
    box-sizing: border-box;
}

.trades-header {
    padding: 8px 12px 6px;
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 6px;
    font-size: 14px;
    color: var(--text-secondary);
    border-bottom: none;
    flex-shrink: 0;
}

.trades-body {
    flex: 1;
    overflow: hidden;
    position: relative;
}

.trades-content {
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 0;
}

/* Scrollbar - hidden by default, show on hover (same as symbol list) */
.trades-content::-webkit-scrollbar {
    width: 3px;
}

.trades-content::-webkit-scrollbar-track {
    background: transparent;
}

.trades-content::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 2px;
    transition: background 0.2s;
}

.trades-body:hover .trades-content::-webkit-scrollbar-thumb {
    background: #c8ccd8;
}

.trades-content::-webkit-scrollbar-thumb:hover {
    background: #a0a4b0;
}

.trade-row {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 6px;
    padding: 5px 12px;
    font-family: var(--font-price);
    font-size: 14px;
    transition: background 0.2s;
    cursor: pointer;
}

.trade-row:hover {
    background: var(--bg-tertiary);
}

.trade-price.buy {
    color: var(--price-up);
}

.trade-price.sell {
    color: var(--price-down);
}

.trade-amount {
    color: var(--text-primary);
    text-align: right;
}

.trade-time {
    color: var(--text-secondary);
    text-align: right;
    font-size: 14px;
}

/* ========== Large Trades Tab ==========
 * Lives inside the orderbook module (#middlePanelBottom) as a third
 * tab named "Large Trades". Same visual rhythm as the Recent Trades
 * list — 5-column grid (time / side / price / qty / usd) — plus a
 * compact toolbar above it for venue / TOP-N / window selection.
 */
.tab-pane[data-pane="largeTrades"] {
    overflow: hidden;
}

/* [REFACTOR] 2026-05-22 — toolbar grid layout.
 * Industry-standard filter panels (Binance / OKX / Coinglass) lay
 * controls in a uniform grid rather than a left-aligned flex row.
 * We use 3 equal columns for the 6 select controls (filling 2 rows
 * neatly), and let the Reset button float in a 4th narrow column
 * that spans both rows on the far right — so it visually anchors
 * the filter cluster the way the "refresh" icon does on Coinglass'
 * heatmap toolbar. minmax(0,1fr) lets each select shrink without
 * blowing out the parent's width. */
/* [REMOVED] 2026-05-23 — `.lt-toolbar / .lt-ctrl / .lt-ctrl-label /
 * .lt-select / .lt-reset-btn / .lt-reset-label / .lt-toolbar__reset`
 * styles deleted. The five filter selects and the Reset button
 * moved out of the in-tab toolbar into a floating settings popover
 * (`.tt-settings-popover` defined further down in the file).
 * The DOM elements with these same IDs are recreated inside the
 * popover body by `tt.large-trades.js#buildSettingsHtml`, using
 * the `.tt-set-*` classes so they pick up popover-internal
 * styling. */

/* [REDESIGN] 2026-05-23 — Summary cards (Large Trades + Liquidations).
 *
 * Previously the summary row sat flush against the list header with a
 * single 1px border, which read as one tall noisy block to the user
 * ("堆在一起"). The new design treats the summary as a self-contained
 * CARD and the list below as a self-contained TABLE, with a clear gap
 * separating the two.
 *
 * Visual recipe (matches Binance / Coinglass aggregate panels):
 *   - Subtle background tint (theme-aware) so the card pops above the
 *     module's base background without using a hard border.
 *   - 10px outer margin-bottom = the air gap separating card from list.
 *   - Internal padding 10px / 14px so the stat cells breathe.
 *   - No bottom border — the air gap does the separation.
 *   - The list header below regains its own subtle bottom border so
 *     the table-style "column titles" affordance still reads as a
 *     proper header row, not the bottom of the card.
 *
 * Theme tints (kept low so the card never fights the list for focus):
 *   dark  theme  → rgba(255,255,255,.03)
 *   light theme  → rgba(0,0,0,.025)
 * Both values are derived from the same `--summary-card-bg` variable
 * so future theme tokens can override one spot.
 *
 * The .lt-stats-bar / .liq-stats-bar classes stay separate (no class
 * sharing) so future per-module tweaks (e.g. a long/short ratio
 * progress strip on the Liquidation card) don't bleed into Large
 * Trades. Common styling lives on a small shared utility class
 * `.tt-summary-card` only for the background + spacing recipe. */

.tt-summary-card {
    background: var(--summary-card-bg, rgba(255, 255, 255, 0.03));
    border-radius: 8px;
    margin: 10px 12px 10px 18px;
    padding: 8px 14px;
    display: flex;
    align-items: stretch;
    gap: 0;
}

body.light-theme .tt-summary-card {
    background: rgba(0, 0, 0, 0.025);
}

.lt-stats-bar,
.liq-stats-bar {
    /* Composed via .tt-summary-card in the HTML so the shared recipe
     * above provides background / radius / margin / padding. These
     * two classes now only host module-specific layout tweaks. */
}

/* [CHG] 2026-05-24 — refactor stat cell layout to mirror the
 * "币对详情 / Futures metrics" grid styling (`.symbol-detail-grid`
 * + `.detail-item`). User asked for the summary bar to "用币对详情
 * 的合约/持仓量/资金费率/多空比 这种带分割线的卡片化外观,UI 看
 * 起来更好看".
 *
 * Implementation — keep the existing flex layout (gear must stay at
 * the end with margin-left:auto), but:
 *   1. Each stat cell carries its own horizontal padding so the
 *      divider gets breathing room on both sides
 *   2. `border-right` is the divider — applied to every stat EXCEPT
 *      the last one (5th cell, before the gear's own ::before
 *      separator) to avoid a double-line right next to the existing
 *      `--toolbar-sep` divider on the gear
 *   3. Label gets uppercase + tracking like the futures-metrics-title
 *      so it reads as a "kpi caption"
 *   4. Value gains weight (16px, font-price family) so it pops above
 *      the 14px label — same hierarchy as `.detail-label` (11px) vs
 *      `.detail-value` (13px), scaled up two notches because the
 *      stats bar lives outside the dense detail grid
 *
 * Font sizes 14 / 16 are both inside the project's typography
 * whitelist `{12, 14, 16, 18, 22, 28}`. Colors stay on the existing
 * `--text-primary` / `--text-secondary` / `--toolbar-sep` /
 * `--price-up` / `--price-down` tokens — no new hardcoded values.
 *
 * [WARN] do NOT remove the gear's own `::before` divider — it lives
 * with `--toolbar-sep` weight per the 2026-05-23 product spec. The
 * stat-side divider here is `:nth-last-of-type(2)` so it visually
 * stops one cell before the gear, letting the gear's own divider
 * take over the final separation. */
.lt-stats-bar .lt-stat,
.liq-stats-bar .liq-stat {
    flex: 1 1 0;
    min-width: 0;
    padding: 2px 14px;
    position: relative;
}

.lt-stats-bar .lt-stat:first-child,
.liq-stats-bar .liq-stat:first-child {
    padding-left: 4px;
}

.lt-stats-bar .lt-stat + .lt-stat,
.liq-stats-bar .liq-stat + .liq-stat {
    border-left: 1px solid var(--toolbar-sep);
}

.lt-stats-bar .tt-module-gear,
.liq-stats-bar .tt-module-gear {
    margin-left: auto;
    flex-shrink: 0;
    position: relative;
}

/* [ADD] 2026-05-23 — vertical divider between the stat cluster and
 * the filter (gear) button, per product spec "过滤按钮前面加一个
 * | 分隔符".
 *
 * [FIX] 2026-05-23 v3 — color aligned with the chart drawing
 * toolbar dividers (`.toolbar-divider`). Reuses the dedicated
 * `--toolbar-sep` token so both surfaces feel like one family:
 *   dark theme  → #2a2e39
 *   light theme → #d6dae3
 * Earlier rgba half-alpha grey worked but desynced from the
 * drawing toolbar look; the user explicitly asked for the same
 * separator weight. Sitting at left: -10px keeps clear breathing
 * room from both the stat cluster and the 24px gear button.
 */
.lt-stats-bar .tt-module-gear::before,
.liq-stats-bar .tt-module-gear::before {
    content: '';
    position: absolute;
    left: -10px;
    top: 2px;
    bottom: 2px;
    width: 1px;
    background: var(--toolbar-sep);
    pointer-events: none;
}

.lt-stat,
.liq-stat {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
}

/* [CHG] 2026-05-24 r2 — drop uppercase + tracking on labels per
 * virtuou-哥 feedback: "爆仓 大单 总结的标题 要用小写". The
 * original "Total / Long / Short / Largest / Count" wording in
 * the i18n dictionary is already mixed-case sentence form, and
 * forcing CSS-level uppercase made them shout (TOTAL / LARGEST)
 * which clashed with the rest of the chart UI's casual case.
 * Removing `text-transform` + `letter-spacing` lets each locale
 * render exactly what the dictionary says — Chinese / Japanese
 * users see the original "总额 / 多单 / 空单 / 最大 / 单数" /
 * "合計 / ロング" etc., and English speakers see Title Case
 * instead of all-caps. Other label rules (color, size, ellipsis)
 * stay untouched so the rest of the summary-bar typography
 * recipe (label vs value hierarchy via 14px → 16px + font-price
 * weight) survives this revert. */
/* [CHG] 2026-05-24 r5 — summary-card type scale tightened per
 * virtuou-哥: "标题 12PX  值 14PX". The previous 14/16 pair was
 * carried over from the symbol-detail card grid which has more
 * vertical breathing room; on the much shorter Liquidations /
 * Large Trades stats bar (one row, 5 cells) the larger numbers
 * crowded the divider rules and pushed the gear button off-axis.
 * Dropping one notch on the white-list (16→14, 14→12) restores
 * the spacing without sacrificing legibility — both sizes are
 * still on the chart-page font white-list and the value cell
 * keeps its monospace --font-price family for column alignment. */
.lt-stat-label,
.liq-stat-label {
    color: var(--text-secondary);
    font-size: 12px;
    line-height: 1.1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.lt-stat-value,
.liq-stat-value {
    color: var(--text-primary);
    font-family: var(--font-price);
    font-size: 14px;
    line-height: 1.15;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.lt-stat-value.up,
.liq-stat-value.up   { color: var(--price-up); }
.lt-stat-value.down,
.liq-stat-value.down { color: var(--price-down); }

/* [ADD] 2026-05-24 — mini volume chart hosted between the summary bar
 * and the events list (Liquidations + Large Trades modules). The
 * canvas height is fixed at 96px so the list still gets the bulk of
 * the panel's vertical space; the toolbar is a slim 24px row of three
 * pill buttons (Bar / Line / Scatter). The chart body is rendered by
 * tt.mini-chart.js using LightweightCharts v5.
 *
 * Layout responsibilities:
 *   - .tt-mini-chart       — outer wrapper, controls vertical rhythm.
 *   - .tt-mini-chart__toolbar — horizontal flex row, right-aligned.
 *   - .tt-mini-chart__canvas  — pure host for the LWC <canvas>.
 *
 * Theme handling: the LWC instance reads body.dark-theme on each
 * rebuild for the axis text colour; the toolbar buttons rely on
 * standard CSS variables (--bg-tertiary / --accent-color / etc.) so
 * theme switches don't require JS work.
 *
 * Constraint: font-size 12px is already on the white-list; no new
 * hard-coded colours — all hues route through CSS variables. */
.tt-mini-chart {
    width: 100%;
    padding: 4px 8px 6px;
    background: transparent;
    box-sizing: border-box;
}

.tt-mini-chart__toolbar {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 4px;
    height: 24px;
    margin-bottom: 4px;
}

/* [CHG] 2026-05-24 r2 — toolbar buttons recolored to neutral
 * black/white per virtuou-哥: "切换按钮不要用蓝色 用黑白色".
 *
 * Active state inverts the page text/background pair:
 *   - light theme: --text-primary (#000) bg + --bg-primary (#fff) text
 *                  → black pill with white label
 *   - dark theme:  --text-primary (#fff) bg + --bg-primary (#000) text
 *                  → white pill with black label
 *
 * Inactive state keeps the original muted look — `--bg-tertiary`
 * background with `--text-secondary` label — so the active button
 * stands out without drawing the user's attention to a colourful
 * accent (the chart pane already uses red/green for direction).
 *
 * Hover (inactive only) shifts toward the page text colour with a
 * subtle bg lift, matching the rest of the chart-page toolbar
 * conventions (`.lt-col-h:hover`, `.tt-module-gear:hover`). */
.tt-mini-chart__btn {
    appearance: none;
    border: 1px solid transparent;
    border-radius: 4px;
    background: var(--bg-tertiary, transparent);
    color: var(--text-secondary);
    cursor: pointer;
    font-size: 12px;
    line-height: 1;
    padding: 4px 8px;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.tt-mini-chart__btn:hover {
    background: var(--bg-hover, var(--bg-tertiary));
    color: var(--text-primary);
}

.tt-mini-chart__btn.is-active,
.tt-mini-chart__btn.is-active:hover {
    background: var(--text-primary);
    border-color: var(--text-primary);
    color: var(--bg-primary);
}

/* [ADD] 2026-05-24 r6 — icon-only variant of .tt-mini-chart__btn.
 *   Used by the bar/line/scatter type switcher in tt.mini-chart.js.
 *   Square layout with centred 16x16 SVG (currentColor stroke so it
 *   inherits the .is-active inversion automatically). Padding tuned
 *   so the button visual height matches text-mode buttons (24px) and
 *   the toolbar row total height is unchanged.                       */
.tt-mini-chart__btn--icon {
    width: 26px;
    height: 22px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.tt-mini-chart__btn--icon > svg {
    width: 14px;
    height: 14px;
    display: block;
}

.tt-mini-chart__canvas {
    width: 100%;
    height: 96px;
    min-height: 96px;
}

/* On very narrow viewports the toolbar wraps onto a second row, which
 * looks cramped above a 96px chart. Tighten the canvas a bit on
 * mobile so the whole module still fits in the standard panel. */
@media (max-width: 480px) {
    .tt-mini-chart__canvas {
        height: 84px;
        min-height: 84px;
    }
}

/* [ADD] 2026-05-24 r3 — hover tooltip rendered by tt.mini-chart.js.
 * Floats above the canvas absolute-positioned, follows the cursor
 * with a 12px offset, and flips to the opposite side near edges
 * (handled in JS). Three rows: Buy/Short, Sell/Long, Total — each
 * with a coloured label and a right-aligned USD value.
 *
 * Surface tokens reuse the chart-page popover family (--popover-bg
 * / --popover-border / --popover-shadow). This keeps the tooltip
 * visually aligned with the settings popover and the AI dropdown
 * (Bloomberg / TradingView Pro convention: same elevation language
 * across all floating surfaces). Background tracks the page colour
 * (#fff in light, #000 in dark) so we never paint a third grey
 * shade, and a 1px brand outline plus drop shadow do the lifting.
 *
 * pointer-events: none keeps the tooltip from intercepting mouse
 * moves — without it, hovering ON the tooltip would dismiss the
 * crosshair and create a flicker loop. */
.tt-mini-chart__tooltip {
    position: absolute;
    z-index: 6;
    min-width: 140px;
    padding: 6px 8px;
    border: 1px solid var(--popover-border);
    border-radius: 4px;
    background: var(--popover-bg);
    color: var(--text-primary);
    font-size: 12px;
    line-height: 1.4;
    pointer-events: none;
    box-shadow: var(--popover-shadow);
    white-space: nowrap;
}

.tt-mini-chart__tt-time {
    font-size: 12px;
    color: var(--text-secondary);
    margin-bottom: 2px;
}

.tt-mini-chart__tt-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 16px;
}

.tt-mini-chart__tt-label {
    font-size: 12px;
}

.tt-mini-chart__tt-val {
    font-family: var(--font-price);
    font-size: 12px;
    color: var(--text-primary);
}

.tt-mini-chart__tt-total {
    margin-top: 2px;
    padding-top: 4px;
    border-top: 1px solid var(--popover-border);
}

/* [CHG] 2026-05-23 — column track ratios harmonized with the
 * Liquidations table so the two modules feel like one family.
 * Same 6 cells: Time / Exchange / Side / Price / Qty / USDT,
 * proportions tuned to character widths (Time 8ch, Exchange up
 * to "Hyperliquid" 11ch, Side 5ch, Price ~9ch, Qty ~6ch,
 * USDT ~6ch like "$2.50M"). */
.lt-list-header {
    display: grid;
    grid-template-columns: 70fr 100fr 55fr 100fr 80fr 95fr;
    gap: 6px;
    padding: 6px 12px 4px 18px;
    color: var(--text-secondary);
    font-size: 14px;
    border-bottom: 1px solid var(--border-primary);
}

/* [FIX] 2026-05-23 — Header cells must align to the same edge as
 * their corresponding row cells. <div>s do stretch to fill the
 * grid track by default, so text-align: right works, but we add
 * justify-self: end as a belt-and-braces guarantee that the
 * header text right-aligns even if a future template change
 * removes the wrapping div. Columns: time | exchange | side |
 * price | qty | usd — last 3 are numeric and right-aligned. */
.lt-list-header > div:nth-child(4),
.lt-list-header > div:nth-child(5),
.lt-list-header > div:nth-child(6) {
    text-align: right;
    justify-self: end;
}

/* [ADD] 2026-05-23 — sortable column headers. The whole header
 * cell is clickable; the sort-arrow glyph sits to the right of
 * the label (or to the left for right-aligned numeric columns
 * via flex-direction reversal — see the :nth-child rules below).
 * Active column gets text-primary color to call out the current
 * sort key without a heavier highlight. */
.lt-col-h {
    display: flex;
    align-items: center;
    gap: 4px;
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
}
.lt-col-h:hover {
    color: var(--text-primary);
}
.lt-col-h--active {
    color: var(--text-primary);
}
.lt-sort-arrow {
    font-size: 12px;
    line-height: 1;
    width: 10px;
    text-align: center;
    color: var(--text-primary);
}
/* Numeric right-aligned columns: keep the label on the right and
 * push the arrow to its left so the right edge of the column
 * still ends with text (not an arrow), matching the data rows
 * underneath which are right-aligned. */
.lt-list-header > div:nth-child(4),
.lt-list-header > div:nth-child(5),
.lt-list-header > div:nth-child(6) {
    flex-direction: row-reverse;
}

/* [FIX] 2026-05-23 — Mirror the Liquidation module scrollbar
 * exactly (`.liquidation-body` + `.liquidation-list:hover .liquidation-
 * body::-webkit-scrollbar-thumb`):
 *   - width: 3px, opacity: 0   on the bar itself
 *   - track always transparent
 *   - thumb transparent by default
 *   - thumb fills to #c8ccd8 ONLY when the user hovers the tab
 *     pane (the container that wraps toolbar + stats + list +
 *     header + body — direct analogue of `.liquidation-list`)
 *   - NO `scrollbar-width: thin` (Firefox would render a wider
 *     native bar than WebKit's 3px, breaking visual parity)
 *   - NO `scrollbar-color` override (would also override
 *     Firefox's hover behaviour and diverge from Liquidation)
 *   - NO `thumb:hover` second-state color shift (Liquidation
 *     doesn't have one; Large Trades shouldn't either)
 *   - NO `transition` on thumb (instant on/off like the
 *     Liquidation module)
 */
.lt-list-body {
    flex: 1 1 auto;
    overflow-y: auto;
    overflow-x: hidden;
}

.lt-list-body::-webkit-scrollbar {
    width: 3px;
    opacity: 0;
}

.lt-list-body::-webkit-scrollbar-track {
    background: transparent;
}

.lt-list-body::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 2px;
}

.tab-pane[data-pane="largeTrades"]:hover .lt-list-body::-webkit-scrollbar-thumb {
    background: #c8ccd8;
}

/* [FIX] 2026-05-23 — Row & header use identical grid tracks so the
 * 6 columns of (time / exchange / side / price / qty / usd) align
 * pixel-perfectly. JS now renders each cell as a <div>, which is
 * a block-level grid item that automatically stretches to fill its
 * track — so text-align: right is sufficient. We still keep
 * justify-self on the numeric columns as a belt-and-braces
 * guarantee for future cell-template changes. */
.lt-row {
    display: grid;
    grid-template-columns: 70fr 100fr 55fr 100fr 80fr 95fr;
    gap: 6px;
    padding: 5px 12px 5px 18px;
    font-family: var(--font-price);
    font-size: 14px;
    font-weight: 400;
    transition: background 0.15s;
}

.lt-row:hover {
    background: var(--bg-tertiary);
}

.lt-cell {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

.lt-time {
    color: var(--text-secondary);
    font-size: 14px;
    justify-self: start;
}

/* [ADD] 2026-05-23 — Exchange (venue) column for cross-venue mode.
 * Always present in the row, even when the user has filtered to a
 * specific exchange via the dropdown — keeps the visual layout
 * stable across filter changes. */
.lt-venue {
    color: var(--text-primary);
    font-size: 14px;
    justify-self: start;
    text-transform: capitalize;
}

/* [FIX] 2026-05-23 — Remove bold from BUY/SELL tag. User feedback
 * "不能出现粗体". Color (price-up / price-down) is already a strong
 * enough channel for direction; weight stays at the row default (400). */
.lt-side {
    justify-self: start;
}
.lt-side.up   { color: var(--price-up); }
.lt-side.down { color: var(--price-down); }

.lt-price {
    color: var(--text-primary);
    text-align: right;
    justify-self: end;
}

.lt-qty {
    color: var(--text-primary);
    text-align: right;
    justify-self: end;
}

/* [FIX] 2026-05-23 — Remove bold from USD column for the same
 * reason as .lt-side above; color carries the up/down semantics. */
.lt-usd {
    text-align: right;
    justify-self: end;
}
.lt-usd.up   { color: var(--price-up); }
.lt-usd.down { color: var(--price-down); }

.lt-empty {
    padding: 18px 12px;
    text-align: center;
    color: var(--text-secondary);
    font-size: 14px;
}

/* Large Trades content stack + in-flight loading overlay */
.lt-content {
    position: relative;
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

.lt-loading {
    position: absolute;
    inset: 0;
    z-index: 3;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    background: color-mix(in srgb, var(--panel-bg, var(--bg-primary, #131722)) 82%, transparent);
    color: var(--text-secondary);
    font-size: 14px;
    pointer-events: none;
}

.lt-loading[hidden] {
    display: none !important;
}

.lt-loading__spinner {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    border: 2px solid var(--border-color, #363a45);
    border-top-color: var(--accent-color, #2962ff);
    animation: lt-loading-spin 0.85s linear infinite;
    flex-shrink: 0;
}

@keyframes lt-loading-spin {
    to { transform: rotate(360deg); }
}

.tab-pane[data-pane="largeTrades"].lt-is-loading .lt-list-body {
    opacity: 0.45;
}

/* ========== Stats ========== */
.stats-bar {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 12px;
    padding: 0 12px 6px 18px;
    margin: 0;
    font-size: 12px;
    white-space: nowrap;
}

/* ========== Orderbook Controls ========== */

.orderbook-controls {
    padding: 0 12px 8px 18px;
    border-bottom: none;
    display: flex;
    justify-content: flex-start;
    align-items: center;
    gap: 12px;
}

.view-mode-selector {
    display: flex;
    gap: 6px;
    padding: 0;
    background: transparent;
    border-radius: 0;
}

.view-mode-btn {
    padding: 4px 10px;
    font-size: 14px;
    background: transparent;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    border-radius: 0;
    border-bottom: 1px solid transparent;
    transition: color 0.2s ease, border-color 0.2s ease;
    font-weight: 500;
}

.view-mode-btn:hover {
    color: var(--text-primary);
    background: transparent;
}

.view-mode-btn.active {
    background: transparent;
    color: var(--text-primary);
    font-weight: 600;
    border-bottom-color: color-mix(in srgb, var(--text-primary) 55%, transparent);
    box-shadow: none;
}

.precision-selector {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: auto;
}

.small-select .custom-select-trigger {
    padding: 4px 8px;
    min-width: 60px;
    font-size: 14px;
}

.small-select .custom-select-trigger svg {
    width: 10px;
    height: 10px;
}

.small-select .custom-select-options {
    min-width: 60px;
}

/* ============================================
   Heatmap Color Sliders Styles
   FIX 2026-01-31: Dual-handle range slider for price range control
   Design: One slider track with two handles (left=green/short, right=red/long)
   Purpose: Control which price range shows heatmap colors
   Principle: Like TradingLite - darker colors show higher liquidation density
   ============================================ */
.heatmap-color-sliders {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-left: 12px;
    padding: 6px 12px;
    background: var(--bg-secondary);
    border-radius: 8px;
}

.slider-label {
    font-size: 11px;
    font-weight: 500;
    color: var(--text-secondary);
    white-space: nowrap;
}

.dual-slider-container {
    position: relative;
    width: 180px;
    height: 20px;
    display: flex;
    align-items: center;
}

.slider-track {
    position: absolute;
    width: 100%;
    height: 4px;
    background: var(--bg-tertiary);
    border-radius: 2px;
    z-index: 1;
}

.slider-range {
    position: absolute;
    height: 4px;
    background: linear-gradient(to right, var(--price-up) 0%, var(--price-down) 100%);
    border-radius: 2px;
    z-index: 2;
    pointer-events: none;
}

.slider-input {
    position: absolute;
    width: 100%;
    height: 4px;
    -webkit-appearance: none;
    appearance: none;
    background: transparent;
    outline: none;
    pointer-events: none;
    z-index: 3;
}

.slider-input::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    cursor: pointer;
    pointer-events: auto;
    border: 2px solid var(--bg-primary);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
    transition: transform 0.2s, box-shadow 0.2s;
}

.slider-input::-moz-range-thumb {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    cursor: pointer;
    pointer-events: auto;
    border: 2px solid var(--bg-primary);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
    transition: transform 0.2s, box-shadow 0.2s;
}

.slider-min::-webkit-slider-thumb {
    background: var(--price-up);
}

.slider-min::-moz-range-thumb {
    background: var(--price-up);
}

.slider-max::-webkit-slider-thumb {
    background: var(--price-down);
}

.slider-max::-moz-range-thumb {
    background: var(--price-down);
}

.slider-input:hover::-webkit-slider-thumb {
    transform: scale(1.15);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
}

.slider-input:hover::-moz-range-thumb {
    transform: scale(1.15);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
}

.slider-values {
    display: flex;
    gap: 8px;
    font-size: 10px;
    font-weight: 500;
}

.slider-value-min {
    color: var(--price-up);
}

.slider-value-max {
    color: var(--price-down);
}

/* Dark theme adjustments */
.dark-theme .heatmap-color-sliders {
    background: rgba(255, 255, 255, 0.05);
}

.dark-theme .slider-track {
    background: rgba(255, 255, 255, 0.1);
}




/* ========== FIX 2026-02-02: Heatmap Range Slider ========== */
.heatmap-range-slider {
    margin-left: 15px;
    padding: 0 10px;
    border-left: 1px solid var(--border-color);
    display: flex;
    align-items: center;
}

.range-slider-wrapper {
    position: relative;
    height: 20px;
    width: 200px;
    cursor: crosshair;
    user-select: none;
}

.range-slider-track {
    position: absolute;
    top: 8px;
    left: 0;
    right: 0;
    height: 4px;
    border-radius: 2px;
    border: none;
    background-image: linear-gradient(
            90deg,
            rgba(46, 213, 115, 0.8) 0%,
            rgba(46, 213, 115, 0.3) 45%,
            rgba(128, 128, 128, 0.2) 50%,
            rgba(255, 71, 87, 0.3) 55%,
            rgba(255, 71, 87, 0.8) 100%
        );
    z-index: 1;
}

.center-marker {
    position: absolute;
    left: 50%;
    top: 7px;
    bottom: 7px;
    width: 1px;
    background: rgba(255, 255, 255, 0.4);
    z-index: 2;
}

.range-mask-left,
.range-mask-right {
    position: absolute;
    top: 8px;
    height: 4px;
    background: rgba(11, 11, 21, 0.6);
    z-index: 3;
    pointer-events: none;
    transition: width 0.05s ease-out, left 0.05s ease-out;
}

.range-mask-left {
    left: 0;
}

.range-mask-right {
    right: 0;
}

.hover-indicator {
    position: absolute;
    top: 5px;
    bottom: 5px;
    width: 2px;
    background-color: #f9ca24;
    box-shadow: 0 0 10px #f9ca24, 0 0 3px #fff;
    z-index: 20;
    pointer-events: none;
    display: none;
    transform: translateX(-50%);
    transition: left 0.05s linear;
}

.hover-indicator::before {
    content: '';
    position: absolute;
    top: -4px;
    left: -4px;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 6px solid #f9ca24;
}

.hover-indicator::after {
    content: '';
    position: absolute;
    bottom: -4px;
    left: -4px;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-bottom: 6px solid #f9ca24;
}

.live-info-tag {
    position: absolute;
    top: -35px;
    transform: translateX(-50%);
    background: rgba(20, 20, 30, 0.95);
    border: 1px solid #f9ca24;
    color: #f9ca24;
    padding: 4px 10px;
    border-radius: 3px;
    font-size: 11px;
    font-weight: bold;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    z-index: 30;
    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.5);
    display: flex;
    gap: 8px;
    transition: left 0.05s linear, opacity 0.2s;
}

.live-info-tag.visible {
    opacity: 1;
}

.live-info-tag #liveType {
    border-left: 1px solid rgba(255, 255, 255, 0.3);
    padding-left: 8px;
}

.range-slider-input {
    position: absolute;
    top: 10px;
    left: 0;
    width: 100%;
    height: 10px;
    background: transparent;
    pointer-events: none;
    -webkit-appearance: none;
    appearance: none;
    z-index: 10;
    margin: 0;
}

.range-slider-input::-webkit-slider-thumb {
    -webkit-appearance: none;
    pointer-events: all;
    width: 8px;
    height: 16px;
    background: var(--text-primary);
    border: 1px solid var(--bg-primary);
    border-radius: 2px;
    cursor: col-resize;
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
    margin-top: -3px;
    position: relative;
    z-index: 11;
}

.range-slider-input::-webkit-slider-thumb:hover {
    background: #fff;
    box-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
}

#minRange::-webkit-slider-thumb {
    border-top: 2px solid #2ed573;
}

#maxRange::-webkit-slider-thumb {
    border-top: 2px solid #ff4757;
}

/* Dark theme adjustments */
body.dark-theme .range-slider-track {
    border-color: rgba(255, 255, 255, 0.15);
}

body.dark-theme .live-info-tag {
    background: rgba(30, 30, 40, 0.95);
}

/* ========== Indicator Settings Modal (Non-modal, draggable) ========== */
.ind-settings-modal {
    display: none;
    position: fixed;
    z-index: 9999;
    pointer-events: none;
}

.ind-settings-modal.show {
    display: block;
}

.ind-settings-dialog {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 900px;
    max-width: 90vw;
    max-height: 85vh;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 12px;
    box-shadow: var(--popover-shadow);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    pointer-events: auto;
}

.ind-settings-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 20px;
    border-bottom: 1px solid var(--popover-border);
    cursor: move;
    user-select: none;
}

.ind-settings-header h3 {
    margin: 0;
    font-size: 18px;
    font-weight: 600;
    color: var(--text-primary);
}

.ind-settings-close {
    width: 32px;
    height: 32px;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--text-primary);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.2s, color 0.2s;
}

.ind-settings-close svg {
    width: 22px;
    height: 22px;
}

.ind-settings-close:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
}

.ind-settings-body {
    display: flex;
    flex: 1;
    min-height: 0;
    overflow: hidden;
}

/* Left Panel */
.ind-settings-left {
    width: 240px;
    border-right: 1px solid var(--popover-border);
    display: flex;
    flex-direction: column;
}

.ind-settings-search {
    display: flex;
    align-items: center;
    padding: 12px;
    border-bottom: 1px solid var(--popover-border);
    gap: 8px;
}

.ind-settings-search svg {
    width: 16px;
    height: 16px;
    color: var(--text-primary);
    flex-shrink: 0;
}

.ind-settings-search input {
    flex: 1;
    border: none;
    background: transparent;
    font-size: 14px;
    color: var(--text-primary);
    outline: none;
}

.ind-settings-search input::placeholder {
    color: var(--text-tertiary);
}

.ind-settings-list {
    flex: 1;
    overflow-y: auto;
    padding: 8px 0;
}

/* [2026-03-09] Thin scrollbar like symbol-list */
.ind-settings-list::-webkit-scrollbar {
    width: 3px;
    opacity: 0;
}

.ind-settings-left:hover .ind-settings-list::-webkit-scrollbar-thumb {
    background: #c8ccd8;
}

.ind-settings-list::-webkit-scrollbar-track {
    background: transparent;
}

.ind-settings-list::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 2px;
    transition: background 0.2s;
}

.ind-settings-list::-webkit-scrollbar-thumb:hover {
    background: #a0a4b0;
}

.ind-settings-category {
    margin-bottom: 4px;
}

.ind-settings-cat-header {
    display: flex;
    align-items: center;
    padding: 10px 12px;
    cursor: pointer;
    transition: background 0.2s;
}

.ind-settings-cat-header:hover {
    background: var(--bg-hover);
}

.ind-settings-cat-icon {
    width: 20px;
    height: 20px;
    margin-right: 10px;
    color: var(--text-primary);
}

.ind-settings-cat-header span {
    flex: 1;
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
}

.ind-settings-expand {
    width: 16px;
    height: 16px;
    fill: none;
    stroke: var(--text-secondary);
    stroke-width: 1.2;
    transition: transform 0.2s;
}

.ind-settings-category.collapsed .ind-settings-expand {
    transform: rotate(-90deg);
}

.ind-settings-category.collapsed .ind-settings-cat-items {
    display: none;
}

.ind-settings-cat-items {
    padding-left: 36px;
}

.ind-settings-item-row {
    display: flex;
    align-items: center;
    padding: 10px 12px;
    font-size: 13px;
    color: var(--text-primary);
    cursor: pointer;
    border-radius: 4px;
    margin: 2px 8px 2px 0;
    transition: background 0.2s, color 0.2s;
}

.ind-settings-item-row:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
}

.ind-settings-item-row.active {
    background: rgba(128, 128, 128, 0.2);
    color: var(--text-primary);
}

/* [2026-03-09] Item row icon */
.ind-settings-item-row .ind-item-icon {
    width: 18px;
    height: 18px;
    margin-right: 10px;
    color: var(--text-primary);
    flex-shrink: 0;
}

.ind-settings-item-row:hover .ind-item-icon,
.ind-settings-item-row.active .ind-item-icon {
    color: inherit;
}

/* Right Panel */
.ind-settings-right {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.ind-settings-panel {
    flex: 1;
    overflow-y: auto;
    padding: 0;
}

.ind-settings-panel-header {
    padding: 16px 20px;
    border-bottom: 1px solid var(--popover-border);
}

.ind-settings-panel-header h4 {
    margin: 0;
    font-size: 16px;
    font-weight: 600;
    color: var(--text-primary);
}

.ind-settings-panel-content {
    padding: 16px 20px;
}

.ind-settings-group {
    margin-bottom: 20px;
}

.ind-settings-label {
    display: block;
    font-size: 13px;
    font-weight: 500;
    color: var(--text-secondary);
    margin-bottom: 8px;
}

.ind-settings-checkbox {
    display: flex;
    align-items: center;
    margin-bottom: 8px;
}

.ind-settings-checkbox input[type="checkbox"] {
    width: 16px;
    height: 16px;
    margin-right: 10px;
    cursor: pointer;
    accent-color: var(--state-active);
}

.ind-settings-checkbox label {
    font-size: 14px;
    color: var(--text-primary);
    cursor: pointer;
}

.ind-settings-input,
.ind-settings-select {
    width: 100%;
    padding: 8px 12px;
    font-size: 14px;
    color: var(--text-primary);
    background: var(--bg-input);
    border: 1px solid var(--text-secondary);
    border-radius: 6px;
    outline: none;
    transition: border-color 0.2s;
}

.ind-settings-input:focus,
.ind-settings-select:focus {
    border-color: #666;
}

.ind-settings-color-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
}

.ind-settings-color-row span {
    font-size: 14px;
    color: var(--text-primary);
}

.ind-settings-color {
    width: 40px;
    height: 28px;
    border: 1px solid var(--text-secondary);
    border-radius: 4px;
    cursor: pointer;
    padding: 0;
}

.ind-settings-slider-row {
    display: flex;
    align-items: center;
    gap: 10px;
}

.ind-settings-slider {
    flex: 1;
    accent-color: var(--state-active);
}

.ind-settings-slider-value {
    font-size: 13px;
    color: var(--text-secondary);
    min-width: 40px;
    text-align: right;
}

.ind-settings-desc {
    font-size: 13px;
    color: var(--text-secondary);
    line-height: 1.5;
    margin: 0 0 8px 0;
}

.ind-settings-note {
    font-size: 13px;
    color: var(--text-tertiary);
    font-style: italic;
    margin: 0;
}

/* Footer */
.ind-settings-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 20px;
    border-top: 1px solid var(--popover-border);
}

.ind-settings-footer-right {
    display: flex;
    gap: 12px;
}

.ind-settings-btn-reset,
.ind-settings-btn-cancel,
.ind-settings-btn-save {
    padding: 8px 20px;
    font-size: 14px;
    font-weight: 500;
    border-radius: 6px;
    cursor: pointer;
    transition: background 0.2s, opacity 0.2s;
}

.ind-settings-btn-reset {
    background: transparent;
    border: 1px solid var(--text-secondary);
    color: var(--text-primary);
}

.ind-settings-btn-reset:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
}

.ind-settings-btn-cancel {
    background: transparent;
    border: 1px solid var(--text-secondary);
    color: var(--text-primary);
}

.ind-settings-btn-cancel:hover {
    background: var(--bg-hover);
}

.ind-settings-btn-save {
    background: #333;
    border: none;
    color: #fff;
}

.ind-settings-btn-save:hover {
    background: #444;
}

/* Light theme adjustments */
.light-theme .ind-settings-btn-save {
    background: #222;
}

.light-theme .ind-settings-btn-save:hover {
    background: #333;
}

/* ============================================
   Symbol Switching Loading State
   TradingView-style dimming effect during symbol change
   Uses opacity to avoid layout issues
   ============================================ */

/* ========== Chart Context Menu ========== */
.chart-context-menu {
    position: fixed;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 8px;
    box-shadow: var(--popover-shadow);
    padding: 4px 0;
    min-width: 260px;
    width: max-content;
    max-width: min(92vw, 520px);
    z-index: 10000;
    display: none;
    font-size: 13px;
    color: var(--text-primary);
}

.chart-context-menu.visible {
    display: block;
}

.chart-context-menu-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 16px;
    cursor: pointer;
    position: relative;
    transition: background 0.15s;
}

.chart-context-menu-item:hover {
    background: var(--bg-hover);
}

.chart-context-menu-item.has-submenu:hover > .chart-context-menu-submenu.visible {
    display: block;
}

.chart-context-menu-icon {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* [FIX] 2026-05-22 — Theme-aware icon colour
 * Reason: Hard-coded stroke:#ffffff made every icon invisible against
 *   the white --popover-bg in light theme (white-on-white). User reported
 *   "白色主题下 菜单图表看不清".
 * Change: stroke inherits from currentColor so it follows the item's
 *   color (which is var(--text-primary) — #000 in light, #fff in dark).
 *   fill stays 'none' for stroke-only icons; the few histogram/area
 *   icons that need a filled body already set `fill="currentColor"`
 *   inline so they pick up the same theme colour automatically.
 * Warning: do NOT restore a hard-coded stroke colour — it will break
 *   the opposite theme. The icon palette MUST follow theme via
 *   currentColor inheritance.
 */
.chart-context-menu-icon svg {
    width: 24px;
    height: 24px;
    stroke: currentColor;
    fill: none;
}

/* Icons declared with `fill="currentColor"` inline (Candlestick / Heikin
 * Ashi / Area body shapes) need fill to follow theme too, so override the
 * shared `fill: none` rule above when the inline attribute requests it.
 * fill is an inherited SVG property, so setting it on the <svg> root
 * propagates to all child shapes that don't override it themselves. */
.chart-context-menu-icon svg[fill="currentColor"] {
    fill: currentColor;
}

.chart-context-menu-label {
    flex: 0 1 auto;
    white-space: nowrap;
}

.chart-context-menu-arrow {
    font-size: 10px;
    color: var(--text-secondary);
    margin-left: auto;
    flex-shrink: 0;
}

.chart-context-menu-divider {
    height: 1px;
    background: var(--popover-border);
    margin: 4px 8px;
}

.chart-context-menu-submenu {
    position: absolute;
    left: 100%;
    top: 0;
    margin-left: -2px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 8px;
    box-shadow: var(--popover-shadow);
    padding: 4px 0;
    min-width: 220px;
    width: max-content;
    max-width: min(92vw, 520px);
    display: none;
    z-index: 10001;
    pointer-events: none;
}

.chart-context-menu-submenu.visible {
    display: block;
    pointer-events: auto;
}

@media (max-width: 768px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    .chart-context-menu {
        width: min(92vw, 340px);
        max-height: min(78vh, 560px);
        overflow-y: auto;
    }
    .chart-context-menu-submenu {
        position: static;
        width: 100%;
        margin-left: 0;
        margin-top: 4px;
        box-shadow: none;
        border-style: dashed;
    }
}

.symbol-switching {
    opacity: 0.3 !important;
    pointer-events: none !important;
    transition: opacity 0.15s ease-out;
}

.symbol-switching * {
    pointer-events: none !important;
}

/* ============================================
   Per-Slot Loading State
   Only dims the individual chart slot being loaded
   ============================================ */

.chart-slot.slot-loading {
    position: relative;
    pointer-events: none;
}

.chart-slot.slot-loading::after {
    content: '';
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.35);
    z-index: 10;
}

.chart-slot.slot-loading::before {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 24px;
    height: 24px;
    margin: -12px 0 0 -12px;
    border: 2px solid rgba(128, 128, 128, 0.2);
    border-top-color: rgba(128, 128, 128, 0.8);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
    z-index: 11;
}

/* ========== Paper Trading Module ========== */

.paper-trading-module { display: flex; flex-direction: column; overflow: hidden; min-height: 300px; }
.paper-trading-module.hidden { display: none !important; }

/* Header */
.pt-header { display: flex; align-items: center; padding: 5px 10px; gap: 6px; border-bottom: 1px solid var(--border-color); min-height: 48px; }
.pt-header-title { font-size: 13px; font-weight: 600; color: var(--text-primary); white-space: nowrap; }
.pt-header-bal { display: flex; align-items: baseline; gap: 4px; margin-left: auto; }
.pt-header-bal-val { font-size: 13px; font-family: var(--font-price); font-weight: 600; color: var(--text-primary); }
.pt-header-bal-unit { font-size: 11px; color: var(--text-secondary); }
.pt-header-pnl { font-size: 11px; font-family: var(--font-price); margin-left: 4px; }

/* Sidebar Tabs */
.pt-sidebar-tabs {
    display: flex; height: 36px; min-height: 36px;
    border-bottom: 1px solid var(--border-color);
    background: var(--bg-panel);
}
.pt-sidebar-tab {
    flex: 1; display: flex; align-items: center; justify-content: center; gap: 5px;
    background: none; border: none; color: var(--text-secondary);
    font-size: 14px; cursor: pointer; transition: color .15s;
}
.pt-sidebar-tab svg {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    flex-shrink: 0;
}
.pt-sidebar-tab.active { color: var(--text-primary); border-bottom: 2px solid var(--text-primary); }
.pt-sidebar-tab:hover { color: var(--text-primary); }

/* Sidebar Panes */
.pt-sidebar-pane { display: none; }
.pt-sidebar-pane.active { display: block; }

/* Content */
.pt-content { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 10px 12px; }
.pt-content::-webkit-scrollbar { width: 3px; }
.pt-content::-webkit-scrollbar-track { background: transparent; }
.pt-content::-webkit-scrollbar-thumb { background: transparent; border-radius: 2px; }
.pt-content:hover::-webkit-scrollbar-thumb { background: #c8ccd8; }
.pt-content::-webkit-scrollbar-thumb:hover { background: #a0a4b0; }

/* Wallet Panel */
.pt-wallet-panel { padding: 8px 0; }
.pt-wallet-block { margin-bottom: 16px; }
.pt-wallet-label { font-size: 12px; color: var(--text-secondary); margin-bottom: 4px; }
.pt-wallet-total { font-size: 20px; font-weight: 600; color: var(--text-primary); font-family: var(--font-price); }
.pt-wallet-unit { font-size: 13px; color: var(--text-secondary); font-weight: 400; }
.pt-wallet-avail { font-size: 12px; color: var(--text-secondary); margin-top: 2px; font-family: var(--font-price); }
.pt-settings-gear { margin-left: 2px; flex-shrink: 0; }
.pt-assets-table { width: 100%; }
.pt-assets-head,
.pt-assets-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.2fr);
    align-items: center;
    gap: 8px;
    padding: 5px 0;
}
.pt-assets-head {
    font-size: 12px;
    color: var(--text-secondary);
    border-bottom: 1px solid var(--border-color);
    padding-bottom: 6px;
    margin-bottom: 2px;
}
.pt-assets-row {
    font-size: 13px;
    border-bottom: 1px solid var(--border-color);
}
.pt-assets-row:last-child { border-bottom: none; }
.pt-assets-col-asset {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    font-weight: 400;
    color: var(--text-primary);
}
.pt-assets-asset-name {
    font-size: 14px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.pt-assets-col-amt {
    font-family: var(--font-price);
    font-weight: 400;
    color: var(--text-primary);
    text-align: right;
}
.pt-assets-col-val {
    font-family: var(--font-price);
    font-size: 12px;
    color: var(--text-primary);
    text-align: right;
    white-space: nowrap;
}
.tt-settings-popover__body .pt-settings-deposit {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    margin-top: 0;
    padding-top: 0;
    border-top: none;
}
.tt-settings-popover__body .pt-settings-deposit .pt-deposit-type-btn {
    flex: 1 1 auto;
    min-height: 32px;
}
.tt-settings-popover__body .pt-settings-deposit .pt-deposit-input { width: 100%; }
.tt-settings-popover__body .pt-settings-deposit #ptDepositBtn,
.tt-settings-popover__body .pt-settings-deposit #ptResetAccount {
    width: 100%;
    min-height: 32px;
}

/* Order Form */
.pt-order-form { display: flex; flex-direction: column; gap: 10px; }

/* Futures bar: margin + leverage pills */
.pt-futures-bar { display: flex; gap: 6px; }
.pt-pill { padding: 5px 16px; border: none; border-radius: 4px; background: var(--bg-input); color: var(--text-primary); font-size: 12px; font-weight: 500; cursor: pointer; transition: background 0.15s; white-space: nowrap; }
.pt-pill:hover { background: var(--bg-hover); }
.pt-pill-accent { color: var(--text-primary); }

/* Leverage */
.pt-lev-label { font-size: 13px; font-weight: 600; color: var(--text-primary); font-family: var(--font-price); padding: 5px 0; }
.pt-lev-row { display: flex; flex-direction: column; gap: 6px; }
.pt-lev-presets { display: flex; gap: 4px; }
.pt-lev-preset { flex: 1; padding: 5px 0; font-size: 12px; font-weight: 500; background: var(--bg-input); color: var(--text-secondary); border: 1px solid var(--border-color); border-radius: 4px; cursor: pointer; transition: all .15s; text-align: center; }
.pt-lev-preset:hover:not(.active) { color: var(--text-primary); }
.pt-lev-preset.active { background: var(--text-primary); color: var(--bg-primary); }
.pt-lev-preset.disabled { opacity: .3; cursor: not-allowed; pointer-events: none; }
.pt-lev-slider-row { display: flex; align-items: center; }
.pt-lev-slider-row input[type="range"] { width: 100%; height: 3px; -webkit-appearance: none; appearance: none; background: var(--border-color); border-radius: 2px; outline: none; }
.pt-lev-slider-row input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 14px; height: 14px; border-radius: 50%; background: var(--text-primary); cursor: pointer; }

/* Custom dropdown */
.pt-dropdown { position: relative; width: 100%; cursor: pointer; user-select: none; }
.pt-dropdown-selected { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; border: none; border-radius: 4px; background: var(--bg-input); color: var(--text-primary); font-size: 13px; }
.pt-dropdown-arrow { font-size: 10px; color: var(--text-secondary); margin-left: 8px; }
.pt-dropdown-menu { display: none; position: absolute; top: 100%; left: 0; right: 0; margin-top: 2px; border-radius: 4px; background: var(--popover-bg); border: 1px solid var(--popover-border); box-shadow: var(--popover-shadow); z-index: 100; overflow: hidden; }
.pt-dropdown.open .pt-dropdown-menu { display: block; }
.pt-dropdown-item { padding: 8px 12px; font-size: 13px; color: var(--text-primary); background: var(--popover-bg); transition: background 0.12s; }
.pt-dropdown-item:hover { background: var(--bg-hover); }
.pt-dropdown-item.active { background: var(--bg-hover); }

/* Field: label on top */
.pt-field { display: flex; flex-direction: column; gap: 4px; }
.pt-field > label { font-size: 12px; color: var(--text-secondary); }
.pt-field-inline { flex-direction: row; align-items: center; gap: 6px; }

/* Input box */
.pt-input-box { display: flex; align-items: center; border: none; border-radius: 4px; background: var(--bg-input); overflow: hidden; flex: 1; }
.pt-input-box input { flex: 1; padding: 8px 12px; border: none; background: transparent; color: var(--text-primary); font-size: 14px; font-family: var(--font-price); outline: none; min-width: 0; -moz-appearance: textfield; appearance: textfield; }
.pt-input-box input::-webkit-outer-spin-button,
.pt-input-box input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.pt-input-box input::placeholder { color: var(--text-secondary); }
.pt-input-unit { padding: 0 12px; font-size: 12px; color: var(--text-secondary); white-space: nowrap; }

/* Percentage bar */
.pt-pct-bar { display: flex; gap: 6px; }
.pt-pct-btn { flex: 1; padding: 5px 0; border: none; border-radius: 4px; background: var(--bg-input); color: var(--text-secondary); font-size: 12px; cursor: pointer; transition: all 0.15s; text-align: center; }
.pt-pct-btn:hover { color: var(--text-primary); background: var(--bg-hover); }
.pt-pct-btn.active { background: var(--bg-hover); color: var(--text-primary); }

/* Summary row */
.pt-summary { display: flex; flex-direction: column; gap: 4px; }
.pt-summary-row { display: flex; justify-content: space-between; font-size: 12px; }
.pt-summary-row span:first-child { color: var(--text-secondary); }
.pt-summary-row span:last-child { color: var(--text-primary); font-family: var(--font-price); font-weight: 500; }

/* Action buttons */
.pt-action-row { display: flex; gap: 8px; }
.pt-btn { flex: 1; padding: 10px 0; border: none; border-radius: 4px; font-size: 14px; font-weight: 600; cursor: pointer; transition: opacity 0.15s; }
.pt-btn:hover { opacity: 0.85; }
.pt-btn:active { opacity: 0.7; }
.pt-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.pt-btn-buy { background: var(--price-up); color: #fff; }
.pt-btn-sell { background: var(--price-down); color: #fff; }

/* Pill action / danger */
.pt-pill-action { background: var(--bg-input); color: var(--text-primary); }
.pt-pill-danger { width: 100%; text-align: center; color: var(--price-down); background: transparent; border: 1px solid var(--price-down); }
.pt-pill-danger:hover { background: var(--price-down); color: #fff; }

/* Positions table */
.pt-pos-table-wrap { overflow-x: auto; }
.pt-pos-table-wrap::-webkit-scrollbar { height: 3px; }
.pt-pos-table-wrap::-webkit-scrollbar-track { background: transparent; }
.pt-pos-table-wrap::-webkit-scrollbar-thumb { background: transparent; border-radius: 2px; }
.pt-pos-table-wrap:hover::-webkit-scrollbar-thumb { background: #c8ccd8; }
.pt-pos-table-wrap::-webkit-scrollbar-thumb:hover { background: #a0a4b0; }
.pt-pos-table { width: 100%; border-collapse: collapse; font-size: 14px; white-space: nowrap; }
.pt-pos-table thead th { position: sticky; top: 0; padding: 6px 8px; text-align: left; font-weight: 500; font-size: 13px; color: var(--text-secondary); background: var(--bg-primary); border-bottom: 1px solid var(--border-color); }
.pt-pos-table tbody td { padding: 6px 8px; color: var(--text-primary); border-bottom: 1px solid var(--border-color); vertical-align: middle; font-family: var(--font-price); }
.pt-pos-row:hover td { background: var(--bg-input); }
.pt-sym-cell {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}
.pt-sym-cell .pt-pos-sym { margin-right: 0; }
.pt-pos-sym { font-weight: 600; font-size: 14px; margin-right: 6px; }
.pt-pos-side { font-size: 12px; font-weight: 600; margin-right: 4px; }
.pt-pos-lev { font-size: 14px; color: var(--text-primary); }
.pt-pos-roe { font-size: 12px; }
.pt-pos-margin { font-size: 14px; }
.pt-pos-margin-mode { font-size: 11px; color: var(--text-secondary); font-weight: 400; }
.pt-pos-actions { white-space: nowrap; }
.pt-pos-act { display: inline-block; padding: 3px 10px; margin-right: 4px; font-size: 13px; color: var(--text-secondary); cursor: pointer; border-radius: 3px; transition: color .15s, background .15s; }
.pt-pos-act:hover { color: var(--text-primary); background: var(--bg-input); }
.pt-pos-act-close:hover { color: var(--price-down); }
.pt-pos-act-tpsl:hover { color: var(--accent-color); }

/* Close / TP-SL Modal */
.pt-modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,.45); z-index: 9999; align-items: center; justify-content: center; }
.pt-modal-overlay.open { display: flex; }
.pt-modal { background: var(--popover-bg); border: 1px solid var(--popover-border); border-radius: 8px; width: 380px; max-width: 90vw; box-shadow: var(--popover-shadow); }
.pt-modal-header { display: flex; align-items: center; gap: 8px; padding: 14px 16px 10px; border-bottom: 1px solid var(--popover-border); }
.pt-modal-title { font-size: 15px; font-weight: 600; color: var(--text-primary); }
.pt-modal-subtitle { font-size: 13px; color: var(--text-secondary); margin-left: auto; }
.pt-modal-close { margin-left: 8px; background: none; border: none; font-size: 20px; color: var(--text-secondary); cursor: pointer; line-height: 1; padding: 0 2px; }
.pt-modal-close:hover { color: var(--text-primary); }
.pt-modal-body { padding: 14px 16px; display: flex; flex-direction: column; gap: 12px; }
.pt-modal-type-tabs { display: flex; gap: 0; }
.pt-modal-type-btn { flex: 1; padding: 7px 0; font-size: 13px; font-weight: 500; background: var(--bg-input); color: var(--text-secondary); border: 1px solid var(--border-color); cursor: pointer; transition: all .15s; }
.pt-modal-type-btn:first-child { border-radius: 4px 0 0 4px; }
.pt-modal-type-btn:last-child { border-radius: 0 4px 4px 0; border-left: none; }
.pt-modal-type-btn.active { background: var(--text-primary); color: var(--bg-primary); }
.pt-modal-type-btn:hover:not(.active) { color: var(--text-primary); }
.pt-modal-field { display: flex; flex-direction: column; gap: 4px; }
.pt-modal-field label { font-size: 12px; color: var(--text-secondary); font-weight: 500; }
.pt-modal-pct-row { display: flex; gap: 6px; }
.pt-modal-pct { flex: 1; padding: 5px 0; font-size: 12px; font-weight: 500; background: var(--bg-input); color: var(--text-secondary); border: 1px solid var(--border-color); border-radius: 4px; cursor: pointer; transition: all .15s; }
.pt-modal-pct.active { background: var(--text-primary); color: var(--bg-primary); }
.pt-modal-pct:hover:not(.active) { color: var(--text-primary); }
.pt-modal-summary { font-size: 12px; color: var(--text-secondary); line-height: 1.6; }
.pt-modal-hint { font-size: 12px; color: var(--text-secondary); line-height: 1.6; }
.pt-modal-footer { display: flex; gap: 8px; padding: 10px 16px 14px; border-top: 1px solid var(--popover-border); }
.pt-modal-btn { flex: 1; padding: 8px 0; border: 1px solid transparent; border-radius: 4px; font-size: 13px; font-weight: 500; cursor: pointer; transition: opacity .15s, background .15s, border-color .15s; }
.pt-modal-btn:hover { opacity: .85; }
.pt-modal-btn-cancel { background: transparent; color: var(--text-primary); border-color: var(--text-secondary); }
.pt-modal-btn-cancel:hover { background: var(--bg-hover); border-color: var(--text-primary); opacity: 1; }
.pt-modal-btn-confirm { background: var(--text-primary); color: var(--bg-primary); }

/* Technical recommendation modal */
.ta-tech-modal-overlay {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 10020;
    pointer-events: none;
}
.ta-tech-modal-overlay.open { display: block; }
.ta-tech-modal {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(1380px, 98vw);
    max-height: min(96vh, 1120px);
    border: 1px solid var(--popover-border);
    border-radius: 12px;
    background: var(--popover-bg);
    box-shadow: var(--popover-shadow);
    display: flex;
    flex-direction: column;
    font-size: 14px;
    overflow: hidden;
    pointer-events: auto;
}
.ta-tech-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 16px 20px;
    border-bottom: 1px solid var(--popover-border);
    cursor: move;
    user-select: none;
}
.ta-tech-modal-title {
    margin: 0;
    font-size: 16px;
    color: var(--text-primary);
}
.ta-tech-modal-subtitle {
    display: block;
    margin: 0;
    font-size: 12px;
    color: var(--text-secondary);
}
.ta-tech-modal-close {
    width: 30px;
    height: 30px;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background .15s;
}
.ta-tech-modal-close:hover {
    background: var(--bg-hover);
}
.ta-tech-modal-body {
    padding: 16px 20px 20px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    overflow: auto;
}
.ta-tech-modal-body.is-loading .ta-tech-summary,
.ta-tech-modal-body.is-loading .ta-tech-indicator-picker,
.ta-tech-modal-body.is-loading .ta-tech-columns {
    opacity: 0.65;
}
.ta-tech-timeframes {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}
.ta-tech-tf-btn {
    border: 1px solid var(--border-color);
    background: transparent;
    color: var(--text-primary);
    border-radius: 4px;
    font-size: 14px;
    padding: 4px 10px;
    cursor: pointer;
}
.ta-tech-tf-btn.active {
    border-color: var(--text-primary);
    color: var(--text-primary);
    background: var(--bg-hover);
}
.ta-tech-summary {
    border: 1px solid var(--border-color);
    border-radius: 6px;
    padding: 10px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}
.ta-tech-summary-main {
    display: flex;
    align-items: baseline;
    gap: 8px;
}
.ta-tech-summary-right {
    margin-left: auto;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 6px;
    text-align: right;
}
.ta-tech-modal-subtitle--summary {
    text-align: right;
    white-space: nowrap;
}
.ta-tech-summary-label {
    font-size: 14px;
    font-weight: 600;
}
.ta-tech-summary-label.up { color: var(--price-up); }
.ta-tech-summary-label.down { color: var(--price-down); }
.ta-tech-summary-label.neutral { color: var(--text-primary); }
.ta-tech-summary-score {
    font-size: 14px;
    color: var(--text-primary);
    font-family: var(--font-price);
}
.ta-tech-summary-counts {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 6px;
    width: auto;
    flex-wrap: nowrap;
    white-space: nowrap;
}
.ta-tech-summary-counts .ta-tech-chip {
    display: inline-flex;
    align-items: baseline;
    justify-content: flex-end;
    gap: 4px;
    text-align: right;
}
.ta-tech-chip {
    border: 1px solid var(--border-color);
    border-radius: 999px;
    font-size: 12px;
    line-height: 1;
    color: var(--text-secondary);
    padding: 2px 8px;
}
.ta-tech-chip b {
    margin-left: 0;
    line-height: 1;
    color: var(--text-primary);
    font-family: var(--font-price);
}
.ta-tech-chip--buy b { color: var(--price-up); }
.ta-tech-chip--sell b { color: var(--price-down); }
.ta-tech-indicator-picker {
    border: 1px solid var(--border-color);
    border-radius: 6px;
    padding: 8px 10px 10px;
}
.ta-tech-picker-title {
    font-size: 12px;
    color: var(--text-secondary);
    margin-bottom: 6px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.ta-tech-picker-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 6px 10px;
}
.ta-tech-check {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 14px;
    color: var(--text-primary);
}
.ta-tech-check span {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ta-tech-check-input {
    accent-color: var(--text-primary);
}
.ta-tech-columns {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
}
.ta-tech-card {
    border: 1px solid var(--border-color);
    border-radius: 6px;
    min-height: 180px;
    display: flex;
    flex-direction: column;
}
.ta-tech-card-title {
    margin: 0;
    padding: 8px 10px;
    font-size: 12px;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border-bottom: 1px solid var(--border-color);
}
.ta-tech-list {
    padding: 4px;
}
.ta-tech-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: 8px;
    align-items: center;
    padding: 6px 8px;
    border-radius: 4px;
}
.ta-tech-row:hover {
    background: var(--bg-hover);
}
.ta-tech-row-name {
    font-size: 14px;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ta-tech-row-val {
    font-size: 12px;
    color: var(--text-secondary);
    font-family: var(--font-price);
}
.ta-tech-sig {
    font-size: 12px;
    border: 1px solid var(--border-color);
    border-radius: 999px;
    padding: 1px 7px;
}
.ta-tech-sig.up {
    color: var(--price-up);
    border-color: rgba(0, 200, 150, 0.45);
}
.ta-tech-sig.down {
    color: var(--price-down);
    border-color: rgba(255, 77, 106, 0.45);
}
.ta-tech-sig.neutral {
    color: var(--text-secondary);
}
.ta-tech-sig.unavailable {
    color: var(--text-secondary);
    border-color: var(--border-color);
}
.ta-tech-sig--loading {
    border-color: var(--border-color);
    background: transparent;
    padding: 1px 7px;
    min-width: 40px;
    min-height: 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
@keyframes ta-tech-load-slide {
    0% { transform: translateX(-100%); }
    100% { transform: translateX(100%); }
}
.ta-tech-load {
    display: inline-flex;
    align-items: center;
    min-height: 14px;
    min-width: 48px;
    vertical-align: middle;
}
.ta-tech-load__track {
    position: relative;
    display: block;
    width: 48px;
    height: 10px;
    border-radius: 4px;
    background: color-mix(in srgb, var(--text-secondary) 16%, transparent);
    border: 1px solid color-mix(in srgb, var(--border-color) 70%, var(--text-secondary));
    overflow: hidden;
}
.ta-tech-load__track--sig {
    width: 40px;
    height: 14px;
}
.ta-tech-load__bar {
    position: absolute;
    inset: 0;
    width: 50%;
    background: linear-gradient(
        90deg,
        transparent,
        color-mix(in srgb, var(--text-secondary) 55%, transparent),
        transparent
    );
    animation: ta-tech-load-slide 1.2s ease-in-out infinite;
}
.ta-tech-modal-body[data-request-state="error"] .ta-tech-summary,
.ta-tech-modal-body[data-request-state="error"] .ta-tech-columns {
    opacity: 1;
}
@media (max-width: 900px) {
    .ta-tech-columns {
        grid-template-columns: 1fr;
    }
}

/* Side colors */
.pt-side-buy { color: var(--price-up); }
.pt-side-sell { color: var(--price-down); }
.paper-trading-module .positive,
.bottom-panel .positive { color: var(--price-up); }
.paper-trading-module .negative,
.bottom-panel .negative { color: var(--price-down); }

/* Empty state */
.pt-empty { padding: 24px; text-align: center; color: var(--text-secondary); font-size: 13px; }


/* Deposit row */
.pt-deposit-row { display: flex; align-items: stretch; gap: 4px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border-color); }
.pt-deposit-type-btn { padding: 0 8px; font-size: 12px; font-weight: 600; background: var(--bg-input); color: var(--text-secondary); border: 1px solid var(--border-color); border-radius: 4px; cursor: pointer; transition: all .15s; white-space: nowrap; }
.pt-deposit-type-btn.active { background: var(--text-primary); color: var(--bg-primary); }
.pt-deposit-type-btn:hover:not(.active) { color: var(--text-primary); }
.pt-deposit-input { width: 180px; min-width: 0; flex: none; }
.pt-deposit-input input { height: 100%; box-sizing: border-box; }
.pt-reset-btn { padding: 0 8px; font-size: 11px; font-weight: 500; background: none; border: 1px solid var(--price-down); color: var(--price-down); border-radius: 4px; cursor: pointer; transition: all .15s; white-space: nowrap; }
.pt-reset-btn:hover { background: var(--price-down); color: #fff; }

/* Section label */
.pt-section-label { font-size: 12px; font-weight: 600; color: var(--text-primary); margin-bottom: 6px; }

/* ========== Global Notify Toast System ========== */

.notify-container {
    position: fixed;
    top: 48px;
    right: 16px;
    z-index: 10000;
    display: flex;
    flex-direction: column;
    gap: 6px;
    pointer-events: none;
    max-width: 360px;
}

.notify-toast {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    border-radius: 4px;
    font-size: 12px;
    color: #fff;
    pointer-events: auto;
    transform: translateX(20px);
    opacity: 0;
    transition: transform 0.25s ease, opacity 0.25s ease;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}

.notify-toast.notify-visible {
    transform: translateX(0);
    opacity: 1;
}

.notify-toast.notify-hiding {
    transform: translateX(20px);
    opacity: 0;
}

.notify-icon {
    flex-shrink: 0;
    display: flex;
    align-items: center;
}

.notify-msg {
    flex: 1;
    line-height: 1.4;
    word-break: break-word;
}

.notify-close {
    flex-shrink: 0;
    background: none;
    border: none;
    color: rgba(255, 255, 255, 0.7);
    font-size: 16px;
    cursor: pointer;
    padding: 0 0 0 6px;
    line-height: 1;
}

.notify-close:hover {
    color: #fff;
}

.notify-info    { background: var(--accent-color); }
.notify-success { background: var(--price-up); }
.notify-warning { background: #e6a23c; }
.notify-error   { background: var(--price-down); }

/* In-site user notification toasts (tt.user-notif.js) */
.tt-notif-toast {
    pointer-events: auto;
    cursor: pointer;
    position: relative;
    background: var(--tt-bg-1, #1e222d);
    color: var(--tt-fg-0, #ffffff);
    border-left: 3px solid var(--text-tertiary, #787b86);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
    padding: 10px 28px 10px 12px;
    border-radius: 4px;
    font-size: 14px;
    line-height: 1.5;
}
.tt-notif-toast--accent {
    border-left-color: var(--accent-color, #2962ff);
}
.tt-notif-toast--accent-fill {
    background: var(--accent-color, #2962ff);
    border-left-color: var(--accent-color, #2962ff);
    color: #fff;
}
.tt-notif-toast--accent-fill .tt-notif-toast__body {
    opacity: 0.9;
}
.tt-notif-toast--accent-fill .tt-notif-toast__close {
    color: rgba(255, 255, 255, 0.75);
    opacity: 1;
}
.tt-notif-toast--accent-fill .tt-notif-toast__close:hover {
    color: #fff;
}
.tt-notif-toast--test {
    border-left-color: var(--price-up, #26a69a);
}
.tt-notif-toast__title {
    font-weight: 600;
    margin-bottom: 2px;
}
.tt-notif-toast__body {
    font-size: 12px;
    opacity: 0.85;
}
.tt-notif-toast__close {
    position: absolute;
    top: 6px;
    right: 6px;
    border: 0;
    background: transparent;
    color: inherit;
    cursor: pointer;
    font-size: 16px;
    line-height: 1;
    padding: 2px 4px;
    opacity: 0.7;
}

/* ========== Global Confirm Dialog ========== */
.confirm-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,.45);
    z-index: 10001;
    align-items: center;
    justify-content: center;
}
.confirm-overlay.confirm-open { display: flex; }

.confirm-dialog {
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 8px;
    width: 360px;
    max-width: 90vw;
    box-shadow: var(--popover-shadow);
    animation: confirmFadeIn .15s ease;
}
@keyframes confirmFadeIn { from { opacity: 0; transform: scale(.96); } to { opacity: 1; transform: scale(1); } }

/* ========== Login Prompt Modal (TV-style) ========== */
.tt-login-prompt-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, .52);
    z-index: 10002;
    align-items: center;
    justify-content: center;
    padding: 16px;
}
.tt-login-prompt-overlay.tt-login-prompt-open { display: flex; }

.tt-login-prompt-dialog {
    position: relative;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 12px;
    width: 400px;
    max-width: 92vw;
    box-shadow: var(--popover-shadow);
    padding: 28px 24px 22px;
    text-align: center;
    animation: confirmFadeIn .18s ease;
}

.tt-login-prompt-close {
    position: absolute;
    top: 10px;
    right: 12px;
    background: none;
    border: none;
    font-size: 22px;
    line-height: 1;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 2px 6px;
}
.tt-login-prompt-close:hover { color: var(--text-primary); }

.tt-login-prompt-illus {
    display: flex;
    justify-content: center;
    margin: 0 auto 14px;
    color: var(--accent-color);
}
.tt-login-prompt-illus svg {
    width: 120px;
    height: 96px;
}

.tt-login-prompt-title {
    margin: 0 0 8px;
    font-size: 18px;
    font-weight: 600;
    color: var(--text-primary);
    line-height: 1.35;
}

.tt-login-prompt-message {
    margin: 0 0 20px;
    font-size: 14px;
    line-height: 1.55;
    color: var(--text-secondary);
}

.tt-login-prompt-actions {
    display: flex;
    flex-direction: column;
    gap: 10px;
    align-items: stretch;
}

.tt-login-prompt-btn {
    display: block;
    padding: 10px 16px;
    border-radius: 8px;
    font-size: 14px;
    font-weight: 600;
    text-decoration: none;
    text-align: center;
    transition: opacity .15s, background .15s, border-color .15s;
}
.tt-login-prompt-btn:hover { opacity: .9; text-decoration: none; }

.tt-login-prompt-btn-primary {
    background: var(--accent-color);
    color: #fff;
    border: 1px solid transparent;
}

.tt-login-prompt-btn-secondary {
    background: transparent;
    color: var(--text-primary);
    border: 1px solid var(--text-secondary);
}
.tt-login-prompt-btn-secondary:hover {
    border-color: var(--text-primary);
    opacity: 1;
}

.confirm-header {
    display: flex;
    align-items: center;
    padding: 14px 16px 10px;
    border-bottom: 1px solid var(--popover-border);
}
.confirm-title { font-size: 15px; font-weight: 600; color: var(--text-primary); flex: 1; }
.confirm-close { background: none; border: none; font-size: 20px; color: var(--text-secondary); cursor: pointer; line-height: 1; padding: 0 2px; }
.confirm-close:hover { color: var(--text-primary); }

.confirm-body {
    padding: 18px 16px;
    font-size: 13px;
    color: var(--text-primary);
    line-height: 1.6;
    text-align: center;
}

.confirm-footer {
    display: flex;
    gap: 8px;
    padding: 10px 16px 14px;
    border-top: 1px solid var(--popover-border);
}
/* [FIX] 2026-05-20 v20260520.3 — Confirm dialog Cancel button visibility.
   Bug: in dark theme --bg-input collapses to #000000 — the exact same
   colour as --bg-primary used by .confirm-dialog itself, AND .confirm-btn
   had border:none. Result: the Cancel button was 100% invisible against
   the dialog body — users only saw the floating "Cancel" text with no
   button outline, breaking the visual pairing with the coloured OK
   button on its right.
   Root cause: relying on --bg-input as a "subtle surface" colour when
   the theme intentionally maps it to pure black for input fields
   (which sit on bg-secondary panels and so look fine there, but on the
   confirm dialog they sit on the same bg-primary as the panel itself).
   Fix: give the Cancel button an explicit 1px outline border AND swap
   its fill to --bg-quaternary, which is one notch lighter than the
   dialog background in both themes (dark: #1c1f2a vs #000000;
   light: #e0e3eb vs #ffffff). Outline-style is the industry pattern
   for "secondary action" in confirm dialogs (Material 3, Apple HIG,
   Bootstrap btn-outline-secondary, shadcn/ui Button variant=outline).
   [UPDATE] 2026-05-20 r10 — dialog body now sits on pure --popover-bg
   (Bloomberg/TradingView Pro pattern: pure-black panel + outer shadow
   for elevation, no grey fill). Swap the Cancel surface to a fully
   transparent outline-button: the 1px --text-secondary border gives
   the secondary-action affordance, and a transparent fill matches the
   dialog background in both themes (white on light, black on dark).
   This is the same pattern Bloomberg / TradingView / shadcn use for
   neutral-action buttons inside a deep dark surface. */
.confirm-btn {
    flex: 1;
    padding: 8px 0;
    border: 1px solid transparent;
    border-radius: 4px;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    transition: opacity .15s, background .15s, border-color .15s;
}
.confirm-btn:hover { opacity: .85; }
.confirm-btn-cancel {
    background: transparent;
    color: var(--text-primary);
    border-color: var(--text-secondary);
}
.confirm-btn-cancel:hover {
    background: var(--bg-hover);
    border-color: var(--text-primary);
    opacity: 1;
}
.confirm-btn-ok { color: #fff; }

/* ── InputDialog (non-modal) ── */
.input-dialog {
    display: none;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 10001;
    width: 300px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 8px;
    box-shadow: var(--popover-shadow);
    animation: confirmFadeIn .15s ease;
}
.input-dialog-open { display: block; }

.input-dialog-header {
    display: flex;
    align-items: center;
    padding: 10px 12px 8px;
    border-bottom: 1px solid var(--popover-border);
    cursor: move;
    user-select: none;
}
.input-dialog-title { font-size: 13px; font-weight: 600; color: var(--text-primary); flex: 1; }
.input-dialog-close { background: none; border: none; font-size: 18px; color: var(--text-secondary); cursor: pointer; line-height: 1; padding: 0 2px; }
.input-dialog-close:hover { color: var(--text-primary); }

.input-dialog-body {
    padding: 12px;
}
.input-dialog-label {
    display: block;
    margin-bottom: 6px;
    font-size: 12px;
    color: var(--text-secondary);
    text-align: left;
}
.input-dialog-input {
    width: 100%;
    box-sizing: border-box;
    padding: 7px 10px;
    border: 1px solid var(--text-secondary);
    border-radius: 4px;
    background: var(--bg-input);
    color: var(--text-primary);
    font-size: 13px;
    outline: none;
    transition: border-color .15s;
}
.input-dialog-input:focus {
    border-color: var(--text-primary);
}
.input-dialog-input-error {
    border-color: var(--price-down) !important;
}
.input-dialog-error {
    display: none;
    margin-top: 6px;
    font-size: 11px;
    color: var(--price-down);
    text-align: left;
}

.input-dialog-footer {
    display: flex;
    gap: 8px;
    padding: 8px 12px 12px;
}
.input-dialog-btn {
    flex: 1;
    padding: 6px 0;
    border: 1px solid var(--text-secondary);
    border-radius: 4px;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    background: transparent;
    color: var(--text-primary);
    transition: opacity .15s, background .15s, border-color .15s;
}
.input-dialog-btn:hover {
    background: var(--bg-hover);
    border-color: var(--text-primary);
    opacity: 1;
}

/* ========== YeScript Panel ========== */
.yes-panel {
    display: flex;
    flex-direction: column;
    flex: 1;
    overflow: hidden;
    height: 100%;
}
.yes-toolbar {
    display: flex;
    align-items: center;
    padding: 4px 6px 4px 0;
    flex-shrink: 0;
    min-height: 36px;
    gap: 0;
}
.yes-toolbar-left {
    display: flex;
    align-items: center;
    flex: 1;
    min-width: 0;
}
.yes-toolbar-right {
    display: flex;
    align-items: center;
    gap: 2px;
    flex-shrink: 0;
}
/* [ADD] 2026-05-29 — YeScript UI Redesign §4.1 — type badge.
   Reflects active script tab's type. Visible only when a script is loaded
   (tt.yescript.js toggles the [hidden] attribute). Color contract matches
   spec §7.1 — uses --tt-up / accent / library purple for visual parity
   with sc-type-chip in the Scripts Library. */
.yes-type-badge {
    display: inline-flex;
    align-items: center;
    margin-left: 6px;
    padding: 2px 7px;
    border-radius: 10px;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .3px;
    flex-shrink: 0;
}
.yes-type-badge[hidden] { display: none; }
.yes-type-badge--strategy {
    background: rgba(38, 166, 154, .15);
    color: var(--tt-up, #26a69a);
    border: 1px solid rgba(38, 166, 154, .25);
}
.yes-type-badge--indicator {
    background: rgba(41, 98, 255, .15);
    color: #6299ff;
    border: 1px solid rgba(41, 98, 255, .25);
}
.yes-type-badge--library {
    background: rgba(171, 116, 248, .15);
    color: #ab74f8;
    border: 1px solid rgba(171, 116, 248, .25);
}
/* Mobile: hide type badge to save horizontal space on the toolbar */
@media (max-width: 768px) {
    .yes-type-badge { display: none !important; }
}
/* [FIX] 2026-05-22 — switched from --border-color to --toolbar-sep so
 * YeScript editor's inline toolbar separator (between save / run / etc.
 * button groups) is actually visible in both themes. Same rationale as
 * the chart .toolbar-divider and .toolbar-divider-v fixes. */
.yes-toolbar-sep {
    width: 1px;
    height: 16px;
    margin: 0 4px;
    background: var(--toolbar-sep);
}
.yes-icon-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border: none;
    border-radius: 4px;
    background: none;
    color: var(--text-primary);
    cursor: pointer;
    transition: color .12s, background .12s;
}
.yes-icon-btn:hover {
    background: var(--bg-input);
}
.yes-icon-btn.yes-btn-labeled {
    width: auto;
    padding: 0 8px;
    gap: 4px;
    font-size: 14px;
}
/* [2026-05-22] Inline spinner used by window.TT.setBtnBusy() — appears
   inside any button while a long-running async task (save / run / network)
   is in flight. Replaces the leading SVG icon. */
.yes-btn-spinner,
.tt-btn-spinner {
    display: inline-block;
    width: 12px;
    height: 12px;
    border: 1.5px solid rgba(128, 128, 128, 0.25);
    border-top-color: currentColor;
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
    flex-shrink: 0;
    vertical-align: middle;
}
button[aria-busy="true"] {
    cursor: progress;
    opacity: 0.75;
}
.yes-save-group { position: relative; display: flex; align-items: center; gap: 0; }
.yes-save-arrow { width: 18px !important; padding: 0 !important; border-radius: 0 4px 4px 0 !important; }
.yes-save-group > #yesSaveBtn { border-radius: 4px 0 0 4px !important; }
.yes-save-dropdown {
    display: none; position: absolute; top: 100%; right: 0; margin-top: 2px;
    background: var(--popover-bg); border: 1px solid var(--popover-border);
    border-radius: 4px; box-shadow: var(--popover-shadow); z-index: 100; min-width: 120px;
}
.yes-save-dropdown.open { display: block; }
.yes-save-dropdown-item {
    display: block; width: 100%; padding: 6px 12px; border: none; background: none;
    color: var(--text-primary); font-size: 12px; text-align: left; cursor: pointer; white-space: nowrap;
}
.yes-save-dropdown-item:hover { background: var(--bg-hover); }
/* [FIX] 2026-05-29 — YeScript UI Redesign §4.1 — ▶ Run repainted green to
 * match the v5 mockup. Green is the industry-standard "execute" colour
 * (matches TradingView Pine's "Add to chart", VSCode's "Run", Jupyter's
 * "Run Cell"). The semantic shift also tightens the visual hierarchy
 * — Run is the only CTA-coloured control on the toolbar so the eye
 * lands on it directly.
 * Warning: keep this on .yes-btn-run only — the .yes-icon-btn base
 * remains neutral so all other buttons stay ghost-style. */
.yes-icon-btn.yes-btn-run {
    border-radius: 6px;
    background: var(--tt-up, #26a69a);
    color: #ffffff;
}
.yes-icon-btn.yes-btn-run:hover {
    background: #1f8e85;
    color: #ffffff;
}
.yes-icon-btn.yes-btn-run svg { color: #ffffff; }

/* [CHG] 2026-05-29 v20260529.17 — .yes-toolbar-legacy-hidden now only
 * hides the Templates anchor (#yesTemplateBtn / .yes-template-wrap).
 * Reset (#yesClearRunBtn) and Layout (#yesLayoutBtn) have been promoted
 * back to visible first-class toolbar buttons per 虎哥 directive
 * 2026-05-29 16:48. The Templates wrap stays mounted-but-hidden
 * because the new topbar #yescriptTemplatesArrow uses
 * #yesTemplateMenu as the menu DOM (which is a child of the wrap) —
 * the wrap is just an opaque shell now.
 * Warning: changing this from `display:none` to e.g. `visibility:hidden`
 * would leave the toolbar with phantom width. Keep it as `display:none`. */
.yes-toolbar-legacy-hidden {
    display: none !important;
}

/* [REMOVED] 2026-05-29 v20260529.17 — .yes-more-* CSS (folded "···" menu)
 * deleted with the corresponding HTML/JS removal. The new visible
 * Reset button uses .yes-icon-btn directly.
 * [CHG] 2026-05-30 v20260530.9 — Layout button removed entirely; the
 *   pre-existing .yes-layout-wrap / .yes-layout-menu styles further down
 *   were deleted along with it. */
.yes-script-tabs {
    display: flex; align-items: center; gap: 0;
    min-width: 0; overflow: hidden;
}
.yes-stab {
    display: flex; align-items: center; gap: 3px;
    padding: 0 8px; height: 26px; margin: 3px 0;
    background: transparent; border: none; border-radius: 4px;
    color: var(--text-secondary); font-size: 14px;
    cursor: pointer; white-space: nowrap;
    flex: 1 1 140px; min-width: 60px; max-width: 160px;
    overflow: hidden;
    transition: color .15s, background .15s;
}
.yes-stab:hover { color: var(--text-primary); background: var(--bg-input); }
.yes-stab.active { color: var(--text-primary); background: var(--bg-primary); }
/* [ADD] 2026-05-29 — YeScript UI Redesign §4.1 — per-tab kind dot.
 * Strategy=green / indicator=blue / library=purple, matching the
 * .yes-type-badge palette in the editor header so both visual cues
 * agree on the script's type at a glance. Rendered by
 * tt.yescript.js#renderScriptTabs() only after a successful compile
 * (when _lastChunk.kind is populated); pre-compile tabs render no dot. */
.yes-stab-dot { width: 5px; height: 5px; border-radius: 50%; flex-shrink: 0; }
.yes-stab-dot--strategy  { background: #26a69a; }
.yes-stab-dot--indicator { background: #2962ff; }
.yes-stab-dot--library   { background: #ab74f8; }
.yes-stab-name { overflow: hidden; text-overflow: ellipsis; flex: 1; min-width: 0; }
.yes-stab-close {
    display: flex; align-items: center; justify-content: center;
    width: 16px; height: 16px; border-radius: 3px; flex-shrink: 0;
    opacity: 0; font-size: 14px; line-height: 1;
    transition: opacity .15s, background .15s;
}
.yes-stab:hover .yes-stab-close, .yes-stab.active .yes-stab-close { opacity: .6; }
.yes-stab-close:hover { opacity: 1 !important; background: var(--border-color); }
.yes-stab-add {
    height: 26px; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center; gap: 2px;
    padding: 0 6px;
    margin: 3px 0 3px 2px;
    background: none; border: 1px solid transparent; border-radius: 4px;
    color: var(--text-primary);
    cursor: pointer; transition: color .15s, background .15s, border-color .15s;
}
.yes-stab-add:hover { background: var(--bg-input); border-color: var(--border-color); }
.yes-stab-add.disabled { opacity: .3; pointer-events: none; }
.yes-stab-add-arrow { opacity: .5; margin-left: 1px; }
.yes-stab-add:hover .yes-stab-add-arrow { opacity: .9; }
.yes-stab-add-wrap { position: relative; flex-shrink: 0; }
.yes-stab-new-menu {
    display: none;
    position: absolute;
    top: calc(100% + 2px);
    left: 0;
    min-width: 170px;
    padding: 6px 0;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 6px;
    box-shadow: var(--popover-shadow);
    z-index: 200;
    overflow: hidden;
}
.yes-stab-new-menu-open { display: block; }
.yes-stab-new-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 16px;
    font-size: 13px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background .12s;
}
.yes-stab-new-item svg {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    opacity: .7;
}
.yes-stab-new-item:hover { background: var(--bg-hover); }
.yes-stab-new-item:hover svg { opacity: 1; }
.yes-body {
    display: flex;
    flex: 1;
    min-height: 0;
    min-width: 0;
    overflow: hidden;
}
/* [CHG] 2026-05-30 v20260530.9 — dock direction collapsed to "bottom"
 * only (虎哥 2026-05-30 13:31). Old multi-direction rules removed:
 *   .yes-body.yes-dock-top  { flex-direction: column-reverse; }
 *   .yes-body.yes-dock-right, .yes-body.yes-dock-left { flex-direction: row; }
 *   .yes-body.yes-dock-left { flex-direction: row-reverse; }
 * Console is now always: editor on top, resizer in middle, console at bottom,
 * which is what `flex-direction: column` natively provides. */
.yes-body.yes-dock-bottom { flex-direction: column; }
.yes-editor-wrap {
    flex: 1;
    min-height: 0;
    min-width: 0;
    overflow: hidden;
}
#yesEditorContainer {
    width: 100%;
    height: 100%;
}
#yesEditorContainer .scrollbar .slider {
    background: transparent !important;
    border-radius: 2px;
    transition: background 0.2s;
}
#yesEditorContainer:hover .scrollbar .slider {
    background: #c8ccd8 !important;
}
#yesEditorContainer .scrollbar .slider:hover {
    background: #a0a4b0 !important;
}
/* [REMOVED] 2026-05-17 — .yes-lang-wrap / .yes-lang-menu / .yes-lang-item.
   Yescript language switcher was retired together with the multi-locale DSL. */
.yes-template-wrap {
    position: relative;
}
.yes-template-menu {
    display: none;
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    min-width: 220px;
    max-height: calc(100vh - 120px);
    overflow-y: auto;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 6px;
    box-shadow: var(--popover-shadow);
    z-index: 100;
    scrollbar-width: thin;
    scrollbar-color: #555 transparent;
    padding-bottom: 8px;
}
.yes-template-menu::-webkit-scrollbar { width: 3px; }
.yes-template-menu::-webkit-scrollbar-track { background: transparent; }
.yes-template-menu::-webkit-scrollbar-thumb { background: transparent; border-radius: 2px; transition: background .2s; }
.yes-template-menu:hover::-webkit-scrollbar-thumb { background: #c8ccd8; }
.yes-template-menu::-webkit-scrollbar-thumb:hover { background: #a0a4b0; }
.yes-template-menu-open { display: block; }
.yes-tmpl-group-title {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 14px 4px;
    font-size: 11px;
    font-weight: 600;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: .5px;
    user-select: none;
    border-top: 1px solid var(--border-color);
}
.yes-tmpl-group-title:first-child { border-top: none; }
.yes-tmpl-group-title svg {
    width: 12px;
    height: 12px;
    flex-shrink: 0;
    opacity: .7;
}
.yes-tmpl-item {
    padding: 6px 14px 6px 22px;
    font-size: 13px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background .12s;
}
.yes-tmpl-item:hover {
    background: var(--bg-hover);
}
/* [CHG] 2026-05-24 r33 — colour switches to --resizer-color; per-direction
 * size 2 → 5 to match the other 3 chart split bars (uniform grab target). */
.yes-console-resizer {
    display: none;
    background: var(--resizer-color);
    flex-shrink: 0;
    transition: background .15s;
}
.yes-console-resizer:hover,
.yes-console-resizer.dragging {
    background: var(--text-secondary);
}
.yes-console-resizer.yes-resizer-visible {
    display: block;
}
/* [CHG] 2026-05-30 v20260530.9 — dock-top / dock-right / dock-left
 * variants removed (虎哥 2026-05-30 13:31). The resizer is always a
 * horizontal bar (row-resize) and the wrap is always 150px tall. */
.yes-dock-bottom .yes-console-resizer { height: 5px; width: auto; cursor: row-resize; }
.yes-console-wrap {
    display: none;
    flex-direction: column;
    flex-shrink: 0;
    overflow: hidden;
}
.yes-dock-bottom .yes-console-wrap { height: 150px; width: auto; }
.yes-console-wrap.yes-console-open {
    display: flex;
}
.yes-console-header {
    display: flex;
    align-items: center;
    padding: 3px 10px;
    flex-shrink: 0;
    background: var(--bg-secondary);
}
.yes-console-title {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .5px;
    color: var(--text-primary);
}
/* [REMOVED] 2026-05-30 v20260530.9 — Layout button + dock-switcher CSS
 * deleted entirely (虎哥 2026-05-30 13:31). Removed 39 lines:
 *   .yes-layout-wrap / .yes-layout-disabled
 *   .yes-layout-menu / .yes-layout-menu-open
 *   .yes-layout-item / .yes-layout-item:hover / .yes-layout-item.active
 *   .yes-layout-item.active svg
 *   .yes-dock-overlay  (drag-drop zone container)
 *   .yes-dock-hi       (drag-drop zone highlight ribbon)
 * Console is now permanently bottom-docked; none of these are reachable. */
.yes-console-actions {
    display: flex;
    align-items: center;
    gap: 2px;
    margin-left: auto;
}
.yes-console-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border: none;
    border-radius: 3px;
    background: none;
    color: var(--text-primary);
    cursor: pointer;
    transition: color .12s, background .12s;
}
.yes-console-btn:hover {
    background: var(--bg-input);
}

/* [ADD] 2026-05-29 v20260529.18 — Log panel collapse/expand toggle.
 * The toggle button carries data-state="expanded|collapsed" and holds
 * two stacked SVG glyphs (▼ down / ▲ up); CSS shows exactly one based
 * on the current state. This is more accessible than swapping innerHTML
 * because the SR text and aria-expanded can update independently of
 * which visual is shown. */
.yes-console-toggle[data-state="expanded"] .yes-console-toggle__icon-up { display: none; }
.yes-console-toggle[data-state="expanded"] .yes-console-toggle__icon-down { display: inline-block; }
.yes-console-toggle[data-state="collapsed"] .yes-console-toggle__icon-up { display: inline-block; }
.yes-console-toggle[data-state="collapsed"] .yes-console-toggle__icon-down { display: none; }

/* When the wrap is collapsed:
 *   - keep .yes-console-header visible (it owns the toggle button — the
 *     user must always be able to click it back open)
 *   - hide the actual log body (.yes-console)
 *   - hide the filter bar (no logs visible, no filter to apply)
 *   - shrink wrap to only the header height
 *   - hide the resizer (nothing to resize)
 * [CHG] 2026-05-30 v20260530.9 — dock-top / dock-left / dock-right
 *   collapse variants removed; console is always bottom-docked. */
.yes-console-wrap.yes-console-collapsed .yes-console,
.yes-console-wrap.yes-console-collapsed .yes-filter-bar {
    display: none !important;
}
.yes-dock-bottom .yes-console-wrap.yes-console-collapsed {
    height: auto !important;
}
/* Resizer is wrap's preceding sibling in dock-bottom (it lives ABOVE the
 * wrap so the drag bar splits editor / console). We can't reach it with
 * a pure-CSS preceding-sibling selector, so JS adds .yes-resizer-hidden
 * in wireLogCollapseToggle when collapsing. */
.yes-console-resizer.yes-resizer-hidden {
    display: none !important;
}
/* [FIX] 2026-05-17 — Yescript Inputs panel (input.* descriptors).
   Renders user-tunable script parameters (length, threshold, ...) above the
   console. Re-runs on commit. Sits in the chart pane scope, so chart-page
   color/typography rules apply (not the home-page strict tt- palette). */
.yes-inputs-panel {
    flex: 0 0 auto;
    border-top: 1px solid var(--border-soft, #2b2e3a);
    background: var(--bg-soft, rgba(255,255,255,0.02));
    padding: 6px 10px;
    display: none; /* opened only when descriptors exist; JS toggles to flex */
    flex-wrap: wrap;
    gap: 8px 14px;
    align-items: center;
    font-size: 12px;
    color: var(--text-primary);
}
/* [REMOVED] 2026-05-30 v20260530.9 — left/right dock variant of inputs
 * panel removed (console is always bottom-docked, no horizontal layout). */
.yes-input-row {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.yes-input-row label {
    color: var(--text-secondary, #9da0aa);
    margin: 0;
}
.yes-input-row input,
.yes-input-row select {
    background: var(--bg-input, rgba(255,255,255,0.04));
    border: 1px solid var(--border-soft, #2b2e3a);
    color: var(--text-primary);
    padding: 2px 6px;
    border-radius: 3px;
    font-size: 12px;
    min-width: 80px;
}
.yes-input-row input[type="checkbox"] {
    min-width: auto;
}
.yes-input-row input[type="color"] {
    padding: 0;
    width: 28px;
    min-width: 28px;
    height: 22px;
    cursor: pointer;
}
.yes-inputs-rerun {
    background: var(--accent, #2962ff);
    color: #fff;
    border: none;
    padding: 3px 10px;
    border-radius: 3px;
    cursor: pointer;
    font-size: 12px;
}
.yes-inputs-rerun:hover {
    filter: brightness(1.1);
}
.yes-console {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 6px 10px;
    font-family: "Consolas", "SF Mono", monospace;
    font-size: 12px;
    line-height: 1.5;
    scrollbar-width: thin;
    scrollbar-color: #555 transparent;
}
.yes-console::-webkit-scrollbar { width: 3px; }
.yes-console::-webkit-scrollbar-track { background: transparent; }
.yes-console::-webkit-scrollbar-thumb { background: transparent; border-radius: 2px; transition: background 0.2s; }
.yes-console:hover::-webkit-scrollbar-thumb { background: #c8ccd8; }
.yes-console::-webkit-scrollbar-thumb:hover { background: #a0a4b0; }
.yes-console-line {
    white-space: pre-wrap;
    word-break: break-all;
}
.yes-info { color: var(--text-primary); }
.yes-success { color: var(--price-up); }
.yes-system { color: var(--text-primary); }
.yes-error { color: #f44336; }
.yes-warn { color: #ffc107; }
.yes-filter-bar {
    display: flex;
    align-items: center;
    gap: 2px;
    margin-left: 8px;
}
.yes-filter-btn {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    padding: 1px 6px;
    border: none;
    border-radius: 3px;
    background: none;
    color: var(--text-secondary);
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    transition: color .12s, background .12s;
}
.yes-filter-btn:hover {
    color: var(--text-primary);
    background: var(--bg-input);
}
.yes-filter-btn.active {
    color: var(--text-primary);
    background: var(--bg-input);
}
.yes-filter-warn.active,
.yes-filter-warn:hover { color: #ffc107; }
.yes-filter-error.active,
.yes-filter-error:hover { color: #f44336; }
.yes-filter-count {
    font-size: 9px;
    opacity: .6;
}
.yes-filter-count:empty { display: none; }

/* ========== YeScript Output Context Menu ========== */
.yes-ctx-menu {
    display: none;
    position: fixed;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 6px;
    box-shadow: var(--popover-shadow);
    padding: 4px 0;
    min-width: 160px;
    z-index: 10000;
    font-size: 13px;
    color: var(--text-primary);
    /* [FIX] 2026-05-21 — user-select:none so the user's existing selection
       inside #yesConsole is NOT cleared when their mousedown lands on a
       ctx-menu item. Without this rule the browser treats the mousedown
       as starting a new selection in the menu, instantly wiping the
       console selection; the Copy action then receives an empty
       getSelection().toString() and silently copies the whole console
       (looks like "Copy didn't work" to the user). Capturing the
       selection at contextmenu-open time also helps, but the no-select
       rule is the root-cause line so mousedown-and-drag inside the menu
       behaves like a click rather than a marquee. */
    -webkit-user-select: none;
    user-select: none;
}
.yes-ctx-menu.visible { display: block; }
.yes-ctx-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 14px;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.12s;
}
.yes-ctx-item:hover { background: var(--bg-hover); }
.yes-ctx-shortcut {
    margin-left: 24px;
    font-size: 11px;
    color: var(--text-secondary);
    opacity: 0.6;
}
.yes-ctx-divider {
    height: 1px;
    background: var(--border-color);
    margin: 4px 8px;
}

/* ========== YeScript Top-bar Button & Dropdown ========== */
.yes-topbar-wrap {
    position: relative;
    display: flex;
    align-items: center;
    height: 100%;
}
.yes-topbar-caret {
    width: 10px;
    height: 10px;
    margin-left: 2px;
    margin-right: 0;
    flex-shrink: 0;
    opacity: .6;
    transition: transform .15s;
}
.yes-topbar-wrap.open .yes-topbar-caret {
    transform: rotate(180deg);
}
.yes-topbar-menu {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    min-width: 180px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 6px;
    box-shadow: var(--popover-shadow);
    z-index: 200;
    padding: 4px 0;
    overflow: hidden;
}
.yes-topbar-wrap.open .yes-topbar-menu {
    display: block;
}
.yes-topbar-menu-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 14px;
    font-size: 13px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background .12s;
    white-space: nowrap;
}
.yes-topbar-menu-item:hover {
    background: var(--bg-hover);
}
.yes-topbar-menu-item svg {
    flex-shrink: 0;
    opacity: .7;
}
.yes-topbar-menu-sep {
    height: 1px;
    margin: 4px 10px;
    background: var(--border-color);
}
.yes-topbar-menu-submenu-wrap {
    position: relative;
}
.yes-topbar-has-sub {
    justify-content: flex-start;
}
.yes-sub-arrow {
    margin-left: auto;
    opacity: .5;
    flex-shrink: 0;
}
.yes-topbar-submenu {
    display: none;
    position: absolute;
    left: 100%;
    top: -4px;
    min-width: 160px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 6px;
    box-shadow: var(--popover-shadow);
    padding: 4px 0;
    z-index: 210;
}
.yes-topbar-menu-submenu-wrap:hover > .yes-topbar-submenu {
    display: block;
}

/* ── Layout Manager Dropdown ───────────────────────────────────── */
.layout-manager-wrap { position: relative; display: inline-flex; }
/* [CHG] 2026-05-20 v20260520.9 — Mobile: top-bar Layouts wrap hidden;
   the dropdown becomes an inline child of the More sheet's Layouts
   row. Inside the inline host, every layout/action ROW is restyled
   to be visually IDENTICAL to the primary `.mms-item` rows in the
   More sheet (same padding, gap, font-size, icon size, border-bottom
   separator) so the inline children read as a contiguous extension
   of the primary list — iOS Settings / Linear / GitHub mobile
   pattern: a single column of equal-weight rows, the parent row
   carries the chevron, the children are inset slightly to imply
   hierarchy without breaking the rhythm. */
@media (max-width: 768px), (max-width: 1366px) and (hover: none) and (pointer: coarse) {
  .layout-manager-wrap { display: none; }
  /* Inline host strips desktop popover chrome (border, radius,
     shadow, fixed width, max-height scroll) so the dropdown lays out
     as a normal full-width block flowing inside the More sheet's
     vertical stack. */
  .mms-layout-host > .layout-manager-dropdown {
    position: static;
    top: auto;
    right: auto;
    left: auto;
    bottom: auto;
    width: 100%;
    max-height: none;
    border: none;
    border-radius: 0;
    box-shadow: none;
    background: transparent;
    padding: 0;
  }
  .mms-layout-host {
    background: var(--bg-tertiary, var(--bg-secondary));
  }
  /* Layout-list items and action items: match `.mms-item` exactly.
     gap:12 / padding:14 18 / font-size:14 / border-bottom 1px /
     left icon 20px / no rounding / left-edge inset of 12px so the
     child rows visually subordinate to the Layouts parent row
     (iOS Settings style nesting). */
  .mms-layout-host .lm-item,
  .mms-layout-host .lm-action {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 18px 14px 30px;
    font-size: 14px;
    color: var(--text-primary);
    background: transparent;
    border: none;
    border-bottom: 1px solid var(--border-color);
    width: 100%;
    text-align: left;
    cursor: pointer;
  }
  .mms-layout-host .lm-item:active,
  .mms-layout-host .lm-action:active {
    background: var(--bg-hover);
  }
  /* Left icon (lm-icon) parity with `.mms-item svg` — 20×20,
     stroke:currentColor + 1.5 already on the inline svg attributes
     (svgIcon() rewrote all 24-grid).
     [SPECIFICITY NOTE] The desktop rules `.lm-item .lm-icon`,
     `.lm-action .lm-icon`, `.lm-item .lm-check` are all defined
     LATER in the stylesheet at specificity (0,0,2,0). For our
     mobile override to actually win we either need to bump
     specificity to (0,0,3,0)+ OR redefine after them. We pick
     specificity bump so the rules can stay grouped inside the
     mobile @media block. Three-class selectors picked:
       .mobile-more-sheet .mms-layout-host .lm-icon   = (0,0,3,0)
       .mobile-more-sheet .mms-layout-host .lm-check  = (0,0,3,0)
     This beats both desktop rules without needing !important. */
  .mobile-more-sheet .mms-layout-host .lm-icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
  }
  /* Layout-name occupies the remaining width before the optional
     check mark, matching how `.mms-item__label` flexes inside the
     Layouts parent row. */
  .mobile-more-sheet .mms-layout-host .lm-item .lm-name {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .mobile-more-sheet .mms-layout-host .lm-check {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    opacity: 0;
  }
  .mobile-more-sheet .mms-layout-host .lm-item.active .lm-check {
    opacity: 1;
    color: var(--primary-color, #2962ff);
  }
  .mobile-more-sheet .mms-layout-host .lm-item.active {
    color: var(--primary-color, #2962ff);
  }
  /* Hide the lm-divider — separation between layouts and actions
     is now carried by the regular per-row border-bottom, the same
     way the primary MORE sheet doesn't need a separate divider
     between its rows. */
  .mms-layout-host .lm-divider {
    display: none;
  }
  .mms-layout-host .lm-action.lm-danger {
    color: #ef4444;
  }
  .mms-layout-host .lm-action.lm-danger:active {
    background: rgba(239, 68, 68, 0.1);
  }
}
.layout-manager-dropdown {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    width: 240px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 8px;
    box-shadow: var(--popover-shadow);
    z-index: 10000;
    padding: 6px 0;
    max-height: calc(100vh - 120px);
    overflow-y: auto;
}
.lm-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    font-size: 14px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background .15s;
    user-select: none;
}
.lm-item:hover { background: var(--bg-hover); }
.lm-item.active { color: var(--primary-color); }
.lm-item .lm-icon {
    width: 16px; height: 16px;
    flex-shrink: 0;
    color: inherit;
}
.lm-item .lm-name {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.lm-item .lm-check {
    width: 16px; height: 16px;
    flex-shrink: 0;
    opacity: 0;
}
.lm-item.active .lm-check { opacity: 1; color: var(--primary-color); }
.lm-divider {
    height: 1px;
    background: var(--border-color);
    margin: 4px 0;
}
.lm-action {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    font-size: 14px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background .15s;
}
.lm-action:hover { background: var(--bg-hover); }
.lm-action .lm-icon { width: 16px; height: 16px; flex-shrink: 0; }
.lm-action.lm-danger { color: #ef4444; }
.lm-action.lm-danger:hover { background: rgba(239,68,68,.1); }
.lm-rename-input {
    flex: 1;
    background: var(--bg-primary);
    border: 1px solid var(--primary-color);
    border-radius: 4px;
    color: var(--text-primary);
    font-size: 13px;
    padding: 2px 6px;
    outline: none;
}
.lm-confirm-overlay {
    position: fixed;
    inset: 0;
    z-index: 10001;
    background: rgba(0,0,0,.5);
    display: flex;
    align-items: center;
    justify-content: center;
}
.lm-confirm-dialog {
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 10px;
    padding: 24px;
    width: 320px;
    box-shadow: var(--popover-shadow);
}
.lm-confirm-title {
    font-size: 16px;
    font-weight: 600;
    color: #ef4444;
    margin-bottom: 10px;
}
.lm-confirm-text {
    font-size: 14px;
    color: var(--text-primary);
    line-height: 1.5;
    margin-bottom: 20px;
}
.lm-confirm-btns {
    display: flex;
    gap: 10px;
    justify-content: flex-end;
}
.lm-confirm-btn {
    padding: 7px 18px;
    border-radius: 6px;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    border: 1px solid transparent;
    transition: background .15s, opacity .15s, border-color .15s;
}
.lm-confirm-cancel {
    background: transparent;
    color: var(--text-primary);
    border-color: var(--text-secondary);
}
.lm-confirm-cancel:hover {
    background: var(--bg-hover);
    border-color: var(--text-primary);
    opacity: 1;
}
.lm-confirm-ok {
    background: #ef4444;
    color: #fff;
}
.lm-confirm-ok:hover { background: #dc2626; }

/* ========== Chart Appearance Modal ========== */
.ca-modal {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 9999;
    pointer-events: none;
}
.ca-modal.show { display: block; }
.ca-dialog {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 520px;
    max-width: 92vw;
    max-height: 85vh;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 12px;
    box-shadow: var(--popover-shadow);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    pointer-events: auto;
}
.ca-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 20px;
    border-bottom: 1px solid var(--popover-border);
    cursor: move;
    user-select: none;
}
.ca-header h3 {
    margin: 0;
    font-size: 16px;
    font-weight: 600;
    color: var(--text-primary);
}
.ca-close {
    width: 30px; height: 30px;
    border: none; border-radius: 6px;
    background: transparent;
    color: var(--text-primary);
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background .15s;
}
.ca-close:hover { background: var(--bg-hover); }
.ca-body {
    flex: 1;
    overflow-y: auto;
    padding: 8px 20px 16px;
    scrollbar-width: thin;
    scrollbar-color: transparent transparent;
}
.ca-body:hover { scrollbar-color: #c8ccd8 transparent; }
.ca-body::-webkit-scrollbar { width: 3px; }
.ca-body::-webkit-scrollbar-track { background: transparent; }
.ca-body::-webkit-scrollbar-thumb { background: transparent; border-radius: 2px; transition: background .2s; }
.ca-body:hover::-webkit-scrollbar-thumb { background: #c8ccd8; }
.ca-body::-webkit-scrollbar-thumb:hover { background: #a0a4b0; }
.ca-section {
    margin-bottom: 6px;
}
.ca-section-title {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--text-secondary);
    padding: 10px 0 6px;
    border-bottom: 1px solid var(--popover-border);
    margin-bottom: 4px;
}
.ca-row {
    display: flex;
    align-items: center;
    padding: 7px 0;
    gap: 10px;
}
.ca-label {
    font-size: 13px;
    color: var(--text-primary);
    white-space: nowrap;
    min-width: 110px;
}
.ca-color-pick {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
}
.ca-swatches {
    display: flex;
    gap: 5px;
}
.ca-swatch {
    width: 20px; height: 20px;
    border-radius: 4px;
    cursor: pointer;
    border: 2px solid transparent;
    transition: border-color .15s, transform .1s;
}
.ca-swatch:hover { transform: scale(1.15); }
.ca-swatch.active { border-color: var(--accent-color); }
.ca-color-input {
    width: 28px; height: 28px;
    border: none; border-radius: 4px;
    background: transparent;
    cursor: pointer;
    padding: 0;
}
.ca-color-input::-webkit-color-swatch-wrapper { padding: 0; }
.ca-color-input::-webkit-color-swatch { border: 1px solid var(--text-secondary); border-radius: 4px; }

/* Toggle switch */
.ca-toggle-row {
    justify-content: flex-start;
}
.ca-toggle-row .ca-label { flex: 1; }
.ca-toggle-wrap { margin-left: auto; }
.ca-icon {
    width: 18px; height: 18px;
    flex-shrink: 0;
    color: var(--text-secondary);
}
.ca-toggle {
    width: 36px; height: 20px;
    border-radius: 10px;
    background: var(--bg-quaternary);
    border: 1px solid var(--text-secondary);
    cursor: pointer;
    position: relative;
    display: inline-block;
    transition: background .2s;
}
.ca-toggle.active {
    background: var(--accent-color);
    border-color: var(--accent-color);
}
.ca-toggle-knob {
    width: 14px; height: 14px;
    border-radius: 50%;
    background: #fff;
    position: absolute;
    top: 2px; left: 2px;
    transition: left .2s;
}
.ca-toggle.active .ca-toggle-knob { left: 18px; }

/* Select
   margin-left:auto pushes the dropdown to the right edge of .ca-row so it
   aligns visually with .ca-toggle (uses .ca-toggle-wrap{margin-left:auto})
   and color-pick swatches. Without this rule the dropdown sits flush to
   the label, leaving a large empty gap on the right side of the row. */
.ca-select {
    background: var(--bg-input);
    color: var(--text-primary);
    border: 1px solid var(--text-secondary);
    border-radius: 6px;
    padding: 5px 10px;
    font-size: 13px;
    cursor: pointer;
    outline: none;
    margin-left: auto;
    min-width: 120px;
}
.ca-select:focus { border-color: var(--accent-color); }

/* Footer */
.ca-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 20px;
    border-top: 1px solid var(--popover-border);
}
.ca-footer-right { display: flex; gap: 8px; }
.ca-btn {
    padding: 7px 18px;
    border-radius: 6px;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    border: 1px solid var(--text-secondary);
    transition: background .15s;
}
.ca-btn-reset {
    background: transparent;
    color: var(--text-secondary);
    border-color: transparent;
    padding-left: 0;
    padding-right: 0;
    margin-right: auto;
}
.ca-btn-reset:hover { color: var(--price-down); }
.ca-btn-cancel {
    background: transparent;
    color: var(--text-primary);
}
.ca-btn-cancel:hover { background: var(--bg-hover); }
.ca-btn-apply {
    background: var(--accent-color);
    color: #fff;
    border-color: var(--accent-color);
}
.ca-btn-apply:hover { opacity: .9; }

/* ===== [RESPONSIVE-2026-05-14] BEGIN =====
 * CHART page multi-device responsive adaptation.
 * Scope: chart page only. Breakpoints:
 *   xs        max-width 480px   - phone portrait, single chart + bottom tabs
 *   mobile    max-width 768px   - phone landscape / small tablet portrait
 *   tablet    max-width 1280px  - tablet landscape / small laptop
 *   desktop   default >=1281px  - full layout (untouched)
 *   ultrawide min-width 1920px  - extra-wide screens
 *
 * Strategy:
 *   1. Wrap pure-hover popups in @media (hover: hover) so they don't fire
 *      from accidental touch hover (iOS/Android).
 *   2. Tablet narrows side panel defaults so chart area stays usable.
 *   3. Mobile collapses left/right panels and switches to bottom-tab paradigm
 *      (Binance/OKX/TradingView APP convention).
 *   4. xs further compresses top bar and forces single chart layout.
 *   5. Touch primary devices get always-visible scrollbars and pane action btns.
 *
 * Rollback: see web_app/_backup_chart_responsive_20260514/README_RESTORE.txt
 * Or: delete everything between [RESPONSIVE-2026-05-14] BEGIN and END.
 */

/* ---------- 1. Hover defense (any viewport, only for fine-pointer devices) ---------- */
/* Replicate the pure-hover popups under (hover: hover) so they remain on
   desktop, while the original :hover rules (above in file) still trigger
   on touch devices but are now harmless because tt.app.js will disable
   them via the .open class path on isTouchPrimary devices. */
@media (hover: none) {
    /* On touch devices, never use hover to expand submenus. The .open
       class (added by JS click handler) takes over.
       Reason: iOS Safari fakes hover on first tap which causes flicker. */
    .nud-submenu-parent:hover > .nud-submenu,
    .chart-user-auth:hover .nav-user-dropdown,
    .heatmap-layer-item:hover > .heatmap-color-submenu,
    .yes-topbar-menu-submenu-wrap:hover > .yes-topbar-submenu,
    .chart-context-menu-item.has-submenu:hover > .chart-context-menu-submenu.visible {
        display: none;
    }
    /* Re-enable when JS adds .open class */
    .nud-submenu-parent.open > .nud-submenu,
    .chart-user-auth.open .nav-user-dropdown,
    .heatmap-layer-item.open > .heatmap-color-submenu,
    .yes-topbar-menu-submenu-wrap.open > .yes-topbar-submenu,
    .chart-context-menu-item.has-submenu.open > .chart-context-menu-submenu.visible {
        display: block;
    }
    /* Always-show pane action buttons on touch (cannot rely on hover) */
    .tc-pane-overlay-actions { opacity: 1 !important; }
    /* Always-show tab close buttons */
    .bp-tab .bp-tab-close { opacity: 1 !important; }
    /* Touch tap target minimum: 36px */
    .toolbar-btn, .top-bar .toolbar-btn, .pt-toolbar-btn,
    .top-bar-chart-btn, .top-bar-ind-gear-btn {
        min-width: 36px;
        min-height: 36px;
    }
    /* Always-on scrollbars on touch (no hover to reveal them) */
    *::-webkit-scrollbar-thumb {
        background: rgba(120, 120, 130, 0.5) !important;
    }
}

/* [FIX] 2026-05-14 — iOS/WebKit can still synthesize hover on coarse pointers
   in some tap paths. Force heatmap color-submenu to obey only `.open` state. */
@media (pointer: coarse) {
    .heatmap-layer-item:hover > .heatmap-color-submenu {
        display: none !important;
    }
    .heatmap-layer-item.open > .heatmap-color-submenu {
        display: block !important;
    }
}

/* ---------- 2. Tablet (max 1280px) ---------- */
@media (max-width: 1280px) {
    /* Narrow side panels so chart keeps breathing room */
    .middle-panel {
        width: 340px;
        max-width: 35vw;
    }
    .right-panel {
        width: 400px;
        max-width: 40vw;
    }
    .bottom-panel {
        height: 180px;
    }
    /* Hide layout options for 6 and 8 charts on tablet (too cramped) */
    .layout-item[data-layout="6"],
    .layout-item[data-layout="8"] {
        display: none !important;
    }
}

/* ---------- 3. Mobile (max 768px) - Bottom Tab paradigm ---------- */
/* ===== Top-bar arrow-scroll lane — applies to mobile / tablet / iPad Pro =====
 * Goal: prevent the user-avatar overlap whenever top-bar-left can't fit all
 * chart controls. Active on any viewport ≤ 1280px AND additionally on any
 * touch-primary tablet up to 1366px (covers iPad Pro 12.9" landscape).
 * Uses .tt-tbl-wrap injected by JS as the positioning context for two arrow
 * buttons that scroll the lane horizontally. */
@media (max-width: 1280px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    .top-bar {
        display: flex;
        align-items: stretch;
        height: 44px;
        /* [FIX] 2026-05-21 — Lift the entire top-bar above #mobilePanelHost
           (z-index:9000). Without this, switching to Markets / Order Book
           tabs causes the mobile panel host to cover the top-bar dropdowns
           (user menu, server-select, layout manager) so they appear non-
           interactive. Picked 9001 so the strategy-mobile-switcher (9100,
           sits BELOW the top bar at top:44 so no z conflict), the bottom
           tabs (9500), more sheet (9600), and TAI overlay (9700) all
           still win their respective layers. */
        position: relative;
        z-index: 9001;
    }
    .tt-tbl-wrap {
        flex: 1 1 0;
        min-width: 0;
        position: relative;
        display: flex;
        align-items: stretch;
    }
    /* Pinned hamburger (always visible, not part of scroll lane). */
    .tt-tbl-wrap > .chart-menu-wrap {
        flex: 0 0 auto;
        width: 45px;
        position: relative;
        z-index: 7;     /* above arrows so the dropdown menu always wins clicks */
        background: var(--bg-primary);
        display: flex;
        align-items: center;
        justify-content: flex-start;
        padding-left: 1px;
    }
    .top-bar-left {
        flex: 1 1 auto;
        min-width: 0;
        overflow-x: auto;
        overflow-y: hidden;
        white-space: nowrap;
        display: flex;
        align-items: center;
        gap: 2px;
        /* [FIX] 2026-05-14 — iOS WebKit may swallow taps / clip fixed dropdowns
           inside momentum-scroll containers. Keep horizontal scroll, but disable
           momentum mode so top-bar menus (Strategy/Footprint/etc.) stay clickable
           and visible on Safari/Chrome iOS. */
        -webkit-overflow-scrolling: auto;
        scroll-behavior: smooth;
        /* Native scrollbar fully hidden — navigation is via arrows. */
        scrollbar-width: none;
        -ms-overflow-style: none;
    }
    .top-bar-left::-webkit-scrollbar {
        display: none;
        width: 0;
        height: 0;
    }
    .top-bar-right {
        flex: 0 0 auto;
        margin-left: 0;
        gap: 4px;
        background: var(--bg-primary);
        padding-left: 6px;
        /* [FIX] 2026-05-21 — DO NOT add position:relative + z-index here.
           Creating a stacking context on .top-bar-right traps its absolute
           descendants (nav-user-dropdown z:9999, server-select-dropdown
           z:10000) inside the local context — meaning they can never paint
           above #mobilePanelHost (z:9000). The parent .top-bar now carries
           z-index:9001 in this media query, which lifts the whole top-bar
           (and all its dropdowns) above the mobile panel host.
           box-shadow renders without z-index. */
        box-shadow: -8px 0 8px -8px rgba(0, 0, 0, 0.25);
    }
    .top-bar-left > * {
        flex-shrink: 0;
    }
    .top-bar-left .toolbar-btn,
    .top-bar-left .toolbar-dropdown-btn,
    .top-bar-left .top-bar-chart-btn,
    .top-bar-left .top-bar-ind-gear-btn,
    .top-bar-left .pt-toolbar-btn {
        touch-action: manipulation;
    }
    .tt-tbl-wrap > .top-bar-left {
        padding-left: 28px;
        padding-right: 28px;
    }
    .top-bar-left > *:not(:first-child)::before {
        display: none !important;
    }
    .tt-tbl-arrow {
        position: absolute;
        top: 0;
        bottom: 0;
        width: 28px;
        display: flex;
        align-items: center;
        justify-content: center;
        background: var(--bg-primary);
        border: none;
        color: var(--text-primary);
        cursor: pointer;
        padding: 0;
        z-index: 6;
        box-shadow: 0 0 6px 0 var(--bg-primary);
    }
    .tt-tbl-arrow.tt-tbl-arrow-left  { left: var(--tt-tbl-pin-w, 0px); }
    .tt-tbl-arrow.tt-tbl-arrow-right { right: 0; }
    .tt-tbl-arrow svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
    .tt-tbl-arrow[disabled] { opacity: 0.25; pointer-events: none; }
    .tt-tbl-arrow:active { background: var(--bg-secondary, var(--border-color)); }
    /* Dropdown menus must escape the clipped top-bar-left scroll lane.
     * On mobile they become a full-width sheet docked under the 44px top bar.
     * The .toolbar-dropdown.open / .show / etc. selectors elsewhere flip
     * display: block; here we only enforce position/size — but we add a
     * defensive `display: block !important` for the .open state because some
     * older inline-style remnants from positionFixedDropdownMenu could leave
     * stale display:block on the menu node and confuse Safari's render. */
    .top-bar-left .toolbar-dropdown.open .toolbar-dropdown-menu,
    .top-bar-left .interval-dropdown-menu.show,
    .top-bar-left .chart-type-dropdown-menu.show,
    .top-bar-left .ma-dropdown-menu.show,
    .top-bar-left .sub-ind-dropdown-menu.show,
    .top-bar-left .toolbar-dropdown.open .heatmap-menu,
    .top-bar-left .toolbar-dropdown.open .fp-menu,
    .tt-tbl-wrap > .chart-menu-wrap.open .chart-menu-dropdown {
        display: block !important;
    }
    .top-bar-left .toolbar-dropdown-menu,
    .top-bar-left .interval-dropdown-menu,
    .top-bar-left .chart-type-dropdown-menu,
    .top-bar-left .ma-dropdown-menu,
    .top-bar-left .sub-ind-dropdown-menu,
    .top-bar-left .heatmap-menu,
    .top-bar-left .fp-menu,
    .top-bar-left .chart-menu-dropdown,
    .top-bar-left .drawing-tool-dropdown-menu,
    .tt-tbl-wrap > .chart-menu-wrap .chart-menu-dropdown {
        position: fixed !important;
        top: 44px !important;
        left: 4px !important;
        right: 4px !important;
        width: auto !important;
        min-width: 0 !important;
        max-width: calc(100vw - 8px) !important;
        max-height: calc(100vh - 100px) !important;
        overflow-x: hidden !important;
        overflow-y: auto !important;
        z-index: 9999 !important;
        box-sizing: border-box !important;
    }
    /* Prevent any descendant from forcing a horizontal overflow. */
    .top-bar-left .toolbar-dropdown-menu *,
    .top-bar-left .interval-dropdown-menu *,
    .top-bar-left .chart-type-dropdown-menu *,
    .top-bar-left .ma-dropdown-menu *,
    .top-bar-left .sub-ind-dropdown-menu *,
    .top-bar-left .heatmap-menu *,
    .top-bar-left .fp-menu *,
    .top-bar-left .chart-menu-dropdown *,
    .top-bar-left .drawing-tool-dropdown-menu *,
    .tt-tbl-wrap > .chart-menu-wrap .chart-menu-dropdown * {
        max-width: 100% !important;
        box-sizing: border-box !important;
    }
    /* [FIX] 2026-05-22 — on mobile the heatmap dropdown is capped at
       viewport width; long bilingual labels (fr/de/es ~660px) would
       silently clip on the right because the parent menu has
       overflow-x:hidden. Re-enable wrapping for these specific rows so
       all locales show in full. We DO NOT use ellipsis (per user
       requirement). The row's flex layout already pushes badge /
       colour preview / arrow to the right edge, so wrapping the label
       only widens the row vertically. */
    .top-bar-left .heatmap-menu .heatmap-layer-name {
        white-space: normal !important;
        word-break: break-word !important;
        overflow-wrap: anywhere !important;
    }
    /* Nested submenus must render inline (otherwise pushed off-screen). */
    .top-bar-left .ma-submenu,
    .top-bar-left .heatmap-color-submenu,
    .top-bar-left .ind-submenu,
    .top-bar-left .nud-submenu,
    .tt-tbl-wrap > .chart-menu-wrap .nud-submenu {
        position: static !important;
        left: auto !important;
        top: auto !important;
        transform: none !important;
        width: 100% !important;
        min-width: 0 !important;
        max-width: 100% !important;
        margin: 4px 0 !important;
        border-left: 2px solid var(--border-color);
        padding-left: 8px;
    }
}

/* [RESPONSIVE-2026-05-14] Mobile-effective layout.
 * The OR clause `(max-width: 1366px) and (hover: none) and (pointer: coarse)`
 * covers iPad Pro 12.9" landscape (1366), iPad Pro 11" (1194), iPad Air,
 * Surface Pro 13" — any touch-primary tablet up to 1366px CANNOT operate
 * the desktop chart layout (hover-only toolbars, tiny click targets) so
 * it correctly falls into mobile UX (bottom tabs, full-screen panels,
 * scrollable top-bar with arrows). A non-touch 1366 desktop laptop does
 * NOT match because of the hover/pointer constraint, preserving its
 * desktop layout. This block is mirrored 1:1 with window.tt.device.isMobile
 * (in tt.ui.manager.js) so JS branches and CSS layout never disagree. */
@media (max-width: 768px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    /* Top bar: compress button gap, hide non-essential buttons */
    .top-bar { padding: 0; height: 44px; }
    .top-bar .toolbar-btn { width: 36px; height: 36px; }
    /* [FIX] 2026-05-31 — keep "Chart" label visible on mobile per 虎哥 review.
       Reason: the chart-settings entry is the most-used drop-down on the mobile
       top bar; collapsing it to icon-only made it indistinguishable from the
       neighbouring chart-appearance / indicator-settings gear icons. Other
       .pt-toolbar-btn labels (Paper Trading / Strategy / YeScript) stay icon-
       only because those entries already have first-class entries in the More
       sheet / bottom tabs, and labels here would overflow the 44px-tall bar. */
    .pt-toolbar-btn span { display: none; } /* icons only on mobile */
    .top-bar-chart-btn .chart-text {
        font-size: 13px;
        max-width: 60px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    /* Mobile top-bar — KEEP ALL FUNCTIONS reachable.
     * Hidden here = available in the More sheet (always reachable).
     * - paperTradingToggle / yescriptToggle / yescriptEditorToggle:
     *   replaced by PT bottom-tab and the Strategy entry in More sheet
     *   (which opens #strategyMobileSwitcher with Editor / Scripts pair)
     * - screenshotBtn: in More sheet
     * - themeToggle: in More sheet (low-frequency setting, frees top-bar
     *   space so the remaining 3 buttons fit comfortably with gap:6px)
     * - fullscreenBtn: meaningless on mobile (browser already chrome-less,
     *   triggers the Fullscreen API which gives a nearly identical view +
     *   inconsistent behaviour across iOS/Android)
     * Layout manager and server-select STAY visible on the right because
     * users need quick access to chart layout and server status indicator.
     *
     * [CHG] 2026-05-29 — YeScript UI Redesign (实施规范 §3.1) — adding
     *   #yescriptEditorToggle here so the new Editor-direct topbar button
     *   does NOT leak into mobile (where Strategy More entry is canonical).
     *   Without this, the new button would show up as a lone glyph next
     *   to PT/Strategy hidden ones, breaking visual balance.
     */
    #paperTradingToggle,
    #yescriptToggle,
    #yescriptEditorToggle,
    /* [FIX] 2026-06-01 — duplicate mobile YeScript templates trigger.
       Reason: the templates picker now lives in the mobile More sheet
       YeScript row (`.mms-yes-row__chevron`). Keeping the top-bar
       `#yescriptTemplatesArrow` on phones duplicates the same action.
       Change: hide the top-bar arrow in mobile-effective breakpoints,
       keep desktop behavior unchanged.
       Warning: do NOT hide `.mms-yes-row__chevron` in the More sheet —
       that is the canonical mobile templates entry. */
    #yescriptTemplatesArrow,
    #screenshotBtn,
    #themeToggle,
    #fullscreenBtn {
        display: none !important;
    }

    /* Right cluster keeps its natural HTML source order:
     *   user → layout → server
     * top-bar-right { gap: 6px } gives even spacing now that
     * #themeToggle / #fullscreenBtn / #screenshotBtn are hidden.
     *
     * Strip the inherited 12px margin-right from user widgets so the
     * gap-controlled spacing is uniform — without this override the user
     * widget would have 6 + 12 = 18px right margin while layout↔server
     * has only 6px, making the user appear to be detached from the
     * cluster on the left. */
    .top-bar-right { gap: 6px; }
    .top-bar-right > .chart-user-guest,
    .top-bar-right > .chart-user-auth { margin: 0 !important; }

    /* Hide left & right toolbars (drawing tools accessible via top-bar drawer) */
    .left-toolbar,
    .right-toolbar {
        display: none !important;
    }

    /* Hide all docked side panels by default (controlled by mobile bottom tabs) */
    .middle-panel,
    .right-panel,
    #rightResizer,
    #middleRightResizer,
    #verticalResizer {
        display: none !important;
    }

    /* Bottom panel hidden by default; bottom tabs control reveal */
    .bottom-panel,
    #bottomResizer {
        display: none !important;
    }

    /* Chart area takes full width minus bottom tabs */
    .chart-layout-container {
        min-height: 0;
    }
    .center-panel {
        /* Leave room for bottom tab bar AND iOS home indicator. */
        padding-bottom: calc(48px + env(safe-area-inset-bottom, 0px));
    }

    /* Force single chart layout - extra defense beyond JS layout limit */
    .chart-layout-container:not(.layout-single) {
        grid-template-columns: 1fr !important;
        grid-template-rows: 1fr !important;
    }
    .chart-layout-container.layout-2v,
    .chart-layout-container.layout-2h,
    .chart-layout-container.layout-4,
    .chart-layout-container.layout-6,
    .chart-layout-container.layout-8 {
        grid-template-columns: 1fr !important;
        grid-template-rows: 1fr !important;
    }
    /* Hide non-first chart slots on mobile to avoid empty grid cells */
    .chart-slot:not(#chart-slot-0) { display: none !important; }
    /* Hide multi-chart layout options 4/6/8/2h, only keep single + 2v */
    .layout-item[data-layout="4"],
    .layout-item[data-layout="6"],
    .layout-item[data-layout="8"],
    .layout-item[data-layout="2h"] {
        display: none !important;
    }

    /* iOS Safari quirks: prevent system Look-Up popup on long-press over
       OHLC text or other text content inside the chart container. */
    #chart-layout-container,
    #chart-layout-container * {
        -webkit-user-select: none !important;
        user-select: none !important;
        -webkit-touch-callout: none !important;
    }

    /* Drawing tool drawer (full-screen panel when open) */
    .drawing-tool-dropdown.mobile-drawer-open .drawing-tool-dropdown-menu {
        position: fixed !important;
        top: 44px !important;
        left: 0 !important;
        right: 0 !important;
        bottom: calc(48px + env(safe-area-inset-bottom, 0px)) !important;
        width: 100% !important;
        max-width: 100% !important;
        max-height: none !important;
        z-index: 9999 !important;
        overflow-y: auto !important;
        display: block !important;
    }

    /* Mobile bottom tab bar (always shown on mobile) */
    .mobile-bottom-tabs {
        display: flex !important;
    }

    /* Mobile panel host (full-screen overlay area between top bar and bottom tabs).
       Uses display: flex (not block) so internal flex children (e.g. .bp-content,
       .bp-pane) can stretch correctly. */
    #mobilePanelHost.active {
        display: flex !important;
        flex-direction: column;
        position: fixed;
        top: 44px;
        left: 0;
        right: 0;
        bottom: calc(48px + env(safe-area-inset-bottom, 0px) + var(--tt-mobile-kb-offset, 0px));
        z-index: 9000;
        background: var(--bg-primary);
        overflow: hidden; /* let inner panel handle its own scroll */
    }
    /* Modules and panels placed inside mobilePanelHost must fill the area
       AND keep their flex layout (bottom-panel uses display: flex internally).
       Override any outer "display: none !important" that hid them. */
    #mobilePanelHost.active > .dock-module,
    #mobilePanelHost.active > .bottom-panel.active,
    #mobilePanelHost.active > .bottom-panel,
    #mobilePanelHost.active > .right-panel-top,
    #mobilePanelHost.active > .right-panel-bottom,
    #mobilePanelHost.active > .middle-panel-top,
    #mobilePanelHost.active > .middle-panel-bottom {
        display: flex !important;
        flex-direction: column !important;
        width: 100% !important;
        height: 100% !important;
        max-width: none !important;
        max-height: none !important;
        min-height: 0 !important;
        position: static !important;
    }
    /* Inside the host, bp-content and bp-pane.active need explicit flex
       stretch since their normal parent context is changed. */
    #mobilePanelHost.active .bp-content {
        flex: 1 1 auto !important;
        min-height: 0 !important;
    }
    #mobilePanelHost.active .bp-pane.active {
        display: flex !important;
        flex: 1 1 auto !important;
        min-height: 0 !important;
    }
    /* Hide drag handles in mobile docked context (no drag on mobile) */
    #mobilePanelHost .module-drag-handle,
    #mobilePanelHost .panel-close-handle {
        display: none !important;
    }
    /* Hide the entire bp-tabs strip when bottomPanel is docked into the
     * mobile host. Reason: on mobile, PT and YeScript are presented as
     * INDEPENDENT full-screen views (one entered via the bottom-tab "PT",
     * the other via the More sheet → YeScript). Showing bp-tabs would
     * misleadingly suggest they're related sub-tabs of the same panel.
     * The maximize / minimize / close buttons would also be unnecessary
     * on full-screen mobile layouts. */
    #mobilePanelHost > .bottom-panel > .bp-tabs {
        display: none !important;
    }

    /* [ADD] 2026-05-19 v6 — Strategy sub-tab switcher (Editor / Scripts).
       Shown ONLY when the user is inside the Strategy pane (entered via
       More → Strategy). Sits at the top of #mobilePanelHost so the user
       can flip between the YeScript editor (where AI-written scripts
       live) and the Scripts list (My / Favorites / Community) without
       backing out to the More sheet. */
    .strategy-mobile-switcher.open {
        display: flex !important;
        position: fixed;
        top: 44px;
        left: 0;
        right: 0;
        height: 40px;
        z-index: 9100;
        background: var(--bg-secondary);
        border-bottom: 1px solid var(--border-color);
        padding: 0 8px;
        align-items: stretch;
        gap: 0;
    }
    .strategy-mobile-switcher .sms-tab {
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 4px;
        background: transparent;
        border: none;
        color: var(--text-secondary);
        font-size: 12px;
        font-weight: 600;
        cursor: pointer;
        position: relative;
        padding: 0 4px;
        min-width: 0;
    }
    .strategy-mobile-switcher .sms-tab span {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .strategy-mobile-switcher .sms-tab.active {
        color: #2962ff;
    }
    .strategy-mobile-switcher .sms-tab.active::after {
        content: "";
        position: absolute;
        left: 20%;
        right: 20%;
        bottom: 0;
        height: 2px;
        background: #2962ff;
        border-radius: 2px 2px 0 0;
    }
    .strategy-mobile-switcher .sms-tab svg {
        flex-shrink: 0;
        opacity: .85;
    }
    .strategy-mobile-switcher .sms-tab .sms-tab-badge {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 18px;
        height: 18px;
        padding: 0 5px;
        border-radius: 9px;
        background: #2962ff;
        color: #fff;
        font-size: 11px;
        font-weight: 700;
        line-height: 1;
    }
    /* When the switcher is open, push the panel host down by the
       switcher's height so editor content isn't covered. */
    .strategy-mobile-switcher.open ~ #mobilePanelHost.active,
    .pt-mobile-switcher.open ~ #mobilePanelHost.active {
        top: 84px; /* 44 (top-bar) + 40 (switcher) */
    }

    .pt-mobile-switcher.open {
        display: flex !important;
        position: fixed;
        top: 44px;
        left: 0;
        right: 0;
        height: 40px;
        z-index: 9100;
        background: var(--bg-secondary);
        border-bottom: 1px solid var(--border-color);
        padding: 0 8px;
        align-items: stretch;
        gap: 0;
    }
    .pt-mobile-switcher .ptms-tab {
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 4px;
        background: transparent;
        border: none;
        color: var(--text-secondary);
        font-size: 12px;
        font-weight: 600;
        cursor: pointer;
        position: relative;
        padding: 0 4px;
        min-width: 0;
    }
    .pt-mobile-switcher .ptms-tab span {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .pt-mobile-switcher .ptms-tab.active {
        color: #2962ff;
    }
    .pt-mobile-switcher .ptms-tab.active::after {
        content: "";
        position: absolute;
        left: 20%;
        right: 20%;
        bottom: 0;
        height: 2px;
        background: #2962ff;
        border-radius: 2px 2px 0 0;
    }
    /* Trade/Wallet live in paperTradingModule; Positions in bottomPanel — hide in-module tabs on mobile */
    #mobilePanelHost.active[data-pt-mobile-pane] > .paper-trading-module .pt-sidebar-tabs {
        display: none !important;
    }

    /* [CHG] 2026-05-21 r6 — Unified mobile fullscreen modal style.
       All chart-page dialogs (Chart Appearance, Indicator Settings,
       Exchange Manager, Paper Trading Close, Paper Trading TP/SL) now
       render as full-viewport modals on mobile, matching what TradingView
       / Binance / OKX / Bybit mobile apps do for every dialog. Rationale:
         - 95vw/90vh leaves a visible "frame" of background page bleeding
           through the edges — this looks broken on phones (mixed-modal
           feel; user can accidentally tap chart underneath).
         - Mixing 95vw modals with the already-fullscreen indicator dialog
           creates a "two styles" feel that the user reported as
           inconsistent.
         - A single inset:0 / no-radius / no-transform rule is the
           industry-standard mobile dialog pattern and gives every form
           the full viewport width for its controls.

       Implementation notes:
         - We rely on `inset: 0` (top/right/bottom/left = 0) plus
           `width: auto / height: auto`, so the dialog always fills the
           *visible* viewport, including under iOS Safari's collapsing
           address-bar chrome (100vh would overflow under the bar).
         - JS-applied inline styles (drag handlers + open-handler reset
           in tt.app.js — left/top/transform/margin) are neutralised with
           !important, since drag isn't usable on a fullscreen dialog
           anyway and the open-handler's center-reset becomes a no-op.
         - All Modal/Overlay containers (.ca-modal, .pt-modal-overlay,
           .exchange-dialog-overlay, .ind-settings-modal) already use
           `position: fixed; inset: 0` so they already match the visible
           viewport — only the inner *dialog* element needs flattening.

       Selector groups all dialog classes for DRY — they share the exact
       same fullscreen positioning rules. Inner layout (panes, headers,
       fields) is then customised per-dialog below. */
    .ca-dialog,
    .ind-settings-dialog,
    .exchange-dialog,
    .pt-modal {
        /* position: fixed forces all four dialogs into the same coordinate
           system (the visual viewport) regardless of their parent overlay's
           layout mode. Desktop variations: ca-dialog is `position: fixed`
           already; ind-settings-dialog is `position: fixed` already;
           exchange-dialog uses `position: absolute` (its overlay is a flex
           container that centres it); pt-modal has NO position (it's a
           flex child of pt-modal-overlay). Promoting all four to fixed
           normalises the behaviour. */
        position: fixed !important;
        width: auto !important;
        max-width: none !important;
        height: auto !important;
        max-height: none !important;
        border-radius: 0 !important;
        top: 0 !important;
        right: 0 !important;
        bottom: 0 !important;
        left: 0 !important;
        transform: none !important;
        margin: 0 !important;
        /* All dialogs are flex columns so their body region can flex-fill
           the available height between sticky header / footer. */
        display: flex !important;
        flex-direction: column !important;
    }

    /* Headers across all dialogs: drag is disabled (full-viewport already)
       and the header is fixed flex-shrink:0 so the scroll-able body below
       gets all remaining height. padding-top respects iOS safe-area-inset
       so the title/close button doesn't slide under the notch / Dynamic
       Island on iPhone X+ models. */
    .ca-header,
    .ind-settings-header,
    .exchange-dialog-header,
    .pt-modal-header {
        cursor: default !important;
        flex: 0 0 auto;
        padding-top: calc(14px + env(safe-area-inset-top, 0px));
    }

    /* Bodies across all dialogs: flex:1 + min-height:0 lets internal
       overflow-y:auto kick in properly inside the flex parent. */
    .ca-body,
    .exchange-dialog-list,
    .pt-modal-body {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
    }

    /* Footers stay pinned to the viewport bottom — they don't flex-grow
       but must stay above the safe-area inset on iPhone X+ devices. */
    .pt-modal-footer {
        flex: 0 0 auto;
        padding-bottom: calc(14px + env(safe-area-inset-bottom, 0px));
    }

    /* Exchange Manager: search bar (between header and list) keeps its
       natural height; the list below it gets the remaining flex height. */
    .exchange-dialog-search {
        flex: 0 0 auto;
    }

    /* [FIX] 2026-05-21 — Indicator-settings dialog inner layout (mobile).
       The unified fullscreen rule above already makes the dialog
       inset:0 / no-radius. This block only handles the dialog-specific
       inner panes: stack the 240px-wide left indicator list and the
       right settings panel VERTICALLY (industry standard for TradingView /
       Binance / OKX mobile indicator dialogs) — at 360-420 px viewport
       width a horizontal split would leave only ~120-180 px for the
       right-side settings form, which iOS users reported as unreadable. */
    .ind-settings-body {
        flex-direction: column;
        flex: 1 1 auto;
        min-height: 0;
    }
    /* Use flex ratios (36/64) instead of vh so iOS Safari's address-bar
       chrome doesn't cause the left pane to overflow into the right pane.
       Each pane scrolls its own content internally. */
    .ind-settings-left {
        width: 100% !important;
        flex: 0 0 36%;
        min-height: 0;
        border-right: none;
        border-bottom: 1px solid var(--popover-border);
    }
    .ind-settings-right {
        flex: 1 1 64%;
        min-height: 0;
    }
    /* Right-pane internal scroll: the settings form must scroll inside its
       own pane, not push the dialog. */
    .ind-settings-panel {
        height: 100%;
    }
    /* The cat-items inset (36px on desktop) is too deep on a 360px phone
       — drop to 22px so the indicator labels read without truncation. */
    .ind-settings-cat-items {
        padding-left: 22px;
    }

    /* Mobile "More" bottom sheet (alt to top-bar menu, anchored from bottom) */
    .mobile-more-sheet {
        position: fixed;
        left: 0;
        right: 0;
        bottom: calc(48px + env(safe-area-inset-bottom, 0px));
        z-index: 9600;
        background: var(--popover-bg);
        border-top: 1px solid var(--popover-border);
        max-height: 60vh;
        overflow-y: auto;
        display: none;
        box-shadow: 0 -12px 32px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(0, 0, 0, 0.4);
    }
    .mobile-more-sheet.open {
        display: block;
    }
    .mobile-more-sheet .mms-item {
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 14px 18px;
        color: var(--text-primary);
        font-size: 14px;
        background: transparent;
        border: none;
        border-bottom: 1px solid var(--border-color);
        width: 100%;
        text-align: left;
        cursor: pointer;
        font-family: inherit;
    }
    .mobile-more-sheet .mms-item .mms-item__label {
        flex: 1 1 auto;
    }
    .mobile-more-sheet .mms-item .mms-item__badge {
        margin-left: auto;
        min-width: 18px;
        height: 18px;
        padding: 0 5px;
        border-radius: 999px;
        background: #2962ff;
        color: #ffffff;
        font-size: 11px;
        font-weight: 700;
        line-height: 18px;
        text-align: center;
    }
    .mobile-more-sheet .mms-item .mms-item__badge[hidden] {
        display: none !important;
    }
    .mobile-more-sheet .mms-item:active {
        background: var(--bg-hover);
    }
    .mobile-more-sheet .mms-item svg {
        width: 20px;
        height: 20px;
        flex-shrink: 0;
        stroke: currentColor;
        fill: none;
        stroke-width: 1.5;
        stroke-linecap: round;
        stroke-linejoin: round;
    }
    /* [ADD] 2026-05-20 v20260520.8 — Layouts inline-expand row.
       Chevron selector needs the explicit `svg.mms-item__chevron`
       to win specificity over `.mobile-more-sheet .mms-item svg` —
       same class count plus one more element matcher. */
    .mobile-more-sheet .mms-item--layout .mms-item__label {
        flex: 1 1 auto;
    }
    .mobile-more-sheet .mms-item--layout svg.mms-item__chevron {
        width: 16px;
        height: 16px;
        opacity: 0.6;
        transition: transform 0.18s ease;
    }
    .mobile-more-sheet .mms-item--layout.mms-item--expanded svg.mms-item__chevron {
        transform: rotate(90deg);
    }
    .mobile-more-sheet .mms-layout-host[hidden] { display: none; }
    .mobile-more-sheet .mms-layout-host {
        display: block;
    }

    /* [ADD] 2026-05-27 v2 — Disabled mms-item ("Coming soon" placeholder).
       Used by the MEDIA > Community row today, and reusable by any future
       not-yet-shipped feature. Visual rules:
         - 50% opacity on the icon + main label so the row reads as
           "available eventually" but clearly inactive (Apple HIG / Material
           pattern for un-built menu items),
         - cursor: default + :active no-op so tapping doesn't simulate a
           pressed state (the click handler still surfaces a Toast — see
           tt.app.js case 'community'),
         - .mms-item__hint pinned to the right edge with smaller, dimmer
           text ("Coming soon") so users know why the row looks faded.
       Specificity matches `.mobile-more-sheet .mms-item` (class+class+class)
       so these rules override the base mms-item without !important. */
    .mobile-more-sheet .mms-item--disabled {
        opacity: 0.55;
        cursor: default;
    }
    .mobile-more-sheet .mms-item--disabled:active {
        background: transparent;
    }
    .mobile-more-sheet .mms-item--disabled .mms-item__label {
        flex: 1 1 auto;
    }
    .mobile-more-sheet .mms-item__hint {
        margin-left: auto;
        font-size: 12px;
        color: var(--text-secondary);
        opacity: 0.9;
        white-space: nowrap;
    }

    /* [ADD] 2026-05-21 — Market Hub inline expand rows + child panels.
       Mirrors the .mms-item--layout chevron pattern above so all
       expand-in-place rows in the sheet share one visual language
       (iOS Settings / Linear / GitHub Mobile convention). We reuse
       the .mms-item__label / .mms-item__chevron / .mms-item--expanded
       classes directly — no new chevron rules needed beyond scope. */
    .mobile-more-sheet .mms-item--mh .mms-item__label {
        flex: 1 1 auto;
    }
    .mobile-more-sheet .mms-item--mh svg.mms-item__chevron {
        width: 16px;
        height: 16px;
        opacity: 0.6;
        transition: transform 0.18s ease;
    }
    .mobile-more-sheet .mms-item--mh.mms-item--expanded svg.mms-item__chevron {
        transform: rotate(90deg);
    }

    /* Child panel: visually nested under the parent row.
       Background sits 6% darker than the sheet body so the eye
       reads the panel as "owned by the row above" without needing
       a left rule. Hidden until aria-expanded="true" flips on the
       parent (`[hidden]` attribute toggled by toggleMarketHubInline). */
    .mobile-more-sheet .mms-mh-children[hidden] { display: none; }
    .mobile-more-sheet .mms-mh-children {
        display: block;
        background: rgba(0, 0, 0, 0.18);
    }

    /* Sub-category label inside a Market Hub group (Scan / Activity /
       Asset / Discovery for Derivatives). Tight uppercase small-caps
       header — same typography as .mms-section but with smaller
       padding so it integrates inside the nested panel rather than
       floating like the top-level section dividers.
       [CHG] 2026-05-27 — color aligned with .mms-section
       (--text-primary). Reason: same user feedback as the section
       header fix above — in light theme the previous secondary-gray
       read as "wrong" against the white sheet background. Hierarchy
       still preserved by 10px font-size + 600 weight + uppercase. */
    .mobile-more-sheet .mms-mh-cat {
        padding: 8px 18px 2px 50px;
        font-size: 10px;
        font-weight: 600;
        letter-spacing: 0.8px;
        text-transform: uppercase;
        color: var(--text-primary);
        user-select: none;
        pointer-events: none;
    }

    /* Sub-row link: tappable area for navigation. Indented 50px from
       the left edge so the row icon sits in the same X column as the
       parent row's label (parent row: 18px padding + 20px icon + 12px
       gap = 50px). This makes the visual hierarchy unambiguous — you
       can see at a glance that a sub-row belongs to its parent
       category.
       Touch target: 44px tall (Apple HIG / Material 48dp minimum
       satisfied at default font-size). */
    .mobile-more-sheet .mms-mh-row {
        display: flex;
        align-items: center;
        gap: 10px;
        padding: 10px 18px 10px 50px;
        color: var(--text-primary);
        font-size: 13px;
        text-decoration: none;
        border: none;
        border-bottom: 1px solid var(--border-color);
        width: 100%;
        font-family: inherit;
        min-height: 40px;
        box-sizing: border-box;
    }
    .mobile-more-sheet .mms-mh-row:active {
        background: var(--bg-hover);
    }
    .mobile-more-sheet .mms-mh-row svg {
        width: 16px;
        height: 16px;
        flex-shrink: 0;
        stroke: currentColor;
        fill: none;
        stroke-width: 1.5;
        stroke-linecap: round;
        stroke-linejoin: round;
    }
    /* Last row in a child panel: drop the bottom-border so the panel
       ends cleanly without a stray hairline. */
    .mobile-more-sheet .mms-mh-children > .mms-mh-row:last-child {
        border-bottom-color: transparent;
    }

    /* [ADD] 2026-05-31 r3 — YeScript composite row in More sheet.
     * Layout: [icon main] (label) [▼ chevron]   followed by an
     * inline-collapsible templates panel (.mms-yes-templates).
     * The main button (`.mms-yes-row__main`) is a regular .mms-item so
     * it shares the 14px padding / 14px font-size / icon sizing with
     * Symbol Details, Liquidations, etc. The chevron button overlays
     * the main button's right edge so the hit-target reads as a
     * SPLIT button (industry pattern: GitHub mobile org rows, Slack
     * channel-with-children, Discord category headers). Total row
     * height stays 48 px so the divider rhythm is preserved. */
    .mobile-more-sheet .mms-yes-row {
        position: relative;
    }
    .mobile-more-sheet .mms-yes-row .mms-yes-row__main {
        /* When inside .mms-yes-row, leave room on the right for the
         * 44 px chevron tap-target so the label never overlaps. */
        padding-right: 56px;
    }
    /* When the templates panel is open, drop the main row's
     * border-bottom — the templates panel's own group separators
     * already provide enough visual division, and stacking two
     * 1px lines on top of each other reads as a thicker noisy
     * stroke. We only do this when the chevron is in --open state,
     * not generally, so the row keeps its normal divider when the
     * templates are collapsed. */
    .mobile-more-sheet .mms-yes-row:has(.mms-yes-row__chevron--open) .mms-yes-row__main {
        border-bottom-color: transparent;
    }
    .mobile-more-sheet .mms-yes-row__chevron {
        position: absolute;
        top: 0;
        right: 0;
        height: 100%;
        width: 44px;
        display: flex;
        align-items: center;
        justify-content: center;
        background: transparent;
        border: none;
        cursor: pointer;
        color: var(--text-secondary);
        font-family: inherit;
        /* The chevron button sits ON TOP of the main button's right
         * edge. We pad-right the main row 56 px so its label never
         * extends under the chevron, but the chevron itself catches
         * its own tap surface independently. */
        z-index: 1;
    }
    .mobile-more-sheet .mms-yes-row__chevron:active {
        background: var(--bg-hover);
    }
    .mobile-more-sheet .mms-yes-row__chevron svg {
        transition: transform 0.18s ease;
    }
    .mobile-more-sheet .mms-yes-row__chevron--open svg {
        transform: rotate(180deg);
    }

    /* Templates panel: visually nested under the YeScript row, same
     * colour-shift trick as .mms-mh-children so the eye reads the
     * panel as belonging to the row above. Tap-friendly capped height
     * with internal scroll so 22 items + 4 group titles never push
     * the More sheet's other sections off-screen on a 360 px phone.
     * Scrollbar follows the global tradeiz-custom.css default-hidden
     * pattern (約束 4 §滚动条行为) — thumb transparent until hover. */
    .mobile-more-sheet .mms-yes-templates[hidden] { display: none; }
    .mobile-more-sheet .mms-yes-templates {
        display: block;
        background: rgba(0, 0, 0, 0.18);
        max-height: 50vh;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
    }
    .mobile-more-sheet .mms-yes-tmpl-group {
        border-bottom: 1px solid var(--border-color);
    }
    .mobile-more-sheet .mms-yes-tmpl-group:last-child {
        border-bottom: none;
    }
    /* Group title: tight uppercase chip, mirrors .mms-mh-cat but with
     * a leading SVG so each group is recognisable at a glance
     * (clock = Quick Start, line-chart = Indicators, shield =
     * Strategies, library = Libraries). */
    .mobile-more-sheet .mms-yes-tmpl-group__title {
        display: flex;
        align-items: center;
        gap: 8px;
        padding: 8px 18px 4px 50px;
        font-size: 12px;
        font-weight: 600;
        letter-spacing: 0.8px;
        text-transform: uppercase;
        color: var(--text-primary);
        user-select: none;
        pointer-events: none;
    }
    .mobile-more-sheet .mms-yes-tmpl-group__title svg {
        width: 12px;
        height: 12px;
        flex-shrink: 0;
        stroke: currentColor;
        fill: none;
        stroke-width: 1.4;
        stroke-linecap: round;
        stroke-linejoin: round;
        opacity: 0.8;
    }
    /* Template item: 50 px left indent so the icon column lines up
     * with the parent .mms-yes-row__main label, mirroring the Market
     * Hub child rows. Touch target 40 px tall with 14 px font for
     * legibility on phones. */
    .mobile-more-sheet .mms-yes-tmpl-item {
        display: flex;
        align-items: center;
        gap: 10px;
        padding: 10px 18px 10px 50px;
        color: var(--text-primary);
        font-size: 14px;
        text-align: left;
        background: transparent;
        border: none;
        border-bottom: 1px solid var(--border-color);
        width: 100%;
        font-family: inherit;
        min-height: 40px;
        cursor: pointer;
        box-sizing: border-box;
    }
    .mobile-more-sheet .mms-yes-tmpl-item:active {
        background: var(--bg-hover);
    }
    .mobile-more-sheet .mms-yes-tmpl-group:last-child .mms-yes-tmpl-item:last-child {
        border-bottom-color: transparent;
    }

    /* [CHG] 2026-05-19 v3 — section header replacing the previous thin
       <hr> divider. Pattern follows Apple Mail / Slack / Linear / iOS
       Settings: a small-caps uppercase label at the top of each logical
       group inside a bottom sheet. The label has its own background
       chip (slightly darker than the sheet body) so the eye groups the
       items beneath it without needing a horizontal rule.
       Spacing tuned for thumb scrolling: 6px top padding so the first
       header sits flush with the sheet's top edge, 4px bottom padding
       so the header reads as "owned by the items below".
       [CHG] 2026-05-27 — color changed from --text-secondary (#666 light
       / #999 dark) to --text-primary (#000 light / #fff dark) so the
       section headers read as the SAME color as the primary item rows
       below them. The previous secondary-gray failed the contrast
       expectation in light theme — the user reported the labels looked
       "wrong / different from the main menu". Visual hierarchy is now
       carried purely by font-size (11px vs 14px), font-weight (600 vs
       400) and text-transform (uppercase vs sentence-case), the same
       way iOS Settings / Linear differentiate section labels from
       row labels using typography alone (not color). */
    .mobile-more-sheet .mms-section {
        padding: 10px 18px 4px;
        font-size: 11px;
        font-weight: 600;
        letter-spacing: 0.8px;
        text-transform: uppercase;
        color: var(--text-primary);
        background: transparent;
        user-select: none;
        pointer-events: none;
    }
    /* The very first header has slightly tighter top padding so it
       doesn't push the sheet down too far. */
    .mobile-more-sheet .mms-section:first-child {
        padding-top: 8px;
    }
    /* The last item in a group keeps its bottom-border because the next
       sibling is now a section header (transparent bg) — the border now
       reads as "end of group" rather than "between two items in the
       same group". The dedicated last-child rule is no longer needed
       because section headers don't have a bottom-border to double up. */
    .mobile-more-sheet .mms-item:last-child {
        border-bottom-color: transparent;
    }
    /* Drop the bottom-border of the item RIGHT BEFORE a section header
       so the visual break is the header's whitespace, not a line. */
    .mobile-more-sheet .mms-item:has(+ .mms-section) {
        border-bottom-color: transparent;
    }
    .mobile-more-overlay {
        position: fixed;
        inset: 0;
        background: rgba(0, 0, 0, 0.4);
        z-index: 9550;
        display: none;
    }
    .mobile-more-overlay.open {
        display: block;
    }

    /* [CHG] 2026-05-19 v3 — Strategy/PT sub-tabs panel re-layout for mobile.
     *
     * Previous v2: forced .bp-sub-tabs into a single overflow-x:auto lane
     *   containing both the [Sub tabs] + [Actions toolbar]. This broke
     *   THREE things at <=480px:
     *     1. Action dropdown menus (#scNewMenu, #scFilterMenu) are
     *        position:absolute → they get CLIPPED by overflow:hidden|auto
     *        on .bp-sub-tabs and never display. Users reported "New /
     *        All dropdowns don't show on mobile".
     *     2. Search input squeezed to ~80px when 3 sub-tabs + 4 actions
     *        + spacer all share the same row → unusable.
     *     3. The natural reading order (tabs → actions for that tab) was
     *        broken because horizontal scroll mixed them in one strip.
     *
     * Industry-standard pattern (GitHub mobile / Cursor mobile /
     * Discord channel admin / Linear mobile): break the strip into
     * TWO vertical rows on narrow viewports:
     *   Row 1: sub-tabs (My Scripts / Favorites / Community) — horizontal
     *          scroll if needed, no clipping concerns because no children
     *          have absolute-positioned popovers.
     *   Row 2: action toolbar (New ⌄ / Import / Refresh / Search) — full
     *          width, dropdowns now break OUT of any parent overflow
     *          because Row 2 itself doesn't clip (overflow: visible).
     *
     * Vertical stack also matches mobile thumb ergonomics: top row is
     * navigation, bottom row is action context for that tab.
     */
    .bp-sub-tabs {
        /* Two-row vertical-stack via flex-wrap + a full-width spacer to
           break the row. Row 1: sub-tab buttons; Row 2: action toolbar. */
        display: flex;
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
        padding: 4px 8px 6px;
        min-height: auto;
        gap: 4px;
        /* CRITICAL: remove the overflow clipping that hid the popover
           dropdowns. */
        overflow: visible;
    }
    .bp-sub-tabs > .bp-sub-tab {
        flex-shrink: 0;
        padding: 6px 10px;
        font-size: 13px;
        display: inline-flex;
    }
    /* The flex spacer is now a row break: full width forces wrapping
       to Row 2. */
    .bp-sub-tabs > .sc-toolbar-spacer {
        flex-basis: 100%;
        height: 0;
        flex: 0 0 100%;
    }

    /* Row 2: the .sc-actions toolbar. Full-width so the search input can
       breathe. Internal items wrap if very narrow (Galaxy Fold etc.). */
    .bp-sub-tabs > .sc-actions {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 6px;
        width: 100%;
        padding-top: 2px;
        /* Make sure popover dropdowns (#scNewMenu / #scFilterMenu) are
           NOT clipped by any ancestor overflow. */
        overflow: visible;
    }
    /* New / Import / Refresh: keep at natural size, taller for touch. */
    .bp-sub-tabs > .sc-actions .sc-btn {
        padding: 6px 10px;
        font-size: 13px;
        min-height: 32px;
    }
    /* Collapse non-CTA labels (Refresh) to icon-only to save space for
       the search input.
       [CHG] 2026-05-30 v20260530.1 — #scImportBtn selector removed; the
       Import button moved to the editor toolbar so this row no longer
       contains it (虎哥 2026-05-30 12:58). */
    .bp-sub-tabs > .sc-actions #scRefreshBtn span {
        display: none;
    }
    /* Search input: take all remaining space on Row 2 so users actually
       have somewhere to type. Previous 130px max was unusable. */
    .bp-sub-tabs > .sc-actions .sc-search-wrap {
        flex: 1 1 auto;
        max-width: none;
        min-width: 120px;
        min-height: 32px;
    }
    .bp-sub-tabs > .sc-actions .sc-search-wrap input.sc-search {
        font-size: 14px;
        width: 100%;
    }

    /* Make sure the trigger anchors don't have an overflow parent that
       clips a (rare) absolute-positioned popover before JS has had a
       chance to portal it.
       [CHG] 2026-05-30 v20260530.1 — .sc-new-wrap removed (虎哥 2026-05-30
       12:58, New ▾ button deleted). */
    .sc-filter-dropdown {
        overflow: visible !important;
    }

    /* ---- PT order entry (top half of PT split) ---- */
    /* Wallet deposit row: wrap into two rows on mobile so the action
     * buttons remain tappable instead of squeezing into a 30px input. */
    .pt-deposit-row {
        flex-wrap: wrap;
        gap: 6px;
    }
    .pt-deposit-row .pt-deposit-type-btn {
        flex: 1 1 calc(50% - 3px);
        min-height: 36px;
    }
    .pt-deposit-row .pt-deposit-input {
        flex: 1 1 100%;
        order: 3;
    }
    .pt-deposit-row #ptDepositBtn {
        flex: 1 1 calc(50% - 3px);
        order: 4;
    }
    .pt-deposit-row #ptResetAccount {
        flex: 1 1 calc(50% - 3px);
        order: 5;
    }
    /* Wallet panel: when standalone, give it a subtle balance-list grid
     * so it doesn't look like an empty pane on big iPad Pro viewports. */
    .pt-wallet-panel { padding: 12px 0 0; }

    /* ---- YeScript file tabs (Monaco multi-file editor strip) ----
     * [CHG] 2026-05-19 v7 — single scroll container at .yes-toolbar level
     * (see comment above). The script-tabs strip itself is overflow:visible
     * so it flows naturally inside the toolbar's horizontal scroll. */
    .yes-script-tabs {
        overflow: visible;
    }
    .yes-stab {
        flex: 0 0 auto;
        max-width: 160px;
        /* [FIX] 2026-05-19 v7 — relative position lets the close X anchor
         * absolutely to the right edge so it can host a generous tap
         * target without disturbing the stab's flex sizing. Visual X
         * still reads as part of the tab strip. Right padding reserves
         * space for the close glyph so .yes-stab-name doesn't slide
         * underneath. */
        position: relative;
        padding-right: 26px;
    }
    /* No hover on mobile — close X must always be visible so users can
     * actually close tabs. */
    .yes-stab .yes-stab-close,
    .yes-stab:hover .yes-stab-close,
    .yes-stab.active .yes-stab-close {
        opacity: 0.7 !important;
    }
    /* [FIX] 2026-05-19 v7 — close X tap target enlarged.
     * Reason: 16x16 hit area was failing per Apple HIG / Material spec.
     *   Users tapping the X often hit the tab body (which triggers
     *   switchScriptTab) instead of the close handler, so the tab
     *   appeared not to close.
     * Change: absolute-position the close span to span the full 26px
     *   tab height on the right edge, giving a 26x26 tap column with
     *   the X glyph centered inside (font-size bumped to 18px so the
     *   "×" reads clearly at thumb distance). .yes-stab gets
     *   `padding-right: 26px` so the script name doesn't slide under.
     * Warning: paired with the capture-phase click listener in
     *   tt.yescript.js _bindScriptTabEvents (composedPath check) AND
     *   the dispose() no-op in tt.yescript.mobile.js createModel. ALL
     *   three pieces are needed — removing any one re-introduces the
     *   "close X doesn't work on mobile" bug. */
    .yes-stab .yes-stab-close {
        position: absolute;
        right: 0;
        top: 0;
        bottom: 0;
        width: 26px !important;
        height: auto !important;
        margin: 0 !important;
        padding: 0 !important;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 18px;
        font-weight: 500;
        color: var(--text-primary);
        background: transparent;
        z-index: 1;
        border-radius: 4px;
    }
    .yes-stab .yes-stab-close:active {
        background: var(--border-color);
        opacity: 1 !important;
    }
}

/* ---------- 4. Extra-small (max 480px) - phone portrait ---------- */
@media (max-width: 480px) {
    /* Slightly tighter top bar; everything still scrollable */
    .top-bar { height: 40px !important; }
    .top-bar .toolbar-btn { width: 32px; height: 32px; }
    /* Adjust mobile panel host AND all top-bar dropdowns AND drawing drawer
     * to use the 40px top bar height as their anchor. */
    #mobilePanelHost.active {
        top: 40px;
    }
    .strategy-mobile-switcher.open,
    .pt-mobile-switcher.open {
        top: 40px;
    }
    .strategy-mobile-switcher.open ~ #mobilePanelHost.active,
    .pt-mobile-switcher.open ~ #mobilePanelHost.active {
        top: 80px;
    }
    .drawing-tool-dropdown.mobile-drawer-open .drawing-tool-dropdown-menu {
        top: 40px !important;
    }
    .top-bar-left .toolbar-dropdown-menu,
    .top-bar-left .interval-dropdown-menu,
    .top-bar-left .chart-type-dropdown-menu,
    .top-bar-left .ma-dropdown-menu,
    .top-bar-left .sub-ind-dropdown-menu,
    .top-bar-left .heatmap-menu,
    .top-bar-left .fp-menu,
    .top-bar-left .chart-menu-dropdown,
    .top-bar-left .drawing-tool-dropdown-menu,
    .tt-tbl-wrap > .chart-menu-wrap .chart-menu-dropdown {
        top: 40px !important;
    }
    /* Smaller bottom tab labels */
    .mbt-label { font-size: 10px; }
    .mbt-icon { width: 18px; height: 18px; }
    .strategy-mobile-switcher .sms-tab svg {
        display: none;
    }
    .strategy-mobile-switcher .sms-tab {
        gap: 0;
        padding: 0 2px;
        font-size: 11px;
    }

    /* [ADD] 2026-05-19 v8 — Phone-only AI chat bubble narrowing.
     * On phones (<=480 px) we re-introduce the 92% bubble cap so user
     * vs assistant bubbles read with a small left/right indent, helping
     * scan turn-by-turn. iPad / large tablets keep the 100% rule set in
     * the mobile media block above — there's enough horizontal real
     * estate that 92% just leaves wasted gutter. */
    .ai-panel.ai-panel--mobile .ai-msg__bubble,
    .ai-panel.ai-panel--mobile .ai-msg {
        max-width: 92%;
    }
}

/* ---------- 5. Ultrawide (min 1920px) ---------- */
@media (min-width: 1920px) {
    /* Allow wider side panels */
    .middle-panel { max-width: 50vw; }
    .right-panel { max-width: 60vw; }
}

/* ---------- 6. Mobile bottom tab bar styling ---------- */
.mobile-bottom-tabs {
    display: none; /* shown only at <=768px via media query above */
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    height: calc(48px + env(safe-area-inset-bottom, 0px));
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: var(--bg-secondary);
    border-top: 1px solid var(--border-color);
    z-index: 9500;
    flex-direction: row;
    justify-content: space-around;
    align-items: stretch;
    margin: 0;
    -webkit-tap-highlight-color: transparent;
}

.mobile-bottom-tabs .mbt-item {
    flex: 1;
    background: transparent;
    border: none;
    padding: 4px 2px;
    margin: 0;
    color: var(--text-secondary);
    cursor: pointer;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    font-family: inherit;
    font-size: 11px;
    transition: color 0.15s;
}

.mobile-bottom-tabs .mbt-item:active {
    background: var(--bg-hover);
}

.mobile-bottom-tabs .mbt-item.active {
    color: var(--accent-color);
}

.mobile-bottom-tabs .mbt-icon {
    width: 20px;
    height: 20px;
    display: block;
    flex-shrink: 0;
}

.mobile-bottom-tabs .mbt-icon svg {
    width: 100%;
    height: 100%;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.mobile-bottom-tabs .mbt-label {
    line-height: 1;
    white-space: nowrap;
}

/* ---------- 6a. TAI center bottom-tab button (2026-05-21 r5) ----------
 * Design intent:
 *   - 48 px CIRCULAR (border-radius: 50%) raised disc — a real 3D FAB.
 *   - Brand gradient (#2962ff → #e53935) drives the disc face, paired
 *     with an inner radial highlight (top-left light) and a bottom
 *     inner shadow so the disc looks domed, not flat. Two outer drop
 *     shadows project it above the strip. A 2-px strip-coloured border
 *     keeps the rim crisp when the disc pokes above the strip edge.
 *   - Hosts the Tradeiz "TI" monogram (two paths lifted from
 *     /images/favicon-tradeiz.svg, omitting the SVG's black square
 *     background) in pure white, so the monogram reads on the gradient.
 *   - The disc must read as the primary CTA — visibly larger than the
 *     20 px sibling icons and projecting above the strip baseline.
 *   - BOTTOM edge of the disc aligns with the BOTTOM edge of the
 *     neighbouring tab labels (Chart / Markets / Book / More), giving
 *     the whole bottom strip a clean baseline.
 *
 * Layout math (48 px strip; other tabs use flex column + justify-
 * content:center with padding 4px 2px + gap 2px + 20px icon + ~13px
 * label):
 *   - other tab icon top:    ~6 px from strip top
 *   - other tab icon bottom: ~26 px
 *   - other tab label top:   ~28 px
 *   - other tab label bottom:~41 px (target baseline for the disc)
 *
 * Anchor the 48 px disc to that 41 px baseline via bottom: 7px
 * (= 48 - 41). Top edge sits at 41 - 48 = -7px, so the disc pokes
 * 7 px above the strip — the classic FAB silhouette. Tap target is
 * still the full .mbt-item flex slot (flex:1, occupies 1/5 of strip
 * width). */
.mobile-bottom-tabs .mbt-item--tai {
    position: relative;
    overflow: visible;
}
.mobile-bottom-tabs .mbt-item--tai .mbt-icon--tai {
    /* Pulled out of the flex flow so the bottom edge can be anchored
       independently of the parent's justify-content (which centres the
       neighbours vertically). */
    position: absolute;
    left: 50%;
    bottom: 7px;
    transform: translateX(-50%);
    /* 48 px circular disc — 2.4× the 20 px sibling icons. */
    width: 48px;
    height: 48px;
    border-radius: 50%;
    /* Brand gradient: galaxy blue (top-left) → red (bottom-right). The
       inner radial highlight (rgba white) gives a domed-glass look so
       the disc reads 3D, not flat. */
    background:
        radial-gradient(circle at 30% 28%, rgba(255, 255, 255, 0.35) 0%, rgba(255, 255, 255, 0) 55%),
        linear-gradient(135deg, #2962ff 0%, #e53935 100%);
    /* 2-px strip-coloured rim keeps the disc visually detached from the
       strip when it pokes above the top edge — same trick used by
       Telegram / Binance centre FABs. */
    border: 2px solid var(--bg-secondary);
    /* Outer projection: tight close shadow under the disc + wider
       ambient brand-tinted bloom so the disc looks lifted; inner ring
       shadow at the bottom inside the disc faces sells the "domed" 3D. */
    box-shadow:
        0 8px 16px rgba(41, 98, 255, 0.45),
        0 4px 8px rgba(229, 57, 53, 0.35),
        0 2px 4px rgba(0, 0, 0, 0.20),
        inset 0 -3px 6px rgba(0, 0, 0, 0.18),
        inset 0 2px 3px rgba(255, 255, 255, 0.25);
    display: flex;
    align-items: center;
    justify-content: center;
    will-change: transform;
}
.mobile-bottom-tabs .mbt-item--tai .mbt-icon--tai svg {
    /* Monogram ~26 px wide inside the 48 px disc (~54% — generous
       icon-to-button ratio per Material Design FAB spec). The viewBox
       145×110 was chosen so the two TI paths sit centred with even
       padding; setting width fixes the rendered size, height = auto
       so aspect ratio (145:110) is preserved. */
    width: 26px;
    height: auto;
    display: block;
    /* Override .mobile-bottom-tabs .mbt-icon svg (which forces
       fill:none + stroke:currentColor on every other tab icon's stroke
       glyphs). The TI monogram is solid filled.
       [EXCEPTION] white monogram on gradient FAB disc — not --text-primary */
    fill: #ffffff;
    stroke: none;
}
.mobile-bottom-tabs .mbt-item--tai .mbt-icon--tai svg path {
    fill: #ffffff;
    stroke: none;
}
.mobile-bottom-tabs .mbt-item--tai:active .mbt-icon--tai {
    /* Press-down: shrink slightly and dampen the projection so the
       disc visually "settles" into the strip — keeps translateX(-50%)
       so the disc stays centred while scaling. */
    transform: translateX(-50%) scale(0.92);
    box-shadow:
        0 4px 8px rgba(41, 98, 255, 0.35),
        0 2px 4px rgba(229, 57, 53, 0.25),
        0 1px 2px rgba(0, 0, 0, 0.15),
        inset 0 -2px 4px rgba(0, 0, 0, 0.18),
        inset 0 2px 3px rgba(255, 255, 255, 0.25);
    transition: transform 100ms ease, box-shadow 100ms ease;
}

/* ---------- 7. Mobile panel host (default hidden, shown when tab active) ---------- */
#mobilePanelHost {
    display: none;
}

/* Mobile More sheet/overlay default-hidden outside the mobile media query
   so they never appear on tablet/desktop even if JS injected them. */
.mobile-more-sheet,
.mobile-more-overlay {
    display: none;
}

/* Strategy mobile sub-tab switcher — default-hidden outside the mobile
   media query (it is mobile-only UX). Visibility is JS-toggled
   (.open class) only when the user is inside the Strategy pane. */
.strategy-mobile-switcher {
    display: none;
}

/* Paper Trading mobile switcher — default hidden; JS .open on mobile PT flow */
.pt-mobile-switcher {
    display: none;
}

/* ---------- 8. Top bar drawing button ----------
 * Desktop: hidden because .left-toolbar already shows drawing tools.
 * Mobile (≤768px): visible inside the scrollable top-bar-left lane so users
 * can open the full-screen drawing drawer with one tap. */
#mobileDrawBtn {
    display: none;
}
@media (max-width: 768px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    #mobileDrawBtn {
        display: inline-flex;
    }

    /* ---------- YeScript mobile-only optimisations (2026-05-19 v7) ------
     * Rewritten layout: the toolbar now stays on ONE row with horizontal
     * scroll, paired with left/right chevron affordances (TTScrollAffordance)
     * so users know they can swipe to reveal more actions. This matches
     * industry standard mobile toolbars (Chrome tabs, VS Code tab strip,
     * iOS Safari, Telegram chat toolbar).
     *
     * Layout details:
     *   - .yes-toolbar          horizontal flex row, no wrap, overflow auto
     *   - .yes-toolbar-left     [script tabs] + [+ New]
     *   - .yes-toolbar-right    [Check] [Run] [Reset] [Save▾] [Templates▾] [Layout▾]
     *   - Chevrons rendered into <body> by tt.scroll-affordance.js
     *
     * Reason this came back from the v6 two-row layout: user asked for the
     * action buttons to stay on the same row as the script tab strip so
     * they can keep typing/scrolling code without a separate row eating
     * editor real estate. The scroll-affordance chevrons make the now-
     * required horizontal scroll discoverable.
     *
     * All 4 dropdowns (New / Save / Templates / Layout) are still
     * portalled to <body> from tt.yescript.js so they escape the
     * overflow:auto containing block.
     */
    /* [FIX] 2026-05-19 v8 — iOS Safari overlap fix.
     * Symptom: New (+) / Run / Check buttons stack on top of each other
     *   on iPhone. Root cause: iOS Safari's flexbox implementation
     *   defaults child `min-width` to `auto` (per spec) but doesn't
     *   reliably keep flex children at their content size inside an
     *   `overflow-x: auto` parent — children get compressed to width 0
     *   and stack at the same x coordinate.
     * Fix recipe (industry standard for horizontal mobile toolbars):
     *   1. EVERY direct + nested wrap child gets `flex-shrink: 0`
     *      (defeats compression) AND `flex-basis: auto` (size from
     *      content), set via `flex: 0 0 auto`.
     *   2. The scroll container gets `width: 100%` + a min-content
     *      sized inner row, so iOS knows the children's natural width
     *      and renders them side by side.
     *   3. Pre-WebKit `-webkit-overflow-scrolling: touch` for momentum.
     * Verified pattern in Telegram Web / Discord / Material You.
     *
     * [REDESIGN] 2026-05-31 v20260531r2 — split into a 2-zone grid so the
     *   action buttons (Check / Run / Reset / Save / Templates) stay
     *   ANCHORED to the right edge — same as desktop. Previous v8 layout
     *   shoved everything into one flex strip; tabs + buttons mixed on
     *   the left, with no right-anchoring, so the buttons floated next
     *   to the tabs (left-of-screen). 虎哥 review.
     *   • .yes-toolbar          → CSS grid: [left scrollable] | [right fixed]
     *   • .yes-toolbar-left     → owns horizontal scroll for the script tabs
     *   • .yes-toolbar-right    → grid auto-sized, pinned to the right edge
     *   This is the same pattern Telegram chat input / VS Code mobile tab
     *   strip use — overflowing tabs scroll under a fixed action area. */
    .yes-toolbar {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        grid-template-rows: auto;
        align-items: center;
        column-gap: 6px;
        padding: 4px 6px;
        min-height: 44px;
        overflow: visible;
        width: 100%;
        max-width: 100%;
    }
    .yes-toolbar::-webkit-scrollbar { display: none; }

    /* Left zone owns the horizontal scroll affordance. Script tabs flow
     * inside; the inner row keeps `min-width: max-content` so iOS knows
     * the tabs' natural width and renders them side by side. */
    .yes-toolbar-left {
        display: flex;
        flex-direction: row;
        align-items: center;
        flex-wrap: nowrap;
        gap: 4px;
        overflow-x: auto;
        overflow-y: visible;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        scroll-behavior: smooth;
        min-width: 0; /* allow grid track to shrink without overflowing */
    }
    .yes-toolbar-left::-webkit-scrollbar { display: none; }
    .yes-toolbar-left > * {
        flex: 0 0 auto;
        min-width: max-content;
    }

    /* Right zone is pinned to the right edge of the grid (auto column).
     * Buttons inside still stay non-shrinkable so iOS doesn't collapse
     * them to 0 px. */
    .yes-toolbar-right {
        display: flex;
        flex-direction: row;
        align-items: center;
        flex-wrap: nowrap;
        gap: 4px;
        overflow: visible;
    }
    .yes-toolbar-right > * {
        flex: 0 0 auto;
        min-width: max-content;
    }

    .yes-toolbar-left .yes-script-tabs {
        overflow: visible;
        display: flex;
        flex-wrap: nowrap;
        gap: 4px;
    }
    .yes-toolbar-left .yes-stab {
        flex: 0 0 auto;
        max-width: 140px;
    }

    /* Button-level safety net. All icon buttons + the "+" add button
     * get the same shrink lock so a deeply-nested case can't sneak
     * through. */
    .yes-toolbar .yes-icon-btn,
    .yes-toolbar .yes-stab-add,
    .yes-toolbar .yes-save-arrow,
    .yes-toolbar .yes-stab {
        flex: 0 0 auto;
        height: 36px;
    }
    .yes-toolbar .yes-icon-btn.yes-btn-labeled {
        /* Hide the text label; keep the SVG icon visible. */
        padding: 0 10px;
    }
    .yes-toolbar .yes-icon-btn.yes-btn-labeled .yes-btn-text {
        display: none;
    }
    /* Run button is the only accent CTA on the strip. */
    .yes-toolbar .yes-icon-btn.yes-btn-run {
        background: linear-gradient(135deg, #2962ff 0%, #1565c0 100%);
        color: #fff;
        min-width: 44px;
    }
    .yes-toolbar-sep {
        width: 1px;
        height: 18px;
        margin: 0 4px;
        background: var(--toolbar-sep);
        flex: 0 0 auto;
    }
    /* Floating dropdowns portalled to <body>: shared visual treatment so
       the four yescript menus (New / Save / Templates / Layout) read as
       one family on touch devices. Their position is set inline by JS
       (top/left/right + max-height).
       [UPDATE 2026-05-20 r10] consolidated onto --popover-shadow so the
       mobile portalled menus inherit the same elevation as their
       desktop counterparts — was a hard-coded two-tier rgba shadow that
       fought the new token treatment. */
    .yes-dropdown--floating {
        box-shadow: var(--popover-shadow);
        border-radius: 8px;
        min-width: 200px;
    }

    /* [ADD] 2026-05-19 v7 — Scroll affordance chevrons.
     * Portalled into <body> by tt.scroll-affordance.js. Pinned over the
     * left/right inner edges of any horizontally-scrollable strip on
     * mobile to make swipe-able overflow discoverable. Industry standard
     * pattern; see Chrome tabs, VS Code tab strip. Buttons hide automatically
     * when scrollLeft == 0 (left) or scrollLeft == max (right). */
    .tt-scroll-affordance {
        position: fixed;
        width: 22px;
        height: 22px;
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        border: 1px solid var(--popover-border);
        background: var(--popover-bg);
        color: var(--text-primary, #d1d4dc);
        box-shadow: var(--popover-shadow);
        cursor: pointer;
        padding: 0;
        opacity: .9;
        transition: opacity .12s, transform .08s;
        z-index: 50;
    }
    .tt-scroll-affordance:active {
        opacity: 1;
        transform: scale(0.92);
    }
    /* [REMOVED 2026-05-20 r10] light-theme override no longer needed —
     * .tt-scroll-affordance now consumes --popover-bg / --popover-border
     * / --popover-shadow which already collapse to the correct white +
     * soft-grey-border surface in light theme. Hard-coded colours here
     * fought the token system and produced inconsistent affordance
     * styling on light theme. */

    /* Console resizer: 12 px transparent hit area, 2 px visible bar.
     * [CHG] 2026-05-30 v20260530.9 — .yes-dock-top variant removed
     *   together with the Layout button deletion. */
    .yes-dock-bottom .yes-console-resizer {
        height: 12px;
        position: relative;
        background: transparent;
    }
    /* [CHG] 2026-05-24 r33 — mobile inner line uses --resizer-color too;
     * line height stays 2px because the touch target is the 12px parent
     * container (already comfortable for finger drag — thickening the
     * visible line would crowd the cramped mobile layout). */
    .yes-dock-bottom .yes-console-resizer::before {
        content: "";
        position: absolute;
        left: 0; right: 0;
        top: 50%;
        transform: translateY(-50%);
        height: 2px;
        background: var(--resizer-color);
        transition: background .15s;
    }
    .yes-dock-bottom .yes-console-resizer:active::before,
    .yes-dock-bottom .yes-console-resizer.dragging::before {
        background: var(--text-secondary);
    }
    /* Larger grab handle indicator in the middle */
    .yes-dock-bottom .yes-console-resizer::after {
        content: "";
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        width: 36px;
        height: 4px;
        border-radius: 2px;
        background: var(--text-secondary);
        opacity: 0.4;
    }
}

/* ---------- 9. Long-press ripple progress indicator ----------
 * Visual cue that grows under the user's finger while they hold to open
 * the chart context menu. Lets them ABORT before the menu appears by
 * releasing or moving the finger.
 *
 * Lifecycle (driven by tt.app.js _showRipple/_hideRipple):
 *   touchstart           -> element placed at finger, opacity 0 (seed)
 *   +180ms (RIPPLE_VISIBLE_AFTER) -> .is-on added, ring fades in & expands
 *   +700ms (LONG_PRESS_DELAY) -> .is-fired flash for 160ms, then removed
 *   abort (move/release)  -> .is-on removed, ring fades out
 *
 * The ring never blocks input (pointer-events: none).
 */
.tt-longpress-ripple {
    position: fixed;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    pointer-events: none;
    /* Below context menu (10000) so the menu can overlay the ripple's
       fired flash; above the chart canvas / overlays. */
    z-index: 9998;
    opacity: 0;
    transform: scale(0.45);
    border: 2px solid rgba(41, 98, 255, 0.55);
    box-shadow: 0 0 0 2px rgba(41, 98, 255, 0.12) inset;
    background: radial-gradient(circle at center,
                rgba(41, 98, 255, 0.18) 0%,
                rgba(41, 98, 255, 0.04) 60%,
                transparent 100%);
    will-change: transform, opacity;
    transition: opacity 120ms ease-out, transform 120ms ease-out;
}
.tt-longpress-ripple.is-on {
    opacity: 1;
    transform: scale(1);
    /* Duration is provided by JS via --tt-lp-duration so the ring "fills"
       in sync with the LONG_PRESS_DELAY. */
    transition:
        opacity 120ms ease-out,
        transform var(--tt-lp-duration, 520ms) cubic-bezier(.2, .7, .35, 1),
        border-color var(--tt-lp-duration, 520ms) ease-out,
        box-shadow var(--tt-lp-duration, 520ms) ease-out;
    transform: scale(1.15);
    border-color: rgba(41, 98, 255, 0.95);
    box-shadow: 0 0 0 4px rgba(41, 98, 255, 0.22) inset;
}
.tt-longpress-ripple.is-fired {
    transform: scale(1.45);
    opacity: 0;
    transition: opacity 160ms ease-out, transform 160ms ease-out;
}

/* iPad / tablet / mobile only — desktop pointer never sees the ripple. */
@media (hover: hover) and (pointer: fine) {
    .tt-longpress-ripple { display: none !important; }
}
/* ===== [RESPONSIVE-2026-05-14] END ===== */

/* ===== [MARKET-HUB-2026-05-18] Right-toolbar Market Hub =====
 * Four top-level icons (Derivatives / Sentiment / DEX / Platform) pinned to
 * the BOTTOM of the right toolbar via `.mh-spacer { flex:1 }`. Each icon
 * opens a left-floating popover whose contents mirror the home-page top
 * menu groups (header-index.ejs). All sub-links open in a new tab.
 *
 * Sub-item layout: tile grid (icon on top + label below). Icons use the
 * same size as the right-toolbar SVG (--toolbar-icon-size = 32px) so the
 * popover visually echoes the toolbar buttons. */
.right-toolbar .mh-spacer {
    flex: 1 1 auto;
    width: 100%;
    min-height: 8px;
}
.market-hub-wrap {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
}
.market-hub-popover {
    display: none;
    position: absolute;
    right: calc(100% + 6px);
    bottom: 0;
    width: 320px;
    max-height: 80vh;
    overflow-y: auto;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 10px;
    box-shadow: var(--popover-shadow);
    padding: 8px 0 10px;
    z-index: 9999;
}
.market-hub-wrap.open .market-hub-popover { display: block; }
.market-hub-wrap.open .mh-trigger { background: var(--bg-tertiary); }
.mhp-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    padding: 4px 14px 8px;
    border-bottom: 1px solid var(--popover-border);
    margin-bottom: 6px;
}
.mhp-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
}
.mhp-hint {
    font-size: 11px;
    color: var(--text-secondary);
}
.mhp-section {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 4px;
    padding: 4px 8px 6px;
}
.mhp-section + .mhp-section {
    border-top: 1px solid var(--popover-border);
    margin-top: 4px;
    padding-top: 8px;
}
.mhp-category {
    grid-column: 1 / -1;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-secondary);
    padding: 2px 6px 6px;
}
.mhp-row {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: 6px;
    padding: 10px 6px;
    border-radius: 8px;
    text-decoration: none;
    color: var(--text-primary);
    transition: background-color 120ms ease;
}
.mhp-row:hover,
.mhp-row:focus-visible {
    background: var(--bg-hover);
    color: var(--text-primary);
    outline: none;
}
.mhp-row .mhp-ico {
    width: var(--toolbar-icon-size);
    height: var(--toolbar-icon-size);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-primary);
    flex-shrink: 0;
}
.mhp-row .mhp-ico svg {
    width: var(--toolbar-icon-size);
    height: var(--toolbar-icon-size);
    fill: none;
    stroke: var(--text-primary);
}
.mhp-row .mhp-lbl {
    font-size: 11px;
    line-height: 1.2;
    color: var(--text-primary);
    text-align: center;
    word-break: break-word;
}
/* ===== [MARKET-HUB-2026-05-18] END ===== */


/* ===== [AI-ASSISTANT-2026-05-18] BEGIN =====
 * Right-toolbar AI button + docked chat panel + Monaco diff preview +
 * deep-backtest progress + toast notification stack.
 * Color tokens follow the existing chart-page palette (var(--bg-*) /
 * var(--text-*) / var(--accent-*)).
 * ============================================================================= */

/* ── AI toolbar button (sparkle galaxy icon) ────────────────────────────── */
/* [REDESIGN] 2026-05-31 — AI button visual anchor for the right rail (虎哥 review).
 *   Symptoms before: the previous transparent-stroked sparkle (18×18) read
 *     smaller than the neighbouring DM / Bell / News stroke icons (28×28).
 *     The path mass concentrated at the centre while the silhouette stayed
 *     thin, so peripheral glance missed the AI entry entirely.
 *   Strategy: turn the AI button into a 28×28 gradient chip (deep blue → red,
 *     same brand gradient as the AI panel header) with a centred white
 *     sparkle and soft outer glow. The chip becomes the visual anchor of the
 *     entire right rail without breaking the row's stroke icon language —
 *     every other button stays transparent stroke; AI alone wears the chip.
 *     Industry pattern (ChatGPT sidebar, Notion AI chip, Cursor cmd-K). */
.toolbar-btn--ai {
    position: relative;
    overflow: visible;
}
.toolbar-btn__ai-chip {
    width: 28px;
    height: 28px;
    border-radius: 8px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #2962ff 0%, #4a3fd9 55%, #e53935 100%);
    box-shadow:
        0 0 0 1px rgba(255, 255, 255, 0.10) inset,
        0 2px 6px rgba(41, 98, 255, 0.28),
        0 1px 3px rgba(229, 57, 53, 0.18);
    transition: transform 160ms ease, box-shadow 160ms ease, filter 160ms ease;
}
/* The chip is the only chromed element; the inner SVG is decorative — kill
 * the global `.toolbar-btn svg { stroke: currentColor; fill: none }` so the white
 * sparkle stays visible on top of the gradient. We have to override BOTH:
 *   1. `stroke: none` — otherwise the sparkle gains a black outline.
 *   2. [EXCEPTION] Explicit `fill: #fff` on path/circle — white on gradient chip.
 *      `.toolbar-btn svg { fill: none }` rule cascades to our chip svg and
 *      CSS fill outranks the inline `fill="#fff"` SVG presentation attribute
 *      (W3C SVG 1.1 §6.4 "presentation attributes are mapped to a hypothetical
 *      author-style-sheet rule of zero specificity"). Without this override
 *      every shape inside the chip would render transparent. */
.toolbar-btn--ai .toolbar-btn__ai-chip svg,
body.dark-theme .toolbar-btn--ai .toolbar-btn__ai-chip svg,
body.theme--light .toolbar-btn--ai .toolbar-btn__ai-chip svg,
body.light-theme .toolbar-btn--ai .toolbar-btn__ai-chip svg {
    width: 18px;
    height: 18px;
    stroke: none;
    display: block;
}
.toolbar-btn--ai .toolbar-btn__ai-chip svg path,
.toolbar-btn--ai .toolbar-btn__ai-chip svg circle {
    fill: #ffffff;
    stroke: none;
}
.toolbar-btn--ai:hover .toolbar-btn__ai-chip {
    transform: scale(1.06);
    box-shadow:
        0 0 0 1px rgba(255, 255, 255, 0.20) inset,
        0 0 12px rgba(229, 57, 53, 0.55),
        0 2px 8px rgba(41, 98, 255, 0.45);
}
.toolbar-btn--ai.is-active .toolbar-btn__ai-chip {
    box-shadow:
        0 0 0 1px rgba(255, 255, 255, 0.30) inset,
        0 0 0 2px rgba(41, 98, 255, 0.45),
        0 2px 10px rgba(41, 98, 255, 0.55);
}
.toolbar-btn--ai.is-active {
    background: rgba(41, 98, 255, 0.14);
}
/* Light theme: nudge the mid-stop warmer so the chip retains its "hot AI"
 * identity on a white background (cyan mid-stop reads cool against white). */
body.theme--light .toolbar-btn__ai-chip,
body.light-theme .toolbar-btn__ai-chip {
    background: linear-gradient(135deg, #2962ff 0%, #6a3fe0 55%, #e53935 100%);
    box-shadow:
        0 0 0 1px rgba(255, 255, 255, 0.30) inset,
        0 1px 4px rgba(41, 98, 255, 0.22),
        0 1px 3px rgba(229, 57, 53, 0.18);
}

/* ── AI Panel (right-docked drawer) ─────────────────────────────────────── */
/* [FIX] 2026-05-18 — anchor to the real top-bar bottom (40px + 4px border)
   and right-toolbar left edge (45px + 1px border). Earlier values used
   undefined CSS vars with wrong fallbacks (52px), leaving a visible gap
   between the panel and the right toolbar. Width 720px chosen to match
   Cursor/Claude-style side chat panels — wide enough for code blocks +
   diffs without horizontal scroll. */
.ai-panel {
    /* [FIX] 2026-05-18 — Width is now controlled by --ai-panel-w (defaults
       720px) which is mutated by tt.ai.js#startResize when the user drags
       the left edge. Persisted to localStorage so it survives reload.
       Min 320px (compact mode), max calc(100vw - 46px). Drag below 280px
       triggers auto-close (mirrors the market panel UX). */
    --ai-panel-w: 720px;
    position: fixed;
    top: 44px;
    right: 46px;
    bottom: 0;
    width: var(--ai-panel-w);
    min-width: 320px;
    max-width: calc(100vw - 46px);
    background: var(--chart-popup-bg);
    /* [FIX] 2026-05-18 — Dark-theme depth (no border-left).
     *   Old: 1px border-left at `var(--border-color, #2a2e39)`. On the chart's
     *   pure-black `--bg-primary=#000` body the border colour ALSO becomes a
     *   near-black `#0a0a0a` (dark theme override), so the panel had ZERO
     *   visible left edge — looked like the chat was glued to the void on
     *   the right side. Users couldn't tell where the panel started or that
     *   the left edge was draggable.
     *   New approach: REMOVE the border-left entirely and let the always-
     *   visible resize handle (.ai-panel__resize, see below) carry the left
     *   edge visual. The handle now renders a permanent 2px brand-blue grip
     *   so the edge IS the affordance — there's nothing to mistake for
     *   "decorative border that you can't drag". Outer shadow doubled in
     *   intensity to keep the depth cue on pitch-black backgrounds.
     *   Body:not(.dark-theme) override below restores a faint 1px neutral
     *   border for light theme (white panel on white-ish body needs a
     *   subtle edge for accessibility). */
    border: 1px solid var(--chart-popup-border);
    border-left: 0;
    /* [FIX] 2026-05-18 — Rounded top-left + top-right corners on the floating
       AI panel for visual separation from the right toolbar and the top
       toolbar. Bottom corners stay square because the panel docks to the
       viewport bottom. */
    border-top-left-radius: 10px;
    border-top-right-radius: 10px;
    box-shadow:
        -12px 0 28px rgba(0, 0, 0, 0.55);
    display: flex;
    flex-direction: column;
    z-index: 9300;
    transform: translateX(calc(var(--ai-panel-w) + 46px));
    transition: transform 220ms cubic-bezier(0.4, 0, 0.2, 1);
    will-change: transform;
    pointer-events: none;
}
/* Light theme keeps its original chrome (white panel + neutral border +
 * soft drop shadow). The accent left border would clash with the white
 * background, so revert to the pre-fix values. */
body:not(.dark-theme) .ai-panel {
    border: 1px solid var(--chart-popup-border);
    border-left: 0;
    background: var(--chart-popup-bg);
    box-shadow: -8px 0 24px rgba(0, 0, 0, 0.18);
}
.ai-panel.is-open {
    transform: translateX(0);
    pointer-events: auto;
}
/* Disable slide transition while dragging so width changes feel direct. */
.ai-panel.is-resizing { transition: none !important; }

/* Left-edge resize handle. 6px wide, sits flush against the panel edge.
   Cursor turns to ew-resize; the thin accent line only shows on hover
   to keep the panel chrome quiet while idle. */
/* [FIX] 2026-05-18 — Resize handle with QUIET-by-default grip.
 *
 *   First attempt used a 2px brand-blue grip line that read as decorative
 *   chrome and dominated the panel edge (user feedback: "blue line is too
 *   loud"). Cursor / VSCode / Figma all use a near-invisible neutral grip
 *   that only lights up on hover — that's the pattern we adopt here.
 *
 *   Default: 1px subdued grey line (low alpha) — present but visually quiet.
 *   Hover / resizing: line widens, switches to brand blue, brightens.
 *   Hit zone stays 6px so the cursor catches it generously. */
/* [CHG] 2026-05-19 v3 — user reported the always-visible 1px grip on the
   panel's left edge looked like a "thin button" sitting in the top-left
   corner of the header. Industry-standard pattern (VSCode side-bar,
   Cursor right-rail, IntelliJ tool windows): keep the resize hit-zone
   wide so the cursor catches it generously, but make the grip
   completely INVISIBLE at rest. Show the brand-blue glow only while the
   user hovers the edge or actively drags, so the affordance appears
   exactly when it's needed and disappears otherwise. */
/* [FIX] 2026-05-19 — Resize handle: always-visible 1px guide + hover boost.
 * Reason: the previous "invisible at rest, blue glow on hover" pattern made
 *   the panel left edge invisible in dark theme (border-left was removed in
 *   favour of this handle as the only edge cue). Users couldn't see where
 *   the chat panel started, couldn't tell it was draggable, and the dark
 *   chart canvas behind it bled into the chat content with no visible seam.
 * Change: at rest the handle now renders a 1px neutral grip line centred
 *   inside the 6px hit zone (subtle but legible against both pitch-black
 *   and white-ish chart canvases). Hover/dragging widens it to a 2px brand
 *   accent (#2962ff) so the affordance is clearly amplified.
 * Warning: do not return to `background: transparent / background-image: none`
 *   as the rest state — that loses the panel edge entirely on dark theme. */
.ai-panel__resize {
    position: absolute;
    left: -3px;
    top: 10px;   /* skip the rounded top-left corner */
    bottom: 0;
    width: 6px;
    cursor: ew-resize;
    z-index: 2;
    background: transparent;
    transition: none;
    background-image: none;
}
.ai-panel__resize:hover,
.ai-panel.is-resizing .ai-panel__resize {
    background-image: none;
}
/* Light theme: same line, darker tint to keep ≥4.5:1 against the white panel. */
body:not(.dark-theme) .ai-panel__resize {
    background-image: none;
}
body:not(.dark-theme) .ai-panel__resize:hover,
body:not(.dark-theme) .ai-panel.is-resizing .ai-panel__resize {
    background-image: none;
}
/* [FIX] 2026-05-18 — Honor the HTML `hidden` attribute. We DO NOT override it
   with display:flex anymore — that was making the panel render off-screen
   right after page load, occasionally bleeding through when other transforms
   created a stacking context. The drawer becomes visible only after JS
   removes the hidden attribute on user click; closing puts the attribute
   back so the panel is fully removed from the layout. */
.ai-panel[hidden] { display: none !important; }

/* Header */
/* [CHG] 2026-05-19 v3 — LOGO + brand name pinned hard-left.
   Previous 12px left padding produced a visible gutter that read as
   "the title is centered-ish in the header". Industry standard for
   right-rail panels (VSCode Explorer, Cursor AI, Linear right-pane)
   is title flush-left at ~8px from the panel edge, action cluster
   flush-right at the same gutter. */
.ai-panel__header {
    flex: 0 0 auto;
    height: 44px;
    padding: 0 8px 0 8px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    border-bottom: 1px solid var(--chart-popup-border);
    background: var(--chart-popup-bg);
    /* Inherit the panel's top-corner rounding so the header background
       does not draw outside the rounded clip area. */
    border-top-left-radius: 10px;
    border-top-right-radius: 10px;
}
.ai-panel__title {
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--text-primary, #d1d4dc);
    font-size: 14px;
    font-weight: 600;
    /* Ensure title cluster never gets pushed right by sibling actions
       overflowing — flex parent already uses space-between which keeps
       title left and actions right. */
    margin-right: auto;
}
.ai-panel__title-icon {
    width: 18px;
    height: 18px;
}
.ai-panel__actions {
    display: flex;
    align-items: center;
    gap: 6px;
}
.ai-panel__actions-group {
    display: flex;
    align-items: center;
    gap: 4px;
}
.ai-panel__actions-group .ai-panel__head-icon {
    transform: translateX(-5px);
}
/* [FIX] 2026-05-18 — vertical separator between the action cluster
   (New / History / Auto-Accept) and the destructive Close button so they
   read as two distinct groups and a user cannot accidentally click Close
   while reaching for one of the toggles. */
.ai-panel__actions-sep {
    display: inline-block;
    width: 1px;
    height: 18px;
    background: var(--border-color, #2a2e39);
    margin: 0 4px;
}
/* [FIX] 2026-05-18 — AI panel header icon buttons (New / History / Auto-Accept).
 *
 * Reported by user: in chart's light theme the header icon buttons read as
 *   "dark background + black icons, unreadable". Root cause is
 *   `.ai-icon-btn:hover { background: var(--bg-quaternary, #2a2e39) }` — but
 *   `--bg-quaternary` is NOT defined in either the `:root` light theme block
 *   or the `body.dark-theme` block in this file. So the fallback `#2a2e39`
 *   (dark slate) is ALWAYS used, and on light theme that produces a dark
 *   chip with the `:root --text-primary=#000000` black icon on top → ~1.5:1
 *   contrast, well below WCAG AA.
 *
 * Fix: keep the dark-theme look (which was already fine on `#000` panel)
 *   but in `body:not(.dark-theme)` override the hover/pressed/title colours
 *   to a real light-theme palette anchored on chart's own --text-primary
 *   (#000) so the contrast stays > 7:1 against the new light-grey chip. */
/* [CHG] 2026-05-20 v20260520.13 — Header icon buttons (+, History, Close)
 *   are now aligned with the chart top-bar `.top-bar .toolbar-btn` scale
 *   so they no longer look undersized next to the chart's own toolbar
 *   icons. User report: "+ 和历史 图标在桌面 和手机上都太小了."
 *
 *   Dimensions (mirrors `.top-bar .toolbar-btn` exactly):
 *     - container: 32×32 (desktop) / 36×36 (≤768 px or coarse-pointer)
 *                       / 32×32 (≤480 px phone portrait)
 *     - svg:       20×20 across all breakpoints
 *
 *   Colour (mirrors `.toolbar-btn svg` exactly — pure black / pure
 *   white per theme, NOT the previous mid-grey `--text-secondary`):
 *     - light theme: stroke #000000 (uses currentColor → color #000)
 *     - dark  theme: stroke #ffffff (currentColor → color #fff)
 *     - hover: keep the background chip change only; stroke colour
 *       stays put (industry convention — top-bar does the same).
 *
 *   The SVG inside uses stroke="currentColor", so setting `color`
 *   on the button is sufficient — no extra `.ai-icon-btn svg { stroke }`
 *   rule needed. The close-button red palette is layered on top via
 *   `.ai-icon-btn--close` below and is intentionally preserved.
 *
 *   Reason this regression sat unnoticed: the AI panel was added long
 *   after the chart toolbar palette stabilised; the icons were sized
 *   for an old 24px header chip cluster and never re-audited when the
 *   top-bar grew to 32/36 px.
 */
.ai-icon-btn {
    width: 32px;
    height: 32px;
    border: none;
    background: transparent;
    color: var(--text-primary);
    cursor: pointer;
    border-radius: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 150ms ease;
}
.ai-icon-btn:hover {
    background: var(--bg-quaternary, #2a2e39);
}
.ai-icon-btn svg { width: var(--chart-icon-size); height: var(--chart-icon-size); }
/* AI header tool cluster (+ / History / Constraints) — 24px glyph. */
.ai-panel__head-icon {
    width: 28px;
    height: 28px;
    min-width: 28px;
    min-height: 28px;
    padding: 0;
    border-radius: 4px;
    color: var(--text-primary);
}
.ai-panel__head-icon svg {
    width: 24px;
    height: 24px;
    display: block;
}
.ai-panel__head-icon:hover {
    color: var(--text-primary);
}
body:not(.dark-theme) .ai-panel__head-icon {
    color: var(--text-primary, #000000);
}
body.dark-theme .ai-panel__head-icon {
    color: var(--text-primary, #ffffff);
}
body:not(.dark-theme) .ai-panel__head-icon:hover {
    background: rgba(0, 0, 0, 0.06);
}
body.dark-theme .ai-panel__head-icon:hover {
    background: rgba(255, 255, 255, 0.08);
}
.ai-icon-btn--toggle[aria-pressed="true"] {
    background: rgba(38, 166, 154, 0.15);
    color: #26a69a;
}

/* [ADD] 2026-05-19 v2 — Auto-Accept switch (replaces the legacy
   .ai-icon-btn--toggle √ icon button).
   Anatomy: <button.ai-switch> > [.ai-switch__label] + [.ai-switch__track > .ai-switch__knob]
   ARIA: role=switch + aria-checked drives the visual state; aria-pressed
   is kept in sync by tt.ai.js for legacy AT.
   Dimensions tuned to match the 28px tall header chip cluster so the
   right-side actions stay on a single row. */
/* [CHG] 2026-05-20 v20260520.3 — Auto-Accept switch reduced to bare
   text + iOS-style switch on user request:
     "自动接受 文字+开关 不要有边线. 开关需要是蓝色的.
      就是文字+蓝色开关就行了."
   Rationale: the previous chip-with-border / chip-with-hover-fill +
   monochrome track introduced two competing visual weights inside the
   already crowded header strip (six chips: model picker, mode, view,
   auto-accept, history, close). Stripping the chip frame collapses
   the control to the universal pattern "label + native-style switch"
   (Apple Settings / Android Material 3 / macOS Control Center), and
   restoring the brand blue (#2962ff = var(--accent-color)) for the
   ON state matches every other "I am turned on" affordance in the
   product (Apply, Run, primary CTAs), making the state obvious in
   one glance. No chip = nothing to mis-read as a clickable button
   surface separate from the switch itself.
   Anatomy unchanged:
     <button.ai-switch> [.ai-switch__label] [.ai-switch__track > .ai-switch__knob]
   The button stays clickable across its whole hit area so the label
   text still toggles the switch — only its visible border/background
   are removed. */
.ai-switch {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 24px;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--text-secondary, #787b86);
    cursor: pointer;
    font-size: 12px;
    line-height: 1;
    transition: color 150ms ease;
    -webkit-tap-highlight-color: transparent;
}
.ai-switch:hover {
    color: var(--text-primary, #d1d4dc);
    background: transparent;
}
.ai-switch:focus-visible {
    outline: 2px solid var(--accent-color, #2962ff);
    outline-offset: 3px;
    border-radius: 4px;
}
.ai-switch__label {
    font-weight: 500;
    white-space: nowrap;
    user-select: none;
}
/* Track + knob — classic Material/iOS switch geometry, 26x14 px. */
.ai-switch__track {
    position: relative;
    display: inline-block;
    width: 26px;
    height: 14px;
    border-radius: 7px;
    background: var(--text-tertiary, #4b4e57);
    transition: background 150ms ease;
    flex-shrink: 0;
}
.ai-switch__knob {
    position: absolute;
    top: 1px;
    left: 1px;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: #fff;
    transition: transform 150ms ease;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}
/* ON state — brand blue track, white knob (Apple/Material standard). */
.ai-switch[aria-checked="true"] {
    color: var(--text-primary, #d1d4dc);
}
.ai-switch[aria-checked="true"] .ai-switch__track {
    background: var(--accent-color, #2962ff);
}
.ai-switch[aria-checked="true"] .ai-switch__knob {
    transform: translateX(12px);
    background: #ffffff;
}
/* Light theme: brand blue stays identical so the ON affordance is
   consistent across themes; off-state track uses the same neutral
   grey as the dark theme's --text-tertiary projection. */
body:not(.dark-theme) .ai-switch {
    color: #555555;
}
body:not(.dark-theme) .ai-switch:hover {
    color: var(--text-primary);
}
body:not(.dark-theme) .ai-switch__track {
    background: #b0b3bf;
}
body:not(.dark-theme) .ai-switch[aria-checked="true"] {
    color: var(--text-primary);
}
body:not(.dark-theme) .ai-switch[aria-checked="true"] .ai-switch__track {
    background: var(--accent-color, #2962ff);
}
body:not(.dark-theme) .ai-switch[aria-checked="true"] .ai-switch__knob {
    background: #ffffff;
}
/* [CHG] 2026-05-19 v3 — Mobile / PAD: hide the "Auto-Accept" wordmark and
   show only the track+knob switch. Industry standard (Telegram, Discord,
   Linear mobile): binary settings collapse to icon/switch only inside
   the cluttered header strip; tooltip (title attr) survives long-press
   on iOS/Android for discoverability.

   Why class-based instead of @media: tt.ai.js adds .ai-panel--mobile
   when _isMobile() (matchMedia OR window.tt.device.isMobile, OR coarse
   pointer + max-w:1366) returns true. Using the same class in CSS means
   ANY tablet / phone / coarse-pointer device with the mobile JS path
   active also gets the mobile chrome — no "JS says mobile, CSS says
   desktop" mismatch on iPad widths > 768px. */
/* Mobile/tablet: hide the wordmark; only the iOS-style switch remains.
   No padding override needed now that the chip frame is gone — the
   switch is its own self-contained interactive element. */
.ai-panel.ai-panel--mobile .ai-switch {
    height: 28px;
}
.ai-panel.ai-panel--mobile .ai-switch__label {
    display: none;
}
/* @media fallback for first-paint before JS adds .ai-panel--mobile. */
@media (max-width: 768px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    .ai-switch {
        height: 28px;
        font-size: 12px;
    }
    .ai-switch__label {
        display: none;
    }
}

/* [FIX] 2026-05-18 — close button visually marked destructive (red tint).
   At rest a subtle red wash; on hover the full red brand colour. This makes
   the panel-close action distinct from the other neutral toggles, matching
   industry convention (Slack/Notion close-X). */
.ai-icon-btn--close {
    background: rgba(239, 83, 80, 0.14);
    color: #ef5350;
}
.ai-icon-btn--close:hover {
    background: #ef5350;
    color: #fff;
}

/* [FIX] 2026-05-18 — Light-theme overrides for AI panel header.
 *
 * In `body:not(.dark-theme)` the chart palette sets --bg-tertiary=#f5f5f5
 * (very light grey, the header background) and --text-primary=#000000.
 * The default rules above target the dark-theme look and break here for
 * three reasons:
 *   1. .ai-icon-btn:hover uses var(--bg-quaternary, #2a2e39) — variable is
 *      undefined in both themes so the dark slate fallback is always used,
 *      which produces a dark chip with black text in light theme.
 *   2. .ai-icon-btn--toggle[aria-pressed=true] uses #26a69a teal text on
 *      a 15% teal tint — fine on dark, but on #f5f5f5 the tint nearly
 *      disappears so only the teal-on-teal hover state is legible.
 *   3. Title and panel divider use --text-primary / --border-color which
 *      collapse to near-white in light theme; readable but worth pinning.
 *
 * The override below pins each interactive surface to an explicit, WCAG-AA
 * compliant pair scoped to body:not(.dark-theme). Close button keeps its
 * red palette (already legible in both themes) so it's not duplicated. */
body:not(.dark-theme) .ai-panel__header { background: var(--chart-popup-bg); }
body:not(.dark-theme) .ai-panel__title { color: var(--text-primary); }
body:not(.dark-theme) .ai-panel__actions-sep { background: #d1d4dc; }
/* [CHG] 2026-07-22 — Light-theme icon colour uses var(--text-primary)
   to match chart toolbar convention (currentColor on SVG stroke). */
body:not(.dark-theme) .ai-icon-btn { color: var(--text-primary); }
body:not(.dark-theme) .ai-icon-btn:hover {
    background: #e0e3eb;
}
body:not(.dark-theme) .ai-icon-btn--toggle[aria-pressed="true"] {
    background: rgba(38, 166, 154, 0.22);
    color: #0d6c5f;
}
/* [FIX] 2026-05-20 v20260520.13 — Restore the close button's red text
   in light theme. The generic `body:not(.dark-theme) .ai-icon-btn
   { color }` rule above (specificity 0,0,2,0) was beating
   `.ai-icon-btn--close { color: #ef5350 }` (specificity 0,0,1,0) and
   silently neutralising the destructive-action signal — only the
   hover state was actually red. Pin both the resting and hover red
   tints with matching specificity so the close X stays red in BOTH
   themes (the dark-theme defaults are already fine). */
body:not(.dark-theme) .ai-icon-btn--close { color: #ef5350; }
body:not(.dark-theme) .ai-icon-btn--close:hover { color: #ffffff; }

/* [ADD] 2026-05-20 v20260520.13 — Mobile/touch breakpoint mirror of
   `.top-bar .toolbar-btn` (see @media (max-width: 768px) block earlier
   in this file). 36×36 hit target satisfies the 44-pt iOS HIG /
   48-pt Material minimum once paired with the panel header padding.
   SVG stays at 20×20 to keep visual weight identical to top-bar icons. */
@media (max-width: 768px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    .ai-icon-btn:not(.ai-panel__head-icon) { width: 36px; height: 36px; }
    .ai-panel__head-icon {
        width: 28px;
        height: 28px;
        min-width: 28px;
        min-height: 28px;
    }
}
@media (max-width: 480px) {
    .ai-icon-btn:not(.ai-panel__head-icon) { width: 32px; height: 32px; }
}

/* History */
.ai-panel__history {
    flex: 0 0 auto;
    max-height: 35%;
    overflow-y: auto;
    border-bottom: 1px solid var(--chart-popup-border);
    padding: 8px 12px;
    background: var(--chart-popup-bg);
}
.ai-history__item {
    padding: 8px 10px;
    margin-bottom: 4px;
    border-radius: 6px;
    cursor: pointer;
    color: var(--text-primary, #d1d4dc);
    font-size: 12px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    transition: background 120ms ease;
}
.ai-history__item:hover { background: var(--bg-hover); }
.ai-history__item.is-active { background: rgba(41, 98, 255, 0.15); }
.ai-history__title {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ai-history__meta {
    font-size: 12px;
    color: var(--text-tertiary, #6a717b);
    white-space: nowrap;
}
.ai-history__del {
    width: 22px;
    height: 22px;
    border: none;
    background: transparent;
    color: var(--text-tertiary, #6a717b);
    cursor: pointer;
    border-radius: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transition: opacity 120ms ease, color 120ms ease;
}
.ai-history__item:hover .ai-history__del { opacity: 1; }
.ai-history__del:hover { color: var(--accent-red, #ef5350); }
.ai-history__del svg { width: 12px; height: 12px; }
.ai-history__empty {
    text-align: center;
    color: var(--text-tertiary, #6a717b);
    font-size: 12px;
    padding: 16px 8px;
}

/* [ADD] 2026-05-20 — Loading placeholders.
   Reason: previously openConversation() / loadConversationList() would
   wipe the chat area / sidebar to empty during their fetch round-trip
   (200ms - 2s), reading as "the app froze". These two pieces add a
   lightweight spinner in the chat region and skeleton rows in the
   history sidebar so the panel always has visible feedback while the
   network is in flight. Same pattern Cursor / Linear / Slack use
   during a conversation switch. */
.ai-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 32px 16px;
    color: var(--text-secondary, #989fa8);
    font-size: 12px;
}
.ai-loading__spinner {
    width: 14px;
    height: 14px;
    border: 2px solid var(--border-color, #2a2e39);
    border-top-color: var(--text-primary, #d1d4dc);
    border-radius: 50%;
    animation: ai-spin 0.9s linear infinite;
}
.ai-loading__retry {
    margin-left: 4px;
    background: transparent;
    border: 1px solid var(--border-color, #2a2e39);
    color: var(--text-primary, #d1d4dc);
    font-size: 12px;
    padding: 4px 10px;
    border-radius: 4px;
    cursor: pointer;
}
.ai-loading__retry:hover {
    background: var(--bg-quaternary, #2a2e39);
}
.ai-loading--error .ai-loading__label {
    color: var(--accent-red, #ef5350);
}
.ai-history__loading {
    padding: 6px 4px;
}

/* [ADD] 2026-05-20 — Bulk history management toolbar (Select / Clear
   all / Cancel / Delete N). Lives at the top of #aiHistoryList, above
   the actual row list (#aiHistoryListBody). Black-and-white palette
   to match the new neutral Auto-Accept switch — no brand-blue
   accents in the header strip. */
.ai-history__toolbar {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 6px 8px;
    border-bottom: 1px solid var(--border-color, #2a2e39);
    margin-bottom: 6px;
    flex-wrap: wrap;
}
.ai-history__toolbar-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: auto;
}

/* [ADD] 2026-05-21 v20260521.5 — Custom Instructions editor panel.
   Lives between the panel header and the messages list (and above the
   history sidebar). Hidden by default; toggled by #aiInstructionsToggle.
   Visual style matches the history sidebar (same border, padding, bg)
   so the panel feels like a single coherent header strip. */
.ai-panel__instructions {
    flex: 0 0 auto;
    padding: 10px 12px;
    border-bottom: 1px solid var(--chart-popup-border);
    background: var(--chart-popup-bg);
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.ai-panel__instructions[hidden] { display: none; }
.ai-instructions__hint {
    font-size: 12px;
    color: var(--text-secondary, #989fa8);
    line-height: 1.5;
}
.ai-instructions__text {
    width: 100%;
    box-sizing: border-box;
    background: var(--chart-popup-bg);
    color: var(--text-primary, #d1d4dc);
    border: 1px solid var(--chart-popup-border);
    border-radius: 6px;
    padding: 8px 10px;
    font-size: 12px;
    font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
    resize: vertical;
    min-height: 110px;
    max-height: 320px;
    line-height: 1.5;
    transition: border-color 120ms ease, box-shadow 120ms ease;
}
.ai-instructions__text:focus {
    outline: none;
    border-color: #2962ff;
    box-shadow: 0 0 0 2px rgba(41, 98, 255, 0.18);
}
.ai-instructions__bar {
    display: flex;
    align-items: center;
    gap: 10px;
}
.ai-instructions__count {
    font-size: 12px;
    color: var(--text-tertiary, #6a717b);
    flex: 0 0 auto;
}
.ai-instructions__count.is-near-limit { color: var(--accent-orange, #ff9800); }
.ai-instructions__count.is-over-limit { color: var(--accent-red, #ef5350); }
.ai-instructions__actions {
    margin-left: auto;
    display: flex;
    gap: 6px;
}
.ai-instructions__btn {
    font-size: 12px;
    padding: 6px 14px;
    border-radius: 4px;
    border: 1px solid var(--border-color, #2a2e39);
    background: transparent;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    color: var(--text-primary, #d1d4dc);
    cursor: pointer;
    transition: background 120ms ease, border-color 120ms ease;
}
.ai-instructions__btn:hover { background: var(--bg-hover); }
.ai-instructions__btn--primary {
    background: #2962ff;
    color: #ffffff;
    border-color: #2962ff;
}
.ai-instructions__btn--primary:hover {
    background: #1e54e5;
    border-color: #1e54e5;
}
.ai-instructions__btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
/* Button-pressed indicator for the header toggle.
   When the panel is open we light the icon blue to mirror the open
   state — same pattern as the Auto-Accept switch when ON. */
#aiInstructionsToggle[aria-pressed="true"] {
    color: #2962ff; /* [EXCEPTION] product accent for active custom-constraints panel */
    background: rgba(41, 98, 255, 0.12);
}
#aiInstructionsToggle.is-disabled {
    opacity: 0.45;
    cursor: not-allowed;
}
.ai-history__sel-count {
    font-size: 12px;
    color: var(--text-secondary, #989fa8);
    margin-right: 4px;
}
.ai-history__tool-btn {
    height: 22px;
    padding: 0 8px;
    border: 1px solid var(--border-color, #2a2e39);
    background: transparent;
    color: var(--text-secondary, #989fa8);
    border-radius: 4px;
    font-size: 12px;
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.ai-history__tool-btn:hover {
    background: var(--bg-quaternary, #2a2e39);
    color: var(--text-primary, #d1d4dc);
}
.ai-history__tool-btn[hidden] { display: none; }
.ai-history__tool-btn--danger {
    color: var(--accent-red, #ef5350);
    border-color: rgba(239, 83, 80, 0.4);
}
.ai-history__tool-btn--danger:hover {
    background: rgba(239, 83, 80, 0.12);
    color: var(--accent-red, #ef5350);
}
body:not(.dark-theme) .ai-history__tool-btn {
    color: #555555;
    border-color: #d1d4dc;
}
body:not(.dark-theme) .ai-history__tool-btn:hover {
    background: #e0e3eb;
    color: var(--text-primary);
}
body:not(.dark-theme) .ai-history__tool-btn--danger {
    color: #c62828;
    border-color: rgba(198, 40, 40, 0.4);
}

/* Inner list body holds the actual rows. The previous .ai-history__item
   block already styles the rows themselves; this only carries the
   selection-mode tweaks (checkbox visuals + slightly tighter padding
   so the row + checkbox fit on a single line). */
.ai-history__item.is-selectable .ai-history__del { display: none; }
.ai-history__check {
    width: 14px;
    height: 14px;
    border: 1.5px solid var(--text-secondary, #787b86);
    border-radius: 3px;
    flex-shrink: 0;
    margin-left: 8px;
    position: relative;
    transition: background 120ms ease, border-color 120ms ease;
}
.ai-history__check.is-on {
    background: var(--text-primary, #d1d4dc);
    border-color: var(--text-primary, #d1d4dc);
}
.ai-history__check.is-on::after {
    content: '';
    position: absolute;
    left: 3px;
    top: 0px;
    width: 4px;
    height: 8px;
    border: solid var(--bg-primary, #0c0f1a);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}
body:not(.dark-theme) .ai-history__check.is-on {
    background: #000000;
    border-color: #000000;
}
body:not(.dark-theme) .ai-history__check.is-on::after {
    border-color: #ffffff;
}
.ai-history__skel {
    height: 28px;
    border-radius: 6px;
    margin-bottom: 6px;
    background: linear-gradient(
        90deg,
        var(--bg-tertiary, #1c2030) 0%,
        var(--bg-quaternary, #2a2e39) 50%,
        var(--bg-tertiary, #1c2030) 100%
    );
    background-size: 200% 100%;
    animation: ai-skel-shimmer 1.4s ease-in-out infinite;
}
@keyframes ai-skel-shimmer {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* Messages */
.ai-panel__messages {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 12px;
    background: var(--chart-popup-bg);
    color: var(--text-primary, #d1d4dc);
    font-size: 14px;
    line-height: 1.55;
    scrollbar-width: thin;
}
.ai-welcome { padding: 20px 4px; }
.ai-welcome__title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary, #d1d4dc);
    margin-bottom: 4px;
}
.ai-welcome__hint {
    font-size: 12px;
    color: var(--text-secondary, #989fa8);
    margin-bottom: 12px;
    line-height: 1.5;
}
.ai-welcome__chips,
.ai-welcome__group-body {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.ai-welcome__groups {
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.ai-welcome__group-header {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    padding: 4px 0;
    background: none;
    border: none;
    cursor: pointer;
    font-size: 12px;
    color: var(--text-secondary, #989fa8);
    text-align: left;
    line-height: 1.4;
}
.ai-welcome__group-header:hover {
    color: var(--text-primary, #d1d4dc);
}
.ai-welcome__group-header.is-collapsed {
    color: var(--text-secondary, #989fa8);
}
.ai-welcome__chevron {
    display: inline-block;
    width: 0;
    height: 0;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 5px solid currentColor;
    transition: transform 0.15s ease;
    flex-shrink: 0;
}
.ai-welcome__group-header.is-collapsed .ai-welcome__chevron {
    transform: rotate(-90deg);
}
.ai-welcome__group-body {
    margin-top: 4px;
}
.ai-welcome__group-body[hidden] {
    display: none;
}
.ai-chip {
    text-align: left;
    padding: 8px 12px;
    background: var(--chart-popup-bg);
    border: 1px solid var(--chart-popup-border);
    border-radius: 6px;
    color: var(--text-primary, #d1d4dc);
    font-size: 12px;
    cursor: pointer;
    transition: background 120ms ease, border-color 120ms ease;
    line-height: 1.45;
}
.ai-chip:hover {
    background: var(--chart-popup-bg);
    border-color: var(--chart-popup-border);
    color: var(--text-primary, #d1d4dc);
}
body:not(.dark-theme) .ai-chip {
    background: var(--bg-tertiary, #f5f5f5);
    border-color: var(--chart-popup-border);
    color: var(--text-primary);
}
body:not(.dark-theme) .ai-chip:hover {
    background: #e9edf3;
    border-color: var(--chart-popup-border);
    color: var(--text-primary);
}

/* Message bubbles
   [FIX] 2026-05-18 — font sizes locked to the 12 / 14 px whitelist
   (web_app_styling.mdc constraint 2). Role label switched from
   uppercase 10px tracker to a normal-case 12px label per UX request
   ("Tradeiz AI / You" should look like regular text, not a tiny pill). */
.ai-msg {
    margin-bottom: 14px;
    display: flex;
    flex-direction: column;
}
.ai-msg__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 4px;
}
/* [FIX] 2026-05-18 — Per-message hover action row.
   Hidden by default to keep the chat clean; appears on bubble hover. */
.ai-msg__actions {
    display: inline-flex;
    gap: 2px;
    opacity: 0;
    transition: opacity 120ms ease;
}
.ai-msg:hover .ai-msg__actions { opacity: 1; }
.ai-msg__action {
    padding: 2px 6px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 4px;
    color: var(--text-secondary, #989fa8);
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.ai-msg__action:hover {
    background: var(--bg-hover);
    color: var(--text-primary, #d1d4dc);
    border-color: var(--popover-border);
}
.ai-msg__action svg { width: 14px; height: 14px; display: block; }

.ai-msg__role {
    font-size: 12px;
    color: var(--text-tertiary, #6a717b);
    margin-bottom: 0;
    font-weight: 600;
}
.ai-msg--user .ai-msg__role { color: #5b8def; }
.ai-msg--assistant .ai-msg__role { color: #c25fff; }
.ai-msg--tool .ai-msg__role { color: #26a69a; }
.ai-msg__body {
    padding: 10px 12px;
    border-radius: 6px;
    word-wrap: break-word;
    overflow-wrap: break-word;
    white-space: pre-wrap;
    font-size: 14px;
}
.ai-msg__body p,
.ai-msg__reasoning-body p { margin: 0 0 8px; }
.ai-msg__body p:last-child,
.ai-msg__reasoning-body p:last-child { margin-bottom: 0; }
.ai-msg__body ul,
.ai-msg__body ol,
.ai-msg__reasoning-body ul,
.ai-msg__reasoning-body ol {
    margin: 0 0 8px;
    padding-left: 20px;
}
.ai-msg__body li + li,
.ai-msg__reasoning-body li + li { margin-top: 4px; }
.ai-msg--user .ai-msg__body {
    background: var(--chart-popup-bg);
    border: 1px solid var(--chart-popup-border);
}
.ai-msg--assistant .ai-msg__body {
    background: var(--chart-popup-bg);
    border: 1px solid var(--chart-popup-border);
}
body:not(.dark-theme) .ai-msg--user .ai-msg__body {
    background: rgba(41, 98, 255, 0.12);
    border: 1px solid rgba(41, 98, 255, 0.30);
}
body:not(.dark-theme) .ai-msg--assistant .ai-msg__body {
    background: #f6f8fb;
    border: 1px solid var(--chart-popup-border);
}
.ai-msg--tool .ai-msg__body {
    background: rgba(38, 166, 154, 0.08);
    border: 1px solid rgba(38, 166, 154, 0.25);
    font-family: ui-monospace, 'JetBrains Mono', 'Cascadia Code', monospace;
    font-size: 12px;
    color: var(--text-secondary, #989fa8);
    max-height: 200px;
    overflow-y: auto;
}
/* [FIX] 2026-05-19 — Anchor inline `<code>` and block `<pre>` text colour
 * to --text-primary (white in dark theme, black in light theme). Reason:
 * the user reported "code is rendered in green" inside the AI chat panel.
 * Root cause was the inline-diff add-line text colour bleeding into the
 * reader's perception of all code; here we make absolutely sure standard
 * code blocks (NOT diff lines) inherit a neutral high-contrast text colour
 * regardless of any theme stylesheet that loads later. */
.ai-msg__body code,
.ai-msg__reasoning-body code {
    background: rgba(0, 0, 0, 0.30);
    padding: 1px 4px;
    border-radius: 3px;
    font-size: 12px;
    font-family: ui-monospace, 'JetBrains Mono', monospace;
    color: var(--text-primary, #ffffff);
}
.ai-msg__body pre,
.ai-msg__reasoning-body pre {
    background: rgba(0, 0, 0, 0.35);
    padding: 8px 10px;
    border-radius: 6px;
    overflow-x: auto;
    margin: 6px 0;
    font-size: 12px;
    font-family: ui-monospace, 'JetBrains Mono', monospace;
    color: var(--text-primary, #ffffff);
}
.ai-msg__body pre code,
.ai-msg__reasoning-body pre code { background: transparent; padding: 0; color: inherit; }
.ai-msg__body table {
    border-collapse: collapse;
    margin: 6px 0;
    font-size: 12px;
}
.ai-msg__body th, .ai-msg__body td {
    border: 1px solid var(--border-color, #2a2e39);
    padding: 4px 8px;
    text-align: left;
}
.ai-msg__body th { background: var(--bg-quaternary, #2a2e39); }

/* [FIX] 2026-05-18 — Code block wrapper with Copy + Apply (YeScript) header.
   Generated by enhanceCodeBlocks() after each content_delta. */
.ai-msg__body .ai-code {
    margin: 8px 0;
    border: 1px solid var(--border-color, #2a2e39);
    border-radius: 6px;
    overflow: hidden;
    background: var(--bg-primary, #0c0f1a);
}
.ai-msg__body .ai-code__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 4px 8px;
    background: var(--bg-tertiary, #1c2030);
    border-bottom: 1px solid var(--border-color, #2a2e39);
}
.ai-msg__body .ai-code__lang {
    font-family: ui-monospace, 'JetBrains Mono', monospace;
    font-size: 12px;
    color: var(--text-secondary, #989fa8);
    text-transform: uppercase;
    letter-spacing: 0.4px;
}
.ai-msg__body .ai-code__actions { display: flex; gap: 4px; }
/* [FIX] 2026-05-18 — Code-block toolbar buttons (Copy / Apply).
 *
 * Before:
 *   color: var(--text-primary, #d1d4dc);
 *   background: var(--bg-secondary, #131722);
 *   :hover background: var(--bg-quaternary, #2a2e39);
 *
 * Problem (reported by user): in chart's light theme `:root` defines
 *   --bg-secondary: #ffffff, --text-primary: #000000, but `--bg-quaternary`
 *   was NEVER defined anywhere in the codebase → fallback `#2a2e39` (dark
 *   slate) kicked in on `:hover`. The result on a white panel was a
 *   dark-slate button with #000 text that read as nearly invisible.
 *
 * Fix: stop relying on the chart palette variables (which are incomplete
 *   for these accents) and define explicit dark/light tokens scoped to the
 *   AI code-block toolbar. Copy now has a strong contrast colour pair in
 *   both themes; Apply keeps its #2962ff brand identity. */
.ai-msg__body .ai-code__btn {
    padding: 2px 8px;
    border-radius: 4px;
    border: 1px solid rgba(255, 255, 255, 0.18);
    background: rgba(255, 255, 255, 0.06);
    color: #ffffff;
    font-size: 12px;
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.ai-msg__body .ai-code__btn:hover {
    background: rgba(255, 255, 255, 0.16);
    border-color: rgba(255, 255, 255, 0.32);
}
.ai-msg__body .ai-code__btn--apply {
    background: rgba(41, 98, 255, 0.22);
    border-color: rgba(41, 98, 255, 0.5);
    color: #ffffff;
}
.ai-msg__body .ai-code__btn--apply:hover {
    background: #2962ff;
    border-color: #2962ff;
    color: #ffffff;
}
/* Light theme: chart uses `body:not(.dark-theme)`.
 * Per user request — Copy text MUST be white in light theme.
 * So Copy becomes a dark chip on the white panel (charcoal background +
 * white text) instead of a low-contrast white-on-white button. Apply stays
 * a solid #2962ff blue with white text in both themes. */
body:not(.dark-theme) .ai-msg__body .ai-code__btn {
    border: 1px solid #1c1f2a;
    background: #1c1f2a;
    color: #ffffff;
}
body:not(.dark-theme) .ai-msg__body .ai-code__btn:hover {
    background: #2a2e39;
    border-color: #2a2e39;
    color: #ffffff;
}
body:not(.dark-theme) .ai-msg__body .ai-code__btn--apply {
    background: #2962ff;
    border-color: #2962ff;
    color: #ffffff;
}
body:not(.dark-theme) .ai-msg__body .ai-code__btn--apply:hover {
    background: #1c54f0;
    border-color: #1c54f0;
    color: #ffffff;
}
/* Pre inside .ai-code: remove its own borders (header already provides them). */
.ai-msg__body .ai-code pre {
    margin: 0;
    border: none;
    border-radius: 0;
    background: transparent;
}

/* Reasoning (thinking-mode trace) collapsible panel */
.ai-msg__reasoning {
    margin-bottom: 6px;
    border-radius: 6px;
    background: rgba(140, 95, 255, 0.06);
    border: 1px solid rgba(140, 95, 255, 0.20);
    overflow: hidden;
}
.ai-msg__reasoning-toggle {
    width: 100%;
    padding: 6px 10px;
    background: transparent;
    border: none;
    color: #b89aff;
    font-size: 12px;
    cursor: pointer;
    text-align: left;
    display: flex;
    align-items: center;
    gap: 6px;
}
.ai-msg__reasoning-toggle::before {
    content: "▸";
    transition: transform 150ms ease;
}
.ai-msg__reasoning.is-open .ai-msg__reasoning-toggle::before {
    transform: rotate(90deg);
}
.ai-msg__reasoning-body {
    padding: 8px 12px 10px;
    color: var(--text-secondary, #989fa8);
    font-size: 12px;
    line-height: 1.5;
    max-height: 240px;
    overflow-y: auto;
    display: none;
    white-space: pre-wrap;
    border-top: 1px solid rgba(140, 95, 255, 0.15);
}
.ai-msg__reasoning.is-open .ai-msg__reasoning-body {
    display: block;
}

/* [FIX] 2026-05-18 — Tool-call pill is now collapsible.
   Header is a button (icon + name + duration + caret); detail body shows
   args/result JSON only after the user clicks. Cursor parity. */
.ai-toolcall {
    margin: 6px 0;
    border-radius: 6px;
    background: var(--bg-tertiary, #1c2030);
    border: 1px solid var(--border-color, #2a2e39);
    overflow: hidden;
}
.ai-toolcall__header {
    width: 100%;
    padding: 6px 10px;
    background: transparent;
    border: none;
    color: var(--text-secondary, #989fa8);
    font-size: 12px;
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    text-align: left;
}
.ai-toolcall__header:hover { background: rgba(91, 141, 239, 0.08); }
.ai-toolcall__icon {
    width: 14px; height: 14px;
    color: #5b8def;
    flex-shrink: 0;
}
.ai-toolcall__name {
    color: var(--text-primary, #d1d4dc);
    font-weight: 500;
    font-family: ui-monospace, 'JetBrains Mono', monospace;
    font-size: 12px;
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ai-toolcall__duration {
    font-family: ui-monospace, 'JetBrains Mono', monospace;
    font-size: 12px;
    padding: 1px 6px;
    border-radius: 4px;
    background: rgba(91, 141, 239, 0.12);
    color: #8aaef5;
}
.ai-toolcall__caret {
    transition: transform 150ms ease;
    color: var(--text-secondary, #989fa8);
}
.ai-toolcall.is-expanded .ai-toolcall__caret { transform: rotate(180deg); }
.ai-toolcall__detail {
    border-top: 1px solid var(--border-color, #2a2e39);
    padding: 6px 10px;
    background: var(--bg-primary, #0c0f1a);
}
.ai-toolcall__section + .ai-toolcall__section { margin-top: 6px; }
.ai-toolcall__label {
    display: block;
    font-size: 12px;
    color: var(--text-secondary, #989fa8);
    margin-bottom: 2px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}
.ai-toolcall__detail pre {
    margin: 0;
    padding: 6px 8px;
    background: rgba(0, 0, 0, 0.25);
    border-radius: 4px;
    font-family: ui-monospace, 'JetBrains Mono', monospace;
    font-size: 12px;
    line-height: 1.4;
    color: var(--text-primary, #d1d4dc);
    max-height: 200px;
    overflow: auto;
    white-space: pre-wrap;
    word-break: break-word;
}
.ai-toolcall.is-running .ai-toolcall__icon { color: #f7a800; animation: ai-spin 1.1s linear infinite; }
.ai-toolcall.is-done .ai-toolcall__icon { color: #26a69a; }
.ai-toolcall.is-error .ai-toolcall__icon { color: #ef5350; }
@keyframes ai-spin { to { transform: rotate(360deg); } }

/* [FIX] 2026-05-19 — Tool-call GROUP container.
   A single strategy-writing turn can fire 20-30 write/run cycles. Each
   .ai-toolcall pill is ~40 px tall, so the run blew past the ~940 px
   .ai-panel__messages viewport and forced manual scrolling. We wrap the
   pills in an .ai-toolgroup that defaults to collapsed and presents a
   one-line summary ("Tools · 18 calls · 1m 24s") — Cursor / Claude Code
   parity. The inner pills keep their own per-pill expand for args/result. */
.ai-toolgroup {
    margin: 6px 0;
    border-radius: 6px;
    background: var(--bg-tertiary, #1c2030);
    border: 1px solid var(--border-color, #2a2e39);
    overflow: hidden;
}
.ai-toolgroup__header {
    width: 100%;
    padding: 6px 10px;
    background: transparent;
    border: none;
    color: var(--text-secondary, #989fa8);
    font-size: 12px;
    cursor: pointer;
    text-align: left;
    display: flex;
    align-items: center;
    gap: 8px;
    font-family: ui-monospace, 'JetBrains Mono', monospace;
}
.ai-toolgroup__header:hover { background: rgba(91, 141, 239, 0.08); }
.ai-toolgroup__caret {
    color: var(--text-secondary, #989fa8);
    transition: transform 150ms ease;
    flex-shrink: 0;
    width: 12px;
    text-align: center;
}
.ai-toolgroup:not(.is-collapsed) .ai-toolgroup__caret { transform: rotate(90deg); }
.ai-toolgroup__summary {
    color: var(--text-primary, #d1d4dc);
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ai-toolgroup__pills {
    /* Pills inside the group: kill outer margin so they don't double-space
       with the group border; keep a thin top divider between group header
       and first pill. */
    border-top: 1px solid var(--border-color, #2a2e39);
    padding: 4px 6px;
    background: var(--bg-primary, #0c0f1a);
}
.ai-toolgroup__pills .ai-toolcall {
    margin: 4px 0;
    /* Inner pills lose their own outer border to avoid double frames inside
       the group. They still keep the header/detail bands. */
    border: 1px solid var(--border-color, #2a2e39);
    background: var(--bg-tertiary, #1c2030);
}

/* [FIX] 2026-05-19 — Inline diff: neutral code-block presentation.
 *
 * Reason (user report 2026-05-19): the previous Cursor-style red/green tint
 *   pattern was reported as illegible — green text (#7ed3c4 add lines) on a
 *   tinted-green background (rgba(38,166,154,0.12)) gave ~2.4:1 contrast on
 *   the panel's near-black body, well below WCAG AA. Same problem on the
 *   red side. Users saw "alternating red/green stripes with green text"
 *   that obscured the actual code, while the `+` / `-` line prefixes added
 *   noise without aiding comprehension (Accept/Reject already conveys
 *   intent — line-level direction is decorative).
 *
 * Change: render the diff body as a plain monospace code block, exactly
 *   like a `.ai-code` block. Text colour is the panel body colour
 *   (--text-primary), background is the panel surface, no per-line tint,
 *   no line-through, no `+`/`-` text glyphs. The only diff cue we keep is
 *   a 2px coloured left rail (teal/red) — a quiet semantic marker that
 *   stays out of the reader's way.
 *
 * Warning: do NOT reintroduce the red/green stripe backgrounds or the
 *   coloured line text — both fail WCAG AA on chart's pitch-black panel
 *   and chart's white panel respectively, and were the root cause of the
 *   2026-05-19 report. The header "+N / -N" summary is the only place
 *   where the add/del COUNTS may use the brand red/green tokens. */
.ai-diff {
    margin: 8px 0;
    border-radius: 8px;
    border: 1px solid var(--border-color, #2a2e39);
    background: var(--bg-primary, #0c0f1a);
    overflow: hidden;
}
.ai-diff.is-resolved { opacity: 0.7; }
.ai-diff__header {
    padding: 6px 10px;
    border-bottom: 1px solid var(--border-color, #2a2e39);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    background: var(--bg-tertiary, #1c2030);
}
.ai-diff__title {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-primary, #d1d4dc);
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.ai-diff__title-icon {
    width: 12px;
    height: 12px;
    color: #c25fff;
}
.ai-diff__summary {
    font-size: 12px;
    color: var(--text-secondary, #989fa8);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Header summary keeps the add/del semantic colours — those are the brand
 * up/down tokens used everywhere in the chart, applied to a 1-2 char count
 * label not running text, so contrast is not a concern. */
.ai-diff__summary .add { color: #26a69a; }
.ai-diff__summary .del { color: #ef5350; }
/* [ADD] 2026-05-21 — "Applied" badge in the diff header (apply-first flow).
 * Signals to the user that the change is ALREADY live in the editor and
 * that Restore reverts. Uses the brand accent ring so it matches the
 * checkpoint chip elsewhere in the chat. */
.ai-diff__applied-badge {
    display: inline-block;
    padding: 1px 6px;
    border-radius: 3px;
    background: rgba(38, 166, 154, 0.18);
    color: #26a69a;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    vertical-align: middle;
    margin-right: 4px;
}
body:not(.dark-theme) .ai-diff__applied-badge {
    background: rgba(38, 166, 154, 0.12);
    color: #00897b;
}
.ai-diff__body {
    max-height: 320px;
    overflow: auto;
    font-family: ui-monospace, 'JetBrains Mono', monospace;
    font-size: 12px;
    line-height: 1.45;
    color: var(--text-primary, #ffffff);
    background: var(--bg-primary, #000000);
}
.ai-diff__line {
    display: block;
    padding: 0 10px;
    white-space: pre;
    color: inherit;
    background: transparent;
}
/* Add/del lines: only a 2px coloured left rail. No tint, no text colour
 * change, no line-through. Context lines render with a transparent rail of
 * the same width so the indentation aligns. */
.ai-diff__line--add { box-shadow: inset 2px 0 0 #26a69a; }
.ai-diff__line--del { box-shadow: inset 2px 0 0 #ef5350; }
.ai-diff__line--ctx { box-shadow: inset 2px 0 0 transparent; }

/* Light theme: same neutral code-block presentation against a white panel. */
body:not(.dark-theme) .ai-diff__body {
    background: #ffffff;
    color: var(--text-primary);
}

/* [FIX] 2026-05-18 — AI panel light-theme audit (comprehensive).
 *
 * After the header-icon-button fix the user pointed out that many OTHER
 * AI panel widgets still read poorly in light theme. A full audit
 * uncovered these failure modes:
 *
 *   A. Inline `<code>` and `<pre>` blocks paint a 30-35% black overlay
 *      (`rgba(0,0,0,0.30/0.35)`) on top of the panel background. On a
 *      black panel that's a subtle ink mark; on a white panel that's a
 *      DARK GREY block with the inherited black text underneath →
 *      invisible. Same problem inside .ai-toolcall__detail pre.
 *
 *   B. Table headers used var(--bg-quaternary) which has just been
 *      defined as #e0e3eb light-grey. Fine for the chip itself, but
 *      the th text was inheriting --text-primary=#000 — OK now, but
 *      the dark-theme th text was relying on the #2a2e39 fallback so
 *      we re-anchor th color explicitly.
 *
 *   C. .ai-btn (used by Diff Accept / Reject and Checkpoint Restore)
 *      hover background was the undefined --bg-quaternary fallback,
 *      same diagnosis as .ai-icon-btn. Now resolved by the new var,
 *      but anchor the text colour for stronger contrast.
 *
 *   D. Accent text colours tuned for dark surfaces (#b89aff thinking
 *      toggle, #8aaef5 toolcall duration / slash command name) read
 *      as washed-out pastels on white. Switch to deeper shades of the
 *      same hue.
 *
 *   E. .ai-send-btn.is-stop used 18%-red on the dark-theme panel; on
 *      the white panel that becomes a pale pink chip with red text —
 *      still readable but lacks the destructive weight Cursor / TV
 *      conventions expect. Bump the tint and switch to a deeper red.
 *
 *   F. .ai-history__item.is-active was a 15%-blue chip — visible on
 *      black, washed out on white. Bump to 22%-blue + accent border.
 *
 * Each override is scoped to body:not(.dark-theme) so dark theme is
 * untouched. All colours target WCAG AA (4.5:1) or better against
 * the new panel chrome. */

/* (A) Code blocks — flip the inky overlay into a subtle grey wash so the
   inherited black text becomes readable, and keep code legible inside
   tool-call detail panes too. */
body:not(.dark-theme) .ai-msg__body code,
body:not(.dark-theme) .ai-msg__reasoning-body code {
    background: rgba(0, 0, 0, 0.06);
    color: var(--text-primary);
}
body:not(.dark-theme) .ai-msg__body pre,
body:not(.dark-theme) .ai-msg__reasoning-body pre {
    background: #f1f3f7;
    color: var(--text-primary);
}
body:not(.dark-theme) .ai-toolcall__detail pre {
    background: #f1f3f7;
    color: var(--text-primary);
}

/* (B) Tables inside an assistant message — explicit text colour so the
   header chip + body cells stay legible in both themes. */
body:not(.dark-theme) .ai-msg__body th { color: #000000; }
body:not(.dark-theme) .ai-msg__body td { color: #000000; }

/* (C) Diff Accept / Reject / Checkpoint Restore buttons — anchor text
   colour so the rule cascades correctly with the new --bg-quaternary. */
body:not(.dark-theme) .ai-btn { color: #000000; }
body:not(.dark-theme) .ai-btn:hover { color: #000000; }
body:not(.dark-theme) .ai-btn--primary,
body:not(.dark-theme) .ai-btn--primary:hover { color: #ffffff; }

/* (D) Accent text deepened for light surfaces. */
body:not(.dark-theme) .ai-msg__reasoning-toggle { color: #6b3fd6; }
body:not(.dark-theme) .ai-toolcall__duration {
    background: rgba(41, 98, 255, 0.16);
    color: #1c54f0;
}
body:not(.dark-theme) .ai-slash__name { color: #1c54f0; }

/* Tool-call header text + caret on the light-grey pill. */
body:not(.dark-theme) .ai-toolcall__header { color: #555555; }
body:not(.dark-theme) .ai-toolcall__name { color: #000000; }
body:not(.dark-theme) .ai-toolcall__caret { color: #555555; }
body:not(.dark-theme) .ai-toolcall__label { color: #555555; }
body:not(.dark-theme) .ai-toolcall__header:hover { background: #e0e3eb; }

/* Diff title + summary text on the light-grey diff header. */
body:not(.dark-theme) .ai-diff__title { color: #000000; }
body:not(.dark-theme) .ai-diff__summary { color: #555555; }

/* History items + meta. */
body:not(.dark-theme) .ai-history__item { color: #000000; }
body:not(.dark-theme) .ai-history__meta { color: #666666; }
body:not(.dark-theme) .ai-history__empty { color: #666666; }

/* Welcome chips on the empty-conversation panel. */
body:not(.dark-theme) .ai-welcome__hint { color: #555555; }
body:not(.dark-theme) .ai-chip { color: #000000; }

/* User / assistant role labels — darker shade for white background. */
body:not(.dark-theme) .ai-msg--user .ai-msg__role { color: #1c54f0; }
body:not(.dark-theme) .ai-msg--assistant .ai-msg__role { color: #6b3fd6; }

/* (E) Stop button in light theme — bolder pink chip, deeper red glyph. */
body:not(.dark-theme) .ai-send-btn.is-stop {
    background: rgba(239, 83, 80, 0.32);
    color: #b31d28;
}
body:not(.dark-theme) .ai-send-btn.is-stop:hover {
    background: #b31d28;
    color: #ffffff;
}

/* (F) Active-history chip — bumped blue tint + accent left border. */
body:not(.dark-theme) .ai-history__item.is-active {
    background: rgba(41, 98, 255, 0.18);
    box-shadow: inset 2px 0 0 #2962ff;
}

/* Message-action hover (edit / regenerate icons next to each bubble). */
body:not(.dark-theme) .ai-msg__action { color: #555555; }
body:not(.dark-theme) .ai-msg__action:hover {
    background: #e0e3eb;
    color: var(--text-primary);
    border-color: #d1d4dc;
}

/* History per-row delete affordance — keep destructive red even at rest. */
body:not(.dark-theme) .ai-history__del { color: #666666; }
body:not(.dark-theme) .ai-history__del:hover { color: #b31d28; }

.ai-diff__actions {
    padding: 6px 10px;
    border-top: 1px solid var(--border-color, #2a2e39);
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 6px;
    background: var(--bg-tertiary, #1c2030);
}
.ai-diff__status {
    margin-right: auto;
    font-size: 12px;
    color: var(--text-secondary, #989fa8);
}
.ai-diff.is-accepted .ai-diff__status { color: #26a69a; }
.ai-diff.is-rejected .ai-diff__status { color: #ef5350; }
.ai-diff.is-resolved .ai-diff__actions .ai-btn { display: none; }

/* [ADD] 2026-05-19 v7 — Guardrail micro-banner.
   Appears at the TOP of an active assistant bubble when the orchestrator
   short-circuits the ReAct loop due to repetition / no-progress / error
   streak detection. Single-line, low-contrast, auto-dismisses after 6 s.
   Intentionally subtler than .ai-checkpoint so the user reads it as
   "informational" rather than "action required". */
.ai-guardrail-hint {
    margin: 0 0 8px 0;
    padding: 6px 10px;
    border-radius: 6px;
    border: 1px solid rgba(255, 167, 38, 0.35);
    background: rgba(255, 167, 38, 0.10);
    color: #d1d4dc;
    font-size: 12px;
    line-height: 1.4;
}

/* [ADD] 2026-05-19 v7 — Auto-Continue countdown chip.
   Rendered inside the assistant bubble when the orchestrator emits
   budget_exhausted.  Stacks horizontally on desktop, wraps on mobile.
   The Continue button is primary because the default action after
   the countdown IS "continue"; Cancel is ghost-styled because most
   users will let it auto-fire. */
.ai-continue-chip {
    margin: 8px 0 0 0;
    padding: 8px 10px;
    border-radius: 6px;
    border: 1px solid var(--border-color, #2a2e39);
    background: rgba(41, 98, 255, 0.06);
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}
.ai-continue-chip__label {
    flex: 1;
    min-width: 120px;
    font-size: 12px;
    color: var(--text-primary, #d1d4dc);
}
.ai-continue-chip .ai-btn { padding: 4px 10px; font-size: 12px; }
.ai-continue-chip .ai-btn:disabled { opacity: 0.55; cursor: default; }

/* [ADD] 2026-05-20 — Inline "Reconnecting (1/3)…" chip.
   Appears at the bottom of the in-flight assistant bubble whenever the
   SSE stream is being re-established after a transient drop (network
   hiccup / pm2 restart / nginx idle). Cursor-style: small, low-noise,
   one Cancel affordance. Removed automatically as soon as the next
   /chat fetch succeeds. */
.ai-reconnect-chip {
    margin: 6px 0 0 0;
    padding: 6px 10px;
    border-radius: 6px;
    border: 1px solid rgba(247, 168, 0, 0.30);
    background: rgba(247, 168, 0, 0.10);
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    color: var(--text-primary, #d1d4dc);
}
.ai-reconnect-chip__spinner {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    border: 2px solid rgba(247, 168, 0, 0.30);
    border-top-color: #f7a800;
    animation: ai-reconnect-spin 0.9s linear infinite;
    flex: 0 0 auto;
}
@keyframes ai-reconnect-spin {
    to { transform: rotate(360deg); }
}
.ai-reconnect-chip__label {
    flex: 1;
    min-width: 0;
}
.ai-reconnect-chip__cancel {
    padding: 2px 8px;
    font-size: 12px;
    line-height: 1.4;
}
body:not(.dark-theme) .ai-reconnect-chip {
    background: rgba(247, 168, 0, 0.16);
    color: #000;
}
/* The reconnect-failed card reuses .ai-stop-card --errors variant
   styling; no extra rule needed beyond the .ai-reconnect-failed-card
   marker class for diagnostic / future-tuning purposes. */

/* [ADD] 2026-05-20 — Guardrail stop card. Replaces the hard-coded
   English wrap-up text the orchestrator used to stream into content
   when sameToolStreak / no_progress / errors crossed their threshold,
   or when maxToolSteps was hit. Title + short explanation + row of
   action buttons (Retry / Rephrase / [Continue, budget-only]). Tone
   is amber for loop/no_progress/errors (warning) and blue for budget
   (informational continuation prompt). Sits inside .ai-msg__body of
   the same bubble that would have held the wrap-up text. */
.ai-stop-card {
    margin: 4px 0;
    padding: 12px 14px;
    border-radius: 8px;
    border: 1px solid rgba(255, 167, 38, 0.35);
    background: rgba(255, 167, 38, 0.08);
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.ai-stop-card--budget {
    border-color: rgba(41, 98, 255, 0.35);
    background: rgba(41, 98, 255, 0.08);
}
.ai-stop-card--errors {
    border-color: rgba(239, 83, 80, 0.45);
    background: rgba(239, 83, 80, 0.10);
}
.ai-stop-card__title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary, #d1d4dc);
}
.ai-stop-card__msg {
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-primary, #d1d4dc);
    opacity: 0.85;
}
.ai-stop-card__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 2px;
}
.ai-stop-card__btn { padding: 6px 12px; font-size: 12px; }
.ai-stop-card__btn:disabled { opacity: 0.55; cursor: default; }

/* Light-theme contrast tweaks so the amber/red/blue surfaces remain
   readable on a white background. */
body:not(.dark-theme) .ai-stop-card {
    background: rgba(255, 167, 38, 0.14);
    color: #000;
}
body:not(.dark-theme) .ai-stop-card--budget {
    background: rgba(41, 98, 255, 0.12);
}
body:not(.dark-theme) .ai-stop-card--errors {
    background: rgba(239, 83, 80, 0.14);
}
body:not(.dark-theme) .ai-stop-card__title,
body:not(.dark-theme) .ai-stop-card__msg {
    color: #000;
}

/* [FIX] 2026-05-18 — Checkpoint card (inline). Appears after each accepted
   YeScript change. Restore button rolls the editor back to the snapshot. */
.ai-checkpoint {
    margin: 6px 0;
    border-radius: 6px;
    border: 1px solid var(--border-color, #2a2e39);
    background: rgba(38, 166, 154, 0.06);
    padding: 6px 10px;
}
.ai-checkpoint.is-restored { opacity: 0.6; }
.ai-checkpoint__row {
    display: flex;
    align-items: center;
    gap: 8px;
}
.ai-checkpoint__icon {
    width: 14px; height: 14px;
    color: #26a69a;
    flex-shrink: 0;
}
.ai-checkpoint__label {
    flex: 1;
    color: var(--text-primary, #d1d4dc);
    font-size: 12px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ai-checkpoint__restore { padding: 2px 10px; }

/* [FIX] 2026-05-20 — Checkpoint GROUP container (collapsible wrapper).
   When the AI iterates on a strategy it can write 5-15 checkpoints in a
   single turn. Cards used to stack vertically, drowning the actual
   answer. We wrap them in an .ai-cp-group that defaults collapsed,
   header reads "Checkpoints · N · <latest desc>". Same UX model as
   .ai-toolgroup so the user has one consistent interaction across
   sub-turn artifacts (tools + checkpoints). The inner cards are
   unchanged — once expanded each still has its own Restore button. */
.ai-cp-group {
    margin: 6px 0;
    border-radius: 6px;
    background: var(--bg-tertiary, #1c2030);
    border: 1px solid var(--border-color, #2a2e39);
    overflow: hidden;
}
.ai-cp-group__header {
    width: 100%;
    padding: 6px 10px;
    background: transparent;
    border: none;
    color: var(--text-secondary, #989fa8);
    font-size: 12px;
    cursor: pointer;
    text-align: left;
    display: flex;
    align-items: center;
    gap: 8px;
}
.ai-cp-group__header:hover { background: var(--bg-hover); }
.ai-cp-group__caret {
    color: var(--text-secondary, #989fa8);
    transition: transform 150ms ease;
    flex-shrink: 0;
    width: 12px;
    text-align: center;
}
.ai-cp-group:not(.is-collapsed) .ai-cp-group__caret { transform: rotate(90deg); }
.ai-cp-group__icon {
    width: 14px; height: 14px;
    color: #26a69a;
    flex-shrink: 0;
}
.ai-cp-group__summary {
    color: var(--text-primary, #d1d4dc);
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ai-cp-group__items {
    border-top: 1px solid var(--border-color, #2a2e39);
    padding: 4px 6px;
    background: var(--bg-primary, #0c0f1a);
}
.ai-cp-group__items .ai-checkpoint {
    margin: 4px 0;
}
.ai-btn {
    padding: 6px 12px;
    border-radius: 4px;
    border: 1px solid var(--border-color, #2a2e39);
    background: var(--bg-tertiary, #1c2030);
    color: var(--text-primary, #d1d4dc);
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    transition: background 120ms ease, border-color 120ms ease;
}
.ai-btn:hover { background: var(--bg-quaternary, #2a2e39); }
.ai-btn--primary {
    background: #2962ff;
    border-color: #2962ff;
    color: #fff;
}
.ai-btn--primary:hover {
    background: #1c54f0;
    border-color: #1c54f0;
}
.ai-btn--ghost {
    background: transparent;
}

/* Deep backtest progress */
.ai-panel__deep-progress {
    flex: 0 0 auto;
    padding: 10px 12px;
    border-top: 1px solid var(--border-color, #2a2e39);
    background: rgba(247, 168, 0, 0.06);
}
.ai-deep__label {
    font-size: 12px;
    color: #f7a800;
    margin-bottom: 6px;
    font-weight: 500;
}
.ai-deep__bar {
    height: 4px;
    border-radius: 2px;
    background: rgba(247, 168, 0, 0.15);
    overflow: hidden;
}
.ai-deep__fill {
    height: 100%;
    background: linear-gradient(90deg, #f7a800, #ef5350);
    transition: width 250ms ease;
}
.ai-deep__meta {
    font-size: 12px;
    color: var(--text-secondary, #989fa8);
    margin-top: 4px;
}

/* [ADD] 2026-05-21 v6 — Command Queue (Cursor parity).
   Sits between the chat scroll area and the composer. Visible only when
   the user has queued one or more messages while a turn is mid-flight.
   Each row is draggable (HTML5 drag), inline-editable (contenteditable),
   and removable. After the active turn's SSE stream completes, the head
   item is dequeued and auto-sent. See tt.ai.js#queue.
   Theme rules below mirror .ai-panel__composer (same border, same bg). */
.ai-queue {
    flex: 0 0 auto;
    max-height: 168px;             /* ~ 4 rows before scroll */
    overflow-y: auto;
    border-top: 1px solid var(--popover-border);
    background: var(--bg-secondary, #131722);
}
.ai-queue__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 12px 4px;
    color: var(--text-secondary, #989fa8);
    font-size: 12px;
    line-height: 1.2;
}
.ai-queue__title {
    font-weight: 500;
    letter-spacing: 0.02em;
}
.ai-queue__title #aiQueueCount {
    color: var(--text-primary, #d1d4dc);
    font-variant-numeric: tabular-nums;
}
.ai-queue__clear {
    width: 22px;
    height: 22px;
    padding: 0;
    background: transparent;
    border: none;
    border-radius: 4px;
    color: var(--text-secondary, #989fa8);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.ai-queue__clear svg { width: 14px; height: 14px; display: block; }
.ai-queue__clear:hover {
    color: #ef5350;
    background: rgba(239, 83, 80, 0.10);
}
.ai-queue__list {
    list-style: none;
    margin: 0;
    padding: 0 8px 8px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.ai-queue__item {
    display: flex;
    align-items: stretch;
    gap: 6px;
    padding: 6px 8px;
    background: var(--bg-primary, #0c0f1a);
    border: 1px solid var(--border-color, #2a2e39);
    border-radius: 6px;
    transition: border-color 120ms ease, background 120ms ease;
}
.ai-queue__item:hover {
    border-color: rgba(41, 98, 255, 0.45);
}
.ai-queue__item.is-dragging {
    opacity: 0.4;
}
.ai-queue__item.is-drop-target {
    border-color: rgba(41, 98, 255, 0.85);
    background: rgba(41, 98, 255, 0.07);
}
.ai-queue__handle {
    flex: 0 0 14px;
    padding: 2px 0;
    color: var(--text-secondary, #989fa8);
    cursor: grab;
    user-select: none;
    line-height: 1;
    align-self: center;
}
.ai-queue__handle:active { cursor: grabbing; }
.ai-queue__handle svg { width: 12px; height: 12px; display: block; }
/* HTML5 native drag-drop is a no-op on touch devices. We keep the
   handle visible on mobile as a visual cue and rely on edit + remove
   for reordering. Future improvement: add touch drag via pointerdown
   if user feedback requests it. */
.ai-queue__text {
    flex: 1 1 auto;
    min-width: 0;
    padding: 2px 4px;
    color: var(--text-primary, #d1d4dc);
    font-size: 12px;
    line-height: 1.4;
    outline: none;
    word-break: break-word;
    white-space: pre-wrap;
    /* Cap at ~3 visual lines to keep queue compact. Overflow scrolls
       via the outer .ai-queue container. */
    max-height: 60px;
    overflow-y: auto;
}
.ai-queue__text:focus {
    background: rgba(41, 98, 255, 0.06);
    border-radius: 4px;
}
.ai-queue__remove {
    flex: 0 0 18px;
    align-self: center;
    width: 18px;
    height: 18px;
    padding: 0;
    background: transparent;
    border: none;
    color: var(--text-secondary, #989fa8);
    border-radius: 4px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.ai-queue__remove svg { width: 12px; height: 12px; display: block; }
.ai-queue__remove:hover {
    color: #ef5350;
    background: rgba(239, 83, 80, 0.10);
}
/* Light theme overrides: app uses [body:not(.dark-theme)] for light. */
body:not(.dark-theme) .ai-queue { background: #f6f8fc; }
body:not(.dark-theme) .ai-queue__item {
    background: #ffffff;
    border-color: #d8dce4;
}
body:not(.dark-theme) .ai-queue__item:hover { border-color: rgba(41, 98, 255, 0.5); }
body:not(.dark-theme) .ai-queue__text { color: #1f2434; }
body:not(.dark-theme) .ai-queue__header { color: #555555; }
body:not(.dark-theme) .ai-queue__title #aiQueueCount { color: #1f2434; }

/* Composer */
.ai-panel__composer {
    flex: 0 0 auto;
    padding: 10px 12px;
    border-top: 1px solid var(--chart-popup-border);
    background: var(--chart-popup-bg);
    display: flex;
    align-items: flex-end;
    gap: 8px;
    position: relative;
}
/* [FIX] 2026-05-18 — Slash command floater. Anchored above the composer. */
.ai-slash {
    position: absolute;
    left: 12px;
    right: 100px;
    bottom: calc(100% + 4px);
    max-height: 220px;
    overflow-y: auto;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 6px;
    box-shadow: var(--popover-shadow);
    z-index: 10;
}
.ai-slash__item {
    width: 100%;
    padding: 6px 10px;
    background: transparent;
    border: none;
    color: var(--text-primary, #d1d4dc);
    font-size: 12px;
    text-align: left;
    cursor: pointer;
    display: flex;
    align-items: baseline;
    gap: 10px;
}
.ai-slash__item.is-selected,
.ai-slash__item:hover { background: rgba(91, 141, 239, 0.14); }
.ai-slash__name {
    font-family: ui-monospace, 'JetBrains Mono', monospace;
    color: #8aaef5;
    flex-shrink: 0;
}
.ai-slash__desc {
    color: var(--text-secondary, #989fa8);
    font-size: 12px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ai-panel__composer textarea {
    flex: 1 1 auto;
    min-height: 36px;
    max-height: 140px;
    resize: none;
    padding: 8px 10px;
    border-radius: 6px;
    border: none;
    background: #000000;
    color: var(--text-primary, #d1d4dc);
    font-family: inherit;
    font-size: 14px;
    line-height: 1.4;
    outline: none;
    transition: none;
}
.ai-panel__composer textarea:focus {
    border: none;
    box-shadow: none;
}
body:not(.dark-theme) .ai-panel__composer textarea {
    border: 1px solid var(--chart-popup-border);
    background: var(--chart-popup-bg);
    transition: border-color 120ms ease, box-shadow 120ms ease;
}
body:not(.dark-theme) .ai-panel__composer textarea:focus {
    border: 1px solid #2962ff;
    box-shadow: 0 0 0 1px rgba(41, 98, 255, 0.18);
}
.ai-voice-btn {
    width: 36px;
    height: 36px;
    border-radius: 6px;
    border: 1px solid var(--chart-popup-border);
    background: transparent;
    color: var(--text-secondary, #989fa8);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 150ms ease, color 150ms ease, border-color 150ms ease, box-shadow 150ms ease;
    flex-shrink: 0;
    position: relative;
}
.ai-voice-btn:hover {
    color: var(--text-primary, #d1d4dc);
    border-color: rgba(41, 98, 255, 0.45);
    background: rgba(41, 98, 255, 0.08);
}
.ai-voice-btn[disabled] {
    opacity: 0.45;
    cursor: not-allowed;
    box-shadow: none;
}
.ai-voice-btn svg { width: 16px; height: 16px; }
.ai-voice-btn.is-listening {
    color: #ef5350;
    border-color: rgba(239, 83, 80, 0.55);
    background: rgba(239, 83, 80, 0.12);
    animation: ai-voice-pulse 1.2s ease-in-out infinite;
}
@keyframes ai-voice-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(239, 83, 80, 0.35); }
    50% { box-shadow: 0 0 0 6px rgba(239, 83, 80, 0); }
}
body:not(.dark-theme) .ai-voice-btn {
    background: var(--chart-popup-bg);
}
.ai-send-btn {
    width: 36px;
    height: 36px;
    border-radius: 6px;
    border: none;
    background: #2962ff;
    color: #fff;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 150ms ease, transform 150ms ease;
    flex-shrink: 0;
    position: relative;
}
.ai-send-btn:hover { background: #1c54f0; transform: translateY(-1px); }
.ai-send-btn[disabled] { opacity: 0.45; cursor: not-allowed; transform: none; }
.ai-send-btn svg { width: 16px; height: 16px; }
/* [FIX] 2026-05-18 — Stop affordance.
   While streaming, JS adds `.is-stop` to the send button which flips it into
   a red square-icon stop button. Clicking it calls abortController.abort()
   which lets the SSE fetch tear down → backend orchestrator receives the
   `req.raw close` event → cancels upstream model stream + bridge waits.
   Two SVGs are stacked: send is default-visible, stop is shown only with the
   modifier class, so we never repaint inner HTML mid-stream. */
.ai-send-btn__send { display: inline-block; }
.ai-send-btn__stop { display: none; }
.ai-send-btn.is-stop { background: rgba(239, 83, 80, 0.18); color: #ef5350; }
.ai-send-btn.is-stop:hover { background: #ef5350; color: #fff; transform: none; }
.ai-send-btn.is-stop .ai-send-btn__send { display: none; }
.ai-send-btn.is-stop .ai-send-btn__stop { display: inline-block; }
/* [CHG] 2026-05-19 v3 — User reported the white stop square inside the
   red button looked "too small". Root cause: previous v1 forced the
   stop SVG to 12px (vs the send icon's 16px), making the stop affordance
   visually weaker than the send icon it replaces. Industry convention
   (Cursor / Copilot / ChatGPT / Claude composer): the stop icon is the
   SAME size or LARGER than the send icon, because cancelling an
   in-flight stream is a critical user intervention and must be an
   obvious, easy-to-hit target — not a tiny dot.
   Stop SVG is now 18px (vs 16px send) so the filled square reads with
   confident weight inside the 36px button — a 50% icon-to-button ratio
   that matches the Material/iOS notion of "primary action affordance". */
.ai-send-btn.is-stop .ai-send-btn__stop { width: 18px; height: 18px; }

/* [FIX] 2026-05-18 — Typing indicator (three pulsing dots).
 *   Shown inside the empty assistant bubble between turn_start and the first
 *   reasoning_delta / content_delta. Without this the chat felt frozen for
 *   1-5 s while the model buffered hidden chain-of-thought.
 *   Standard 3-dot pattern used by iMessage / WhatsApp / ChatGPT / Slack. */
.ai-typing {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 0;
}
.ai-typing > span {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.45);
    animation: ai-typing-bounce 1.2s infinite ease-in-out both;
}
.ai-typing > span:nth-child(1) { animation-delay: -0.32s; }
.ai-typing > span:nth-child(2) { animation-delay: -0.16s; }
@keyframes ai-typing-bounce {
    0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
    40%           { transform: scale(1);   opacity: 1;   }
}
body:not(.dark-theme) .ai-typing > span { background: rgba(0, 0, 0, 0.45); }

/* ───────────────────────────────────────────────────────────────────────
   AI panel — mobile / touch-tablet full-screen adaptation (2026-05-19)
   Activated via media query (≤768px OR ≤1366px+coarse touch, matching the
   global isMobile breakpoint used by tt.ui.manager.js + .right-toolbar
   hide rule) AND the .ai-panel--mobile class injected by tt.ai.js when
   window.tt.device.isMobile is true. Two-layer guard keeps desktop
   pixel-perfect even at narrow window widths.

   Design: industry-standard mobile AI chat (Claude / ChatGPT app) —
   - Full-screen overlay between top bar (44px) and bottom tab bar (48px)
   - 100% width (no more 92vw with awkward right gutter)
   - Header bumped to 48px with a Back button on the left (≥44×44 touch)
   - Composer textarea font-size 16px (prevents iOS Safari auto-zoom)
   - Resize handle hidden (full-screen has no need to drag-resize)
   - Slide-up + fade-in animation (translateY 20px → 0, opacity 0 → 1)
   ────────────────────────────────────────────────────────────────────── */
@media (max-width: 768px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    .ai-panel.ai-panel--mobile {
        top: 44px;
        left: 0;
        right: 0;
        bottom: calc(48px + env(safe-area-inset-bottom, 0px));
        width: 100%;
        max-width: 100%;
        min-width: 0;
        border-radius: 0;
        border-left: none;
        z-index: 9700; /* above bottom tabs (9500) and More sheet (9600) */
        transform: translateY(20px);
        opacity: 0;
        transition: transform 220ms ease, opacity 220ms ease;
    }
    .ai-panel.ai-panel--mobile.is-open {
        transform: translateY(0);
        opacity: 1;
    }
    /* Full-screen mode has no resize handle */
    .ai-panel.ai-panel--mobile #aiPanelResize { display: none !important; }

    /* [CHG] 2026-05-19 v3 — Mobile header simplified: removed the redundant
       Back '<' button on the left. The right-side X close button serves
       the same dismiss role and keeping both was visual duplication that
       crowded the LOGO + brand name. Header now reads: LOGO+name (left)
       → action chips → X close (right), matching desktop layout. */
    .ai-panel.ai-panel--mobile .ai-panel__header {
        height: 48px;
        /* [FIX] 2026-05-19 v8 — kill the right-side 8 px padding so the
         * Close X chip can sit flush against the viewport edge (Apple HIG
         * + Material both pin destructive close to the absolute edge in
         * full-screen sheets). Left padding stays for the brand title's
         * breathing room. The X button below grows its own padding so the
         * 44×44 tap target is preserved. */
        padding: 0 0 0 8px;
    }

    /* Touch-friendly action buttons in the header (close only; head icons stay 24px) */
    .ai-panel.ai-panel--mobile .ai-icon-btn:not(.ai-panel__head-icon),
    .ai-panel.ai-panel--mobile #aiPanelClose {
        min-width: 44px;
        min-height: 44px;
    }

    /* [FIX] 2026-05-19 v8 — Close X flush-right on mobile.
     * Reason: user reported visible white gap to the right of the X in
     *   full-screen mobile mode. Header had `padding: 0 8px` AND the
     *   button had no edge-pin, so X sat 8 px in from the viewport edge.
     * Change: header right padding now 0 (above); Close X gains a small
     *   internal right padding so the SVG glyph still reads centred but
     *   the chip's right edge IS the viewport edge — the chip extends
     *   into where the gap used to be and is fully tappable to the
     *   corner (matches iOS sheet + Cursor close affordance). */
    .ai-panel.ai-panel--mobile #aiPanelClose {
        padding-right: 12px;
        padding-left: 8px;
    }

    /* [FIX] 2026-05-19 v8 — Bubble + message row fill 100% of panel.
     * Reason: iPad Pro (1024–1366 px wide) hits the mobile media query
     *   but a `max-width: 92%` on .ai-msg / .ai-msg__bubble left a
     *   visible ~80–100 px white strip on the right of the chat. 92%
     *   only makes sense on phones (<480 px) where it preserves a
     *   left-right asymmetry between user / assistant lanes.
     * Change: phone (<=480 px) keeps 92% (see next media block); tablet
     *   + larger mobile width are 100%. .ai-panel__messages already
     *   controls inner padding so the chat still reads with breathing
     *   room — bubble itself goes edge-to-edge inside it. */
    .ai-panel.ai-panel--mobile .ai-msg__bubble,
    .ai-panel.ai-panel--mobile .ai-msg {
        max-width: 100%;
    }
    .ai-panel.ai-panel--mobile .ai-panel__messages {
        padding: 12px;
        font-size: 14px;
    }

    /* Composer: 16px font prevents iOS Safari from auto-zooming on focus.
       min-height 44px gives a proper touch target. extra bottom padding
       handles the safe-area + leaves room for the keyboard accessory bar. */
    .ai-panel.ai-panel--mobile .ai-panel__composer {
        padding: 8px 10px;
        padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
    }
    .ai-panel.ai-panel--mobile .ai-panel__composer textarea {
        font-size: 16px !important;
        min-height: 44px;
        padding: 12px 14px;
        line-height: 1.4;
    }
    .ai-panel.ai-panel--mobile .ai-send-btn {
        min-width: 44px;
        min-height: 44px;
    }

    /* History sub-panel full-width inside the overlay */
    .ai-panel.ai-panel--mobile .ai-panel__history {
        max-height: 50%;
    }

    /* When mobile overlay is open, lock background scroll. Class added by
       tt.ai.js on body so the chart underneath doesn't scroll-through. */
    body.ai-panel-mobile-open {
        overflow: hidden;
        touch-action: none;
    }
}

/* Legacy: keep desktop behaviour intact at <=768px BUT WITHOUT the mobile
   class (some QA paths force narrow viewport without isMobile JS detection).
   Falls back to the old 92vw right-anchored drawer so nothing breaks. */
@media (max-width: 768px) {
    .ai-panel:not(.ai-panel--mobile) {
        right: 0;
        width: 92vw;
        max-width: 92vw;
        top: 44px;
        transform: translateX(100%);
    }
}

/* [FIX] 2026-05-18 — Scrollbars inside the AI panel follow the CHART page
   industry-standard convention: 3px wide, fully transparent at rest, only
   the thumb becomes visible while the user is hovering inside the container.
   Same physical metrics as `.liquidation-body`, `.bp-sub-pane`, `.sc-list`
   et al. so the AI panel does not feel like a foreign UI surface. */
.ai-panel ::-webkit-scrollbar { width: 3px; height: 3px; }
.ai-panel ::-webkit-scrollbar-track { background: transparent; }
.ai-panel ::-webkit-scrollbar-thumb { background: transparent; border-radius: 2px; }
.ai-panel__messages:hover::-webkit-scrollbar-thumb,
.ai-panel__history:hover::-webkit-scrollbar-thumb,
.ai-msg--tool .ai-msg__body:hover::-webkit-scrollbar-thumb,
.ai-msg__reasoning-body:hover::-webkit-scrollbar-thumb,
.ai-diff__body:hover::-webkit-scrollbar-thumb,
.ai-msg__body pre:hover::-webkit-scrollbar-thumb,
.ai-panel__composer textarea:hover::-webkit-scrollbar-thumb { background: #c8ccd8; }
/* Visual hint while user drags the resize handle into the close zone. */
.ai-panel.is-collapse-armed { box-shadow: -8px 0 24px rgba(239, 83, 80, 0.32); }
.ai-panel.is-collapse-armed .ai-panel__resize { background-image: none; }
.ai-panel ::-webkit-scrollbar-thumb:hover { background: #a0a4b0; }
/* Firefox path — thin track + transparent thumb that lights up on hover */
.ai-panel ::-webkit-scrollbar { }
.ai-panel__messages,
.ai-panel__history,
.ai-msg--tool .ai-msg__body,
.ai-msg__reasoning-body,
.ai-diff__body,
.ai-panel__composer textarea { scrollbar-width: thin; scrollbar-color: transparent transparent; }
.ai-panel__messages:hover,
.ai-panel__history:hover,
.ai-msg--tool .ai-msg__body:hover,
.ai-msg__reasoning-body:hover,
.ai-diff__body:hover,
.ai-panel__composer textarea:hover { scrollbar-color: #c8ccd8 transparent; }

/* ── Toast stack ───────────────────────────────────────────────────────── */
#tt-toast-stack {
    position: fixed;
    top: 56px;
    right: 16px;
    z-index: 12000;
    display: flex;
    flex-direction: column;
    gap: 8px;
    pointer-events: none;
}
.tt-toast {
    pointer-events: auto;
    min-width: 240px;
    max-width: 360px;
    padding: 10px 12px;
    border-radius: 6px;
    background: #171c28;
    border: 1px solid #2a3242;
    box-shadow: 0 14px 32px rgba(0, 0, 0, 0.38);
    color: #ecf1fb;
    font-size: 12.5px;
    line-height: 1.45;
    display: flex;
    align-items: flex-start;
    gap: 10px;
    transform: translateX(20px);
    opacity: 0;
    transition: transform 200ms ease, opacity 200ms ease;
}
.theme--light .tt-toast {
    background: #ffffff;
    border-color: #d9dee8;
    box-shadow: 0 12px 28px rgba(15, 23, 42, 0.16);
    color: #111827;
}
.tt-toast.is-visible { transform: translateX(0); opacity: 1; }
.tt-toast.is-leaving { transform: translateX(20px); opacity: 0; }
.tt-toast--info    { border-left: 3px solid #2962ff; background: #132a4e; border-color: #1f4f96; color: #e8f1ff; }
.tt-toast--success { border-left: 3px solid #26a69a; background: #103c35; border-color: #1f756a; color: #d9fff8; }
.tt-toast--warn    { border-left: 3px solid #f7a800; background: #3f2d05; border-color: #8b6408; color: #ffe8b4; }
.tt-toast--error   { border-left: 3px solid #ef5350; background: #4a1b1d; border-color: #8f3f43; color: #ffd9da; }
.theme--light .tt-toast--info    { background: #eaf2ff; border-color: #9ec0ff; color: #0f2f62; }
.theme--light .tt-toast--success { background: #e8fff7; border-color: #8fd7c7; color: #0f4f43; }
.theme--light .tt-toast--warn    { background: #fff4db; border-color: #e6c073; color: #6a4a00; }
.theme--light .tt-toast--error   { background: #ffe9ea; border-color: #e09ca0; color: #711a1e; }
.tt-toast__msg { flex: 1 1 auto; }
.tt-toast__close {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    border: none;
    background: transparent;
    color: var(--text-tertiary, #6a717b);
    cursor: pointer;
    border-radius: 3px;
    font-size: 16px;
    line-height: 1;
    padding: 0;
    transition: color 120ms ease, background 120ms ease;
}
.tt-toast__close:hover { color: var(--text-primary, #d1d4dc); background: rgba(255, 255, 255, 0.06); }
/* [ADD] 2026-05-19 v3 — actionable toast CTA (Material Snackbar pattern).
 *   Used by AI write_strategy_to_editor success path so the user gets a
 *   one-tap "Open Editor" jump straight into the YeScript pane. */
.tt-toast__action {
    flex: 0 0 auto;
    border: none;
    background: transparent;
    color: var(--text-accent, #2962ff);
    cursor: pointer;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    padding: 4px 8px;
    border-radius: 4px;
    transition: background 120ms ease;
}
.tt-toast__action:hover { background: rgba(41, 98, 255, 0.10); }
.tt-toast__action:active { background: rgba(41, 98, 255, 0.18); }
/* ===== [AI-ASSISTANT-2026-05-18] END ===== */

/* ===== [AI-COMPLIANCE-2026-05-22] BEGIN =====
 * AI panel compliance widgets:
 *   1. .ai-panel__disclaimer  — always-visible single-line disclaimer pinned
 *                               below the composer inside .ai-panel.
 *   2. .ai-first-use-modal     — first-open / 30-day-recurring modal that
 *                                spells out the no-investment-advice posture
 *                                in detail (rendered at <body> root level,
 *                                outside .ai-panel).
 * Both surfaces draw labels from data-i18n keys (ai.disclaimer.*,
 * ai.firstUseModal.*) and follow the chart's existing dark surface tokens
 * (var(--popover-bg)/--popover-border) so they sit naturally next to the
 * existing composer and AI panel chrome.
 */
.ai-panel__disclaimer {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 8px 14px;
    border-top: 1px solid var(--chart-popup-border);
    background: var(--chart-popup-bg);
    color: var(--text-tertiary, #9598a1);
    font-size: 11.5px;
    line-height: 1.35;
}
.ai-panel__disclaimer-main {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}
.ai-panel__disclaimer-icon {
    flex: 0 0 auto;
    width: 14px;
    height: 14px;
    color: var(--text-tertiary, #9598a1);
    opacity: .85;
}
.ai-panel__disclaimer-text {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ai-panel__disclaimer-link {
    flex: 0 0 auto;
    color: var(--text-accent, #2962ff);
    text-decoration: none;
    border-bottom: 1px dashed transparent;
    transition: border-color 120ms ease, color 120ms ease;
}
.ai-panel__disclaimer-link:hover {
    color: var(--text-accent, #2962ff);
    border-bottom-color: var(--text-accent, #2962ff);
}
.ai-panel__billing {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 8px;
}
.ai-panel__balance {
    color: var(--text-secondary, #b7bcc8);
    font-size: 12px;
    white-space: nowrap;
}
@media (max-width: 860px) {
    .ai-panel__disclaimer {
        flex-direction: column;
        align-items: flex-start;
    }
}
@media (max-width: 720px) {
    .ai-panel__disclaimer { padding: 6px 12px; font-size: 11px; }
    .ai-panel__disclaimer-text { white-space: normal; }
}

/* ----- First-use modal ----- */
.ai-first-use-modal {
    position: fixed;
    inset: 0;
    z-index: 1090;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
.ai-first-use-modal[hidden] { display: none !important; }
.ai-first-use-modal__backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, .55);
    backdrop-filter: blur(2px);
}
.ai-first-use-modal__card {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: 560px;
    max-height: calc(100vh - 48px);
    overflow: auto;
    background: var(--popover-bg, #1e222d);
    border: 1px solid var(--popover-border, #2a2e39);
    border-radius: 12px;
    box-shadow: 0 20px 60px rgba(0, 0, 0, .55);
    padding: 24px 26px 20px;
    color: var(--text-primary, #d1d4dc);
}
.ai-first-use-modal__title {
    margin: 0 0 8px;
    font-size: 14px;
    font-weight: 700;
    color: var(--text-primary, #d1d4dc);
}
.ai-first-use-modal__lead {
    margin: 0 0 14px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--text-secondary, #b2b5be);
}
.ai-first-use-modal__list {
    list-style: none;
    margin: 0 0 14px;
    padding: 0;
}
.ai-first-use-modal__list > li {
    position: relative;
    padding: 8px 0 8px 22px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--text-secondary, #b2b5be);
    border-top: 1px solid var(--popover-border, #2a2e39);
}
.ai-first-use-modal__list > li:first-child { border-top: none; }
.ai-first-use-modal__list > li::before {
    content: '';
    position: absolute;
    left: 4px;
    top: 17px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--text-accent, #2962ff);
    opacity: .85;
}
.ai-first-use-modal__list strong {
    display: inline-block;
    margin-right: 4px;
    color: var(--text-primary, #d1d4dc);
    font-weight: 600;
}
.ai-first-use-modal__links {
    margin: 6px 0 16px;
    font-size: 12px;
    color: var(--text-tertiary, #9598a1);
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
}
.ai-first-use-modal__links a {
    color: var(--text-accent, #2962ff);
    text-decoration: none;
}
.ai-first-use-modal__links a:hover { text-decoration: underline; }
.ai-first-use-modal__footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    border-top: 1px solid var(--popover-border, #2a2e39);
    padding-top: 14px;
}
.ai-first-use-modal__hint {
    margin: 0;
    font-size: 12px;
    color: var(--text-tertiary, #9598a1);
    flex: 1 1 auto;
}
.ai-first-use-modal__btn {
    flex: 0 0 auto;
    appearance: none;
    border: none;
    border-radius: 8px;
    padding: 8px 14px;
    background: var(--text-accent, #2962ff);
    color: #ffffff;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: background 120ms ease;
}
.ai-first-use-modal__btn:hover { background: #1e4dd1; }
.ai-first-use-modal__btn:focus-visible {
    outline: 2px solid #ffffff;
    outline-offset: 2px;
}
@media (max-width: 600px) {
    .ai-first-use-modal { padding: 12px; }
    .ai-first-use-modal__card { padding: 18px 18px 14px; max-height: calc(100vh - 24px); }
}
/* ===== [AI-COMPLIANCE-2026-05-22] END ===== */

/* ===== [PANE-SEPARATOR-HITAREA-2026-05-22] BEGIN =====
 * Slim the lightweight-charts sub-pane resize hit area.
 *
 * Root cause:
 *   The bundled lightweight-charts library (web_app/public/js/lib/
 *   lightweight-charts.standalone.production.js) injects a hit-area div
 *   above every pane separator with inline styles:
 *     position: absolute; z-index: 50; top: -4px; height: 9px;
 *     width: 100%; cursor: row-resize;
 *   That makes the draggable cursor zone 9px tall — 4px above and 4px
 *   below the 1px visible separator. Users perceive this as "the line
 *   gets thick when I hover" even though the visible line stays 1px:
 *   anywhere inside the 9-pixel band turns the cursor into ↕ row-resize
 *   and starts a potential drag.
 *
 * Why not patch the bundle:
 *   The values are hardcoded into the minified vendor file. Patching it
 *   would not survive the next lightweight-charts upgrade and we lose
 *   the library's official build provenance.
 *
 * Fix:
 *   The hit-area div has no class but its inline styles are unique
 *   enough to target by attribute selector: it is an absolute child of
 *   the pane's <td>, has `cursor: row-resize` AND height `9px`. CSS
 *   `!important` is allowed to override inline styles (per the cascade),
 *   so we narrow the hit area to 5px (2px above + 1px line + 2px below).
 *   This matches TradingView desktop and Cursor's own pane splitter
 *   feel, while still being draggable on touch.
 *
 * Warning:
 *   If lightweight-charts ever changes the inline `height` value or
 *   stops using `row-resize` (e.g. switches to `ns-resize`), this rule
 *   silently stops working. Re-verify after any LWC upgrade by hovering
 *   a sub-pane boundary in DevTools and inspecting the .style.height of
 *   the hit-area div.
 */
.chart-container td > div[style*="row-resize"][style*="height: 9px"] {
    top: -2px !important;
    height: 5px !important;
}
/* ===== [PANE-SEPARATOR-HITAREA-2026-05-22] END ===== */

/* ===== [SYMBOL-INSIGHTS-TAIL-2026-05-22] ===== */
/* Chart-page Symbol Insights tail styles.
 *
 * Scope: only `.symbol-insights-tail` (the merged tail inside the
 * Symbol Details module) and descendants (`.si-*` classes). This block
 * is intentionally NOT covered by web_app_styling.mdc — chart.ejs is the
 * "app.css 自管" zone and uses its own theme tokens (--text-primary /
 * --text-secondary / --price-up / --price-down / --border-color), not
 * the --tt-fg-* variables that govern home/aggregations pages.
 *
 * 2026-05-22 — restructured from independent dock module to a tail
 * section appended to #symbolDetailPanel. The previous Panel B dedicated
 * module duplicated funding-rate / OI / L-S which were already rendered
 * by symbol-details controller; the tail now extends those metrics with
 * countdown / basis / cross-ex / OI dominance / whale KPI + tape / mini
 * sparklines / liquidation levels.
 *
 * Composition rules:
 *   · All text colours fall back to --text-primary / --text-secondary.
 *   · Price up/down = --price-up / --price-down (consistent with rest
 *     of chart-page surfaces such as Symbol Detail and Orderbook).
 *   · No hardcoded hex outside of borders/shadows.
 *   · Backgrounds defer to the panel's own surface; only subtle hover
 *     tints are introduced to mark interactive rows.
 */
.symbol-insights-tail {
    margin-top: 10px;
    padding-top: 10px;
    border-top: none;
}

.si-body {
    display: flex;
    flex-direction: column;
    gap: 6px;
    overflow: visible;
}

/* Row 1 — Countdown + Basis side-by-side */
.si-row-2col {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 6px;
}
.si-card {
    border: 1px solid var(--border-color);
    border-radius: 4px;
    padding: 8px 10px;
    background: transparent;
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-height: 64px;
}
.si-card-title {
    font-size: 12px;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.si-card-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.si-countdown-time {
    font-size: 14px;
    color: var(--text-primary);
    font-family: var(--font-price);
    letter-spacing: 0.03em;
}
.si-countdown-meta,
.si-basis-meta {
    display: flex;
    align-items: baseline;
    gap: 6px;
}
.si-meta-label {
    color: var(--text-secondary);
    font-size: 12px;
}
.si-meta-value {
    color: var(--text-primary);
    font-family: var(--font-price);
    font-size: 14px;
}
.si-basis-value {
    font-size: 14px;
    color: var(--text-primary);
    font-family: var(--font-price);
}
.si-basis-value.up { color: var(--price-up); }
.si-basis-value.down { color: var(--price-down); }
.si-basis-value.neutral { color: var(--text-primary); }

/* Row 2 — 24h Whale KPI strip */
.si-kpi-strip {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0;
    border: 1px solid var(--border-color);
    border-radius: 4px;
    overflow: hidden;
}

/* [ADD] 2026-05-24 r14 — Symbol Insights 4-tab navigation.
   Industry pattern: Binance / Coinglass / OKX all use a 4–5 tab strip on
   their symbol-detail panels to group ~15 metric cards into themes. This
   replaces a single linear scroll of 14 unrelated cards. The always-visible
   blocks above (symbol header / price / 24h grid / futures metrics / Whale
   KPI) live OUTSIDE the tab system — they are the "instant snapshot" and
   are intentionally not hidden behind a tab.
   Mobile: horizontal scroll (Binance App style) — never wraps to 2 lines
   because a 2-line tab bar in a docked module would steal too much height.
   Active tab indicator: 2px bottom border in the brand-accent color so it
   reads as "currently selected" without needing a fill swap.
   Warning: do NOT add a fill background to the active tab — the docked
   module already has a tinted background and the fill swap renders muddy
   in the dark theme. Stay with the border-bottom indicator. */
.si-tabs {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    border-bottom: 1px solid var(--border-color);
    margin: 4px 0 6px;
    gap: 0;
    /* [EXCEPTION] web_app_styling.mdc rule 4 default behaviour is "scrollbar
       hidden, visible on hover". The tab bar is a *navigation* element, not
       a content list — a scrollbar appearing under the tabs (even on hover)
       reads as visual noise. Permanently hiding the horizontal scrollbar
       here is the industry pattern (Binance App, Coinglass mobile, OKX
       futures detail) and is explicitly opted in. Users still scroll via
       touch-swipe (mobile) or trackpad/shift-wheel (desktop). */
    scrollbar-width: none;
}
.si-tabs::-webkit-scrollbar { display: none; }
.si-tab {
    flex: 0 0 auto;
    padding: 6px 12px;
    background: transparent;
    border: none;
    border-bottom: 2px solid transparent;
    color: var(--tt-fg-0);
    opacity: 0.7;
    font-size: 12px;
    font-weight: 500;
    line-height: 1.3;
    cursor: pointer;
    white-space: nowrap;
    transition: opacity 0.12s ease, border-color 0.12s ease;
}
.si-tab:hover { opacity: 1; }
.si-tab--active {
    opacity: 1;
    border-bottom-color: #2962ff;
    font-weight: 600;
}
.si-tab:focus-visible {
    outline: 1px solid #2962ff;
    outline-offset: -1px;
}
/* Tab pane visibility — [FIX] 2026-05-24 r17
   `.si-mini-row` / `.si-section` / `.si-row-2col` each set their own
   `display` LATER in this file with the same (0,1,0) specificity as
   `.si-tab-pane { display:none }`, so inactive panes stayed visible
   (tab buttons switched but content did not). Inactive panes MUST use
   `:not(.si-tab-pane--active)` + `!important` to beat layout rules.
   Active panes restore the correct display mode per component class.
   Optional API-empty sections: `data-si-empty="1"`. No inline style.display
   on tab panes — that breaks tab switching. */
.si-tab-pane:not(.si-tab-pane--active),
.si-tab-pane[data-si-empty="1"] {
    display: none !important;
}
.si-tab-pane.si-tab-pane--active:not([data-si-empty="1"]) {
    display: block;
}
.si-section.si-tab-pane.si-tab-pane--active:not([data-si-empty="1"]) {
    display: flex;
}
.si-mini-row.si-tab-pane.si-tab-pane--active:not([data-si-empty="1"]) {
    display: grid;
}
.si-row-2col.si-tab-pane.si-tab-pane--active:not([data-si-empty="1"]) {
    display: grid;
}
/* Spot mode visibility — [ADD] 2026-05-24 r21 / [CHANGE] r22
   When the active symbol is spot, tt.symbol-insights.js adds
   `si-spot-mode` to <body>. Every Symbol Insights block marked
   `data-si-market="futures"` becomes display:none. r22 expansion:
   the Liquidation tab + its three "global liquidation" cards are
   now also futures-only (liquidations are a perp-only concept;
   spot can't be liquidated by definition — Binance/Coinbase/Kraken
   spot pages all omit liquidation views).
   futures-only nodes (18 total, all carrying data-si-market="futures"):
   · #siTabs container (entire tab nav — when only 1 of 4 tabs
     would remain visible, a lone highlighted button looks broken)
   · 3 tab buttons: Funding / Positioning / Liquidation
   · #futuresMetricsContainer (OI / Funding / L-S / OI L-S)
   · 13 panes:
       overview tab: Funding Countdown + Basis, CVD-Z / OI-Base,
         OI 24h / Funding 8x / L-S 24h, ±1% Depth
       funding tab: Funding Arb, Funding Flow, Cross-Ex Funding
       positioning tab: TopTrader L/S, OI Exchange Dominance
       liquidation tab: Predicted Liq Levels, Cascade Events,
         Market Liquidation Pulse, 24h Global Liq Ratio
   Visible on spot (2 nodes, no data-si-market attribute):
   · #siWhaleKpi strip (Whale flow aggregates spot+futures via
     market=all — meaningful per-symbol trading signal)
   · Market Sentiment pane (Greed + AltSeason, global macro
     sentiment with no per-symbol scope — meaningful anywhere)
   Spot UX on a perp-converted page therefore renders:
     [8-cell 24h price grid] + [Whale 4 KPI] + [Market Sentiment]
   That matches industry conventions for spot detail pages.
   Why CSS instead of inline style/JS: ① one selector covers 18
   DOM nodes; ② futures⇆spot toggle is instant (no layout-thrash from
   per-element style writes); ③ no greyed-out look — element is
   simply gone (per virtu Hu's "不要变灰" rule).
   The `!important` is required because some children re-declare
   display:flex/grid with the same specificity (see si-tab-pane
   fallback rules right above this block). */
body.si-spot-mode [data-si-market="futures"] {
    display: none !important;
}
.si-kpi-cell {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 6px 8px;
    border-right: 1px solid var(--border-color);
    border-bottom: 1px solid var(--border-color);
}
.si-kpi-cell:nth-child(4n) {
    border-right: none;
}
.si-kpi-cell:nth-last-child(-n + 4) {
    border-bottom: none;
}
.si-kpi-label {
    font-size: 12px;
    color: var(--text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.si-kpi-value {
    font-size: 14px;
    color: var(--text-primary);
    font-family: var(--font-price);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.si-kpi-value.up { color: var(--price-up); }
.si-kpi-value.down { color: var(--price-down); }
.si-kpi-value.neutral { color: var(--text-primary); }
@media (max-width: 760px) {
    .si-kpi-strip {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
    .si-kpi-cell:nth-child(4n) {
        border-right: 1px solid var(--border-color);
    }
    .si-kpi-cell:nth-last-child(-n + 4) {
        border-bottom: 1px solid var(--border-color);
    }
    .si-kpi-cell:nth-child(2n) {
        border-right: none;
    }
    .si-kpi-cell:nth-last-child(-n + 2) {
        border-bottom: none;
    }
}

/* Row 3 — Mini sparklines (OI/Funding/L-S) */
.si-mini-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: 6px;
    min-width: 0;
}
.si-mini-cell {
    border: 1px solid var(--border-color);
    border-radius: 4px;
    padding: 6px 8px 4px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-height: 70px;
    min-width: 0;
}
.si-mini-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
}
.si-mini-label {
    font-size: 12px;
    color: var(--text-secondary);
}
.si-mini-value {
    font-size: 14px;
    color: var(--text-primary);
    font-family: var(--font-price);
    min-width: 0;
    white-space: nowrap;
}
.si-mini-chart {
    flex: 1 1 auto;
    min-height: 36px;
    min-width: 0;
    overflow: hidden;
    position: relative;
}

/* Generic section header */
.si-section {
    border: 1px solid var(--border-color);
    border-radius: 4px;
    padding: 6px 8px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
/* [REMOVE] 2026-05-24 r14 — .si-section-grid wrapper deleted from chart.ejs
   (its two children — Cross-Exchange Funding + OI Dominance — were split
   into separate panes for the 4-tab nav). No other view uses this class. */
.si-flow-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: 6px;
}
.si-flow-cell {
    border: 1px solid var(--border-color);
    border-radius: 4px;
    padding: 6px 8px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.si-flow-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
}
.si-flow-label {
    font-size: 12px;
    color: var(--text-secondary);
    text-transform: uppercase;
}
.si-flow-net {
    font-size: 14px;
    font-family: var(--font-price);
    color: var(--text-primary);
}
.si-flow-net.up { color: var(--price-up); }
.si-flow-net.down { color: var(--price-down); }
.si-flow-net.neutral { color: var(--text-secondary); }
.si-flow-bar {
    height: 6px;
    background: var(--border-color);
    border-radius: 3px;
    overflow: hidden;
    display: flex;
}
.si-flow-bar-long {
    height: 100%;
    background: rgba(38, 166, 154, 0.8);
}
.si-flow-bar-short {
    height: 100%;
    background: rgba(239, 83, 80, 0.8);
}
.si-flow-legend {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    font-size: 12px;
    color: var(--text-secondary);
}
.si-flow-legend-long { color: var(--price-up); }
.si-flow-legend-short { color: var(--price-down); }
.si-section-title {
    font-size: 12px;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.si-section-title-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
}
.si-section-hint {
    font-size: 12px;
    color: var(--text-secondary);
    font-family: var(--font-price);
}
.si-empty {
    font-size: 12px;
    color: var(--text-secondary);
    text-align: center;
    padding: 10px 0;
    font-style: italic;
}

/* [ADD] 2026-07-06 — BACKLOG-009 per-venue pair-history chart (funding / OI). */
.si-pairhist-controls {
    display: inline-flex;
    gap: 6px;
    align-items: center;
}
.si-pairhist-select {
    font-size: 12px;
    color: var(--text-primary);
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 4px;
    padding: 2px 6px;
    text-transform: capitalize;
}
.si-pairhist-chart {
    position: relative;
    width: 100%;
    height: 260px;
    margin-top: 8px;
}
.si-pairhist__empty {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    color: var(--text-secondary);
    font-style: italic;
    pointer-events: none;
    text-align: center;
    padding: 0 12px;
}

/* Row 4 — Cross-Exchange Funding list */
.si-xex-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.si-xex-row {
    display: grid;
    grid-template-columns: 80px 1fr 80px;
    align-items: center;
    gap: 8px;
    padding: 4px 0;
    border-bottom: 1px dashed var(--border-color);
}
.si-xex-row:last-child {
    border-bottom: none;
}
.si-xex-ex {
    font-size: 12px;
    color: var(--text-primary);
    text-transform: capitalize;
}
.si-xex-rate {
    font-size: 14px;
    color: var(--text-primary);
    font-family: var(--font-price);
    text-align: right;
}
.si-xex-rate.up { color: var(--price-up); }
.si-xex-rate.down { color: var(--price-down); }
.si-xex-cd {
    font-size: 14px;
    color: var(--text-secondary);
    font-family: var(--font-price);
    text-align: right;
}

/* Row 5 — OI Exchange Dominance */
.si-dom-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.si-dom-row {
    display: grid;
    grid-template-columns: 80px 1fr 50px 80px;
    align-items: center;
    gap: 8px;
}
.si-dom-ex {
    font-size: 12px;
    color: var(--text-primary);
    text-transform: capitalize;
}
.si-dom-bar {
    height: 6px;
    background: var(--border-color);
    border-radius: 3px;
    overflow: hidden;
}
.si-dom-bar-fill {
    height: 100%;
    background: var(--accent-color);
    border-radius: 3px;
    transition: width 0.3s ease;
}
.si-dom-pct {
    font-size: 14px;
    color: var(--text-secondary);
    font-family: var(--font-price);
    text-align: right;
}
.si-dom-usd {
    font-size: 14px;
    color: var(--text-primary);
    font-family: var(--font-price);
    text-align: right;
}

/* [REMOVED] 2026-05-22 — .si-tape-* styles for the inline Whale Tape.
 * Reason: Whale Tape was moved into the orderbook module as a new
 * "Large Trades" tab. The tab uses its own .lt-* class hierarchy
 * (see "Large Trades Tab" block above) which mirrors the recent-
 * trades row layout for visual consistency with the surrounding
 * orderbook tabs. */

/* Row 7 — Liquidation Levels
 *
 * [FIX] 2026-05-22 — left-align the price column. The earlier
 *   grid `100px 1fr 80px` + `text-align:right` on .si-liq-price
 *   left an empty 30-40 px gutter on shorter prices (e.g. 84.55),
 *   which read as "missing content" on the left edge. We now use
 *   `auto` + left-align so the price hugs the row's left side and
 *   the bar fills the remaining space. */
.si-liq-levels {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.si-liq-row {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 8px;
    padding: 2px 0;
}
.si-liq-price {
    font-size: 14px;
    font-weight: 400;
    color: var(--text-primary);
    font-family: var(--font-price);
    text-align: left;
    min-width: 56px;
}
.si-liq-bar {
    height: 6px;
    background: var(--border-color);
    border-radius: 3px;
    overflow: hidden;
}
.si-liq-bar-fill {
    height: 100%;
    border-radius: 3px;
    transition: width 0.3s ease;
}
.si-liq-bar-fill.up { background: var(--price-up); }
.si-liq-bar-fill.down { background: var(--price-down); }
.si-liq-usd {
    font-size: 14px;
    font-weight: 400;
    font-family: var(--font-price);
    text-align: right;
}
.si-liq-usd.up { color: var(--price-up); }
.si-liq-usd.down { color: var(--price-down); }

/* Light theme: ensure mini sparkline transparent backgrounds stay clean */
body:not(.dark-theme) .si-mini-chart canvas {
    background: transparent !important;
}

/* [ADD] 2026-05-24 — Symbol Insights P0/P1 batch:
   · P0-5 Market Liquidation Pulse  → .si-pulse-row / .si-pulse-chip
   · P0-1 ±1% Depth Liquidity       → .si-depth-grid / .si-depth-row
   · P1-1 Cascade Events            → .si-collapsible / .si-cascade-*
   · P1-2 Cross-Ex view toggle      → .si-view-toggle / .si-cxf-heat
   · P1-5 24h Global Liq Ratio      → .si-liqratio-*
   · P1-4 Markets Scanner mode      → .symbol-mode-*
   · P1-3 Top Predicted Strip       → REMOVED 2026-05-24 r7 (low value vs. topbar real-estate)
   · P0-4 OI vs Price Divergence — REMOVED 2026-05-24 r11 (no value for majors)
   All colours use var(--tt-fg-0) or the up/down exception palette per
   web_app_styling.mdc constraint #1. Font sizes restricted to
   {12,14,16,18,22,28} per constraint #2. */

/* P0-5 Market Pulse */
.si-pulse-row {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 6px;
}
.si-pulse-chip {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 6px 8px;
    border: 1px solid var(--border-color);
    border-radius: 4px;
    min-width: 0;
}
.si-pulse-win {
    font-size: 12px;
    color: var(--tt-fg-0, var(--text-secondary));
    opacity: 0.7;
    text-transform: uppercase;
}
.si-pulse-total {
    font-size: 14px;
    color: var(--tt-fg-0, var(--text-primary));
    font-family: var(--font-price);
    white-space: nowrap;
}
.si-pulse-bar {
    height: 4px;
    background: var(--border-color);
    border-radius: 2px;
    display: flex;
    overflow: hidden;
}
.si-pulse-bar-long { height: 100%; background: rgba(38, 166, 154, 0.85); width: 0%; }
.si-pulse-bar-short { height: 100%; background: rgba(239, 83, 80, 0.85); width: 0%; }

/* P0-1 Depth Liquidity */
.si-depth-grid {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.si-depth-row {
    display: grid;
    grid-template-columns: 80px 1fr auto;
    align-items: center;
    gap: 8px;
    padding: 2px 0;
}
.si-depth-label {
    font-size: 12px;
    color: var(--tt-fg-0, var(--text-secondary));
    opacity: 0.7;
}
.si-depth-value {
    font-size: 14px;
    color: var(--tt-fg-0, var(--text-primary));
    font-family: var(--font-price);
    text-align: right;
    grid-column: 2 / span 2;
    white-space: nowrap;
}
.si-depth-bid .si-depth-value { color: var(--price-up); }
.si-depth-ask .si-depth-value { color: var(--price-down); }
.si-depth-imb .si-depth-value { grid-column: auto; }
.si-depth-bar {
    height: 6px;
    background: var(--border-color);
    border-radius: 3px;
    position: relative;
    overflow: hidden;
}
.si-depth-bar-fill {
    height: 100%;
    background: var(--accent-color, #2962ff);
    border-radius: 3px;
    transition: width 0.3s ease;
}
.si-depth-bar-fill.up   { background: var(--price-up); }
.si-depth-bar-fill.down { background: var(--price-down); }
.si-depth-bar-fill.neutral { background: var(--accent-color, #2962ff); }
.si-depth-pct {
    font-size: 14px;
    color: var(--tt-fg-0, var(--text-primary));
    font-family: var(--font-price);
    text-align: right;
    min-width: 56px;
}

/* P1-5 24h Global Liq Ratio */
.si-liqratio-row {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.si-liqratio-bar {
    height: 8px;
    background: var(--border-color);
    border-radius: 4px;
    display: flex;
    overflow: hidden;
}
.si-liqratio-bar-long { height: 100%; background: rgba(38, 166, 154, 0.85); width: 0%; }
.si-liqratio-bar-short { height: 100%; background: rgba(239, 83, 80, 0.85); width: 0%; }
.si-liqratio-legend {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    font-size: 12px;
    color: var(--tt-fg-0, var(--text-secondary));
}
.si-liqratio-legend-long  { color: var(--price-up); }
.si-liqratio-legend-short { color: var(--price-down); }

/* P1-1 Cascade Events collapsible */
.si-collapsible-head {
    cursor: pointer;
    user-select: none;
}
.si-collapsible-head:focus-visible {
    outline: 1px solid var(--accent-color, #2962ff);
    outline-offset: 2px;
    border-radius: 2px;
}
.si-collapsible-arrow {
    width: 12px;
    height: 12px;
    margin-left: 6px;
    transition: transform 0.18s ease;
    vertical-align: middle;
}
.si-collapsible[data-collapsed="0"] .si-collapsible-arrow {
    transform: rotate(180deg);
}
.si-cascade-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.si-cascade-row {
    display: grid;
    grid-template-columns: 64px 60px 1fr auto;
    align-items: center;
    gap: 8px;
    padding: 2px 0;
    border-bottom: 1px dashed var(--border-color);
}
.si-cascade-row:last-child { border-bottom: none; }
.si-cascade-time {
    font-size: 12px;
    color: var(--tt-fg-0, var(--text-secondary));
    font-family: var(--font-price);
}
.si-cascade-sym {
    font-size: 12px;
    color: var(--tt-fg-0, var(--text-primary));
    font-weight: 600;
}
.si-cascade-usd {
    font-size: 14px;
    color: var(--tt-fg-0, var(--text-primary));
    font-family: var(--font-price);
    text-align: right;
}
.si-cascade-usd.up   { color: var(--price-up); }
.si-cascade-usd.down { color: var(--price-down); }
.si-cascade-usd.neutral { color: var(--tt-fg-0, var(--text-primary)); }
.si-cascade-meta {
    font-size: 12px;
    color: var(--tt-fg-0, var(--text-secondary));
    opacity: 0.7;
    text-align: right;
    white-space: nowrap;
}

/* P1-2 Cross-Ex Funding view toggle + heat view */
.si-view-toggle {
    display: inline-flex;
    border: 1px solid var(--border-color);
    border-radius: 4px;
    overflow: hidden;
}
.si-view-tab {
    background: transparent;
    border: none;
    padding: 2px 8px;
    font-size: 12px;
    color: var(--tt-fg-0, var(--text-secondary));
    cursor: pointer;
    font-family: inherit;
}
.si-view-tab + .si-view-tab {
    border-left: 1px solid var(--border-color);
}
.si-view-tab.active {
    background: var(--accent-color, #2962ff);
    color: #ffffff;
}
.si-cxf-heat {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
    gap: 4px;
}
.si-cxf-heat-cell {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 6px 4px;
    border-radius: 4px;
    border: 1px solid var(--border-color);
    background: color-mix(in srgb, var(--border-color) calc(20% + (var(--si-cxf-i, 0) * 50%)), transparent);
}
.si-cxf-heat-cell.up {
    background: color-mix(in srgb, rgba(38, 166, 154, 1) calc(15% + (var(--si-cxf-i, 0) * 55%)), transparent);
}
.si-cxf-heat-cell.down {
    background: color-mix(in srgb, rgba(239, 83, 80, 1) calc(15% + (var(--si-cxf-i, 0) * 55%)), transparent);
}
.si-cxf-heat-ex {
    font-size: 12px;
    color: var(--tt-fg-0, var(--text-primary));
    text-transform: capitalize;
}
.si-cxf-heat-rate {
    font-size: 12px;
    font-family: var(--font-price);
    color: var(--tt-fg-0, var(--text-primary));
}

/* [REMOVE] 2026-05-24 r11 — P0-4 OI vs Price Divergence card removed.
   Reason: only top 28+30 extreme small-caps are surfaced by the backend
   divergence aggregation; majors (BTC/ETH/SOL/DOGE/HYPE) never appear in
   that leaderboard, so the card always rendered "No divergence" for the
   active base — pure noise. Per virtu Hu 2026-05-24. */

/* P1-4 Scanner mode toggle (inside #symbolColumnsMenu) */
.symbol-mode-toggle {
    display: flex;
    gap: 4px;
    padding: 4px 8px 8px;
}
.symbol-mode-btn {
    flex: 1 1 auto;
    background: transparent;
    border: 1px solid var(--border-color);
    border-radius: 4px;
    padding: 4px 8px;
    font-size: 12px;
    color: var(--tt-fg-0, var(--text-primary));
    cursor: pointer;
    font-family: inherit;
}
.symbol-mode-btn.active {
    background: var(--accent-color, #2962ff);
    color: #ffffff;
    border-color: var(--accent-color, #2962ff);
}

/* [REMOVED 2026-05-24 r7] P1-3 Top toolbar Predicted Funding strip.
   Reason: per-user audit — the chip occupied scarce topbar pixels for a
   single base + percent with no actionable context (no historical, no
   per-base relation to the active chart). Cross-exchange funding extremes
   are already surfaced in the Symbol Insights "Cross-Ex Funding" card and
   in the dedicated /movers landing page; no need for a top-bar duplicate. */

/* Mobile: trim per-section grid columns for the Symbol Insights cards
   that overflow on narrow viewports (Pulse / Cascade / Cross-Ex heatmap). */
@media (max-width: 768px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    .si-pulse-row {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
    .si-pulse-total {
        font-size: 12px;
    }
    .si-cascade-row {
        grid-template-columns: 56px 56px 1fr auto;
    }
    .si-cxf-heat {
        grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
    }
}
@media (max-width: 768px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    .si-flow-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
    .si-flow-cell {
        min-width: 0;
    }
    .si-flow-net {
        font-size: 12px;
    }
}
/* ===== [SYMBOL-INSIGHTS-2026-05-22] END ===== */

/* ===== [MODULE-ICON-2026-05-23] BEGIN =====
 *
 * Standardized "round-disc + glyph" icon for every dockable module
 * title (Markets / Liquidations / Orderbook / Paper Trading) and for
 * the orderbook tab triggers (Orderbook / Recent Trades / Large
 * Trades). The visual reference is the BTC asset logo in the Symbol
 * Details header (.symbol-detail-icon) — same 24×24 disc, same
 * border-radius:50%. The glyph inside is drawn with currentColor so
 * it inherits whatever color the surrounding text uses (e.g. accent
 * blue when the parent tab is active).
 *
 * Industry reference: every modern trading dashboard (Binance UI,
 * OKX desktop, TradingView's Trading Platform) uses a uniform disc-
 * sized icon system in module headers so the sidebar reads as a
 * coherent toolbar rather than mismatched ad-hoc glyphs.
 *
 * Sizing:
 *   .module-icon         — 24×24 disc, used by every module title
 *                          AND by every Orderbook tab trigger
 *                          (.module-icon--tab is just a marker
 *                          class for tab context; geometry is
 *                          intentionally identical to the title
 *                          disc so the active tab reads as the
 *                          same visual weight as a full module
 *                          identity icon).
 */
.module-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    /* [CHANGED] 2026-05-23 — theme-inverted disc colors.
     *
     * Light theme: BLACK disc + WHITE glyph (maximum contrast on
     *              the bright #ffffff page surface).
     * Dark theme:  WHITE disc + BLACK glyph (maximum contrast on
     *              the pure #000000 page surface).
     *
     * Industry reference: Bloomberg Terminal and TradingView Pro
     * use solid black/white module identity discs in their dark
     * mode toolbars precisely because pure black-on-white (and
     * inverted) yields a 21:1 WCAG contrast, the highest possible
     * for any glyph — eliminating any ambiguity about which module
     * a header refers to even at a glance.
     *
     * The default block below sets the LIGHT-theme colors; the
     * `body.dark-theme .module-icon { ... }` override further down
     * flips them for the dark theme. */
    background: #000000;
    color: #ffffff;
    flex-shrink: 0;
}

/* Dark-theme inversion — white disc + black glyph on #000 surface. */
body.dark-theme .module-icon {
    background: #ffffff;
    color: #000000;
}

/* Icon ↔ drag-handle distance is the canonical "1px" used by the
 * Symbol Details header (`.symbol-detail-header .symbol-detail-drag-
 * handle + .symbol-detail-icon { margin-left: 1px }`) — see CSS line
 * around 5857. We achieve it implicitly: every dock-module header
 * already sets `.module-drag-handle { margin-right: 1px }` and
 * `gap: 0`, so the 1px sits naturally between the handle and the
 * icon without needing any margin on `.module-icon` itself.
 *
 * Icon ↔ title distance is set to 8px (matches the panel-header
 * default flex gap of 8px). We state it explicitly via margin-right
 * because the various dock-module headers override the parent flex
 * gap to 0 in several places (e.g. `.panel-header.dock-module-
 * header { gap: 0 }`), so relying on parent `gap` would collapse
 * the spacing inconsistently. */
.dock-module-header > .module-icon {
    margin-left: 0;
    margin-right: 8px;
}

.module-icon svg {
    width: 14px;
    height: 14px;
    display: block;
    stroke: currentColor;
    fill: none;
}

/* [CHANGED] 2026-05-23 — two-state tab disc sizing:
 *   - active tab  → 24×24 disc (matches module-title icons; the
 *                   active tab is "promoted" to module-identity
 *                   weight per user requirement: "订单簿 左边的
 *                   图标 ... 要和爆仓 市场的图标一样的大小").
 *   - inactive tab → 16×16 disc (the two tabs not currently in
 *                    view stay smaller so they don't compete with
 *                    the active tab visually — same affordance
 *                    pattern Binance / OKX use for their order-
 *                    book sub-tabs).
 *
 * The tab strip is 48px tall (`.right-tabs { height: 48px }`), so
 * both 16px and 24px discs fit comfortably with vertical centring.
 *
 * Higher-specificity `.right-tab .module-icon--tab` selectors below
 * defeat the legacy `.right-tab svg { width: 16px; height: 16px }`
 * rule that lives upstream in this file. */
.module-icon--tab {
    width: 16px;
    height: 16px;
}
.right-tab .module-icon--tab {
    width: 16px;
    height: 16px;
}
.right-tab .module-icon--tab svg,
.module-icon--tab svg {
    width: 10px;
    height: 10px;
}

/* Active tab is promoted to the full 24×24 module-identity disc
 * size so it visually anchors the strip the same way the
 * Liquidations / Markets / Paper Trading module discs anchor their
 * headers. The inner SVG glyph follows the same 14×14 proportion
 * used by the title-icon variant for consistency. */
.right-tab.active .module-icon--tab {
    width: 24px;
    height: 24px;
}
.right-tab.active .module-icon--tab svg {
    width: 14px;
    height: 14px;
}
/* ===== [MODULE-ICON-2026-05-23] END ===== */

/* ===== [TT-SETTINGS-POPOVER-2026-05-23] BEGIN =====
 *
 * Non-modal draggable settings popover used by chart-page modules
 * (Large Trades + Liquidations). Visual language mirrors the
 * chart-indicator settings dialog (`.ind-settings-dialog`):
 *   - same `--popover-bg / --popover-border / --popover-shadow`
 *   - same 12px corner radius
 *   - same 16/20 header padding
 *   - same `cursor: move` on the header
 * Differences vs ind-settings:
 *   - position: fixed (JS-driven), not transform-centered
 *   - smaller default width (300-360px)
 *   - non-modal (no scrim, pointer-events on body OK)
 *   - higher z-index (8000) above panel headers but below the
 *     top-bar dropdowns (which use 9999)
 */
.tt-settings-popover {
    position: fixed;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 12px;
    box-shadow: var(--popover-shadow);
    color: var(--text-primary);
    font-size: 14px;
    overflow: hidden;
    user-select: none;
    -webkit-user-select: none;
}
.tt-settings-popover__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 14px;
    border-bottom: 1px solid var(--popover-border);
    cursor: move;
    background: var(--popover-bg);
}
.tt-settings-popover__title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
}
.tt-settings-popover__close {
    background: transparent;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 4px;
}
.tt-settings-popover__close:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
}
.tt-settings-popover__body {
    padding: 12px 14px 14px;
    max-height: 70vh;
    overflow-y: auto;
}

/* Generic row inside the popover body: a stacked label + control
 * pair, mirroring the .lt-ctrl pattern used previously inline. The
 * body is a flex column with consistent gaps so the popover keeps
 * rhythm regardless of how many rows the caller emits. */
.tt-settings-popover__body .tt-set-row {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 10px;
}
.tt-settings-popover__body .tt-set-row:last-child {
    margin-bottom: 0;
}
.tt-settings-popover__body .tt-set-label {
    font-size: 12px;
    color: var(--text-secondary);
}
.tt-settings-popover__body .tt-set-select {
    width: 100%;
    height: 28px;
    padding: 0 8px;
    background: var(--bg-tertiary);
    border: 1px solid var(--border-primary);
    color: var(--text-primary);
    border-radius: 3px;
    font-size: 14px;
    font-family: inherit;
}
.tt-settings-popover__body .tt-set-select:hover {
    border-color: var(--accent);
}
.tt-settings-popover__body .tt-set-actions {
    display: flex;
    justify-content: flex-start;
    margin-top: 4px;
}
.tt-settings-popover__body .tt-set-reset {
    background: var(--bg-tertiary);
    border: 1px solid var(--border-primary);
    border-radius: 3px;
    color: var(--text-secondary);
    cursor: pointer;
    height: 28px;
    padding: 0 12px;
    font-size: 12px;
    font-family: inherit;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    line-height: 1;
}
.tt-settings-popover__body .tt-set-reset:hover {
    color: var(--text-primary);
    border-color: var(--accent);
}
.tt-settings-popover__body .tt-set-reset svg {
    width: 14px;
    height: 14px;
}

/* Gear button that lives inside the host module's stats bar and
 * opens the popover. 22×22 disc so it blends with the existing
 * stat-item baselines without dominating. */
.tt-module-gear {
    background: transparent;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    width: 24px;
    height: 24px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 5px;
    flex-shrink: 0;
}
.tt-module-gear:hover {
    color: var(--text-primary);
    background: var(--bg-hover);
}
.tt-module-gear svg {
    width: 20px;
    height: 20px;
}
/* ===== [TT-SETTINGS-POPOVER-2026-05-23] END ===== */


/* ============================================================================
 * [ADD] 2026-05-24 r3 — Chart-page focus / select theming reset.
 *
 * virtuou-哥 reported:
 *   "过滤按钮在桌面版上 点击后, 选择下拉框的焦点颜色不对.
 *    在白色主题下是黄色  在黑色主题下是白黄色.
 *    我需要你去除选择后的焦点. 保留原来的主题颜色.
 *    CHART 页面所有的按钮 选择等都要这样处理."
 *
 * Root cause:
 *   1. Chrome / Edge default `:focus` outline picks the OS accent
 *      color, which on Windows defaults to a dark amber-yellow on
 *      dark backgrounds and a bright golden on light backgrounds —
 *      the exact "黄色 / 白黄色" the user described.
 *   2. Inside <select>, the highlighted (keyboard / current) <option>
 *      uses `accent-color` from the user-agent stylesheet, which
 *      defaults to the same OS yellow on a Tradeiz dark theme.
 *   3. The chart page already has a project accent color
 *      (`--accent-color: #2962ff`, Material Blue A700) that should
 *      be the *only* focus / selected highlight color anywhere
 *      under the chart UI — anything else feels off-brand.
 *
 * Design pillars (financial-grade UX):
 *   - Mouse interactions get NO visible focus ring. Keyboard-only
 *     users (Tab navigation) still get a 1px ring in the brand
 *     accent so a11y is not regressed.
 *     This is the standard `:focus:not(:focus-visible)` pattern
 *     popularised by GitHub / Stripe / Linear.
 *   - All form controls / buttons / interactive divs use the brand
 *     accent (`--accent-color`) for their `accent-color` CSS prop.
 *     This drives <option>:checked, range thumb, checkbox/radio fill.
 *   - The reset is intentionally chart-page-only because app.css
 *     is loaded only by chart.ejs (verified via repo grep). Other
 *     pages (home / aggregations / account) load tradeiz-*.css and
 *     are governed by their own rule files (tradeiz-custom.css).
 *
 * What it does NOT change:
 *   - Existing intentional `:focus { border-color: var(--accent-color) }`
 *     declarations sprinkled across this file (e.g. `.ca-select:focus`,
 *     `.ind-settings-input:focus`) keep their accent-blue border —
 *     those are explicit visual feedback authored by us, not the
 *     browser default outline. Border ≠ outline, so they survive.
 *   - The native `:focus-visible` ring stays for keyboard users
 *     because we explicitly redraw it with `outline: 1px solid
 *     var(--accent-color)` rather than relying on the browser
 *     default that keeps producing the OS yellow.
 *
 * [WARN] Do NOT remove the `accent-color: var(--accent-color)`
 *   declaration on `body` — without it, the browser default
 *   accent (OS yellow on Windows) leaks into <select> option
 *   highlighting again and the user-visible "黄色" returns.
 * ============================================================================ */

body {
    accent-color: var(--accent-color, #2962ff);
}
body.dark-theme {
    accent-color: #000000;
}

/* Mouse-driven focus produces NO outline ring. Keyboard focus is
 * handled separately by `:focus-visible` below.
 *
 * Note: we only kill `outline`, NOT `box-shadow`. Some controls
 * (e.g. `.ai-instructions__text:focus`) use `box-shadow: 0 0 0 2px
 * rgba(...)` as an intentional brand-accent ring; that's an
 * explicit author choice and outside the OS-yellow problem this
 * reset addresses. */
*:focus:not(:focus-visible) {
    outline: none !important;
}

/* Keyboard a11y: a thin brand-accent ring (NOT the browser-default
 * OS-yellow). 1px / 2px offset matches GitHub's accessibility
 * pattern. We use `outline` here (not box-shadow) so existing
 * author box-shadows on focused controls are preserved. */
*:focus-visible {
    outline: 1px solid var(--accent-color, #2962ff) !important;
    outline-offset: 1px;
}
body.dark-theme *:focus-visible {
    outline: 1px solid #000000 !important;
}

/* Inputs / selects / buttons get the same accent-color carried
 * over from <body> — defensive in case a future ancestor sets
 * a different `accent-color`. */
input,
select,
textarea,
button {
    accent-color: var(--accent-color, #2962ff);
}
body.dark-theme :is(input, select, textarea, button) {
    accent-color: #000000;
}

/* [CHG] 2026-05-31 — dark-theme input focus ring removed.
 * User requested no visible black focus border on any dark-theme input
 * controls. Keep !important so this reset wins over module-local focus
 * border/box-shadow rules. */
/* [FIX] 2026-06-10 — exclude appearance:none checkboxes/radios from the
 *   focus-border reset. Those custom controls draw their own box via
 *   `border` (e.g. .symbol-column-item checkbox = 1.5px solid --text-primary);
 *   `border-color: transparent !important` on :focus made the unchecked
 *   box vanish on dark theme after a click. Text-entry controls still lose
 *   their focus ring.
 * Reason: blanket input:focus reset also matched custom checkbox/radio.
 * Change: scope the reset to text/select/textarea, not checkbox/radio.
 * Warning: do NOT re-add bare `input:focus` here — it will hide the
 *   self-drawn checkbox border again. */
body.dark-theme input:not([type="checkbox"]):not([type="radio"]):focus,
body.dark-theme select:focus,
body.dark-theme textarea:focus,
body.dark-theme input:not([type="checkbox"]):not([type="radio"]):focus-visible,
body.dark-theme select:focus-visible,
body.dark-theme textarea:focus-visible {
    border-color: transparent !important;
    box-shadow: none !important;
    outline: none !important;
}

/* Native <option> highlight inside the popover's filter selects.
 * Most browsers honour `background-color: -internal-light-dark()`
 * which fights theme switching; pin both to the brand accent so
 * the highlight is brand-blue regardless of OS theme.
 *
 * Note: Firefox respects this; Chrome/Edge respect `accent-color`
 * on the parent <select> instead — both routes converge. */
body.dark-theme .tt-settings-popover__body select option:checked,
body.dark-theme .tt-settings-popover__body select option:focus,
body.dark-theme .tt-settings-popover__body select option:hover {
    background: #000000;
    color: #ffffff;
}
body:not(.dark-theme) .tt-settings-popover__body select option:checked,
body:not(.dark-theme) .tt-settings-popover__body select option:focus,
body:not(.dark-theme) .tt-settings-popover__body select option:hover {
    background: var(--accent-color, #2962ff);
    color: #ffffff;
}

/* ════════════════════════════════════════════════════════════════════
   [ADD] 2026-05-24 r8 — P2-1 / P2-2 / P2-3 / P2-4 batch
   [REMOVE] 2026-05-24 r10 — P2-5 NEW listings badge removed (no value to traders)
   Differentiated visualizations (intentionally not unified) so the
   user gets distinct shape language per data type:
     · P2-2 Greed Index      → SVG half-circle gauge with needle
     · P2-3 AltSeason        → horizontal dual-direction bar (pivot at 50)
     · P2-1 TopTrader L/S    → LightweightCharts dual-line sparkline
     · P2-4 Funding Arb      → 2-venue chip + arrow + APR badge (flow diagram)
   All colours: var(--tt-fg-0) or the up/down/accent exception palette
   per web_app_styling.mdc; font sizes restricted to {12,14,16,18,22,28}.
   ════════════════════════════════════════════════════════════════════ */

/* ── P2-2 + P2-3 Market Sentiment row container ─────────────────── */
.si-sent-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
}
.si-sent-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 10px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    background: transparent;
}
.si-sent-card-title {
    font-size: 12px;
    color: var(--tt-fg-0);
    opacity: 0.7;
    text-align: center;
}

/* P2-2 Greed half-circle gauge */
.si-gauge {
    position: relative;
    width: 100%;
    max-width: 200px;
}
.si-gauge-svg {
    width: 100%;
    height: auto;
    color: var(--tt-fg-0);
    display: block;
}
.si-gauge-value {
    display: block;
    font-size: 22px;
    font-family: var(--font-price);
    font-weight: 700;
    color: var(--tt-fg-0);
}
.si-gauge-label {
    display: block;
    font-size: 12px;
    color: var(--tt-fg-0);
    opacity: 0.7;
}

/* P2-3 AltSeason — [REWRITE] 2026-05-24 r23
   Industry-standard layout (Blockchain Center / CoinMarketCap):
   · Title + state chip (red/grey/green by zone)
   · Big score number
   · 3-zone gauge with 25/75 tick lines + zone-coloured needle
   · Split-percent stacked bar (X beat / N-X lost)
   · Plain-text counter
   Old layout (.si-altbar / .si-altbar-track / .si-altbar-needle /
   .si-altbar-fill / .si-altbar-scale / .si-altbar-value /
   .si-altbar-hint) was a single gradient bar — replaced wholesale.
   Warning: keep .si-alt-gauge-track height ≥ 14px so the 25/75
   ticks remain visible above the dimmed track background. */
/* [FIX] 2026-05-24 r24 — override .si-sent-card's `align-items: center`
   for the AltSeason card. Without this, the deep-nested gauge/split
   containers (which rely on `width: 100%` + flex children) collapse
   to 0-width because the parent's `align-items: center` causes the
   browser's flex sizing algorithm to use content size as the
   cross-axis baseline, and our gauge has no intrinsic content (all
   visuals are pure colour fills / absolutely-positioned ticks).
   Symptom before fix: card showed only the text rows ("AltSeason
   Index", "Neutral", "48 / 100", "0 25 75 100", "BTC Season",
   "Alt Season", "Beat BTC: 24 · Lost: 26") — every bar, tick,
   needle and split rendered at 0 height.
   The Greed card stays untouched because its visual is a <svg>
   which is a replaced element with intrinsic dimensions and
   already renders correctly under align-items:center.
   Warning: keep this rule with the more-specific .si-sent-card--altseason
   selector; do NOT change the parent .si-sent-card rule itself (the
   Greed gauge depends on the centred layout). */
.si-sent-card--altseason {
    gap: 8px;
    align-items: stretch;
}
.si-alt-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    gap: 8px;
}
.si-alt-chip {
    font-size: 12px;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: 10px;
    border: 1px solid currentColor;
    line-height: 1.4;
    white-space: nowrap;
}
.si-alt-chip[data-zone="btc"] { color: var(--tt-down); }
.si-alt-chip[data-zone="mid"] { color: var(--tt-fg-0); opacity: 0.85; }
.si-alt-chip[data-zone="alt"] { color: var(--tt-up); }

.si-alt-score {
    font-size: 22px;
    font-family: var(--font-price);
    font-weight: 700;
    color: var(--tt-fg-0);
    line-height: 1.2;
    text-align: center;
    width: 100%;
}

.si-alt-gauge {
    display: flex;
    flex-direction: column;
    gap: 4px;
    width: 100%;
}
.si-alt-gauge-track {
    position: relative;
    display: flex;
    height: 14px;
    border-radius: 7px;
    overflow: visible;
    /* Keep a visible neutral rail without relying on color-mix support. */
    background: rgba(120, 125, 135, 0.20);
}
/* [FIX] 2026-05-24 r24 — all gauge seg / tick / needle elements MUST
   declare display:block explicitly. The markup now uses <div>, but we
   keep explicit display declarations as an anti-regression guard: if
   someone later switches back to <span>, bars/ticks/needle still render.
   This directly addresses virtu Hu's report: "只有文字 没有图形". */
.si-alt-gauge-seg {
    display: block;
    height: 100%;
}
.si-alt-gauge-seg--btc {
    flex: 0 0 25%;
    background: rgba(255, 77, 106, 0.48);
    border-radius: 7px 0 0 7px;
}
.si-alt-gauge-seg--mid {
    flex: 0 0 50%;
    background: rgba(120, 125, 135, 0.34);
}
.si-alt-gauge-seg--alt {
    flex: 0 0 25%;
    background: rgba(0, 200, 150, 0.48);
    border-radius: 0 7px 7px 0;
}
/* Tick marks at the industry-standard 25 / 75 thresholds. */
.si-alt-gauge-tick {
    display: block;
    position: absolute;
    top: -3px;
    bottom: -3px;
    width: 1px;
    background: rgba(120, 125, 135, 0.9);
    transform: translateX(-0.5px);
    pointer-events: none;
}
/* Needle recolours per zone (matches the chip colour for consistency). */
.si-alt-gauge-needle {
    display: block;
    position: absolute;
    top: -4px;
    width: 4px;
    height: 22px;
    border-radius: 2px;
    transform: translateX(-2px);
    transition: left 0.4s ease, background-color 0.2s ease;
    background: var(--tt-fg-0);
    box-shadow: 0 0 0 1px rgba(120, 125, 135, 0.55);
}
.si-alt-gauge-needle[data-zone="btc"] { background: var(--tt-down); }
.si-alt-gauge-needle[data-zone="mid"] { background: var(--tt-fg-0); }
.si-alt-gauge-needle[data-zone="alt"] { background: var(--tt-up); }

/* 0 / 25 / 75 / 100 anchored at the gauge's tick positions. Absolute
   positioning keeps the labels aligned with the 25 / 75 tick lines
   even if the track width changes (responsive). */
.si-alt-gauge-scale {
    position: relative;
    height: 14px;
    font-size: 12px;
    color: var(--tt-fg-0);
    opacity: 0.6;
}
.si-alt-gauge-scale > span {
    position: absolute;
    top: 0;
    transform: translateX(-50%);
    line-height: 1;
}
.si-alt-gauge-scale > span:nth-child(1) { left: 0;   transform: translateX(0); }
.si-alt-gauge-scale > span:nth-child(2) { left: 25%; }
.si-alt-gauge-scale > span:nth-child(3) { left: 75%; }
.si-alt-gauge-scale > span:nth-child(4) { left: 100%; transform: translateX(-100%); }
.si-alt-gauge-zones {
    display: flex;
    justify-content: space-between;
    font-size: 12px;
    color: var(--tt-fg-0);
    opacity: 0.7;
}

/* ── P2-1 TopTrader vs Retail L/S dual-line sparkline ───────────── */
.si-tt-ls {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.si-tt-ls-chart {
    width: 100%;
    height: 96px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    background: transparent;
}
.si-tt-ls-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    font-size: 12px;
    color: var(--tt-fg-0);
}
.si-tt-ls-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.si-tt-ls-swatch {
    display: inline-block;
    width: 14px;
    height: 2px;
    border-radius: 1px;
}
.si-tt-ls-swatch--acc {
    background: var(--accent-color, #2962ff);
}
.si-tt-ls-swatch--oi {
    background: var(--tt-fg-0);
    background-image: repeating-linear-gradient(90deg,
        var(--tt-fg-0) 0 4px, transparent 4px 7px);
}
.si-tt-ls-value {
    font-family: var(--font-price);
    font-weight: 600;
    margin-left: 4px;
}

/* ── P2-4 Funding Arb 2-venue flow diagram ──────────────────────── */
.si-arb-card {
    display: grid;
    grid-template-columns: 1fr auto 1fr auto;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    background: transparent;
}
.si-arb-leg {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
.si-arb-leg-label {
    font-size: 12px;
    color: var(--tt-fg-0);
    opacity: 0.7;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}
.si-arb-leg--long  .si-arb-leg-label { color: var(--tt-up); opacity: 1; }
.si-arb-leg--short .si-arb-leg-label { color: var(--tt-down); opacity: 1; }
.si-arb-leg-venue {
    font-size: 14px;
    font-weight: 600;
    color: var(--tt-fg-0);
    text-transform: capitalize;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.si-arb-leg-rate {
    font-size: 12px;
    font-family: var(--font-price);
    color: var(--tt-fg-0);
    opacity: 0.85;
}
.si-arb-arrow {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--tt-fg-0);
    opacity: 0.7;
}
.si-arb-arrow svg {
    width: 22px;
    height: 22px;
}
.si-arb-apr {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 2px;
    padding-left: 10px;
    border-left: 1px solid var(--border-color);
}
.si-arb-apr-label {
    font-size: 12px;
    color: var(--tt-fg-0);
    opacity: 0.6;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}
.si-arb-apr-value {
    font-size: 18px;
    font-family: var(--font-price);
    font-weight: 700;
    color: var(--tt-fg-0);
}
.si-arb-apr-value.up   { color: var(--tt-up); }
.si-arb-apr-value.down { color: var(--tt-down); }

/* ── Mobile responsiveness for P2 batch ─────────────────────────── */
@media (max-width: 600px) {
    .si-sent-row {
        grid-template-columns: 1fr;
    }
    .si-arb-card {
        grid-template-columns: 1fr 1fr;
        grid-template-rows: auto auto;
        gap: 8px;
    }
    .si-arb-arrow { display: none; }
    .si-arb-apr {
        grid-column: 1 / -1;
        border-left: none;
        border-top: 1px solid var(--border-color);
        padding-left: 0;
        padding-top: 6px;
        flex-direction: row;
        align-items: baseline;
        justify-content: space-between;
    }
    .si-tt-ls-chart { height: 80px; }
}


/* ============================================================================
 * [ADD] 2026-05-27 — News drawer (chart-page right-side NON-MODAL panel)
 *
 * The drawer is a singleton aside#newsDrawer rendered next to #aiPanel.
 *
 * [CHG] 2026-05-27 (round 2) — Switched from modal-dialog (400px + backdrop +
 * body-scroll-lock + aria-modal=true) to NON-MODAL side panel (340px, NO
 * backdrop, NO body lock, aria-modal=false), mirroring the AI panel pattern
 * (#aiPanel @ right:46px, top:44px). User feedback: original modal width hid
 * the chart and the backdrop blocked clicks on chart controls.
 *
 * Geometry (desktop, > 768px):
 *   - width        : 720px (aligned with AI panel default width)
 *   - min-width    : 320px (same as AI panel)
 *   - max-width    : calc(100vw - 46px) (same right-toolbar clearance)
 *   - top          : 44px   (clears the top toolbar; same as #aiPanel)
 *   - right        : 46px   (sits to the LEFT of the right toolbar, not
 *                            over it; identical offset to #aiPanel)
 *   - bottom       : 0
 *   - z-index      : 1100   (above chart layers / overlays, below modals)
 *
 * Mobile (≤ 768px): the @media block below restores the full-screen sheet
 * behavior (entry from More menu, viewport-wide, body-locked) because the
 * non-modal "peek" pattern doesn't translate to phones.
 *
 * Theme variables reused (no new tokens introduced):
 *   --bg-primary / --bg-secondary, --text-primary / --text-secondary,
 *   --border-color, --accent-color (#2962ff)
 * ============================================================================ */
.news-drawer {
    /* [CHG] 2026-05-27 round 5 — align with AI panel width model.
       User requested the News drawer "same width as the AI panel + draggable
       left edge". We adopt the same CSS variable pattern as .ai-panel:
       - --news-drawer-w (default 720px) is mutated by tt.news.drawer.js
         #startResize on left-edge drag, persisted to localStorage.
       - min 320px (compact), max calc(100vw - 46px).
       - drag below 280px (COLLAPSE_THRESHOLD) auto-closes the drawer.
       The chrome (border-radius / shadow / no border-left in dark theme)
       also mirrors .ai-panel so both right-side panels feel consistent. */
    --news-drawer-w: 720px;
    position: fixed;
    top: 44px;
    right: 46px;
    bottom: 0;
    width: var(--news-drawer-w);
    min-width: 320px;
    max-width: calc(100vw - 46px);
    background: var(--chart-popup-bg);
    color: var(--text-primary, #fff);
    /* [CHG] 2026-05-27 round 5 — drop border-left to mirror .ai-panel;
       the resize handle below carries the left edge visual. */
    border: 1px solid var(--chart-popup-border);
    border-left: 0;
    border-top-left-radius: 10px;
    border-top-right-radius: 10px;
    z-index: 9300;
    display: flex;
    flex-direction: column;
    transform: translateX(calc(var(--news-drawer-w) + 46px));
    transition: transform .22s cubic-bezier(.4, 0, 0.2, 1);
    will-change: transform;
    box-shadow:
        -12px 0 28px rgba(0, 0, 0, 0.55);
    overflow: hidden;
    pointer-events: none;
}
/* Light theme: restore a faint neutral border so the white panel is
   distinguishable against light chart canvases (same rationale as .ai-panel). */
body:not(.dark-theme) .news-drawer {
    border: 1px solid var(--chart-popup-border);
    border-left: 0;
    box-shadow: -8px 0 24px rgba(0, 0, 0, 0.18);
    background: var(--chart-popup-bg);
}
.news-drawer.is-open {
    transform: translateX(0);
    pointer-events: auto;
}
/* Disable slide transition while dragging so width changes feel direct. */
.news-drawer.is-resizing { transition: none !important; }
.news-drawer[hidden] { display: none !important; }

/* Left-edge resize handle. Mirrors .ai-panel__resize:
 *   - 6px wide hit zone at left: -3px (straddles the panel edge)
 *   - 1px neutral grip line at rest (centred in the 6px hit zone)
 *   - widens to 2px brand-blue on hover / during drag
 *   - hidden on mobile (full-screen sheet has no left edge to drag)
 * Visual contract: stays exactly in sync with .ai-panel__resize so both
 * right-side panels share the same affordance. */
.news-drawer__resize {
    position: absolute;
    left: -3px;
    top: 10px;
    bottom: 0;
    width: 6px;
    cursor: ew-resize;
    z-index: 2;
    background: transparent;
    transition: none;
    background-image: none;
}
.news-drawer__resize:hover,
.news-drawer.is-resizing .news-drawer__resize {
    background-image: none;
}
body:not(.dark-theme) .news-drawer__resize {
    background-image: none;
}
body:not(.dark-theme) .news-drawer__resize:hover,
body:not(.dark-theme) .news-drawer.is-resizing .news-drawer__resize {
    background-image: none;
}
/* Visual hint while user drags toward the COLLAPSE_THRESHOLD zone. */
.news-drawer.is-collapse-armed {
    box-shadow:
        -12px 0 28px rgba(239, 83, 80, 0.45);
}

.news-drawer__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 12px;
    border-bottom: 1px solid var(--chart-popup-border);
    background: var(--chart-popup-bg);
    min-height: 44px;
    flex-shrink: 0;
}
.news-drawer.is-list-mode .news-drawer__head--list {
    position: relative;
}
.news-drawer__head-actions {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
}
.news-drawer__icon-btn {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-secondary, #666);
    cursor: pointer;
    padding: 6px;
    border-radius: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.news-drawer__icon-btn:hover { color: var(--text-primary, #000); }
#newsDrawer .news-drawer__more-menu {
    position: absolute;
    top: calc(100% - 2px);
    right: 44px;
    min-width: 220px;
    background: var(--popover-bg, var(--chart-popup-bg));
    border: 1px solid var(--popover-border, var(--chart-popup-border));
    border-radius: 4px;
    box-shadow: var(--popover-shadow, 0 4px 16px rgba(0, 0, 0, .18));
    padding: 6px 0;
    z-index: 50;
}
#newsDrawer .news-drawer__more-menu[hidden] { display: none; }
.news-drawer__more-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 10px 14px;
    margin: 0;
    background: transparent;
    border: 0;
    text-align: left;
    font-size: 14px;
    color: var(--text-primary, #000);
    cursor: pointer;
    font-family: inherit;
    line-height: 1.3;
    box-sizing: border-box;
}
.news-drawer__more-item:hover { background: var(--border-color, #eee); }
.news-drawer__more-item--check { cursor: pointer; }
.news-drawer__more-item--check input[type="checkbox"] {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
    margin: 0;
    pointer-events: none;
}
.news-drawer__more-check {
    flex-shrink: 0;
    width: 14px;
    opacity: 0;
    color: var(--text-primary, #000);
}
.news-drawer__more-item.nd-sort.is-active .news-drawer__more-check,
.news-drawer__more-item--check:has(input:checked) .news-drawer__more-check {
    opacity: 1;
}
.news-drawer__more-sep {
    height: 1px;
    background: var(--chart-popup-border, #eee);
    margin: 4px 0;
}
.news-drawer__more-item--legend {
    cursor: default;
    gap: 8px;
}
.news-drawer__more-item--legend:hover { background: transparent; }
.news-drawer__more-item--legend > span:last-child {
    flex: 1 1 auto;
    font-size: 14px;
    color: var(--text-secondary, #666);
}
.news-drawer__head-main {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 8px;
    min-width: 0;
    flex: 1 1 auto;
}
.news-drawer__section-title {
    margin: 0;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 14px;
    font-weight: 600;
    line-height: 1.25;
    color: var(--text-primary, #000);
    flex-shrink: 0;
    min-width: 0;
}
.news-drawer__section-icon {
    flex-shrink: 0;
    color: var(--text-secondary, #666);
}
.news-drawer__section-icon--news,
.news-drawer__section-icon--calendar {
    display: none;
}
.news-drawer.is-news-section .news-drawer__section-icon--news {
    display: block;
}
.news-drawer.is-calendar-section .news-drawer__section-icon--calendar {
    display: block;
}
.news-drawer__section-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.news-drawer__calendar-menu {
    display: none;
    position: relative;
    flex-shrink: 0;
}
.news-drawer.is-calendar-section .news-drawer__calendar-menu {
    display: block;
}
.news-drawer__calendar-menu-btn {
    width: 30px;
    height: 30px;
}
.news-drawer__calendar-menu-btn svg {
    width: 18px;
    height: 18px;
}
.news-drawer__calendar-menu-popover {
    top: calc(100% + 6px);
    right: 0;
    left: auto;
    min-width: 300px;
    margin: 0;
    padding: 6px;
}
.news-drawer__calendar-menu.open .news-drawer__calendar-menu-popover {
    display: block;
}
.news-drawer__calendar-menu-popover .news-drawer__cal-filter {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    padding: 10px;
    white-space: normal;
}
.tt-calendar-event-popover {
    display: block;
    position: fixed;
    top: auto;
    left: auto;
    width: min(340px, calc(100vw - 24px));
    min-width: 0;
    max-height: min(520px, calc(100vh - 24px));
    margin: 0;
    padding: 12px;
    overflow-y: auto;
}
.tt-calendar-event-popover__item + .tt-calendar-event-popover__item {
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--popover-border);
}
.tt-calendar-event-popover__title {
    margin: 0 0 6px;
    color: var(--text-primary);
    font-size: 14px;
    font-weight: 600;
}
.tt-calendar-event-popover__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 10px;
    color: var(--text-secondary);
    font-size: 12px;
}
.tt-calendar-event-popover__metrics {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 8px;
    margin-top: 8px;
}
.tt-calendar-event-popover__metric {
    min-width: 0;
    color: var(--text-secondary);
    font-size: 12px;
}
.tt-calendar-event-popover__metric strong {
    display: block;
    overflow: hidden;
    color: var(--text-primary);
    font-size: 14px;
    text-overflow: ellipsis;
}
.tt-calendar-event-popover__actions {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 12px;
}
.tt-calendar-event-popover__close {
    width: 100%;
    min-height: 34px;
    border: 1px solid var(--text-primary);
    border-radius: 6px;
    background: var(--text-primary);
    color: var(--bg-primary);
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}
.tt-calendar-event-popover__hide-chart {
    align-self: center;
    min-height: 28px;
    padding: 0 12px;
    border: 1px solid var(--border-color, rgba(255, 255, 255, 0.16));
    border-radius: 4px;
    background: transparent;
    color: var(--text-secondary);
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
}
.tt-calendar-event-popover__hide-chart:hover {
    color: var(--text-primary);
    background: rgba(255, 255, 255, 0.04);
}
body:not(.dark-theme) .tt-calendar-event-popover__hide-chart:hover {
    background: rgba(0, 0, 0, 0.04);
}
.news-drawer__primary {
    display: flex;
    align-items: stretch;
    gap: 0;
    width: 100%;
    border: 1px solid var(--chart-popup-border, rgba(255,255,255,0.08));
    border-radius: 6px;
    overflow: hidden;
    flex-shrink: 0;
}
body:not(.dark-theme) .news-drawer__primary {
    border-color: rgba(0, 0, 0, 0.1);
}
.nd-primary {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-secondary, #666);
    font-size: 14px;
    font-weight: 500;
    line-height: 1.2;
    padding: 8px 12px;
    min-height: 36px;
    flex: 1 1 0;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-align: center;
}
.nd-primary + .nd-primary {
    border-left: 1px solid var(--chart-popup-border, rgba(255,255,255,0.08));
}
body:not(.dark-theme) .nd-primary + .nd-primary {
    border-left-color: rgba(0, 0, 0, 0.1);
}
.nd-primary:hover { color: var(--text-primary, #000); }
.nd-primary.is-active {
    color: var(--text-primary, #000);
    background: var(--border-color, rgba(255,255,255,0.08));
}
body:not(.dark-theme) .nd-primary.is-active {
    background: rgba(0, 0, 0, 0.06);
}
.news-drawer__news-sub {
    min-width: 0;
}
.news-drawer__news-sub[hidden],
.news-drawer__news-controls[hidden],
.news-drawer__news-footer[hidden],
.news-drawer__calendar-controls[hidden],
.news-drawer__body--calendar[hidden] {
    display: none !important;
}
.news-drawer__news-controls {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 8px;
    padding: 4px 12px 6px;
    border-bottom: 1px solid var(--border-color, #eee);
    flex-shrink: 0;
}
.news-drawer__news-controls .nd-sort {
    font-size: 14px;
}
.news-drawer__calendar-controls {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 4px 12px 6px;
    border-bottom: 1px solid var(--border-color, #eee);
    flex-shrink: 0;
}
.news-drawer__cal-filter {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    color: var(--text-secondary, #666);
    white-space: nowrap;
    flex-shrink: 0;
}
.news-drawer__cal-filter .ca-toggle-wrap { margin-left: 0; }
.news-drawer__news-footer {
    padding: 8px 12px;
}
.news-drawer__news-footer .news-drawer__tabs {
    justify-content: center;
}
.macro-cal-head {
    display: none;
}
.macro-cal-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.macro-cal-row {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
    padding: 10px 12px;
    border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.06));
    font-size: 14px;
    line-height: 1.35;
}
body:not(.dark-theme) .macro-cal-row {
    border-bottom-color: rgba(0, 0, 0, 0.06);
}
.macro-cal-row--high {
    position: relative;
}
.macro-cal-row--high::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 3px;
    background: var(--accent-color, #2962ff);
}
.macro-cal-row__time-row {
    display: flex;
    align-items: center;
    min-width: 0;
}
.macro-cal-row__content {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    gap: 10px;
    min-width: 0;
}
.macro-cal-row__icon {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
}
.macro-cal-row__icon--high {
    color: var(--tt-down, #ef5350);
}
.macro-cal-row__icon--medium {
    color: #f59e0b;
}
.macro-cal-row__icon--low {
    color: var(--text-primary, #000);
}
.macro-cal-row__time {
    color: var(--text-secondary, #666);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    font-size: 12px;
    line-height: 1.25;
}
.macro-cal-row__body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.macro-cal-row__event {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    min-width: 0;
}
.macro-cal-row__flag {
    font-size: 16px;
    line-height: 1.25;
    flex-shrink: 0;
}
.macro-cal-row__impact {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex-shrink: 0;
    margin-top: 4px;
}
.macro-cal-row__impact--high { background: #ef5350; }
.macro-cal-row__impact--medium { background: #f59e0b; }
.macro-cal-row__impact--low { background: var(--text-secondary, #787b86); }
.macro-cal-row__event-name {
    flex: 1 1 auto;
    min-width: 0;
    color: var(--text-primary, #000);
    font-size: 14px;
    font-weight: 500;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.macro-cal-row__event-name--high {
    color: var(--tt-down, #ef5350);
}
.macro-cal-row__event-name--medium {
    color: #f59e0b;
}
.macro-cal-row__metrics {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 8px;
}
.macro-cal-row__metric {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
.macro-cal-row__metric-label {
    font-size: 12px;
    color: var(--text-secondary, #666);
}
.macro-cal-row__val {
    text-align: left;
    color: var(--text-primary, #000);
    word-break: break-word;
    font-variant-numeric: tabular-nums;
}
.macro-cal-row__val--muted {
    color: var(--text-secondary, #666);
    font-style: italic;
}
.macro-cal-row__val--secondary {
    color: var(--text-secondary, #666);
}
.macro-cal-row__val--actual {
    font-weight: 600;
}
.macro-cal-row__val--better {
    color: var(--tt-up, #26a69a);
}
.macro-cal-row__val--worse {
    color: var(--tt-down, #ef5350);
}
.news-drawer__body--calendar {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.macro-cal-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    overscroll-behavior: contain;
}
.macro-cal-timezone-notice {
    flex-shrink: 0;
    margin: 0;
    padding: 8px 12px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--text-secondary, #666);
    border-top: 1px solid var(--border-color, #eee);
}
.news-drawer__calendar-controls .nd-sort {
    font-size: 14px;
}
.news-drawer__title {
    margin: 0;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
    color: var(--text-primary, #000);
    font-size: 14px;
    font-weight: 700;
    line-height: 1;
}
.news-drawer__title--detail {
    flex: 1 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.news-drawer__tabs {
    display: flex;
    gap: 4px;
    min-width: 0;
    overflow-x: auto;
}
.nd-tab {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-secondary, #666);
    font-size: 14px;
    padding: 6px 12px;
    border-radius: 4px;
    cursor: pointer;
    line-height: 1.2;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.nd-tab__icon { flex-shrink: 0; color: currentColor; opacity: .85; }
.nd-tab:hover .nd-tab__icon,
.nd-tab.is-active .nd-tab__icon { opacity: 1; }
.nd-tab:hover { color: var(--text-primary, #000); }
.nd-tab.is-active {
    color: var(--text-primary, #000);
    background: var(--border-color, #eee);
}
.news-drawer__close:not(.ai-icon-btn--close) {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-secondary, #666);
    cursor: pointer;
    padding: 6px;
    border-radius: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.news-drawer__close:not(.ai-icon-btn--close):hover { color: var(--text-primary, #000); }

/* ====================================================================
   2026-05-28 — Sort sub-tabs (Top × Latest)
   ====================================================================
   Sits BETWEEN the primary tab row (Hot / Symbol) and the filter row
   (High importance only). Inspired by the Bloomberg Terminal news
   panel's segmented control: low-prominence chip-style buttons that
   indicate "you are looking at the Top sort of this feed".

   Visual contract:
     - inherits .nd-tab idle/hover/active colours so the two row types
       feel consistent;
     - smaller font (12px) and padding so it reads as a SECONDARY axis,
       not as a fourth top-level destination;
     - bottom border to attach the sort row to the filter row below
       (matches existing .news-drawer__filters separator pattern).
   ==================================================================== */
.news-drawer__sort {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 4px;
    padding: 4px 12px 6px;
    border-bottom: 1px solid var(--border-color, #eee);
}
.news-drawer__sort-group {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.nd-sort {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-secondary, #666);
    font-size: 12px;
    padding: 4px 10px;
    border-radius: 3px;
    cursor: pointer;
    line-height: 1.2;
    display: inline-flex;
    align-items: center;
    gap: 5px;
}
.nd-sort__icon { flex-shrink: 0; color: currentColor; opacity: .85; }
.nd-sort:hover .nd-sort__icon,
.nd-sort.is-active .nd-sort__icon { opacity: 1; }
.nd-sort:hover { color: var(--text-primary, #000); }
.nd-sort.is-active {
    color: var(--text-primary, #000);
    background: var(--border-color, #eee);
}

.news-drawer__filters {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 6px 12px;
    border-bottom: 1px solid var(--border-color, #eee);
    font-size: 12px;
    color: var(--text-secondary, #666);
    flex-shrink: 0;
}
.news-filter { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.news-filter input[type="checkbox"] { margin: 0; }

.news-drawer__body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    position: relative;
    overscroll-behavior: contain;
}

/* ─── Scrollbar styling: mirrors .symbol-list (markets panel)
 *   Industry pattern (Cursor / VSCode side-bar / TradingView markets panel):
 *     - 3px width, invisible at rest
 *     - thumb fades in to neutral grey when the user hovers the container
 *     - thumb darkens when the cursor lands directly on it
 *   Two scrollable containers in the drawer (list body + detail body)
 *   share the same chrome. The `.news-drawer:hover` sensor makes the
 *   thumb appear as soon as the cursor enters the drawer surface, not
 *   only when it precisely sits over the scroll track.
 *   Firefox falls back to `scrollbar-color` / `scrollbar-width: thin`. */
.news-drawer__body,
.macro-cal-scroll,
.news-drawer.is-detail-mode .news-drawer__detail {
    scrollbar-width: thin;
    scrollbar-color: transparent transparent;
}
.news-drawer__body::-webkit-scrollbar,
.macro-cal-scroll::-webkit-scrollbar,
.news-drawer.is-detail-mode .news-drawer__detail::-webkit-scrollbar {
    width: 3px;
    opacity: 0;
}
.news-drawer__body::-webkit-scrollbar-track,
.macro-cal-scroll::-webkit-scrollbar-track,
.news-drawer.is-detail-mode .news-drawer__detail::-webkit-scrollbar-track {
    background: transparent;
}
.news-drawer__body::-webkit-scrollbar-thumb,
.macro-cal-scroll::-webkit-scrollbar-thumb,
.news-drawer.is-detail-mode .news-drawer__detail::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 2px;
    transition: background 0.2s;
}
.news-drawer:hover .news-drawer__body::-webkit-scrollbar-thumb,
.news-drawer:hover .macro-cal-scroll::-webkit-scrollbar-thumb,
.news-drawer:hover.is-detail-mode .news-drawer__detail::-webkit-scrollbar-thumb {
    background: #c8ccd8;
}
.news-drawer__body::-webkit-scrollbar-thumb:hover,
.macro-cal-scroll::-webkit-scrollbar-thumb:hover,
.news-drawer.is-detail-mode .news-drawer__detail::-webkit-scrollbar-thumb:hover {
    background: #a0a4b0;
}
/* Firefox: brighten the thumb when the drawer is hovered. */
.news-drawer:hover .news-drawer__body,
.news-drawer:hover .macro-cal-scroll,
.news-drawer:hover.is-detail-mode .news-drawer__detail {
    scrollbar-color: #a0a4b0 transparent;
}
.news-list {
    list-style: none;
    padding: 0;
    margin: 0;
}
.news-item {
    position: relative;
    border-bottom: 1px solid var(--border-color, #eee);
}
.news-item:last-child { border-bottom: 0; }
.news-item--high {
    box-shadow: inset 3px 0 0 var(--accent-color, #2962ff);
}
.news-item--high::before {
    content: none;
    display: none;
}

/* ==== [ADD] 2026-05-27 v3.4 — infinite-scroll sentinel ==================
 * Footer-of-list element observed by IntersectionObserver. Static state
 * is fully invisible (the empty li still has block layout so the observer
 * picks up its bbox). When .is-loading is added during a page fetch we
 * show a centred 3-dot pulse so the user sees something is happening
 * (rather than wondering why the list just stopped). The pulse uses
 * `currentColor` so it auto-themes with the drawer's body text. */
.news-list-sentinel {
    list-style: none;
    height: 1px;       /* 1px instead of 0 so the observer has a bbox */
    padding: 0;
    margin: 0;
    pointer-events: none;
}
.news-list-sentinel.is-loading {
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-secondary);
}
.news-list-sentinel.is-loading::after {
    content: '\2022 \2022 \2022';   /* three bullet dots */
    letter-spacing: 4px;
    font-size: 14px;
    animation: nd-sentinel-pulse 1.2s ease-in-out infinite;
}
@keyframes nd-sentinel-pulse {
    0%, 100% { opacity: 0.4; }
    50%      { opacity: 1.0; }
}

.news-link {
    appearance: none;
    -webkit-appearance: none;
    border: 0;
    background: transparent;
    width: 100%;
    text-align: left;
    font: inherit;
    cursor: pointer;
    display: block;
    padding: 10px 12px;
    color: var(--text-primary, #000);
    text-decoration: none;
}
.news-link,
.news-link:visited,
.news-link:hover,
.news-link:focus-visible {
    color: var(--text-primary, #000);
    text-decoration: none;
}
.news-link * {
    color: inherit;
    text-decoration: none;
}
.news-link:hover,
.news-link:focus-visible {
    background: var(--border-color, #eee);
}
.news-title {
    font-size: 14px;
    line-height: 1.35;
    margin: 0 0 4px 0;
    color: var(--text-primary, #000);
    font-weight: 600;
    text-decoration: none;
    overflow-wrap: anywhere;
    word-break: break-word;
    /* Cap to 3 lines so a single mega-headline can't dominate the panel. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.news-link .news-title,
.news-link:visited .news-title,
.news-link:hover .news-title,
.news-link .news-title a {
    color: var(--text-primary, #000);
    text-decoration: none;
}
/* [CHG] 2026-05-27 v3 — summary in the LIST is now a paragraph preview,
   not a card. We removed the "AI" badge so this is just the second line
   of the article entry; visual weight is intentionally lower than the
   title so users still scan headlines first. */
.news-summary {
    font-size: 12px;
    line-height: 1.45;
    color: var(--text-secondary, #666);
    margin: 0 0 6px 0;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.news-meta {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    font-size: 12px;
    color: var(--text-secondary, #666);
}
.news-src-logo {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    object-fit: cover;
    background: var(--border-color, #eee);
    flex-shrink: 0;
}
.news-src-name { font-weight: 500; }
.news-time { color: var(--text-secondary, #666); }
.news-base {
    display: inline-block;
    padding: 1px 6px;
    border: 1px solid var(--border-color, #eee);
    border-radius: 10px;
    font-size: 11px;
    color: var(--text-primary, #000);
    line-height: 1.4;
}
/* Importance hint uses .news-item--high::before so row text stays aligned. */

/* Sentiment badge (small dot, semantic only — green/red are industry-standard). */
.news-sent {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    display: inline-block;
    flex-shrink: 0;
}
.news-sent--positive { background: var(--price-up,   #26a69a); }
.news-sent--negative { background: var(--price-down, #ef5350); }
.news-sent--neutral  { background: var(--text-secondary, #999); }

.news-empty,
.news-error {
    padding: 24px 16px;
    text-align: center;
    color: var(--text-secondary, #666);
    font-size: 13px;
}

.news-loading[hidden],
.news-empty[hidden],
.news-error[hidden] {
    display: none !important;
}

.news-loading:not([hidden]) {
    padding: 24px 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    color: var(--text-secondary, #666);
    font-size: 13px;
}

.news-loading__spinner {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    border: 2px solid var(--border-color, #d7d9df);
    border-top-color: var(--accent-color, #2962ff);
    animation: news-loading-spin 0.85s linear infinite;
    flex-shrink: 0;
}

@keyframes news-loading-spin {
    to { transform: rotate(360deg); }
}

/* [CHG] 2026-05-27 round 3 — Body scroll lock kept ONLY for true-mobile
 *   full-screen mode (see @media (max-width: 480px) block below). On desktop
 *   the page must remain interactive. */
.news-drawer-locked { /* desktop: no-op */ }

/* ─── List / Detail dual-view headers (2026-05-27 round 3)
 *   The drawer header has two layouts driven by `.is-list-mode` /
 *   `.is-detail-mode` on the .news-drawer root. Only one is visible at a
 *   time; the other is `display:none`. Both share the same 44px chrome
 *   height for visual continuity during the cross-fade. */
.news-drawer__head--list,
.news-drawer__head--detail { display: none; }
.news-drawer.is-list-mode   .news-drawer__head--list   { display: flex; }
.news-drawer.is-detail-mode .news-drawer__head--detail { display: flex; }

/* Sort row + list body: list mode only. */
.news-drawer.is-detail-mode .news-drawer__body,
.news-drawer.is-detail-mode .news-drawer__news-controls,
.news-drawer.is-detail-mode .news-drawer__calendar-controls,
.news-drawer.is-detail-mode .news-drawer__body--calendar { display: none; }
.news-drawer.is-detail-mode .news-drawer__more-menu { display: none !important; }

/* Detail view: visible in detail mode only.
   [FIX] 2026-05-27 round 4 — Removed standalone `display:none` line; the
   detail container is now ONLY shown when .is-detail-mode is on, and the
   geometry rules below (flex:1 + min-height:0 + overflow-y:auto) apply
   only at that time. min-height:0 is mandatory for inner overflow:auto to
   work inside a flex-column container — without it the flex item refuses
   to shrink below content height and the scrollbar never appears. */
.news-drawer__detail { display: none; }
.news-drawer.is-detail-mode .news-drawer__detail {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    overscroll-behavior: contain;
    padding: 12px;
}

/* Back button (detail header left) and detail crumb (truncated source). */
.news-drawer__back {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-primary, #000);
    cursor: pointer;
    padding: 6px;
    border-radius: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.news-drawer__back svg {
    width: 20px;
    height: 20px;
    stroke: currentColor;
}
.news-drawer__back:hover {
    color: var(--text-primary, #000);
    background: var(--border-color, rgba(255,255,255,0.08));
}
body:not(.dark-theme) .news-drawer__back:hover {
    background: rgba(0, 0, 0, 0.06);
}
.news-drawer__back:focus-visible {
    outline: 2px solid var(--accent-color, #2962ff);
    outline-offset: 2px;
}

.nd-detail-title {
    font-size: 18px;
    line-height: 1.35;
    color: var(--text-primary, #000);
    margin: 0 0 10px 0;
    word-wrap: break-word;
}
/* [CHG] 2026-05-27 v3 — detail-body. The AI summary is rendered AS BODY
   COPY (not as a labelled card). Per UX feedback the labelled "Summary"
   chip felt like a divider for an absent article. So this block is just
   the article paragraphs — large readable font, generous line-height. */
.nd-detail-body {
    margin: 0 0 14px 0;
    font-size: 14px;
    line-height: 1.65;
    color: var(--text-primary, #000);
}
.nd-detail-body p {
    margin: 0 0 10px 0;
    word-wrap: break-word;
}
.nd-detail-body p:last-child { margin-bottom: 0; }
.nd-detail-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-size: 12px;
    color: var(--text-secondary, #666);
    margin-bottom: 12px;
}
.nd-detail-meta .news-src-logo { width: 16px; height: 16px; }
.nd-detail-image {
    width: 100%;
    max-height: 200px;
    object-fit: cover;
    border-radius: 6px;
    background: var(--border-color, #2a2e39);
    display: block;
    margin-bottom: 12px;
}
.nd-detail-subtitle {
    font-size: 14px;
    line-height: 1.55;
    color: var(--text-primary, #000);
    margin: 0 0 14px 0;
    word-wrap: break-word;
}
.nd-detail-bases {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-bottom: 10px;
}
/* Authors line — small caption-style row above the keywords. */
.nd-detail-authors {
    display: flex;
    align-items: baseline;
    gap: 6px;
    flex-wrap: wrap;
    margin: 0 0 10px 0;
    font-size: 12px;
    color: var(--text-secondary, #666);
    line-height: 1.5;
}
.nd-detail-authors__label {
    color: var(--text-secondary, #666);
    font-weight: 500;
}
.nd-detail-authors__value {
    color: var(--text-primary, #fff);
    word-wrap: break-word;
    overflow-wrap: anywhere;
}
/* Keyword/category tag row. Visually quieter than .news-base so it
   doesn't compete with the BASE chips above. */
.nd-detail-tags-row {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 6px;
    margin: 0 0 14px 0;
}
.nd-detail-tag {
    display: inline-block;
    padding: 1px 8px;
    border: 1px solid var(--border-color, #2a2e39);
    border-radius: 10px;
    font-size: 11px;
    color: var(--text-secondary, #666);
    line-height: 1.6;
    background: transparent;
}
.nd-detail-actions {
    margin: 4px 0 16px 0;
}
.nd-detail-cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    padding: 10px 12px;
    border-radius: 6px;
    border: 1px solid var(--accent-color, #2962ff);
    color: var(--accent-color, #2962ff);
    background: transparent;
    font-size: 13px;
    font-weight: 500;
    text-decoration: none;
    cursor: pointer;
    transition: background .12s, color .12s;
    box-sizing: border-box;
    /* Allow long publisher names to wrap without breaking the CTA's
       single-row layout (small viewports). */
    text-align: center;
    word-wrap: break-word;
}
.nd-detail-cta:hover {
    background: var(--accent-color, #2962ff);
    color: #fff;
}
.nd-detail-cta:focus-visible {
    outline: 2px solid var(--accent-color, #2962ff);
    outline-offset: 2px;
}
.nd-detail-cta svg { width: 14px; height: 14px; flex-shrink: 0; }

/* ─── Mobile full-screen sheet (aligned with ai-panel's media query)
 *   [CHG] 2026-05-27 v2 — Aligned with the project-wide mobile breakpoint
 *   used by .ai-panel / .top-bar / .mobile-more-sheet:
 *     (max-width: 768px)                                       — true phones
 *     OR (max-width: 1366px) AND (hover:none) AND (pointer:coarse)
 *                                                              — tablets / touch
 *                                                                laptops
 *   Previous v1 was `(max-width: 480px)` only, which kept tablets and 13"
 *   touch laptops on the narrow desktop side-panel even though the rest
 *   of the UI (More sheet, AI panel, top bar) already treated them as
 *   "mobile". That mismatch surfaced on iPads landing on the chart page:
 *   News opened as a 720px side panel covering the chart even though the
 *   top bar had already collapsed into the mobile More sheet.
 *   Real desktops with mouse pointers stay on the side-panel UX because
 *   the second clause requires (hover:none)+(pointer:coarse).
 *   Safe-area insets honoured so close button clears notches/home bar. */
@media (max-width: 768px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    .news-drawer {
        /* On true-mobile reset the CSS var to fill the viewport. The
           transform formula uses --news-drawer-w so we must reset it or
           the slide-in animation will overshoot offscreen. */
        --news-drawer-w: 100vw;
        width: 100%;
        max-width: 100%;
        min-width: 0;
        top: 0;
        right: 0;
        /* [CHG] 2026-05-31 r4 — Stop ABOVE the mobile bottom-tab bar
         *   (same fix as .sc-drawer family). News drawer slides up
         *   from the bottom on mobile (translateY), so the bottom
         *   anchor needs to clear the 48 px tab strip + iOS safe-
         *   area. Without this the news article footer / pagination
         *   was being eaten by the tab bar. */
        bottom: calc(48px + env(safe-area-inset-bottom, 0px));
        left: 0;
        border-left: 0;
        border-top-left-radius: 0;
        border-top-right-radius: 0;
        border-top: 1px solid var(--border-color, #2a2e39);
        transform: translateY(100%);
        box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.45);
        padding-top: env(safe-area-inset-top, 0);
        /* [CHG] 2026-05-31 r4 — drop inner padding-bottom because
         *   the `bottom: calc(48px + safe-area-inset-bottom)` already
         *   reserves the safe area + tab bar slot. */
        padding-bottom: 0;
    }
    .news-drawer.is-open { transform: translateY(0); }
    /* Hide the resize handle on full-screen sheet — there's no left edge
       to drag, and the touch hit zone would intercept content scrolls. */
    .news-drawer__resize { display: none !important; }
    .news-drawer__head--list,
    .news-drawer__head--detail { min-height: 52px; padding: 10px 12px; }
    .news-drawer__close,
    .news-drawer__back { width: 44px; height: 44px; padding: 0; }
    .nd-tab { padding: 10px 14px; font-size: 14px; }
    .news-link { padding: 12px 14px; }
    .news-title { font-size: 14px; }
    .news-meta { gap: 6px; font-size: 12px; }
    .news-drawer__filters { padding: 8px 14px; }

    /* Mobile-only: lock body so iOS Safari doesn't rubber-band the chart
       underneath when the full-screen sheet is on screen. */
    .news-drawer-locked { overflow: hidden; touch-action: none; }
}
/* ============================================================================
 * END News drawer block (2026-05-27)
 * ============================================================================ */


/* ============================================================================
 * Script Detail Drawer (Phase 1 / P1-5, 2026-05-27)
 * ----------------------------------------------------------------------------
 * Visual contract:
 *   - Right-side fixed slide-out drawer; same geometry/animation as
 *     .news-drawer (top:44px / right:46px / bottom:0; --sc-drawer-w default
 *     720px; min 320px, max calc(100vw - 46px); draggable left edge).
 *   - Two stacked modes via class on the .sc-drawer root:
 *       .is-detail-mode  → script detail visible    (default)
 *       .is-author-mode  → author page visible (pushed)
 *     Author mode REPLACES detail body — they don't co-exist. Hash router
 *     handles transitions; CSS only does the show/hide.
 *   - Mobile (< 1024px): full-width drawer (`width: 100vw; right: 0`),
 *     drag-resize disabled, 44px touch targets on every button.
 *
 * Why not inherit .news-drawer styles?
 *   - Independent class names give the JS / mutex logic an unambiguous
 *     ownership of each panel. Sharing .news-drawer for two distinct
 *     features would couple their open/close lifecycles via specificity.
 *   - Most pixels are duplicated, but the duplication is intentional
 *     ("each drawer has its own visual identity / brand chip / lineage
 *     block"). Tokens (--bg-secondary / --text-primary / etc.) are shared
 *     so both drawers stay in sync on theme switches.
 *
 * Theme variables reused (no new tokens introduced):
 *   --bg-primary / --bg-secondary, --text-primary / --text-secondary,
 *   --border-color, --accent-color (#2962ff)
 * ============================================================================ */
.sc-drawer {
    --sc-drawer-w: 720px;
    position: fixed;
    top: 44px;
    right: 46px;
    bottom: 0;
    width: var(--sc-drawer-w);
    min-width: 320px;
    max-width: calc(100vw - 46px);
    background: var(--chart-popup-bg);
    color: var(--text-primary, #fff);
    border: 1px solid var(--chart-popup-border);
    border-left: 0;
    border-top-left-radius: 10px;
    border-top-right-radius: 10px;
    z-index: 9300;
    display: flex;
    flex-direction: column;
    transform: translateX(calc(var(--sc-drawer-w) + 46px));
    transition: transform .22s cubic-bezier(.4, 0, 0.2, 1);
    will-change: transform;
    box-shadow:
        -12px 0 28px rgba(0, 0, 0, 0.55);
    overflow: hidden;
    pointer-events: none;
}
body:not(.dark-theme) .sc-drawer {
    border: 1px solid var(--chart-popup-border);
    border-left: 0;
    box-shadow: -8px 0 24px rgba(0, 0, 0, 0.18);
    background: var(--chart-popup-bg);
}
.sc-drawer.is-open {
    transform: translateX(0);
    pointer-events: auto;
}
.sc-drawer.is-resizing { transition: none !important; }
.sc-drawer[hidden] { display: none !important; }

/* Left-edge resize handle. Mirrors .news-drawer__resize. */
.sc-drawer__resize {
    position: absolute;
    left: -3px;
    top: 10px;
    bottom: 0;
    width: 6px;
    cursor: ew-resize;
    z-index: 2;
    background: transparent;
    transition: none;
    background-image: none;
}
.sc-drawer__resize:hover,
.sc-drawer.is-resizing .sc-drawer__resize {
    background-image: none;
}
body:not(.dark-theme) .sc-drawer__resize {
    background-image: none;
}

/* [ADD] 2026-05-30 — Visual hint while user drags the resize handle into
   the COLLAPSE_THRESHOLD zone. Mirrors .ai-panel.is-collapse-armed and
   .news-drawer.is-collapse-armed. Applies to ALL .sc-drawer instances
   (DM / scriptDetail / scRun). The drag handler in tt.dm.drawer.js +
   tt.scripts.detail.js toggles this class as the cursor crosses the
   threshold. */
.sc-drawer.is-collapse-armed {
    box-shadow: -12px 0 28px rgba(239, 83, 80, 0.45);
}
.sc-drawer.is-collapse-armed .sc-drawer__resize {
    background-image: none;
}
/* Disable the slide animation while actively resizing (avoid lag). */
.sc-drawer.is-resizing {
    transition: none !important;
}

/* Header (detail + author modes). Display gated by .is-detail-mode /
   .is-author-mode classes on root. */
.sc-drawer__head {
    display: none;
    align-items: center;
    gap: 8px;
    padding: 12px 14px;
    border-bottom: 1px solid var(--chart-popup-border);
    background: var(--chart-popup-bg);
    flex: 0 0 auto;
    min-height: 48px;
}
body:not(.dark-theme) .sc-drawer__head {
    border-bottom-color: var(--chart-popup-border);
}
.sc-drawer.is-detail-mode .sc-drawer__head--detail { display: flex; }
.sc-drawer.is-author-mode .sc-drawer__head--author { display: flex; }
/* [CHG] 2026-05-31 — run drawer header markup renamed
   `.sc-drawer__head--detail` → `.sc-drawer__head--run` per 虎哥
   directive "title is title, nothing else": the run drawer no longer
   re-uses the detail-drawer header (which carries a status chip +
   arrow icon + title). The new header is purely "title + close" and
   carries its own modifier class so future divergent layouts don't
   bleed into the script-detail drawer. */
.sc-drawer.is-run-mode .sc-drawer__head--run { display: flex; }
/* [FIX] 2026-05-29 — drawer header title aligned with AI Editor's title
   contract (虎哥反馈): pure black/white text via --text-primary + 14px /
   600 weight, exactly matching .ai-panel__title above. Letter-spacing
   relaxed to 0.06em and weight bumped to 700 so the uppercase still
   reads as a section-title rather than a body-text fragment. */
.sc-drawer__type-chip {
    display: inline-flex;
    align-items: center;
    padding: 0;
    font-size: 14px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    background: transparent;
    color: var(--text-primary);
    flex: 1 1 auto;
    justify-self: flex-start;
}
.sc-drawer__head-actions {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 10px;
}
.sc-drawer__open-page {
    font-size: 12px;
    font-weight: 500;
    color: var(--text-primary);
    text-decoration: none;
    white-space: nowrap;
}
.sc-drawer__open-page:hover,
.sc-drawer__open-page:focus-visible {
    color: var(--accent-color, #2962ff);
    text-decoration: underline;
}
.sc-drawer__head-actions .drawer-head-close {
    margin-left: 0;
}
.sc-drawer__type-chip.is-strategy,
.sc-drawer__type-chip.is-library {
    background: transparent;
    color: var(--text-primary);
}
body:not(.dark-theme) .sc-drawer__type-chip,
body:not(.dark-theme) .sc-drawer__type-chip.is-strategy,
body:not(.dark-theme) .sc-drawer__type-chip.is-library {
    background: transparent;
    color: var(--text-primary);
}
.sc-drawer__author-title {
    flex: 1 1 auto;
    font-size: 15px;
    font-weight: 600;
    margin: 0;
    color: var(--text-primary);
}
.sc-drawer__close:not(.ai-icon-btn--close),
.sc-drawer__back {
    background: transparent;
    border: 0;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 6px;
    border-radius: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 0.12s ease, color 0.12s ease;
}
.sc-drawer__close:not(.ai-icon-btn--close):hover,
.sc-drawer__back:hover {
    background: rgba(255,255,255,0.06);
    color: var(--text-primary);
}
body:not(.dark-theme) .sc-drawer__close:not(.ai-icon-btn--close):hover,
body:not(.dark-theme) .sc-drawer__back:hover {
    background: rgba(0,0,0,0.06);
}

/* Body containers — exactly one is visible at a time. */
.sc-drawer__body {
    display: none;
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 16px 16px 24px;
    line-height: 1.55;
}
.sc-drawer.is-detail-mode .sc-drawer__body--detail { display: block; }
.sc-drawer.is-author-mode .sc-drawer__body--author { display: block; }
/* [CHG] 2026-05-31 — run drawer body renamed `--detail` → `--run` to
   match the header-class rename above; same display-block rule, kept
   on its own selector so detail-drawer and run-drawer can diverge
   in body padding / max-width later without cross-pollution. */
.sc-drawer.is-run-mode .sc-drawer__body--run { display: block; }

/* Detail content blocks ---------------------------------------------------- */
.sc-detail-title {
    font-size: 19px;
    font-weight: 600;
    margin: 0 0 6px;
    color: var(--text-primary);
    line-height: 1.3;
    word-break: break-word;
}
.sc-detail-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    font-size: 12px;
    color: var(--text-secondary);
    margin-bottom: 12px;
}
.sc-detail-meta .sc-detail-meta__sep::before {
    content: "·";
    margin-right: 12px;
}
.sc-detail-meta a {
    color: var(--accent-color, #2962ff);
    text-decoration: none;
    cursor: pointer;
}
.sc-detail-meta a:hover { text-decoration: underline; }
.sc-detail-meta__author {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
}
.sc-detail-meta__author .ys-script-card__author-avatar {
    margin-right: 0;
}

/* [ADD] 2026-05-28 — Phase 3a / P3a-5 — Protected badge in the detail
 * drawer title row. The badge is composed with `.tt-help--down` so the
 * hover tooltip drops below (titles sit near the top of the drawer, an
 * upward tooltip would clip). The lock icon is inline with the label so
 * the badge reads as a single unit. */
.sc-detail-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-left: 8px;
    padding: 2px 8px;
    font-size: 11px;
    font-weight: 600;
    border-radius: 10px;
    vertical-align: middle;
    line-height: 1.4;
    white-space: nowrap;
}
.sc-detail-badge--protected {
    color: var(--text-primary, #fff);
    background: rgba(247, 147, 26, 0.18);
    border: 1px solid rgba(247, 147, 26, 0.45);
}
body:not(.dark-theme) .sc-detail-badge--protected {
    color: #6a3500;
    background: rgba(247, 147, 26, 0.20);
    border-color: rgba(247, 147, 26, 0.50);
}
.sc-detail-badge--protected > svg {
    stroke: rgba(247, 147, 26, 0.95);
    flex: 0 0 auto;
}

/* [ADD] 2026-05-28 — Phase 3b — invite-only badge, sibling of --protected.
   Uses indigo (#2962ff family) to signal "server-run + invite required".
   Tooltip uses .tt-help--down (defined elsewhere) so visual is identical
   to other site-wide help tooltips per web_app_styling.mdc constraint 6. */
.sc-detail-badge--invite {
    color: var(--text-primary, #fff);
    background: rgba(41, 98, 255, 0.18);
    border: 1px solid rgba(41, 98, 255, 0.5);
}
body:not(.dark-theme) .sc-detail-badge--invite {
    color: #0a2873;
    background: rgba(41, 98, 255, 0.18);
    border-color: rgba(41, 98, 255, 0.55);
}
.sc-detail-badge--invite > svg {
    stroke: var(--text-primary);
    flex: 0 0 auto;
}

.sc-detail-badge__label { font-size: 12px; font-weight: 600; }

.sc-detail-stats {
    display: flex;
    gap: 16px;
    font-size: 13px;
    color: var(--text-secondary);
    margin-bottom: 16px;
    padding: 10px 12px;
    background: rgba(255,255,255,0.03);
    border: 1px solid var(--border-medium);
    border-radius: 6px;
}
body:not(.dark-theme) .sc-detail-stats {
    background: #f6f7fa;
    border-color: rgba(0,0,0,0.10);
}
body:not(.dark-theme) .sc-detail-stat strong { color: #1f2329; }
.sc-detail-stat {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.sc-detail-stat strong {
    color: var(--text-primary);
    font-size: 14px;
    font-weight: 600;
}

.sc-detail-section {
    margin: 16px 0;
}
/* [CHG] 2026-05-28 — Phase 2 / P2-13m — section label upgraded from a
   simple span to a flex row that hosts a 16px SVG glyph + uppercase
   text. Mirrors TradingView / Bloomberg block labels (icon + caption
   in muted hue). Icon stroke inherits currentColor → adapts to theme.
   Layout: 14→16px icon, 6px gap, 8px bottom margin (was 6 — bigger
   gap because the row is now taller).
   [FIX] 2026-05-29 — virhu Yao bug report: when a section authors
   used <span class="sc-detail-desc"> instead of <div> for the content
   (Chart context did this) the label and the content collapsed onto
   the same line because `display: inline-flex` keeps the label
   inline. Promote to block-level `flex` so the label always owns a
   line by itself regardless of what its sibling is — eliminates an
   entire class of "label is glued to content" bugs and preserves the
   8px margin-bottom unconditionally. All other section content
   wrappers are already block elements (<div>) so the visual layout
   is unchanged. */
.sc-detail-section__label {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-secondary);
    margin-bottom: 8px;
    display: flex;
    align-items: center;
    gap: 6px;
}
.sc-detail-section__icon {
    flex: 0 0 16px;
    color: inherit;
}
.sc-detail-desc {
    color: var(--text-primary);
    font-size: 13px;
    white-space: pre-wrap;
    word-break: break-word;
}
.sc-detail-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.sc-detail-tag {
    display: inline-flex;
    padding: 3px 9px;
    border-radius: 12px;
    background: rgba(255,255,255,0.06);
    color: var(--text-primary);
    font-size: 12px;
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease;
}
body:not(.dark-theme) .sc-detail-tag {
    background: rgba(0,0,0,0.06);
}
.sc-detail-tag:hover {
    background: rgba(41,98,255,0.15);
    color: var(--accent-color);
}

/* Fork lineage display ---------------------------------------------------- */
.sc-detail-lineage {
    background: rgba(255,255,255,0.03);
    border: 1px solid var(--border-medium);
    border-radius: 6px;
    padding: 10px 12px;
    font-size: 12px;
    color: var(--text-secondary);
}
body:not(.dark-theme) .sc-detail-lineage {
    background: #f6f7fa;
    border-color: rgba(0,0,0,0.10);
}
.sc-detail-lineage__link {
    color: var(--text-primary);
    cursor: pointer;
    text-decoration: none;
}
.sc-detail-lineage__link:hover { color: var(--accent-color); }
.sc-detail-lineage__arrow {
    margin: 0 6px;
    color: var(--text-secondary);
}

/* Actions row ------------------------------------------------------------- */
/* [REWRITE] 2026-05-28 — Phase 2 / P2-13m — TV-style compact action bar.
   Buttons are 32px tall (was 44px), font-size 12px, flex auto-grow so
   ALL fit on one row on common drawer widths (380px+). Primary action
   uses monochrome CTA (white-on-black dark / black-on-white light) so
   it doesn't compete with accent blue used elsewhere; secondary actions
   are subtle outline-on-surface that read on both themes.
   Removed: flex-basis 50%/100% (caused 2-row stack), 44px min-height,
   the "1 row primary + 2 row secondary" old layout. */
.sc-detail-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin: 16px 0 8px;
    padding-top: 14px;
    border-top: 1px solid var(--border-color, rgba(255,255,255,0.08));
}
body:not(.dark-theme) .sc-detail-actions {
    border-top-color: rgba(0,0,0,0.10);
}
.sc-detail-action {
    flex: 1 1 auto;
    min-height: 32px;
    padding: 0 12px;
    border-radius: 5px;
    border: 1px solid var(--border-color, rgba(255,255,255,0.12));
    background: transparent;
    color: var(--text-primary);
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    line-height: 1;
    transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
/* [ADD] 2026-05-28 — Phase 2 / P2-13o — leading icon sizing inside
   action button. flex-shrink: 0 keeps the icon visible even if the
   label gets squeezed (small-viewport bottom-sheet). currentColor
   inheritance lets primary/favorited variants pick up the right hue. */
.sc-detail-action svg {
    flex: 0 0 14px;
    width: 14px;
    height: 14px;
    display: inline-block;
}
.sc-detail-action__label {
    line-height: 1;
    white-space: nowrap;
}
body:not(.dark-theme) .sc-detail-action {
    border-color: rgba(0,0,0,0.16);
    color: #1f2329;
}
.sc-detail-action:hover {
    background: rgba(255,255,255,0.06);
    border-color: rgba(255,255,255,0.30);
}
body:not(.dark-theme) .sc-detail-action:hover {
    background: rgba(0,0,0,0.05);
    border-color: rgba(0,0,0,0.32);
}
.sc-detail-action.is-primary {
    background: #ffffff;
    color: var(--text-primary);
    border-color: #ffffff;
    font-weight: 600;
    flex: 0 0 auto;
    min-width: 110px;
}
.sc-detail-action.is-primary:hover {
    background: #e9ecef;
    border-color: #e9ecef;
    color: var(--text-primary);
}
body:not(.dark-theme) .sc-detail-action.is-primary {
    background: #000000;
    color: #ffffff;
    border-color: #000000;
}
body:not(.dark-theme) .sc-detail-action.is-primary:hover {
    background: #1f2329;
    border-color: #1f2329;
    color: #ffffff;
}
/* [CHG] 2026-05-28 — Phase 2 / P2-13o — Favorited state visual:
   dark theme: white text on transparent + low-opacity border
   light theme: black text on transparent + low-opacity border
   Filled star icon still uses fill=currentColor so it picks up the
   right hue.
   [CHG] 2026-05-30 — §A14 — virhu Yao directive: keep the border
   visible in the favorited state. Previously border-color was
   transparent (matching the TV/Binance "saved chrome strip" style),
   but the result looked like the button had disappeared rather than
   flipped into an active state. Now we keep the same border weight
   as the inactive state — same visual hit-box, just the icon fills
   to telegraph the state. */
.sc-detail-action.is-favorited {
    color: #ffffff;
    border-color: rgba(255,255,255,0.16);
    background: transparent;
}
.sc-detail-action.is-favorited:hover {
    background: rgba(255,255,255,0.06);
    border-color: rgba(255,255,255,0.30);
}
body:not(.dark-theme) .sc-detail-action.is-favorited {
    color: var(--text-primary);
    border-color: rgba(0,0,0,0.16);
    background: transparent;
}
body:not(.dark-theme) .sc-detail-action.is-favorited:hover {
    background: rgba(0,0,0,0.05);
    border-color: rgba(0,0,0,0.32);
}
.sc-detail-action.is-favorited svg { fill: currentColor; }
.sc-detail-action.is-favorited svg path {
    fill: currentColor;
    stroke: currentColor;
}
.sc-detail-action[disabled],
.sc-detail-action.is-disabled {
    opacity: 0.55;
    cursor: not-allowed;
}
/* [ADD] 2026-05-30 — §A14 — Disabled primary CTA is rendered as a
   true grey button (not a half-transparent white/black). Used by the
   invite_only Add-to-chart placeholder so users see "there's an
   action here but it's not the right entry point" — they still get a
   click-toast that points them to the Invited tab. The hint is also
   surfaced via native `title=` for keyboard / hover discovery. */
.sc-detail-action.is-primary.is-disabled {
    background: #5a5d66;
    color: #cccccc;
    border-color: #5a5d66;
    opacity: 1;
    cursor: not-allowed;
}
.sc-detail-action.is-primary.is-disabled:hover {
    background: #6a6d76;
    border-color: #6a6d76;
    color: #ffffff;
}
body:not(.dark-theme) .sc-detail-action.is-primary.is-disabled {
    background: #d1d5db;
    color: #6b7280;
    border-color: #d1d5db;
}
body:not(.dark-theme) .sc-detail-action.is-primary.is-disabled:hover {
    background: #c4c8ce;
    border-color: #c4c8ce;
    color: #4b5563;
}
/* [ADD] 2026-06-02 — virhu round 11: the detail-drawer Run button no
   longer uses .is-primary (visual parity with the surrounding ghost
   buttons — Favorite / Run on Server / Copy link / Report). The
   disabled state for that ghost button still needs to read as
   "explicitly not actionable", so we lock in a solid grey body
   matching the previous .is-primary.is-disabled treatment but
   without the white/black CTA chrome. Hover keeps the same grey so
   the button doesn't appear to wake up when the cursor passes over. */
.sc-detail-action.is-disabled {
    background: #5a5d66;
    color: #cccccc;
    border-color: #5a5d66;
    opacity: 1;
}
.sc-detail-action.is-disabled:hover {
    background: #5a5d66;
    border-color: #5a5d66;
    color: #cccccc;
}
body:not(.dark-theme) .sc-detail-action.is-disabled {
    background: #d1d5db;
    color: #6b7280;
    border-color: #d1d5db;
}
body:not(.dark-theme) .sc-detail-action.is-disabled:hover {
    background: #d1d5db;
    border-color: #d1d5db;
    color: #6b7280;
}

/* Inline notice (e.g. "library cannot be added to chart") ---------------- */
.sc-detail-notice {
    margin-top: 12px;
    padding: 10px 12px;
    border-radius: 6px;
    background: rgba(255, 152, 0, 0.10);
    color: #ff9800;
    font-size: 12px;
    line-height: 1.5;
}

/* Author view -------------------------------------------------------------- */
.sc-author-card {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 0 16px;
    border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.06));
    margin-bottom: 16px;
}
body:not(.dark-theme) .sc-author-card {
    border-bottom-color: rgba(0,0,0,0.08);
}
.sc-author-avatar {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    overflow: hidden;
    flex: 0 0 48px;
}
.sc-author-avatar img { width: 100%; height: 100%; object-fit: cover; }
.sc-author-name {
    font-size: 16px;
    font-weight: 600;
    margin: 0;
    color: var(--text-primary);
}
.sc-author-since {
    font-size: 12px;
    color: var(--text-secondary);
    margin-top: 2px;
}
/* [ADD] 2026-05-30 — secondary handle line under display_name in author panel */
.sc-author-handle {
    font-size: 12px;
    color: var(--text-secondary);
    margin-top: 1px;
    font-family: var(--font-mono, ui-monospace, monospace);
}
/* [ADD] 2026-05-30 — DM CTA on script-detail header (next to "by author").
   Visual: filled brand-blue pill so it reads as a primary action even
   when scanned at the meta line height. Mirrors the "primary" intent of
   the Message-author CTA below in the author 2nd-face panel. */
.sc-detail-author-dm {
    margin-left: 8px;
    padding: 4px 6px;
    font-size: 12px;
    line-height: 1;
    border: 1px solid var(--accent-color, #2962ff);
    background: var(--accent-color, #2962ff);
    color: #ffffff;
    border-radius: 4px;
    cursor: pointer;
    vertical-align: middle;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.sc-detail-author-dm svg {
    display: block;
}
.sc-detail-author-dm:hover {
    background: #1e4ed8;
    border-color: #1e4ed8;
}
/* [ADD] 2026-05-29 — Author action row (currently hosts only the
   "Message author" CTA; future: Follow / Block live next to it). */
.sc-author-actions {
    display: flex;
    gap: 8px;
    margin: 8px 0 12px 0;
}
.sc-author-action {
    border: 1px solid var(--border-color, rgba(120, 123, 134, 0.25));
    background: transparent;
    color: var(--tt-fg-0, #ffffff);
    border-radius: 4px;
    padding: 6px 14px;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: background-color .12s ease;
}
.sc-author-action:hover {
    background: var(--hover-bg, rgba(120, 123, 134, 0.10));
    border-color: var(--accent-color, #2962ff);
    color: var(--accent-color, #2962ff);
}
.sc-author-dm {
    /* DM CTA emphasises with accent color so users find the new feature. */
    border-color: var(--accent-color, #2962ff);
    color: var(--accent-color, #2962ff);
}
.sc-author-stats {
    display: flex;
    gap: 14px;
    font-size: 12px;
    color: var(--text-secondary);
    margin-bottom: 16px;
}
.sc-author-stats strong {
    color: var(--text-primary);
    font-size: 13px;
    font-weight: 600;
    margin-right: 3px;
}

/* Loading / error placeholders ------------------------------------------- */
.sc-drawer__loading,
.sc-drawer__error {
    padding: 32px 16px;
    text-align: center;
    font-size: 13px;
    color: var(--text-secondary);
}
.sc-drawer__error { color: var(--text-secondary); }

/* ============================================================================
 * Notification Drawer (Slice 2, 2026-05-30)
 *   #notifDrawer reuses .sc-drawer for the slide-in shell + resize handle
 *   + mobile bottom-sheet adaptation. This section adds:
 *     1. is-inbox-mode display gating (header + body) — mirrors the dm-drawer
 *        pattern so the base .sc-drawer__head / .sc-drawer__body are
 *        visible when the inbox class is on.
 *     2. .notif-drawer__head — title + actions row.
 *     3. .notif-drawer__tabs — segmented control for category filter.
 *     4. .notif-drawer__list / .notif-drawer__row — list rendering.
 *     5. .notif-drawer__empty / __loading / __error — placeholder states.
 *
 *   Selector discipline: All .notif-drawer__* rules are scoped from the
 *   .notif-drawer root so they cannot leak into other .sc-drawer instances
 *   that share the same .is-inbox-mode modifier (currently only dm-drawer
 *   uses it). No new theme tokens introduced.
 * ============================================================================ */
.sc-drawer.is-inbox-mode .sc-drawer__head { display: flex; }
.sc-drawer.is-inbox-mode .sc-drawer__body { display: block; }

.notif-drawer__head {
    align-items: center;
    gap: 8px;
}
.notif-drawer__title {
    flex: 1 1 auto;
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    margin: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* [CHG] 2026-05-31 — unify all right-drawer headers to DM header contract.
 * Contract source: #dmDrawer .dm-head (52px height, dark/light background split,
 * 14px title size + primary text color). Applies to AI / News / Scripts /
 * Notifications drawers so the right rail reads as one system. */
.ai-panel__header,
.news-drawer__head,
.sc-drawer__head {
    height: 52px;
    min-height: 52px;
    background: var(--drawer-head-bg, var(--chart-popup-bg));
    border-bottom: 1px solid var(--chart-popup-border);
}
body:not(.dark-theme) :is(.ai-panel__header, .news-drawer__head, .sc-drawer__head) {
    background: var(--bg-tertiary, #f6f8fb);
    border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}

.ai-panel__title,
.sc-drawer__type-chip,
.sc-drawer__author-title,
.sc-drawer__title,
.notif-drawer__title,
.news-drawer__title,
.dm-head__title {
    font-size: 14px;
    color: var(--text-primary);
}
.ai-panel__title,
.sc-drawer__author-title,
.sc-drawer__title,
.notif-drawer__title {
    font-weight: 700;
}
.drawer-head-title__icon {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    flex-shrink: 0;
    opacity: 0.9;
}
.dm-head__title,
.sc-drawer__type-chip,
.sc-drawer__author-title,
.sc-drawer__title,
.notif-drawer__title {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}
.sc-drawer__title--run {
    flex: 1 1 auto;
    margin: 0 8px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.drawer-head-close {
    position: relative;
    margin-left: 0;
}
.drawer-head-close::before {
    content: "";
    position: absolute;
    left: -8px;
    top: 50%;
    width: 1px;
    height: 18px;
    transform: translateY(-50%);
    background: rgba(255, 255, 255, 0.58);
    opacity: 0;
}
:is(.ai-panel__header, .news-drawer__head, .sc-drawer__head, .dm-head):has(> :not(.drawer-head-close):not([hidden])) > .drawer-head-close {
    margin-left: 10px;
}
:is(.ai-panel__header, .news-drawer__head, .sc-drawer__head, .dm-head):has(> :not(.drawer-head-close):not([hidden])) > .drawer-head-close::before {
    opacity: 1;
}
body:not(.dark-theme) .drawer-head-close::before {
    background: rgba(0, 0, 0, 0.30);
}
.notif-drawer__head-actions {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex: 0 0 auto;
}
/* More menu — absolute inside drawer (mirrors #dmDrawer .dm-more-menu). */
#notifDrawer .notif-drawer__more-menu {
    position: absolute;
    top: calc(52px + 2px);
    right: 8px;
    min-width: 210px;
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 4px;
    box-shadow: var(--popover-shadow);
    padding: 6px 0;
    z-index: 50;
}
#notifDrawer .notif-drawer__more-menu[hidden] { display: none; }
#notifDrawer .notif-drawer__more-menu-item[hidden] { display: none; }
#notifDrawer .notif-drawer__more-menu-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    background: transparent;
    border: 0;
    width: 100%;
    text-align: left;
    font-size: 14px;
    color: var(--text-primary);
    cursor: pointer;
    transition: background 0.12s ease;
    font-family: inherit;
}
#notifDrawer .notif-drawer__more-menu-item > svg {
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    color: var(--text-secondary);
}
#notifDrawer .notif-drawer__more-menu-item:hover { background: rgba(255, 255, 255, 0.06); }
body:not(.dark-theme) #notifDrawer .notif-drawer__more-menu-item:hover { background: rgba(0, 0, 0, 0.05); }
#notifDrawer .notif-drawer__more-menu-item.is-danger { color: #ef4444; }
#notifDrawer .notif-drawer__more-menu-item.is-danger > svg { color: #ef4444; }
#notifDrawer .notif-drawer__more-menu-item.is-danger:hover { background: rgba(239, 68, 68, 0.12); }
#notifDrawer .notif-drawer__more-menu-sep {
    height: 1px;
    background: var(--border-color, rgba(255, 255, 255, 0.08));
    margin: 4px 0;
}
body:not(.dark-theme) #notifDrawer .notif-drawer__more-menu-sep {
    background: rgba(0, 0, 0, 0.08);
}

/* Tabs row — segmented control between header and body. */
.notif-drawer__tabs {
    display: flex;
    gap: 0;
    border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.06));
    background: transparent;
    flex: 0 0 auto;
    padding: 0 8px;
    overflow-x: auto;
    overflow-y: hidden;
}
body:not(.dark-theme) .notif-drawer__tabs {
    border-bottom-color: rgba(0,0,0,0.08);
}
.notif-drawer__tab {
    background: transparent;
    border: 0;
    color: var(--text-secondary);
    font-size: 12px;
    font-weight: 500;
    padding: 10px 12px;
    cursor: pointer;
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
    transition: color 0.12s ease;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}
.notif-drawer__tab:hover {
    color: var(--text-primary);
}
.notif-drawer__tab.is-active {
    color: var(--text-primary);
    border-bottom-color: #2962ff;
}
.notif-drawer__tab-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 8px;
    background: #ef4444;
    color: #ffffff;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    pointer-events: none;
}
.notif-drawer__tab-badge[hidden] { display: none !important; }

/* Body — list scroll area. Override the base .sc-drawer__body padding so
   rows can run full-bleed; rows themselves provide internal padding. */
.notif-drawer__body {
    padding: 0 !important;
    contain: layout;
}
.notif-drawer__list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.notif-drawer__row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.04));
    cursor: pointer;
    transition: background 0.12s ease;
}
body:not(.dark-theme) .notif-drawer__row {
    border-bottom-color: rgba(0,0,0,0.06);
}
.notif-drawer__row:last-child { border-bottom: 0; }
.notif-drawer__row:hover { background: rgba(255,255,255,0.04); }
body:not(.dark-theme) .notif-drawer__row:hover { background: rgba(0,0,0,0.03); }
.notif-drawer__row.is-unread { background: rgba(41, 98, 255, 0.06); }
.notif-drawer__row.is-unread:hover { background: rgba(41, 98, 255, 0.10); }
body:not(.dark-theme) .notif-drawer__row.is-unread { background: rgba(41, 98, 255, 0.06); }

.notif-drawer__row-dot {
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    margin-top: 6px;
    background: transparent;
}
.notif-drawer__row.is-unread .notif-drawer__row-dot {
    background: #2962ff;
}
.notif-drawer__row-main {
    flex: 1 1 auto;
    min-width: 0;
}
.notif-drawer__row-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0 0 2px 0;
    line-height: 1.3;
    word-break: break-word;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.notif-drawer__row-body {
    font-size: 12px;
    color: var(--text-secondary);
    margin: 0 0 4px 0;
    line-height: 1.4;
    word-break: break-word;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.notif-drawer__row-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    color: var(--text-secondary);
    opacity: 0.85;
}
.notif-drawer__row-kind {
    display: inline-flex;
    align-items: center;
    padding: 1px 6px;
    border-radius: 4px;
    background: rgba(255,255,255,0.06);
    font-size: 12px;
    text-transform: lowercase;
}
body:not(.dark-theme) .notif-drawer__row-kind {
    background: rgba(0,0,0,0.05);
}
.notif-drawer__row-time {
    margin-left: auto;
}

/* Placeholder states (empty / loading / error) — reuse the inner
   container layout but customise spacing for the drawer. */
.notif-drawer__empty,
.notif-drawer__loading,
.notif-drawer__error {
    padding: 32px 16px;
    text-align: center;
    font-size: 14px;
    color: var(--text-secondary);
}
.notif-drawer__error { color: var(--text-secondary); }

/* [ADD] 2026-05-30 (post-Slice4) — search + bulk actions row, mute
   toggle, per-row delete button, retention footer. */

.notif-drawer__icon-btn {
    background: transparent;
    border: 0;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 4px;
    border-radius: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: color 0.12s ease, background 0.12s ease;
}
.notif-drawer__icon-btn:hover {
    color: var(--text-primary);
    background: rgba(255, 255, 255, 0.06);
}
body:not(.dark-theme) .notif-drawer__icon-btn:hover {
    background: rgba(0, 0, 0, 0.05);
}

.notif-drawer__search-row {
    display: flex;
    gap: 8px;
    padding: 8px 12px;
    border-bottom: 1px solid var(--border-color, rgba(255, 255, 255, 0.06));
    align-items: center;
    flex: 0 0 auto;
}
body:not(.dark-theme) .notif-drawer__search-row {
    border-bottom-color: rgba(0, 0, 0, 0.08);
}
.notif-drawer__search {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
}
.notif-drawer__search-icon {
    position: absolute;
    left: 8px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--text-secondary);
    pointer-events: none;
}
.notif-drawer__search-input {
    width: 100%;
    background: rgba(255, 255, 255, 0.04);
    border: 0;
    border-radius: 4px;
    color: var(--text-primary);
    padding: 6px 8px 6px 28px;
    font-size: 12px;
    line-height: 1.4;
    outline: none;
    transition: border-color 0.12s ease, background 0.12s ease;
    box-sizing: border-box;
}
body:not(.dark-theme) .notif-drawer__search-input {
    background: rgba(0, 0, 0, 0.03);
}
.notif-drawer__search-input:focus {
    border-color: transparent;
    box-shadow: none;
    background: rgba(255, 255, 255, 0.04);
}
body:not(.dark-theme) .notif-drawer__search-input:focus {
    background: rgba(0, 0, 0, 0.03);
}
.notif-drawer__search-input::placeholder { color: var(--text-secondary); }

.notif-drawer__row {
    position: relative;
}
.notif-drawer__row-del {
    position: absolute;
    top: 8px;
    right: 8px;
    background: transparent;
    border: 0;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 4px;
    border-radius: 4px;
    opacity: 0;
    transition: opacity 0.12s ease, color 0.12s ease, background 0.12s ease;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.notif-drawer__row:hover .notif-drawer__row-del,
.notif-drawer__row:focus-within .notif-drawer__row-del {
    opacity: 1;
}
.notif-drawer__row-del:hover {
    color: #ef4444;
    background: rgba(239, 68, 68, 0.08);
}

.notif-drawer__footer {
    flex: 0 0 auto;
    padding: 8px 12px;
    border-top: 1px solid var(--border-color, rgba(255, 255, 255, 0.06));
    font-size: 12px;
    color: var(--text-secondary);
    text-align: center;
    opacity: 0.85;
}
body:not(.dark-theme) .notif-drawer__footer {
    border-top-color: rgba(0, 0, 0, 0.08);
}

/* Mobile full-screen adaptation for .sc-drawer family ---------------------- */
@media (max-width: 768px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    .sc-drawer {
        --sc-drawer-w: 100vw;
        top: 0;
        left: 0;
        right: 0;
        /* [CHG] 2026-05-31 r4 — Stop the drawer ABOVE the mobile
         *   bottom-tab bar (48 px high + iOS safe-area-inset). Before:
         *   `bottom: 0` made the drawer cover the full viewport, but
         *   the bottom-tabs overlay (z-index: 9500) sits on top of the
         *   drawer (z-index: 9300), so the bottom 48 px of the drawer
         *   — which is exactly where DM composer (input + send button)
         *   lives — was visually clipped/covered by the tab bar. Now
         *   the drawer reserves a slot for the tab bar so:
         *     1) the composer is always fully visible and tappable;
         *     2) the user can swap tabs without having to close the
         *        drawer (Discord/Telegram/iMessage all do this).
         *   `--tt-mobile-bottom-bar` shared with bottom-tabs height
         *   (calc(48px + safe-area-inset-bottom)) so a single source
         *   of truth keeps both in sync if we ever bump the tab bar
         *   height. We compute inline to avoid introducing a new
         *   variable when 48px is the only consumer.
         *   Same fix applies to .news-drawer below. */
        bottom: calc(48px + env(safe-area-inset-bottom, 0px));
        width: 100vw !important;
        max-width: 100vw !important;
        min-width: 0;
        border-top-left-radius: 0;
        border-top-right-radius: 0;
        transform: translateX(100vw);
        /* [CHG] 2026-05-31 r4 — drop the inner safe-area padding on
         *   the drawer root because the new `bottom: calc(48px +
         *   env(safe-area-inset-bottom))` already lifts the drawer
         *   above the safe area (the tab bar consumes the inset).
         *   Keeping the old padding would add a double gap. The
         *   per-section .sc-drawer__body padding below is still
         *   useful for non-composer sections that don't host their
         *   own anchored input row. */
        padding-bottom: 0;
    }
    .sc-drawer.is-open {
        transform: translateX(0);
    }
    .sc-drawer__resize { display: none !important; }
    /* [CHG] 2026-05-29 — YeScript UI Redesign — respect iOS safe-area-inset
       so the close/back buttons don't slide under the notch / Dynamic
       Island on iPhone X+ (env() falls back to 0 on Android / older iOS). */
    .sc-drawer__head {
        min-height: 52px;
        padding: calc(10px + env(safe-area-inset-top, 0px)) 14px 10px;
    }
    .sc-drawer__body {
        padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    }
    .sc-drawer__close,
    .sc-drawer__back { width: 44px; height: 44px; padding: 0; }
    .sc-detail-action { flex: 1 1 100%; }
    body.sc-drawer-locked { overflow: hidden; touch-action: none; }
    /* [ADD] 2026-05-29 — overlay on mobile: drawer fully covers screen,
       so the overlay is visually superfluous but cheap (no rendering
       impact when drawer is opaque on top). Keep it for the iPad split
       case (1024px breakpoint includes iPad portrait where drawer is
       100vw but tablets in landscape may have side gaps — overlay
       prevents click-through to chart underneath in those cases). */
}
/* ============================================================================
 * END Script Detail Drawer block (Phase 1 / P1-5, 2026-05-27)
 * ============================================================================ */

/* ============================================================================
 * Run-on-Server Inputs Dialog (Slice 3, 2026-05-30)
 *   Modal that opens BEFORE TTInviteSubscriber.start() so a subscriber
 *   can customise the script's input parameters and see whether their
 *   chosen config will share an existing instance. Sits at z-index
 *   10004 — above the publish modal (10003) so it can be opened from
 *   the script-detail drawer while the chart is in any state.
 * ============================================================================ */
.tt-inputs-dialog-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 10004;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
}
.tt-inputs-dialog {
    background: var(--chart-popup-bg, #0d0d0d);
    color: var(--text-primary, #ffffff);
    border-radius: 8px;
    width: 100%;
    max-width: 520px;
    max-height: 80vh;
    overflow: visible;
    display: flex;
    flex-direction: column;
    box-shadow: 0 16px 40px rgba(0, 0, 0, 0.55);
}
body:not(.dark-theme) .tt-inputs-dialog {
    background: #ffffff;
    color: var(--text-primary);
}
.tt-inputs-dialog__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 16px;
    border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.06));
}
body:not(.dark-theme) .tt-inputs-dialog__head {
    border-bottom-color: rgba(0,0,0,0.08);
}
.tt-inputs-dialog__title {
    margin: 0;
    font-size: 16px;
    font-weight: 600;
    color: var(--text-primary);
}
.tt-inputs-dialog__close {
    background: transparent;
    border: 0;
    color: var(--text-secondary);
    font-size: 22px;
    cursor: pointer;
    line-height: 1;
    padding: 4px 8px;
}
.tt-inputs-dialog__close:hover { color: var(--text-primary); }
.tt-inputs-dialog__sub {
    padding: 10px 16px;
    font-size: 12px;
    color: var(--text-secondary);
    border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.04));
}
body:not(.dark-theme) .tt-inputs-dialog__sub {
    border-bottom-color: rgba(0,0,0,0.05);
}
.tt-inputs-dialog__share {
    padding: 10px 16px;
    font-size: 12px;
    border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.04));
    line-height: 1.5;
}
body:not(.dark-theme) .tt-inputs-dialog__share {
    border-bottom-color: rgba(0,0,0,0.05);
}
.tt-inputs-dialog__share.is-shared {
    background: rgba(41, 98, 255, 0.10);
    color: var(--text-primary);
}
.tt-inputs-dialog__share.is-private {
    background: rgba(38, 166, 154, 0.10);
    color: var(--text-primary);
}
.tt-inputs-dialog__form {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    overflow: visible;
}
.tt-inputs-dialog__loading,
.tt-inputs-dialog__error,
.tt-inputs-dialog__empty {
    padding: 24px 16px;
    text-align: center;
    color: var(--text-secondary);
    font-size: 13px;
}
.tt-inputs-dialog__error { color: #ef4444; }
.tt-inputs-dialog__fields {
    padding: 12px 16px;
    overflow-y: auto;
    flex: 1 1 auto;
}
.tt-inputs-dialog__fields[data-paper-trading] {
    overflow: visible;
    flex: 0 0 auto;
}
.tt-inputs-dialog__field {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 12px;
    gap: 12px;
}
.tt-inputs-dialog__field label {
    flex: 1 1 auto;
    font-size: 13px;
    color: var(--text-primary);
}
.tt-inputs-dialog__paper-trading-field {
    align-items: center;
}
.tt-inputs-dialog__paper-trading-label {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex: 1 1 auto;
    font-size: 14px !important;
    color: var(--tt-fg-0) !important;
}
.tt-inputs-dialog__paper-trading-label .tt-help {
    margin-left: auto;
}
/* Reuses the signal-notification toggle structure from account.css. */
.tt-inputs-dialog .tz-toggle {
    position: relative;
    display: inline-block;
    width: 44px;
    height: 24px;
    flex: 0 0 auto;
}
.tt-inputs-dialog .tz-toggle input {
    opacity: 0;
    width: 0;
    height: 0;
}
.tt-inputs-dialog .tz-toggle-slider {
    position: absolute;
    cursor: pointer;
    inset: 0;
    background: #999;
    border-radius: 24px;
    transition: background 0.2s;
}
.tt-inputs-dialog .tz-toggle-slider::before {
    content: "";
    position: absolute;
    width: 18px;
    height: 18px;
    left: 3px;
    bottom: 3px;
    background: #ffffff;
    border-radius: 50%;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
    transition: transform 0.2s;
}
.tt-inputs-dialog .tz-toggle input:checked + .tz-toggle-slider {
    background: #333333;
}
.tt-inputs-dialog .tz-toggle input:checked + .tz-toggle-slider::before {
    transform: translateX(20px);
}
.tt-inputs-dialog .tz-toggle input:disabled + .tz-toggle-slider {
    cursor: not-allowed;
    opacity: 0.45;
}
.tt-inputs-dialog__paper-trading-warning {
    margin: -4px 0 12px;
    font-size: 12px;
    line-height: 1.5;
    /* [EXCEPTION] Financial risk disclosure must use the warning semantic color. */
    color: var(--tt-down);
}
.tt-inputs-dialog__field input[type="number"],
.tt-inputs-dialog__field input[type="text"] {
    width: 180px;
    padding: 6px 10px;
    background: var(--bg-primary, #0d1117);
    color: var(--text-primary);
    border: 1px solid var(--border-color, rgba(255,255,255,0.12));
    border-radius: 4px;
    font-size: 13px;
}
body:not(.dark-theme) .tt-inputs-dialog__field input[type="number"],
body:not(.dark-theme) .tt-inputs-dialog__field input[type="text"] {
    background: #ffffff;
    color: var(--text-primary);
    border-color: #d1d4dc;
}
.tt-inputs-dialog__field input[type="number"]:focus,
.tt-inputs-dialog__field input[type="text"]:focus {
    outline: none;
    border-color: #2962ff;
}
.tt-inputs-dialog__foot {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 12px 16px;
    border-top: 1px solid var(--border-color, rgba(255,255,255,0.06));
}
body:not(.dark-theme) .tt-inputs-dialog__foot {
    border-top-color: rgba(0,0,0,0.08);
}
.tt-inputs-dialog__btn--secondary,
.tt-inputs-dialog__btn--primary {
    padding: 8px 16px;
    border-radius: 4px;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    border: 1px solid transparent;
}
.tt-inputs-dialog__btn--secondary {
    background: transparent;
    color: var(--text-secondary);
    border-color: var(--border-color, rgba(255,255,255,0.16));
}
.tt-inputs-dialog__btn--secondary:hover {
    color: var(--text-primary);
    background: rgba(255, 255, 255, 0.04);
}
body:not(.dark-theme) .tt-inputs-dialog__btn--secondary:hover {
    background: rgba(0, 0, 0, 0.04);
}
.tt-inputs-dialog__btn--primary {
    background: #2962ff;
    color: #ffffff;
    border-color: #2962ff;
}
.tt-inputs-dialog__btn--primary:hover { background: #1e53e6; border-color: #1e53e6; }
.tt-inputs-dialog__btn--primary:disabled {
    background: rgba(41, 98, 255, 0.5);
    border-color: rgba(41, 98, 255, 0.5);
    cursor: not-allowed;
}

/* ============================================================================
 * Publish Modal (Phase 1 / P1-6, 2026-05-27)
 *   Single source of truth for the "publish-to-community" UX. Mirrors
 *   .confirm-overlay layout for fade-in / centered card / scrim, but
 *   richer (form fields + counters + disclaimer + chart context). Z-index
 *   10003 to sit above Confirm (10001) and Notify (12000) — Notify
 *   should still float on top of the modal for "Published!" success
 *   toast.
 * ============================================================================ */
.tt-publish-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 10003;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 16px;
    opacity: 0;
    transition: opacity 160ms ease;
}
.tt-publish-overlay.is-open {
    display: flex;
    opacity: 1;
}
.tt-publish-overlay[hidden] { display: none !important; }

.tt-publish-modal {
    background: var(--chart-popup-bg, #0d0d0d);
    color: var(--text-primary, #fff);
    border: 1px solid var(--popover-border, #2a2e39);
    border-radius: 10px;
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.55);
    width: min(560px, 100%);
    max-height: calc(100vh - 32px);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
body:not(.dark-theme) .tt-publish-modal {
    background: #ffffff;
    color: var(--text-primary);
    border-color: #d1d4dc;
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.18);
}

/* [ADD] 2026-05-28 — Phase 2 / P2-13d — Non-modal draggable panel variant.
   Only the Publish dialog opts into this; the Invite-Access drawer keeps
   the original modal behavior. JS adds `.tt-publish-overlay--panel` to
   the overlay (alongside `.tt-publish-overlay`) when the panel is built.
   The selector specificity (.tt-publish-overlay.tt-publish-overlay--panel)
   matches the base rules above so these overrides win cleanly.
   Warning: do not drop the modifier — without it the Invite-Access drawer
     becomes non-modal too (it shares the .tt-publish-overlay base class).
*/
.tt-publish-overlay.tt-publish-overlay--panel {
    /* Strip backdrop, drop flex centering — JS handles modal position. */
    background: transparent;
    align-items: initial;
    justify-content: initial;
    padding: 0;
    pointer-events: none; /* Let clicks pass through the empty area */
}
.tt-publish-overlay.tt-publish-overlay--panel.is-open {
    display: block; /* Override the base "display:flex" so JS-fixed modal positions correctly */
}

/* Modal-as-panel: independent fixed positioning, width 820 (was 560),
   max-height 80vh (was full-height-minus-32). The panel must always be
   interactive even though the overlay is pointer-events:none. */
.tt-publish-overlay--panel .tt-publish-modal {
    position: fixed;
    top: 12vh;
    left: 50%;
    transform: translateX(-50%);
    pointer-events: auto;
    width: 820px;
    max-width: calc(100vw - 32px);
    max-height: 80vh;
}
/* Once JS sets inline top/left for a drag, drop the centering translate
   so the inline values take effect cleanly. */
.tt-publish-overlay--panel .tt-publish-modal:not(.is-default-pos) {
    transform: none;
}

.tt-publish__head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 14px 16px;
    border-bottom: 1px solid var(--border-color, #2a2e39);
}
/* [ADD] 2026-05-28 — Phase 2 / P2-13d — draggable head (panel variant only).
   Scoped to .tt-publish-overlay--panel so the Invite-Access drawer keeps
   the default cursor + selectable header. JS pointerdown listener still
   short-circuits when (a) the click lands on the close [x] button,
   (b) the viewport is coarse-pointer or ≤768px. */
.tt-publish-overlay--panel .tt-publish__head {
    cursor: move;
    user-select: none;
    touch-action: none;
}
.tt-publish-overlay--panel .tt-publish__head .tt-publish__title { cursor: move; }
.tt-publish-overlay--panel .tt-publish__head .tt-publish__close { cursor: pointer; }
.tt-publish-overlay--panel .tt-publish-modal.is-dragging { user-select: none; }
.tt-publish-overlay--panel .tt-publish-modal.is-dragging .tt-publish__head { cursor: grabbing; }
.tt-publish__title {
    flex: 1 1 auto;
    margin: 0;
    font-size: 16px;
    font-weight: 600;
    color: var(--text-primary, #fff);
}
body:not(.dark-theme) .tt-publish__title { color: #000; }
.tt-publish__close {
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 0;
    border-radius: 6px;
    color: var(--text-secondary, #b1b5c2);
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease;
}
.tt-publish__close:hover,
.tt-publish__close:focus-visible {
    background: var(--bg-tertiary, rgba(255, 255, 255, 0.06));
    color: var(--text-primary, #fff);
    outline: none;
}
body:not(.dark-theme) .tt-publish__close:hover {
    background: rgba(0, 0, 0, 0.05);
    color: #000;
}

.tt-publish__body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 14px 16px 4px;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.tt-publish__error {
    margin: 0;
    padding: 8px 12px;
    background: rgba(239, 83, 80, 0.12);
    color: var(--price-down, #ef5350);
    border: 1px solid rgba(239, 83, 80, 0.4);
    border-radius: 6px;
    font-size: 12px;
}
.tt-publish__error[hidden] { display: none; }
/* [ADD] 2026-05-29 — Success variant of the error banner — same slot,
   green palette. Used by Manage-subscribers to confirm 'invitation
   granted' / 'subscriber reactivated' without spawning a separate
   toast widget. */
.tt-publish__error.tt-publish__error--ok {
    background: rgba(38, 166, 154, 0.12);
    color: var(--price-up, #26a69a);
    border-color: rgba(38, 166, 154, 0.4);
}

.tt-publish__field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.tt-publish__label {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-primary, #fff);
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
body:not(.dark-theme) .tt-publish__label { color: #000; }
.tt-publish__locked {
    font-size: 11px;
    font-weight: 400;
    color: var(--text-secondary, #b1b5c2);
}

.tt-publish__name-display {
    padding: 8px 12px;
    background: var(--bg-tertiary, rgba(255, 255, 255, 0.04));
    border: 1px solid var(--popover-border, #2a2e39);
    border-radius: 6px;
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary, #fff);
    word-break: break-all;
}
body:not(.dark-theme) .tt-publish__name-display {
    background: rgba(0, 0, 0, 0.03);
    color: #000;
    border-color: #d1d4dc;
}

.tt-publish__type-chip {
    display: inline-block;
    padding: 2px 8px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border-radius: 4px;
    background: rgba(38, 166, 154, 0.18);
    color: var(--price-up, #26a69a);
    align-self: flex-start;
}
.tt-publish__type-chip.is-strategy {
    background: rgba(41, 98, 255, 0.18);
    color: #2962ff;
}
.tt-publish__type-chip.is-library {
    background: rgba(255, 167, 38, 0.18);
    color: #ffa726;
}

.tt-publish__lib-notice {
    padding: 10px 12px;
    background: rgba(255, 167, 38, 0.10);
    border: 1px solid rgba(255, 167, 38, 0.4);
    border-radius: 6px;
    color: var(--text-primary, #fff);
    font-size: 12px;
    line-height: 1.5;
}
body:not(.dark-theme) .tt-publish__lib-notice { color: #000; }

.tt-publish__textarea,
.tt-publish__input {
    width: 100%;
    background: var(--bg-tertiary, rgba(255, 255, 255, 0.04));
    color: var(--text-primary, #fff);
    border: 1px solid var(--popover-border, #2a2e39);
    border-radius: 6px;
    padding: 8px 10px;
    font: 13px/1.5 inherit;
    box-sizing: border-box;
    resize: vertical;
    transition: border-color 120ms ease, background 120ms ease;
}
.tt-publish__textarea {
    min-height: 96px;
    max-height: 240px;
    font-family: inherit;
}
body:not(.dark-theme) .tt-publish__textarea,
body:not(.dark-theme) .tt-publish__input {
    background: #ffffff;
    color: #000;
    border-color: #d1d4dc;
}
.tt-publish__textarea:focus,
.tt-publish__input:focus {
    outline: none;
    border-color: var(--accent-color, #2962ff);
    background: var(--bg-primary, rgba(0, 0, 0, 0.18));
}
body:not(.dark-theme) .tt-publish__textarea:focus,
body:not(.dark-theme) .tt-publish__input:focus {
    background: #fff;
}

.tt-publish__counter {
    font-size: 11px;
    color: var(--text-secondary, #b1b5c2);
    text-align: right;
}
.tt-publish__counter.is-overflow {
    color: var(--price-down, #ef5350);
    font-weight: 600;
}

.tt-publish__ctx-row {
    display: grid;
    grid-template-columns: 1fr 1fr 96px;
    gap: 8px;
}
.tt-publish__input--sm {
    font-size: 12px;
}

.tt-publish__hint {
    font-size: 11px;
    color: var(--text-secondary, #b1b5c2);
    line-height: 1.4;
    margin-top: 2px;
}

/* [CHG] 2026-05-28 — P2-13e — disclaimer checkbox compressed (still
   required by sec/CFTC standards for distributing trading strategies —
   user must actively ack risk). Was 44px min-height with chunky 10/12
   padding; now 36px / 6-8 padding and tighter line-height so it sits
   as a slim banner above the footer. */
.tt-publish__check {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 6px 10px;
    background: var(--bg-tertiary, rgba(255, 255, 255, 0.04));
    border: 1px solid var(--popover-border, #2a2e39);
    border-radius: 6px;
    cursor: pointer;
    user-select: none;
    min-height: 36px;
    box-sizing: border-box;
}
body:not(.dark-theme) .tt-publish__check {
    background: rgba(0, 0, 0, 0.03);
    border-color: #d1d4dc;
}
.tt-publish__check > input[type="checkbox"] {
    flex: 0 0 auto;
    margin: 2px 0 0;
    width: 16px;
    height: 16px;
    accent-color: var(--accent-color, #2962ff);
    cursor: pointer;
}
.tt-publish__check > span {
    flex: 1 1 auto;
    font-size: 12px;
    line-height: 1.4;
    color: var(--text-primary, #fff);
}
body:not(.dark-theme) .tt-publish__check > span { color: #000; }

.tt-publish__foot {
    flex: 0 0 auto;
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 12px 16px;
    border-top: 1px solid var(--border-color, #2a2e39);
    background: var(--bg-secondary, #131722);
}
body:not(.dark-theme) .tt-publish__foot { background: #ffffff; }

/* [FIX] 2026-05-31 — Modal footer surface parity (dark theme).
   Reason: some chart modals kept legacy section surfaces (e.g. --bg-secondary /
   --surface-2) on action footers, so "Manage subscribers" close-button area
   visually diverged from the modal body.
   Change: force modal/footer sections that host confirm/cancel actions to
   consume the same popup surface tokens as the modal body.
   Warning: keep this dark-theme scoped; light theme should stay unchanged. */
body.dark-theme .tt-publish__foot,
body.dark-theme .tt-inputs-dialog__foot,
body.dark-theme .confirm-footer,
body.dark-theme .input-dialog-footer,
body.dark-theme .pt-modal-footer,
body.dark-theme .ca-footer,
body.dark-theme .ind-settings-footer,
body.dark-theme .exchange-dialog-footer,
body.dark-theme .sc-report__foot {
    background: var(--chart-popup-bg, #0d0d0d);
    border-top-color: var(--chart-popup-border, #000000);
}
body.dark-theme .sc-report-modal {
    background: var(--chart-popup-bg, #0d0d0d);
    border-color: var(--chart-popup-border, #000000);
}
body.dark-theme .sc-report__head {
    background: var(--chart-popup-bg, #0d0d0d);
    border-bottom-color: var(--chart-popup-border, #000000);
}

.tt-publish__btn {
    height: 36px;
    min-width: 96px;
    padding: 0 16px;
    background: transparent;
    color: var(--text-primary, #fff);
    border: 1px solid var(--popover-border, #2a2e39);
    border-radius: 6px;
    font: 600 13px/1 inherit;
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
body:not(.dark-theme) .tt-publish__btn {
    color: #000;
    border-color: #d1d4dc;
}
.tt-publish__btn:hover,
.tt-publish__btn:focus-visible {
    background: var(--bg-tertiary, rgba(255, 255, 255, 0.06));
    outline: none;
}
/* [CHG] 2026-05-28 — Phase 2 / P2-13l — Confirm "Publish to community"
   button switched from blue accent to a black-and-white CTA on user
   request. Mirrors Coinbase / Bybit / Binance Pro "primary" buttons:
   high-contrast monochrome that does NOT compete with green / red
   trade-action chrome elsewhere in the modal.
   Dark theme  → white surface, black ink (high contrast on dark bg)
   Light theme → black surface, white ink (high contrast on light bg)
   Hover lifts a soft shadow + slight tonal shift instead of a hue change.
   Warning: do NOT restore `var(--accent-color)` here — accent blue is
     reserved for hover/focus emphasis on secondary controls only. */
.tt-publish__btn--confirm {
    background: #ffffff;
    color: var(--text-primary);
    border-color: #ffffff;
}
.tt-publish__btn--confirm:hover:not([disabled]),
.tt-publish__btn--confirm:focus-visible:not([disabled]) {
    background: #e9ecef;
    border-color: #e9ecef;
    color: var(--text-primary);
    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.12);
    outline: none;
}
.tt-publish__btn--confirm[disabled] {
    background: rgba(255, 255, 255, 0.35);
    border-color: rgba(255, 255, 255, 0.35);
    color: rgba(0, 0, 0, 0.5);
    cursor: not-allowed;
}
body:not(.dark-theme) .tt-publish__btn--confirm {
    background: #000000;
    color: #ffffff;
    border-color: #000000;
}
body:not(.dark-theme) .tt-publish__btn--confirm:hover:not([disabled]),
body:not(.dark-theme) .tt-publish__btn--confirm:focus-visible:not([disabled]) {
    background: #1f2329;
    border-color: #1f2329;
    color: #ffffff;
}
body:not(.dark-theme) .tt-publish__btn--confirm[disabled] {
    background: rgba(0, 0, 0, 0.35);
    border-color: rgba(0, 0, 0, 0.35);
    color: rgba(255, 255, 255, 0.7);
}

/* [NOTE] 2026-05-28 — `body.tt-publish-locked` is now ONLY used by
   tt.scripts.invite-access.js (which remains a true modal drawer).
   The Publish dialog converted to a non-modal draggable panel in P2-13d
   and no longer adds this class. Keep the rule so the invite-access
   drawer keeps locking body scroll. */
body.tt-publish-locked { overflow: hidden; }

/* ==== [ADD] 2026-05-28 — Phase 3a / P3a-5 — Visibility + Private link ==== */
/* Two-radio chooser inside the Publish modal. The pills span the modal
 * width with an accent border on the selected one and a hover lift on
 * the other. Both are full-keyboard-navigable (radios + clickable label). */
.tt-publish__radio-row {
    display: grid;
    /* [CHG] 2026-05-28 — P2-13e — was 1fr 1fr (2 cols, 3rd pill broke onto
       a second row eating ~80px vertical). Now 3 equal cols so all three
       Visibility pills sit on one line on desktop. Mobile media query
       collapses to 1fr. */
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
}
/* [CHG] 2026-05-28 — P2-13g — radio label container changed from `block`
   to `flex` + h:100% so the pill inside fills the full grid-cell height.
   Combined with `align-items: stretch` (the grid default), all three
   pills paint at the height of the tallest content — fixes the visual
   regression where the short "Open source" pill was visibly shorter
   than "Invite-only (Server)" whose description wraps to more lines. */
.tt-publish__radio {
    display: flex;
    position: relative;
    cursor: pointer;
    user-select: none;
    height: 100%;
}
.tt-publish__radio > input[type="radio"] {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}
/* [CHG] 2026-05-28 — P2-13e/g — pill collapsed in both directions:
   padding 10/12 → 8/10, min-height 64 → 52, gap 4 → 2, strong 13→12.
   Now also `flex: 1 1 auto` + `width: 100%` so the pill stretches to
   fill the label box (which itself fills the grid cell). */
.tt-publish__radio-pill {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px 10px;
    min-height: 52px;
    flex: 1 1 auto;
    width: 100%;
    background: var(--bg-tertiary, rgba(255, 255, 255, 0.04));
    border: 1px solid var(--popover-border, #2a2e39);
    border-radius: 6px;
    transition: background 120ms ease, border-color 120ms ease;
    box-sizing: border-box;
}
body:not(.dark-theme) .tt-publish__radio-pill {
    background: rgba(0, 0, 0, 0.03);
    border-color: #d1d4dc;
}
.tt-publish__radio:hover .tt-publish__radio-pill,
.tt-publish__radio:focus-within .tt-publish__radio-pill {
    background: var(--bg-tertiary, rgba(255, 255, 255, 0.08));
}
.tt-publish__radio > input[type="radio"]:checked + .tt-publish__radio-pill {
    border-color: var(--accent-color, #2962ff);
    background: rgba(41, 98, 255, 0.08);
}
.tt-publish__radio-pill > strong {
    font-size: 12px;
    color: var(--text-primary, #fff);
    font-weight: 600;
}
body:not(.dark-theme) .tt-publish__radio-pill > strong { color: #000; }
.tt-publish__radio-desc {
    font-size: 11px;
    line-height: 1.4;
    color: var(--text-secondary, #b1b5c2);
}
.tt-publish__protected-note {
    margin-top: 8px;
    padding: 8px 10px;
    background: rgba(239, 83, 80, 0.10);
    border: 1px solid rgba(239, 83, 80, 0.30);
    border-radius: 6px;
    font-size: 11px;
    line-height: 1.5;
    color: var(--text-secondary, #b1b5c2);
}
body:not(.dark-theme) .tt-publish__protected-note {
    background: rgba(239, 83, 80, 0.08);
    border-color: rgba(239, 83, 80, 0.25);
}
/* [ADD] 2026-05-28 — P2-13g — Invite-only info note. Was missing from
   the stylesheet, so the wrapped text rendered at body default (16px) —
   visually MUCH larger than the protected-note (11px) sibling. Now
   mirrors the protected-note typography but uses the accent-blue tint
   (informational, not warning) since invite-only is a feature flag
   reserved for premium members, not a security disclaimer. */
.tt-publish__invite-note {
    margin-top: 8px;
    padding: 8px 10px;
    background: rgba(41, 98, 255, 0.10);
    border: 1px solid rgba(41, 98, 255, 0.30);
    border-radius: 6px;
    font-size: 11px;
    line-height: 1.5;
    color: var(--text-secondary, #b1b5c2);
}
body:not(.dark-theme) .tt-publish__invite-note {
    background: rgba(41, 98, 255, 0.08);
    border-color: rgba(41, 98, 255, 0.25);
}

/* Private-link toggle (mutually exclusive with public): a checkbox that
 * mints / revokes a 32-char share_token. When enabled, a read-only URL
 * box appears with Copy / Rotate / Revoke buttons. */
.tt-publish__check--toggle {
    margin-bottom: 8px;
}
/* [CHG] 2026-05-28 — P2-13e — was flex column (input on row 1, three
   buttons wrapping on row 2/3 = ~110px tall when visible). Now a single
   row: input takes remaining width, 3 ghost buttons sit on the right
   at compact widths. */
.tt-publish__priv-snap {
    display: flex;
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 6px;
}
/* [FIX] 2026-05-28 — P2-13e — `.tt-publish__priv-snap` had `display: flex`
   which overrode the [hidden] attribute's default `display: none`. When
   _renderPrivSnap(null) set $privSnap.hidden=true the JS state said
   "hidden" but the browser still painted the empty input + 3 ghost
   buttons. Force [hidden] precedence via !important. */
.tt-publish__priv-snap[hidden] { display: none !important; }
.tt-publish__priv-url {
    flex: 1 1 240px;
    min-width: 0;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: 11px;
    color: var(--text-primary, #fff);
    background: var(--bg-tertiary, rgba(255, 255, 255, 0.04));
    border: 1px solid var(--popover-border, #2a2e39);
    border-radius: 6px;
    padding: 6px 8px;
    box-sizing: border-box;
}
body:not(.dark-theme) .tt-publish__priv-url {
    background: rgba(0, 0, 0, 0.03);
    border-color: #d1d4dc;
    color: #000;
}
.tt-publish__priv-actions {
    display: flex;
    flex-wrap: nowrap;
    gap: 4px;
    flex: 0 0 auto;
}
.tt-publish__btn--ghost {
    height: 28px;
    min-width: 0;
    padding: 0 10px;
    font-size: 11px;
}

@media (max-width: 768px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    .tt-publish__radio-row { grid-template-columns: 1fr; }
    .tt-publish__priv-actions { width: 100%; }
    .tt-publish__btn--ghost { flex: 1 1 30%; }
}

@media (max-width: 768px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    .tt-publish-overlay {
        align-items: flex-end;
        padding: 0;
    }
    .tt-publish-modal {
        width: 100vw;
        max-width: 100vw;
        max-height: 90vh;
        border-radius: 12px 12px 0 0;
        border-bottom: 0;
    }
    /* [ADD] 2026-05-28 — Phase 2 / P2-13d — Panel variant must also revert
       to the bottom-sheet on mobile (override the desktop fixed/translate). */
    .tt-publish-overlay--panel {
        pointer-events: auto;     /* Re-enable click-out behavior on mobile */
        background: rgba(0, 0, 0, 0.5); /* Re-add backdrop for bottom-sheet */
    }
    .tt-publish-overlay--panel.is-open {
        display: flex;
        align-items: flex-end;
    }
    .tt-publish-overlay--panel .tt-publish-modal,
    .tt-publish-overlay--panel .tt-publish-modal.is-default-pos,
    .tt-publish-overlay--panel .tt-publish-modal:not(.is-default-pos) {
        position: static;
        top: auto;
        left: auto;
        transform: none;
        width: 100vw;
        max-width: 100vw;
        max-height: 90vh;
        border-radius: 12px 12px 0 0;
        border-bottom: 0;
    }
    .tt-publish-overlay--panel .tt-publish__head {
        cursor: default;
        touch-action: auto;
    }
    .tt-publish-overlay--panel .tt-publish__head .tt-publish__title {
        cursor: default;
    }
    .tt-publish__ctx-row {
        grid-template-columns: 1fr;
    }
    .tt-publish__btn {
        min-width: 120px;
        height: 44px;
    }
    .tt-publish__close {
        width: 44px;
        height: 44px;
    }
}
/* ============================================================================
 * END Publish Modal block (Phase 1 / P1-6, 2026-05-27)
 * ============================================================================ */

/* ============================================================================
 * [ADD] 2026-05-28 — Phase 2 / P2-6 — YeScript Editor Strategy Tester sub-tabs
 *
 * Sub-tab strip itself reuses .bp-sub-tabs (defined at L3006). This block
 * only adds:
 *   • .yes-sub-tabs   — yescript-specific border + spacing inside #bpPaneYescript
 *   • .yes-sub-pane   — three sibling panes of #yesBody (Backtest / Trades /
 *                       Properties); hidden by default, toggled by JS
 *   • .yes-st-empty   — "Run a strategy to see the backtest report" placeholder
 *   • .yes-st-metrics — 5-card metrics grid (Total PnL / Max DD / etc.)
 *   • .yes-st-table   — List of Trades grid
 *   • .yes-st-props__dl — Properties two-col grid
 *
 * Visual contract: feel must match the Paper Trading sub-tabs (`#bpPtSubTabs`)
 * so users perceive sub-tabs as a uniform Tradeiz idiom. We deliberately do
 * NOT redefine .bp-sub-tab itself — the parent class already paints the pill.
 * ============================================================================ */
.yes-sub-tabs {
    border-bottom: 1px solid var(--border-medium);
    background: var(--bg-secondary);
}
.yes-sub-tabs .bp-sub-tab svg {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
}
.yes-sub-pane {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
    padding: 12px;
    background: var(--bg-primary);
    color: var(--text-primary);
}
.yes-st-empty,
.yes-st-empty-inline {
    color: var(--text-secondary);
    font-size: 13px;
    padding: 24px 8px;
    text-align: center;
}
.yes-st-empty-inline { padding: 16px 8px; }
.yes-st-body {
    display: flex;
    flex-direction: column;
    gap: 16px;
}
/* ──────────────────────────────────────────────────────────────────────
   [REWRITE] 2026-05-28 — Phase 2 / P2-13k — TV-style Backtest report.
   Three vertical sections inside #yesStMetrics:
     .yes-st-hero       — 3 large KPI tiles (Net P&L / Max DD / Win rate)
     .yes-st-equity-wrap — labelled equity curve mini-chart (180px SVG)
     .yes-st-grid       — 8-tile secondary KPI grid
   The previous flat 5-tile grid was too sparse for a backtest report
   and had no chart at all — users had to read raw numbers to estimate
   curve shape. TradingView Strategy Tester layout is the reference.
   ────────────────────────────────────────────────────────────────────── */
.yes-st-metrics {
    display: flex;
    flex-direction: column;
    gap: 16px;
}
/* Hero KPI row — 3 large tiles, equal width, mobile-collapses to 1-col. */
.yes-st-hero {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
}
.yes-st-hero-tile {
    background: var(--bg-secondary);
    border: 1px solid var(--border-medium);
    border-radius: 6px;
    padding: 14px 16px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.yes-st-hero-tile__label {
    color: var(--text-secondary);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-weight: 500;
}
.yes-st-hero-tile__value {
    color: var(--text-primary);
    font-size: 24px;
    font-weight: 600;
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}
.yes-st-hero-tile__value--up   { color: var(--tt-up, #26a69a); }
.yes-st-hero-tile__value--down { color: var(--tt-down, #ef5350); }
.yes-st-hero-tile__sub {
    color: var(--text-secondary);
    font-size: 12px;
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-variant-numeric: tabular-nums;
    min-height: 16px;
}
.yes-st-hero-tile__sub--up   { color: var(--tt-up, #26a69a); }
.yes-st-hero-tile__sub--down { color: var(--tt-down, #ef5350); }

/* Equity curve section. */
.yes-st-equity-wrap {
    background: var(--bg-secondary);
    border: 1px solid var(--border-medium);
    border-radius: 6px;
    padding: 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.yes-st-equity-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
}
.yes-st-equity-title {
    color: var(--text-primary);
    font-size: 13px;
    font-weight: 600;
}
.yes-st-equity-sub {
    color: var(--text-secondary);
    font-size: 11px;
}
/* [REWRITE] 2026-05-28 — Phase 2 / P2-13p² — Strategy Tester equity
   sparkline. Same TV/Bybit card-sparkline structure as
   .sc-strategy-equity (Detail Drawer): wrapper holds the absolute
   positioning, SVG fills wrapper 100 %/100 %, HTML labels overlay
   the right gutter at absolute right:6 + top:<%>. Container is
   sized 180 px tall on desktop, 140 px on mobile (see @768 media
   query). Padding reserves 64 px gutter for label pills (slightly
   wider than the Drawer's 56 px because the Tester runs at a wider
   font baseline). */
.yes-st-equity {
    position: relative;
    width: 100%;
    height: 180px;
    box-sizing: border-box;
    overflow: hidden;
}
/* Two-column grid layout:
     [ SVG drawing column ][ 64 px label gutter ]
   The SVG column flexes, gutter is fixed-width. SVG fills its
   column 100%/100%, labels sit in the gutter column at absolute
   `top: <%>` (% relative to wrapper height = column height).
   This matches TradingView desktop equity curves where the right
   gutter is its own reserved axis-label column rather than
   labels-on-curve. */
.yes-st-equity__svgwrap {
    position: absolute;
    top: 8px;
    left: 8px;
    right: 8px;
    bottom: 8px;
    /* The wrapper IS positioned (absolute), so child labels with
       position:absolute resolve against this box. Padding-right
       reserves 64 px for label pills; SVG fills the remaining
       content box (% width/height honour padding by default with
       content-box sizing). */
    padding-right: 64px;
    box-sizing: border-box;
}
.yes-st-equity__svg {
    width: 100%;
    height: 100%;
    display: block;
    pointer-events: none;
}
.yes-st-equity__line {
    stroke-width: 1.6;
    stroke-linejoin: round;
    stroke-linecap: round;
    fill: none;
}
.yes-st-equity__line--up   { stroke: var(--tt-up,   #26a69a); }
.yes-st-equity__line--down { stroke: var(--tt-down, #ef5350); }
.yes-st-equity__lbl {
    position: absolute;
    right: 8px;
    transform: translateY(-50%);
    font-size: 12px;
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
    line-height: 1;
    padding: 2px 6px;
    border-radius: 2px;
    background: rgba(20, 24, 32, 0.55);
    pointer-events: none;
    white-space: nowrap;
}
body:not(.dark-theme) .yes-st-equity__lbl {
    background: rgba(255, 255, 255, 0.78);
    color: var(--text-secondary);
}
.yes-st-equity__lbl--up   { color: var(--tt-up,   #26a69a); }
.yes-st-equity__lbl--down { color: var(--tt-down, #ef5350); }
.yes-st-equity__lbl--zero { color: var(--text-secondary); }
.yes-st-equity-empty {
    color: var(--text-secondary);
    font-size: 12px;
    padding: 28px 8px;
    text-align: center;
    background: var(--bg-input);
    border-radius: 4px;
}

/* Secondary KPI grid — 8 dense tiles (4x2 on desktop, 2x4 on mobile). */
.yes-st-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
}
.yes-st-grid-tile {
    background: var(--bg-secondary);
    border: 1px solid var(--border-medium);
    border-radius: 4px;
    padding: 8px 10px;
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.yes-st-grid-tile__label {
    color: var(--text-secondary);
    font-size: 11px;
}
.yes-st-grid-tile__value {
    color: var(--text-primary);
    font-size: 14px;
    font-weight: 500;
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-variant-numeric: tabular-nums;
}
.yes-st-grid-tile__value--up   { color: var(--tt-up,   #26a69a); }
.yes-st-grid-tile__value--down { color: var(--tt-down, #ef5350); }
.yes-st-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}
/* [ADD] 2026-05-28 — Phase 2 / P2-13i — when the actions row is placed
   ABOVE the metrics grid (Backtest report top-right corner), give it
   bottom margin so the Save button does not hug the metric tiles. The
   plain `.yes-st-actions` baseline rule still applies to other usages
   (in case future panes keep it at the bottom). */
.yes-st-actions--top {
    margin-bottom: 4px;
}
/* ── List of Trades ────────────────────────────────────────────────── */
.yes-st-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 12px;
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
}
.yes-st-table thead th {
    position: sticky;
    top: 0;
    background: var(--bg-secondary);
    border-bottom: 1px solid var(--border-medium);
    padding: 6px 8px;
    text-align: left;
    color: var(--text-secondary);
    font-weight: 500;
    z-index: 1;
}
.yes-st-table tbody td {
    padding: 6px 8px;
    border-bottom: 1px solid var(--border-subtle);
}
.yes-st-table tbody tr:nth-child(even) { background: rgba(255,255,255,0.02); }
/* [ADD] 2026-05-28 — Phase 2 / P2-13i — numeric columns right-align so
   negative sign + decimals stack vertically (finance-table convention,
   used by TradingView / Bybit / Binance pro). `.type-col` centers the
   LONG/SHORT pill so it floats inside the cell instead of hugging the
   left edge. Apply to both <th> and <td> so header lines up with
   numeric values below it. */
.yes-st-table th.num-col,
.yes-st-table td.num-col   { text-align: right; font-variant-numeric: tabular-nums; }
.yes-st-table th.type-col,
.yes-st-table td.type-col  { text-align: center; }
.yes-st-side {
    display: inline-block;
    min-width: 48px;
    text-align: center;
    padding: 2px 6px;
    border-radius: 3px;
    font-weight: 600;
    font-size: 11px;
}
.yes-st-side--up   { background: rgba(38,166,154,0.18); color: var(--tt-up, #26a69a); }
.yes-st-side--down { background: rgba(239,83,80,0.18); color: var(--tt-down, #ef5350); }
.yes-st-table .up   { color: var(--tt-up, #26a69a); }
.yes-st-table .down { color: var(--tt-down, #ef5350); }

/* ──────────────────────────────────────────────────────────────────────
   [ADD] 2026-05-28 — Phase 2 / P2-13k — TV-style trades summary bar.
   Row of mini-tiles above the table: Total / Long / Short / Wins /
   Losses / Net P&L. Read-only summary; cells are flex-equal-width.
   ────────────────────────────────────────────────────────────────────── */
.yes-st-trades-summary {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 8px;
    background: var(--bg-secondary);
    border: 1px solid var(--border-medium);
    border-radius: 6px;
    padding: 10px 12px;
    margin-bottom: 10px;
}
.yes-st-trades-summary__cell {
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.yes-st-trades-summary__lbl {
    color: var(--text-secondary);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}
.yes-st-trades-summary__val {
    color: var(--text-primary);
    font-size: 16px;
    font-weight: 600;
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-variant-numeric: tabular-nums;
}
.yes-st-trades-summary__val.up   { color: var(--tt-up, #26a69a); }
.yes-st-trades-summary__val.down { color: var(--tt-down, #ef5350); }
.yes-st-trades-tablewrap {
    border: 1px solid var(--border-medium);
    border-radius: 6px;
    overflow: hidden;
}

/* ──────────────────────────────────────────────────────────────────────
   [REWRITE] 2026-05-28 — Phase 2 / P2-13k — TV-style Properties cards.
   3 cards in a grid (Script / Market / Range) + Inputs card below.
   ────────────────────────────────────────────────────────────────────── */
.yes-st-props-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
    margin-bottom: 12px;
}
.yes-st-props-inputs {
    display: block;
}
.yes-st-props-card {
    background: var(--bg-secondary);
    border: 1px solid var(--border-medium);
    border-radius: 6px;
    padding: 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.yes-st-props-card__head {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--text-primary);
    padding-bottom: 6px;
    border-bottom: 1px solid var(--border-medium);
}
.yes-st-props-card__head svg,
.yes-st-props-card__icon {
    width: var(--chart-icon-size);
    height: var(--chart-icon-size);
    color: var(--text-secondary);
    flex-shrink: 0;
}
.yes-st-props-card__title {
    font-size: 13px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}
.yes-st-props-card__dl {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 6px 14px;
    margin: 0;
}
.yes-st-props-card__dl dt {
    color: var(--text-secondary);
    font-size: 12px;
}
.yes-st-props-card__dl dd {
    margin: 0;
    color: var(--text-primary);
    font-size: 12px;
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-variant-numeric: tabular-nums;
    word-break: break-word;
    text-align: right;
}
/* Mobile: shrink pill icons (the parent .bp-sub-tabs CSS keeps the strip
   horizontally scrollable already). Cards stay 2-col instead of 5-col. */
@media (max-width: 768px) {
    /* [REDESIGN] 2026-05-31 r2 — Mobile YeScript Strategy Tester report layout
     *   (虎哥 review). Symptoms before: hero KPIs were stacked 1-per-row, with
     *   each tile expanding to a 4-line vertical block (label / value / pct
     *   sub / wins-losses sub) — readers reported the report read as a 9-line
     *   tall vertical column ("净盈亏 / -1.66K / -16.59% / 最大回撤 / 2.44K /
     *   -22.65% / 胜率 / 18.99% / 15W / 64L") which violated the "2-3 lines
     *   per data card" mobile UX rule. Strategy: keep the 3 tiles side-by-side
     *   (3-column grid) and tighten each tile's internal vertical rhythm so
     *   the whole hero strip stays ≤ 90 px tall, even on a 360 px viewport.
     *   Type sizes match the 12/14/16/18/20/22 white-list (general.mdc rule). */
    .yes-st-hero {
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 6px;
    }
    .yes-st-hero-tile {
        padding: 8px 10px;
        gap: 2px;
        min-width: 0;
    }
    .yes-st-hero-tile__label {
        font-size: 12px;
        letter-spacing: 0.3px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .yes-st-hero-tile__value {
        font-size: 16px;
        line-height: 1.2;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .yes-st-hero-tile__sub {
        font-size: 12px;
        line-height: 1.2;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .yes-st-grid { grid-template-columns: repeat(2, 1fr); }
    .yes-st-trades-summary { grid-template-columns: repeat(3, 1fr); row-gap: 10px; }
    .yes-st-props-grid     { grid-template-columns: 1fr; }
    .yes-st-table { font-size: 12px; }
    .yes-sub-pane { padding: 8px; }
    .yes-st-equity { height: 140px; }
}
/* ============================================================================
 * END Strategy Tester sub-tabs block (Phase 2 / P2-6, 2026-05-28)
 * ============================================================================ */

/* ============================================================================
 * Detail drawer — Phase 2 additions (P2-9 comments / P2-10 strategy report &
 * release notes / Report dialog), 2026-05-28
 *
 * Lives inside the existing #scriptDetailDrawer so it inherits color tokens
 * via the parent context. No global overrides; everything is scoped under
 * .sc-detail-* / .sc-comments__* / .sc-report* prefixes.
 * ============================================================================ */

/* Strategy report snapshot (P2-10, P2-13m TV-style rewrite) -------------- */
/* [REWRITE] 2026-05-28 — Phase 2 / P2-13m — TV-style hero KPI strip +
   secondary KPI grid inside the snapshot card. The single 3-col grid
   that used to host all 6 metrics flattened "Total return" to look
   identical to "Bars" which is wrong UX — Total return / Max DD /
   Win rate are the three the trader compares strategies on. Now:
     Hero strip → 3 large tiles (color-coded)
     Secondary grid → 4 small tiles (mono numerics)
   Subtitle / disclaimer untouched; both themes get explicit colours
   so the snapshot reads as intended on light surface too. */
.sc-strategy-snapshot {
    border: 1px solid var(--border-medium);
    border-radius: 8px;
    padding: 12px;
    background: var(--surface-2, var(--background-secondary));
    display: flex;
    flex-direction: column;
    gap: 10px;
}
body:not(.dark-theme) .sc-strategy-snapshot {
    background: #f6f7fa;
    border-color: rgba(0,0,0,0.10);
}
.sc-strategy-snapshot__header {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    font-size: 12px;
    color: var(--text-secondary);
    flex-wrap: wrap;
}
.sc-strategy-snapshot__pair {
    font-weight: 600;
    color: var(--text-primary);
}
body:not(.dark-theme) .sc-strategy-snapshot__pair { color: #1f2329; }
.sc-strategy-hero {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
}
.sc-strategy-hero-tile {
    background: rgba(255,255,255,0.04);
    border: 1px solid var(--border-medium);
    border-radius: 6px;
    padding: 10px 12px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
body:not(.dark-theme) .sc-strategy-hero-tile {
    background: #ffffff;
    border-color: rgba(0,0,0,0.10);
}
.sc-strategy-hero-tile__label {
    font-size: 11px;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.4px;
    font-weight: 500;
}
.sc-strategy-hero-tile__value {
    font-size: 18px;
    font-weight: 600;
    color: var(--text-primary);
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-variant-numeric: tabular-nums;
    line-height: 1.15;
    word-break: break-word;
}
body:not(.dark-theme) .sc-strategy-hero-tile__value { color: #1f2329; }
.sc-strategy-hero-tile__value.is-pos { color: var(--tt-up,   #26a69a); }
.sc-strategy-hero-tile__value.is-neg { color: var(--tt-down, #ef5350); }
/* [ADD] 2026-05-28 — Phase 2 / P2-13n — Equity curve SVG container. The
   SVG itself uses preserveAspectRatio=none so it stretches to the
   container width; min-height keeps the box at a stable size when
   the equity series is missing (empty placeholder). Line colors
   match the global tt-up / tt-down semantic palette. */
/* [REWRITE] 2026-05-28 — Phase 2 / P2-13p — Equity sparkline:
   TV / Bybit / Binance card-sparkline pattern (SVG renders the
   graphics only; numeric labels are real HTML and overlaid on
   the right gutter). Why:
   1. The previous SVG used preserveAspectRatio="none" so the
      browser X-stretched the viewBox to fill 100 % width. That
      pulled the path's geometry AND any <text> child along the
      same axis, producing "crooked tall digits".
   2. Real industry sparklines NEVER put numeric tick labels into
      the same SVG that's being stretched. They render the SVG as
      pure graphics + position HTML <span>/<div> labels in the
      gutter, so font-size is governed by CSS alone.
   3. We keep the SVG width: 100% but reserve `padding-right: 56px`
      on the container for the gutter where labels live. The SVG
      stops 8 px before the gutter so the path never crashes into
      the label column. */
/* [FIX] 2026-05-28 — Phase 2 / P2-13p² — Single-wrapper layout:
   - .sc-strategy-equity   → container with border/bg (height 100px)
   - .__svgwrap            → absolute drawing box, holds BOTH the SVG
                             AND the HTML labels. padding-right reserves
                             56 px for label pills. SVG (width:100%,
                             height:100%) fills the content-box (after
                             padding) cleanly because we use the
                             positioned-wrapper + plain SVG combination
                             that the browser spec defines without quirks.
   - .__lbl                → absolute child of the wrapper, right:6 px,
                             top:<%> relative to wrapper height. % maps
                             1:1 to SVG y-coords because wrapper height
                             IS the SVG drawing area height. */
.sc-strategy-equity {
    position: relative;
    width: 100%;
    height: 100px;
    border: 1px solid var(--border-medium);
    border-radius: 6px;
    background: rgba(255,255,255,0.02);
    box-sizing: border-box;
    overflow: hidden;
}
body:not(.dark-theme) .sc-strategy-equity {
    background: #ffffff;
    border-color: rgba(0,0,0,0.10);
}
.sc-strategy-equity__svgwrap {
    position: absolute;
    top: 6px;
    left: 8px;
    right: 8px;
    bottom: 6px;
    padding-right: 56px;
    box-sizing: border-box;
}
.sc-strategy-equity__svg {
    width: 100%;
    height: 100%;
    display: block;
    pointer-events: none;
}

.sc-strategy-equity__empty-text {
    position: absolute;
    top: 50%;
    left: 0;
    right: 0;
    transform: translateY(-50%);
    text-align: center;
    font-size: 12px;
    color: var(--text-secondary);
    font-style: italic;
    pointer-events: none;
}

.sc-strategy-equity__line { stroke-width: 1.5px; }
.sc-strategy-equity__line--up   { stroke: var(--tt-up,   #26a69a); }
.sc-strategy-equity__line--down { stroke: var(--tt-down, #ef5350); }

/* HTML labels — absolutely positioned in the right gutter.
   `top` is set inline (px-aligned with the path's Y coordinate),
   font-size is fixed 12 px (CSS white-list), text never warps
   because it lives in HTML coordinate space, not SVG. */
.sc-strategy-equity__lbl {
    position: absolute;
    right: 6px;
    transform: translateY(-50%);  /* center on the Y position */
    font-size: 12px;
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
    line-height: 1;
    padding: 1px 4px;
    border-radius: 2px;
    background: rgba(20, 24, 32, 0.55);
    pointer-events: none;
    white-space: nowrap;
}
body:not(.dark-theme) .sc-strategy-equity__lbl {
    background: rgba(255, 255, 255, 0.78);
    color: var(--text-secondary);
}
.sc-strategy-equity__lbl--up   { color: var(--tt-up,   #26a69a); }
.sc-strategy-equity__lbl--down { color: var(--tt-down, #ef5350); }
.sc-strategy-equity__lbl--zero { color: var(--text-secondary); }

.sc-strategy-snapshot__metrics {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 8px 12px;
}
.sc-strategy-metric {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.sc-strategy-metric__label {
    font-size: 11px;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.sc-strategy-metric__value {
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-variant-numeric: tabular-nums;
}
body:not(.dark-theme) .sc-strategy-metric__value { color: #1f2329; }
.sc-strategy-metric__value.is-pos { color: var(--tt-up,   #26a69a); }
.sc-strategy-metric__value.is-neg { color: var(--tt-down, #ef5350); }
.sc-strategy-snapshot__disclaimer {
    padding-top: 6px;
    border-top: 1px dashed var(--border-medium);
    font-size: 11px;
    color: var(--text-secondary);
    font-style: italic;
}
body:not(.dark-theme) .sc-strategy-snapshot__disclaimer {
    border-top-color: rgba(0,0,0,0.12);
}
.sc-strategy-snapshot__empty,
.sc-strategy-snapshot__error {
    padding: 8px 0;
    font-size: 12px;
    color: var(--text-secondary);
}

/* Release notes timeline (P2-10) -------------------------------------------- */
.sc-release-timeline {
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.sc-release-entry {
    border-left: 2px solid var(--border-medium);
    padding: 4px 0 4px 10px;
}
.sc-release-entry__head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 8px;
    margin-bottom: 4px;
}
.sc-release-entry__ver {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
}
.sc-release-entry__date {
    font-size: 11px;
    color: var(--text-secondary);
}
.sc-release-entry__body {
    font-size: 12px;
    line-height: 1.45;
    color: var(--text-primary);
    white-space: pre-wrap;
    word-break: break-word;
}
.sc-release-timeline__empty,
.sc-release-timeline__error {
    padding: 8px 0;
    font-size: 12px;
    color: var(--text-secondary);
}

/* Comments block (P2-9) ----------------------------------------------------- */
.sc-comments__signin {
    padding: 12px;
    border: 1px dashed var(--border-medium);
    border-radius: 6px;
    color: var(--text-secondary);
    font-size: 12px;
    text-align: center;
    margin-top: 14px;
    margin-bottom: 0;
}
.sc-comments__composer {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 14px;
    margin-bottom: 0;
}
/* [CHG] 2026-05-28 — Phase 2 / P2-13o — beef up the comment input
   visibility. The previous `--border-medium` token resolved to a
   barely-visible 8 % alpha line; in pro chat UIs (TV/Bybit/Slack)
   the input must read as an obvious text-entry surface. Use a
   stronger explicit border + a slightly raised inner surface for
   both themes; on focus highlight with the accent blue ring. */
.sc-comments__textarea {
    width: 100%;
    resize: vertical;
    min-height: 72px;
    max-height: 200px;
    padding: 10px 12px;
    font-size: 13px;
    line-height: 1.45;
    background: var(--surface-2, var(--background-secondary));
    color: var(--text-primary);
    border: 1px solid rgba(255,255,255,0.18);
    border-radius: 6px;
    box-sizing: border-box;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
body:not(.dark-theme) .sc-comments__textarea {
    border: 1px solid rgba(0,0,0,0.20);
    background: #ffffff;
}
.sc-comments__textarea:hover {
    border-color: rgba(255,255,255,0.30);
}
body:not(.dark-theme) .sc-comments__textarea:hover {
    border-color: rgba(0,0,0,0.32);
}
.sc-comments__textarea:focus {
    outline: none;
    border-color: var(--accent-primary, #2962ff);
    box-shadow: 0 0 0 2px rgba(41, 98, 255, 0.18);
}
.sc-comments__composer-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}
.sc-comments__counter {
    font-size: 11px;
    color: var(--text-secondary);
}
.sc-comments__counter.is-overflow { color: var(--accent-negative, #ef5350); }
.sc-comments__post {
    padding: 6px 14px;
    font-size: 12px;
    font-weight: 600;
    color: #fff;
    background: var(--accent-primary, #2962ff);
    border: none;
    border-radius: 4px;
    cursor: pointer;
}
.sc-comments__post:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}
.sc-comments__list {
    display: flex;
    flex-direction: column;
    gap: 12px;
}
.sc-comments__empty,
.sc-comments__error {
    padding: 16px 0;
    font-size: 12px;
    color: var(--text-secondary);
    text-align: center;
}
.sc-comment {
    display: flex;
    gap: 10px;
    align-items: flex-start;
}
.sc-comment__avatar {
    flex: 0 0 32px;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    overflow: hidden;
    display: inline-block;
    border: none;
    cursor: pointer;
}
.sc-comment__avatar img { width: 100%; height: 100%; object-fit: cover; }
.sc-comment__body {
    flex: 1 1 auto;
    min-width: 0;
}
.sc-comment__head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 2px;
}
.sc-comment__author {
    background: none;
    border: none;
    padding: 0;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-primary);
    cursor: pointer;
}
.sc-comment__author:hover { text-decoration: underline; }
.sc-comment__time {
    font-size: 11px;
    color: var(--text-secondary);
}
.sc-comment__del {
    margin-left: auto;
    background: none;
    border: none;
    padding: 2px 4px;
    color: var(--text-secondary);
    cursor: pointer;
    border-radius: 4px;
    line-height: 0;
}
.sc-comment__del:hover {
    color: var(--accent-negative, #ef5350);
    background: rgba(239, 83, 80, 0.08);
}
.sc-comment__text {
    font-size: 13px;
    line-height: 1.45;
    color: var(--text-primary);
    white-space: pre-wrap;
    word-break: break-word;
}
.sc-comments__footer {
    margin-top: 0;
    margin-bottom: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
    align-items: center;
}
.sc-comments__loadmore {
    padding: 6px 14px;
    font-size: 12px;
    color: var(--text-primary);
    background: transparent;
    border: 1px solid var(--border-medium);
    border-radius: 4px;
    cursor: pointer;
}
.sc-comments__loadmore:hover {
    background: var(--surface-2, var(--background-secondary));
}
.sc-comments__count {
    font-size: 11px;
    color: var(--text-secondary);
}

/* Report dialog (P2-10) ----------------------------------------------------- */
/* [CHG] 2026-05-28 — Phase 2 / P2-13o — Report modal visibility:
   HTML `hidden` attribute does NOT win against `display:flex` (CSS
   wins by specificity). The previous rule left the modal visible
   when `hidden` was set, which caused a "ghost panel" on first
   page-load. Fix: explicitly set `display:none` on the [hidden]
   variant; flex layout only when the dialog is actively open. */
.sc-report-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    display: none;
    align-items: center;
    justify-content: center;
    z-index: 10003;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
    padding: 16px;
    box-sizing: border-box;
}
.sc-report-overlay[hidden] { display: none !important; }
.sc-report-overlay.is-open {
    display: flex;
    opacity: 1;
    pointer-events: auto;
}
.sc-report-modal {
    width: min(440px, calc(100vw - 32px));
    max-height: calc(100vh - 64px);
    background: var(--surface-1, var(--background-primary));
    color: var(--text-primary);
    border: 1px solid var(--border-medium);
    border-radius: 8px;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.35);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.sc-report__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 16px;
    border-bottom: 1px solid var(--border-medium);
}
.sc-report__title {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
}
.sc-report__close {
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 4px;
    border-radius: 4px;
    line-height: 0;
}
.sc-report__close:hover { color: var(--text-primary); background: var(--surface-2, var(--background-secondary)); }
.sc-report__body {
    padding: 14px 16px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    overflow-y: auto;
}
.sc-report__targetnote {
    font-size: 12px;
    color: var(--text-secondary);
}
.sc-report__targetnote strong { color: var(--text-primary); }
.sc-report__error {
    padding: 8px 10px;
    border: 1px solid var(--accent-negative, #ef5350);
    background: rgba(239, 83, 80, 0.08);
    color: var(--accent-negative, #ef5350);
    border-radius: 4px;
    font-size: 12px;
}
.sc-report__field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.sc-report__label {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.sc-report__opts {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.sc-report__opt {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: var(--text-primary);
    cursor: pointer;
    padding: 4px 6px;
    border-radius: 4px;
}
.sc-report__opt:hover { background: var(--surface-2, var(--background-secondary)); }
.sc-report__textarea {
    width: 100%;
    resize: vertical;
    min-height: 64px;
    max-height: 180px;
    padding: 8px 10px;
    font-size: 13px;
    line-height: 1.45;
    background: var(--surface-2, var(--background-secondary));
    color: var(--text-primary);
    border: 1px solid var(--border-medium);
    border-radius: 4px;
    box-sizing: border-box;
}
.sc-report__textarea:focus { outline: none; border-color: var(--accent-primary, #2962ff); }
.sc-report__counter {
    font-size: 11px;
    color: var(--text-secondary);
    align-self: flex-end;
}
.sc-report__counter.is-overflow { color: var(--accent-negative, #ef5350); }
.sc-report__disclaimer {
    font-size: 11px;
    color: var(--text-secondary);
    font-style: italic;
    padding-top: 4px;
    border-top: 1px dashed var(--border-medium);
}
.sc-report__foot {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 12px 16px;
    border-top: 1px solid var(--border-medium);
    background: var(--surface-2, var(--background-secondary));
}
.sc-report__btn {
    padding: 6px 14px;
    font-size: 12px;
    font-weight: 600;
    border-radius: 4px;
    cursor: pointer;
    border: 1px solid transparent;
}
.sc-report__cancel {
    background: transparent;
    color: var(--text-primary);
    border-color: var(--border-medium);
}
.sc-report__cancel:hover { background: var(--surface-1, var(--background-primary)); }
.sc-report__confirm {
    background: var(--accent-negative, #ef5350);
    color: #fff;
    border-color: var(--accent-negative, #ef5350);
}
.sc-report__confirm:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

/* Publish modal — Phase 2 additions (release notes / backtest dropdown) ------ */
.tt-publish__required { color: var(--accent-negative, #ef5350); }
/* [CHG] 2026-05-28 — P2-13e — Backtest empty-state hint was a boxed
   "alert" with dashed border that took ~38px tall. The text alone is
   enough — strip the box so it visually pairs with the .tt-publish__hint
   disclaimer immediately below it, both sitting as compact 11px gray
   lines. The two messages stay logically distinct (one is "what to do",
   the other is "what these results mean") but visually unified. */
.tt-publish__bt-empty {
    margin-top: 4px;
    padding: 0;
    font-size: 11px;
    line-height: 1.4;
    color: var(--text-secondary, #b1b5c2);
}

/* Mobile: keep snapshot 2-col + collapse report-foot to vertical ------------ */
@media (max-width: 768px) {
    .sc-strategy-hero {
        grid-template-columns: repeat(3, 1fr);
        gap: 6px;
    }
    .sc-strategy-hero-tile { padding: 8px 10px; }
    .sc-strategy-hero-tile__value { font-size: 16px; }
    .sc-strategy-snapshot__metrics {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
    .sc-report__foot {
        flex-direction: column-reverse;
    }
    .sc-report__btn { width: 100%; }
    .sc-comment__avatar { flex-basis: 28px; width: 28px; height: 28px; }
}
/* ============================================================================
 * END Phase 2 detail-drawer additions (P2-9 / P2-10), 2026-05-28
 * ============================================================================ */

/* ============================================================================
 * [ADD] 2026-05-28 — Phase 3b — .tt-invite-access modal (Manage subscribers)
 *
 * Author-side modal opened from My Scripts ▸ Manage subs on an invite_only
 * server-run script. Wraps the existing .tt-publish-overlay / .tt-publish-modal
 * frame, then injects four sections inside .tt-publish__body:
 *
 *   1) Status row     — review_status badge + admin-approval hint
 *   2) Grant access   — handle <input> + Grant button
 *   3) Current subs   — list of granted users (with Revoke buttons)
 *   4) Server runs    — live runs (status / tf / subs / worker / last_bar)
 *
 * All children are double-underscore BEM (.tt-invite-access__*). The outer
 * frame inherits all desktop sizing, dark/light theming, 160ms open
 * transition, and 768px bottom-sheet mobile layout from .tt-publish-overlay.
 *
 * Adds one new generic button variant .tt-publish__btn--danger so the
 * Revoke button shares the publish-modal visual contract instead of
 * inventing its own.
 *
 * Sources cross-checked at write time:
 *   - tt.scripts.invite-access.js (HTML structure)
 *   - tt.scripts.publish.js       (proven open/close pattern + class names)
 *   - web_app/views/chart.ejs L3680 (script include order)
 * ============================================================================ */

/* Danger variant (Revoke / destructive actions) — pairs with --confirm. */
.tt-publish__btn--danger {
    background: rgba(239, 83, 80, 0.10);
    color: var(--price-down, #ef5350);
    border-color: rgba(239, 83, 80, 0.5);
    min-width: 0;
    padding: 0 12px;
}
.tt-publish__btn--danger:hover:not([disabled]),
.tt-publish__btn--danger:focus-visible {
    background: rgba(239, 83, 80, 0.18);
    border-color: var(--price-down, #ef5350);
}
.tt-publish__btn--danger[disabled] {
    background: rgba(239, 83, 80, 0.05);
    color: rgba(239, 83, 80, 0.5);
    border-color: rgba(239, 83, 80, 0.2);
    cursor: not-allowed;
}

/* Slightly wider than the default 560px publish modal — three list sections
   need horizontal room for the run row's status / tf / subs / worker
   columns. Capped at 90vw so it still respects desktop side margins. */
.tt-invite-access__modal {
    width: min(640px, 90vw);
}

/* [ADD] 2026-05-29 — virhu Yao directive: the Manage-subscribers panel
   now uses the non-modal `.tt-publish-overlay--panel` chrome (transparent
   overlay, pointer-events:none on empty area, header drag, etc.).
   We override two things vs. the default Publish-panel pose so it
   matches the chart Indicator-Settings dialog (#indSettingsModal):

     1. Centred default position (was: top:12vh / left:50% / translateX
        only — biased to the top of the viewport). The Indicator
        Settings dialog uses translate(-50%, -50%) for true centring;
        we do the same here.
     2. Width stays 640px (inherited from .tt-invite-access__modal
        above — we deliberately do NOT inherit the panel's 820px).

   Selector specificity:
     .tt-publish-overlay--panel .tt-invite-access__modal beats
     .tt-publish-overlay--panel .tt-publish-modal (the base panel pose)
     by one extra class, so the `top` / `left` / `transform` overrides
     apply without !important.

   The `:not(.is-default-pos)` variant clears the centring transform so
   JS-driven `left:/top:` inline px values (set by _setupDragging in
   tt.scripts.invite-access.js) take effect cleanly after the first
   user drag. The same pattern is used by the Publish panel — see
   `.tt-publish-overlay--panel .tt-publish-modal:not(.is-default-pos)`
   in this file. */
.tt-publish-overlay--panel .tt-invite-access__modal.is-default-pos {
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}
.tt-publish-overlay--panel .tt-invite-access__modal:not(.is-default-pos) {
    transform: none;
}

/* [ADD] 2026-05-29 — Context strip rendered at the top of the modal
   body (right after the (optional) error banner, before the first
   Grant/Subscribers/Runs section). Hosts a small uppercase muted
   label ("Script") + the actual script name in primary text. This
   exists so the modal <h2> title can stay short ("Manage subscribers")
   regardless of how long the script's name is — per virhu Yao
   directive 2026-05-29. Visual idiom mirrors TradingView / Bloomberg
   modal subtitles: small caps label, slightly larger primary text,
   thin accent rule on the left, full width within the body. */
.tt-invite-access__context {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 8px 12px;
    background: var(--bg-tertiary, rgba(255,255,255,0.04));
    border-left: 2px solid var(--accent-color, #2962ff);
    border-radius: 4px;
}
body:not(.dark-theme) .tt-invite-access__context {
    background: rgba(0,0,0,0.04);
}
.tt-invite-access__context[hidden] { display: none; }
.tt-invite-access__context-label {
    flex: 0 0 auto;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-secondary);
}
.tt-invite-access__context-name {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary, #fff);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
body:not(.dark-theme) .tt-invite-access__context-name { color: #000; }

/* Compact pill used on subscriber rows to flag "Webhook configured".
   Originally part of the admin-review badge family — review states
   (unreviewed/approved/rejected) and the status-row banner have been
   retired (see docs/CHART-策略脚本社区-实施实时文档-2026-05-28.md §A2);
   only the webhook variant remains. */
.tt-invite-access__badge {
    display: inline-flex;
    align-items: center;
    padding: 2px 10px;
    border-radius: 12px;
    font-size: 12px;
    font-weight: 600;
    line-height: 1.5;
    white-space: nowrap;
}
.tt-invite-access__badge--web {
    background: rgba(41, 98, 255, 0.18);
    color: #2962ff;
    padding: 2px 8px;
    font-size: 12px;
}

/* Section block (Grant / Subscribers / Runs). Each one stacks h3 + content. */
.tt-invite-access__section {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.tt-invite-access__h3 {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary, #fff);
}
body:not(.dark-theme) .tt-invite-access__h3 { color: #000; }

/* Grant row: <input> + <button>. Desktop side-by-side, mobile stacks. */
.tt-invite-access__grant-row {
    display: flex;
    align-items: stretch;
    gap: 8px;
}
.tt-invite-access__handle {
    flex: 1 1 auto;
    min-width: 0;
}

/* [ADD] 2026-05-29 — Helper text under the Grant input.
   Tells users to type the @handle (not the display_name) and points
   them to Account → Profile to set it. Subtle styling that doesn't
   compete with the input row itself. */
.tt-invite-access__help {
    margin-top: 6px;
    font-size: 12px;
    color: var(--text-secondary, var(--tt-fg-2, #787b86));
    line-height: 1.5;
}

/* List containers — bounded height with native scrollbar (rules §"滚动条
   行为" in web_app_styling.mdc handles thumb visibility globally). */
.tt-invite-access__list,
.tt-invite-access__runs {
    display: flex;
    flex-direction: column;
    gap: 6px;
    max-height: 188px;
    overflow-y: auto;
    padding-right: 2px;
}
.tt-invite-access__empty {
    padding: 16px;
    text-align: center;
    font-size: 12px;
    color: var(--text-secondary, #b1b5c2);
    border: 1px dashed var(--popover-border, #2a2e39);
    border-radius: 6px;
}
body:not(.dark-theme) .tt-invite-access__empty {
    color: #6b6f80;
    border-color: #d1d4dc;
}

/* Subscriber row (one per granted user). */
.tt-invite-access__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 7px 10px;
    background: var(--bg-tertiary, rgba(255, 255, 255, 0.04));
    border: 1px solid var(--popover-border, #2a2e39);
    border-radius: 6px;
}
body:not(.dark-theme) .tt-invite-access__row {
    background: rgba(0, 0, 0, 0.03);
    border-color: #d1d4dc;
}
.tt-invite-access__row-name {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
    flex: 1 1 auto;
}
.tt-invite-access__row-name strong {
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary, #fff);
    word-break: break-all;
    line-height: 1.25;
}
body:not(.dark-theme) .tt-invite-access__row-name strong { color: #000; }
.tt-invite-access__row-meta {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0 8px;
}
.tt-invite-access__row-date {
    font-size: 11px;
    color: var(--text-secondary, #b1b5c2);
    line-height: 1.25;
}
/* [ADD] 2026-05-29 — Expiry pill below the granted-at date.
   Renders muted-green when active and amber-flagged (is-soon) when
   < 7 days remain. The text stays plain (no background pill) to
   match the surrounding minimal-table aesthetic of the panel. */
.tt-invite-access__row-expires {
    font-size: 11px;
    color: var(--text-secondary, #b1b5c2);
    line-height: 1.25;
}
.tt-invite-access__row-expires.is-soon {
    color: #f0b90b;
}
body:not(.dark-theme) .tt-invite-access__row-expires.is-soon {
    color: #b07a06;
}
/* Expiry input sits between the @handle field and the Grant button.
   Width is fixed (matches typical YYYY-MM-DD render width with the
   native calendar icon) so the row layout stays predictable across
   browsers; it shrinks to full-width on mobile via the breakpoint
   override below. */
.tt-invite-access__expires {
    flex: 0 0 auto;
    width: 160px;
    min-width: 0;
    /* [FIX] 2026-05-31 — whole-control click target for the native date picker.
       Reason: a bare <input type="date"> only opens its calendar when the user
       clicks the narrow ::-webkit-calendar-picker-indicator hit-box; clicking
       the date text or the (background-image) icon did nothing, so users had to
       hunt for a narrow sliver between text and icon.
       Change: make the input a positioning context + pointer cursor, then below
       stretch the (transparent) picker indicator to cover the full field so a
       click anywhere opens the calendar.
       Warning: keep position:relative — the full-bleed indicator depends on it. */
    position: relative;
    cursor: pointer;
}
/* Stretch the real (clickable) calendar-picker indicator to fill the whole
   input on BOTH themes. opacity:0 hides the UA glyph (we draw our own icon via
   the background-image below); the indicator itself stays the click target, now
   spanning the entire field. Scoped to .tt-invite-access__expires so no other
   date input on the chart changes. */
.tt-invite-access__expires::-webkit-calendar-picker-indicator {
    position: absolute;
    inset: 0;
    width: auto;
    height: auto;
    margin: 0;
    padding: 0;
    opacity: 0;
    cursor: pointer;
}
/* [FIX] 2026-05-31 — Manage-subscribers expiry picker icon visibility.
   Reason: native calendar icon of <input type="date"> can render with poor
   contrast depending on theme/UA.
   Change: render a per-theme icon via background-image + color-scheme hint;
   the actual click target remains the full-bleed transparent indicator above.
   Warning: keep scoped to .tt-invite-access__expires to avoid global date
   input appearance drift across unrelated chart panels. */
body.dark-theme .tt-invite-access__expires {
    color-scheme: dark;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'/%3E%3Cline x1='16' y1='2' x2='16' y2='6'/%3E%3Cline x1='8' y1='2' x2='8' y2='6'/%3E%3Cline x1='3' y1='10' x2='21' y2='10'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 10px center;
    background-size: 14px 14px;
    padding-right: 34px;
}
body:not(.dark-theme) .tt-invite-access__expires {
    color-scheme: light;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23434651' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'/%3E%3Cline x1='16' y1='2' x2='16' y2='6'/%3E%3Cline x1='8' y1='2' x2='8' y2='6'/%3E%3Cline x1='3' y1='10' x2='21' y2='10'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 10px center;
    background-size: 14px 14px;
    padding-right: 34px;
}
.tt-invite-access__row-actions {
    display: flex;
    align-items: center;
    gap: 5px;
    flex: 0 0 auto;
}
/* Compact Revoke button to free vertical and horizontal space. */
.tt-invite-access__row-actions .tt-publish__btn--danger {
    min-height: 22px;
    height: 22px;
    padding: 0 8px;
    font-size: 11px;
    border-radius: 4px;
}

/* [FIX] 2026-05-31 — Manage-subscribers action buttons theme-tone alignment.
   Reason: Grant inherited the global high-contrast confirm style
   (dark:white / light:black), which looked detached from this modal's
   native palette. Close should visually match Indicator Settings "Cancel".
   Change: scope button tones to invite-access only:
     - Grant uses same family as .ind-settings-btn-save.
     - Close uses same family as .ind-settings-btn-cancel.
   Warning: keep scoped under .tt-invite-access__modal to avoid changing
   other publish-related dialogs. */
.tt-invite-access__modal .tt-publish__btn--confirm {
    background: var(--state-active, #2962ff);
    border-color: var(--state-active, #2962ff);
    color: #fff;
}
.tt-invite-access__modal .tt-publish__btn--confirm:hover:not([disabled]),
.tt-invite-access__modal .tt-publish__btn--confirm:focus-visible:not([disabled]) {
    background: #1f54f0;
    border-color: #1f54f0;
    color: #fff;
    box-shadow: none;
}
.tt-invite-access__modal .tt-publish__btn--confirm[disabled] {
    background: rgba(41, 98, 255, 0.45);
    border-color: rgba(41, 98, 255, 0.45);
    color: rgba(255, 255, 255, 0.76);
}
.tt-invite-access__modal .tt-publish__btn--cancel {
    background: transparent;
    border: 1px solid var(--text-secondary);
    color: var(--text-primary);
}
.tt-invite-access__modal .tt-publish__btn--cancel:hover,
.tt-invite-access__modal .tt-publish__btn--cancel:focus-visible {
    background: var(--bg-hover);
    border-color: var(--text-secondary);
    color: var(--text-primary);
    box-shadow: none;
}

/* Server run row — two-line layout: status+tf+subs / last_bar. */
.tt-invite-access__run {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 7px 10px;
    background: var(--bg-tertiary, rgba(255, 255, 255, 0.04));
    border: 1px solid var(--popover-border, #2a2e39);
    border-radius: 6px;
}
body:not(.dark-theme) .tt-invite-access__run {
    background: rgba(0, 0, 0, 0.03);
    border-color: #d1d4dc;
}
.tt-invite-access__run-line1 {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}
.tt-invite-access__run-line2 {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    font-size: 11px;
    color: var(--text-secondary, #b1b5c2);
}
.tt-invite-access__run-status {
    display: inline-block;
    padding: 1px 6px;
    border-radius: 10px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    background: rgba(127, 127, 127, 0.18);
    color: var(--text-primary, #fff);
}
/* [FIX] 2026-05-28 — Round 9 audit: split starting / running into distinct
   color cues so the operator can visually tell the transient state from
   the steady-state. starting = blue (matches publish modal --confirm),
   running = green (financial industry "live"). idle = yellow, pending =
   neutral grey, failed/reaped = red. */
.tt-invite-access__run-status--starting { background: rgba(41, 98, 255, 0.18); color: #2962ff; }
.tt-invite-access__run-status--running  { background: rgba(38, 166, 154, 0.18); color: var(--price-up, #26a69a); }
.tt-invite-access__run-status--idle     { background: rgba(255, 193, 7, 0.18);  color: #ffa726; }
.tt-invite-access__run-status--pending  { background: rgba(127, 127, 127, 0.18); color: var(--text-secondary, #b1b5c2); }
.tt-invite-access__run-status--failed,
.tt-invite-access__run-status--reaped   { background: rgba(239, 83, 80, 0.18); color: var(--price-down, #ef5350); }
.tt-invite-access__run-tf {
    font-weight: 600;
    color: var(--text-primary, #fff);
    font-size: 12px;
}
body:not(.dark-theme) .tt-invite-access__run-tf { color: #000; }
.tt-invite-access__run-subs {
    font-size: 11px;
    color: var(--text-secondary, #b1b5c2);
}
.tt-invite-access__run-err {
    margin-top: 2px;
    padding: 3px 6px;
    font-size: 11px;
    color: var(--price-down, #ef5350);
    background: rgba(239, 83, 80, 0.10);
    border-radius: 4px;
    word-break: break-word;
}

/* Mobile responsive — most layout already inherited from .tt-publish-modal
   mobile rules. We only need to stack the grant row vertically (input full
   width + button below) and let row-actions wrap to a second line if the
   subscriber name is long. */
@media (max-width: 768px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    .tt-invite-access__modal { width: 100vw; max-width: 100vw; }
    /* [ADD] 2026-05-29 — Mobile bottom-sheet must also clear the desktop
       centring transform (top:50% / left:50% / translate(-50%,-50%))
       introduced in the non-modal redesign — otherwise mobile invite-access
       paints centred (not at the bottom) because our desktop
       .tt-publish-overlay--panel .tt-invite-access__modal.is-default-pos
       rule has higher specificity than the generic mobile
       .tt-publish-overlay--panel .tt-publish-modal reset (which uses
       .tt-publish-modal — one fewer class than .tt-invite-access__modal).
       Mirror exactly the same `position: static` / `transform: none`
       reset the panel base uses on mobile so the bottom-sheet renders. */
    .tt-publish-overlay--panel .tt-invite-access__modal,
    .tt-publish-overlay--panel .tt-invite-access__modal.is-default-pos,
    .tt-publish-overlay--panel .tt-invite-access__modal:not(.is-default-pos) {
        position: static;
        top: auto;
        left: auto;
        transform: none;
    }
    .tt-invite-access__grant-row {
        flex-direction: column;
        gap: 10px;
    }
    .tt-invite-access__handle {
        font-size: 16px;  /* iOS Safari ≥16px to avoid focus-zoom */
    }
    .tt-invite-access__expires {
        width: 100%;
        font-size: 16px;
    }
    .tt-invite-access__row {
        flex-wrap: wrap;
    }
    .tt-invite-access__row-name { flex: 1 1 100%; }
    .tt-invite-access__row-actions {
        width: 100%;
        justify-content: flex-end;
    }
    /* List sections take a touch more vertical space on phones because
       the screen real estate is taller and bottom sheet is full-height. */
    .tt-invite-access__list,
    .tt-invite-access__runs {
        max-height: 280px;
    }
}
/* ============================================================================
 * END Phase 3b — .tt-invite-access (2026-05-28)
 * ============================================================================ */

/* ============================================================================
 * Global scrollbar baseline for the chart page (P2-13h, 2026-05-28)
 *
 * Why we need this:
 *   • New sub-panes (yes-sub-pane, Strategy-Tester trades table, future
 *     Phase 3b drawers, etc.) gained `overflow: auto;` without anyone
 *     also writing the matching `::-webkit-scrollbar` overrides.
 *     The result was the OS-default 16px gray scrollbar painting next to
 *     elegant 3px scrollbars elsewhere — the user saw it as visual chaos.
 *   • Earlier we patched every offender one-by-one (.bp-sub-pane,
 *     .sc-list, .yes-console, .ca-body, .pt-content, …). Every new
 *     module repeated the same omission. Babysitting per-module rules
 *     does not scale.
 *
 * Strategy:
 *   • One low-specificity (0,0,1) selector tree on `*` paints the
 *     "market module symbol-list" scrollbar (default-invisible,
 *     container-hover shows light-gray thumb, thumb-hover shows
 *     darker gray, 3px width, 2px radius). This becomes the universal
 *     default for every scroll container the chart page renders.
 *   • Any module that genuinely needs different behavior (e.g. mobile
 *     orderbook keeps a 4px always-visible scrollbar — touch users
 *     cannot hover) already has a more-specific selector (0,1,1 or
 *     higher) or uses !important, so it naturally overrides our base.
 *
 * What this fixes today (2026-05-28):
 *   • Strategy Tester sub-panes (Backtest report / Trades table /
 *     Properties) used to paint OS-default scrollbars; now matches
 *     symbol-list.
 *   • All future modules inherit the canonical Tradeiz scrollbar
 *     without their author having to remember to copy 5 selectors.
 *
 * Firefox (Gecko):
 *   • scrollbar-width: thin paints a ~6px scrollbar at minimum.
 *     We use `thin` + transparent thumb by default, then color the
 *     thumb on container hover via scrollbar-color.
 *
 * Hard rule:
 *   • DO NOT add per-module ::-webkit-scrollbar blocks for new
 *     containers. If a module legitimately needs different visuals
 *     (e.g. always-visible for touch), use !important so the intent
 *     is explicit in the diff. Otherwise rely on this global default.
 * ============================================================================ */
* {
    scrollbar-width: thin;
    scrollbar-color: transparent transparent;
}
*::-webkit-scrollbar {
    width: 3px;
    height: 3px;
}
*::-webkit-scrollbar-track {
    background: transparent;
}
*::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 2px;
    transition: background 0.2s;
}
*:hover {
    scrollbar-color: #c8ccd8 transparent;
}
*:hover::-webkit-scrollbar-thumb {
    background: #c8ccd8;
}
*::-webkit-scrollbar-thumb:hover {
    background: #a0a4b0;
}
body:not(.dark-theme) *:hover {
    scrollbar-color: #a0a4b0 transparent;
}
body:not(.dark-theme) *:hover::-webkit-scrollbar-thumb {
    background: #a0a4b0;
}
body:not(.dark-theme) *::-webkit-scrollbar-thumb:hover {
    background: #6a6e7a;
}

/* ============================================================================
 * .yes-body / .yes-sub-pane [hidden] enforcement (P2-13h, 2026-05-28)
 *
 * The HTML5 [hidden] attribute is a user-agent stylesheet rule that maps
 * to `display: none`, but its specificity is just `[hidden]` = (0,1,0).
 * Many of our sub-panes already declare `display: flex` (or grid) via a
 * class selector at (0,1,0), so they tie on specificity but the class
 * wins by source order (it's defined after the UA stylesheet). Result:
 * Strategy-Tester clicking "Backtest" successfully sets
 * #yesBody.hidden = true, but Editor + Console stay painted because
 * `.yes-body { display: flex }` overrides `[hidden] { display: none }`.
 *
 * Fix: pin [hidden] back to `display: none !important` on these specific
 * containers. Limited to the strategy-tester containers (NOT a blanket
 * *[hidden]) because some unrelated code paths may rely on `[hidden]`
 * semantics intentionally being soft. The intent here is narrow: "when
 * JS says hide me, hide me, full stop."
 * ============================================================================ */
.yes-body[hidden],
.yes-sub-pane[hidden],
.yes-st-body[hidden],
.yes-st-empty[hidden] {
    display: none !important;
}


/* ============================================================================
 * [ADD] 2026-05-29 — YeScript UI Redesign (CHART-yescript-UI-实施规范-2026-05-29.md)
 *
 * Scope: Scripts Library panel (#bpPaneScripts) — Phase 1 visual refresh.
 *   - My Scripts        → dense table grid (.sc-tr / .sc-th)
 *   - Favorites         → hybrid rows with inline KPIs (.sc-fav-row)
 *   - Community         → auto-fill responsive grid (.ys-script-card)
 *   - Nav tabs          → TV-style underline (.sc-nav, .sc-nav-tab)
 *   - More menu         → per-row dropdown (.sc-more-menu, .sc-mi)
 *   - Drawer            → position:fixed NON-MODAL (spec §6.1, no overlay)
 *
 * Coexists with legacy .sc-card / .sc-list / .sc-action-btn styles above
 * for backwards-compat during incremental migration. Once tt.scripts.js
 * is fully rewritten the legacy rules become dead code (kept for safety).
 *
 * Visual reference: docs/mockup-yescript-ui-redesign-v4.html
 * Constraint: chart-page tooltips must use .tt-help (web_app_styling.mdc §6)
 * ============================================================================ */

/* ── Nav tabs (TV-style underline) ───────────────────────────────────────── */
.sc-nav {
    height: 36px;
    display: flex;
    align-items: flex-end;
    border-bottom: 1px solid var(--border-color);
    padding: 0 10px;
    flex-shrink: 0;
    gap: 0;
}
.sc-nav-tab {
    height: 36px;
    padding: 0 13px;
    border: none;
    border-bottom: 2px solid transparent;
    background: transparent;
    color: var(--text-secondary);
    /* [FIX] 2026-05-29 — YeScript UI Redesign §5.1 — unified 14px font
     * size across Scripts Library (tabs / buttons / rows / column
     * headers) for consistent reading density (虎哥反馈). */
    font-size: 14px;
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: -1px;
    transition: color .12s;
    cursor: pointer;
    font-family: inherit;
}
.sc-nav-tab:hover { color: var(--text-primary); }
.sc-nav-tab.active {
    color: var(--text-primary);
    border-bottom-color: var(--accent-color, #2962ff);
    font-weight: 500;
}
.sc-nav-count {
    font-size: 10px;
    color: var(--text-secondary);
    background: var(--bg-input);
    border-radius: 9px;
    padding: 1px 6px;
    line-height: 1.5;
    min-width: 18px;
    text-align: center;
}
.sc-nav-tab.active .sc-nav-count {
    background: rgba(41, 98, 255, 0.2);
    color: var(--accent-color, #2962ff);
}
.sc-nav-right {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 4px;
    padding-bottom: 4px;
}

/* [ADD] 2026-05-29 v20260529.13 — Scripts Library nav grouping (spec §5.1).
 * Two visual clusters (PERSONAL / PUBLIC) separated by a 1px vertical
 * divider. The labels are small uppercase captions that sit BEFORE
 * their group's tabs and identify the grouping at a glance — same
 * pattern as TradingView's "PERSONAL / PUBLISHED" navigation. The
 * divider sits OUTSIDE the active tab's underline ink, so flipping
 * tabs never visually disturbs the column rule.
 * Warning: keep .sc-nav-group-lbl unhoverable / uncliackable (pointer-
 * events:none) — otherwise the label steals focus from the next tab
 * and reads as an extra button to screen readers. */
.sc-nav-group-lbl {
    display: inline-flex;
    align-items: center;
    height: 36px;
    padding: 0 8px 0 4px;
    margin-bottom: -1px;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.7px;
    text-transform: uppercase;
    color: var(--text-secondary);
    opacity: 0.55;
    pointer-events: none;
    flex-shrink: 0;
    user-select: none;
}
.sc-nav-div {
    flex-shrink: 0;
    width: 1px;
    height: 18px;
    margin: 0 6px;
    background: var(--border-color);
    align-self: center;
}

/* ── My Scripts: compact table grid ──────────────────────────────────────── */
.sc-table-wrap {
    flex: 1;
    min-height: 0;
    min-width: 0;
    max-width: 100%;
    overflow-x: clip;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
}
/* Column template: icon | name | visibility | strategy KPIs | tags/context | actions
 * [CHG] 2026-06-02 — virhu 2026-06-02 feedback batch 5: actions
 *   column expanded 140px → 220px so the visibility / strategy-
 *   report / tags-context columns sit further away from the right-
 *   edge action buttons, avoiding the cramped overlap between
 *   "tags/context" and "Add to Chart". The buttons themselves are
 *   right-anchored inside the actions cell (.sc-tr-actions justify-
 *   content:flex-end), so widening the column moves the left edge
 *   of the cell leftward (= more breathing room for column 5)
 *   without shifting the buttons. 220px also accommodates the three
 *   localised buttons (Load / Add to Chart / Run on Server) at full
 *   labels in any language without squeezing. */
.sc-th {
    display: grid;
    grid-template-columns: 28px minmax(0, 1.4fr) 110px minmax(0, 1.4fr) minmax(0, 1fr) 220px;
    gap: 0;
    /* [FIX] 2026-05-29 — column-header row height bumped from 26px to
     * 32px to match the new 14px font (虎哥反馈: 统一 14px). */
    height: 32px;
    align-items: center;
    padding: 0 10px;
    border-bottom: 1px solid var(--border-color);
    background: var(--bg-secondary);
    flex-shrink: 0;
    /* [FIX] 2026-05-29 — was 10px (TV-style micro caps); virtualized to
     * 14px per 虎哥反馈 to align with all Scripts Library text. The
     * uppercase + letter-spacing is dropped because 14px uppercase is
     * visually too loud for a column header. */
    font-size: 14px;
    color: var(--text-secondary);
    font-weight: 600;
    letter-spacing: 0.2px;
    position: sticky;
    top: 0;
    z-index: 2;
}
.sc-th-cell { padding: 0 6px; }
.sc-th-cell--actions { text-align: right; }

.sc-tr {
    display: grid;
    grid-template-columns: 28px minmax(0, 1.4fr) 110px minmax(0, 1.4fr) minmax(0, 1fr) 220px;
    gap: 0;
    /* [FIX] 2026-05-29 — row min-height 36→44 to accommodate the new
     * 14px content (虎哥反馈: 统一 14px). */
    min-height: 44px;
    align-items: center;
    padding: 4px 10px;
    border-bottom: 1px solid var(--border-color);
    transition: background .1s;
    /* [FIX] 2026-05-29 — was 12px; 14px to match the unified Scripts
     * Library text size. */
    font-size: 14px;
}
.sc-tr.is-pub { cursor: pointer; }
.sc-tr.is-pub:hover { background: var(--bg-hover); }
.sc-tr.is-priv:hover { background: var(--bg-input); }
.sc-tr-cell {
    padding: 0 6px;
    min-width: 0;
    display: flex;
    align-items: center;
}
.sc-tr-icon {
    width: 20px;
    height: 20px;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    color: var(--text-secondary);
}
.sc-tr-icon.is-strategy  { background: rgba(38, 166, 154, 0.15); color: #26a69a; }
.sc-tr-icon.is-indicator { background: rgba(41, 98, 255, 0.15); color: #6299ff; }
.sc-tr-icon.is-library   { background: rgba(171, 116, 248, 0.15); color: #ab74f8; }
.sc-tr-icon svg { width: 13px; height: 13px; }

.sc-tr-main {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    width: 100%;
}
/* [FIX] 2026-05-29 — name/meta/name-icon all bumped to 14px per 虎哥 unified
 * Scripts Library font request. The "↗" icon stays one tick smaller (12px)
 * so it remains a hint rather than competing with the name text. */
.sc-tr-name {
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.sc-tr-name-icon {
    color: var(--text-secondary);
    font-weight: 400;
    font-size: 12px;
    margin-left: 4px;
}
/* [CHG] 2026-06-02 — virhu 2026-06-02 feedback batch 7 + batch 8:
 *   batch 7 compressed gap to 4px ("缩短三分之二"), batch 8 doubled
 *   it back to 8px ("在现在的基础上增加一倍距离" — 4px was too tight).
 *   My Scripts meta line stays in sync with Favorites (same 8px). */
/* [CHG] 2026-06-02 — virhu round 11: align the meta line right
 *   (type chip · visibility · ★ · vN · by author) so every script
 *   row's secondary info hugs the row's right edge regardless of the
 *   script name length. Same treatment applies to Favorites / Invited
 *   / Community / Running (see .sc-fav-meta below). Author slot is no
 *   longer flex-grow:1 — it would otherwise push the leading chips
 *   back to the left. */
.sc-tr-meta {
    font-size: 14px;
    color: var(--text-secondary);
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    overflow: hidden;
}
.sc-tr-meta.sc-meta-slots {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    align-items: center;
}
.sc-type-chip {
    font-size: 10px;
    padding: 0 5px;
    border-radius: 2px;
    font-weight: 600;
    text-transform: uppercase;
    line-height: 1.6;
    flex-shrink: 0;
}
.sc-type-chip.is-strategy  { background: rgba(38, 166, 154, 0.15); color: #26a69a; }
.sc-type-chip.is-indicator { background: rgba(41, 98, 255, 0.15); color: #6299ff; }
.sc-type-chip.is-library   { background: rgba(171, 116, 248, 0.15); color: #ab74f8; }

/* [FIX] 2026-05-29 v20260529.14 — Visibility badge font 10 → 14px per
 * 虎哥反馈; padding / border-radius / line-height proportionally
 * adjusted so the badge stays a clean pill (TradingView style) and
 * doesn't squish the 14px text. 14px aligns with the Scripts Library
 * unified text size (sc-th / sc-tr / sc-tr-act / sc-mi etc.). */
.sc-status-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 14px;
    padding: 3px 10px;
    border-radius: 12px;
    font-weight: 600;
    line-height: 1.35;
}
.sc-status-badge.is-public {
    background: rgba(38, 166, 154, 0.15);
    color: #26a69a;
    border: 1px solid rgba(38, 166, 154, 0.2);
}
.sc-status-badge.is-private {
    background: var(--bg-input);
    color: var(--text-secondary);
    border: 1px solid var(--border-color);
}
.sc-status-badge.is-protected {
    background: rgba(255, 183, 77, 0.1);
    color: #ffb74d;
    border: 1px solid rgba(255, 183, 77, 0.2);
}
.sc-status-badge.is-invite {
    background: rgba(41, 98, 255, 0.12);
    color: #6299ff;
    border: 1px solid rgba(41, 98, 255, 0.2);
}

/* [ADD] 2026-06-02 — virhu 2026-06-02 feedback batch 3:
 *   Compact visibility chip for inline meta rows (Favorites + Community).
 *   Re-uses .sc-status-badge palettes (.is-public/-protected/-invite/
 *   -private) but overrides typography to match the adjacent
 *   .sc-type-chip (10px uppercase) instead of the larger 14px badge
 *   used in the My Scripts table column. Keeps the four colour
 *   variants visually consistent across surfaces while staying inline
 *   with the type chip on the same meta row.
 *   Web_app styling rule §1-3 do NOT apply to chart.ejs / app.css per
 *   the rule's "完全排除" list — this 10px is the same baseline as
 *   .sc-type-chip and matches the chart-page design language.
 */
.sc-vis-chip.sc-status-badge {
    font-size: 10px;
    padding: 0 6px;
    border-radius: 2px;
    line-height: 1.6;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    flex-shrink: 0;
}

/* Strategy KPI mini stats (My Scripts middle column) */
.sc-tr-stats {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
}
.sc-mini-stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    flex: 1;
    min-width: 0;
}
/* [FIX] 2026-05-29 — KPI mini-stat values + labels lifted to 14px to
 * match Scripts Library unified font (虎哥反馈). NA placeholder stays
 * smaller (12px) so it visually de-emphasises empty backtest slots. */
.sc-mini-val {
    font-size: 14px;
    font-weight: 600;
    line-height: 1.2;
}
.sc-mini-val.is-up { color: var(--tt-up, #26a69a); }
.sc-mini-val.is-dn { color: var(--tt-down, #ef5350); }
/* [FIX] 2026-05-29 v20260529.15 — NA placeholder now matches the 14px
 * unified font (虎哥反馈: Strategy Report 列的值全部 14px). Was 12px to
 * de-emphasise, but the placement (centered alone in a 200px cell) is
 * already visually muted enough — the smaller font just looked sloppy
 * next to the 14px content of populated rows. */
.sc-mini-val.is-na {
    color: var(--text-secondary);
    font-weight: 400;
    font-size: 14px;
}
.sc-mini-lbl {
    font-size: 14px;
    color: var(--text-secondary);
    line-height: 1.2;
}
.sc-tr-empty {
    font-size: 14px;
    color: var(--text-secondary);
}

/* Tag cloud (My Scripts indicator/library middle column) */
.sc-tr-tags {
    display: flex;
    flex-wrap: nowrap;
    gap: 4px;
    overflow: hidden;
    align-items: center;
}
/* [FIX] 2026-05-29 — tags raised to 14px to match Scripts Library unified
 * font (虎哥反馈). Padding bumped slightly so the tags don't look cramped
 * at the bigger size. */
.sc-tr-tag {
    font-size: 14px;
    padding: 2px 8px;
    border-radius: 3px;
    background: var(--bg-input);
    color: var(--text-secondary);
    border: 1px solid var(--border-color);
    white-space: nowrap;
}

/* Actions cell */
.sc-tr-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    justify-content: flex-end;
    width: 100%;
}
/* [FIX] 2026-05-29 — row-action buttons raised to 14px font + 28px height
 * to match Scripts Library unified font (虎哥反馈). Icons grow slightly
 * with the new height. */
.sc-tr-act {
    height: 28px;
    padding: 0 10px;
    border-radius: 4px;
    border: 1px solid var(--border-color);
    background: transparent;
    color: var(--text-secondary);
    font-size: 14px;
    display: flex;
    align-items: center;
    gap: 4px;
    transition: all .12s;
    white-space: nowrap;
    cursor: pointer;
    font-family: inherit;
}
.sc-tr-act span { white-space: nowrap; }
.sc-tr-act svg { width: 12px; height: 12px; }
.sc-tr-act:hover {
    background: var(--bg-input);
    color: var(--text-primary);
}
.sc-tr-act.is-primary {
    background: var(--accent-color, #2962ff);
    border-color: var(--accent-color, #2962ff);
    color: #fff;
}
.sc-tr-act.is-primary:hover {
    background: #1e53e5;
}

/* [ADD] 2026-06-02 — virhu 2026-06-02 feedback batch 6: My Scripts
 *   table "Run family" (Run, Run on Server). Same theme-neutral
 *   ghost recipe as .sc-fav-btn--run for visual coherence across
 *   tabs. text-primary at rest so the verb reads clearly; hover
 *   bumps border-color to text-primary as a soft affordance hint
 *   without resorting to the brand accent. */
.sc-tr-act--run {
    color: var(--text-primary);
    font-weight: 500;
}
.sc-tr-act--run:hover {
    background: var(--bg-hover);
    border-color: var(--text-primary);
    color: var(--text-primary);
}
.sc-tr-act--run.is-disabled,
.sc-tr-act--run[disabled] {
    color: var(--text-secondary);
    border-color: var(--border-color);
    background: transparent;
    cursor: not-allowed;
    pointer-events: none;
    opacity: 0.7;
}
.sc-tr-act-more {
    width: 28px;
    height: 28px;
    border-radius: 4px;
    border: 1px solid var(--border-color);
    background: transparent;
    color: var(--text-secondary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    letter-spacing: 1px;
    line-height: 1;
    transition: all .12s;
    cursor: pointer;
    font-family: inherit;
}
.sc-tr-act-more:hover {
    background: var(--bg-input);
    color: var(--text-primary);
}

/* Per-row more dropdown menu */
.sc-more-wrap { position: relative; }
/* [FIX] 2026-05-29 v20260529.16 — menu width clamped to a tight band
 * (虎哥反馈 round 5: still too wide). min-width 180 + max-width 220
 * gives the popover a fixed-feeling footprint; items wider than that
 * truncate with ellipsis (white-space:nowrap on .sc-mi span) — same
 * approach as Gmail / Linear context menus when a label is unusually
 * long. Combined with the .sc-mi padding 10 / gap 6 / icon 12 trim
 * (above), the menu actually renders at ~198px for the worst-case
 * "Publish to community…" item. */
.sc-more-menu {
    position: absolute;
    right: 0;
    top: calc(100% + 4px);
    z-index: 600;
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    border-radius: 4px;
    box-shadow: 0 8px 40px rgba(0, 0, 0, 0.6);
    padding: 4px 0;
    min-width: 180px;
    max-width: 220px;
    display: none;
}
/* Items truncate gracefully if a translation is unexpectedly long
 * (e.g. Russian "Опубликовать в сообществе…" would otherwise burst
 * the 220 max-width). The wrapping <span data-i18n=...> is the
 * actual text node. */
.sc-mi > span,
.sc-mi span[data-i18n] {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1 1 auto;
    min-width: 0;
}
.sc-more-menu.is-open { display: block; }
/* [FIX] 2026-05-29 v20260529.16 — per-row "···" menu items tightened
 * (虎哥反馈 round 5: menu still too wide). 14px font kept (虎哥
 * directive), but row chrome is reduced:
 *   - padding 14 → 10 (saves 8px total)
 *   - gap     9  → 6  (saves 3px)
 *   - icon    14 → 12 (saves 2px)
 *   - row height 36 → 32 (also reduces vertical bulk so the menu
 *     reads as a context menu, not a settings panel)
 * Net width for longest item "Publish to community…" at 14px font:
 *   text ~160 + icon 12 + gap 6 + padding 20 = ~198px (was ~225).
 * Combined with the .sc-more-menu min-width 180 + max-width 220 clamp,
 * the menu now sits in a 180-220px band and never spills wider. */
.sc-mi {
    height: 32px;
    padding: 0 10px;
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 14px;
    color: var(--text-primary);
    transition: background .12s;
    cursor: pointer;
    border: none;
    background: transparent;
    width: 100%;
    text-align: left;
    font-family: inherit;
}
.sc-mi:hover {
    background: var(--bg-input);
    color: var(--text-primary);
}
.sc-mi.is-danger { color: var(--tt-down, #ef5350); }
.sc-mi.is-danger:hover { background: rgba(239, 83, 80, 0.08); }
.sc-mi svg {
    width: 12px;
    height: 12px;
    flex-shrink: 0;
    opacity: 0.75;
}
.sc-mi.is-danger svg { opacity: 1; }
.sc-mi-sep {
    height: 1px;
    background: var(--border-color);
    margin: 4px 0;
}

/* ── Community: auto-fill responsive grid ────────────────────────────────── */
.sc-grid-wrap {
    flex: 1;
    min-height: 0;
    min-width: 0;
    max-width: 100%;
    overflow-x: clip;
    overflow-y: auto;
    padding: 8px 10px;
}
.sc-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
    gap: 12px;
    align-content: start;
    min-width: 0;
    max-width: 100%;
}
.sc-grid .ys-script-card {
    min-height: 100%;
}
/* [FIX] 2026-07-20 — Community script cards keep visible parity hairlines in
 * both themes. The previous dark-only button override plus transparent card/
 * curve borders produced inconsistent rest-state lines between themes. */
body.light-theme .sc-grid,
body.theme--light .sc-grid,
.theme--light .sc-grid,
body:not(.dark-theme):not(.theme--dark) .sc-grid {
    --sc-community-line: rgba(0, 0, 0, 0.16);
}
body.dark-theme .sc-grid,
body.theme--dark .sc-grid,
.theme--dark .sc-grid {
    --sc-community-line: rgba(255, 255, 255, 0.16);
}
.sc-grid :where(.ys-script-card,
                .ys-script-card__curve,
                .ys-script-card__actions .sc-btn-run,
                .ys-script-card__actions .sc-btn-ros) {
    border-color: var(--sc-community-line, rgba(0, 0, 0, 0.16));
}
.sc-grid .ys-script-card__actions {
    grid-template-columns: auto 1fr auto;
    align-items: center;
}
.sc-grid .ys-script-card__actions .sc-btn-run {
    width: auto;
    min-width: 72px;
    justify-self: end;
}
.sc-grid .ys-script-card__actions .sc-btn-ros {
    width: auto;
    min-width: 0;
}
.sc-grid .ys-script-card__actions a.sc-btn-star {
    width: auto;
    min-width: 0;
    white-space: nowrap;
    justify-self: start;
}
.sc-grid .ys-script-card__actions button.sc-btn-star {
    width: 18px;
    min-width: 18px;
    padding: 0;
    justify-self: start;
}
.sc-grid .ys-script-card__meta-link {
    border: 0;
    background: transparent;
    color: var(--text-primary);
    padding: 0;
    text-decoration: underline;
    cursor: pointer;
}
.sc-grid .ys-script-card__meta-link:hover {
    color: var(--accent-color, #2962ff);
}

/* ── Favorites: hybrid row layout ────────────────────────────────────────── */
.sc-fav-wrap {
    flex: 1;
    min-height: 0;
    min-width: 0;
    max-width: 100%;
    overflow-x: clip;
    overflow-y: auto;
}
.sc-fav-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-bottom: 1px solid var(--border-color);
    cursor: pointer;
    transition: background .12s;
}
.sc-fav-row:hover { background: var(--bg-hover); }

.sc-fav-icon {
    width: 32px;
    height: 32px;
    border-radius: 5px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    color: var(--text-secondary);
}
.sc-fav-icon.is-strategy  { background: rgba(38, 166, 154, 0.15); color: #26a69a; }
.sc-fav-icon.is-indicator { background: rgba(41, 98, 255, 0.15); color: #6299ff; }
.sc-fav-icon.is-library   { background: rgba(171, 116, 248, 0.15); color: #ab74f8; }
.sc-fav-icon.is-invited  { background: rgba(41, 98, 255, 0.12); color: #6b8fff; font-size: 16px; line-height: 32px; text-align: center; }
.sc-fav-icon svg { width: 16px; height: 16px; }

.sc-invited-badge {
    font-size: 12px;
    font-weight: 700;
    padding: 1px 7px;
    border-radius: 10px;
    background: rgba(41, 98, 255, 0.15);
    color: #6b8fff;
    border: 1px solid rgba(41, 98, 255, 0.3);
    text-transform: uppercase;
    letter-spacing: 0.3px;
}
.sc-invited-range {
    font-size: 12px;
    color: var(--text-secondary, var(--tt-fg-2, #787b86));
}
.sc-invited-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin: 0 0 10px;
}
.sc-invited-toolbar__note {
    font-size: 12px;
    color: var(--text-secondary);
}
.bp-sub-tab.is-locked {
    opacity: .55;
    cursor: not-allowed;
}
.sc-fav-info {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
/* [FIX] 2026-05-29 — Favorites row content (name/meta/KPI/desc) unified
 * to 14px (虎哥反馈). KPI line-height bumped slightly so the stacked
 * value+label tile breathes at the new size. */
.sc-fav-name {
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-bottom: 2px;
}
/* [CHG] 2026-06-02 — virhu 2026-06-02 feedback batch 7 (re-tuned in
 *   batch 8 to "在现在的基础上增加一倍距离" — 4px was too tight after
 *   the previous "缩短三分之二" pass, so the gap is doubled to 8px).
 *   Layout itself stays as natural-width flex (no fixed-width grid
 *   columns) so each chip still hugs the previous chip's content
 *   edge; only the gap between chips changes. flex-wrap retained so
 *   very long author names wrap on narrow widths. */
/* [CHG] 2026-06-02 — virhu round 11: right-align the entire meta line
 *   so the secondary info group (type · visibility · ★ · vN · by …)
 *   hugs the row's right edge across My Scripts / Favorites / Invited
 *   / Community / Running. Author slot is no longer flex-grow:1 — it
 *   would otherwise push the leading chips back to the left and the
 *   row would never collapse against the right edge. */
.sc-fav-meta {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    font-size: 14px;
    color: var(--text-secondary);
    gap: 8px;
    flex-wrap: wrap;
    min-width: 0;
}
.sc-fav-meta.sc-meta-slots,
.sc-meta-slots--fav,
.sc-meta-slots--invited,
.sc-meta-slots--my {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}
.sc-meta-slot {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
}
.sc-meta-slot--author {
    flex: 0 0 auto;
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
}
.sc-fav-star-inline {
    color: #f9a825;
    font-variant-numeric: tabular-nums;
}
.sc-fav-kpis {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 6px;
    padding: 0;
    border: none;
}
.sc-fav-kpi {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    padding: 1px 6px;
    border-radius: 4px;
    background: var(--bg-tertiary, rgba(127, 127, 127, 0.10));
    font-variant-numeric: tabular-nums;
}
.sc-fav-kpi-val {
    font-size: 12px;
    font-weight: 600;
    line-height: 1.25;
}
.sc-fav-kpi-val.is-up { color: var(--tt-up, #26a69a); }
.sc-fav-kpi-val.is-dn { color: var(--tt-down, #ef5350); }
.sc-fav-kpi-lbl {
    font-size: 12px;
    color: var(--text-secondary);
    line-height: 1.25;
}
.sc-fav-desc {
    min-width: 0;
    padding: 0;
    border: none;
    font-size: 14px;
    color: var(--text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.sc-fav-acts {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    flex-wrap: nowrap;
}
/* [FIX] 2026-05-29 v20260529.15 — Favorites-row "remove favourite" toggle
 * now uses a filled gold star, no border. Was a #f9a825 orange-bordered
 * heart icon (虎哥反馈: remove the border, use star). Same visual
 * contract is shared across favorites/community cards for consistency. */
.sc-fav-btn-fav {
    width: 28px;
    height: 28px;
    border-radius: 4px;
    border: none;
    background: transparent;
    color: #f9a825;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    transition: background .12s;
    cursor: pointer;
    font-family: inherit;
}
.sc-fav-btn-fav svg { width: 16px; height: 16px; }
.sc-fav-btn-fav:hover { background: rgba(249, 168, 37, 0.08); }
.sc-fav-btn {
    height: 28px;
    padding: 0 12px;
    border-radius: 4px;
    border: 1px solid var(--border-color);
    background: var(--bg-input);
    color: var(--text-secondary);
    font-size: 14px;
    display: flex;
    align-items: center;
    gap: 4px;
    transition: all .12s;
    cursor: pointer;
    font-family: inherit;
    white-space: nowrap;
}
.sc-fav-btn span { white-space: nowrap; }
.sc-fav-btn:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
}

/* [ADD] 2026-06-02 — virhu 2026-06-02 feedback batch 6: the "Run
 *   family" of buttons (Run, Run on Server) on Favorites rows uses
 *   theme-primary text colour at rest so the verb is legible
 *   without falling back to the loud blue is-primary accent. Same
 *   bounding box as the base .sc-fav-btn (no size delta) so the
 *   row's actions column width is unchanged. */
.sc-fav-btn--run {
    color: var(--text-primary);
    font-weight: 500;
}
.sc-fav-btn--run:hover {
    background: var(--bg-hover);
    border-color: var(--text-primary);
    color: var(--text-primary);
}
.sc-fav-btn--run.is-disabled,
.sc-fav-btn--run[disabled] {
    color: var(--text-secondary);
    border-color: var(--border-color);
    background: var(--bg-input);
    cursor: not-allowed;
    pointer-events: none;
    opacity: 0.7;
}

.sc-fav-btn.is-primary {
    background: var(--accent-color, #2962ff);
    border-color: var(--accent-color, #2962ff);
    color: #fff;
}
.sc-fav-btn.is-primary:hover { background: #1e53e5; }

/* [ADD] 2026-05-31 — 虎哥 directive: Invited row "Run on Server" button
   shows a disabled grey "Running" state when the same script is already
   running on a worker. Mirrors the .sc-detail-action.is-primary.is-disabled
   visual contract (grey track, no hover, not-allowed cursor) so the same
   busy-state language is used across both the row button and the detail
   drawer button. Applies to BOTH .is-primary and the base .sc-fav-btn so
   any future "Run" variant inherits the same grey-out treatment. */
.sc-fav-btn.is-disabled,
.sc-fav-btn.is-primary.is-disabled,
.sc-fav-btn[disabled],
.sc-fav-btn.is-primary[disabled] {
    background: var(--bg-input);
    border-color: var(--border-color);
    color: var(--text-secondary);
    cursor: not-allowed;
    pointer-events: none;
    opacity: 0.7;
}
.sc-fav-btn.is-disabled:hover,
.sc-fav-btn.is-primary.is-disabled:hover,
.sc-fav-btn[disabled]:hover,
.sc-fav-btn.is-primary[disabled]:hover {
    background: var(--bg-input);
    color: var(--text-secondary);
}

/* [ADD] 2026-06-02 — virhu 2026-06-02 feedback batch 4: column
 *   alignment placeholder for Favorites rows. When a row's local Add-
 *   to-Chart button is suppressed (protected / invite_only visibility,
 *   F2 fix), we still emit an invisible <button> so the next button
 *   ("Run on Server") stays in the same column as adjacent rows.
 *   visibility:hidden preserves the bounding box but skips paint and
 *   pointer events; pointer-events:none doubles up so the disabled
 *   button never accidentally captures mouse events. */
.sc-fav-btn--placeholder {
    visibility: hidden;
    pointer-events: none;
}

/* [DEL] 2026-05-29 — YeScript UI Redesign §6.1 — drawer is NON-MODAL.
   Previous .sc-drawer-overlay backdrop removed; spec §6.1 forbids any
   overlay/backdrop. Detail drawer relies on a left-edge box-shadow for
   visual layering and leaves the chart/bottom panel/topbar interactive. */

/* ── #bpYesSubTabs: always visible + disabled state ──────────────────────── */
/* [ADD] 2026-05-29 — Strategy Tester sub-tabs are now always visible.
   Pre-run state: Backtest / Trades / Properties show as disabled (opacity .4,
   pointer-events:none). Post-run: tt.yescript.strategy-tester.js removes
   the hidden attr on these buttons and unlocks them. Editor tab is always
   active by default; the others become clickable once a strategy script
   has produced a fresh backtest result in window.YeScriptDebug.lastResult. */
#bpYesSubTabs[hidden] { display: flex !important; }
#bpYesSubTabs .bp-sub-tab[data-yes-sub="backtest"][hidden],
#bpYesSubTabs .bp-sub-tab[data-yes-sub="trades"][hidden],
#bpYesSubTabs .bp-sub-tab[data-yes-sub="properties"][hidden] {
    display: inline-flex !important;
    opacity: 0.4;
    pointer-events: none;
    cursor: not-allowed;
}
/* [ADD] 2026-05-29 — YeScript UI Redesign (实施规范 §3.4 + §3.2) — mobile
   horizontal scroll for sub-tab strips. Two cases:
   1. #bpYesSubTabs (Strategy Tester, 4 pills, "List of Trades" worst case)
   2. #bpScSubTabs  (Scripts Library, 3 nav tabs + spacer + 1 of 3 actions
                     toolbars including New/Import/Refresh/Search which
                     would otherwise wrap or get truncated on phones)
   Horizontal scroll keeps every pill / button fully readable; this is the
   pattern Binance / Bybit / OKX use for their mobile strategy panels.
   -webkit-overflow-scrolling:touch gives iOS momentum scroll, scrollbar
   visually hidden (gesture is enough; matches the rest of the chart UI). */
@media (max-width: 768px) {
    #bpYesSubTabs,
    #bpScSubTabs {
        overflow-x: auto;
        overflow-y: hidden;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
    }
    #bpYesSubTabs::-webkit-scrollbar,
    #bpScSubTabs::-webkit-scrollbar { display: none; }
    /* Spacer must not flex-grow in scroll mode — let actions sit naturally
       next to the nav tabs so the whole strip can scroll as one unit. */
    #bpScSubTabs .sc-toolbar-spacer {
        flex: 0 0 12px;
    }
    /* Search input must not collapse to 0 width in flex scroll context. */
    #bpScSubTabs .sc-search-wrap { flex-shrink: 0; min-width: 140px; }
}

/* ── Mobile responsive (≤768px) ──────────────────────────────────────────── */
/* [CHG] 2026-05-29 — YeScript UI Redesign (实施规范 §3.2.2) — mobile layout
   for the new Scripts Library trio. Mobile is constrained to ~340-414px
   width (iPhone SE → iPhone 14 Pro Max), so the 6-col desktop grid for
   My Scripts must reflow.
   Strategy: keep the most decision-critical data on screen — for Strategy
   scripts that's the Net P / Win / MaxDD KPI strip (without these the
   user has no way to compare scripts at-a-glance and must drill into each
   one). For Indicator / Library scripts the strip is replaced by the
   tags / context line. Visibility chip is suppressed on mobile because
   it's secondary metadata; the title font weight + the published-row
   "↗" glyph already signal pub vs priv status. The "···" more-menu
   exposes all hidden actions (publish/manage/visibility-change) so no
   functionality is lost.
   Touch target: every interactive element ≥ 32px tall per WCAG / Apple
   Human Interface Guidelines. */
/* [REWRITE] 2026-05-31 r2 — Mobile (≤768px) Scripts Library card layout (虎哥 review).
 * Symptoms before this iteration: even after the v1 card-stack rewrite the
 *   rows still consumed 5-6 visual lines on a 360-400 px viewport (icon row
 *   → name → status → report → tags → actions), which felt cluttered and
 *   wasteful — every script was ~140 px tall before content padding, so a
 *   user could only see ~3 scripts above the fold. 虎哥 directive: keep
 *   each card to **2-3 lines** maximum and feel compact, professional.
 * Strategy: collapse the row into a Twitter / Linear / Apple App Store
 *   compact-list pattern:
 *     Row 1: [icon][name + ↗ pub indicator][···]                  ← title
 *     Row 2: [type-chip] · v3.2 · 2h ago · [WR 65%] · [PF 1.8]    ← meta
 *     Row 3: [secondary actions] [Add ▸]                          ← CTAs
 *   Strategy KPI tiles + tags are merged into the meta strip as inline
 *   chips (the old dedicated rows are hidden). The "···" menu still hosts
 *   the secondary actions, so functionality is unchanged.
 *   Touch target: every interactive control stays ≥ 36 px tall (WCAG /
 *   Apple HIG / Material 3). */
@media (max-width: 768px) {
    /* Hide desktop column header — mobile is card-based. */
    .sc-th { display: none; }

    /* Tighter list container so cards have edge-to-edge padding. */
    .sc-table-wrap,
    .sc-fav-wrap { padding: 6px 8px; }

    /* My Scripts row → 2-line compact card.
     *   Row 1: [icon][name][···]
     *   Row 2: inline meta chips (type · version · time · visibility · stats · tags) + actions (right-aligned)
     * Multiple .sc-tr-cell containers (3..6) all collapse into row 2 of a
     * 2-column grid (col 1 = icon, col 2 = content) so the card never
     * exceeds ~80 px in height. Each cell uses inline-flex-wrap so its
     * children render as inline chips next to each other. */
    .sc-tr {
        display: grid;
        grid-template-columns: 28px minmax(0, 1fr);
        column-gap: 8px;
        row-gap: 4px;
        padding: 10px 12px;
        margin-bottom: 6px;
        border-radius: 8px;
        border: 1px solid var(--border-color);
        background: var(--bg-secondary, var(--bg-input));
        align-items: start;
    }
    /* Row 1: icon (top-left). */
    .sc-tr-cell:nth-child(1) {
        grid-column: 1;
        grid-row: 1;
        align-self: center;
    }
    /* Row 1: title cell (name + meta strip stacked).
     *   The cell child .sc-tr-main is a column flex so name + meta wrap
     *   nicely (name on top, meta inline beneath). */
    .sc-tr-cell:nth-child(2) {
        grid-column: 2;
        grid-row: 1;
        min-width: 0;
        display: block;
    }
    .sc-tr-cell:nth-child(2) .sc-tr-main {
        display: flex;
        flex-direction: column;
        gap: 2px;
        min-width: 0;
    }
    /* Row 2: secondary chips (visibility / stats / tags) — share grid row 2
     *   of column 2 by stacking on top of each other; flex-wrap keeps the
     *   inner chips inline. We let auto-flow place them in row 2, row 3,
     *   row 4 — any cell that has an .sc-tr-empty child is hidden below
     *   so empty rows never appear. */
    .sc-tr-cell:nth-child(3),
    .sc-tr-cell:nth-child(4),
    .sc-tr-cell:nth-child(5) {
        grid-column: 2;
        min-width: 0;
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 4px 8px;
    }
    /* Action row at the bottom, full-width, right-aligned. */
    .sc-tr-cell:nth-child(6) {
        grid-column: 1 / -1;
        justify-self: end;
    }

    /* Title row — name takes the full main column, truncates with ellipsis
     *   on long titles. */
    .sc-tr-name {
        font-size: 14px;
        line-height: 1.35;
        font-weight: 500;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        min-width: 0;
    }

    /* Meta strip — type chip · version · time · KPI chips · tags all flow
     *   inline with consistent 12px font + 6px gap, wrapping to a 2nd line
     *   only on extreme overflow. Right-aligned per virhu round 11 so the
     *   meta group hugs the row's right edge on mobile too. */
    .sc-tr-meta {
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-end;
        gap: 4px 8px;
        font-size: 12px;
        line-height: 1.4;
        color: var(--text-secondary);
        align-items: center;
    }
    .sc-tr-meta.sc-meta-slots {
        display: flex;
        justify-content: flex-end;
        grid-template-columns: none;
        column-gap: 0;
    }

    /* Empty placeholders ("—") and dedicated stat/tag rows are merged into
     *   the meta strip on mobile — hide their wrapper containers. */
    .sc-tr-empty,
    .sc-tr-cell:has(> .sc-tr-empty) { display: none; }
    /* Strategy KPI strip — render as inline pill-chips (Net P · Win · MaxDD).
     *   Class names match tt.scripts.js: .sc-mini-stat > .sc-mini-val + .sc-mini-lbl. */
    .sc-tr-stats {
        display: inline-flex;
        flex-wrap: wrap;
        gap: 4px 6px;
        font-size: 12px;
    }
    .sc-mini-stat {
        display: inline-flex;
        align-items: baseline;
        gap: 4px;
        padding: 2px 6px;
        border-radius: 4px;
        background: var(--bg-tertiary, rgba(127, 127, 127, 0.10));
        font-variant-numeric: tabular-nums;
    }
    .sc-mini-val {
        font-size: 12px;
        font-weight: 500;
    }
    .sc-mini-lbl {
        color: var(--text-secondary);
        font-size: 12px;
    }
    .sc-tr-tags {
        display: inline-flex;
        flex-wrap: wrap;
        gap: 4px;
        font-size: 12px;
    }
    .sc-tr-tag {
        padding: 1px 6px;
        border-radius: 3px;
        background: var(--bg-tertiary, rgba(127, 127, 127, 0.10));
        color: var(--text-secondary);
    }

    /* Action row pinned to the right; primary CTA wins visual weight. */
    .sc-tr-actions {
        display: flex;
        flex-wrap: nowrap;
        justify-content: flex-end;
        gap: 6px;
    }
    .sc-tr-act {
        height: auto;
        min-height: 36px;
        padding: 0 10px;
        font-size: 13px;
    }
    .sc-tr-act svg { width: 14px; height: 14px; }
    .sc-tr-act-more {
        width: 32px;
        height: 32px;
        line-height: 1;
        padding: 0;
    }

    /* Community grid → single column cards. */
    .sc-grid {
        grid-template-columns: 1fr;
        padding: 6px 8px;
        gap: 8px;
    }
    /* ── Favorites / Invited / Running / Community card ────────────────────
     * Class names from tt.scripts.js: .sc-fav-row containing
     *   .sc-fav-icon + .sc-fav-info(.sc-fav-name + .sc-fav-meta) +
     *   <middle div with .sc-fav-kpis | .sc-fav-desc | .sc-invited-* | …> +
     *   .sc-fav-acts (action buttons).
     * Mobile layout: 2-line compact card.
     *   Row 1 (col 1 = icon, col 2 = info)
     *   Row 2 (col 2 = middle: kpis / desc / invited-meta lines)
     *   Row 3 (col 1/-1 = acts, right-aligned)
     * The middle section is HTML-variable so we use grid auto-flow. Each
     * sibling under .sc-fav-row that is neither .sc-fav-icon nor
     * .sc-fav-info nor .sc-fav-acts ends up in column 2 of subsequent rows
     * via the explicit `grid-column` rules below. */
    .sc-fav-row {
        display: grid;
        grid-template-columns: 28px minmax(0, 1fr);
        column-gap: 8px;
        row-gap: 4px;
        align-items: start;
        padding: 10px 12px;
        margin-bottom: 6px;
        border-radius: 8px;
        border: 1px solid var(--border-color);
        background: var(--bg-secondary, var(--bg-input));
    }
    .sc-fav-icon {
        grid-column: 1;
        grid-row: 1;
        align-self: center;
    }
    .sc-fav-info {
        grid-column: 2;
        grid-row: 1;
        min-width: 0;
        display: flex;
        flex-direction: column;
        gap: 2px;
    }
    /* All other middle siblings (.sc-fav-kpis, .sc-fav-desc,
     *   .sc-invited-granted, .sc-invited-expires) flow into column 2 of
     *   subsequent rows automatically. */
    .sc-fav-row > .sc-fav-kpis,
    .sc-fav-row > .sc-fav-desc {
        grid-column: 2;
        min-width: 0;
    }
    .sc-fav-acts {
        grid-column: 1 / -1;
        justify-self: end;
        display: flex;
        flex-wrap: nowrap;
        gap: 6px;
    }

    /* Title + meta (inside .sc-fav-info). */
    .sc-fav-name {
        font-size: 14px;
        line-height: 1.35;
        font-weight: 500;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        margin: 0;
    }
    .sc-fav-meta {
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-end;
        gap: 4px 8px;
        font-size: 12px;
        line-height: 1.4;
        color: var(--text-secondary);
        align-items: center;
    }
    .sc-fav-meta.sc-meta-slots {
        display: flex;
        justify-content: flex-end;
        grid-template-columns: none;
        column-gap: 0;
    }

    /* Strategy KPI chips inside .sc-fav-kpis. The legacy row may render
     *   .sc-fav-kpi children (rare). Both are normalized to inline chips. */
    .sc-fav-kpis {
        display: flex;
        flex-wrap: wrap;
        gap: 4px 6px;
        padding: 0;
        border: none;
        background: transparent;
        margin: 0;
    }
    .sc-fav-kpis > * {
        display: inline-flex;
        align-items: baseline;
        gap: 4px;
        padding: 2px 6px;
        border-radius: 4px;
        background: var(--bg-tertiary, rgba(127, 127, 127, 0.10));
        font-size: 12px;
        font-variant-numeric: tabular-nums;
    }
    .sc-fav-desc {
        font-size: 12px;
        line-height: 1.4;
        color: var(--text-secondary);
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
        margin: 0;
        padding: 0;
        border: none;
    }

    .sc-fav-btn,
    .sc-fav-btn-fav {
        height: auto;
        min-height: 36px;
        min-width: 36px;
        font-size: 13px;
        padding: 0 10px;
    }
    /* Toolbar (search + sort + type filters) — keep search full-width and
     *   let filter chips scroll horizontally. */
    .sc-toolbar { padding: 6px 8px; gap: 6px; }
}


/* ============================================================================
 * [ADD] 2026-05-29 v2 — YeScript UI Redesign §5.1/§5.2 sc-nav-btn + sc-toolbar
 * ============================================================================ */

/* §5.1 — Right-aligned action buttons inside .sc-nav (Refresh / Import / New)
 * [FIX] 2026-05-29 — 11→14px font + 24→28px height for Scripts Library
 * unified text size (虎哥反馈). Icons grow to 12px to keep balance. */
.sc-nav-btn {
    height: 28px;
    padding: 0 11px;
    border-radius: 4px;
    border: 1px solid var(--border-color);
    background: transparent;
    color: var(--text-secondary);
    font-size: 14px;
    display: flex;
    align-items: center;
    gap: 5px;
    transition: all .12s;
    white-space: nowrap;
    cursor: pointer;
    font-family: inherit;
}
.sc-nav-btn svg { width: 12px; height: 12px; flex-shrink: 0; }
.sc-nav-btn:hover { background: var(--bg-input); color: var(--text-primary); }
.sc-nav-btn.primary {
    background: var(--accent-color, #2962ff);
    border-color: var(--accent-color, #2962ff);
    color: #fff;
}
.sc-nav-btn.primary:hover { background: #1e53e5; }

/* §5.2 — Independent toolbar row (search + type filter pills + sort) */
.sc-toolbar {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 7px 10px;
    border-bottom: 1px solid var(--border-color-subtle, rgba(255,255,255,0.04));
    flex-shrink: 0;
}
/* search input wrap inside toolbar: flex:1 so it grows. Old .sc-search-wrap
   rule earlier in this file (line ~3105) lives in a different context
   (per-tab quick-actions row) and paints its own background — we must
   explicitly null its background / border / padding here so the toolbar
   input reads as transparent (虎哥反馈 round 3: search must blend into
   the page like GitHub / Linear / Notion search boxes). */
.sc-toolbar .sc-search-wrap {
    position: relative;
    flex: 1;
    min-width: 0;
    max-width: none;
    background: transparent;
    border: none;
    padding: 0;
}
.sc-toolbar .sc-search-ico {
    position: absolute;
    left: 8px;
    top: 50%;
    transform: translateY(-50%);
    width: 13px;
    height: 13px;
    color: var(--text-secondary);
    pointer-events: none;
    opacity: 0.7;
}
/* [FIX] 2026-05-29 v20260529.14 — search input background transparent
 * (虎哥反馈 round 3). Previously the input painted its own --bg-input /
 * --bg-secondary fill which made the search box visually "float" on
 * the page; per design intent the search box should read as embedded
 * INTO the page (same convention as GitHub / Linear / Notion search
 * inputs: transparent ground, border + focus ring carry the affordance
 * of "this is editable"). Hover gives a faint tint so the box still
 * visibly responds; focus uses the accent border. */
.sc-toolbar .sc-search {
    width: 100%;
    height: 32px;
    background: transparent;
    border: 1px solid var(--border-color);
    border-radius: 4px;
    padding: 0 8px 0 28px;
    color: var(--text-primary);
    font-size: 14px;
    outline: none;
    font-family: inherit;
    transition: border-color .12s, background-color .12s;
}
.sc-toolbar .sc-search:hover { background: rgba(127, 127, 127, 0.06); }
.sc-toolbar .sc-search:focus {
    border-color: var(--accent-color, #2962ff);
    background: transparent;
}
.sc-toolbar .sc-search::placeholder { color: var(--text-secondary); }

/* §5.2 — Type filter pills (inline, only shown on Community via data-sc-only) */
.sc-type-filters {
    display: flex;
    align-items: center;
    gap: 3px;
    flex-shrink: 0;
}
.sc-type-btn {
    /* [FIX] 2026-05-29 — type filter pills bumped to 28px / 14px to align
     * with Scripts Library unified text size (虎哥反馈). */
    height: 28px;
    padding: 0 11px;
    border-radius: 4px;
    border: 1px solid var(--border-color);
    background: transparent;
    color: var(--text-secondary);
    font-size: 14px;
    transition: all .12s;
    cursor: pointer;
    font-family: inherit;
}
.sc-type-btn:hover { color: var(--text-primary); }
.sc-type-btn.is-active {
    border-color: var(--accent-color, #2962ff);
    color: var(--accent-color, #2962ff);
    background: rgba(41, 98, 255, 0.1);
}

/* §5.2 — Sort button (replaces the old `.sc-filter-trigger` look for sort)
 * [FIX] 2026-05-29 — 26→28px / 11→14px to match Scripts Library unified
 * sizing (虎哥反馈). */
.sc-toolbar .sc-sort-btn {
    height: 28px;
    padding: 0 11px;
    border-radius: 4px;
    border: 1px solid var(--border-color);
    background: var(--bg-input, var(--bg-secondary));
    color: var(--text-secondary);
    font-size: 14px;
    display: flex;
    align-items: center;
    gap: 5px;
    transition: all .12s;
    flex-shrink: 0;
    cursor: pointer;
    font-family: inherit;
}
.sc-toolbar .sc-sort-btn:hover { color: var(--text-primary); }
.sc-toolbar .sc-sort-btn svg { width: 12px; height: 12px; flex-shrink: 0; }

/* Mobile: sc-nav scrollable; sc-toolbar wrap if too narrow */
@media (max-width: 768px),
       (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    .sc-nav {
        overflow-x: auto;
        overflow-y: hidden;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        /* [FIX] 2026-05-31 r8 — force single-row horizontal strip on mobile.
         * .sc-nav also carries `.bp-sub-tabs`; an older mobile rule for
         * `.bp-sub-tabs` sets `flex-wrap: wrap` (for the legacy scripts
         * toolbar split layout). Without overriding it here, narrow widths
         * wrap trailing tabs (Running / Purchased / Community / Store) to
         * line 2, then `.sc-nav` clips them because the row itself has a
         * fixed 36px height + overflow-y:hidden.
         * Result: tabs "disappear" on iOS/DevTools and never become
         * horizontally scrollable.
         * Force nowrap so overflow is horizontal (the intended UX). */
        flex-wrap: nowrap;
        /* [ADD] 2026-05-31 r3 — left/right padding leaves room for the
         *   TTScrollAffordance chevrons (22 px chip + 4 px breathing).
         *   Without this padding the first tab ("My Scripts") sits flush
         *   under the left chevron and the last tab ("Community") sits
         *   under the right chevron, hurting tappability on 360 px
         *   viewports. The chevrons are position:fixed so they don't
         *   themselves consume layout — we just reserve visual gutters. */
        padding-left: 28px;
        padding-right: 28px;
    }
    .sc-nav::-webkit-scrollbar { display: none; }
    /* [CHG] 2026-05-31 r7 — Mobile sc-nav: ALL flex children must keep
     *   their natural width so the row can overflow past viewport and
     *   become horizontally scrollable. Previously only `.sc-nav-tab`
     *   had `flex-shrink: 0`; the labels (PERSONAL/PUBLIC), the 1 px
     *   `.sc-nav-div`, and `.sc-nav-right` (Refresh button cluster)
     *   inherited the default `flex-shrink: 1` and got squeezed,
     *   causing the row to look like it "fits" while later tabs
     *   (Running / Purchased / Community / Store) silently disappear
     *   off-screen with no scroll affordance because the container
     *   never overflowed.
     *
     *   Equally critical: `.sc-nav-right` carries `margin-left: auto`
     *   on desktop to right-align the Refresh button. In a horizontally
     *   scrollable flexbox that auto margin EATS all remaining space,
     *   forcing previous siblings into clientWidth and CLIPPING the
     *   tabs after the auto-margin item — exactly what 虎哥 reported
     *   on iOS ("Running / Purchased / Community / Store hidden").
     *   On mobile we cancel the auto margin so .sc-nav-right behaves
     *   as a normal trailing flex child (Refresh just follows the
     *   last tab). The mobile chevrons + horizontal scroll handle
     *   discoverability instead of the desktop right-aligned action
     *   cluster.
     */
    .sc-nav > * { flex: 0 0 auto; }
    .sc-nav-right {
        margin-left: 0;
        /* [FIX] 2026-05-31 r9 — keep trailing "Refresh list" label baseline
         * aligned with adjacent tab labels on mobile.
         * After r7/r8 the action cluster moved into the same horizontal strip
         * as tabs (`margin-left: 0`) and `padding-bottom` was flattened to 0.
         * On narrow viewports this made the button text sit ~2px lower than
         * `.sc-nav-tab` text (visible as "重新整理" not on the same horizontal
         * line as "社区/商店"). 2px bottom padding restores optical baseline
         * parity without changing interaction size or scroll behaviour. */
        padding-bottom: 2px;
        padding-right: 4px;
    }
    .sc-nav-btn {
        height: auto;
        min-height: 32px;
        padding: 4px 9px;
    }
    /* [CHG] 2026-05-31 r6 — Mobile sc-toolbar layout: keep search + sort
     *   on the SAME row (desktop parity), with type-filters wrapping to
     *   its own scrollable second row only when visible (community tab).
     *
     *   Old behaviour:
     *     .sc-search-wrap { flex: 1 1 100% }  → forced search to its
     *                                            own line on mobile.
     *     .sc-type-filters { width: 100% }    → second line.
     *     .sc-sort-btn { margin-left: auto }  → third line.
     *   Result: 3 stacked rows, taking 96+ px of vertical space —
     *   虎哥 reported "搜索和下拉框换行了，要和桌面一样在一行".
     *
     *   New behaviour (matches desktop layout):
     *     row 1 = [search-wrap (flex:1)] [sort-btn (auto)]
     *     row 2 = [type-filters] (community-only, h-scroll if too wide)
     *
     *   `flex-wrap: wrap` retained because type-filters needs to break
     *   to a new row; we control the break-point with `flex-basis:100%`
     *   on type-filters rather than 100% on search.
     */
    .sc-toolbar { flex-wrap: wrap; row-gap: 6px; }
    .sc-toolbar .sc-search-wrap {
        flex: 1 1 0;
        min-width: 0;
        order: 1;
    }
    .sc-toolbar .sc-sort-btn {
        height: auto;
        min-height: 32px;
        padding: 4px 9px;
        order: 2;
        flex: 0 0 auto;
    }
    /* The Sort dropdown wrapper itself sits next to search on row 1.
     * (`.sc-filter-dropdown` is the wrap; the inner `.sc-sort-btn` was
     * what carried the old margin-left:auto. We now anchor at the
     * dropdown wrap level too so the menu stays right-aligned to the
     * trigger.) */
    .sc-toolbar > .sc-filter-dropdown {
        order: 2;
        flex: 0 0 auto;
    }
    /* type-filters owns row 2; flex-basis:100% forces the wrap break.
     * order:3 keeps it AFTER search+sort no matter what visual order
     * the DOM uses. */
    .sc-type-filters {
        order: 3;
        flex: 1 1 100%;
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        width: 100%;
        min-width: 0;
    }
    .sc-type-filters::-webkit-scrollbar { display: none; }
    .sc-type-btn {
        height: auto;
        min-height: 32px;
        padding: 4px 12px;
        flex-shrink: 0;
    }
}

/* ============================================================
   [ADD] 2026-05-29 — Running tab + Run-detail drawer.
   Reuses the .sc-fav-row layout so the row sizing/hover/focus
   states automatically match Invited and Favorites. Only the
   colours and the status-dot/pill visuals are new.
   ============================================================ */

/* Nav pulse dot on the "Running" tab — lights up green for ~1 s
   whenever a new signal arrives, even if the user isn't on the
   Running pane. */
.sc-nav-tab .sc-nav-pulse {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: transparent;
    margin-left: 2px;
    transition: background 200ms ease, box-shadow 200ms ease;
    vertical-align: middle;
}
.sc-nav-tab .sc-nav-pulse.is-on {
    background: #26a69a;
    box-shadow: 0 0 0 2px rgba(38, 166, 154, 0.25);
}

/* Running-row status dot inside the .sc-fav-icon slot. */
.sc-fav-icon.is-running {
    background: rgba(38, 166, 154, 0.12);
    color: #26a69a;
    position: relative;
}
.sc-run-status-dot {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: #787b86;
    box-shadow: 0 0 0 2px rgba(120, 123, 134, 0.18);
}
.sc-run-status-dot.sc-run-status--running {
    background: #26a69a;
    box-shadow: 0 0 0 2px rgba(38, 166, 154, 0.25);
    animation: tt-run-pulse 2s ease-in-out infinite;
}
.sc-run-status-dot.sc-run-status--idle {
    background: #f0b90b;
    box-shadow: 0 0 0 2px rgba(240, 185, 11, 0.20);
}
.sc-run-status-dot.sc-run-status--starting {
    background: #2962ff;
    box-shadow: 0 0 0 2px rgba(41, 98, 255, 0.22);
    animation: tt-run-pulse 1.4s ease-in-out infinite;
}
.sc-run-status-dot.sc-run-status--failed {
    background: #ef5350;
    box-shadow: 0 0 0 2px rgba(239, 83, 80, 0.22);
}
@keyframes tt-run-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.45; }
}

/* Status pill inside row meta + inside drawer overview. */
.sc-run-status-pill {
    display: inline-block;
    font-size: 12px;
    font-weight: 600;
    padding: 1px 7px;
    border-radius: 10px;
    text-transform: uppercase;
    letter-spacing: 0.3px;
    background: rgba(120, 123, 134, 0.15);
    color: var(--text-secondary, var(--tt-fg-2, #787b86));
    border: 1px solid rgba(120, 123, 134, 0.28);
}
.sc-run-status-pill.sc-run-status--running {
    background: rgba(38, 166, 154, 0.15);
    color: #26a69a;
    border-color: rgba(38, 166, 154, 0.35);
}
.sc-run-status-pill.sc-run-status--idle {
    background: rgba(240, 185, 11, 0.13);
    color: #f0b90b;
    border-color: rgba(240, 185, 11, 0.30);
}
.sc-run-status-pill.sc-run-status--starting {
    background: rgba(41, 98, 255, 0.13);
    color: #6299ff;
    border-color: rgba(41, 98, 255, 0.30);
}
.sc-run-status-pill.sc-run-status--failed {
    background: rgba(239, 83, 80, 0.13);
    color: #ef5350;
    border-color: rgba(239, 83, 80, 0.32);
}

/* [EXCEPTION] Green background distinguishes explicit simulated-order consent,
   a financial-account authorization state, from ordinary run metadata. */
.sc-run-paper-trading {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    min-height: 18px;
    padding: 1px 7px;
    border: 1px solid rgba(38, 166, 154, 0.45);
    border-radius: 999px;
    background: rgba(38, 166, 154, 0.16);
    color: var(--tt-fg-0, var(--text-primary));
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
}
.sc-run-paper-trading svg {
    width: 12px;
    height: 12px;
    color: var(--tt-up);
    flex: 0 0 auto;
}

.sc-run-context {
    font-size: 12px;
    color: var(--text-secondary, var(--tt-fg-2, #787b86));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 280px;
}

/* [ADD] 2026-06-02 — CHART-RUN-ON-SERVER-FAV-GRANT-2026-06-02 §9.4.
   Reaped reason badge — only renders when run.status === 'reaped' AND
   run.last_error is one of (user_unfav | unpublish | author_kick).
   Visual: same pill silhouette as .sc-run-status-pill but in the
   neutral-warning palette (amber on dark / brown on light) so it
   reads as "informational, not critical". The status pill itself
   already conveys 'reaped' via colour; this badge explains *why*. */
.sc-run-reaped-reason {
    display: inline-block;
    font-size: 12px;
    font-weight: 500;
    padding: 1px 7px;
    border-radius: 10px;
    background: rgba(240, 185, 11, 0.13);
    color: #f0b90b;
    border: 1px solid rgba(240, 185, 11, 0.30);
    max-width: 220px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* [CHG] 2026-06-02 — virhu 2026-06-02 feedback batch 5: same as
 *   .sc-invited-granted — Running-row variants of .sc-fav-desc pin
 *   grow:0 + max-width:none so the Favorites flex:1 base does not
 *   change Running-row column widths. */
.sc-run-last-signal {
    flex: 0 0 auto;
    width: auto;
    min-width: 110px;
    max-width: none;
    font-size: 12px;
    color: var(--text-secondary, var(--tt-fg-2, #787b86));
}
.sc-run-counters {
    flex: 0 0 auto;
    width: auto;
    min-width: 80px;
    max-width: none;
    font-size: 12px;
    color: var(--text-secondary, var(--tt-fg-2, #787b86));
    text-align: right;
}
.sc-run-counters .sc-run-counter-num {
    color: var(--tt-fg-0, #ffffff);
    font-weight: 600;
}

/* [ADD] 2026-06-01 — Server-run TTL badge inside the row meta strip.
   Five colour variants: ok (≥14d, muted) / warn (≤14d, amber) /
   soon (≤7d, red) / expired (≤0d, red+strikethrough) / open (∞, blue).
   Visual weight matches .sc-run-status-pill so they sit side-by-side
   without one dominating. Title attribute on the span surfaces the raw
   expires_at ISO timestamp on hover for power users. */
.sc-run-ttl {
    display: inline-block;
    font-size: 11px;
    font-weight: 600;
    padding: 1px 6px;
    border-radius: 3px;
    border: 1px solid transparent;
    line-height: 16px;
    white-space: nowrap;
    cursor: help;
}
.sc-run-ttl.sc-run-ttl--ok {
    background: rgba(120, 123, 134, 0.10);
    color: var(--text-secondary, var(--tt-fg-2, #787b86));
    border-color: rgba(120, 123, 134, 0.22);
}
.sc-run-ttl.sc-run-ttl--warn {
    background: rgba(240, 185, 11, 0.13);
    color: #f0b90b;
    border-color: rgba(240, 185, 11, 0.30);
}
.sc-run-ttl.sc-run-ttl--soon {
    background: rgba(239, 83, 80, 0.13);
    color: #ef5350;
    border-color: rgba(239, 83, 80, 0.32);
}
.sc-run-ttl.sc-run-ttl--expired {
    background: rgba(239, 83, 80, 0.22);
    color: #ef5350;
    border-color: rgba(239, 83, 80, 0.45);
    text-decoration: line-through;
}
.sc-run-ttl.sc-run-ttl--open {
    background: rgba(41, 98, 255, 0.13);
    color: #6299ff;
    border-color: rgba(41, 98, 255, 0.30);
}

/* Running empty state — two-line variant of the standard .sc-empty. */
.sc-empty .sc-running-empty-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--tt-fg-0, #ffffff);
    margin-bottom: 6px;
}
.sc-empty .sc-running-empty-hint {
    font-size: 14px;
    color: var(--text-secondary, var(--tt-fg-2, #787b86));
}

/* [REWRITE] 2026-05-31 — Run-detail drawer body — 虎哥 directive.
   Six logical blocks rendered in this order by tt.run-drawer.js
   renderBody (see header comment there):
     (a) .sc-run-actionbar    — Stop + Notification channels.
                                Sits at the top with its own divider so
                                actions are never confused with read-only
                                data rows.
     (b) .sc-run-info         — status pill + Symbol · TF · Venue · Author
                                + started-at as a single context line.
     (c) .sc-run-kpi          — 3 equal-width tiles (Uptime / Signals /
                                Last signal), label-on-top
                                value-below — the financial-industry KPI
                                tile pattern (TradingView strategy tester,
                                Binance trade history).
     (d) .sc-run-meta         — <details> collapsed by default with
                                Run ID + Input hash inside.
     (e) .sc-run-section      — live signal feed (h4 + ul).
     (f) .sc-run-section      — Recent (last 7 days, lazy-loaded).
   No more `<dl><dt><dd>` definition list — that was the "AI dump" look. */
/* [CHG] 2026-05-31 — flex column body. The drawer body itself stops
   scrolling (`overflow: hidden`); only the merged signals list
   (the .sc-run-feed inside .sc-run-section--signals) scrolls
   internally. This way the action bar / info strip / KPI grid /
   parameters stay pinned at the top of the body while the signals
   list grows downward to fill all remaining vertical space, so the
   user always sees the most recent signals without having to scroll
   past static metadata. */
#scRunDrawer .sc-drawer__body {
    padding: 14px 16px 16px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* [REWRITE] 2026-05-31 r14 — Run-detail drawer visual language.
 * 虎哥 directive: drop framed cards, mirror "我的策略设置" (.sc-detail-action)
 * button colours and TV Strategy Tester layout (label-on-top KPIs,
 * no tile borders, hairline section dividers only). Dark + light
 * themes share the same selectors; only the soft tokens invert.
 *
 * Local tokens scoped to #scRunDrawer:
 *   --sc-run-divider     — single hairline used for ALL section
 *                          separators (action bar bottom, KPI bottom,
 *                          run-params bottom, signals top).
 *   --sc-run-soft-border — only for the merged signal feed list —
 *                          keeps a faint frame so the table reads as
 *                          a TV Strategy Tester-style data grid.
 */
#scRunDrawer {
    --sc-run-divider: rgba(255, 255, 255, 0.08);
    --sc-run-soft-border: rgba(255, 255, 255, 0.12);
}
body:not(.dark-theme) #scRunDrawer {
    --sc-run-divider: rgba(0, 0, 0, 0.10);
    --sc-run-soft-border: rgba(0, 0, 0, 0.14);
}

/* (a) Action bar — Stop + Notification channels.
 * Visual language matches `.sc-detail-action` from the strategy
 * detail drawer: transparent fill, 1px semi-opaque border, primary
 * danger gets the TV-style coloured stroke. Bottom hairline divider
 * separates actions from data without painting a card. */
.sc-run-actionbar {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding-bottom: 12px;
    margin-bottom: 14px;
    border-bottom: 1px solid var(--sc-run-divider);
}
.sc-run-actionbar__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 32px;
    padding: 0 12px;
    border-radius: 5px;
    border: 1px solid rgba(255, 255, 255, 0.16);
    background: transparent;
    color: var(--text-primary);
    font-size: 12px;
    font-weight: 500;
    line-height: 1;
    font-family: inherit;
    text-decoration: none;
    cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
body:not(.dark-theme) .sc-run-actionbar__btn {
    border-color: rgba(0, 0, 0, 0.16);
    color: #1f2329;
}
.sc-run-actionbar__btn svg {
    flex: 0 0 14px;
    width: 14px;
    height: 14px;
}
.sc-run-actionbar__btn:hover {
    background: rgba(255, 255, 255, 0.06);
    border-color: rgba(255, 255, 255, 0.30);
}
body:not(.dark-theme) .sc-run-actionbar__btn:hover {
    background: rgba(0, 0, 0, 0.05);
    border-color: rgba(0, 0, 0, 0.32);
}
.sc-run-actionbar__btn:focus-visible {
    outline: 2px solid #2962ff;
    outline-offset: 1px;
}
.sc-run-actionbar__btn[disabled] {
    cursor: not-allowed;
    opacity: 0.55;
    pointer-events: none;
}
/* Stop = destructive — keep red stroke + hover tint, no filled red. */
.sc-run-actionbar__btn--stop {
    color: #ef5350;
    border-color: rgba(239, 83, 80, 0.35);
}
.sc-run-actionbar__btn--stop:hover {
    background: rgba(239, 83, 80, 0.10);
    border-color: rgba(239, 83, 80, 0.55);
    color: #ef5350;
}
body:not(.dark-theme) .sc-run-actionbar__btn--stop {
    color: #d4373a;
    border-color: rgba(212, 55, 58, 0.40);
}
body:not(.dark-theme) .sc-run-actionbar__btn--stop:hover {
    background: rgba(212, 55, 58, 0.08);
    border-color: rgba(212, 55, 58, 0.60);
    color: #d4373a;
}
.sc-run-actionbar__btn--ghost {
    color: var(--text-secondary, #b1b5c2);
}
.sc-run-actionbar__btn--ghost:hover {
    color: var(--text-primary);
}

/* (b) Info strip — status pill + context line in one row. */
.sc-run-info {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 14px;
    font-size: 14px;
    line-height: 1.5;
    color: var(--text-primary);
}
.sc-run-info__ctx {
    color: var(--text-primary);
    font-weight: 500;
}
.sc-run-info__sep {
    color: var(--text-secondary);
}
.sc-run-info__author {
    color: var(--text-secondary);
}
.sc-run-info__started {
    margin-left: auto;
    color: var(--text-secondary);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

/* (c) KPI block — TV Strategy Tester style: borderless cells in a
 * 3-column grid, separated only by faint vertical dividers and a
 * single bottom hairline that ends the block. Label sits on top in
 * --text-secondary, value sits below in --text-primary at 18px so
 * KPIs read like TV's "Net P&L | Max DD | Win rate | Trades" strip. */
.sc-run-kpi {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0;
    margin-bottom: 14px;
    padding-bottom: 14px;
    border-bottom: 1px solid var(--sc-run-divider);
}
.sc-run-kpi__tile {
    padding: 4px 14px;
    border: 0;
    background: transparent;
    border-left: 1px solid var(--sc-run-divider);
    min-width: 0;
}
.sc-run-kpi__tile:first-child {
    padding-left: 0;
    border-left: 0;
}
.sc-run-kpi__label {
    font-size: 12px;
    line-height: 1.3;
    color: var(--text-secondary);
    margin-bottom: 4px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.sc-run-kpi__value {
    font-size: 18px;
    line-height: 1.25;
    color: var(--text-primary);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    word-break: break-word;
}

/* (d) Run parameters — flat collapsible group, no card.
 * Just a summary row that toggles, with content underneath, divided
 * from the signals section by a hairline. No surrounding border. */
.sc-run-meta {
    margin-bottom: 14px;
    padding-bottom: 14px;
    border: 0;
    border-bottom: 1px solid var(--sc-run-divider);
    background: transparent;
    border-radius: 0;
}
.sc-run-meta__summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 0 4px 0;
    font-size: 12px;
    font-weight: 500;
    color: var(--text-secondary);
    cursor: pointer;
    list-style: none;
    user-select: none;
}
.sc-run-meta__summary::-webkit-details-marker { display: none; }
.sc-run-meta__summary:hover { color: var(--text-primary); }
.sc-run-meta__chev {
    transition: transform 0.15s ease;
    color: inherit;
}
.sc-run-meta[open] .sc-run-meta__chev { transform: rotate(180deg); }
.sc-run-meta__body {
    padding: 6px 0 0 0;
    display: grid;
    gap: 6px;
}
.sc-run-meta__row {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 12px;
}
.sc-run-meta__label {
    color: var(--text-secondary);
    flex: 0 0 auto;
    min-width: 80px;
}
/* Mono code chip — borderless, faint surface tint so the value is
 * visually grouped without a hard outline. Uses the same low-alpha
 * lift as TV's read-only field hints. */
.sc-run-mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
    background: rgba(255, 255, 255, 0.05);
    border: 0;
    padding: 2px 8px;
    border-radius: 3px;
    color: var(--text-primary);
    word-break: break-all;
}
body:not(.dark-theme) .sc-run-mono {
    background: rgba(0, 0, 0, 0.05);
}

/* (e) Signals section — the only section that fills remaining height.
   Action bar / info strip / KPI grid / parameters stay natural-sized
   at the top of the body; this section flex-grows to occupy the rest
   of the drawer all the way down to the bottom of the modal. */
.sc-run-section {
    margin-bottom: 18px;
}
.sc-run-section--signals {
    margin-bottom: 0;
    flex: 1 1 auto;
    min-height: 0;        /* allow inner ul to scroll */
    display: flex;
    flex-direction: column;
}
.sc-run-section:last-child {
    margin-bottom: 0;
}
.sc-run-section__title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0 0 8px 0;
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
}
.sc-run-section__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 6px;
    border-radius: 9px;
    background: rgba(120, 123, 134, 0.18);
    color: var(--text-secondary);
    font-size: 12px;
    font-weight: 500;
    line-height: 1;
}
.sc-run-signals-host {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* (e) Signals list — fills the host, scrolls internally on overflow.
   Border follows the run-drawer-local --sc-run-soft-border token so
   the merged signal table reads as a TV Strategy Tester-style data
   grid in both themes. */
.sc-run-feed {
    list-style: none;
    margin: 0;
    padding: 0;
    border: 1px solid var(--sc-run-soft-border);
    border-radius: 4px;
    overflow-y: auto;
    flex: 1 1 auto;
    min-height: 0;
    /* Scroll chaining guard — when the user scrolls to top/bottom of
       the feed, browsers (esp. Chrome on touchpads + iOS Safari) will
       happily bubble the wheel/touch up to the drawer body or the
       window. The drawer body is overflow:hidden now so it can't
       scroll, but the wheel can still reach the chart canvas behind
       the drawer and zoom it. `contain` stops both behaviours. */
    overscroll-behavior: contain;
}
/* Live/history rows are visually identical — owner directive: "merge
   history + live as one, signals region extends to the bottom of the
   modal". A subtle left-rail accent on history-sourced rows is
   intentionally avoided so the user perceives them as one continuous
   feed, not as two interleaved sources. The `data-src` attribute is
   kept in DOM (for future debugging / A/B) but unstyled. */
.sc-run-feed__row {
    display: grid;
    grid-template-columns: 88px 96px 1fr;
    gap: 10px;
    align-items: center;
    padding: 8px 12px;
    font-size: 14px;
    /* [CHG] 2026-05-31 r17 — 虎哥 directive: keep the outer feed
     * border (so the table reads as one container) but remove the
     * per-row divider so individual signals don't look fenced. */
    border-bottom: 0;
}
.sc-run-feed__row:last-child {
    border-bottom: 0;
}
.sc-run-feed__row--rejected {
    border-left: 2px solid var(--tt-down);
}
.sc-run-feed__time {
    color: var(--text-secondary);
    font-size: 12px;
}
.sc-run-feed__event {
    font-weight: 600;
    text-transform: uppercase;
    font-size: 12px;
    letter-spacing: 0.4px;
}
.sc-run-evt-entry_long  { color: #26a69a; }
.sc-run-evt-entry_short { color: #ef5350; }
.sc-run-evt-exit        { color: #f0b90b; }
.sc-run-evt-alert       { color: #6299ff; }
.sc-run-evt-signal      { color: var(--text-secondary); }
.sc-run-feed__body {
    color: var(--text-primary);
    word-break: break-word;
}
.sc-run-feed__execution {
    display: inline-block;
    font-size: 12px;
    font-weight: 600;
}
.sc-run-feed__execution--executed {
    /* [EXCEPTION] filled execution is a financial trade outcome. */
    color: var(--tt-up);
}
.sc-run-feed__execution--rejected {
    /* [EXCEPTION] rejected execution is a financial order-risk outcome. */
    color: var(--tt-down);
}
.sc-run-empty {
    padding: 12px;
    border: 1px dashed var(--sc-run-soft-border);
    border-radius: 4px;
    color: var(--text-secondary);
    font-size: 14px;
    text-align: center;
    flex: 0 0 auto;
}
/* When Signals host is in loading / empty / error state, its only child
   is .sc-drawer__loading or .sc-run-empty. Centering inside the flex-
   column host so the placeholder doesn't collapse to the top edge of
   a tall drawer. */
.sc-run-signals-host > .sc-drawer__loading,
.sc-run-signals-host > .sc-run-empty {
    align-self: stretch;
    margin: auto 0;
}

/* Mobile: collapse the .sc-run-feed__row time column to spare width;
   stack KPI grid 2×2 instead of 4×1 (4 tiles on a narrow drawer would
   crush each tile to ~70px — value text wraps and looks broken).
   Because KPI tiles use border-left as the column divider, we must
   reset border-left on every row's first child when the grid wraps
   to 2 columns. */
@media (max-width: 768px), (max-width: 1366px) and (hover: none) and (pointer: coarse) {
    .sc-run-feed__row {
        grid-template-columns: 64px 84px 1fr;
        gap: 8px;
        padding: 6px 10px;
    }
    .sc-run-kpi {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        row-gap: 10px;
    }
    .sc-run-kpi__tile:nth-child(2n + 1) {
        padding-left: 0;
        border-left: 0;
    }
    .sc-run-info {
        font-size: 12px;
    }
    .sc-run-info__started {
        margin-left: 0;
        flex-basis: 100%;
    }
    .sc-run-context {
        max-width: 180px;
    }
}

/* ============================================================================
   ==== [REWRITE] 2026-05-30 — DM CHAT REDESIGN v2 (mockup-aligned)
   ====   Spec :  docs/CHART-DM-聊天界面重设计-2026-05-30.md
   ====   Source: docs/mockup-dm-redesign-v1.html (v1.3 — code-aligned)
   ====   Scope : all rules below are scoped INSIDE #dmDrawer; nothing
   ====           escapes the drawer per the single-drawer rule (§0).
   ====   This block REPLACES the previous DM CSS (was ~1320 lines across
   ====   "2026-05-29 — DM drawer styles" + "Phase 2 receipts" + first
   ====   "DM REDESIGN" attempts). New block strictly mirrors the mockup
   ====   classes: .dm-head, .dm-search-bar, .dm-more-menu, .dm-inbox-search,
   ====   .dm-conv (__r1/__r2), .dm-thread, .dm-messages, .dm-msg-group,
   ====   .dm-msg + .dm-msg__bubble + .dm-msg__meta + .dm-msg__acts,
   ====   .dm-composer, .dm-emoji-picker, .dm-action-sheet, .dm-inner-panel,
   ====   .dm-empty, .dm-typing, .dm-tick, .dm-scroll-btn, .dm-online-dot,
   ====   .dm-avatar (incl. 5 g-* gradients), .dm-date-divider,
   ====   .dm-unread-divider, .dm-reactions / .dm-reaction.
   ====   Existing functionality preserved: edit-inline, delete, clear,
   ====   delete-conv, ctx-bar, search, more menu, scroll-btn FAB.
   ============================================================================ */

/* Toolbar bell badge — used by #dmToolbarBadge. Kept from previous block:
   accent blue (not red) per finance UX convention. */
.toolbar-btn { position: relative; }
.toolbar-btn__badge {
    position: absolute;
    top: 2px; right: 2px;
    min-width: 18px; height: 18px;
    padding: 0 6px;
    border-radius: 9px;
    background: var(--accent-color, #2962ff);
    color: #ffffff;
    font-size: 12px;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
    pointer-events: none;
    box-shadow: 0 0 0 2px var(--bg-secondary, #1e222d);
}

/* ──────────────────────────────────────────────────────────────────────────
   DRAWER CONTAINER & TOKENS
   ────────────────────────────────────────────────────────────────────────── */

#dmDrawer.dm-drawer,
.dm-profile-host {
    /* Mockup-defined tokens. Re-defined locally so the drawer is
       theme-aware regardless of global var availability. */
    --dm-bg-primary:    var(--bg-secondary, #131722);
    --dm-bg-secondary:  var(--bg-tertiary, #1e222d);
    --dm-bg-tertiary:   rgba(120, 123, 134, 0.13);
    --dm-bg-hover:      rgba(120, 123, 134, 0.10);
    --dm-border:        rgba(120, 123, 134, 0.18);
    --dm-border-strong: rgba(120, 123, 134, 0.30);
    --dm-text:          var(--tt-fg-0, #ffffff);
    --dm-text-2:        var(--text-secondary, var(--tt-fg-2, #787b86));
    --dm-accent:        var(--accent-color, #2962ff);
    --dm-accent-hover:  #1e53e5;
    --dm-accent-dim:    rgba(41, 98, 255, 0.13);
    --dm-online:        #26a69a;
    --dm-away:          #ffb74d;
    --dm-danger:        var(--tt-down, #ef5350);
    --dm-danger-dim:    rgba(239, 83, 80, 0.13);
    --dm-popover-bg:    var(--bg-tertiary, #1e222d);
    --dm-popover-border:rgba(120, 123, 134, 0.28);
    --dm-popover-shadow:0 6px 20px rgba(0, 0, 0, 0.45);
    --dm-r-sm: 4px;
    --dm-r-md: 8px;
    --dm-r-lg: 12px;
    --dm-r-full: 999px;
    --dm-tr: 0.15s ease;
    --dm-head-h: 52px;
    /* JS tracks composer height into this for scroll-bottom FAB + emoji picker. */
    --composer-h: 74px;

    /* [FIX] 2026-05-30 — do NOT add position/display/flex here. The base
       .sc-drawer rule is `position: fixed; top: 44px; right: 46px;
       transform: translateX(...)` and we MUST inherit all of it
       (slide-in animation, mobile bottom-sheet override, mutex with
       other drawers) — overriding position to `relative` collapsed the
       drawer into the page flow at viewport (0, 0) which made the
       mockup-aligned widgets render at the wrong place.
       .sc-drawer already provides: position fixed + display flex +
       flex-direction column + overflow hidden, all of which are also
       what mockup wants. We are a child class — just add tokens. */
}

/* Light theme overrides for DM-scope tokens. */
body.theme--light #dmDrawer.dm-drawer,
body.light #dmDrawer.dm-drawer,
body.theme--light .dm-profile-host,
body.light .dm-profile-host {
    --dm-bg-primary:    var(--bg-secondary, #ffffff);
    --dm-bg-secondary:  var(--bg-tertiary, #f6f8fb);
    --dm-bg-tertiary:   rgba(0, 0, 0, 0.06);
    --dm-bg-hover:      rgba(0, 0, 0, 0.05);
    --dm-border:        rgba(0, 0, 0, 0.08);
    --dm-border-strong: rgba(0, 0, 0, 0.15);
    --dm-popover-bg:    #ffffff;
    --dm-popover-border:rgba(0, 0, 0, 0.12);
    --dm-popover-shadow:0 6px 20px rgba(0, 0, 0, 0.15);
}

/* ──────────────────────────────────────────────────────────────────────────
   ICON BUTTONS (shared) — head/composer/conv/msg/search-nav
   Visual 30px desktop, expand to 44px on touch via ::after.
   ────────────────────────────────────────────────────────────────────────── */
#dmDrawer .dm-icon-btn,
#dmDrawer .dm-head__back {
    width: 30px; height: 30px;
    border: 0; background: transparent;
    color: var(--dm-text-2);
    border-radius: var(--dm-r-sm);
    display: inline-flex; align-items: center; justify-content: center;
    flex-shrink: 0;
    transition: background var(--dm-tr), color var(--dm-tr);
    cursor: pointer;
    position: relative;
    padding: 0;
}
#dmDrawer .dm-icon-btn:hover,
#dmDrawer .dm-head__back:hover {
    background: var(--dm-bg-hover);
    color: var(--dm-text);
}
#dmDrawer .dm-icon-btn:focus-visible,
#dmDrawer .dm-head__back:focus-visible {
    outline: 2px solid var(--dm-accent);
    outline-offset: -2px;
}
#dmDrawer .dm-icon-btn.is-active { color: var(--dm-accent); background: var(--dm-accent-dim); }

/* ──────────────────────────────────────────────────────────────────────────
   HEAD — dual mode (inbox / thread)
   ────────────────────────────────────────────────────────────────────────── */
#dmDrawer .dm-head {
    height: var(--dm-head-h);
    min-height: var(--dm-head-h);
    background: var(--drawer-head-bg, var(--chart-popup-bg));
    border-bottom: 1px solid var(--chart-popup-border);
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 0 10px;
    flex-shrink: 0;
    z-index: 10;
    position: relative;
}
body:not(.dark-theme) #dmDrawer .dm-head {
    background: var(--dm-bg-secondary);
    border-bottom: 1px solid var(--dm-border);
}
#dmDrawer .dm-head__inbox,
#dmDrawer .dm-head__thread {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
    min-width: 0;
}
#dmDrawer.is-inbox-mode  .dm-head__thread { display: none; }
#dmDrawer.is-thread-mode .dm-head__inbox  { display: none; }
#dmDrawer.is-thread-mode .dm-head__back   { display: inline-flex; }
/* [FIX] 2026-08-10 — Match drawer-level actions to the active header mode.
   Reason: thread mode already has its own conversation actions.
   Change: hide inbox-only controls through the existing root mode class.
   Warning: do not replace this with JavaScript inline styles. */
#dmDrawer.is-thread-mode .dm-head__inbox-action { display: none; }

#dmDrawer .dm-head__brand {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}
#dmDrawer .dm-head__title {
    font-size: 14px;
    font-weight: 700;
    color: var(--dm-text);
    letter-spacing: -0.01em;
}
#dmDrawer .dm-head__unread {
    min-width: 18px; height: 18px;
    padding: 0 5px;
    border-radius: 9px;
    background: var(--dm-accent);
    color: #ffffff;
    font-size: 12px;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
}
#dmDrawer .dm-head__unread[hidden] { display: none; }

#dmDrawer .dm-head__peer {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    cursor: pointer;
    background: transparent;
    border: 0;
    padding: 0;
}
#dmDrawer .dm-head__peer:focus-visible { outline: 2px solid var(--dm-accent); outline-offset: 2px; border-radius: var(--dm-r-sm); }
#dmDrawer .dm-head__peer-avatar-wrap { position: relative; flex-shrink: 0; }
#dmDrawer .dm-head__peer-avatar {
    width: 30px; height: 30px;
}
#dmDrawer .dm-head__peer-info {
    min-width: 0;
    flex: 1;
    text-align: left;
}
#dmDrawer .dm-head__peer-name {
    font-size: 14px;
    font-weight: 600;
    color: var(--dm-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
#dmDrawer .dm-head__peer-status {
    font-size: 12px;
    color: var(--dm-online);
    display: flex;
    align-items: center;
    gap: 4px;
}
#dmDrawer .dm-head__peer-status.is-offline { color: var(--dm-text-2); }
#dmDrawer .dm-head__peer-status.is-away    { color: var(--dm-away); }

#dmDrawer .dm-avatar {
    width: 40px; height: 40px;
    border-radius: 50%;
    overflow: hidden;
    display: inline-block;
    flex-shrink: 0;
}
#dmDrawer .dm-avatar img { width: 100%; height: 100%; object-fit: cover; }

#dmDrawer .dm-online-dot {
    position: absolute;
    bottom: 1px; right: 1px;
    width: 10px; height: 10px;
    border-radius: 50%;
    background: var(--dm-online);
    border: 2px solid var(--dm-bg-primary);
}
#dmDrawer .dm-online-dot--offline { background: var(--dm-text-2); }
#dmDrawer .dm-online-dot--away    { background: var(--dm-away); }
#dmDrawer .dm-online-dot[hidden]  { display: none; }

/* ──────────────────────────────────────────────────────────────────────────
   THREAD-MODE SEARCH BAR (inline expand below head)
   ────────────────────────────────────────────────────────────────────────── */
#dmDrawer .dm-search-bar {
    height: 42px;
    background: var(--dm-bg-secondary);
    border-bottom: 1px solid var(--dm-border);
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 0 12px;
    flex-shrink: 0;
    z-index: 9;
}
#dmDrawer .dm-search-bar[hidden] { display: none; }
#dmDrawer .dm-search-bar > svg { color: var(--dm-text-2); flex-shrink: 0; }
#dmDrawer .dm-search-bar input {
    flex: 1;
    border: 0;
    background: transparent;
    color: var(--dm-text);
    font-size: 14px;
    outline: none;
    min-width: 0;
    font-family: inherit;
}
#dmDrawer .dm-search-bar input::placeholder { color: var(--dm-text-2); }
#dmDrawer .dm-search-bar__close { width: 24px; height: 24px; }

/* ──────────────────────────────────────────────────────────────────────────
   MORE MENU — absolute INSIDE drawer
   ────────────────────────────────────────────────────────────────────────── */
#dmDrawer .dm-more-menu {
    position: absolute;
    top: calc(var(--dm-head-h) + 2px);
    right: 8px;
    min-width: 210px;
    background: var(--dm-popover-bg);
    border: 1px solid var(--dm-popover-border);
    border-radius: var(--dm-r-md);
    box-shadow: var(--dm-popover-shadow);
    padding: 6px 0;
    z-index: 50;
}
#dmDrawer .dm-more-menu[hidden] { display: none; }
/* [FIX] 2026-08-10 — Author display overrides UA [hidden] on menu items.
   Reason: #dmDrawer .dm-more-menu__item { display: flex } beats the
   [hidden] attribute's display:none (UA specificity tie; class wins by
   source order), so remove-friend showed for non-friend threads.
   Change: companion [hidden] rule for .dm-more-menu__item.
   Warning: any new DM element toggled via [hidden] that also sets display
   in author CSS MUST get a matching [hidden] rule. */
#dmDrawer .dm-more-menu__item[hidden] { display: none; }
#dmDrawer .dm-more-menu__item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    background: transparent;
    border: 0;
    width: 100%;
    text-align: left;
    font-size: 14px;
    color: var(--dm-text);
    cursor: pointer;
    transition: background var(--dm-tr);
    font-family: inherit;
}
#dmDrawer .dm-more-menu__item > svg {
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    color: var(--dm-text-2);
}
#dmDrawer .dm-more-menu__item:hover { background: var(--dm-bg-hover); }
#dmDrawer .dm-more-menu__item.is-danger { color: var(--dm-danger); }
#dmDrawer .dm-more-menu__item.is-danger > svg { color: var(--dm-danger); }
#dmDrawer .dm-more-menu__item.is-danger:hover { background: var(--dm-danger-dim); }
#dmDrawer .dm-more-menu__sep {
    height: 1px;
    background: var(--dm-border);
    margin: 4px 0;
}

/* ──────────────────────────────────────────────────────────────────────────
   CTX BAR — sticky context strip with × dismiss (FIX G)
   ────────────────────────────────────────────────────────────────────────── */
#dmDrawer .dm-ctx-bar {
    padding: 6px 10px 6px 14px;
    background: rgba(41, 98, 255, 0.08);
    border-bottom: 1px solid var(--dm-border);
    font-size: 12px;
    color: var(--dm-text-2);
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}
#dmDrawer .dm-ctx-bar[hidden] { display: none; }
#dmDrawer .dm-ctx-bar > svg { color: var(--dm-text-2); flex-shrink: 0; }
#dmDrawer .dm-ctx-bar__re { color: var(--dm-text-2); flex-shrink: 0; }
#dmDrawer .dm-ctx-tag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 1px 7px;
    border-radius: var(--dm-r-full);
    background: rgba(41, 98, 255, 0.14);
    color: var(--dm-accent);
    font-size: 12px;
    font-weight: 600;
    flex: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
#dmDrawer .dm-ctx-dismiss {
    width: 20px; height: 20px;
    border: 0;
    background: transparent;
    color: var(--dm-text-2);
    border-radius: var(--dm-r-sm);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    cursor: pointer;
    transition: color var(--dm-tr);
    position: relative;
    padding: 0;
}
#dmDrawer .dm-ctx-dismiss:hover { color: var(--dm-text); }

/* ──────────────────────────────────────────────────────────────────────────
   BODY + INBOX
   ────────────────────────────────────────────────────────────────────────── */
#dmDrawer .dm-body { flex: 1; display: flex; flex-direction: column; min-height: 0; position: relative; }

#dmDrawer .dm-drawer__inbox {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
}
#dmDrawer.is-thread-mode .dm-drawer__inbox { display: none; }
#dmDrawer.is-inbox-mode  .dm-drawer__thread { display: none; }

#dmDrawer .dm-inbox-search {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 12px;
    border-bottom: 1px solid var(--dm-border);
    flex-shrink: 0;
}
#dmDrawer .dm-inbox-search__wrap {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    background: var(--dm-bg-tertiary);
    border: 1px solid var(--dm-border);
    border-radius: var(--dm-r-full);
    padding: 6px 12px;
    transition: border-color var(--dm-tr);
}
/* [CHG] 2026-05-30 — user wants NO accent border on focus for any DM input
   (inbox search / composer / edit input / new-conv search / emoji search).
   The static .dm-border keeps the field visible without the loud blue. */
#dmDrawer .dm-inbox-search__wrap > svg { color: var(--dm-text-2); flex-shrink: 0; }
#dmDrawer .dm-inbox-search__wrap input {
    flex: 1;
    border: 0;
    background: transparent;
    color: var(--dm-text);
    font-size: 14px;
    outline: none;
    min-width: 0;
    font-family: inherit;
}
#dmDrawer .dm-inbox-search__wrap input::placeholder { color: var(--dm-text-2); }
#dmDrawer .dm-head__inbox-action { position: relative; }
#dmDrawer .dm-add-friend__badge {
    position: absolute;
    top: 1px;
    right: 1px;
    min-width: 8px;
    width: 8px;
    height: 8px;
    padding: 0;
    border: 1px solid var(--dm-bg-secondary);
    font-size: 0;
    line-height: 0;
}

#dmDrawer .dm-inbox {
    flex: 1;
    overflow-y: auto;
    list-style: none;
    margin: 0;
    padding: 0;
}
#dmDrawer .dm-inbox-tabs {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 8px 12px 0;
    border-bottom: 1px solid var(--dm-border);
    flex-shrink: 0;
}
#dmDrawer .dm-inbox-tabs__tab {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px 10px 8px;
    border: 0;
    border-bottom: 2px solid transparent;
    background: transparent;
    color: var(--dm-text-2);
    font: inherit;
    font-size: 13px;
    cursor: pointer;
}
#dmDrawer .dm-inbox-tabs__tab:hover { color: var(--dm-text); }
#dmDrawer .dm-inbox-tabs__tab.is-selected {
    color: var(--dm-text);
    border-bottom-color: var(--dm-accent);
}
#dmDrawer .dm-inbox-tabs__tab:focus-visible {
    outline: 2px solid var(--dm-accent);
    outline-offset: -2px;
}
#dmDrawer .dm-inbox-tabs__count {
    min-width: 18px;
    padding: 1px 5px;
    border-radius: 9px;
    background: var(--dm-bg-tertiary);
    color: inherit;
    font-size: 11px;
    line-height: 16px;
    text-align: center;
}

#dmDrawer .dm-conv {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    cursor: pointer;
    border-bottom: 1px solid var(--dm-border);
    transition: background var(--dm-tr);
    position: relative;
    min-height: 62px;
}
#dmDrawer .dm-conv:hover { background: var(--dm-bg-hover); }
#dmDrawer .dm-conv.is-active {
    background: var(--dm-accent-dim);
    border-left: 3px solid var(--dm-accent);
    padding-left: 11px;
}
#dmDrawer .dm-conv.is-active .dm-conv__name { color: var(--dm-accent); }

#dmDrawer .dm-conv__av-wrap { position: relative; flex-shrink: 0; }
#dmDrawer .dm-conv__main { flex: 1; min-width: 0; }
#dmDrawer .dm-conv__r1, #dmDrawer .dm-conv__r2 {
    display: flex; align-items: center; gap: 6px;
}
#dmDrawer .dm-conv__r2 { margin-top: 2px; }
#dmDrawer .dm-conv__name {
    flex: 1;
    font-size: 14px;
    font-weight: 600;
    color: var(--dm-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
#dmDrawer .dm-conv__time {
    font-size: 12px;
    color: var(--dm-text-2);
    flex-shrink: 0;
}
#dmDrawer .dm-conv__preview {
    flex: 1;
    font-size: 12px;
    color: var(--dm-text-2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
#dmDrawer .dm-conv__unread {
    flex-shrink: 0;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 9px;
    background: var(--dm-accent);
    color: #ffffff;
    font-size: 12px;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
}

/* [FIX] 2026-05-31 — hover action strip alignment v2
   Reason: previous v1 used top:50% + translateY(-50%) which placed the
   30x30 icons on the mathematical centre BETWEEN r1 (time) and r2
   (unread). Because neither time nor unread sits on that mid-line the
   icons read as "floating between two rows" — the mis-alignment the
   user reported on 2026-05-31.
   Change: anchor the strip to the full right-edge column (top:0 bottom:0)
   and let flex align-items:center handle vertical centring. This puts
   the icon on the same baseline as the avatar mid-line (the natural
   visual centre of the row) regardless of whether the row has unread
   badge or not. Strip background is transparent so it inherits the
   row's own :hover background (no double-fill, no edge).
   Warning: do not revert to top:50% + translateY(-50%); that re-creates
   the v1 row-mid-line offset bug. */
#dmDrawer .dm-conv__acts {
    display: none;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
    position: absolute;
    right: 10px;
    top: 0;
    bottom: 0;
    padding: 0 4px;
    background: transparent;
    pointer-events: auto;
}
#dmDrawer .dm-conv:hover .dm-conv__acts,
#dmDrawer .dm-conv.is-swiped .dm-conv__acts,
#dmDrawer .dm-conv:focus-within .dm-conv__acts { display: flex; }
/* When the row is hovered/swiped/focused, hide the right-side meta so
   the floating action strip lands on the same horizontal slot. */
#dmDrawer .dm-conv:hover .dm-conv__time,
#dmDrawer .dm-conv:hover .dm-conv__unread,
#dmDrawer .dm-conv.is-swiped .dm-conv__time,
#dmDrawer .dm-conv.is-swiped .dm-conv__unread,
#dmDrawer .dm-conv:focus-within .dm-conv__time,
#dmDrawer .dm-conv:focus-within .dm-conv__unread { visibility: hidden; }
/* The acts strip is transparent and overlays the row's own hover
   background (--dm-bg-hover from .dm-conv:hover) or accent-dim
   (.dm-conv.is-active) — no separate fill needed on the strip. */

#dmDrawer .dm-conv__act {
    width: 30px; height: 30px;
    border: 0;
    background: transparent;
    color: var(--dm-text-2);
    border-radius: var(--dm-r-sm);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background var(--dm-tr), color var(--dm-tr);
    position: relative;
    cursor: pointer;
    padding: 0;
}
#dmDrawer .dm-conv__act:hover { background: var(--dm-bg-tertiary); color: var(--dm-text); }
#dmDrawer .dm-conv__act.is-danger:hover { background: var(--dm-danger-dim); color: var(--dm-danger); }

#dmDrawer .dm-conv__swipe-hint {
    display: none;
    position: absolute;
    right: 0; top: 0; bottom: 0;
    background: var(--dm-danger);
    color: #ffffff;
    align-items: center;
    padding: 0 18px;
    gap: 6px;
    font-size: 12px;
    font-weight: 700;
    border-radius: 0 var(--dm-r-sm) var(--dm-r-sm) 0;
}
@media (hover: none) and (pointer: coarse) {
    #dmDrawer .dm-conv__swipe-hint {
        display: flex;
        transform: translateX(100%);
        transition: transform 0.2s ease;
    }
    #dmDrawer .dm-conv.is-swiped .dm-conv__swipe-hint { transform: translateX(0); }
    /* On touch devices the swipe-hint takes over the right edge entirely.
       Suppress the hover-acts strip so the two don't visually overlap
       when .is-swiped is active. */
    #dmDrawer .dm-conv.is-swiped .dm-conv__acts { display: none; }
}

/* ──────────────────────────────────────────────────────────────────────────
   THREAD — date/unread dividers, message groups, bubbles
   ────────────────────────────────────────────────────────────────────────── */
#dmDrawer .dm-thread {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
    position: relative;
}

#dmDrawer .dm-messages {
    flex: 1;
    overflow-y: auto;
    /* [FIX] 2026-05-31 — explicit overflow-x:hidden so any single message's
       intrinsic min-width (long URL, narrow drawer, edit textarea, OSS
       attachment grid, etc.) cannot ever produce a horizontal scrollbar
       on the thread.  Chat messages don't have a sensible "scroll right"
       gesture so we'd rather clip than scroll. */
    overflow-x: hidden;
    padding: 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    list-style: none;
    margin: 0;
}

#dmDrawer .dm-date-divider {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 0 6px;
}
#dmDrawer .dm-date-divider::before,
#dmDrawer .dm-date-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--dm-border);
}
#dmDrawer .dm-date-divider > span {
    font-size: 12px;
    color: var(--dm-text-2);
    padding: 2px 10px;
    border-radius: var(--dm-r-full);
    background: var(--dm-bg-tertiary);
    white-space: nowrap;
}

#dmDrawer .dm-unread-divider {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 0;
}
#dmDrawer .dm-unread-divider::before,
#dmDrawer .dm-unread-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: rgba(41, 98, 255, 0.40);
}
#dmDrawer .dm-unread-divider > span {
    font-size: 12px;
    color: var(--dm-accent);
    font-weight: 600;
    padding: 2px 10px;
    border-radius: var(--dm-r-full);
    background: var(--dm-accent-dim);
    white-space: nowrap;
}

#dmDrawer .dm-msg-group {
    display: flex;
    flex-direction: column;
    margin-bottom: 8px;
}
#dmDrawer .dm-msg-group.is-mine { align-items: flex-end; }
#dmDrawer .dm-msg-group.is-peer { align-items: flex-start; }
#dmDrawer .dm-msg-group__top {
    display: flex;
    gap: 8px;
    /* [CHG] 2026-05-31 r4 — Avatar + bubble cluster aligns to TOP of
     *   the first bubble in the group, not the BOTTOM of the last one
     *   (was `align-items: flex-end`). Industry standard:
     *   WeChat / Telegram / iMessage / Slack / Discord all anchor the
     *   avatar to the top of the first message so:
     *     1) the avatar is always at a stable, predictable position
     *        next to the speaker's name (or the first bubble if no
     *        name is shown), no matter how many message bubbles or
     *        how many lines each bubble contains;
     *     2) for long multi-bubble groups (e.g. 3 consecutive 5-line
     *        replies from the same speaker) the avatar isn't pushed
     *        far down to the BOTTOM of the last bubble, which made
     *        users mis-attribute the message to a different speaker;
     *     3) the timestamp / actions cluster on the bottom row reads
     *        as "the last bubble's metadata", which is also what
     *        every other chat product does.
     *   We keep `flex-direction: row-reverse` for `is-mine` (right-
     *   side own messages) so the avatar still sits on the right edge
     *   for the speaker = self case, just now top-aligned instead of
     *   bottom-aligned. */
    align-items: flex-start;
    max-width: 100%;
}
#dmDrawer .dm-msg-group.is-mine .dm-msg-group__top { flex-direction: row-reverse; }
#dmDrawer .dm-msg-group__av {
    width: 28px; height: 28px;
    border-radius: 50%;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 700;
    color: #ffffff;
    flex-shrink: 0;
}
#dmDrawer .dm-msg-group__bubbles {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    max-width: 76%;
}
#dmDrawer .dm-msg-group.is-mine .dm-msg-group__bubbles { align-items: flex-end; }
#dmDrawer .dm-msg-group.is-peer .dm-msg-group__bubbles { align-items: flex-start; }

#dmDrawer .dm-msg {
    position: relative;
    display: inline-flex;
    flex-direction: column;
    max-width: 100%;
}
#dmDrawer .dm-msg-group.is-mine .dm-msg { align-items: flex-end; }
#dmDrawer .dm-msg-group.is-peer .dm-msg { align-items: flex-start; }

#dmDrawer .dm-msg__bubble {
    padding: 8px 12px;
    border-radius: var(--dm-r-lg);
    font-size: 14px;
    line-height: 1.45;
    word-break: break-word;
    overflow-wrap: anywhere;
    max-width: 100%;
}
#dmDrawer .dm-msg-group.is-peer .dm-msg__bubble {
    background: var(--dm-bg-tertiary);
    color: var(--dm-text);
    border-bottom-left-radius: var(--dm-r-sm);
}
#dmDrawer .dm-msg-group.is-mine .dm-msg__bubble {
    background: var(--dm-accent);
    color: #ffffff;
    border-bottom-right-radius: var(--dm-r-sm);
}

/* Meta row (timestamp + edit badge + tick). In mockup this is an
   independent .dm-msg inside the group bubbles, with empty body and
   only .dm-msg__meta. Style accordingly so each group ends with one
   meta line, regardless of message count above. */
/* [CHG] 2026-05-30 — user feedback: the per-group meta line (time +
   tick + edited tag) was rendered in --dm-text-2 (#787b86 in dark)
   which is unreadable against the chart's dark background. Bumped to
   --dm-text with 0.85 opacity so it stays visually quieter than the
   bubble body but easily readable. Same fix for .dm-msg__time and
   .dm-msg__edited which inherit from this row. */
#dmDrawer .dm-msg__meta {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 2px 4px 0;
    font-size: 12px;
    color: var(--dm-text);
    opacity: 0.85;
}
#dmDrawer .dm-msg-group.is-mine .dm-msg__meta { justify-content: flex-end; }
/* [CHG] 2026-05-30 — see paired comment on .dm-msg__meta. */
#dmDrawer .dm-msg__edited { opacity: 0.85; font-size: 12px; color: var(--dm-text); }

#dmDrawer .dm-tick {
    display: inline-flex;
    align-items: center;
    line-height: 1;
}
#dmDrawer .dm-tick.is-sent,
#dmDrawer .dm-tick.is-pending { color: var(--dm-text); opacity: 0.7; }
#dmDrawer .dm-tick.is-delivered { color: var(--dm-text); opacity: 0.85; }
#dmDrawer .dm-tick.is-read { color: #4fc3f7; }

/* Reactions row directly below bubble. */
#dmDrawer .dm-reactions {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: 4px;
}
#dmDrawer .dm-reaction {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 8px;
    border-radius: var(--dm-r-full);
    border: 1px solid var(--dm-border);
    background: var(--dm-bg-tertiary);
    font-size: 12px;
    color: var(--dm-text);
    cursor: pointer;
    transition: background var(--dm-tr), border-color var(--dm-tr);
    min-height: 26px;
    font-family: inherit;
}
#dmDrawer .dm-reaction:hover { border-color: var(--dm-accent); background: var(--dm-accent-dim); }
#dmDrawer .dm-reaction.is-mine {
    border-color: var(--dm-accent);
    background: var(--dm-accent-dim);
    color: var(--dm-accent);
    font-weight: 700;
}

/* Hover actions floating beside the bubble — desktop hover, mobile tap.
   [FIX] 2026-05-31 — three changes to fix the "buttons disappear before
     I can mouse over them" complaint:
       1) The hit-area gap between .dm-msg and .dm-msg__acts (the 6px
          margin) used to be DEAD SPACE: mouse leaves .dm-msg → :hover is
          lost → acts vanish before the cursor reaches them.  We now
          anchor acts at right/left:100% (zero margin) and use an
          ::before "invisible bridge" of 8px to keep the visual gap.
       2) Exit transition is now 0.45s with a 0.15s linger before fade
          starts, so even brief mouse meandering doesn't dismiss the
          buttons mid-traverse.
       3) Acts themselves listen for :hover and force-hold opacity:1 —
          so once the cursor IS on the buttons, they stay visible
          regardless of whether .dm-msg itself is hovered. */
#dmDrawer .dm-msg__acts {
    position: absolute;
    top: 4px;
    opacity: 0;
    transition: opacity 0.45s ease 0.15s;
    display: flex;
    gap: 2px;
    background: var(--dm-bg-secondary);
    border: 1px solid var(--dm-border);
    border-radius: var(--dm-r-full);
    padding: 3px 5px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
    z-index: 5;
    pointer-events: none;
}
/* Invisible hover bridge — fills the 6 px visual gap so the cursor never
   crosses dead space when travelling from bubble → action buttons. */
#dmDrawer .dm-msg__acts::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    width: 8px;
}
#dmDrawer .dm-msg-group.is-mine .dm-msg__acts {
    right: 100%;
    margin-right: 6px;
}
#dmDrawer .dm-msg-group.is-mine .dm-msg__acts::before { left: 100%; }
#dmDrawer .dm-msg-group.is-peer .dm-msg__acts {
    left: 100%;
    margin-left: 6px;
}
#dmDrawer .dm-msg-group.is-peer .dm-msg__acts::before { right: 100%; }
#dmDrawer .dm-msg:hover .dm-msg__acts,
#dmDrawer .dm-msg.is-selected .dm-msg__acts,
#dmDrawer .dm-msg__acts:hover {
    opacity: 1;
    pointer-events: auto;
    /* Snap to visible on enter (no delay), only delay applies on leave. */
    transition: opacity 0.08s ease 0s;
}
#dmDrawer .dm-msg__act-btn {
    width: 28px; height: 28px;
    border: 0; background: transparent;
    color: var(--dm-text-2);
    border-radius: var(--dm-r-full);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    transition: color var(--dm-tr), background var(--dm-tr);
    position: relative;
    cursor: pointer;
    padding: 0;
}
#dmDrawer .dm-msg__act-btn:hover { color: var(--dm-text); background: var(--dm-bg-hover); }
#dmDrawer .dm-msg__act-btn.is-danger { color: var(--dm-danger); }
#dmDrawer .dm-msg__act-btn.is-danger:hover { background: var(--dm-danger-dim); }
@media (hover: none) and (pointer: coarse) {
    #dmDrawer .dm-msg__act-btn { width: 36px; height: 36px; }
}

/* System / grant / deleted variants stay visible inside the bubble. */
#dmDrawer .dm-msg__type {
    font-size: 12px;
    color: var(--dm-text-2);
    margin-bottom: 4px;
}
#dmDrawer .dm-msg.is-mine .dm-msg__type { color: rgba(255, 255, 255, 0.85); }
#dmDrawer .dm-msg__sys, #dmDrawer .dm-msg__grant {
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-weight: 700;
}
#dmDrawer .dm-msg.is-deleted .dm-msg__bubble {
    background: rgba(120, 123, 134, 0.18);
    color: var(--dm-text-2);
    font-style: italic;
}
#dmDrawer .dm-msg__body.is-deleted { color: var(--dm-text-2); font-style: italic; }

/* Inline edit textarea.
   [FIX] 2026-05-31 — removed the min-width: 220px hard-floor that, in
     narrow drawers (mobile / split-screen) or for short messages whose
     bubble naturally rendered narrower than 220 px, was forcing the
     bubble + its parent .dm-msg-group__bubbles past max-width:76% and
     producing a horizontal scrollbar on the whole drawer.  The textarea
     keeps width:100% of its bubble; min-width:0 lets flex children
     actually shrink below their intrinsic content size (default
     min-width:auto for flex items prevents shrinking, see CSS-Flex
     §4.5 "Implied Minimum Size of Flex Items").
   [FIX] 2026-05-31 — added box-sizing:border-box on the textarea so its
     1-px border + 8-px horizontal padding don't push the rendered width
     beyond 100% of the bubble (the prior implicit content-box made the
     effective width = 100% + 18 px, which itself was a secondary cause
     of the horizontal scroll). */
#dmDrawer .dm-msg__edit {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
    width: 100%;
    max-width: 100%;
}
#dmDrawer .dm-msg__edit-input {
    width: 100%;
    min-width: 0;
    min-height: 60px;
    max-height: 240px;
    box-sizing: border-box;
    border: 1px solid var(--dm-border-strong);
    background: var(--dm-bg-primary);
    color: var(--dm-text);
    border-radius: var(--dm-r-sm);
    padding: 6px 8px;
    font-size: 14px;
    font-family: inherit;
    resize: vertical;
    outline: none;
}
/* [CHG] 2026-05-30 — no accent border on focus for the inline edit
   input (see paired comment in .dm-inbox-search__wrap). */
#dmDrawer .dm-msg__edit-actions {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    justify-content: flex-end;
}
#dmDrawer .dm-msg__edit-cancel,
#dmDrawer .dm-msg__edit-save {
    padding: 4px 12px;
    border-radius: var(--dm-r-sm);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    border: 1px solid var(--dm-border-strong);
    font-family: inherit;
}
#dmDrawer .dm-msg__edit-cancel { background: transparent; color: var(--dm-text); }
#dmDrawer .dm-msg__edit-cancel:hover { background: var(--dm-bg-hover); }
#dmDrawer .dm-msg__edit-save {
    background: var(--dm-accent);
    color: #ffffff;
    border-color: var(--dm-accent);
}
#dmDrawer .dm-msg__edit-save:hover { background: var(--dm-accent-hover); }

/* ──────────────────────────────────────────────────────────────────────────
   TYPING + SCROLL-BOTTOM FAB
   ────────────────────────────────────────────────────────────────────────── */
#dmDrawer .dm-typing {
    display: flex;
    align-items: flex-end;
    gap: 8px;
    padding: 4px 14px 6px;
    flex-shrink: 0;
}
#dmDrawer .dm-typing[hidden] { display: none; }
#dmDrawer .dm-typing__av {
    width: 22px; height: 22px;
    border-radius: 50%;
    background: linear-gradient(135deg, #2962ff, #6990ff);
    flex-shrink: 0;
}
#dmDrawer .dm-typing__bubble {
    background: var(--dm-bg-tertiary);
    border-radius: var(--dm-r-lg);
    border-bottom-left-radius: var(--dm-r-sm);
    padding: 8px 14px;
    display: flex;
    gap: 4px;
    align-items: center;
}
#dmDrawer .dm-typing__dot {
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--dm-text-2);
    animation: dm-typing-bounce 1.2s infinite ease-in-out;
}
#dmDrawer .dm-typing__dot:nth-child(2) { animation-delay: 0.2s; }
#dmDrawer .dm-typing__dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes dm-typing-bounce {
    0%, 60%, 100% { transform: translateY(0); opacity: 0.5; }
    30% { transform: translateY(-5px); opacity: 1; }
}

#dmDrawer .dm-scroll-btn {
    position: absolute;
    bottom: calc(var(--composer-h) + 10px);
    right: 12px;
    width: 36px; height: 36px;
    border-radius: 50%;
    border: 1px solid var(--dm-border);
    background: var(--dm-bg-secondary);
    color: var(--dm-text-2);
    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.35);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    z-index: 6;
    transition: color var(--dm-tr);
    padding: 0;
}
#dmDrawer .dm-scroll-btn[hidden] { display: none; }
#dmDrawer .dm-scroll-btn:hover { color: var(--dm-text); }
#dmDrawer .dm-scroll-btn__dot {
    position: absolute;
    top: -3px; right: -3px;
    width: 10px; height: 10px;
    border-radius: 50%;
    background: var(--dm-danger);
    border: 2px solid var(--dm-bg-primary);
}
#dmDrawer .dm-scroll-btn__dot[hidden] { display: none; }

/* ──────────────────────────────────────────────────────────────────────────
   EMPTY STATES
   ────────────────────────────────────────────────────────────────────────── */
#dmDrawer .dm-empty {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 32px 24px;
    text-align: center;
    gap: 10px;
}
#dmDrawer .dm-empty[hidden] { display: none; }
#dmDrawer .dm-empty__icon {
    font-size: 42px;
    opacity: 0.3;
    line-height: 1;
}
#dmDrawer .dm-empty__title {
    font-size: 14px;
    font-weight: 700;
    color: var(--dm-text);
}
#dmDrawer .dm-empty__body {
    font-size: 12px;
    color: var(--dm-text-2);
    line-height: 1.55;
    max-width: 240px;
}
#dmDrawer .dm-empty__cta {
    margin-top: 4px;
    padding: 9px 20px;
    border-radius: var(--dm-r-md);
    border: 0;
    background: var(--dm-accent);
    color: #ffffff;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: background var(--dm-tr);
    font-family: inherit;
}
#dmDrawer .dm-empty__cta:hover { background: var(--dm-accent-hover); }

/* ──────────────────────────────────────────────────────────────────────────
   COMPOSER
   ────────────────────────────────────────────────────────────────────────── */
#dmDrawer .dm-composer {
    flex-shrink: 0;
    border-top: 1px solid var(--chart-popup-border);
    background: var(--chart-popup-bg);
    padding: 10px 12px 12px;
    position: relative;
}
body:not(.dark-theme) #dmDrawer .dm-composer {
    border-top: 1px solid var(--dm-border);
    background: var(--dm-bg-secondary);
}
/* [CHG] 2026-05-30 — Composer now keeps a fixed 40px single-line
   baseline so the emoji / attach icons and the text caret share the
   same horizontal line. The <textarea> grows from min 40px to max 120px
   on multi-line input; the icons and send button stay anchored to the
   bottom while still aligning with the first line of typed text at the
   single-line baseline (align-items: center on a 40px row gives perfect
   vertical centring of 20px-wide SVG icons against a 14px caret). */
#dmDrawer .dm-composer__main {
    display: flex;
    align-items: center;
    gap: 4px;
    background: #000000;
    border: 1px solid var(--chart-popup-border);
    border-radius: var(--dm-r-lg);
    padding: 4px 6px 4px 8px;
    min-height: 40px;
    transition: border-color var(--dm-tr);
}
body:not(.dark-theme) #dmDrawer .dm-composer__main {
    background: var(--dm-bg-secondary);
    border: 1px solid var(--dm-border);
}
/* [CHG] 2026-05-30 — no accent border on focus for the composer
   wrapper (see paired comment in .dm-inbox-search__wrap). */
/* [CHG] 2026-05-30 — multi-line layout: when the textarea grows beyond
   one line we want icons to sit at the BOTTOM, not the centre, so they
   align with the bottom of the typed text. JS toggles .is-multiline on
   the main wrapper based on scrollHeight > LINE_HEIGHT. */
#dmDrawer .dm-composer__main.is-multiline { align-items: flex-end; }
#dmDrawer .dm-composer__icon-btn {
    width: 32px; height: 32px;
    border: 0;
    background: #000000;
    color: var(--dm-text-2);
    border-radius: var(--dm-r-sm);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: color var(--dm-tr), background var(--dm-tr);
    position: relative;
    cursor: pointer;
    padding: 0;
}
#dmDrawer .dm-composer__icon-btn:hover { color: var(--dm-accent); background: #000000; }
#dmDrawer .dm-composer__icon-btn.is-active { color: var(--dm-accent); background: #000000; }
body:not(.dark-theme) #dmDrawer .dm-composer__icon-btn {
    background: transparent;
}
body:not(.dark-theme) #dmDrawer .dm-composer__icon-btn:hover,
body:not(.dark-theme) #dmDrawer .dm-composer__icon-btn.is-active {
    background: var(--dm-bg-tertiary);
}
#dmDrawer .dm-composer__icon-btn svg { width: 20px; height: 20px; }
#dmDrawer .dm-composer__ta {
    flex: 1;
    border: 0;
    background: #000000;
    color: var(--dm-text);
    font-size: 14px;
    line-height: 20px;
    resize: none;
    outline: none;
    min-height: 20px;
    max-height: 120px;
    overflow-y: auto;
    padding: 6px 4px;
    font-family: inherit;
    align-self: stretch;
    display: block;
}
body:not(.dark-theme) #dmDrawer .dm-composer__ta {
    background: transparent;
}
#dmDrawer .dm-composer__ta::placeholder { color: var(--dm-text-2); }
#dmDrawer .dm-composer__send {
    width: 32px; height: 32px;
    border: 0;
    background: var(--dm-accent);
    color: #ffffff;
    border-radius: var(--dm-r-md);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: background var(--dm-tr), opacity var(--dm-tr);
    cursor: pointer;
    padding: 0;
    position: relative;
}
#dmDrawer .dm-composer__send svg { width: 14px; height: 14px; }
#dmDrawer .dm-composer__send:hover:not(:disabled) { background: var(--dm-accent-hover); }
#dmDrawer .dm-composer__send:disabled {
    background: var(--dm-bg-tertiary);
    color: var(--dm-text-2);
    opacity: 0.6;
    cursor: not-allowed;
}
#dmDrawer .dm-composer__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 4px 2px 0;
    min-height: 18px;
}
#dmDrawer .dm-composer__hint {
    font-size: 12px;
    color: var(--dm-text-2);
    opacity: 0.6;
    transition: opacity var(--dm-tr);
}
#dmDrawer .dm-composer__count {
    font-size: 12px;
    color: var(--dm-text-2);
}
#dmDrawer .dm-composer__count.is-near-limit { color: var(--dm-away); }
#dmDrawer .dm-composer__count.is-over { color: var(--dm-danger); }

/* ──────────────────────────────────────────────────────────────────────────
   EMOJI PICKER
   ────────────────────────────────────────────────────────────────────────── */
#dmDrawer .dm-emoji-picker {
    position: absolute;
    bottom: var(--composer-h);
    left: 12px;
    right: 12px;
    max-height: 280px;
    background: var(--dm-popover-bg);
    border-top: 1px solid var(--dm-popover-border);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    z-index: 20;
    box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.35);
}
#dmDrawer .dm-emoji-picker[hidden] { display: none; }
#dmDrawer .dm-emoji__search {
    padding: 8px 12px;
    border-bottom: 1px solid var(--dm-border);
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 8px;
}
#dmDrawer .dm-emoji__search > svg { color: var(--dm-text-2); flex-shrink: 0; }
#dmDrawer .dm-emoji__search input {
    flex: 1;
    border: 0;
    background: transparent;
    color: var(--dm-text);
    font-size: 14px;
    outline: none;
    height: 30px;
    min-width: 0;
    font-family: inherit;
}
#dmDrawer .dm-emoji__search input::placeholder { color: var(--dm-text-2); }
#dmDrawer .dm-emoji__tabs {
    display: flex;
    border-bottom: 1px solid var(--dm-border);
    flex-shrink: 0;
}
#dmDrawer .dm-emoji__tab {
    flex: 1;
    height: 36px;
    border: 0;
    background: transparent;
    font-size: 16px;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background var(--dm-tr);
    padding: 0;
    font-family: inherit;
    color: var(--dm-text);
}
#dmDrawer .dm-emoji__tab:hover { background: var(--dm-bg-hover); }
#dmDrawer .dm-emoji__tab.is-active { border-bottom-color: var(--dm-accent); }

#dmDrawer .dm-emoji__grid {
    flex: 1;
    overflow-y: auto;
    padding: 6px;
    display: grid;
    grid-template-columns: repeat(9, 1fr);
    gap: 2px;
    align-content: start;
}
@media (hover: none) and (pointer: coarse) {
    #dmDrawer .dm-emoji__grid { grid-template-columns: repeat(7, 1fr); }
}
#dmDrawer .dm-emoji__section {
    grid-column: 1 / -1;
    font-size: 12px;
    font-weight: 700;
    color: var(--dm-text-2);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    padding: 5px 2px 2px;
}
#dmDrawer .dm-emoji__item {
    width: 100%;
    height: 34px;
    border: 0;
    background: transparent;
    font-size: 18px;
    border-radius: var(--dm-r-sm);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background var(--dm-tr);
    line-height: 1;
    padding: 0;
    font-family: inherit;
}
#dmDrawer .dm-emoji__item:hover { background: var(--dm-bg-hover); }
#dmDrawer .dm-emoji__item:focus,
#dmDrawer .dm-emoji__item:focus-visible {
    outline: none !important;
    box-shadow: none !important;
    background: var(--dm-bg-hover);
}
#dmDrawer .dm-emoji__status {
    height: 28px;
    border-top: 1px solid var(--dm-border);
    padding: 0 12px;
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    font-size: 12px;
    color: var(--dm-text-2);
}

/* ──────────────────────────────────────────────────────────────────────────
   ACTION SHEET — slides up from bottom of drawer
   ────────────────────────────────────────────────────────────────────────── */
#dmDrawer .dm-as-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 80;
    display: flex;
    align-items: flex-end;
    animation: dm-as-in 0.18s ease;
}
#dmDrawer .dm-as-backdrop[hidden] { display: none; }
@keyframes dm-as-in { from { opacity: 0; } to { opacity: 1; } }
#dmDrawer .dm-action-sheet {
    width: 100%;
    background: var(--dm-bg-secondary);
    border-top: 1px solid var(--dm-border-strong);
    padding: 0 0 16px;
    animation: dm-as-up 0.22s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes dm-as-up {
    from { transform: translateY(100%); }
    to { transform: translateY(0); }
}
#dmDrawer .dm-as-handle {
    width: 36px;
    height: 4px;
    border-radius: 2px;
    background: var(--dm-border-strong);
    margin: 10px auto 14px;
    display: block;
}
#dmDrawer .dm-as-icon {
    width: 46px; height: 46px;
    border-radius: 50%;
    background: var(--dm-danger-dim);
    color: var(--dm-danger);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 22px;
    margin: 0 auto 10px;
}
#dmDrawer .dm-as-title {
    font-size: 14px;
    font-weight: 700;
    color: var(--dm-text);
    text-align: center;
    padding: 0 20px;
    margin: 0 0 6px;
}
#dmDrawer .dm-as-body {
    font-size: 12px;
    color: var(--dm-text-2);
    text-align: center;
    line-height: 1.6;
    padding: 0 24px;
    margin: 0 0 20px;
}
#dmDrawer .dm-as-btns {
    display: flex;
    gap: 10px;
    padding: 0 16px;
}
#dmDrawer .dm-as-cancel,
#dmDrawer .dm-as-ok {
    flex: 1;
    padding: 11px;
    border-radius: var(--dm-r-md);
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    font-family: inherit;
    transition: background var(--dm-tr);
}
#dmDrawer .dm-as-cancel {
    border: 1px solid var(--dm-border-strong);
    background: transparent;
    color: var(--dm-text);
}
#dmDrawer .dm-as-cancel:hover { background: var(--dm-bg-hover); }
#dmDrawer .dm-as-ok {
    border: 0;
    background: var(--dm-danger);
    color: #ffffff;
    font-weight: 700;
}
#dmDrawer .dm-as-ok:hover { background: #c62828; }

/* ──────────────────────────────────────────────────────────────────────────
   INNER PANELS — slide in from right, profile + new conv
   ────────────────────────────────────────────────────────────────────────── */
#dmDrawer .dm-inner-panel {
    position: absolute;
    inset: 0;
    /* [FIX] 2026-05-31 — match the drawer-home (inbox/thread) surface which
       sits on .sc-drawer's var(--chart-popup-bg) (#0d0d0d dark / #fff light).
       Previously used --dm-bg-primary (#131722) so the profile pane looked a
       different colour from the chat home the user navigated from. */
    background: var(--chart-popup-bg);
    z-index: 60;
    display: flex;
    flex-direction: column;
    animation: dm-panel-in 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
#dmDrawer .dm-inner-panel[hidden] { display: none; }
@keyframes dm-panel-in {
    from { transform: translateX(100%); opacity: 0.5; }
    to { transform: translateX(0); opacity: 1; }
}
#dmDrawer .dm-inner-panel__head {
    height: var(--dm-head-h);
    background: var(--drawer-head-bg, var(--chart-popup-bg));
    border-bottom: 1px solid var(--chart-popup-border);
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 0 12px;
    flex-shrink: 0;
}
body:not(.dark-theme) #dmDrawer .dm-inner-panel__head {
    background: var(--dm-bg-secondary);
    border-bottom: 1px solid var(--dm-border);
}
#dmDrawer .dm-inner-panel__title {
    flex: 1;
    font-size: 14px;
    font-weight: 700;
    color: var(--dm-text);
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
#dmDrawer .dm-inner-panel__body {
    flex: 1;
    overflow-y: auto;
}

/* Friend panel */
#dmDrawer .dm-friends-panel { padding: 14px; display: flex; flex-direction: column; gap: 16px; }
#dmDrawer .dm-friends-panel__search { display: flex; gap: 8px; }
#dmDrawer .dm-friends-panel__search input,
#dmDrawer .dm-friends-panel__message {
    width: 100%;
    border: 1px solid var(--dm-border);
    border-radius: var(--dm-r-sm);
    background: var(--dm-bg-tertiary);
    color: var(--dm-text);
    font: inherit;
    font-size: 14px;
    padding: 8px 10px;
}
#dmDrawer .dm-friends-panel__search input { flex: 1; min-width: 0; }
#dmDrawer .dm-friends-panel__action {
    border: 1px solid var(--dm-border);
    border-radius: var(--dm-r-sm);
    background: transparent;
    color: var(--dm-text);
    font: inherit;
    font-size: 14px;
    cursor: pointer;
    padding: 8px 10px;
}
#dmDrawer .dm-friends-panel__action.is-primary { background: var(--dm-accent); border-color: var(--dm-accent); color: #ffffff; }
#dmDrawer .dm-friends-panel__action.is-danger { color: var(--dm-danger); }
/* [FIX] 2026-08-10 — Keep friend lookup proportional to its supporting role.
   Reason: the former shared search selector forced a dominant primary button.
   Change: reuse the secondary action treatment and align its height to the input.
   Warning: do not apply this treatment to primary request-management actions. */
#dmDrawer .dm-friends-panel__search-action {
    align-self: stretch;
    min-height: 28px;
    color: var(--dm-accent);
}
#dmDrawer .dm-friends-panel__search-action:hover:not(:disabled) {
    background: var(--dm-accent-dim);
    border-color: var(--dm-accent);
}
#dmDrawer .dm-friends-panel__search-action:focus-visible {
    outline: 2px solid var(--dm-accent);
    outline-offset: 2px;
}
#dmDrawer .dm-friends-panel__search-action:disabled {
    cursor: not-allowed;
    opacity: 0.5;
}
#dmDrawer .dm-friends-panel__hint,
#dmDrawer .dm-friends-panel__empty,
#dmDrawer .dm-friends-panel__request-message { color: var(--dm-text-2); font-size: 12px; line-height: 1.45; }
#dmDrawer .dm-friends-panel__section { display: flex; flex-direction: column; gap: 8px; }
#dmDrawer .dm-friends-panel__section-title { color: var(--dm-text); font-size: 14px; font-weight: 700; }
#dmDrawer .dm-friends-panel__card {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px;
    border: 1px solid var(--dm-border);
    border-radius: var(--dm-r-md);
}
#dmDrawer .dm-friends-panel__card .dm-avatar { width: 36px; height: 36px; }
#dmDrawer .dm-friends-panel__identity { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
#dmDrawer .dm-friends-panel__name { color: var(--dm-text); font-size: 14px; font-weight: 600; }
#dmDrawer .dm-friends-panel__handle { color: var(--dm-text-2); font-size: 12px; }
#dmDrawer .dm-friends-panel__actions { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }
#dmDrawer .dm-friends-panel__result { display: flex; flex-direction: column; gap: 8px; }

/* Friend-request system messages */
#dmDrawer .dm-msg__friend-card {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 220px;
    padding: 10px;
    border: 1px solid var(--dm-border);
    border-radius: var(--dm-r-md);
    background: var(--dm-bg-tertiary);
}
#dmDrawer .dm-msg__friend-card p { margin: 0; color: var(--dm-text); font-size: 14px; }
#dmDrawer .dm-msg__friend-card-actions { display: flex; gap: 8px; }
#dmDrawer .dm-msg__friend-card-actions button {
    border: 1px solid var(--dm-border);
    border-radius: var(--dm-r-sm);
    background: transparent;
    color: var(--dm-text);
    cursor: pointer;
    font: inherit;
    font-size: 12px;
    padding: 6px 10px;
}
#dmDrawer .dm-msg__friend-card-actions button.is-primary { background: var(--dm-accent); border-color: var(--dm-accent); color: #ffffff; }
#dmDrawer .dm-msg__friend-card-actions button.is-danger { color: var(--dm-danger); }

/* New conv panel */
#dmDrawer .dm-new-conv__search {
    padding: 10px 12px;
    border-bottom: 1px solid var(--dm-border);
    flex-shrink: 0;
}
#dmDrawer .dm-new-conv__sw {
    display: flex;
    align-items: center;
    gap: 8px;
    background: var(--dm-bg-tertiary);
    border: 1px solid var(--dm-border);
    border-radius: var(--dm-r-full);
    padding: 7px 12px;
    height: 42px;
    transition: border-color var(--dm-tr);
}
/* [CHG] 2026-05-30 — no accent border on focus for new-conv search
   (see paired comment in .dm-inbox-search__wrap). */
#dmDrawer .dm-new-conv__sw > svg { color: var(--dm-text-2); flex-shrink: 0; }
#dmDrawer .dm-new-conv__sw input {
    flex: 1;
    border: 0;
    background: transparent;
    color: var(--dm-text);
    font-size: 14px;
    outline: none;
    min-width: 0;
    font-family: inherit;
}
#dmDrawer .dm-new-conv__sw input::placeholder { color: var(--dm-text-2); }
#dmDrawer .dm-new-conv__label {
    font-size: 12px;
    font-weight: 700;
    color: var(--dm-text-2);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    padding: 10px 14px 4px;
}
#dmDrawer .dm-new-conv__user {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    cursor: pointer;
    border-bottom: 1px solid var(--dm-border);
    transition: background var(--dm-tr);
    min-height: 62px;
}
#dmDrawer .dm-new-conv__user:hover { background: var(--dm-bg-hover); }
#dmDrawer .dm-new-conv__user .dm-avatar {
    width: 36px; height: 36px;
}
#dmDrawer .dm-new-conv__name {
    font-size: 14px;
    font-weight: 600;
    color: var(--dm-text);
}
#dmDrawer .dm-new-conv__handle {
    font-size: 12px;
    color: var(--dm-text-2);
}

/* Profile panel */
:is(#dmDrawer, .dm-profile-host) .dm-profile__body {
    padding: 24px 20px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__avatar {
    width: 72px; height: 72px;
    border-radius: 50%;
    overflow: hidden;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__avatar img { width: 100%; height: 100%; object-fit: cover; }
:is(#dmDrawer, .dm-profile-host) .dm-profile__name {
    font-size: 18px;
    font-weight: 700;
    color: var(--dm-text);
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__handle {
    font-size: 12px;
    color: var(--dm-text-2);
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__online {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--dm-online);
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__stats {
    display: flex;
    gap: 28px;
    background: var(--dm-bg-tertiary);
    border-radius: var(--dm-r-md);
    padding: 14px 20px;
    width: 100%;
    justify-content: center;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__stat-val {
    font-size: 16px;
    font-weight: 700;
    color: var(--dm-text);
    text-align: center;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__stat-lbl {
    font-size: 12px;
    color: var(--dm-text-2);
    text-align: center;
    margin-top: 2px;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__bio {
    font-size: 14px;
    color: var(--dm-text-2);
    text-align: center;
    line-height: 1.55;
    width: 100%;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__divider {
    width: 100%;
    height: 1px;
    background: var(--dm-border);
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__sect {
    font-size: 12px;
    font-weight: 700;
    color: var(--dm-text-2);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    width: 100%;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__scripts-list {
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__script {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 10px 12px;
    background: transparent;
    border: 1px solid var(--dm-border);
    border-radius: var(--dm-r-md);
    cursor: pointer;
    transition: background var(--dm-tr);
    min-height: 52px;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__script:hover { background: var(--dm-bg-hover); }
:is(#dmDrawer, .dm-profile-host) .dm-profile__stat--tap {
    cursor: pointer;
    border-radius: var(--dm-r-md);
    transition: background var(--dm-tr);
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__stat--tap:hover {
    background: var(--dm-bg-hover);
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__scripts-empty {
    list-style: none;
    padding: 8px 0;
    font-size: 12px;
    color: var(--dm-text-2);
    text-align: center;
}
/* [ADD] 2026-05-30 — DM profile pane v2: stats card / CTA row / social
   row / Popular Scripts section. All values stay inside the mockup
   token palette. */
:is(#dmDrawer, .dm-profile-host) .dm-profile__online--away    { color: var(--dm-away); }
:is(#dmDrawer, .dm-profile-host) .dm-profile__online--offline { color: var(--dm-text-2); }
:is(#dmDrawer, .dm-profile-host) .dm-profile__stat {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__stat-n {
    font-size: 16px;
    font-weight: 700;
    color: var(--dm-text);
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__stat-l {
    font-size: 12px;
    color: var(--dm-text-2);
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__social {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    justify-content: center;
    width: 100%;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__social-btn {
    padding: 4px 10px;
    border-radius: var(--dm-r-sm);
    background: var(--dm-bg-tertiary);
    color: var(--dm-text);
    text-decoration: none;
    font-size: 12px;
    border: 1px solid var(--dm-border);
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__social-btn:hover {
    background: var(--dm-bg-hover);
    text-decoration: none;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__cta-row {
    display: flex;
    gap: 8px;
    width: 100%;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__cta {
    flex: 1;
    padding: 8px 12px;
    border-radius: var(--dm-r-md);
    border: 1px solid var(--dm-border);
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    background: transparent;
    color: var(--dm-text);
    transition: background var(--dm-tr), border-color var(--dm-tr);
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__cta--primary {
    background: var(--dm-accent);
    border-color: var(--dm-accent);
    color: #ffffff;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__cta--primary:hover { background: var(--dm-accent-hover); }
:is(#dmDrawer, .dm-profile-host) .dm-profile__cta--ghost:hover { background: var(--dm-bg-hover); }
:is(#dmDrawer, .dm-profile-host) .dm-profile__cta--danger { color: var(--dm-danger); }
:is(#dmDrawer, .dm-profile-host) .dm-profile__cta:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__section {
    width: 100%;
    margin-top: 6px;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__section-title {
    font-size: 12px;
    font-weight: 700;
    color: var(--dm-text-2);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 8px;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__scripts {
    display: flex;
    flex-direction: column;
    gap: 6px;
    list-style: none;
    margin: 0;
    padding: 0;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__script-row1 {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__script-perf {
    margin-left: auto;
    flex-shrink: 0;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__script-name {
    flex: 1;
    font-size: 14px;
    font-weight: 600;
    color: var(--dm-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__script-cat {
    flex-shrink: 0;
    font-size: 12px;
    color: var(--dm-text-2);
    padding: 2px 6px;
    background: var(--dm-bg-primary);
    border-radius: var(--dm-r-sm);
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__script-dsc {
    font-size: 12px;
    color: var(--dm-text-2);
    line-height: 1.4;
}
:is(#dmDrawer, .dm-profile-host) .dm-profile__err {
    padding: 6px 10px;
    border-radius: var(--dm-r-sm);
    background: rgba(239, 83, 80, 0.10);
    color: var(--dm-danger);
    font-size: 12px;
}

/* ──────────────────────────────────────────────────────────────────────────
   LOAD-MORE / ERROR BANNER
   ────────────────────────────────────────────────────────────────────────── */
#dmDrawer .dm-drawer__loadmore,
#dmDrawer .dm-drawer__loadmore-top {
    text-align: center;
    padding: 8px 0;
}
#dmDrawer .dm-drawer__loadmore[hidden],
#dmDrawer .dm-drawer__loadmore-top[hidden] { display: none; }
#dmDrawer .dm-loadmore-btn {
    border: 1px solid var(--dm-border);
    background: transparent;
    color: var(--dm-text);
    border-radius: var(--dm-r-sm);
    padding: 6px 16px;
    cursor: pointer;
    font-size: 12px;
    font-family: inherit;
}
#dmDrawer .dm-loadmore-btn:hover { background: var(--dm-bg-hover); }

#dmDrawer .dm-drawer__banner {
    position: absolute;
    left: 12px; right: 12px;
    bottom: calc(var(--composer-h) + 8px);
    z-index: 70;
    padding: 10px 14px;
    border-radius: var(--dm-r-md);
    background: rgba(239, 83, 80, 0.16);
    border: 1px solid rgba(239, 83, 80, 0.40);
    color: var(--dm-danger);
    font-size: 14px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}
#dmDrawer .dm-drawer__banner.is-success {
    background: rgba(38, 166, 154, 0.16);
    border-color: rgba(38, 166, 154, 0.40);
    color: var(--dm-online);
}
#dmDrawer .dm-drawer__banner[hidden] { display: none; }

/* ──────────────────────────────────────────────────────────────────────────
   TOUCH / MOBILE
   ────────────────────────────────────────────────────────────────────────── */
@media (hover: none) and (pointer: coarse) {
    #dmDrawer .dm-icon-btn::after,
    #dmDrawer .dm-head__back::after {
        content: "";
        position: absolute;
        inset: -7px;
    }
    #dmDrawer .dm-conv__act::after { content: ""; position: absolute; inset: -9px; }
    #dmDrawer .dm-composer__icon-btn::after { content: ""; position: absolute; inset: -7px; }
    #dmDrawer .dm-composer__send::after { content: ""; position: absolute; inset: -6px; }
    #dmDrawer .dm-ctx-dismiss::after { content: ""; position: absolute; inset: -10px; }
    #dmDrawer .dm-more-menu__item { padding-top: 12px; padding-bottom: 12px; }
    #dmDrawer .dm-composer__hint { display: none; }
}

@media (max-width: 480px) {
    #dmDrawer.dm-drawer { border-radius: 0; }
    #dmDrawer .dm-msg-group__bubbles { max-width: 82%; }
    #dmDrawer .dm-emoji-picker { max-height: 45vh; }
}

/* [FIX] 2026-05-31 — notification / DM toolbar badge mis-positioning.
   Reason: #notifToolbarBadge (position:absolute; top:2px; right:2px) was
     rendering at the VIEWPORT top-right corner — visually overlapping the
     top-bar #serverSelectBtn — instead of pinned inside its own bell
     button. Root cause: the absolute badge could not find a positioned
     ancestor, so it resolved against the initial containing block (the
     viewport). The generic `.toolbar-btn { position: relative }` rule
     (specificity 0,1,0) is fragile — any later same-or-higher specificity
     rule, or a stale cached app.css, drops the containing block.
   Change: pin position:relative with ID-level specificity (1,0,0) on the
     exact toolbar buttons that host a badge, at end-of-file so nothing can
     override it. This guarantees the badge stays inside its own button.
   Warning: do NOT remove — deleting this re-introduces the "badge floats
     to the screen corner / lands on server-select" bug. The host button
     MUST stay positioned for the badge's top/right offsets to anchor. */
#notifToolbarBtn,
#dmToolbarBtn {
    position: relative;
}

/* [CHG] 2026-05-31 — Chart modal/drawer/dropdown input cleanup.
 * User request (dark + light themes):
 *   1) remove visible borders from dropdown/input controls
 *   2) remove focus highlight color from those controls
 * Scope is intentionally limited to chart overlay surfaces. */
/* [FIX] 2026-06-10 — checkbox/radio excluded from the transparent-border
 *   reset below. The .symbol-columns-menu custom-column items use
 *   appearance:none checkboxes whose visible box IS the border
 *   (1.5px solid --text-primary). The blanket `border-color: transparent
 *   !important` on every input made unchecked boxes invisible on dark
 *   theme. Reason: reset targeted text controls but also caught the
 *   self-drawn checkboxes. Change: :not([type=checkbox]):not([type=radio]).
 *   Warning: do NOT drop the :not() guards — it re-hides the checkbox box. */
:where(
    .chart-menu-dropdown,
    .nud-submenu,
    .nav-user-dropdown,
    .server-select-dropdown,
    .drawing-tool-dropdown-menu,
    .custom-select-options,
    .symbol-columns-menu,
    .data-control-menu,
    .exchange-dialog,
    .confirm-dialog,
    .input-dialog,
    .ca-dialog,
    .ind-settings-panel,
    .tt-settings-popover,
    .pt-modal,
    .ta-tech-modal,
    .sc-filter-menu,
    .interval-dropdown-menu,
    .chart-type-dropdown-menu,
    .ma-dropdown-menu,
    .ma-submenu,
    .ai-panel,
    .news-drawer,
    .sc-drawer,
    #dmDrawer.dm-drawer,
    #dmDrawer .dm-more-menu,
    #dmDrawer .dm-emoji-picker,
    #dmDrawer .dm-action-sheet,
    #dmDrawer .dm-inner-panel
) :is(input:not([type="checkbox"]):not([type="radio"]), select, textarea, .custom-select-trigger, .pt-dropdown-selected) {
    border-color: transparent !important;
    outline: none !important;
    box-shadow: none !important;
}

:where(
    .chart-menu-dropdown,
    .nud-submenu,
    .nav-user-dropdown,
    .server-select-dropdown,
    .drawing-tool-dropdown-menu,
    .custom-select-options,
    .symbol-columns-menu,
    .data-control-menu,
    .exchange-dialog,
    .confirm-dialog,
    .input-dialog,
    .ca-dialog,
    .ind-settings-panel,
    .tt-settings-popover,
    .pt-modal,
    .ta-tech-modal,
    .sc-filter-menu,
    .interval-dropdown-menu,
    .chart-type-dropdown-menu,
    .ma-dropdown-menu,
    .ma-submenu,
    .ai-panel,
    .news-drawer,
    .sc-drawer,
    #dmDrawer.dm-drawer,
    #dmDrawer .dm-more-menu,
    #dmDrawer .dm-emoji-picker,
    #dmDrawer .dm-action-sheet,
    #dmDrawer .dm-inner-panel
) :is(input:not([type="checkbox"]):not([type="radio"]), select, textarea, .custom-select-trigger, .pt-dropdown-selected):is(:focus, :focus-visible) {
    border-color: transparent !important;
    outline: none !important;
    box-shadow: none !important;
}

/* ==== [ADD] 2026-05-31 DM-4 — image attachments ============================
 *
 * Three blocks below, in render order:
 *   1) .dm-msg__attach   — image grid inside a message bubble (1..4 thumbs).
 *   2) .dm-composer__attach-row + .dm-composer__attach-tile — pending list
 *      above the textarea while uploads are queued / in-flight / done.
 *   3) .dm-img-lightbox — full-viewport zoom modal triggered by tap on
 *      a message thumbnail.
 *
 * All three scope under #dmDrawer where applicable so they cannot leak
 * into chart popovers / sc-drawer.  Spec §3.5.3 / §6.x.
 * --------------------------------------------------------------------------- */

/* (1) Bubble attachment grid */
#dmDrawer .dm-msg__attach {
    display: grid;
    gap: 4px;
    border-radius: var(--dm-r-md, 8px);
    overflow: hidden;
    margin: 0 0 4px 0;
}
#dmDrawer .dm-msg__attach.dm-msg__attach--n1 { grid-template-columns: 1fr; max-width: 320px; }
#dmDrawer .dm-msg__attach.dm-msg__attach--n2 { grid-template-columns: 1fr 1fr; max-width: 320px; }
#dmDrawer .dm-msg__attach.dm-msg__attach--n3,
#dmDrawer .dm-msg__attach.dm-msg__attach--n4 {
    grid-template-columns: 1fr 1fr;
    max-width: 320px;
}
#dmDrawer .dm-msg__attach-tile {
    position: relative;
    display: block;
    padding: 0;
    border: 0;
    background: #000;
    cursor: zoom-in;
    overflow: hidden;
    aspect-ratio: 1 / 1;
    border-radius: var(--dm-r-sm, 4px);
}
#dmDrawer .dm-msg__attach.dm-msg__attach--n1 .dm-msg__attach-tile {
    aspect-ratio: auto;
    max-height: 320px;
}
#dmDrawer .dm-msg__attach-tile img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
#dmDrawer .dm-msg__attach.dm-msg__attach--n1 .dm-msg__attach-tile img {
    object-fit: contain;
    max-height: 320px;
}
#dmDrawer .dm-msg__attach-tile:focus-visible {
    outline: 2px solid var(--dm-accent, #2962ff);
    outline-offset: 2px;
}

/* Image-only bubble (no body text) — bubble shrinks to the grid.  We
   already skip rendering .dm-msg__body when text is empty, so the
   default bubble paddings still apply; tighten them slightly so the
   thumbnail edge doesn't look "floating" inside an oversized chrome. */
#dmDrawer .dm-msg__bubble:has(.dm-msg__attach):not(:has(.dm-msg__body)) {
    padding: 4px;
}

/* (2) Pending-attachment row above the textarea */
#dmDrawer .dm-composer__attach-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 6px 4px;
    border-bottom: 1px dashed var(--dm-border, rgba(255, 255, 255, 0.08));
    margin-bottom: 4px;
}
#dmDrawer .dm-composer__attach-row[hidden] { display: none; }
#dmDrawer .dm-composer__attach-tile {
    position: relative;
    width: 56px;
    height: 56px;
    border-radius: var(--dm-r-sm, 4px);
    overflow: hidden;
    background: #000;
    flex-shrink: 0;
}
#dmDrawer .dm-composer__attach-tile img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
#dmDrawer .dm-composer__attach-tile.is-uploading::before {
    content: '';
    position: absolute; inset: 0;
    background: rgba(0, 0, 0, 0.35);
}
#dmDrawer .dm-composer__attach-tile.is-failed {
    box-shadow: inset 0 0 0 2px var(--tt-down, #ef5350);
}
#dmDrawer .dm-composer__attach-skeleton {
    width: 100%;
    height: 100%;
    background: linear-gradient(90deg, #181818 0%, #2a2a2a 50%, #181818 100%);
    background-size: 200% 100%;
    animation: dmAttachShimmer 1.2s linear infinite;
}
@keyframes dmAttachShimmer {
    0%   { background-position: 100% 0; }
    100% { background-position: -100% 0; }
}
#dmDrawer .dm-composer__attach-mark {
    position: absolute;
    left: 4px; bottom: 4px;
    min-width: 16px; height: 16px;
    padding: 0 4px;
    border-radius: 8px;
    background: rgba(0, 0, 0, 0.65);
    color: #fff;
    font-size: 12px;
    line-height: 16px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
#dmDrawer .dm-composer__attach-mark.is-ok  { background: var(--tt-up,   #26a69a); }
#dmDrawer .dm-composer__attach-mark.is-err { background: var(--tt-down, #ef5350); }
#dmDrawer .dm-composer__attach-rm {
    position: absolute;
    right: 2px; top: 2px;
    width: 18px; height: 18px;
    border-radius: 9px;
    border: 0;
    background: rgba(0, 0, 0, 0.7);
    color: #fff;
    font-size: 12px;
    line-height: 18px;
    cursor: pointer;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
#dmDrawer .dm-composer__attach-rm:hover { background: var(--tt-down, #ef5350); }

/* Drag-over visual feedback on the composer */
#dmDrawer .dm-composer.is-dragover .dm-composer__main {
    border-color: var(--dm-accent, #2962ff);
    background: rgba(41, 98, 255, 0.05);
}

/* (3) Lightbox.  Lives directly on <body>, NOT inside #dmDrawer, so the
   scope above does not apply.  Keep z-index above every drawer
   (dm-drawer z-index lives at ~10000 in this file). */
.dm-img-lightbox {
    position: fixed;
    inset: 0;
    z-index: 100000;
    background: rgba(0, 0, 0, 0.92);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    cursor: zoom-out;
}
.dm-img-lightbox[hidden] { display: none; }
.dm-img-lightbox__img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    display: block;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
}
.dm-img-lightbox__close {
    position: absolute;
    top: 16px; right: 16px;
    width: 36px; height: 36px;
    border-radius: 18px;
    border: 0;
    background: rgba(255, 255, 255, 0.12);
    color: #fff;
    font-size: 18px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.dm-img-lightbox__close:hover { background: rgba(255, 255, 255, 0.22); }
body.dm-no-scroll { overflow: hidden; }


/* ============================================================================
 * [CHG] 2026-07-19 — Scripts personal tabs compact rows (.sc-row)
 *
 * Scope: My Scripts / Favorites / Invited / Running.
 * Community remains card-based (.ys-script-card) and is intentionally untouched.
 * ============================================================================ */
.sc-row {
    display: grid;
    grid-template-columns: 22px minmax(0, 1fr) minmax(0, auto) minmax(0, auto);
    gap: 12px;
    align-items: center;
    width: 100%;
    min-width: 0;
    max-width: 100%;
    padding: 9px 0 9px 14px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    color: var(--tt-fg-0, var(--text-primary));
    cursor: pointer;
    transition: background .12s ease;
}
.sc-row:last-child { border-bottom: none; }
.sc-row:hover { background: rgba(255, 255, 255, 0.03); }
body:not(.dark-theme) .sc-row { border-bottom-color: rgba(0, 0, 0, 0.08); }
body:not(.dark-theme) .sc-row:hover { background: rgba(0, 0, 0, 0.03); }

.sc-row__icon {
    width: 22px;
    height: 22px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.sc-row__icon svg { width: 16px; height: 16px; }
.sc-row__icon.is-strategy { color: var(--tt-up); }
.sc-row__icon.is-indicator,
.sc-row__icon.is-library { color: var(--tt-fg-0, var(--text-primary)); opacity: .88; }

.sc-row__info {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.sc-row__head {
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    overflow: hidden;
}
.sc-row__name {
    min-width: 0;
    flex: 0 1 auto;
    font-size: 14px;
    line-height: 1.2;
    font-weight: 600;
    color: var(--tt-fg-0, var(--text-primary));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
.sc-row__name-icon {
    font-size: 12px;
    opacity: .75;
}
.sc-row__chips {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}
.sc-row__chip,
.sc-row__vis {
    display: inline-flex;
    align-items: center;
    height: 18px;
    padding: 0 6px;
    border-radius: 999px;
    font-size: 12px;
    line-height: 1;
    white-space: nowrap;
}
.sc-row__chip {
    border: 1px solid rgba(255, 255, 255, 0.2);
    background: rgba(255, 255, 255, 0.06);
    color: var(--tt-fg-0, var(--text-primary));
    opacity: .9;
}
.sc-row__chip.is-strategy { border-color: rgba(38, 166, 154, 0.35); color: var(--tt-up); background: rgba(38, 166, 154, 0.12); }
.sc-row__chip.is-indicator,
.sc-row__chip.is-library { border-color: rgba(255, 255, 255, 0.25); }

.sc-row__vis.is-open {
    color: var(--tt-up);
    background: rgba(38, 166, 154, 0.10);
    border: 1px solid rgba(38, 166, 154, 0.28);
}
.sc-row__vis.is-protected,
.sc-row__vis.is-invite {
    color: var(--tt-down);
    background: rgba(239, 83, 80, 0.10);
    border: 1px solid rgba(239, 83, 80, 0.28);
}
.sc-row__vis.is-private {
    color: var(--tt-fg-0, var(--text-primary));
    opacity: .72;
    border: 1px solid var(--border-color);
    background: transparent;
}

.sc-row__meta {
    min-width: 0;
    margin-top: 2px;
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: 0;
    font-size: 12px;
    color: var(--tt-fg-0, var(--text-primary));
    opacity: .62;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.sc-row__author-avatar {
    display: inline-flex;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    overflow: hidden;
    flex: 0 0 16px;
    margin-right: 4px;
    vertical-align: middle;
}
.sc-row__author-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.sc-row__meta-item {
    display: inline;
}
.sc-row__meta-item--star {
    color: var(--tt-up);
}
.sc-row__meta-sep {
    opacity: .55;
    margin: 0 4px;
}
.sc-row__author a {
    color: inherit;
    text-decoration: none;
}
.sc-row__author a:hover {
    text-decoration: underline;
}

.sc-row__perf {
    min-width: 0;
    font-size: 12px;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 2px;
    text-align: right;
    white-space: nowrap;
}
.sc-row__perf--metrics {
    flex-direction: row;
    align-items: baseline;
    gap: 4px;
}
.sc-row__perf-net {
    font-size: 13px;
    line-height: 1.2;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.sc-row__perf-net.is-up { color: var(--tt-up); }
.sc-row__perf-net.is-dn { color: var(--tt-down); }
.sc-row__perf-net.is-na {
    color: var(--tt-fg-0, var(--text-primary));
    opacity: .62;
}
.sc-row__perf-trades,
.sc-row__perf-sep,
.sc-row__perf-sub,
.sc-row__perf-na {
    font-size: 11px;
    line-height: 1.2;
    color: var(--tt-fg-0, var(--text-primary));
    opacity: .62;
    white-space: nowrap;
}
.sc-row__perf-sub {
    max-width: 120px;
    overflow: hidden;
    text-overflow: ellipsis;
}
.sc-row__perf--na {
    justify-content: center;
}

.sc-row__acts {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    justify-self: end;
    min-width: 0;
    max-width: 100%;
    padding-inline-end: 14px;
    opacity: .35;
    transition: opacity .12s ease;
}
.sc-row:hover .sc-row__acts,
.sc-row:focus-within .sc-row__acts {
    opacity: 1;
}
.sc-row__acts--norun {
    gap: 8px;
}

.sc-btn-star {
    width: 22px;
    height: 22px;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    color: var(--tt-fg-0, var(--text-primary));
    opacity: .78;
    font-size: 14px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    cursor: pointer;
}
.sc-btn-star:hover,
.sc-btn-star.is-on {
    color: var(--tt-up);
    opacity: 1;
}
.sc-btn-star[disabled] {
    opacity: .38;
    cursor: not-allowed;
}
.sc-row__chips .sc-btn-star {
    width: 18px;
    height: 18px;
    margin-left: 2px;
    border: 0;
    border-radius: 4px;
    opacity: .66;
}
.sc-row__chips .sc-btn-star svg {
    width: 12px;
    height: 12px;
}
.sc-row__chips .sc-btn-star:hover,
.sc-row__chips .sc-btn-star.is-on {
    opacity: 1;
}

.sc-btn-run,
.sc-btn-ros {
    height: 24px;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    background: transparent;
    color: var(--tt-fg-0, var(--text-primary));
    font-size: 12px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 0 10px;
    white-space: nowrap;
    cursor: pointer;
}
.sc-btn-run svg,
.sc-btn-ros svg {
    width: 12px;
    height: 12px;
    flex-shrink: 0;
}
.sc-btn-run:hover,
.sc-btn-ros:hover {
    border-color: rgba(255, 255, 255, 0.45);
}
body:not(.dark-theme) .sc-btn-run:hover,
body:not(.dark-theme) .sc-btn-ros:hover {
    border-color: rgba(0, 0, 0, 0.35);
}
.sc-btn-run.is-disabled,
.sc-btn-run[disabled],
.sc-btn-ros.is-disabled,
.sc-btn-ros[disabled] {
    opacity: .44;
    pointer-events: none;
}
/* [ADD] 2026-08-09 — hover title on disabled Run-on-Server (inner btn blocks pointer events) */
.sc-ros-tip-wrap {
    display: inline-flex;
    max-width: 100%;
    min-width: 0;
    width: 100%;
    cursor: not-allowed;
    position: relative;
}
.sc-grid .ys-script-card__actions .sc-ros-tip-wrap {
    width: 100%;
    min-width: 0;
}
.ys-script-card__actions .sc-ros-tip-wrap {
    width: 100%;
}
.sc-btn-ros.is-on {
    color: var(--tt-up);
    border-color: rgba(38, 166, 154, 0.5);
    background: rgba(38, 166, 154, 0.12);
}
.sc-btn-ros.is-on.is-disabled,
.sc-btn-ros.is-on[disabled] {
    opacity: 1;
}

.sc-row__acts .sc-tr-act-more {
    width: 24px;
    height: 24px;
    font-size: 12px;
    line-height: 1;
}

.sc-row__pill {
    display: inline-flex;
    align-items: center;
    height: 18px;
    padding: 0 7px;
    border-radius: 999px;
    font-size: 12px;
    line-height: 1;
    border: 1px solid var(--border-color);
    white-space: nowrap;
}
.sc-row__pill.is-running {
    color: var(--tt-up);
    border-color: rgba(38, 166, 154, 0.5);
    background: rgba(38, 166, 154, 0.12);
}
.sc-row__pill.is-failed {
    color: var(--tt-down);
    border-color: rgba(239, 83, 80, 0.5);
    background: rgba(239, 83, 80, 0.12);
}
.sc-row__pill.is-starting,
.sc-row__pill.is-idle {
    color: var(--tt-fg-0, var(--text-primary));
    opacity: .75;
}

@media (hover: none) {
    .sc-row__acts { opacity: 1; }
}

@media (max-width: 768px) {
    /* [FIX] 2026-08-30 — strategy rows: Run on Server squeezed .sc-row__name to
     * 2-4 chars when chips + acts shared the title row. Stack chips under the
     * title and move actions to a full-width footer row so strategy vs indicator
     * rows get the same name width. */
    .sc-row {
        grid-template-columns: 22px minmax(0, 1fr);
        grid-template-areas:
            "icon main"
            "icon perf"
            "acts acts";
        gap: 6px 8px;
        padding: 9px 0 9px 10px;
    }
    .sc-row__icon {
        grid-area: icon;
        width: 22px;
        height: 22px;
    }
    .sc-row__icon svg {
        width: 14px;
        height: 14px;
    }
    .sc-row__info {
        grid-area: main;
        min-width: 0;
    }
    .sc-row__head {
        flex-direction: column;
        align-items: flex-start;
        gap: 4px;
        overflow: visible;
    }
    .sc-row__name {
        flex: 0 0 auto;
        width: 100%;
        max-width: 100%;
    }
    .sc-row__chips {
        flex-wrap: wrap;
        max-width: 100%;
    }
    .sc-row__perf {
        grid-area: perf;
        align-items: flex-start;
        text-align: left;
    }
    .sc-row__perf-sub { max-width: 100%; }
    .sc-row__acts {
        grid-area: acts;
        align-self: stretch;
        width: 100%;
        flex-wrap: wrap;
        justify-content: flex-end;
        padding-inline: 10px;
        padding-top: 2px;
    }
}

/* ===== [ADD] 2026-06-28 — TT entitlement upgrade modal (chart terminal style) ===== */
.tt-ent-backdrop {
    position: fixed;
    inset: 0;
    z-index: 10050;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 16px;
    background: rgba(0, 0, 0, 0.55);
}
.tt-ent-backdrop.is-open { display: flex; }
.tt-ent-card {
    width: 100%;
    max-width: 460px;
    overflow: hidden;
    color: var(--text-primary);
    background: var(--popover-bg);
    border: 1px solid var(--popover-border);
    border-radius: 4px;
    box-shadow: var(--popover-shadow);
}
.tt-ent-preview {
    position: relative;
    min-height: 132px;
    padding: 14px 16px 12px;
    background: #0a0a0a;
    border-bottom: 1px solid var(--popover-border);
}
body:not(.dark-theme) .tt-ent-preview {
    background: #131722;
}
.tt-ent-preview-label {
    margin: 0 0 10px;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.02em;
    color: rgba(255, 255, 255, 0.55);
    text-transform: uppercase;
}
.tt-ent-preview-grid {
    display: grid;
    gap: 4px;
    height: 88px;
}
.tt-ent-preview-grid--1 { grid-template-columns: 1fr; }
.tt-ent-preview-grid--2 { grid-template-columns: 1fr 1fr; }
.tt-ent-preview-grid--4 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.tt-ent-preview-grid--6 { grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 1fr 1fr; }
.tt-ent-preview-grid--8 { grid-template-columns: 1fr 1fr 1fr 1fr; grid-template-rows: 1fr 1fr; }
.tt-ent-preview-pane {
    position: relative;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 2px;
}
.tt-ent-preview-pane--active {
    border-color: rgba(41, 98, 255, 0.65);
    box-shadow: inset 0 0 0 1px rgba(41, 98, 255, 0.25);
}
.tt-ent-preview-pane--locked {
    border-style: dashed;
    border-color: rgba(255, 255, 255, 0.14);
    background: rgba(0, 0, 0, 0.25);
}
.tt-ent-preview-pane--locked::after {
    content: "";
    position: absolute;
    inset: 0;
    background: repeating-linear-gradient(
        -45deg,
        transparent,
        transparent 5px,
        rgba(255, 255, 255, 0.03) 5px,
        rgba(255, 255, 255, 0.03) 10px
    );
}
.tt-ent-mini-bars {
    position: absolute;
    right: 8px;
    bottom: 8px;
    left: 8px;
    display: flex;
    align-items: flex-end;
    gap: 3px;
    height: 52px;
}
.tt-ent-mini-bars i {
    display: block;
    flex: 1;
    min-width: 0;
    border-radius: 1px;
    background: var(--price-up);
}
.tt-ent-mini-bars i:nth-child(2),
.tt-ent-mini-bars i:nth-child(5),
.tt-ent-mini-bars i:nth-child(8) { background: var(--price-down); }
.tt-ent-mini-bars i:nth-child(1) { height: 38%; }
.tt-ent-mini-bars i:nth-child(2) { height: 52%; }
.tt-ent-mini-bars i:nth-child(3) { height: 44%; }
.tt-ent-mini-bars i:nth-child(4) { height: 62%; }
.tt-ent-mini-bars i:nth-child(5) { height: 48%; }
.tt-ent-mini-bars i:nth-child(6) { height: 70%; }
.tt-ent-mini-bars i:nth-child(7) { height: 56%; }
.tt-ent-mini-bars i:nth-child(8) { height: 40%; }
.tt-ent-preview-lock {
    position: absolute;
    top: 50%;
    left: 50%;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    color: rgba(255, 255, 255, 0.72);
    background: rgba(0, 0, 0, 0.55);
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 2px;
    transform: translate(-50%, -50%);
}
.tt-ent-preview-lock svg {
    width: 12px;
    height: 12px;
}
.tt-ent-preview-illustration {
    position: relative;
    display: grid;
    place-items: center;
    height: 88px;
    padding: 8px;
    overflow: hidden;
    background:
        radial-gradient(circle at 18% 18%, rgba(41, 98, 255, 0.32), transparent 55%),
        linear-gradient(180deg, rgba(21, 26, 34, 0.95), rgba(13, 17, 24, 0.95));
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 2px;
}
.tt-ent-preview-illustration-img {
    display: block;
    width: min(250px, 100%);
    max-width: 100%;
    height: auto;
    max-height: 52px;
    object-fit: contain;
    opacity: 0.94;
}
.tt-ent-preview-illustration-name {
    position: absolute;
    bottom: 8px;
    left: 10px;
    right: 44px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12px;
    font-weight: 600;
    line-height: 1.2;
    color: rgba(255, 255, 255, 0.9);
}
.tt-ent-preview-illustration .tt-ent-preview-lock {
    top: auto;
    right: 10px;
    bottom: 10px;
    left: auto;
    transform: none;
}
.tt-ent-body { padding: 14px 16px 0; }
.tt-ent-tier-row {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-bottom: 10px;
}
.tt-ent-tier {
    display: inline-flex;
    align-items: center;
    padding: 3px 8px;
    font-size: 12px;
    font-weight: 600;
    line-height: 1.2;
    border: 1px solid var(--popover-border);
    border-radius: 2px;
}
.tt-ent-tier--current {
    color: var(--text-secondary);
    background: var(--bg-input);
}
.tt-ent-tier--target {
    color: #fff;
    background: var(--accent-color);
    border-color: var(--accent-color);
}
.tt-ent-tier-arrow {
    font-size: 12px;
    color: var(--text-secondary);
}
.tt-ent-title {
    margin: 0 0 6px;
    font-size: 16px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--text-primary);
}
.tt-ent-desc {
    margin: 0 0 12px;
    font-size: 14px;
    line-height: 1.5;
    color: var(--text-secondary);
}
.tt-ent-quota {
    margin-bottom: 14px;
}
.tt-ent-quota-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 6px;
    font-size: 12px;
    color: var(--text-secondary);
}
.tt-ent-quota-track {
    height: 4px;
    overflow: hidden;
    background: var(--bg-input);
    border-radius: 2px;
}
.tt-ent-quota-fill {
    height: 100%;
    background: var(--accent-color);
    border-radius: 2px;
    transition: width 0.2s ease;
}
.tt-ent-quota-fill.is-over { background: var(--price-down); }
.tt-ent-quota-val {
    font-size: 12px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}
.tt-ent-actions {
    display: flex;
    gap: 8px;
    justify-content: flex-end;
    padding: 0 16px 14px;
}
.tt-ent-btn {
    padding: 8px 14px;
    font-size: 14px;
    line-height: 1.2;
    color: var(--text-primary);
    cursor: pointer;
    background: var(--bg-input);
    border: 1px solid var(--popover-border);
    border-radius: 4px;
    transition: background 0.15s ease, border-color 0.15s ease;
}
.tt-ent-btn:hover {
    background: var(--bg-hover);
}
.tt-ent-btn--primary {
    color: #fff;
    background: var(--accent-color);
    border-color: var(--accent-color);
}
.tt-ent-btn--primary:hover {
    background: #1e53e5;
    border-color: #1e53e5;
}
.tt-ent-btn[disabled] {
    opacity: 0.6;
    cursor: not-allowed;
}

/* Avatar ring — white halo in dark and light themes; PNG assets ship halo-free. */
:is(
    .nav-user-avatar,
    .nud-avatar-sm,
    .sc-author-avatar,
    .sc-row__author-avatar,
    .sc-comment__avatar,
    .ys-script-card__author-avatar,
    .ys-detail-author__avatar,
    #dmDrawer .dm-avatar,
    :is(#dmDrawer, .dm-profile-host) .dm-profile__avatar
) {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.85);
}

