/* 牛量化 · Research terminal. Shared tokens, scoped dashboard components.
   2026-09-28 起为浅色「青柠」主题。强调色拆成三件（填充/前景/墨色），
   图表主序列另立 --chart-series —— 原实现 series 直接借 --accent，
   青柠当墨色在白底上只有 1.18:1，主权益曲线会整条消失。 */
:root {
  --bg:#E9E9E9; --bg-2:#F1F1F1; --bg-3:#E3E3E3;
  --surface:#FFFFFF; --surface-2:#F7F7F7; --surface-3:#EFEFEF;
  --border:#E1E1E1; --border-strong:#C9C9C9;
  --text:#0A0A0A; --text-2:#4B5563; --text-3:#5F6672;
  --accent:#BFFF6B; --accent-2:#A8E82E; --accent-soft:rgba(191,255,107,.34);
  --on-accent:#0A0A0A; --accent-ink:#365E00; --border-accent:#C9E89A;
  --chart-series:#3B5EB0;
  /* 深底（黑胶囊）上的前景与市况三态。亮底版 --chart-up/bull/alt-3 在 #222 上
     只有 3.2~3.3:1，必须另配一档亮色。 */
  --on-dark:#FFFFFF; --on-dark-2:rgba(255,255,255,.55);
  --state-bull-dark:#FF9B9B; --state-bear-dark:#7EE0AE; --state-flat-dark:#F2C078;
  /* 青柠底上的市况三态（2026-09-30 顶栏对调后，状态岛由黑改青柠）。
     上面那档亮色是给黑底选的，压到青柠 chip 上实测只有 1.6~1.8:1；
     --ink-up/--ink-down 也够不着（3.89 / 4.15，13px 不算大字号，要 4.5）。
     这三个是按「青柠 chip（#ACE661）+ 13px 600」选出来的，实测 5.5~6.1:1，
     口径不变：牛=红（A股红涨）、熊=青绿、震荡=琥珀。 */
  --state-bull-lime:#8F1D12; --state-bear-lime:#0F5A50; --state-flat-lime:#7A3A05;
  --amber:#A8500A; --orange:#A8500A; --green:#15803D; --red:#D92D20;
  /* 文字用的涨跌色。图表 ink（--chart-up/down）在白底上只有 3.7~4.8:1，
     当文字用不达标；这一对是按 白/页面/侧栏 三种底都 >=4.5 选的。
     方向口径与图表一致：涨=红、跌=青绿（A股红涨绿跌）。 */
  --ink-up:#C4291C; --ink-down:#116E63;
  /* 图表 ink：红涨绿跌口径不变，仅按白底重新取色
     （原值搬到白底：sell 2.28 / accent 2.18 / neutral 2.51，全部不达标） */
  --chart-up:#D92D20;      /* 涨 / 收>开（A股红涨绿跌） */
  --chart-down:#35978D;    /* 跌（柔和版：同色相降饱和） */
  --chart-buy:#B23E38;     /* 买/加仓点 */
  --chart-sell:#2F9353;    /* 卖/减仓点 */
  --chart-bull:#2F9353; --chart-bear:#B23E38;
  --chart-neutral:#6E737C; --chart-pos:#3B5EB0; --chart-dd:#B23E38;
  --chart-alt-1:#AE6832; --chart-alt-2:#6E3DAE; --chart-alt-3:#A37A33;
  --chart-tag:#0A0A0A;
  /* 分类色板（2026-09-30 重做）：整体降饱和（柔和），并按**感知色差 ΔE** 铺满色相环 ——
     12 主色（每 30°，饱和 .50）+ 12 暗档（每 +15°，饱和 .45），共 24 槽。
     实测最小成对 ΔE = 8.2、无 ΔE<8 的对 ⇒ 页面最多 24 条线时**不会撞色**
     （原先 12 槽要装 24 条线，全开时实测 10 处同色）。
     ⚠ 注释里**绝不能出现**结束符本身（星号+斜杠）：上一版就是这个原因 ——
       把整段 cat-* 变量吞进注释，NQTerminal.palette 全取 fallback 色、24 条线同一个颜色。 */
  --chart-cat-1:#385AA8; --chart-cat-2:#A86938; --chart-cat-3:#38A89D; --chart-cat-4:#6938A8;
  --chart-cat-5:#A8386C; --chart-cat-6:#38A861; --chart-cat-7:#388AA8; --chart-cat-8:#6CA838;
  --chart-cat-9:#A88A38; --chart-cat-10:#4038A8; --chart-cat-11:#A838A8; --chart-cat-12:#A8384B;
  --chart-cat-13:#30347E; --chart-cat-14:#7E6530; --chart-cat-15:#30727E; --chart-cat-16:#65307E;
  --chart-cat-17:#7E3041; --chart-cat-18:#307E60; --chart-cat-19:#30557E; --chart-cat-20:#417E30;
  --chart-cat-21:#7E7C30; --chart-cat-22:#48307E; --chart-cat-23:#7E306A; --chart-cat-24:#7E3630;
  --radius-lg:18px; --radius-md:13px; --radius-sm:9px;
  --shadow-1:0 1px 2px rgba(10,10,10,.05),0 6px 18px rgba(10,10,10,.05);
  --shadow-2:0 2px 6px rgba(10,10,10,.07),0 18px 40px rgba(10,10,10,.10);
  --title-size:25px; --title-ls:-.6px;   /* 全站页面标题唯一规格 */
  --notch-r:23px;          /* 卡片右上角缺口半径 */
  --notch-x:9px;           /* 圆心距右边 */
  --notch-y:9px;           /* 圆心距上边 */
  --font-body:'Fira Sans',-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;
  --font-data:'Fira Code',ui-monospace,'SF Mono',monospace;
  color-scheme:light;
}
*{box-sizing:border-box;margin:0;padding:0;scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent}
html,body{height:100%}
body{display:flex;flex-direction:column;overflow:hidden;background:var(--bg);color:var(--text);font:14px/1.5 var(--font-body);-webkit-font-smoothing:antialiased}
button,input,select{font:inherit}
button,a,input,select,summary{touch-action:manipulation}
button{cursor:pointer}
button,a,summary{transition:background .16s,color .16s,border-color .16s}
a{color:var(--accent-ink);text-decoration:none}
button,input,select{border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-2);color:var(--text)}
button,select{min-height:34px;padding:6px 10px}
input{padding:7px 10px;min-width:0}
input::placeholder{color:var(--text-3)}
button:hover,select:hover{border-color:var(--border-strong);background:var(--surface-3)}
button:active{background:var(--accent-soft)}
input:focus{border-color:var(--accent-ink)}
:is(button,input,select,a,summary,[tabindex]):focus-visible{outline:2px solid var(--accent-ink);outline-offset:3px}
input[type=checkbox]{accent-color:var(--accent-ink);width:15px;height:15px;vertical-align:middle}
input[type=date]{color-scheme:dark;font-family:var(--font-data);font-size:11px;max-width:142px}
input[type=date]::-webkit-calendar-picker-indicator{opacity:.7}
select option{background:var(--surface)}
.icon{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;flex-shrink:0;vertical-align:middle}
/* 顶栏「页头底线」= 20px（2026-10-05，用户反馈「页头底部没有留白，和下面的内容区重叠了」）。
   原先 margin-bottom 是 0 ⇒ .wrap 的顶边与黑胶囊的底边**同一条线**（实测 topbar 底 66 /
   gapTopbarToWrap 0），而 .side 的 --bg-2 浅灰面正是从这条线开始 ⇒ 页头看着压在内容区上。
   20px 用 margin 而不是 .wrap 的 padding-top：加 padding 会让 .side/.main 自己的底色
   （#F1F1F1 / #E9E9E9）填满这段，留出来的不是「页面底色留白」而是一块内容底色，等于没留。
   代价：body 是 height:100% 的 flex 列，.wrap{flex:1} 会少 20px —— 主滚动区随之矮 20px。
   body.standalone 的顶栏另有 `margin:0 0 16px`（更高特异性），本行不影响独立页。 */
.topbar{display:flex;align-items:center;gap:12px;margin:14px 20px 20px;flex:0 0 auto}
.brand{display:flex;align-items:center;gap:11px;flex:0 0 auto}
/* 品牌图标（2026-10-06）：青柠砖 + lucide 折线 → 黑砖 +「几何牛头·金渐变」lg2_geoB。
   ⚠ 为什么牛头**必须**坐在黑砖上，不能裸放（实测数字，非审美偏好）：
     · 金渐变 #F6DFA9 → #AD8021 压在顶栏底色 --bg #E9E9E9 上只有 1.08:1 / 2.94:1。
       1.0:1 就是「与底色同亮度」—— 那只牛脸下半（渐变的亮端）在灰底上是**真的没了**；
     · 同一张 SVG 压在 #0A0A0A 上是 15.13:1 / 5.55:1，渐变两端都读得出来；
     · 黑砖同时与顶栏既有的黑色胶囊（.tab-rail 也是 #0A0A0A / radius 11px）同一语言，
       不用重算任何颜色关系。
   图标尺寸写 82% 而不是写死 px：600px 断点把砖收到 36px 时它自动跟着缩，
   **不需要**再补一条 media 规则（写死 px 的话两处尺寸会各自漂移）。
   砖里的 <img> 是装饰性的（相邻 .brand-name 已经给出品牌名，它的 alt 为空）。

   ── 2026-10-06 第二轮（用户：「文字的视觉重心没有对齐，看起来比图标高，文字下面比较空。
      图标还可以增大一些。」）—— 两个都是实测存在的，都改了：

   ① 图标增大：砖 32 → **40px**（牛头墨迹 18 → **25px**，+39%）。
      40 不是随手取的：`.tab-rail .tab` 的 `min-height` 正是 40px ⇒ 品牌砖与导航小胶囊
      **等高**。圆角同步按比例 11 → **13px**（保持 0.325 的圆角/边长比）。
      旧版 lucide 图标的墨迹有 19px 高，换成牛头后只有 18px（牛头墨迹只占砖的 53%，
      且下半是渐变亮端）⇒ 视觉上整块变小了，这正是用户要求放大的原因。

   ② 文字光学对齐：`.brand-text` 下移 **8px**（`position:relative;top`）。
      ⚠ 这不是拍脑袋的微调，是把**主导行**对到图标中心：
        · 换图标前的实测：`牛量化` 墨迹中心 30.50、砖内图标墨迹中心 40.00 ⇒ **高 9.5px**；
        · 文字 CSS 当时一字未动 ⇒ 这个 9.5px 早就存在，只是旧的青柠砖 + 19px 图标
          「体量大、够显眼」，把偏差盖住了；牛头更小更淡之后它就露出来了。
        · 为什么不能靠「两行文字盒居中」自动对齐：`align-items:center` 居中的是**盒子**，
          而眼睛称重的是**墨**。9px 的灰色副标本身很淡，视觉重心被 17px 的粗体行拉高。
        · 实测阶梯（**真页 1440 视口**，砖 40 / 牛头墨迹 h25，`牛量化` 像素墨迹中心 − 牛头像素墨迹中心）：
            top:0 → **高 9.5px**（用户报的「看起来比图标高」）
            top:6 → 高 2.5px
            top:8 → 高 **0.5px**   ← 取这一档
            top:10 → 低 1.5px（副标明显吊在砖下面，文字上面开始空）
          判据就是「主导行的墨迹中心 = 图标的墨迹中心」。
          ⚠ 别用「隔离探针页」定这个数：探针与真页会差 ~2px（真页有 `.tab-rail` 参与
            `.topbar` 的行高分配），必须先探针选档、**再回真页复测**。
        · 同时把两行收紧（行高 1.3 → **1.10**、副标上距 3px → **1px**），
          把 `牛量化` 与副标之间的**墨隙从 11px 收到 4px**（用户说的「文字下面比较空」）。
          收紧后文字盒 37.1 → 30.7px，仍矮于 40px 的砖 ⇒ `.brand` 高度由砖决定，
          顶栏高度**不受影响**（顶栏由 52px 的 `.tab-rail` 决定）。
      ⚠ `top:8px` 是**光学**偏移，不是几何偏移：改字号 / 行高 / 副标文案后要重新量
        （`test/visual-baselines/logo-lg2geoB-20261006/README.md` 里有量法）。
   ══════════════════════════════════════════════════════════════════════════ */
.brand-mark{width:40px;height:40px;display:grid;place-items:center;border:0;background:#0A0A0A;border-radius:13px;box-shadow:none}
.brand-mark .brand-logo{width:82%;height:auto;display:block}
/* 文字块：两行收紧 + 整体下移做光学对齐（理由见上）。 */
.brand-text{position:relative;top:8px;display:block}
.brand-name{font-size:17px;letter-spacing:1.5px;font-weight:700;line-height:1.1}
.brand-caption{font:9px var(--font-data);letter-spacing:1.8px;color:var(--text-3);margin-top:1px}
.tab{display:inline-flex;align-items:center;gap:8px;padding:7px 13px;min-height:34px;background:transparent;border:1px solid transparent;border-radius:999px;color:var(--text-2);font-size:13px;white-space:nowrap}
.tab.on{color:#0A0A0A;background:#FFFFFF;border-color:#FFFFFF}
/* 独立页入口的基样式只留排版（2026-10-05 起**不再带 ↗**，箭头已按用户要求去掉）；
   底色/前景/圆角一律由 .tab-rail 内的层决定
   （.breadth-link 只由 app-nav.js 产出，且只出现在青柠条里）。 */
.breadth-link{display:flex;align-items:center;gap:8px;font-size:12px}
.wrap{display:flex;flex:1;min-height:0}
.panel{display:none;min-height:0;min-width:0;flex:1}
.panel.on{display:flex}
.side{width:272px;flex:0 0 272px;display:flex;flex-direction:column;min-height:0;min-width:0;padding:24px 12px 16px;border-right:1px solid var(--border);background:var(--bg-2)}
.side h2{font-size:15px;font-weight:600;margin-bottom:14px;padding:0 8px}
.side-heading{display:flex;align-items:center;justify-content:space-between;padding:0 8px;margin-bottom:14px}
.side-heading h2{padding:0;margin:0}
.side-kicker{font:10px var(--font-data);letter-spacing:1px;color:var(--text-3)}
.strategy-search{display:flex;align-items:center;gap:8px;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:0 10px;margin:0 6px 8px;color:var(--text-3)}
/* 聚焦态**只留一层**描边（2026-10-05，用户反馈「搜索框有叠加的框、还多出一层描边」）。
   改前同时画了三层：① 外框 border 变绿 ② 外框自己的 outline(offset 2px)
   ③ input 继承的全局 :focus-visible 环(offset 3px) —— 而 ③ 是从图标右侧开始算的，
   所以它还会与外框左右错位，看着像两个框叠在一起。
   现在统一成「2px 环贴合外框边缘」这一层：outline-offset:-1px 让环正好压住那 1px 灰边
   （实测：扫描线上只剩 1 条强调色描边带），既不加尺寸也不动布局。
   注意 input 那层的取消必须写成 `input:focus-visible`：上面 `input` 选择器的
   `outline:none` 特指度是 (0,1,1)，压不住第 76 行全局 `:focus-visible` 规则的 (0,2,0)。 */
.strategy-search input{border:0;background:none;padding:9px 0;width:100%;font-size:12px;min-height:38px;outline:none}
.strategy-search:focus-within{outline:2px solid var(--accent-ink);outline-offset:-1px}
.strategy-search input:focus-visible{outline:none}
.strategy-search .icon{width:15px;height:15px}
/* 左栏排序行：指标下拉 + 升降序按钮（指标值本身由后端 stats.py 算，前端只排不计算） */
.strategy-sort{display:flex;align-items:center;gap:6px;margin:0 6px 10px}
.strategy-sort select{flex:1;min-width:0;font-size:12px;padding:6px 8px;min-height:32px}
.strategy-sort button{flex:0 0 auto;width:34px;min-height:32px;padding:0;font-family:var(--font-data);font-size:14px;line-height:1;color:var(--text-2);background:var(--surface)}
.strategy-sort button:hover{color:var(--accent-ink);border-color:var(--accent-ink)}
.list-caption{display:flex;justify-content:space-between;gap:8px;padding:0 9px 9px;color:var(--text-3);font-size:11px}
#stratListWrap{flex:1;min-height:0;overflow:auto}
#stratList{list-style:none}
.sitem{position:relative;border:1px solid transparent;border-radius:9px;padding:12px 11px;margin:2px 0 5px;cursor:pointer;transition:background .16s,border-color .16s}
.sitem:hover{background:var(--surface-2)}
.sitem.sel{background:var(--accent);border-color:var(--accent)}
.sitem.sel:before{content:none}
.sname{font-weight:500;line-height:1.6;font-size:13px;overflow-wrap:anywhere}
.sitem.sel .sname{color:var(--on-accent)}
.ssub{font-size:11px;color:var(--text-3);margin-top:7px;line-height:1.65;font-variant-numeric:tabular-nums}
/* 6 项指标：2 列 × 3 行（累计/年化 · 回撤/Calmar · Sharpe/Sortino），窄栏下不换行截断 */
.ssub .list-metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 10px}
.ssub .list-metrics>span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ssub b{font-family:var(--font-data);font-weight:400;color:var(--text-2)}
.side-footer{border-top:1px solid var(--border);margin:12px 8px 0;padding-top:12px;font:10px var(--font-data);color:var(--text-3);letter-spacing:1px;display:flex;align-items:center;gap:8px}
.side-footer:before{content:'';width:6px;height:6px;border:1px solid var(--accent-ink);border-radius:50%}
.empty-search{padding:24px 12px;color:var(--text-2);font-size:13px;text-align:center}
/* ── 研究线 / 已下架分组（2026-10-06）─────────────────────────────────────────
   左栏三分：生产（#stratList，参与排序与搜索）／研究线／已下架。
   两个折叠组**不参与排序过滤**（applyStratListSort / filterStrategies 只扫 #stratList），
   下架条目保留可点选 —— 「下架」是从生产清单摘牌，不是删数据。 */
/* ── 研究线 / 已下架分组（2026-10-06）─────────────────────────────────────────
   左栏三分：生产（#stratList）／研究线／已下架。
   ★ 铁律：**全栏只有一个滚动容器**（#stratListWrap）。两个 <details> 是它的**子节点**
     （不是兄弟）—— 兄弟写法实测会把生产列表挤成 0 高，且制造 3 层嵌套滚动，
     滚轮进入小组后被内层吃掉（用户 2026-10-06 报「一滚动就出组件视口」）。
     组头 sticky 吸顶，滚动时始终知道在哪一组。 */
.strat-extra{margin:10px 6px 0;border-top:1px solid var(--border);padding-top:2px}
.strat-extra>summary{position:sticky;top:0;z-index:2;background:var(--bg-2);cursor:pointer;list-style:none;color:var(--text-3);font-size:11px;padding:8px 4px;display:flex;justify-content:space-between;gap:8px;user-select:none}
.strat-extra>summary::-webkit-details-marker{display:none}
.strat-extra>summary:before{content:'▸';margin-right:6px;display:inline-block;transition:transform .16s}
.strat-extra[open]>summary:before{transform:rotate(90deg)}
.strat-extra>summary:hover{color:var(--text-1)}
.strat-extra .extra-count{color:var(--text-3)}
/* ⚠ 这里**不得**再写 max-height/overflow —— 那正是被删掉的嵌套滚动源。组内条目直接
   参与外层唯一的滚动流；组的长短不再挤占生产列表。 */
.strat-extra ul{list-style:none;margin:0;padding:0}
/* 组内条目走**紧凑版**：只留名字 + 一行关键指标（年化/回撤/Calmar），
   把"清单本身"露出来（生产列表保留 6 项指标：它是主导航，指标就是它的信息量）。 */
.slist-compact .sitem{padding:7px 9px;margin:1px 0 2px;border-radius:7px}
.slist-compact .sname{font-size:12px;line-height:1.4;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.slist-compact .ssub{margin-top:3px;font-size:10px;line-height:1.35}
.slist-compact .ssub .list-metrics{display:flex;gap:10px;flex-wrap:nowrap;overflow:hidden}
.slist-compact .ssub .list-metrics>span{flex:0 0 auto}
.sitem.is-retired{opacity:.6}
.sitem.is-retired .sname{text-decoration:line-through;text-decoration-color:var(--text-3)}
.sretired-why{font-size:10px;color:var(--text-3);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sitem.is-retired.sel{opacity:1}
.main{flex:1;min-width:0;overflow:auto;padding:24px;background:var(--bg)}
.strategy-heading{display:flex;justify-content:space-between;align-items:center;gap:20px;margin-bottom:18px;min-height:64px}
#stratSubtitle{color:var(--text-2);font-size:12px;margin-top:7px;line-height:1.6;overflow-wrap:anywhere}
.strategy-heading>div:first-child{min-width:0}
/* ── 策略页页头一条带（2026-10-05 用户第 2 轮）────────────────────────────────
   ① 策略名形如「ETF宇宙·高βTop3（满收益）」：split 出来的第一段是分组、第二段才是真标题。
      用户要求把第二段上移到与第一段同一行、同样大小，合成 1 个标题 ⇒ .strategy-title 排成一行，
      两段同用 --title-size；分组压成 --text-2，真标题保持 --text（谁是标题一眼可辨）。
      「保持一定的宽度」= 左列固定占 2/6（grid 2fr），长标题在容器内换行、不撑破布局。
   ② 指标卡片上移与标题容器同起同止（「底部建一个透明容器」= 左列容器贴底放口径信息）。
      左列宽 = 指标网格 2 个轨道 + 1 个 gap（2fr 4fr + 12px gap 的误差 ≤1px），
      所以卡片尺寸与上一版一致，只是整体右移、与标题同一水平带。
   ⚠ 只作用于 #strategyPanel：组合页（index.html#comboPanel）也挂 .strategy-heading，
     但它只有标题、没有 #stratInfo，也没有 .strategy-band，改基类会连带影响它。 */
.strategy-band{display:grid;grid-template-columns:2fr 4fr;column-gap:12px;align-items:stretch;margin-bottom:18px}
#strategyPanel .strategy-heading{display:flex;flex-direction:column;justify-content:space-between;
  align-items:flex-start;gap:12px;margin:0;min-height:0;min-width:0}
.strategy-title{display:flex;align-items:baseline;flex-wrap:nowrap;gap:0 10px;min-width:0}
#strategyPanel #stratTitle{color:var(--text-2)}
/* 真标题可收缩、在容器内自行换行 —— 长名（如「债券腿 · 511010国债ETF(含息估算) · 买入持有」）
   不会把分组挤到下一行，标题始终读作「分组 + 真标题」同行；overflow-wrap 已在 #stratSubtitle 上设过 */
#strategyPanel #stratSubtitle{color:var(--text);font-size:var(--title-size);font-weight:600;
  line-height:1.2;letter-spacing:var(--title-ls);margin:0;flex:1 1 auto;min-width:0}
#stratInfo{text-align:left;flex-shrink:0;font-size:11px;color:var(--text-2);max-width:100%}
.signal-badge{display:inline-flex;align-items:center;gap:6px;margin-top:6px;font:12px var(--font-body);font-weight:500;border:1px solid var(--border-accent);background:#F7F7F7;color:#3F6B00;padding:6px 10px;border-radius:7px}
.signal-badge[data-action="buy"]{color:#9F1239;background:#FFF1F2;border-color:#FECDD3}
.signal-badge[data-action="sell"]{color:#166534;background:#F0FDF4;border-color:#BBF7D0}
.signal-badge:before{content:'';width:5px;height:5px;background:currentColor;border-radius:50%}
#stratStats{margin-bottom:0}
.metric-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}
/* 策略页核心指标 6 项（总收益/年化/回撤/Calmar/Sharpe/Sortino）。
   宽屏（≥1501px）= 4 列 × 2 行，坐在 .strategy-band 的右列：总收益/年化 占第 1、2 列整高，
   右侧四项在第 3、4 列各占半高 ⇒ 2×2 宫格，整块高度与左侧两张整卡等高。
   窄一档放不下宫格里的「标签+数值」同一行 ⇒ 回落成等宽网格（标题回到卡片上方）。 */
#stratStats .metric-grid{grid-template-columns:repeat(4,minmax(0,1fr))}
@media(min-width:1501px){
  /* height:100% 让网格跟着 .strategy-band 的行高走：标题换行把带拉高时（长策略名），
     卡片也跟着长高，底边仍与左列的口径信息齐平 —— 否则底下会吊着一条几 px 的空档。 */
  #stratStats .metric-grid{height:100%;grid-template-rows:1fr 1fr}
  #stratStats .metric-card:nth-child(1){grid-column:1;grid-row:1/3}
  #stratStats .metric-card:nth-child(2){grid-column:2;grid-row:1/3}
  #stratStats .metric-card:nth-child(3){grid-column:3;grid-row:1}
  #stratStats .metric-card:nth-child(4){grid-column:4;grid-row:1}
  #stratStats .metric-card:nth-child(5){grid-column:3;grid-row:2}
  #stratStats .metric-card:nth-child(6){grid-column:4;grid-row:2}
  /* 右侧四项 = 半高小卡（一行高度约 53px）：第一行「图标 + 标签 …… 数值」，第二行口径/全区间。
     内距、字号、圆角、角标同步收一档；右上角缺口是给 28px 角标留位的，这里角标改为行内 ⇒ 去掉缺口。 */
  #stratStats .metric-card:nth-child(n+3){
    display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-rows:auto auto;
    align-content:space-between;column-gap:8px;row-gap:2px;
    padding:9px 12px 8px;border-radius:12px;background-image:none;
  }
  #stratStats .metric-card:nth-child(n+3) .metric-label{
    grid-column:1;grid-row:1;justify-content:flex-start;gap:5px;
    margin:0;min-height:0;padding-right:0;font-size:10.5px;
  }
  #stratStats .metric-card:nth-child(n+3) .metric-icon{
    position:static;order:-1;flex:0 0 auto;width:17px;height:17px;
  }
  #stratStats .metric-card:nth-child(n+3) .metric-icon .icon{width:10px;height:10px}
  #stratStats .metric-card:nth-child(n+3) .metric-value{
    grid-column:2;grid-row:1;align-self:center;text-align:right;
    font-size:16px;line-height:1.2;letter-spacing:-.6px;
  }
  /* overflow:hidden 顺带把网格项的自动最小尺寸降为 0，否则 nowrap 的口径行会把列撑开 */
  #stratStats .metric-card:nth-child(n+3) .metric-note{
    grid-column:1/3;grid-row:2;margin:0;font-size:9px;line-height:1.3;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  }
}
@media(max-width:1500px){
  #stratStats .metric-grid{grid-template-columns:repeat(3,minmax(0,1fr))}
  /* 窄屏并排放不下「标题容器 + 指标卡片」⇒ 退回上下堆叠（标题回到卡片上方，与改版前一致） */
  .strategy-band{display:block}
  #strategyPanel .strategy-heading{margin-bottom:18px}
}
@media(max-width:900px){#stratStats .metric-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
.metric-card{padding:14px 16px 12px;background-color:var(--surface);background-image:radial-gradient(circle var(--notch-r) at calc(100% - var(--notch-x)) var(--notch-y),var(--bg) 99%,transparent 100%);background-origin:border-box;background-clip:border-box;border:0;border-radius:16px;box-shadow:var(--shadow-1);position:relative;overflow:visible}
.metric-card:first-child{background-color:#0A0A0A;box-shadow:var(--shadow-2)}
.metric-label{display:flex;align-items:center;justify-content:space-between;color:var(--text-2);font-size:12px;margin-bottom:8px}
.metric-index{font:9px var(--font-data);color:var(--text-3)}
.metric-value{font:500 clamp(20px,2vw,29px)/1.2 var(--font-data);letter-spacing:-1.3px;white-space:nowrap}
.metric-value small{font-size:.58em;letter-spacing:0;margin-left:3px;color:var(--text-2)}
.metric-card:first-child .metric-value{color:#FFFFFF}
.metric-note{font-size:10px;color:var(--text-3);margin-top:7px}
.chart-workspace{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:0 16px 14px;position:relative}
.chart-heading{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:49px;border-bottom:1px solid var(--border)}
.chart-heading h2{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500;white-space:nowrap}
.chart-heading h2 .icon{color:var(--accent-ink);width:16px;height:16px}
.chart-heading h2 small{font:9px var(--font-data);letter-spacing:1px;color:var(--text-3);margin-left:2px}
.chart-options{display:flex;align-items:center;gap:12px;font-size:11px;color:var(--text-2)}
.chart-options>label{display:flex;align-items:center;gap:5px}
.chart-options select{font-size:11px;max-width:160px;min-height:30px}
.display-options{position:relative;z-index:10}
.display-options summary{list-style:none;display:flex;align-items:center;gap:5px;padding:7px;color:var(--text-2);border-radius:6px;cursor:pointer}
.display-options summary::-webkit-details-marker{display:none}
.display-options summary:hover,.display-options[open] summary{color:var(--accent-ink);background:var(--accent-soft)}
.display-options summary .icon{width:15px;height:15px}
.options-popover{position:absolute;right:0;top:36px;width:185px;border:1px solid var(--border-strong);border-radius:9px;background:var(--surface-2);box-shadow:0 10px 30px rgba(10,10,10,.18);padding:10px}
.options-popover label{display:flex;gap:9px;align-items:center;min-height:36px;font-size:12px;cursor:pointer}
.range-toolbar{display:flex;align-items:center;gap:7px;flex-wrap:wrap;padding:12px 0 9px;font-size:11px;color:var(--text-2)}
.range-toolbar input,.range-toolbar select,.range-toolbar button{min-height:30px;font-size:11px;padding:4px 8px}
.range-toolbar .range-label{margin-right:4px}
.range-toolbar button.primary{background:#0A0A0A;color:#FFFFFF;border-color:#0A0A0A}
.range-toolbar button.primary:hover{background:#262626}
#stratQuickRange{margin-left:auto;max-width:132px}
#stratRangeStats{font-size:11px;color:var(--text-2);padding:7px 0 0;min-height:24px;display:flex;align-items:center;gap:7px;flex-wrap:wrap}
#stratRangeStats span{color:var(--accent-ink)!important}
#stratChartWrap{position:relative;width:100%}
#stratGrid0{height:270px;width:100%;margin-bottom:22px}
#stratGridDD{height:140px;width:100%;margin-bottom:22px}
#stratGrid1{height:250px;width:100%;margin-bottom:22px}
#stratGrid2{height:280px;width:100%}
#klineChartLabel{position:absolute;left:58px;z-index:6;display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text-2);background:var(--surface);padding:0 7px}
#klineIdxSel{height:26px;min-height:26px;font-size:11px;max-width:145px;padding:2px 5px}
#stratTimeAxis{position:relative;margin:-6px 32px 0 50px;height:30px;border-top:1px solid var(--border);overflow:hidden}
#stratTicks{position:absolute;top:0;left:0;right:0;height:16px;color:var(--text-2);font-size:10px;padding:0 4px}
#stratHoverTag,#stHoverTag{position:absolute;top:2px;left:-999px;background:var(--chart-tag);color:#fff;font-size:10px;padding:1px 5px;border-radius:3px;white-space:nowrap;z-index:6}
.section-label{display:flex;align-items:center;gap:10px;font:10px var(--font-data);letter-spacing:1.4px;color:var(--text-3);margin:23px 0 12px}
.section-label:after{content:'';flex:1;height:1px;background:var(--border)}
.research-detail{border:1px solid var(--border);border-radius:9px;background:var(--surface);margin:8px 0;font-size:12px;overflow:hidden}
.research-detail>summary{padding:13px 16px;font-size:13px;font-weight:500;cursor:pointer;color:var(--text-2)}
.research-detail>summary:hover{color:var(--text);background:var(--surface-2)}
.research-detail[open]>summary{color:var(--text);border-bottom:1px solid var(--border)}
.detail-body{padding:14px 16px;overflow:auto;color:var(--text-2);line-height:1.8}
.detail-metrics{display:flex;flex-wrap:wrap;gap:12px 24px;font-size:12px;margin-bottom:15px}
.detail-metrics b{font-family:var(--font-data);font-weight:400;color:var(--text);margin-left:5px}
#stratCmpTable{overflow:auto}
#stratCmpTable table{min-width:500px}
table{border-collapse:collapse;width:100%;font-size:12px;font-variant-numeric:tabular-nums}
th{font-weight:500;color:var(--text-2);background:var(--surface-2)}
td,th{padding:9px 10px!important;border-bottom:1px solid var(--border);text-align:left}
tr:hover td{background:#F4F4F4}
td{color:var(--text-2)}
#stratLog,#stratMono,#timingCompare,#stockLog{max-height:420px;overflow:auto;padding:0 14px 8px}
#stratPrinciplesBody{line-height:1.9}
.load-state{padding:10px 14px;border-radius:8px;margin-bottom:12px;background:var(--accent);color:var(--on-accent);font-size:12px}
.load-state:empty{display:none}
.load-state.error{color:#9F1239;background:#FFF1F2}
.load-state button{margin-left:12px;font-size:12px}
.toolbar{display:flex;gap:8px;margin-bottom:12px}
.toolbar input{flex:1;min-width:0}
.toolbar input,.toolbar select{font-size:12px;width:auto}
/* 个股搜索框（.toolbar 里唯一的 input）聚焦态同样只留一层（2026-10-05，与 .strategy-search 同一处反馈）：
   改前是「border 变绿(第 75 行) + 全局 :focus-visible 2px 外环(第 76 行)」两层，
   现在只保留贴合边缘的那一层 2px 环，边界从「两层绿」回到「一条 2px 绿边」。 */
.toolbar input:focus{border-color:var(--border)}
.toolbar input:focus-visible{outline:2px solid var(--accent-ink);outline-offset:-1px}
.ltab{flex:1;padding:7px 6px;background:transparent;font-size:11px;color:var(--text-2);white-space:nowrap}
.ltab.on{background:#0A0A0A;color:#FFFFFF;border-color:#0A0A0A}
#vlist{flex:1;min-height:0;overflow:auto;position:relative}
#spacer{width:100%}
#list{list-style:none;position:absolute;top:0;left:0;right:0}
#list li{padding:8px;border-radius:8px;cursor:pointer;font-size:12px;border:1px solid transparent}
#list li:hover{background:var(--surface-2)}
#list li.sel{background:var(--accent-soft);border-color:var(--border-accent)}
#list .nm,#list .nm2{font-weight:500}
#list .mt,#list .mt2{font-size:10px;color:var(--text-3)}
.star{color:var(--text-3);cursor:pointer;font-size:15px;margin-right:5px}
.star.on{color:var(--amber)}
#title{margin-bottom:12px}
#stats{display:block;margin-bottom:18px}
#stats b{color:var(--text)}
#stockSecondary{margin:14px 0 0}
.badge{display:inline-block;padding:3px 9px;border-radius:5px;font-size:11px;color:#fff;margin-left:6px}
.b-牛{background:#B91C1C}.b-熊{background:#0F766E}.b-震荡{background:#A16207}
.spinner{display:inline-block;width:13px;height:13px;border:2px solid var(--border-strong);border-top-color:var(--accent-ink);border-radius:50%;animation:sp .8s linear infinite;vertical-align:-2px;margin-right:6px}
@keyframes sp{to{transform:rotate(360deg)}}
.loading{color:var(--text-2);font-size:12px}

@media(min-width:1500px){.main{padding:28px 32px}.metric-card{padding:18px 20px}#stratGrid0{height:340px}}
@media(max-width:1100px){.side{width:240px;flex-basis:240px}.main{padding:18px}.metric-grid{gap:8px}.metric-card{padding:12px}.chart-heading h2 small{display:none}.chart-options{gap:8px}.metric-value{font-size:23px}}
@media(max-width:900px){
  html,body{height:auto;min-height:100%;overflow:auto}
  .panel.on{flex-direction:column}.wrap{display:block}.side{width:100%;flex:auto;border-right:0;border-bottom:1px solid var(--border);padding:16px;max-height:245px}.side-heading{margin-bottom:9px}.strategy-search{margin-bottom:7px}.side-footer{display:none}.list-caption{padding-bottom:4px}#stratListWrap{max-height:none;min-height:0}#strategyPanel .side{max-height:44vh}.sitem{padding:8px 12px}.ssub{margin-top:2px}.main{overflow:visible;padding:20px 16px}.strategy-heading{gap:12px}.chart-options{gap:7px}.chart-heading h2 small{display:inline}#stratTitle{font-size:24px}#strategyPanel #stratTitle,#strategyPanel #stratSubtitle{font-size:22px}.metric-value{font-size:24px}.metric-grid{gap:10px}footer{flex-wrap:wrap;position:static}#footStatus{text-align:left}
  #vlist{min-height:120px}#stockPanel .side{max-height:275px}#stockChart{max-width:100%}
}
@media(max-width:600px){
  .side{padding:12px;max-height:220px}#stratListWrap{max-height:none;min-height:0}#strategyPanel .side{max-height:46vh}.main{padding:18px 12px}.strategy-heading{display:block;margin-bottom:16px}#stratInfo{text-align:left;max-width:100%;margin-top:12px;display:flex;align-items:center;gap:10px;flex-wrap:wrap}.signal-badge{margin-top:0}#stratTitle{font-size:23px}#strategyPanel #stratTitle,#strategyPanel #stratSubtitle{font-size:19px}
  .metric-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:9px}.metric-card{padding:13px}.metric-value{font-size:25px}.metric-note{font-size:10px}.metric-label{font-size:12px}#stratStats{margin-bottom:16px}.chart-workspace{padding:0 10px 10px}.chart-heading{align-items:flex-start;flex-direction:column;gap:8px;padding:12px 0}.chart-heading h2 small{display:inline}.chart-options{width:100%;justify-content:space-between;gap:6px}.chart-options select{max-width:125px}.chart-options>label{font-size:10px}.display-options summary{min-height:36px}.range-toolbar{gap:7px}.range-toolbar input{width:calc(50% - 16px);max-width:none;min-height:38px;font-size:11px}.range-label{display:none}.range-toolbar button,.range-toolbar select{min-height:38px;font-size:11px}#stratQuickRange{max-width:110px}#stratGrid0{height:280px}#stratRangeStats{font-size:10px}.chart-options select{min-height:36px}#klineChartLabel{left:44px;font-size:10px}#klineIdxSel{max-width:128px}.detail-metrics{gap:10px;flex-direction:column}.research-detail>summary{font-size:12px;padding:14px 12px}footer{padding:10px 12px;line-height:1.8}
}
/* Local Lucide SVG symbols: one stroke family across the research workspace.
   ⚠ 线宽**只有上面 .icon 基类一处**（2026-10-07 起）。
   这一行原先再写一遍 `stroke-width:1.8` 覆盖基类的 1.7 —— 两个值各自会漂移，
   且读代码的人以为这里是唯一来源、改基类不生效。已删，值统一在基类。
   Lucide 上游是 24 格 / 2px；本站渲染尺寸 10–18px（`.icon` 18px，徽章 11px），
   2.1 相当于同样渲染尺寸下比上游默认约 +5% —— 用户 2026-10-07 反馈「线条过细」后的取值。
   实测依据：1.8 / 2.1 / 2.4 × 15/17/18/22px 的并排扫描图（本轮归档）。 */
.side-heading h2{display:flex;align-items:center;gap:8px}
.side-heading h2 .icon{width:17px;height:17px;color:var(--accent-ink)}
.metric-label{padding-right:34px;min-height:18px}
.metric-icon{position:absolute;right:-12px;top:-12px;display:grid;place-items:center;width:28px;height:28px;border:0;border-radius:50%;background:#0A0A0A;color:#FFFFFF}
.metric-icon .icon{width:15px;height:15px}
.metric-icon-1{color:#5B21B6;background:#F5F3FF;border-color:#DDD6FE}
.metric-icon-2{color:#92400E;background:#FFFBEB;border-color:#FDE68A}
.metric-icon-3{color:#0F766E;background:#F0FDFA;border-color:#99F6E4}
.range-label{display:inline-flex;align-items:center;gap:5px}
.range-label .icon{width:13px;height:13px}
:is(#applyStratRange,#resetStratRange,#applyStRange,#resetStRange,.ltab){display:inline-flex;align-items:center;justify-content:center;gap:5px}
:is(#applyStratRange,#resetStratRange,#applyStRange,#resetStRange) .icon{width:13px;height:13px}
.ltab .icon{width:12px;height:12px}
.research-detail>summary{display:flex;align-items:center;gap:11px;list-style:none}
.research-detail>summary::-webkit-details-marker{display:none}
.research-detail .detail-icon{color:var(--accent-ink);width:17px;height:17px}
.research-detail .detail-chevron{margin-left:auto;width:15px;height:15px;color:var(--text-3);transition:transform .18s}
.research-detail[open] .detail-chevron{transform:rotate(180deg)}
@media(max-width:600px){.range-label{display:none}.ltab{gap:4px}.metric-icon{width:24px;height:24px}.research-detail>summary{gap:9px}}
@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation:none!important;transition:none!important;scroll-behavior:auto!important}}
/* 「券源口径」显式提示（2026-09-16）：48% 是「PIT 市值≥60亿」必要条件子集、**不是**真实可借券源，
   原先只放在 <option title=…> 里，用户不 hover 就看不见 → 会读成「真能借到这么多」。
   flex-basis:100% 让它在 .range-toolbar（flex-wrap）里独占一行。
   注意 .caliber-warn[hidden] 必须显式 display:none —— display:flex 会盖掉 hidden 的默认行为。 */
.caliber-warn{flex-basis:100%;display:flex;align-items:baseline;gap:6px;margin:2px 0 0;
  padding:7px 10px;border-radius:var(--radius-sm);font-size:10.5px;line-height:1.55;
  border:1px solid var(--border);color:var(--text-2);background:var(--surface-2)}
.caliber-warn[hidden]{display:none}
.caliber-warn>b{flex:none;font-weight:700}
.caliber-warn.is-warn{color:#92400E;background:#FFFBEB;border-color:#FDE68A}
.caliber-warn.is-warn>b{color:var(--amber)}
.caliber-warn.is-info>b{color:var(--accent-ink)}

/* ══════════════════════════════════════════════════════════════════════════
   设备 1 · 顶栏（2026-09-30；同日三次调整：品牌出胶囊 → 绿/黑对调 → 删掉外框）
     .topbar 顶栏行（灰底）
       ├─ .brand        品牌，落在页面底色上
       └─ .tab-rail     黑胶囊（顶栏里唯一的黑面，本身就是最外层）
            ├─ .rail-left   3 个主应用入口
            ├─ .nav-stats   青柠「状态岛」：数据截至 / 市况 / 持仓
            └─ .rail-right  3 个独立页入口
   状态读数在正中央，像灵动岛：两翼各 3 个入口。
   居中用 grid 的 `minmax(0,1fr) auto minmax(0,1fr)`（两翼列宽恒相等 ⇒ 岛是
   大胶囊的几何中心），不用 flex:1 —— flex 的 min-width:auto 会让内容较宽的
   一侧（右翼的「高β广度仪表」）多占，岛随之偏移，实测最坏偏 29px。

   2026-09-30 三次调整：原先 .tabs / .tab-rail 两层都是 #0A0A0A，外圈的
   7px padding 只是「看不见的黑边」，却把整条 bar 从 52px 撑到 66px。
   现在整层删掉（DOM 里也删了），黑面就是 .tab-rail 自己：
   bar 高 66 → 52，左右各向外 7px —— 实测黑面 x 176→169、宽 1260→1274。

   ⚠ 绿/黑对调不是换两个 background 就完事：两层的 chip / 字色 / 市况三态 /
     焦点环全部要跟着翻。规则是「谁坐在谁上面」——
       · 黑大胶囊上：chip 提亮一档 rgba(255,255,255,.10)，字亮色
       · 青柠岛上：  chip 压暗一档 rgba(10,10,10,.10)，字用 --accent-ink / 深三态
   ══════════════════════════════════════════════════════════════════════════ */

/* 大胶囊：黑。**宽度按内容来**（2026-10-05 起不再 flex:1 撑满）。
   高度链：岛 42 → 条内高 42 → 条 42+2×5 = 52。
   岛是「贴满条内高」的，所以想让岛变高只能整条一起长 —— 不能只加岛的 height。

   2026-10-05 用户要求：「黑色胶囊的宽度做成自适应，根据里面的内容和屏幕宽度自适应，
   默认根据内容来测量总宽度；屏幕小就做成 2 行或者 3 行，分组一下」。
   于是宽度与行数**不再用断点猜**：`lib/app-nav.js` 量出三组的自然宽（左/岛/右），
   算出三种行布局各自需要多宽，挑放得下的最小行数，写 `data-rows` 与 `--wing`：
     · data-rows=1：两列 `--wing` 等宽 ⇒ 两翼等宽 ⇒ **岛恒在胶囊几何中心**
       （`--wing` = max(左组, 右组)，窄的那一翼补白；这是用户 2026-10-05 选定的口径）
     · data-rows=2：岛独占第一行居中，左翼｜右翼 一行在**下面**
     · data-rows=3：岛 / 左翼 / 右翼 各一行（岛仍在最上面）
   换行时岛为什么在最上面（而不是夹在中间）——用户 2026-10-05 的要求：
     「如果换行，绿色胶囊放上面，其他放下面。你现在刚好相反了。」
     用 `order:-1` 而不是改 DOM：岛里没有可聚焦元素（role=group + 三个 span），
     所以「视觉顺序 ≠ DOM 顺序」不会造成焦点顺序错乱；链接之间的相对顺序一个没动。
   `--wing` 没写出来时退回 `auto auto auto`（无 JS 时的兜底：仍是内容宽，只是岛不居中）。 */
.tab-rail{flex:0 0 auto;max-width:100%;display:grid;align-items:center;gap:5px;padding:5px;
  border-radius:999px;background:#0A0A0A;
  grid-template-columns:var(--wing,auto) auto var(--wing,auto)}
/* 两翼：按内容宽靠外缘站。一行态下列宽 = --wing（等宽）⇒ 岛恒居中。 */
.rail-group{display:flex;align-items:center;gap:5px;min-width:0}
.rail-left{justify-self:start}
.rail-right{justify-self:end}
.nav-stats{justify-self:center}
/* 多行态的胶囊不能再是胶囊形（999px 圆角会变成两端半圆）：收成 22px。 */
/* 两行态两条轨道都用 auto（= 各自的内容宽）。不能用 1fr 1fr：胶囊宽度是内容宽，
   定宽下 1fr 会均分，右翼 510px 只拿到 410px ⇒ 组内被迫折行（实测 900px 就是如此）。 */
.tab-rail[data-rows="2"]{grid-template-columns:auto auto;row-gap:5px;border-radius:22px}
/* 换行态：青柠状态岛提到第一行（用户 2026-10-05）。两行态它横跨两条轨道并居中。 */
.tab-rail[data-rows="2"]>.nav-stats{order:-1;grid-column:1/-1;justify-self:center}
.tab-rail[data-rows="3"]{grid-template-columns:1fr;row-gap:5px;border-radius:22px}
.tab-rail[data-rows="3"]>.nav-stats{order:-1}
/* 三行态（岛 / 左翼 / 右翼）：两翼各占一行，且**共用同一条中轴**。
   ⚠ 2026-10-05 用户指出「持续缩小时顶部胶囊会变成不对齐的 3 行」——
   根因是 `.rail-left{justify-self:start}` / `.rail-right{justify-self:end}` 在多行态照样生效：
   左翼贴左缘、右翼贴右缘，右翼放不下时还在组内折行 ⇒ 视觉上是「左对齐 + 右对齐 + 一条孤零零的尾巴」。
   这里把两翼改成 `justify-self:stretch` + 组内 `justify-content:center`：
   每一行都以胶囊中线对齐，一行放不下时也只是在同一中轴上多折一行。
   （配合 app-nav.js 的 4/4 分组：常见窄屏下两翼各只占一行 ⇒ 干净的 3 行。） */
.tab-rail[data-rows="3"] .rail-group{justify-self:stretch;flex-wrap:wrap;justify-content:center}
/* JS 没跑成的兜底（`data-rows` 缺失）：胶囊允许被压窄、组内允许折行 —— 否则
   「flex:0 0 auto + 组内 nowrap」在 900–1100px 会整条顶出页边距（实测 1024px 越界 61px）。 */
.tab-rail:not([data-rows]){flex:0 1 auto}
.tab-rail:not([data-rows]) .rail-group{flex-wrap:wrap;justify-content:center}
/* 品牌要不要独占一行：由 app-nav.js 量出来（「独占后行数更少」才独占），
   不再是写死的 ≤900 断点 —— 否则 956→900 会出现 3 行→2 行的回跳。 */
/* 品牌要不要独占一行由 JS 定：只需允许换行，品牌**不撑行** ——
   撑行（flex:1 1 100%）会让品牌盒宽变成整行宽，量宽度的人拿到 1560 就会误判并锁死。 */
.topbar[data-stack="1"]{flex-wrap:wrap}
.topbar[data-stack="1"] .tab-rail{margin-top:2px}
/* 品牌出胶囊后落在 --bg 上：黑底白字那套（原来的 .tabs .brand-name{#fff}）必须换掉，
   否则白字压 #E9E9E9 只有 1.05:1。字色显式写死 --accent-ink，不靠 <a> 继承
   （继承的是「链接色」，改链接色会顺手改掉品牌）。实测 6.27:1。 */
.topbar .brand-name{color:var(--accent-ink)}
.tab-rail .tab,
.tab-rail .breadth-link{
  display:inline-flex;align-items:center;gap:7px;flex:0 0 auto;
  padding:8px 13px;min-height:40px;           /* 条长高后小胶囊跟着长高；横向内距不动 ⇒ 宽度不变 */
  border:0;border-radius:999px;
  background:rgba(255,255,255,.10);        /* 小胶囊：比黑底提亮一档 */
  color:rgba(255,255,255,.88);font-size:13px;white-space:nowrap}
.tab-rail .tab:hover,
.tab-rail .breadth-link:hover{background:rgba(255,255,255,.18);color:#FFFFFF;border:0}
.tab-rail .breadth-link{font-size:12px}
/* 「当前」= 白胶囊 + 黑字，是黑层里唯一的满亮块（青柠让给状态岛，
   免得「我在这儿」和「市况读数」两个绿色抢同一个语义）。 */
.tab-rail .tab.on{background:#FFFFFF;color:#0A0A0A;border:0}
.tab-rail .tab.on .icon{color:#0A0A0A}

/* 状态岛：大胶囊正中的青柠胶囊（高度吃满内高，两侧各留 5px 黑）。
   岛上沿用原来「青柠承载条 + 略深一档小胶囊」的语言。
   2026-09-30 二次放大：k 9→11px、v 13→15px、chip 内距 4/11→5/12 —— 原来 9px 的
   「数据截至」实测只有 7.5pt，肉眼发虚。字号变大后岛会变宽约 35px，
   单行断点随之从 1280 上移，见下面 ≤1420 的收紧档。 */
.nav-stats{flex:0 0 auto;display:flex;align-items:center;gap:4px;padding:0 5px;
  height:42px;border-radius:999px;background:var(--accent)}
.nav-stats .stat{display:flex;align-items:baseline;gap:6px;padding:5px 12px;
  border-radius:999px;background:rgba(10,10,10,.10);white-space:nowrap}
.nav-stats .stat-k{font:11px/1 var(--font-data);letter-spacing:.8px;color:var(--accent-ink)}
.nav-stats .stat-v{font:600 15px/1.2 var(--font-data);color:var(--on-accent)}
.nav-stats .stat-v.is-bull{color:var(--state-bull-lime)}
.nav-stats .stat-v.is-bear{color:var(--state-bear-lime)}
.nav-stats .stat-v.is-flat{color:var(--state-flat-lime)}
.nav-stats .stat-v.is-lag{color:var(--state-flat-lime)}

/* ── 第三格「持仓」= 按钮 + 悬浮气泡（2026-10-14 用户要求）─────────────────────
   用户：「页头顶部 持仓 0% 的这个是否能做成一个按钮，点击的时候出一个悬浮气泡…」

   ⚠ **必须逐 px 复刻 `.stat` 的盒模型**：状态岛的宽/高是 `lib/app-nav.js::layoutRail()`
     量出来的（`natWidth` = 子项宽 + gap + 内距），一行/两行/三行的分档全靠它。
     而全局 `button{...}`（terminal.css:69-73）会给按钮加 `border:1px`、
     `border-radius`、`background`、`min-height:34px`、`padding:6px 10px`
     —— 任何一条留着都会让岛宽变化 ⇒ **窄一档就多折一行**（实测过同类事故：
     岛宽从 325 长到 390 没人重算断点，1280px 下直接压住右翼入口）。
     所以这里把按钮默认样式**全部**抹掉，再按 `.stat` 的值写一遍。
     ⚠ `.stat-v` 等子元素的选择器是 `.nav-stats .stat-v`（0,2,0），本按钮自己的规则不能
       覆盖它们；这里只碰按钮**自身**的盒模型与交互态。 */
.nav-stats .stat-btn{appearance:none;-webkit-appearance:none;margin:0;font:inherit;
  border:0;min-height:0;cursor:pointer;color:inherit;text-align:left;
  transition:background .16s}
.nav-stats .stat-btn:hover{background:rgba(10,10,10,.18)}
.nav-stats .stat-btn:active{background:rgba(10,10,10,.26)}
.nav-stats .stat-btn[aria-expanded="true"]{background:rgba(10,10,10,.26)}
/* 展开提示箭头：从 `.stat-k` 右侧吊着，**不进 baseline 基线**（否则"持仓"与数值会错位）。
   宽度算进岛宽 ⇒ 一行的分档会跟着变：这是有意的（多一个可点的信号），但必须在
   1280 / 1024 / 768 三档重测（见本轮交付说明的实测数字）。 */
.nav-stats .stat-caret{font-size:9px;line-height:1;opacity:.55;margin-left:1px;
  align-self:center;pointer-events:none}

/* 气泡本体。定位靠 JS（`posPlace()`），这里只给外观与"任意视口都不溢出"的上限。
   ⚠ 它是 `#appNav` 的子节点、`position:absolute` ⇒ 相对 `.topbar`（唯一有定位的祖先）。
     `.topbar` 到 `body` 之间没有 `overflow` 容器（`body{overflow:hidden}` 在更外层、
     且气泡在顶栏内部不会被裁），所以不需要 portal 到 body。 */
.pos-panel{position:absolute;z-index:60;width:min(430px,calc(100vw - 24px));
  max-height:min(82vh,760px);overflow:auto;padding:12px 14px 10px;
  border:1px solid var(--border-strong);border-radius:12px;background:var(--surface);
  box-shadow:0 14px 40px rgba(10,10,10,.22);text-align:left;color:var(--text);
  font:12px/1.55 var(--font-body)}
.pos-panel[hidden]{display:none}
.pp-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}
.pp-head>b{flex:1;font-size:13px}
.pp-x{appearance:none;border:0;background:transparent;color:var(--text-3);font-size:16px;
  line-height:1;width:24px;height:24px;min-height:0;padding:0;border-radius:6px;cursor:pointer}
.pp-x:hover{background:var(--surface-3);color:var(--text)}
.pp-note{margin:8px 0 0;color:var(--text-3);font-size:11px;line-height:1.6}
.pp-note>summary{cursor:pointer;color:var(--text-3);min-height:0;list-style:none}
.pp-note>summary::-webkit-details-marker{display:none}
.pp-note>summary::before{content:"▸ ";font-size:9px}
.pp-note[open]>summary::before{content:"▾ "}
.pp-note>p{margin:5px 0 0}
.pp-sec{margin:0 0 10px}
.pp-sec h3{margin:0 0 2px;font-size:12px;font-weight:700}
.pp-hint{margin:0 0 5px;color:var(--text-3);font-size:11px;line-height:1.55}
.pp-warn{margin:0 0 5px;color:var(--amber);font-size:11px;line-height:1.55}
/* 主表（2026-10-14 第二版：三段合一张 4 列表）
   ⚠ 用**真 `<table>`** 而不是 flex 行：列对齐要靠表格算法（数字列右对齐 + 等宽数字），
     而 flex 行在"名字长短不一"时会各列错位。窄屏用外层 `.pp-scroll` 横滑 ——
     **不要**给 `table` 自己 `overflow`（那会在 Safari 上把 caption 一起裁掉）。 */
/* ⚠ `table-layout:fixed` + 显式列宽是**必须**的，不是审美选择：
   2026-10-14 实测（用户截图）：auto 布局下"今日 14:30 窗口"这个较长的列头把列撑宽，
   总宽超出容器 ⇒ 右侧列被裁出一条**竖长灰块**（看着像"名字后面跟了一个被切掉的框"）。
   固定布局让每一列宽度可预期、长名字走 `text-overflow:ellipsis`，
   同时把列头也纳入 `overflow:hidden` —— 这样"裁掉"永远是一个省略号，而不是半截色块。
   列宽口径：第 1 列吃掉剩余（名字最长），三个数值/短标签列各 72px
   （"今日 14:30 窗口"正好落在 72×3=216 里，480px 档把第 1 列压到约 110px）。 */
.pp-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}
.pp-table{width:100%;border-collapse:collapse;table-layout:fixed;
  font:12px/1.5 var(--font-body)}
.pp-table col.pp-c1,.pp-table col.pp-c2,.pp-table col.pp-c3{width:68px}
.pp-table caption{caption-side:top;text-align:left;color:var(--text-3);font-size:10.5px;
  padding:0 0 4px}
/* ⚠ `padding` 必须带 `!important`：terminal.css:350 有一条**全局**
   `td,th{padding:9px 10px!important;border-bottom:1px solid var(--border);text-align:left}`。
   实测（2026-10-14）没有它时表头拿到的是 **9px/10px**，列头被撑到 70px 高、`text-align:left`
   把数值列的右对齐也一起顶掉 —— 而 `!important` 只能靠 `!important` 压。
   这是本项目里少数几处合规的 `!important`（理由与那两条全局规则写在一起）。 */
.pp-table th,.pp-table td{padding:3px 2px!important;border-bottom:1px solid var(--border);
  overflow:hidden;text-overflow:ellipsis}
/* ⚠ 列头**必须换行**，不能 nowrap：2026-10-14 用户截图报「名字后面有个被裁剪的框」——
   根因是 "今日 14:30 窗口" 这一列在 72px 里 nowrap ⇒ 文字被硬切在列边界上，
   右侧露出半截灰块（看着像一个框）。列头是短标签，换行没有代价；数据格才需要 nowrap。 */
.pp-table thead th{font-size:10.5px;font-weight:600;color:var(--text-2);text-align:right;
  white-space:normal;line-height:1.25;border-bottom:1px solid var(--border-strong)}
.pp-table thead th:first-child{text-align:left}
.pp-thsub{display:block;font-weight:400;color:var(--text-3);font-size:10px}
.pp-name{text-align:left;font-weight:400;white-space:nowrap}
/* 三列的语义差**只靠这四个类**表达：
     · is-ok     = 当日 14:30 现算出来的真值（实线、深色）；「次日」列里也用它表示"有 T+1 计划"
     · is-same   = 收盘口径 ⇒ 值**就是**「已确认」那一格（浅一档 + 左侧虚线，防"重复数字=算错"）
     · is-wait   = 定义上要**下一个时点**才能有值（「次日」列：要明日 14:30 的价）
                   ⚠ 与 is-pending 不同：它不是"没接入/读不到"，是"到点自然就有"
     · is-pending= 还没到 / 未接入 / 读不到（"待定"）
   颜色不是唯一手段：is-same 另有虚线 + `title`，is-wait/is-pending 另有文字本身。 */
/* ⚠ 选择器必须是 `.pp-table .pp-v`（0,2,0）：全局 `td,th{text-align:left}` 是 (0,1,0)，
   但它在样式表里更靠后 ⇒ 只写 `.pp-v` 会被它按"同特指度、后者胜"压成左对齐（实测踩到）。 */
.pp-table .pp-v{text-align:right;font-family:var(--font-data);font-weight:600;
  font-variant-numeric:tabular-nums;white-space:nowrap}
.pp-v.is-ok{color:var(--text)}
/* ⚠ 颜色**不是**涨跌色，是"有没有仓位"（2026-10-14 用户指定：「有持仓的数据标红，
   持仓为 0% 的标绿」）。取的是站内**当文字用**的那一对（白/浅底实测 ≥4.5:1）：
   `--ink-up` 红 = 有仓位（>0）、`--ink-down` 青绿 = 空仓（0）。
   口径写在注释里是因为它与"红涨绿跌"用的是同一对颜色、含义却不同 —— 下一个人容易改错。 */
.pp-v.is-hold{color:var(--ink-up);font-weight:700}
.pp-v.is-zero{color:var(--ink-down);font-weight:600}
/* "值就是已确认那一格"（收盘口径，盘中不变）：虚线 + 降一档不透明度。
   ⚠ 只用 opacity 压淡会连带把红/绿也压暗 ⇒ 同时保留色相（用户要看的正是颜色）。 */
.pp-v.is-same{border-left:2px dotted var(--border-strong);opacity:.72}
.pp-v.is-pending{color:var(--text-3);font-weight:500;font-size:11px}
/* 「待明日 14:30」：等的是**下一个时点**，与"未接入"要能一眼分开。
   用虚线下边框（不是左边框，避免与 is-same 的视觉语言混）+ 中等灰。 */
.pp-v.is-wait{color:var(--text-2);font-weight:500;font-size:11px;
  border-bottom:1px dotted var(--border-strong)}
.pp-v.is-none{color:var(--text-3)}
.pp-table tbody tr:hover th,.pp-table tbody tr:hover td{background:var(--surface-2)}
.pp-tag{font-size:10px;color:var(--amber);border:1px solid var(--border);border-radius:4px;
  padding:0 3px}
.pp-empty{margin:0;color:var(--text-2);font-size:12px;line-height:1.7}
.pp-foot{margin:2px 0 0;padding-top:6px;border-top:1px solid var(--border);
  color:var(--text-3);font-size:10.5px;line-height:1.6}
/* 窄屏：面板宽度已由 `min(430px, 100vw-24px)` 兜住。表在 480px 下会横向溢出 ⇒
   外层 `.pp-scroll` 横滑；这里只缩小字号与内距，让 4 列尽量在 320px 内排得下。 */
@media (max-width:600px){
  .pos-panel{padding:10px 11px 8px}
  .pp-table{font-size:11px}
  .pp-table th,.pp-table td{padding:3px 3px}
  /* 58px 装不下 '2026-10-14'（实测差 1~2px）⇒ 列头多折一行、表头长到 70px；62px 正好 2 行 */
  .pp-table col.pp-c1,.pp-table col.pp-c2,.pp-table col.pp-c3{width:62px}
}
@media (prefers-reduced-motion:reduce){
  .nav-stats .stat-btn{transition:none}
}

/* ══════════════════════════════════════════════════════════════════════════
   设备 2 · 卡片右上角缺口：页面底色的圆咬掉直角，圆形动作按钮坐进凹口
   ══════════════════════════════════════════════════════════════════════════ */
.metric-icon{right:-5px;top:-5px;width:28px;height:28px;z-index:1;border:0;background:#0A0A0A;color:#FFFFFF}
.metric-card:first-child .metric-icon{background:var(--accent);color:var(--on-accent)}
.metric-icon-1,.metric-icon-2,.metric-icon-3{background:#0A0A0A;color:#FFFFFF;border:0}

/* 首卡为黑底、选中策略为青柠底，两者的次级文字必须单独指定：
   直接继承 --text-2/--text-3 会在深色/亮色底上失去对比度（实测 1.5~2.6:1）。 */
.metric-card:first-child .metric-label,
.metric-card:first-child .metric-note,
.metric-card:first-child .metric-value small{color:rgba(255,255,255,.72)}
.metric-card:first-child .metric-index{color:rgba(255,255,255,.60)}
.sitem.sel .ssub,.sitem.sel .ssub span,.sitem.sel .ssub b{color:rgba(10,10,10,.78)}
.sitem.sel .sname{color:var(--on-accent)}
/* 黑底导航内的焦点环：--accent-ink 在 #0A0A0A 上只有 3.12:1，肉眼几乎看不出，
   黑面上一律用青柠（16.7:1）。规则落在 .tab-rail 上（见下），
   顶栏里唯一的黑面现在就是它。 */

/* ── 状态岛引入后的修正 ──────────────────────────────────────────────────
   1) 焦点环：导航项坐在黑大胶囊上，环一律青柠（见下面 .tab-rail 那条）。
   2) ≤1420px 三处内距各收一档（入口 −6px/个、chip −2px/个、gap −1px），
      把整条买回 ~50px，抬高「一行放得下」的临界点。
   3) ⚠ 2026-10-05 起**断点式换行已全部删掉**（原先 ≤1279 两行、≤900 三行）：
      行数改由 `lib/app-nav.js` 按测量结果写 `data-rows`。
      原因就是这套断点已经和实际尺寸脱钩 —— 按旧注释 1280px 该是「单行、gapR 14px」，
      实测是**状态岛压住右翼 99.6px**（岛从 325px 长到 ~390px 后没人重算过这些数）。
      实测记录见 test/visual-baselines/topbar-autowidth-20261005/。 */
/* 顶栏里唯一的黑面就是 .tab-rail，导航项的焦点环一律青柠（黑底上 16.7:1）；
   青柠墨色 --accent-ink 压黑只有 3.12:1，肉眼几乎看不出。 */
.tab-rail :is(button,a,[tabindex]):focus-visible{outline-color:var(--accent);outline-offset:2px}
/* ≤1599px：内距各收一档（字不缩 —— 只收 padding/gap），能收窄整条胶囊 ~113px。
   为什么是 1599 而不是原来的 1420：行数由测量决定后，内距档位会**移动一行的临界点**——
   收紧后一行只要 1291px、不收紧要 1404px。档位若留在 1420，就会出现
   「1440 是 2 行、收窄到 1420 反而变回 1 行」的回跳（实测 1440 恰在 1421–1443 那段）。
   把收紧档盖满「品牌同行放不下、独占才放得下」的整个区间，行数才随视口单调。 */
@media(max-width:1599px){
  .tab-rail{gap:4px}
  .rail-group{gap:4px}
  .tab-rail .tab,.tab-rail .breadth-link{padding:6px 9px;gap:6px}
  .nav-stats{gap:3px;padding:0 4px}
  .nav-stats .stat{padding:4px 9px;gap:5px}
}

/* ── 窄屏兜底（≤900，**只在 JS 没把 data-stack 写出来时生效**）：
   品牌独占一行、胶囊整幅占下一行、组内可折行。
   JS 正常时品牌独占与否由 app-nav.js 量出来（见 .topbar[data-stack="1"]），
   本块留着是为了「脚本挂了也不塌」。 */
@media(max-width:900px){
  .topbar{flex-wrap:wrap;gap:10px}
  .tab-rail{margin-top:2px;padding:6px}
  /* 下面两条只在 JS 还没写 data-rows 时生效（`:not([data-rows])`）：
     JS 正常时三行态由 .tab-rail[data-rows="3"] 负责，两行态**不能**允许组内折行 ——
     轨道被夹 1px 就可能把整条入口挤到下一行去。 */
  .tab-rail:not([data-rows]){grid-template-columns:1fr;row-gap:5px;border-radius:22px}
  .tab-rail:not([data-rows]) .rail-left,
  .tab-rail:not([data-rows]) .rail-right{justify-self:stretch;flex-wrap:wrap;justify-content:center}
  /* 无 JS 兜底也是三行 ⇒ 同样把岛提到第一行，别让两条路径的观感不同 */
  .tab-rail:not([data-rows])>.nav-stats{order:-1}
  .tab-rail .tab,.tab-rail .breadth-link{min-height:40px;padding:8px 12px}
}
/* 手机宽度：胶囊内距再收一档，保证组内折行后仍留得住内边距 */
@media(max-width:600px){
  .topbar{gap:8px}
  .brand{gap:9px}.brand-name{font-size:16px}.brand-mark{width:36px;height:36px;border-radius:12px}
  .tab-rail{padding:6px}
  .tab-rail .tab,.tab-rail .breadth-link{min-height:40px;padding:8px 11px}
  /* 手机宽度下岛整行内塞不下 11/15px 版（390px 实测岛 324 / 可用 317，溢出 3px），
     这里回到一档小字 10/13 —— 只降字号，不再降岛高（42px 的触摸友好尺寸留着）。 */
  .nav-stats{padding:0 3px}
  .nav-stats .stat{padding:4px 7px;gap:4px}
  .nav-stats .stat-k{font-size:10px;letter-spacing:.6px}
  .nav-stats .stat-v{font-size:13px}
}

/* 顶栏品牌名/副题是两层 <span>（整块由 app-nav.js 渲染），
   需要显式 block 才能保持两行堆叠。 */
.topbar .brand-name,.topbar .brand-caption{display:block}
/* 独立页（hb_breadth / timing_panel / timing_trace）的 body 是 block + padding，
   让顶栏落在与主应用相同的位置。 */
body.standalone{padding:14px 20px 20px}
body.standalone .topbar{margin:0 0 16px}

/* ══════════════════════════════════════════════════════════════════════════
   子页页头（2026-09-28）
   此前 hb_breadth / timing_panel / timing_trace 各写一份 .top（18px 裸 h1 +
   自带 CSS，还各自挂一串返回链接），与首页 .strategy-heading 不是一套。
   这里收成同一个组件：25px 标题 / 副题，数值与首页逐项对齐。
   （2026-09-28 二次调整：原先标题上方还有一行全大写拉字距的眉标，
     4 个页面一起去掉了 —— 那是「生成感」最重的一处，且不承载信息。）
   ══════════════════════════════════════════════════════════════════════════ */
.page-head{margin-bottom:18px}
.page-head h1{font-size:var(--title-size);font-weight:600;line-height:1.2;letter-spacing:var(--title-ls);color:var(--text)}
.page-head .page-sub{color:var(--text-2);font-size:12px;margin-top:7px;line-height:1.6}

/* ══════════════════════════════════════════════════════════════════════════
   全站页面标题：一个规格，7 处共用
   首页 3 个面板 + 3 个子页。此前同一个东西有四个值：
   #title 21px / #stratTitle 25px / #comboTitle 19px（内联）/ 子页 18px。
   ══════════════════════════════════════════════════════════════════════════ */
:is(#title,#stratTitle,#comboTitle,.page-head h1){
  font-size:var(--title-size);font-weight:600;line-height:1.2;
  letter-spacing:var(--title-ls);color:var(--text)}
:is(#stratSubtitle,#comboSub,.page-head .page-sub){
  color:var(--text-2);font-size:12px;margin-top:7px;line-height:1.6}

/* ══════════════════════════════════════════════════════════════════════════
   全站统一页尾（由 lib/app-footer.js 渲染，4 个页面共用）
   取代原先的三套写法：index 一套、timing_trace 一套、timing_panel 藏在正文、
   hb_breadth 完全没有。免责声明的措辞与位置从此只有一处定义。
   ══════════════════════════════════════════════════════════════════════════ */
.app-footer{flex:0 0 auto;padding:9px 20px 11px;border-top:1px solid var(--border);
  background:var(--bg-2);color:var(--text-3)}
.af-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:10px}
.af-brand{font-weight:500;letter-spacing:.5px;white-space:nowrap;color:var(--text-3)}
.af-dim{opacity:.75}
.af-status{text-align:right;font-variant-numeric:tabular-nums}
.af-status b{color:var(--text);font-weight:500}
.af-warn{color:var(--amber)}
/* 「已更新」不再用绿色：绿色在本站只表示「跌」。
   成功态交给 ✓ 符号 + 中性字色，避免与红涨绿跌撞语义。 */
.af-ok{color:var(--text-2)}
.af-legal{margin-top:7px;padding-top:7px;border-top:1px dashed var(--border);
  font-size:10.5px;line-height:1.6;color:var(--text-2)}
/* 独立页 body 有 14/20/20 内边距，页尾要通栏贴底 */
body.standalone .app-footer{margin:16px -20px -20px}

/* 错误卡：原先错误直接塞进 .metric-card，一旦它是首个卡片就套上「黑底主指标」
   样式，深红错误字压黑底实测只有 2.47:1（不可读）。错误态改用独立卡片。 */
.err-card{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;
  padding:14px 16px;border-radius:var(--radius-md);
  background:#FFF1F2;border:1px solid #FECDD3;color:#9F1239}
.err-card .err-title{font-weight:600;font-size:14px;white-space:nowrap}
.err-card .err-detail{font-size:12px;color:#9F1239;flex:1;min-width:200px;line-height:1.6}
.err-card .err-retry{flex:none;font-size:12px;min-height:30px;padding:5px 14px;
  border-radius:999px;border:1px solid #FECDD3;background:#FFFFFF;color:#9F1239}
.err-card .err-retry:hover{background:#FFE4E6;border-color:#FDA4AF}
.err-card .err-retry:disabled{opacity:.6;cursor:default}

/* 窄屏表格不再被裁：原先表格父级 overflow-x:visible，320px 实测表宽
   410~550px、视口 309px → 内容被切掉且没有横向滚动可够到。
   只在窄屏开启，避免宽屏下 details 变成滚动容器影响浮层。 */
@media(max-width:900px){
  main details,main section{overflow-x:auto;-webkit-overflow-scrolling:touch}
}

/* ══════════════════════════════════════════════════════════════════════════
   跳到主内容（app-nav.js 注入，4 页共用）
   键盘/读屏用户此前必须先穿过 7 个导航入口才能到内容区。
   平时视觉隐藏，获得焦点时出现在左上角。
   ══════════════════════════════════════════════════════════════════════════ */
.skip-link{position:fixed;top:8px;left:8px;z-index:100;transform:translateY(-250%);
  padding:10px 18px;border-radius:999px;background:#0A0A0A;color:#FFFFFF;
  font-size:13px;font-weight:500;text-decoration:none;box-shadow:var(--shadow-2)}
.skip-link:focus{transform:none;outline:2px solid var(--accent);outline-offset:2px}

/* 全站最小的可点目标：timing_panel 的「显示满收益对照」实测 84×16px，
   低于 WCAG 2.2 SC 2.5.8 的 24×24。撑到 26px 高并给 hover 反馈。 */
#ctgl{display:inline-flex;align-items:center;min-height:26px;padding:3px 9px;
  border-radius:999px;line-height:1.2}
#ctgl:hover{background:var(--accent-soft);text-decoration:none}
