/* StockBoard 自托管样式（无 Tailwind 依赖） */
:root {
  --bg: #0f172a;
  --bg-soft: #1e293b;
  --border: #334155;
  --text: #e2e8f0;
  --muted: #94a3b8;
  --primary: #3b82f6;
  --primary-hover: #2563eb;
  --danger: #dc2626;
  --up: #ef4444;
  --down: #22c55e;
}

* { box-sizing: border-box; }

/* 全局 toast：顶部居中，无论滚动到哪都在视口内，且离大多数操作按钮（卡头）更近；
   底部提示会和刚点的按钮隔开一整屏，容易被当成"没反应"。淡入淡出，pointer-events:none 不挡操作。 */
#app-toast {
  position: fixed; left: 50%; top: 14px; transform: translateX(-50%) translateY(-8px);
  background: var(--bg-soft); color: var(--text);
  border: 1px solid var(--primary); border-radius: 8px;
  padding: 9px 18px; font-size: 14px; z-index: 9999;
  box-shadow: 0 4px 16px rgba(0,0,0,.45);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .25s, transform .25s, visibility .25s;
  max-width: min(86vw, 480px); text-align: center;
}
#app-toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }

html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text); font-family: -apple-system, "PingFang SC", "Microsoft YaHei", "Segoe UI", sans-serif; font-size: 14px; line-height: 1.5; }
a { color: var(--primary); }
code { background: var(--bg); padding: 1px 6px; border-radius: 3px; font-family: ui-monospace, "Cascadia Mono", Menlo, monospace; font-size: 12px; border: 1px solid var(--border); }
input, select, textarea { background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 6px 10px; color: var(--text); font-size: 14px; font-family: inherit; }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--primary); }
textarea { width: 100%; resize: vertical; }
input[type="checkbox"] { width: 16px; height: 16px; vertical-align: middle; }

.muted { color: var(--muted); }
.small { font-size: 12px; }
.center { text-align: center; }
.mt-1 { margin-top: 4px; }
.mt-2 { margin-top: 8px; }
.mt-3 { margin-top: 16px; }
.mb-1 { margin-bottom: 4px; }
.mb-2 { margin-bottom: 8px; }
.mb-3 { margin-bottom: 16px; }
.flex { display: flex; align-items: center; }
/* .gap-1：HTML 里「勾选框 + 说明文字」多处用到，CSS 里没定义会让两者贴在一起 */
.gap-1 { gap: 4px; }
.gap-2 { gap: 8px; }
.wrap { flex-wrap: wrap; }
.items-center { align-items: center; }
.spacer { flex: 1; }

.up { color: var(--up); }
.down { color: var(--down); }
.flat { color: var(--muted); }

.btn { padding: 6px 12px; border-radius: 6px; font-size: 13px; cursor: pointer; border: 1px solid #475569; background: #334155; color: var(--text); transition: background .15s; text-decoration: none; }
.btn:hover { background: #475569; }
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--primary); border-color: var(--primary); color: white; }
.btn-primary:hover { background: var(--primary-hover); }
.btn-danger { background: var(--danger); border-color: var(--danger); color: white; }
.btn-sm { padding: 3px 10px; font-size: 12px; }
.btn-block { display: block; width: 100%; margin-bottom: 8px; }

.badge { display: inline-block; padding: 1px 8px; border-radius: 4px; font-size: 11px; }
.badge-bull { background: var(--up); color: white; }
.badge-bear { background: var(--down); color: white; }
.badge-neutral { background: #64748b; color: white; }
.badge-admin { background: #f59e0b; color: white; }
.badge-warn { background: #ef4444; color: white; }
.badge-ok { background: var(--up); color: white; }
.badge-pending { background: #eab308; color: #1e293b; }
.badge-rejected { background: #64748b; color: white; }

.card { background: var(--bg-soft); border: 1px solid var(--border); border-radius: 10px; padding: 16px; }
.card h3 { margin: 0 0 12px; font-size: 15px; }
/* 「消息源配置」里每个来源的小标题（邮件 / 微信），比 h3 弱一档 */
.msg-src-h { margin: 0 0 6px; font-size: 14px; }
.msg-src-h + .muted { margin-top: 0; }
.card-head { display: flex; align-items: center; margin-bottom: 12px; }
.card-head h3 { margin: 0; flex: 1; }
.kpi-num { font-size: 22px; font-weight: 700; margin-top: 4px; }
.kpi { padding: 12px 16px; }
/* 可点的 KPI 数字：虚线下划线 + pointer 表明可点 —— 触屏没有 hover，光靠 cursor 看不出来 */
.kpi-num.kpi-link { cursor: pointer; text-decoration: underline dotted; text-underline-offset: 4px; }
.kpi-num.kpi-link:hover { color: var(--primary); }
/* 持仓表「代码 / 名称」可点（点了弹 K 线）。虚下划线 = 可点的视觉暗示。
   ⚠️ 不能只靠 title 属性 —— 触屏没有 hover，必须让"这格能点"看得出来。 */
.kl-link { cursor: pointer; text-decoration: underline dotted; text-underline-offset: 3px; }
.kl-link:hover { color: var(--primary); }

table { width: 100%; border-collapse: collapse; }
th, td { padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--border); font-size: 13px; }
th { color: var(--muted); font-weight: 500; font-size: 12px; }

.grid { display: grid; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.grid-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
@media (max-width: 900px) {
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .grid-5 { grid-template-columns: repeat(2, 1fr); }
  .grid-6 { grid-template-columns: repeat(2, 1fr); }
  .grid-3 { grid-template-columns: repeat(1, 1fr); }
  /* 2 列也塌成 1 列：手机上每格只剩 ~150px，里面的 input（浏览器默认最小 ~170px）
     根本放不下 → 撑破容器、右侧按钮被挤到屏幕外点不到（推送设置的 👁📋✏ 就是这么丢的）。 */
  .grid-2 { grid-template-columns: repeat(1, 1fr); }
}
/* 真实持仓汇总 KPI（#hold-kpi-row）：手机上和模拟账户汇总一样 2 卡一行。
   它是 grid-3，默认会塌成 1 列 → 6 个指标每卡独占一行太浪费；
   这里按 id 覆盖成 2 列（桌面端仍保持 3+3 不变）。 */
@media (max-width: 900px) {
  #hold-kpi-row { grid-template-columns: repeat(2, 1fr); }
}

/* 设置项「紧凑行」：给"一行里都是短项"（开关 / 时间 / 按钮）的组用。
   为什么不用 .grid-3：手机上 grid 会塌成 1 列，于是每个短项独占一行、整块被拉得很长。
   flex + wrap 只在**放不下**时才换行，手机端能省掉一半行数，桌面端排布与原来一致。 */
.settings-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 22px; }
.settings-row > .form-row { flex: 0 1 auto; min-width: 0; }
.settings-row > button, .settings-row > .btn { flex: 0 0 auto; }
@media (max-width: 600px) {
  .settings-row { gap: 8px 14px; }
}

.nav { display: flex; align-items: center; gap: 6px; padding: 10px 14px; margin: 14px; flex-wrap: wrap; }
.nav .brand { font-size: 17px; font-weight: 700; margin-right: 10px; }
.nav-item { padding: 5px 12px; border-radius: 6px; cursor: pointer; font-size: 13px; user-select: none; }
.nav-item:hover:not(.active) { background: rgba(255,255,255,0.05); }
.nav-item.active { background: #334155; }

/* 顶栏右侧「用户名 + 角色徽章 + 退出」= 一个整体。
   `.nav` 是 flex-wrap:wrap → 不包起来的话，窄屏上一行放不下时会被拆开
   （用户名留在第一行尾、退出按钮掉到第二行最左边），看着像两个不相干的控件。
   这里让它整组不拆行、且 `margin-left:auto` 保证"换到下一行时依然贴右"。 */
.nav-me {
  display: flex; align-items: center; gap: 6px;
  margin-left: auto;          /* 独占一行时也靠右 */
  white-space: nowrap;        /* 组内绝不折行 */
  max-width: 100%;            /* 用户名再长也不撑破卡片 */
}
/* 用户名最长 32 字符（32 个中文 ≈ 416px）→ 顶栏**不整段显示**：
   限宽 + 省略号，角色徽章与「退出」永远完整可见（顶栏只是身份提示，
   完整用户名在「用户」页）。想看全名：桌面悬停（title），触屏点一下 →
   跳「用户」页 —— 触屏没有 hover，所以点整段名字都能跳，别只依赖 title。 */
.nav-me #nav-username {
  display: inline-block; max-width: 9em; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom;
  cursor: pointer;
}

.page { margin: 0 14px 30px; }

.view { display: none; }
.view.active { display: block; }
#login-view.active { display: flex; align-items: center; justify-content: center; min-height: 100vh; }

.login-card { width: 100%; max-width: 360px; }
.login-card h1 { margin: 0 0 4px; font-size: 22px; text-align: center; }
.login-card > p { text-align: center; margin: 0 0 18px; }
.login-card .form-row { margin-bottom: 12px; }
.login-card .form-row label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 4px; }
.login-card input { width: 100%; }
.login-error { color: var(--danger); font-size: 12px; min-height: 16px; margin: 0 0 8px; text-align: center; }

.form-row { display: flex; flex-direction: column; gap: 4px; }
.form-row.inline { flex-direction: row; align-items: center; gap: 8px; }
.form-row > span { font-size: 12px; color: var(--muted); }
/* inline 行里的输入控件默认 min-width:auto（≈170px）不肯收缩，
   手机窄屏会把标签挤出屏幕 —— 允许收缩并让标签保持完整 */
.form-row.inline > input, .form-row.inline > select { flex: 0 1 auto; min-width: 0; width: auto; }
/* ⚠️ 上面那条也命中了 checkbox —— 特异性 (0,2,1) 高于 `input[type="checkbox"]` 的 (0,1,1)，
   于是 `width: 16px` 被 `width: auto` 顶掉、`min-width: 0` 又允许压到 0，行内空间不够时
   勾选框会被 flex 挤压，**同一行的两个 checkbox 大小就不一样**（文本越长压得越扁，
   实测「定时分析完成后推送消息（…）」比它后面那个小一圈）。钉回原尺寸且不伸缩。
   与 `.ctl-group > input[type="checkbox"]` 是同一个坑的两处补丁。 */
.form-row.inline > input[type="checkbox"],
.form-row.inline > input[type="radio"] {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin: 0;
}
/* AI 设置四控件（#ai-limits-grid）：防挤压 —— 标签占自然宽度、永不折成两行
   （极窄时省略号），输入框吃剩余空间但不小于可用的最小宽度 */
#ai-limits-grid .form-row.inline > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 0 1 auto; min-width: 0; }
#ai-limits-grid .form-row.inline > input,
#ai-limits-grid .form-row.inline > select { flex: 1 1 0; min-width: 34px; }

.panel-detail { padding: 8px 12px; border: 1px dashed #475569; border-radius: 6px; background: rgba(51, 65, 85, .35); min-height: 20px; }

/* 搜索选择器：输入文字时下拉显示候选 */
.search-picker { position: relative; }
.search-picker > input { width: 100%; }
.search-suggest {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 30;
  max-height: 320px; overflow-y: auto;
  background: var(--card, #1e293b); border: 1px solid #475569; border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0,0,0,.4); margin-top: 2px;
}
.search-suggest .sg-item {
  padding: 6px 10px; cursor: pointer; display: flex; gap: 8px; align-items: center;
  border-bottom: 1px solid rgba(71,85,105,.4); font-size: 13px;
}
.search-suggest .sg-item:last-child { border-bottom: none; }
.search-suggest .sg-item:hover { background: rgba(96,165,250,.15); }
.search-suggest .sg-code { color: #94a3b8; min-width: 80px; font-family: ui-monospace,monospace; }
.search-suggest .sg-name { flex: 1; }
.search-suggest .sg-tag { font-size: 11px; padding: 1px 6px; border-radius: 10px; background: rgba(96,165,250,.2); color: #93c5fd; }
.search-suggest .sg-tag.fund { background: rgba(251,191,36,.2); color: #fbbf24; }
.search-suggest .sg-tag.hk { background: rgba(239,68,68,.18); color: #fca5a5; }
.search-suggest .sg-tag.us { background: rgba(16,185,129,.18); color: #6ee7b7; }
.search-suggest .sg-mini { font-size: 10px; padding: 0 4px; }
.search-suggest .sg-noprice { opacity: .82; }
.search-suggest .sg-noprice:hover { opacity: 1; }
.search-suggest .sg-empty { padding: 10px; color: var(--muted,#94a3b8); font-size: 12px; text-align: center; }
/* 关键词高亮 / 键盘选中态 / 现价与涨跌幅 */
.search-suggest .sg-hl { color: #e2e8f0; background: rgba(96,165,250,.28); border-radius: 2px; padding: 0 1px; font-weight: 600; }
.search-suggest .sg-item.active { background: rgba(96,165,250,.18); }
.search-suggest .sg-price { font-family: ui-monospace,monospace; color: #e2e8f0; margin-left: auto; }
.search-suggest .sg-chg { font-family: ui-monospace,monospace; font-size: 11px; margin-left: 6px; }
.search-suggest .sg-chg.up { color: #ef4444; }
.search-suggest .sg-chg.down { color: #22c55e; }

.kline-chart { width: 100%; height: 380px; background: var(--bg); border-radius: 6px; }

/* 子标签：组合页内细分 tab */
.sub-tabs {
  display: flex; gap: 4px; margin-bottom: 12px;
  border-bottom: 1px solid var(--border);
  padding-bottom: 6px;
  flex-wrap: wrap;
}
.sub-tab {
  padding: 6px 14px; border-radius: 6px; cursor: pointer;
  font-size: 13px; user-select: none;
  background: transparent; border: 1px solid transparent;
  color: var(--muted);
}
.sub-tab:hover { background: rgba(255,255,255,0.05); color: var(--text); }
.sub-tab.active { background: #334155; color: var(--text); border-color: #475569; }

/* 卡片内的小切换（曲线视图/口径）：比 .sub-tab 更紧凑，塞在图表上方一行里 */
.mini-tabs { display: flex; gap: 4px; flex-wrap: wrap; }.mini-tab {
  padding: 3px 10px; border-radius: 999px; cursor: pointer; font-size: 12px;
  user-select: none; background: transparent;
  border: 1px solid var(--border); color: var(--muted);
}
.mini-tab:hover { background: rgba(255,255,255,0.05); color: var(--text); }
.mini-tab.active { background: #334155; color: var(--text); border-color: #475569; }
.mini-sep { width: 1px; background: var(--border); margin: 2px 6px; }

.sub-view { display: none; }
.sub-view.active { display: block; }

/* 顶部说明条：解释每个视图是什么 */
.hint {
  background: rgba(59,130,246,.08);
  border-left: 3px solid var(--primary);
  padding: 8px 12px; margin-bottom: 12px;
  border-radius: 0 6px 6px 0;
  font-size: 13px; color: #cbd5e1;
}
.hint strong { color: var(--primary); }
.hint code { font-size: 12px; }

/* 通用模态框 */
.modal {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(0,0,0,0.55);
  display: none; align-items: center; justify-content: center;
  padding: 16px;
}
.modal.active { display: flex; }
.modal-card {
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: 10px;
  width: 100%; max-width: 640px;
  max-height: 86vh;
  display: flex; flex-direction: column;
  box-shadow: 0 20px 50px rgba(0,0,0,.5);
}
.modal-card.wide { max-width: 880px; }
.modal-head {
  display: flex; align-items: center;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}
.modal-head h3 { margin: 0; font-size: 15px; flex: 1; }
.modal-head .x {
  cursor: pointer; font-size: 20px; line-height: 1;
  color: var(--muted); padding: 0 6px;
}
.modal-head .x:hover { color: var(--text); }
.modal-body {
  padding: 14px 16px; overflow-y: auto; flex: 1;
  /* 必须限高，否则长内容（如 25 行的 AI 报告表格）会超出视口：
     .modal 是 position:fixed 且 align-items:center，内容比视口高时
     会**上下同时被切掉**（露出中间一段表格），而且 fixed 层不产生滚动条，
     用户永远滚不到剩下的部分。限高后改为弹窗内部滚动。 */
  max-height: calc(100vh - 32px);
}
.modal-foot {
  padding: 10px 16px;
  border-top: 1px solid var(--border);
  display: flex; gap: 8px; justify-content: flex-end;
}

/* 导入弹窗用的轻量容器（没有 head/body 分区）：约束宽度 + 内部滚动 */
.modal-box {
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: 10px;
  width: 100%; max-width: 860px;
  max-height: 88vh;
  overflow-y: auto;
  padding: 16px;
  box-shadow: 0 20px 50px rgba(0,0,0,.5);
}

/* AI 报告 Markdown 渲染 */
.md-content { font-size: 14px; line-height: 1.7; }
.md-content h1, .md-content h2, .md-content h3 {
  margin: 14px 0 8px; color: var(--text); font-weight: 600;
}
.md-content h1 { font-size: 18px; border-bottom: 1px solid var(--border); padding-bottom: 6px; }
.md-content h2 { font-size: 16px; }
.md-content h3 { font-size: 14px; color: var(--primary); }
.md-content p { margin: 6px 0; }
.md-content ul, .md-content ol { padding-left: 22px; margin: 6px 0; }
.md-content li { margin: 2px 0; }
.md-content strong { color: #fde68a; }
.md-content em { color: #fca5a5; }
.md-content code { background: var(--bg); padding: 1px 5px; border-radius: 3px; }
.md-content pre {
  background: var(--bg); border: 1px solid var(--border);
  border-radius: 6px; padding: 10px; overflow-x: auto;
  font-size: 12px;
}
.md-content blockquote {
  border-left: 3px solid var(--primary);
  padding: 4px 12px; margin: 8px 0;
  background: rgba(59,130,246,.06);
  color: var(--muted);
}
.md-content table {
  width: 100%; margin: 8px 0;
  border-collapse: collapse;
}
.md-content th, .md-content td {
  border: 1px solid var(--border); padding: 6px 10px;
}
.md-content th { background: var(--bg); }

/* AI 报告折叠/展开：默认只显示 1 行，点按钮展开全文 */
.report-box { position: relative; }
.report-body.collapsed { max-height: 1.7em; overflow: hidden; }
.report-toggle { display: block; margin: 10px auto 0; }

/* 策略编辑器的函数/变量列表 */
.fn-list {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin: 6px 0;
}
.fn-chip {
  background: rgba(59,130,246,.15);
  border: 1px solid rgba(59,130,246,.35);
  color: #93c5fd;
  padding: 2px 8px; border-radius: 4px;
  font-family: ui-monospace, "Cascadia Mono", monospace;
  font-size: 12px; cursor: pointer;
}
.fn-chip:hover { background: rgba(59,130,246,.3); }

/* 函数/变量说明面板：手机端没有鼠标悬停，`title` 完全看不到，
   所以把说明渲染成一个可展开的列表（桌面端也能用，比逐条悬停省事）。 */
.fn-help {
  margin: 4px 0 2px; padding: 8px 10px;
  border: 1px solid var(--border); border-radius: 6px;
  background: var(--bg); max-height: 280px; overflow: auto;
}
.fn-help-row { display: flex; gap: 10px; padding: 3px 0; font-size: 12px; align-items: baseline; }
.fn-help-row code {
  flex: 0 0 auto; white-space: nowrap;
  color: #93c5fd; font-family: ui-monospace, "Cascadia Mono", monospace;
}
.fn-help-row span { color: var(--muted); }
@media (max-width: 600px) {
  /* 窄屏放不下"签名 + 说明"并排 → 说明换到签名下面 */
  .fn-help-row { flex-direction: column; gap: 2px; }
  .fn-help-row code { white-space: normal; }
}
/* 示例行（帮助区里的内置/自定义示例）：标签 + 看板开关在第一行，
   详细说明**另起一行**（用户指定）；整体仍紧凑（小行距）。 */
.fn-help-row.ex-row { display: block; padding: 2px 0; line-height: 1.75; }
.fn-help-row.ex-row code { white-space: nowrap; }
.fn-help-row.ex-row span { display: block; margin-left: 0; color: var(--muted); }
@media (max-width: 600px) {
  .fn-help-row.ex-row { flex-direction: row; }   /* 不吃掉上面的通用列布局 */
}
/* 配置与数据同栏时的分隔线 */
.exb-hr { border: 0; border-top: 1px solid var(--border); margin: 10px 0; }
.fn-chip.var { background: rgba(34,197,94,.12); border-color: rgba(34,197,94,.35); color: #86efac; }
.fn-chip.example { background: rgba(251,191,36,.12); border-color: rgba(251,191,36,.35); color: #fcd34d; }
/* 「我的示例」chip 的删除角标：触屏也能点（不依赖 title），阻止冒泡避免触发填入表达式 */
.ex-del {
  margin-left: 6px; padding: 0 4px; border-radius: 4px;
  color: #fca5a5; cursor: pointer; font-weight: 700; font-style: normal;
}
.ex-del:hover { background: rgba(239,68,68,.2); color: #fecaca; }

/* ---------- 策略表单 ---------- */
/* 「基本信息」三列：名称/动作/数量；手机端塌 1 列（显式进断点）。
   当前只用于「存我的示例」内联面板。 */
.strat-grid { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 10px 12px; }
@media (max-width: 900px) { .strat-grid { grid-template-columns: 1fr; } }
/* 定投表单：手机上左右各留 14px 太占宽 → 收到 10px；底部间隔同步收一点 */
@media (max-width: 900px) {
  .invest-form { padding: 10px 10px 2px; margin-bottom: 14px; }
}
/* 表达式框：✕ 清空按钮放在框**内部右上角**（用户要求，不放框下面占一行） */
.expr-wrap { position: relative; }
.expr-wrap textarea { padding-right: 38px; }
.expr-clear {
  position: absolute; top: 6px; right: 6px; width: 26px; height: 26px;
  border-radius: 6px; border: 1px solid var(--border); background: transparent;
  color: var(--muted); cursor: pointer; font-size: 12px; line-height: 1; padding: 0;
}
.expr-clear:hover { color: #f87171; border-color: rgba(239,68,68,.5); background: rgba(239,68,68,.08); }
/* 「我的示例」chip：在金色示例底色上再加实线描边，与内置示例区分 */
.fn-chip.example.mine { border-color: rgba(251,191,36,.7); background: rgba(251,191,36,.18); font-weight: 600; }
/* 「存为我的示例」面板：虚线框内嵌表单 */
.strat-save-panel { border: 1px dashed var(--border); border-radius: 10px; padding: 10px 12px; }

/* 「我的账号」里的一行：项名 + 当前值（只读）+ 右侧「修改」按钮。
   手机端把值挤到下一行也要保证按钮可见（按钮 flex:0 0 auto 不参与收缩）。 */
.acct-row {
  display: flex; align-items: center; gap: 8px 12px; flex-wrap: wrap;
  padding: 10px 0; border-bottom: 1px solid var(--border);
}
.acct-row:last-child { border-bottom: none; padding-bottom: 0; }
.acct-label { flex: 0 0 72px; font-size: 12px; color: var(--muted); }
.acct-value { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.acct-row > button { flex: 0 0 auto; margin-left: auto; }

/* 调度器面板：**一个调度作业一块**（名称/说明/调度时间/下次运行），
   与下面的运行日志分开，避免"所有调度器的执行信息混在一张表里看不懂"。 */
/* 调度作业：作业之间是互斥选项（一排选项 + 只显示当前那块），避免平铺撑长面板 */
.sched-job-tabs { margin-bottom: 10px; }
.sched-job-tabs .mini-tab { padding: 4px 12px; font-size: 13px; }
.sched-job-view { display: none; }
.sched-job-view.active { display: block; }

.sched-job {
  border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 12px; margin-bottom: 10px; background: var(--bg);
}
.sched-job-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
/* 折叠时只显示这一行摘要（作业一多才不至于刷满屏） */
.sched-job-brief { margin-top: 4px; }
/* 展开后的完整说明：左侧一条竖线，视觉上属于"附加信息" */
.sched-job-desc {
  margin-top: 6px; padding: 6px 10px; line-height: 1.6;
  border-left: 2px solid var(--border); color: var(--muted);
}
/* 「说明」按钮：长得像链接，但它是真按钮（手机端能点、有可点区域） */
.btn-link {
  background: none; border: none; padding: 2px 4px; margin-left: auto;
  color: #60a5fa; cursor: pointer; font-size: 12px; font-family: inherit;
  text-decoration: underline dotted; flex: 0 0 auto;
}
.btn-link:hover { color: #93c5fd; }
.sched-job-meta {
  display: flex; gap: 6px 24px; flex-wrap: wrap;
  margin-top: 6px; font-size: 12px; color: var(--muted);
}
/* 通用「控件行」：一行里放 [说明文字] + [输入框] + [按钮]，
   要点是**输入框和按钮永远同一行**（手机上也一样），状态文字窄屏另起一行。
   调度时间、注册名额都用它。 */
.ctl-row {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 6px 12px; margin-top: 8px;
}
.ctl-group { display: flex; flex: 0 0 auto; align-items: center; gap: 8px; min-width: 0; }
/* 行内小标签（"每天"/"注册名额"）：不许换行、可以被压缩 ——
   这样它再长也不会把 input / 按钮挤到第二行去 */
.ctl-label { white-space: nowrap; flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.ctl-group > input { width: 92px; min-width: 0; }
/* 护栏：.ctl-group 按"时间/数字输入框"设计（input 钉 92px 宽），勾选框放进来
   会被拉成长条、挤掉同行内容 —— 这里把 checkbox 显式钉回原尺寸且不伸缩 */
.ctl-group > input[type="checkbox"] { width: 16px; height: 16px; flex: 0 0 auto; }
/* 原生时间选择器：点一下弹系统时间选择器（手机上是滚轮/表盘），值天然是 HH:MM。
   用 min-width 而不是固定 width —— en-US 这类 12 小时制会渲染成 "04:00 PM"，固定宽度会截断。 */
.ctl-group > input[type="time"] {
  width: auto; min-width: 118px; flex: 0 1 auto;
  color-scheme: dark;      /* 时钟图标/选择器跟深色主题一致，否则是黑图标看不见 */
}
.ctl-group > input[type="time"]::-webkit-calendar-picker-indicator {
  cursor: pointer; opacity: .7;
}
.ctl-group > input[type="time"]::-webkit-calendar-picker-indicator:hover { opacity: 1; }
.ctl-status { flex: 1 1 auto; min-width: 0; }
/* 状态为空时**不占行**：flex-basis:100% 的空元素会白吃一行的 row-gap，
   看上去就是"平白多出一段空白"（用户反馈"不美观"的来源之一） */
.ctl-status:empty { display: none; }
/* 管理页两处"说明 + 现状"（注册名额 / 新闻关注默认上限）：文字较长，挤同一行
   会被压缩换行 → 另起一行、左对齐。
   ⚠️ 选择器必须写到 `.ctl-row > .ctl-status.ctl-hint` 这个权重，才压得住
      下面 600px 媒体查询里的 `.ctl-status { flex: 1 1 100% }`。 */
.ctl-row > .ctl-status.ctl-hint { flex: 1 1 100%; text-align: left; }
@media (max-width: 600px) {
  /* 窄屏让"下次运行/上次运行"各占一行，别挤成一条读不清的串 */
  .sched-job-meta { flex-direction: column; gap: 3px; }
  /* 强制不换行：保证"时间选择 + 操作按钮"永远同一行 */
  .ctl-group { flex: 1 1 100%; flex-wrap: nowrap; }
  .ctl-group > input { flex: 1 1 auto; width: auto; }
  .ctl-group > input[type="time"] { flex: 0 1 auto; width: auto; min-width: 104px; }
  .ctl-status { flex: 1 1 100%; }
  /* 站点开关的「邮箱验证码 + 发送验证码」：上面那条 `.ctl-group { flex: 1 1 100% }`
     会让组独占整行、把按钮顶下去 → 收回本行：组不占满、数字框钉 84px。
     ⚠️ 必须限定 `#svc-code-row`：改全局会把调度时间、注册名额那些行一起弄坏。 */
  #svc-code-row > .ctl-group { flex: 0 1 auto; }
  #svc-code-row .ctl-group > input { flex: 0 0 auto; width: 84px; }
}

/* 手动恢复命令说明：单独给一条左侧细边 —— "出事时才回头找"的信息，
   平日不抢眼、要找时一眼能定位；行高放宽是因为里面混了 <code>。 */
.svc-manual {
  border-left: 3px solid var(--border);
  padding: 2px 0 2px 10px;
  line-height: 1.8;
}
/* ⚠️ 千万别给这里的 code 加 `white-space: nowrap`：resume 命令比窄屏卡片还宽，
   nowrap 之后没有断点 → 顶破卡片边框。
   `word-break: break-all` 是必须的：命令里没有空格可断（长路径 + 长参数连成一片）。 */
.svc-manual code {
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-all;
}
/* 同一条护栏推广到整张卡：说明文字里的长 token（命令 / URL / 端点名）一律允许
   在任意位置断行 —— 宁可折成两行，也不许把卡片撑破、让手机端出现横向滚动。 */
#adm-card-svc p { overflow-wrap: anywhere; }

/* 站点开关卡里「回源带宽」那一行的范围选择器：推到行尾（与系统监控的时间范围同一档位）。
   ⚠️ 用类而不是 inline style —— 行内 flex 会压过窄屏媒体查询（硬约束 11）。
   窄屏归 0：那时整行会被挤成两行，选择器贴左跟文字排在一起才正常。 */
.svc-bw-range { margin-left: auto; }
@media (max-width: 600px) {
  .svc-bw-range { margin-left: 0; }
}

/* 「配置降级模式 / 熔断动作」两个下拉：用 `.ctl-group` 原子组（宽度收缩到内容宽，紧凑）；
   `.form-row.inline` 是块级 flex、占满整行，右边空一大片。max-width 只是兜底。
   ⚠️ 别顺手改全站 `.ctl-group > select` —— 只是本卡的下拉需要这道上限。 */
#adm-card-svc .ctl-group > select { max-width: 260px; }

/* 卡片内的四级小标题。与 .msg-src-h 同款外观，但名字通用 ——
   别往新卡片里借 msg-src-h（那是消息源专用名）。 */
.card-sub-h { margin: 14px 0 6px; font-size: 14px; }

/* 小标题行：标题在左、本小节的操作按钮在右（如看板「去策略页管理」）。
   窄屏 wrap → 按钮换到下一行。 */
.card-sub-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 14px 0 6px; }
.card-sub-head .card-sub-h { margin: 0; flex: 1; min-width: 0; }

/* 「流量控制」卡内的两块（站点开关 / 流量防护）：各给一圈边框区分。
   ⚠️ 只借边框 + 圆角区分，别再套 `.card`（会双层内边距 + 标题字号跳档）。
   ⚠️ 第一块的 h4 靠 `:first-child` 把上外边距归零。 */
.sub-card {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px;
  margin-top: 12px;
  min-width: 0;             /* 窄屏/flex 子项防溢出 */
}
.sub-card > .card-sub-h:first-child { margin-top: 0; }

/* 「熔断阈值被自动抬高」的红字提示：必须能任意断行（长句窄屏会顶破卡片）；
   空的时候靠 :empty 收起，不白占一行。 */
#bwg-clamp-note { color: #fca5a5; overflow-wrap: anywhere; }
#bwg-clamp-note:empty { display: none; }

@media (max-width: 600px) {
  /* 手机上卡片本身已经只有 ~320px 内容宽，双层内边距太挤 → 收一档 */
  .sub-card { padding: 10px; }
  /* 流量防护（手机端）：标签不省略（nowrap + overflow:visible）+ 数字框收窄到 64px
     + flex-wrap 兜底 —— 放得下就一行，放不下才折行，绝不溢出。
     ⚠️ 作用域限定在 #svc-box-bwguard：站点开关那几行（时长/验证码）沿用原口径，别顺手改。 */
  #svc-box-bwguard .ctl-label {
    white-space: nowrap; overflow: visible; text-overflow: clip; flex: 0 1 auto;
  }
  #svc-box-bwguard .ctl-group { flex-wrap: wrap; }
  #svc-box-bwguard .ctl-group > input { flex: 0 0 auto; width: 64px; }
}

/* 策略校验结果 */
.validate-ok { color: #86efac; font-size: 12px; }
.validate-err { color: #fca5a5; font-size: 12px; white-space: pre-wrap; }

/* 搜索弹窗内的市场筛选 chip */
.market-chips { display: flex; gap: 6px; margin-bottom: 10px; flex-wrap: wrap; }
.market-chip {
  padding: 3px 10px; border-radius: 14px;
  font-size: 12px; cursor: pointer;
  background: rgba(255,255,255,0.05);
  border: 1px solid var(--border);
  color: var(--muted);
  user-select: none;
}
.market-chip.active {
  background: var(--primary);
  border-color: var(--primary);
  color: white;
}

/* 秘钥字段的状态小字（· 已设置 · 清空）：清空做成小文字链，不再占一个按钮位 */
#my-webhook-hint a, #my-pushdeer-hint a {
  color: var(--danger); text-decoration: underline; cursor: pointer;
}

/* 推送通道：一个通道一块（启用开关在最前，凭据在后），块间虚线分隔。
   「清空」这类只属于某一栏的操作留在该栏内部，全局的「保存/测试」才放卡片最后。 */
.channel-block + .channel-block {
  margin-top: 12px; padding-top: 12px;
  border-top: 1px dashed var(--border);
}

/* 秘钥显示切换按钮 */
.key-wrap { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
/* min-width:0 是关键：input 默认 min-width:auto（≈170px），不许收缩就会把按钮顶出容器 */
.key-wrap input { flex: 1 1 160px; min-width: 0; font-family: ui-monospace, monospace; }
.key-wrap .btn { padding: 4px 8px; font-size: 12px; }
@media (max-width: 600px) {
  /* 触屏：这几个图标按钮原来只有 4px/8px padding，手指点不准 → 加大到 40×32 以上 */
  .key-wrap .btn { min-width: 42px; min-height: 34px; padding: 6px 10px; font-size: 15px; }
}

/* 加载 / 错误状态 */
.loading { padding: 16px; text-align: center; color: var(--muted); font-size: 13px; }
.empty-state { padding: 24px; text-align: center; color: var(--muted); font-size: 13px; }

/* 紧凑表格：用于自选股列表等 */
.table-compact th, .table-compact td { padding: 6px 8px; font-size: 12px; }

/* 定投表单：展开后必须是看得见边界的区域（浅底色 + 边框 + 内边距），
   否则「保存计划」和表格筛选框贴在一起分不清；「保存计划」一行加虚线划出操作区。 */
.invest-form {
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px 4px;
  margin-bottom: 18px;
}
.invest-form > .settings-row { margin-bottom: 10px; }
.invest-form > .form-row.inline {
  margin-top: 2px;
  padding: 10px 0 8px;
  border-top: 1px dashed var(--border);
}
/* 定投两张表（计划 / 执行记录）：一行一条，单元格一律 nowrap；
   过长的「计划/标的」「状态」「备注」用 .inv-clip 省略号截断、title 悬停看全文；
   外层 .invest-scroll 兜底横向滚动。手机端全局 table 是 display:block 横滚，
   此时放开 clip 让横向滚动看到全文（触屏没有 hover，title 无效）。 */
.invest-scroll { overflow-x: auto; }
.invest-tbl th, .invest-tbl td { white-space: nowrap; }
.invest-tbl .inv-act { white-space: nowrap; }
.invest-tbl .inv-clip {
  max-width: 230px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 「计划」列单独再窄一档（200px）：它是最长的一列，独立成列后给别的列让点宽度 */
.invest-tbl .inv-clip.inv-sm { max-width: 200px; }
@media (max-width: 900px) {
  .invest-tbl .inv-clip { max-width: none; }
}

/* 表格工具条：筛选 + 分页（成交记录 / 三类持仓 / 看板三卡共用） */
.table-tools {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin-bottom: 10px;
}
.table-tools .tt-filter {
  flex: 1; min-width: 160px; max-width: 320px;
}
.table-tools .tt-sel { max-width: 130px; }
.table-tools .tt-right {
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
}
.table-tools .tt-count { white-space: nowrap; }
.table-tools input[type="number"] { width: 62px; }

/* 搜索下拉：加宽 + 抬高层级，避免被后面的卡片/表格盖住 */
.search-picker { position: relative; }
.search-picker > input { width: 100%; padding-right: 26px; }
.search-suggest { z-index: 60; }
/* 选择器右侧的「×」：一键回到「全部持仓」（清空选择） */
.search-picker .picker-clear {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  cursor: pointer; color: var(--muted, #94a3b8); font-size: 16px; line-height: 1;
  padding: 2px 5px; border-radius: 4px; user-select: none;
}
.search-picker .picker-clear:hover { background: rgba(255,255,255,0.08); color: var(--text, #e2e8f0); }

/* 真实持仓添加表单：flex+wrap 自适应。原先 grid-6 在手机塌成 2 列后
   span 输入框被拉满整行（用户反馈"输入框/成本价特别长"）——
   这里每个输入给 max-width 上限，手机端一行放 2~3 个、宽度合理。 */
.hold-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.hold-form > input, .hold-form > select, .hold-form > .search-picker { min-width: 0; }
.hold-form .hold-pick { flex: 2 1 200px; max-width: 420px; }
.hold-form #hold-market { flex: 0 1 92px; }
.hold-form #hold-shares, .hold-form #hold-cost { flex: 1 1 100px; max-width: 170px; }
.hold-form #hold-note { flex: 1 1 140px; max-width: 260px; }
.hold-form > .btn { flex: 0 0 auto; }
/* 基金买入单位切换（金额/份数），只在选中场外基金时显示 */
#sim-unit-toggle a {
  margin-left: 8px;
  font-size: 11px;
  font-weight: 400;
  opacity: .6;
  text-decoration: underline;
}
#sim-unit-toggle a.active { font-weight: 700; opacity: 1; text-decoration: none; }

@media (max-width: 600px) {
  .hold-form { gap: 6px; }
  .hold-form .hold-pick { flex-basis: 100%; max-width: none; }   /* 标的独占一行 */
}

/* 模拟账户「卖出」面板（数量/价格/卖出/取消，grid grid-4）手机端适配：
   grid-4 在 ≤900px 是 2 列，每格 ~165px 放不下 input 浏览器默认宽（~170px+）→ 溢出；
   .form-row 是 flex column、默认 align-items:stretch → 按钮被拉成整格宽显得巨长。
   → input 铺满格子并可收缩；按钮收回自然宽度（留 96px 触屏最小宽）。 */
@media (max-width: 900px) {
  #trade-sell-panel .form-row > input { width: 100%; min-width: 0; }
  #trade-sell-panel .form-row > .btn { align-self: flex-start; min-width: 96px; }
}

/* 批量导入：名称命中多个标的的行 —— 琥珀色高亮「框出来」+ 行内候选切换链接。
   不把候选拆成多行：多行要多删 N-1 行、还容易忘删导致重复导入。 */
#import-tbody tr.imp-ambig > td { background: rgba(245, 158, 11, .08); }
#import-tbody tr.imp-ambig > td:first-child { border-left: 3px solid #f59e0b; }
.imp-alts { margin-top: 4px; font-size: 12px; line-height: 1.6; }
.imp-alts a { margin-right: 8px; color: var(--primary); text-decoration: underline; white-space: nowrap; }
@media (max-width: 600px) {
  .imp-alts a { display: inline-block; margin: 2px 10px 2px 0; }
}

/* 收益曲线的「数据来源」下拉：页面级一行，放在权益曲线卡片正上方；
   切换时权益曲线 + 个股曲线同时换数据源 */
.curve-src-row {
  display: flex; align-items: center; gap: 8px;
}
.curve-src-row select { min-width: 150px; }

/* AI 连接测试：长错误折叠区（服务商原始 JSON 折在这里，点「详情」展开）。
   `word-break: break-all` 必须有 —— 原始报错里常有一长串没有空格的 URL/token，
   不换行会把卡片顶宽、手机上直接横向溢出。 */
.ai-test-detail {
  display: block; margin-top: 4px; padding: 6px 8px;
  border-left: 2px solid var(--border); background: var(--bg);
  border-radius: 0 6px 6px 0; font-size: 11px; color: #94a3b8;
  word-break: break-all; white-space: pre-wrap;
}

/* ===== 消息栏 ===== */
/* 导航上的未读红点 */
.nav-badge {
  display: inline-block; margin-left: 5px; padding: 0 6px;
  background: #ef4444; color: #fff; border-radius: 9px;
  font-size: 10px; line-height: 15px; font-weight: 700; vertical-align: top;
}
/* 表头里可点击的 ⓘ（手机端没有 hover，title 等于不存在 → 必须可点） */
.th-help {
  display: inline-block; cursor: pointer; color: #60a5fa;
  border: 1px solid rgba(96,165,250,.5); border-radius: 50%;
  width: 15px; height: 15px; line-height: 13px; text-align: center;
  font-size: 11px; user-select: none;
}
.th-help:hover { background: rgba(96,165,250,.2); }

.msg-item { border-bottom: 1px solid var(--border); padding: 8px 4px; }
.msg-item:last-child { border-bottom: none; }
.msg-item.unread { background: rgba(96,165,250,.06); }
.msg-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.msg-title { flex: 1; min-width: 140px; cursor: pointer; font-size: 13px; }
.msg-item.unread .msg-title { font-weight: 600; }
.msg-title:hover { color: #93c5fd; }
.msg-dot {
  width: 7px; height: 7px; border-radius: 50%; background: #ef4444; flex: 0 0 auto;
}
.msg-time { white-space: nowrap; }
.msg-body {
  margin: 6px 0 2px 26px; padding: 8px 10px;
  border-left: 2px solid var(--border); background: var(--bg);
  border-radius: 0 6px 6px 0; font-size: 12px; color: #cbd5e1; line-height: 1.65;
  word-break: break-word;
}
/* 广播指定用户选择器：搜索 + 分页，当页勾选，手机端也能点准 */
#bc-list {
  display: flex; flex-wrap: wrap; gap: 2px 16px;
  min-height: 34px; padding: 6px 8px;
  border: 1px solid var(--border); border-radius: 6px; background: var(--bg);
}
.bc-user-item { padding: 5px 2px; }
/* SMTP 设置网格：三行显式布局，PC/手机同构、手机只收紧列宽：
   [主机 | 端口 | SSL] / [用户名 | 发件人…] / [授权码…] */
.smtp-grid { display: grid; grid-template-columns: minmax(0, 1fr) 110px auto; gap: 10px 14px; }
.smtp-grid .smtp-ssl { align-self: end; padding-bottom: 8px; }
.smtp-grid .smtp-from { grid-column: 2 / -1; }
.smtp-grid .smtp-pass { grid-column: 1 / -1; }
@media (max-width: 600px) {
  .smtp-grid { grid-template-columns: minmax(0, 1fr) 84px auto; gap: 8px 10px; }
}
/* 微信测试号配置栅格：template_id 是 43 位长串，塞进 SMTP 那种 110px 窄列
   根本看不全 → 单独一类：appID / appsecret 一行两列，template_id 独占整行。 */
.wxmp-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 10px 14px; }
.wxmp-grid .wxmp-tid { grid-column: 1 / -1; }
@media (max-width: 600px) {
  .wxmp-grid { grid-template-columns: minmax(0, 1fr); gap: 8px 10px; }
}
/* 卡片内分隔线 */
.card-divider { border-top: 1px solid var(--border); }
/* 看板 Tab 组的桌面端拉伸：基础规则放这里，窄屏规则在下面 600px 块内覆盖。
   ⚠️ 不能写内联 style="flex:1" —— 内联优先级会压过分行规则。 */
.card-head .dash-tabs { flex: 1; }
@media (max-width: 600px) {
  .msg-body { margin-left: 0; }
  .msg-time { width: 100%; }
}

@media (max-width: 600px) {
  /* 手机端：搜索下拉铺满屏宽（原来被挤在窄列里，右侧内容看不全） */
  .search-suggest {
    left: 0; right: 0; width: auto !important; max-width: none !important;
    max-height: 46vh;
  }
  .table-tools { gap: 6px; }
  .table-tools .tt-filter { max-width: none; }
  .nav { gap: 4px; padding: 8px; margin: 8px; }
  .nav-item { padding: 4px 8px; font-size: 12px; }
  .nav .brand { font-size: 15px; margin-right: 4px; }
  /* 手机端：用户名/角色/退出仍是一组。基础规则的 margin-left:auto 已保证
     "放得下贴上一行行末、放不下才整组换行且贴右"——这里**不要** width:100%
     （那会无条件占满一行、行末有空位也强制另起一行）。 */
  .nav-me { gap: 4px; }
  .nav-me #nav-username { max-width: 7em; }
  .page { margin: 0 8px 20px; }
  .kpi-num { font-size: 18px; }
  .modal-card { max-height: 92vh; }
  .modal { padding: 8px; }
  .modal-box { padding: 10px; max-height: 92vh; }
  .sub-tab { padding: 5px 10px; font-size: 12px; }

  /* 手机端：表格横向滚动（不撑破页面宽度），K 线图收边距 */
  .card { padding: 10px; }
  table { display: block; overflow-x: auto; white-space: nowrap; -webkit-overflow-scrolling: touch; }
  .kline-chart { height: 300px; }
  .card-head { flex-wrap: wrap; gap: 6px; }
  .search-suggest .sg-code { min-width: 62px; font-size: 12px; }

  /* 手机端：所有横向 flex 一律允许换行。
     否则一排按钮（如推送设置底部的 保存/测试/清空×2）会被挤出屏幕右侧 ——
     看不见、也点不到。这是"按钮点不到"最常见的成因。 */
  .flex { flex-wrap: wrap; }

  /* 看板行情卡头部：Tab（模拟/真实/自选）与动作（跳转/刷新）各占一整行。
     同一行时 Tab 组被 flex:1 压成 ~160px、三个 Tab 全挤在左边，动作按钮反而占住右侧一大块。 */
  .card-head .dash-tabs { flex: 1 1 100%; }
  .card-head .dash-actions { flex: 1 1 100%; justify-content: flex-start; }
}

/* ==================== 示例策略看板 ==================== */
.exb-filters { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.exb-filters > input { flex: 1 1 200px; min-width: 0; }
.exb-filters > select { flex: 0 1 auto; min-width: 0; }
/* 看板用表格（紧凑）：列 = 名称 / 代码 · 策略范围 · 来源 · 命中 · 时间 · 操作，
   窄屏横向滚动。 */
.exb-tbl-wrap { width: 100%; overflow-x: auto; }
.exb-tbl { width: 100%; border-collapse: collapse; }
.exb-tbl th { font-size: 12px; white-space: nowrap; }
.exb-tbl td { vertical-align: top; }
.exb-tbl tr.is-hit td:first-child { box-shadow: inset 3px 0 0 var(--up); }
.exb-name { font-weight: 600; white-space: nowrap; }
.exb-code { color: var(--muted); font-size: 12px; font-weight: 400; white-space: nowrap; }
.exb-tbl td .badge { vertical-align: middle; }
/* 标题旁的「!」说明按钮（替代独立「ⓘ 说明」按钮，省一格）；点击展开/收起说明区 */
.exb-help-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 17px; height: 17px; margin-left: 7px; padding: 0; border-radius: 50%;
  border: 1px solid var(--muted); color: var(--muted); background: transparent;
  font-size: 11px; font-weight: 700; line-height: 1; vertical-align: 3px; cursor: pointer;
}
.exb-help-ico:hover { color: var(--fg); border-color: var(--fg); }
/* 说明区里示例行内的「看板 ✓/✗」小开关：与策略名同一行（在 code 里，跟随其后） */
.ex-help-apply {
  display: inline-block; margin-left: 8px; padding: 0 6px; border-radius: 4px;
  font-size: 11px; font-weight: 500; cursor: pointer; vertical-align: 1px;
  background: rgba(148,163,184,.20); color: var(--muted); border: 1px solid transparent;
}
.ex-help-apply.is-on { background: rgba(239,68,68,.18); color: #f87171; border-color: rgba(239,68,68,.45); }
.exb-empty { color: var(--muted); font-size: 13px; padding: 14px 10px; text-align: center; }
.exb-pager { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.exb-page-info { color: var(--muted); font-size: 12px; }
/* 分页条两组：宽屏同一行；手机端（≤900px）各占一整行 ——
   「共N条+每页N条」一行、「上一页+页码+下一页」一行（用户指定） */
.exb-pg-grp { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
/* 分页条与工具条同一风格：每页条数下拉 + 可直接输入的页码 */
.exb-pager select { padding: 2px 6px; }
.exb-pager input[type="number"] { width: 3.4em; padding: 2px 4px; }
.exb-detail-row { border-bottom: 1px solid var(--border); padding: 9px 2px; }
.exb-detail-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.exb-detail-expr { font-size: 12px; color: var(--muted); overflow-wrap: anywhere; }
.exb-detail-snap { font-size: 12px; color: var(--muted); overflow-wrap: anywhere; }
.exb-hist-row { border-bottom: 1px solid var(--border); padding: 8px 2px; font-size: 13px; }

/* 示例 chip 上的「看板」应用标记 + 编辑按钮：手机端点得到、状态一眼可见（不靠 hover 的 title）。 */
.fn-chip .ex-apply {
  display: inline-block; margin-left: 6px; padding: 0 5px; border-radius: 4px;
  font-size: 10px; font-weight: 500; cursor: pointer;
  background: rgba(148,163,184,.20); color: var(--muted); border: 1px solid transparent;
}
.fn-chip .ex-apply.on { background: rgba(239,68,68,.18); color: #f87171; border-color: rgba(239,68,68,.45); }
.fn-chip .ex-edit {
  display: inline-block; margin-left: 4px; padding: 0 4px; border-radius: 4px;
  font-size: 11px; color: #93c5fd; cursor: pointer;
}
.fn-chip .ex-edit:hover { background: rgba(59,130,246,.25); }

/* 看板标题区（一行）：标题 + 「!」说明图标 + 动作按钮同一行；
   手机端按钮组自动换到下一行（flex-wrap 由既有 .flex/.card-head 规则保证）。 */
.exb-head { flex-wrap: wrap; gap: 6px 10px; margin-bottom: 8px; }
.exb-head h3 { flex: 0 0 auto; margin: 0; }
.exb-head-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-left: auto; align-items: center; }
.exb-snap-head { flex-wrap: wrap; gap: 6px 10px; }
.exb-snap-head h3 { flex: 0 0 auto; margin: 0; font-size: 15px; }
/* 历史快照：没有详情/操作列，整行可点开弹窗（用户指定） */
.exb-snap-row { cursor: pointer; }
.exb-snap-row:hover td { background: rgba(148, 163, 184, 0.10); }
/* 时间筛选框：未选日期时**框内**盖一层「所有快照」提示（用户指定），
   选中日期后由 JS 隐藏；pointer-events:none 保证不挡点击/日历图标 */
.exb-date-wrap { position: relative; display: inline-flex; }
/* 输入框：保证足够点击面积 + 手型光标（桌面端原来只在小图标上能弹日历，
   现在 JS 里 showPicker() 让框内任意位置可点，这两条配合让「可点」看得见摸得着） */
.exb-date-wrap > input { flex: 0 0 auto; min-width: 150px; cursor: pointer; }
.exb-date-wrap > input::-webkit-calendar-picker-indicator { cursor: pointer; }
.exb-date-hint {
  position: absolute; left: 2px; top: 2px; bottom: 2px; right: 28px; z-index: 2;
  display: flex; align-items: center; padding: 0 8px;
  background: var(--bg); color: var(--muted); font-size: 13px;
  pointer-events: none; border-radius: 5px 0 0 5px; overflow: hidden;}
.exb-switches { gap: 4px 14px; margin-top: 6px; }
.exb-switches > .form-row.inline > input { min-width: 0; }
/* 勾选后的保存反馈放**行尾**（用户指定）：margin-left:auto 顶到该行最右 */
.exb-row-status { margin-left: auto; min-width: 0; }
/* 手机端：保存反馈另起一行、在行首（用户指定）—— 不再贴行尾，
   flex: 1 1 100% 强制独占一行。 */
@media (max-width: 900px) {
  .exb-row-status { flex: 1 1 100%; margin-left: 0; text-align: left; }
}
/* AI 设置卡分组标题（用户指定归类整理）+ 自动保存反馈右对齐 */
.ai-set-title { font-weight: 600; margin-top: 14px; }
#ai-settings-status { text-align: right; }
@media (max-width: 900px) {
  .ai-set-title { margin-top: 10px; }
  #ai-settings-status { flex: 1 1 100%; text-align: left; }
}

/* 系统监控：指标卡网格 */
.metrics-cards { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
.metric-card {
  background: var(--bg-soft); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 12px; display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
.metric-card .metric-label { font-size: 12px; color: var(--muted); }
.metric-card b { font-size: 20px; font-weight: 500; color: var(--text); }
.metric-card .metric-sub { font-size: 11px; color: var(--muted); }
@media (max-width: 900px) {
  .metrics-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* 历史快照删除的二次确认条：就地显示在快照栏内部（用户指定"要在区域内"），
   红色边框警示；手机端按钮换行不挤爆。 */
.exb-confirm {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 8px 0 0; padding: 8px 12px; border-radius: 8px;
  background: rgba(239, 68, 68, 0.10);
  border: 1px solid rgba(239, 68, 68, 0.40);
}
.exb-confirm .small { color: var(--text); }
@media (max-width: 900px) {
  /* 手机端：标题一行、按钮组整行左对齐，绝不让按钮被挤出屏幕 */
  .exb-head-actions { flex: 1 1 100%; margin-left: 0; }
  .exb-switches { gap: 6px 12px; }
  /* 分页两组各占一行：信息行（共N条/每页N条）+ 翻页行（上一页/页码/下一页） */
  .exb-pg-grp { flex: 1 1 100%; }
}

/* 管理页模块卡片切换 —— UI 复用持仓页 .sub-tabs/.sub-tab，只留面板显隐 */
.adm-pane { display: none; }
.adm-pane.active { display: block; }

/* ===== 价格预警表单：统一标的输入 + 选中信息条 + K 线弹窗 ===== */

/* 「选中标的」信息条：名称/代码 + 现价/涨跌 + 距目标 + K线、换标的按钮。
   ⚠️ 用 grid 而不是 flex + align-items:center —— 「窄的固定标记」和「会折行的正文」
   并排时，center 会让标记在正文折行后跑到垂直中间而错位；baseline 才对齐自然。
   手机端（≤900px）收成单列，按钮整行左对齐（不能居中，否则触屏够不着）。 */
.alert-picked {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  column-gap: 10px; row-gap: 6px;
  padding: 8px 10px;
  margin-bottom: 8px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
}
.alert-picked .ap-main { min-width: 0; }
.alert-picked .ap-name { font-weight: 600; }
.alert-picked .ap-code { color: var(--muted); font-family: ui-monospace, monospace; font-size: 12px; }
.alert-picked .ap-price { font-family: ui-monospace, monospace; font-size: 15px; font-weight: 600; }
.alert-picked .ap-price.up { color: #ef4444; }
.alert-picked .ap-price.down { color: #22c55e; }
.alert-picked .ap-chg { font-family: ui-monospace, monospace; font-size: 12px; margin-left: 6px; }
.alert-picked .ap-chg.up { color: #ef4444; }
.alert-picked .ap-chg.down { color: #22c55e; }
.alert-picked .ap-note { font-size: 12px; color: var(--muted); }
.alert-picked .ap-actions { display: flex; gap: 6px; flex-wrap: wrap; }
@media (max-width: 900px) {
  .alert-picked { grid-template-columns: minmax(0, 1fr); }
  .alert-picked .ap-actions { justify-content: flex-start; }
}

/* 下拉里标出「这条是从哪儿来的」（自选 / 真实持仓 / 模拟持仓），
   并给分组加一个小标题行 —— 合并成一个输入框后，用户需要一眼看出
   候选里的自选/持仓和全市场搜索结果的区别。 */
.search-suggest .sg-scope {
  font-size: 10px; padding: 0 5px; border-radius: 8px;
  background: rgba(148,163,184,.18); color: #cbd5e1; margin-left: 6px;
  white-space: nowrap;
}
.search-suggest .sg-group {
  padding: 5px 10px; font-size: 11px; color: var(--muted);
  background: rgba(148,163,184,.08);
  border-bottom: 1px solid rgba(71,85,105,.4);
}

/* K 线弹窗里的图表：按视口自适应高度，手机上不至于把弹窗撑满、
   导致下面的「目标价」输入框看不见（用户开弹窗就是为了定目标价）。 */
#alert-kline-chart, #pos-kline-chart { width: 100%; height: min(52vh, 420px); background: var(--bg); border-radius: 6px; }

/* 预警表单：栅格/弹性行里的 select 与 input 必须能收缩。
   ⚠️ 坑：`select` 的**固有最小宽度由最长的那个 option 决定**（不是控件本身），
   而 `.grid-2` 在 600~900px 之间**仍是两列** —— "触发后自动买入（模拟）"这种长文案
   会把右边界顶出去，手机上看起来就是"下拉框右边越界了"。
   只有 `min-width: 0` 能让 grid/flex 子项缩到轨道宽度以内（grid 子项默认
   `min-width: auto` = 内容最小宽，所以光靠轨道的 `minmax(0,1fr)` 拦不住）。 */
#view-alerts .grid > select,
#view-alerts .grid > input { min-width: 0; width: 100%; }
/* 标的输入框在弹性行里（input + 搜索按钮）：只要 min-width，不要 width:100%，
   否则会和按钮一起撑破那一行。 */
#view-alerts .form-row .flex > input { min-width: 0; }
/* 「快速填入」（可搜索的自选/持仓候选）：直接放在 .form-row（flex column）里铺满一行。
   仍然显式给 `min-width: 0` —— 它的内容宽度由候选名称决定
   （如"招商民安增益债券C（of008476）· 自选/模拟持仓"），手机端会顶破父容器。 */
#view-alerts #alertcand-symbol { min-width: 0; width: 100%; }

/* 「快速填入」右侧的下拉箭头 —— 用户要求「保留下拉框啊，只是显示时支持筛选」。
   它是个 <span> 而非原生 <select>：原生 select 只支持"首字母跳转"、没有真正的搜索；
   但外观上仍要一眼看出"这是个能展开的下拉"。点它 = 展开全部候选（等价于点进输入框）。 */
.search-picker .picker-caret {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  cursor: pointer; color: var(--muted, #94a3b8); font-size: 12px; line-height: 1;
  padding: 3px 5px; border-radius: 4px; user-select: none;
}
.search-picker .picker-caret:hover { background: rgba(255,255,255,0.08); color: var(--text, #e2e8f0); }
/* 展开时箭头翻转 —— 让"现在是展开还是收起"一眼可见（用户反馈过"再点箭头不收"，
   除了改成切换，还得让状态看得见）。用 :has() 读兄弟元素的 inline style：
   老浏览器不支持就只是不转，不会影响功能。 */
.search-picker:has(#alertcand-suggest[style*="display: block"]) .picker-caret {
  transform: translateY(-50%) rotate(180deg);
}
/* 给箭头留出位置，别压在输入的文字上（基础 padding-right 见文件上方 `.search-picker > input`） */
#view-alerts #alertcand-symbol { padding-right: 28px; }
@media (max-width: 900px) {
  /* 触屏：把箭头的**可点区域**撑大（视觉不变，只加 padding），手机上不至于点不中 */
  .search-picker .picker-caret { padding: 8px 9px; right: 0; }
  #view-alerts #alertcand-symbol { padding-right: 38px; }
}

/* ==================== 预警列表的紧凑化 ====================
   手机端表格横向滚动，一行的行高 ≈ 格子里块级元素的个数 ——
   铁律：同一格里的短信息一律 inline（名称/代码、徽标/标签、动作/数量）。 */
.al-name { font-weight: 600; white-space: nowrap; }
.al-code { color: var(--muted); font-size: 12px; white-space: nowrap; }
/* 「触发后停用」做成同行小标签 —— 独立 <div> 会让状态列在手机上占两行 */
.al-tag {
  display: inline-block; margin-left: 6px; padding: 0 5px; border-radius: 8px;
  font-size: 11px; white-space: nowrap;
  background: rgba(148, 163, 184, 0.20); color: var(--muted);
}

@media (max-width: 600px) {
  /* 标题独占一行（统计跟着）、按钮另起一行左对齐 —— 否则标题区只剩 ~160px、
     统计被挤断。
     ⚠️ 必须限定到 `.alert-list-head`：用 `#view-alerts .card-head h3` 会连带把
     上面「新建预警」卡的标题也撑成独占一行。 */
  #view-alerts .alert-list-head h3 { flex: 1 1 100%; }
  #view-alerts .alert-list-head > .flex { flex: 1 1 100%; justify-content: flex-start; }
}

/* IP 防护 · 拉黑输入行：桌面端一行（IP / 原因平分宽度，时长 + 拉黑按钮靠右），
   ≤900px 拆两行（IP + 原因 / 时长 + 按钮）。
   ⚠️ 布局全部写在这里，别给这些元素写 inline 的 flex / flex-wrap ——
   内联样式优先级高于媒体查询，窄屏 4 个控件会硬挤一行。 */
.ipguard-addrow {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: nowrap;
  margin: 6px 0 10px;
}
.ipguard-addrow > #ipguard-add { flex: 1 1 0; min-width: 0; }
.ipguard-addrow > #ipguard-reason { flex: 1.3 1 0; min-width: 0; }
.ipguard-addrow > #ipguard-ban,
.ipguard-addrow > .btn { flex: 0 0 auto; white-space: nowrap; }

/* 自动封禁阈值输入框：数值到 4 位（如 1440）时会被 WebKit 步进箭头遮住
   → 隐藏步进按钮（键盘输入不受影响）并给足宽度；Firefox 用 -moz-appearance 收掉同款箭头。 */
#ab-lfw, #ab-lfn, #ab-4w, #ab-4n, #ab-ban { width: 72px; vertical-align: middle; }
#ab-rpm { width: 84px; vertical-align: middle; }
#ab-lfw::-webkit-outer-spin-button, #ab-lfw::-webkit-inner-spin-button,
#ab-lfn::-webkit-outer-spin-button, #ab-lfn::-webkit-inner-spin-button,
#ab-4w::-webkit-outer-spin-button, #ab-4w::-webkit-inner-spin-button,
#ab-4n::-webkit-outer-spin-button, #ab-4n::-webkit-inner-spin-button,
#ab-rpm::-webkit-outer-spin-button, #ab-rpm::-webkit-inner-spin-button,
#ab-ban::-webkit-outer-spin-button, #ab-ban::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}
#ab-lfw, #ab-lfn, #ab-4w, #ab-4n, #ab-rpm, #ab-ban {
  -moz-appearance: textfield; appearance: textfield;
}

@media (max-width: 900px) {
  /* 两个文本框各占半行 → 第一行刚好放下这两个；时长与拉黑按钮被挤到第二行。
     calc(50% - 4px)：留出 gap 的余量，避免浏览器取整后第二行只剩一个输入框。 */
  .ipguard-addrow { flex-wrap: wrap; }
  .ipguard-addrow > #ipguard-add,
  .ipguard-addrow > #ipguard-reason { flex: 1 1 calc(50% - 4px); }
}

/* ==================== 新闻页 ====================
   列表用「行」而不是表格：一条新闻 = 时间 + 标题（+ 摘要 / 来源标签）。
   本页每行都是长标题，手机上横滚读不了 → 按卡片行排，窄屏自动塌成竖排。 */
.news-topbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 10px;
}
.news-topbar .spacer { flex: 1; }
/* 「更新于xxx」+「刷新」是个整体：靠 margin-left:auto 顶到行尾，
   不依赖 .spacer（spacer 在手机上会被隐藏）。 */
.news-topbar .news-topbar-right {
  display: flex; align-items: center; gap: 8px; margin-left: auto; min-width: 0;
}

.news-list { display: flex; flex-direction: column; }
.news-item {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 9px 0; border-bottom: 1px solid var(--border);
}
.news-item:last-child { border-bottom: none; }
.news-item .nt-mark { flex: none; width: 6px; height: 6px; margin-top: 7px; border-radius: 50%; }
.news-item.unread .nt-mark { background: var(--primary); }
.news-item.unread .nt-title { font-weight: 500; }
.news-item.unread .nt-time { color: var(--primary); }
.news-item .nt-time {
  flex: 0 0 104px; color: var(--muted); font-size: 12px; padding-top: 1px;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.news-item .nt-body { flex: 1; min-width: 0; }
.news-item .nt-title {
  display: block; font-size: 13px; line-height: 1.5; color: var(--text);
  text-decoration: none; word-break: break-word; overflow-wrap: anywhere;
  cursor: pointer;
}
.news-item .nt-title:hover { color: var(--primary); }
.news-item .nt-caret {
  color: var(--muted); font-size: 10px; margin-left: 4px; user-select: none;
}
.news-item.open .nt-caret { color: var(--primary); }
/* 没有详细描述 → 标题本身就是外链，用 ↗ 提示"会开新窗口" */
.news-item .nt-ext { color: var(--muted); font-size: 11px; margin-left: 4px; }
.news-item a.nt-title:hover .nt-ext { color: var(--primary); }
/* 本地展开区：摘要全文 + 关联报道 + 原文链接（**不强制跳外链** —— 用户口径） */
.news-item .nt-detail {
  margin-top: 6px; padding: 8px 10px; border-left: 2px solid var(--primary);
  background: rgba(127,127,127,.08); border-radius: 0 4px 4px 0;
  font-size: 12.5px; line-height: 1.7; color: var(--text);
  white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere;
}
.news-item .nt-detail .nt-orig {
  display: inline-block; margin-top: 6px; color: var(--primary); text-decoration: none;
}
.news-item .nt-detail .nt-orig:hover { text-decoration: underline; }
.news-item .nt-detail .news-rel { margin-top: 6px; }
.news-item .nt-src {
  flex: none; font-size: 11px; color: var(--muted); white-space: nowrap;
  border: 1px solid var(--border); border-radius: 4px; padding: 0 5px;
}
.news-item .nt-tag {
  flex: none; font-size: 11px; color: var(--primary); white-space: nowrap;
  background: rgba(59,130,246,.12); border-radius: 4px; padding: 0 5px;
}
/* 收藏星标：默认淡（可点），收藏后点亮 */
.news-item .nt-star {
  flex: none; cursor: pointer; user-select: none; padding: 0 2px;
  font-size: 13px; line-height: 1.4; color: var(--border);
}
.news-item .nt-star.on { color: #f59e0b; }
/* 分页工具条（复用 .table-tools 的观感）：挂在列表下方，所以改成上间距 */
.table-tools.news-tools { margin-top: 10px; margin-bottom: 0; justify-content: flex-end; }
/* 空态（未关注 → 不推荐持仓相关；入口在卡头「管理关注标的」，这里只说明） */
.news-empty { padding: 14px 2px; color: var(--muted); font-size: 13px; line-height: 1.8; }
/* 同事件折叠：其余来源默认收起，点开才看 */
.news-rel { margin-top: 5px; font-size: 12px; color: var(--muted); }
.news-rel summary { cursor: pointer; color: var(--primary); }
.news-rel ul { margin: 4px 0 0; padding-left: 16px; }
.news-rel li { padding: 2px 0; word-break: break-word; }

/* 今日要闻：15 条编号短句（扫读场景，一行一句）。
   每条标题挂搜索外链（60s 源只给一句话、没有 url/正文），仍排在序号后面、不换行。 */
.news-brief { margin: 0; padding-left: 22px; }
.news-brief li {
  padding: 5px 0; font-size: 13px; line-height: 1.55;
  border-bottom: 1px solid var(--border); word-break: break-word;
}
.news-brief li:last-child { border-bottom: none; }
.news-brief a { color: var(--text); text-decoration: none; }
.news-brief a:hover { color: var(--primary); }
.news-brief a:hover .nt-ext { color: var(--primary); }

/* 热榜：与股市两张表同一套行式（复用 .news-item）；旧的 .news-hot 一套已删
   （选择器权重更高，会顶掉 .news-item 的内边距，两套样式不能同时存在）。
   序号由 .nt-rank 承担（热榜没有发布时间，左格放序号）。 */
.nt-rank {
  flex: 0 0 22px; color: var(--muted); font-size: 12px; padding-top: 1px;
  text-align: right; font-variant-numeric: tabular-nums;
  /* 序号是"列表序号"语义 → 永远贴首行。⚠️ 不能依赖容器的 align-items：
     标题折成 N 行时按整段居中，序号会漂到标题正中间 → 用 align-self 单独钉死。 */
  align-self: flex-start;
}
.news-item:nth-child(-n+3) .nt-rank { color: var(--danger); font-weight: 700; }

/* 布局加固：兜底 —— 即使某条 flex 属性被覆盖，标题也不会被压成竖排。 */
.news-list > .news-item { width: 100%; }
.news-item .nt-body { flex: 1 1 auto; min-width: 0; }
.news-item .nt-title { min-width: 0; }

/* 关注管理弹窗：分组勾选列表，三组可折叠。 */
.nf-search-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.nf-search-row input { flex: 1; min-width: 0; }
.nf-search-row .nf-all { flex: none; }
.nf-groups { max-height: 54vh; overflow-y: auto; border-top: 1px solid var(--border); }
.nf-group-h {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 2px; margin-top: 2px; font-size: 12px; color: var(--muted);
  cursor: pointer; user-select: none;
  border-bottom: 1px solid var(--border);
  position: sticky; top: 0; background: var(--bg-soft); z-index: 1;
}
.nf-group-h:hover { color: var(--text); }
.nf-group-h .nf-caret { flex: none; width: 10px; font-size: 10px; }
.nf-group-h .nf-gname { flex: 1; min-width: 0; }
.nf-group-h.open .nf-gname { color: var(--text); }
/* 「已选 N」—— 折叠着也能一眼看出这组有多少是你的人 */
.nf-group-h .nf-gsel { flex: none; color: var(--primary); }
.nf-group-h .nf-all { flex: none; cursor: pointer; color: var(--primary); }
.nf-none { padding: 6px 2px 6px 18px; }
/* 组内的「未关注」分隔（用户口径「最好按已关注、未关注分组」）：
   勾过的排在前面，中间一条小字分隔 —— 比整块重排更不容易让人找不到原来的位置。 */
.nf-sep {
  padding: 5px 2px 3px 18px; font-size: 11px; color: var(--muted);
  border-bottom: 1px dashed var(--border);
}
/* 一行一只：勾选框 + 「名称 代码」+ 副标题（持仓市值，只有真实持仓 / 模拟持仓有）。
   ⚠️ 名称与代码必须同在一行（两块 block 会让手机上一只占两行）。
   ⚠️ `.nf-sub` 是兄弟节点 + `margin-left: auto`：放得下贴行尾、放不下自己换一行，
      塞进 `.nf-body` 里就没这个能力。
   ⚠️ `padding-right` 必须留够：`.nf-groups` 是 `overflow-y: auto`，窄屏滚动条会压住行尾文字。 */
.nf-row {
  display: flex; align-items: center; gap: 6px 8px; flex-wrap: wrap;
  padding: 5px 8px 5px 18px; border-bottom: 1px solid var(--border); cursor: pointer;
}
.nf-row:last-child { border-bottom: none; }
.nf-row input[type="checkbox"] { flex: none; margin: 0; }
.nf-row .nf-body { flex: 1 1 auto; min-width: 0; }
.nf-row .nf-name {
  display: block; font-size: 13px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.nf-row .nf-sub {
  flex: 0 0 auto; margin-left: auto; font-size: 11px; color: var(--muted);
  white-space: nowrap;
}
/* 已达上限：未勾选项置灰且不可点 —— "为什么点不动"要一眼看得出来 */
.nf-row.off { opacity: .45; cursor: not-allowed; }
.nf-row.off input { cursor: not-allowed; }

/* 管理页 · 用户列表「新闻上限」列：11 列的表里数字框吃满宽度会把行撑爆，
   全局 input 样式在这个尺寸下太胖 → 单独收窄。 */
.u-news-limit { width: 72px; padding: 4px 6px; font-size: 12px; }

@media (max-width: 600px) {
  /* 一条新闻重排成「首行元信息 / 标题 / 来源」三层：
     首行 = 时间 + 收藏星（热榜没有发布时间，首行就是序号），贴行首；
     标题（+ 展开区）单独占一行吃满整宽；来源 / 标的标签落第三行。
     ⚠️ 不能用 flex-direction: column —— 竖排下每个子元素独占整行，
        收藏星和时间各自白吃一行；用 row + wrap 表达"元信息同行、正文另起"。 */
  .news-item { flex-direction: row; flex-wrap: wrap; align-items: flex-start; gap: 3px 8px; }
  .news-item .nt-time { flex: none; order: 1; padding-top: 0; }
  .news-item .nt-star { flex: none; order: 2; padding: 0 4px; }   /* 触摸目标略放大 */
  .news-item .nt-rank { flex: none; order: 1; padding-top: 1px; text-align: left; }
  .news-item .nt-body { flex: 1 1 100%; order: 4; }
  /* 来源回首行行尾（margin-left:auto 顶右侧，长平台名可收缩 + 省略号）；
     标的标签留在最后一行：它跟标题是一对，挤首行行尾会和来源打架。 */
  .news-item .nt-src {
    flex: 0 1 auto; order: 3; margin-left: auto;
    overflow: hidden; text-overflow: ellipsis; max-width: 50%;
  }
  .news-item .nt-tag { flex: none; order: 5; }
  .news-item .nt-mark { display: none; }   /* 整宽圆点会独占一行 → 未读改用字重+时间色区分 */
  /* 热榜行（`.has-rank` = 有序号）：序号 + 标题同一行，否则标题被上面的
     `flex: 1 1 100%` 顶到第二行就成了"序号独占一行"。
     ⚠️ 标题的基准宽度必须是 0（`flex: 1 1 0`），不能写 auto：auto 在 flex-wrap 下
        按内容最大宽度占位，标题一长就整块换行（标题最长的知乎最先翻车）。 */
  .news-item.has-rank .nt-body { flex: 1 1 0; order: 2; }
  .news-item.has-rank .nt-src { order: 3; max-width: 46%; }
  /* ⚠️ 徽标别试"独占一行"（flex-grow:1 会把边框盒撑满整行，短文本配大框很难看）：
     保持贴标题首行行尾，折叠态与展开态都不另占竖向空间。 */
  /* ⚠️ align-items 必须是 flex-start：序号与来源徽标一律贴首行，
     否则按整行交叉轴居中会漂移（展开态徽标甚至落在行正中间）。
     普通新闻行首行三者高差 ≤0.25px，flex-start 不影响既有观感。 */
  /* 展开态：`display: contents` 化掉 .nt-body 一层，标题 / 详情升格为 .news-item
     的 flex 项 → 详情独占整宽、来源徽标贴标题首行行尾（不另占竖向空间）。
     ⚠️ .nt-detail 必须显式给 order：默认 0 会排到 order:1 的序号前面。
     ⚠️ 只作用在手机端 + 热榜 + 展开态：桌面端与折叠态行为不变。 */
  .news-item.has-rank.open .nt-body { display: contents; }
  .news-item.has-rank.open .nt-title { flex: 1 1 0; order: 2; }
  .news-item.has-rank.open .nt-detail { flex: 1 1 100%; order: 4; }
  .news-topbar { gap: 8px; }
  .news-topbar .spacer { display: none; }
  /* 「更新于xxx」+「刷新」整组另起一行、仍贴行尾（用户口径："若不能同时放一行，
     一起另起一行放行尾"）。 */
  .news-topbar .news-topbar-right { flex: 1 1 100%; justify-content: flex-end; margin-left: 0; }
  .nf-groups { max-height: 46vh; }
}
