/**
 * styles/object-tree.css — 对象树（画线 + 指标 平铺混排 · 右侧抽屉）
 *
 * ═══ 谁在用 ═══
 *   tv/ui/object-tree.js（#kp-object-tree 面板 + #kp-ot-menu 右键菜单 + #kp-ot-toggle 开合按钮
 *   + #kp-ot-mk 定位竖标）。
 *
 * ═══ 三条约定（改之前先读）═══
 *   ① 全部类名以 `.kp-ot*` 前缀命名，**不加全局选择器**（唯一例外见 ③）——
 *      本文件不在 kp-main.css 里，不做前缀就会渗到 K 线画布和自选面板。
 *   ② 颜色只用 kp-main.css :root 里已有的变量（--bg-2/--bg-4/--border-default/--text-*），
 *      品牌橙 #E8590C 用于选中，标记金 #f59e0b 与草图一致。不要写新的主题色。
 *   ③ 只有一处非前缀规则：`@media (max-width:760px)` 里的 `.kp-ot{position:fixed}` ——
 *      移动端退化成全屏抽屉，层级压过顶栏（见该块内注释）。
 *      用 fixed 而不是 absolute：#chart-area 的 position:relative **只在
 *      `body.kp-mobile` 下存在**（kp-main.css 的 kp-mobile 段），而本文件的媒体查询只看
 *      宽度 —— 桌面窗口缩到 600px 时 kp-mobile 未必挂上，absolute 就会掉到更上层
 *      （body）去，于是又回到"顶栏盖住头部"的老问题。fixed + 高于顶栏的 z-index
 *      不依赖任何祖先的定位上下文，两头都成立。
 *
 * ═══ 与草图的差异 ═══
 *   草图的 .toast / .spec / .toolrail 是演示壳，实装不复刻：
 *   toast 走 window.__KP_TOAST__，工具条已存在（#drawing-bar），说明卡片不进产物。
 */
.kp-ot {
  width: 320px;
  flex: 0 0 320px;
  display: none;
  flex-direction: column;
  min-height: 0;
  border-left: 1px solid var(--border-default);
  background: var(--bg-2);
  color: var(--text-primary);
  font: 13px/1.5 var(--font-ui, -apple-system, BlinkMacSystemFont, 'PingFang SC', sans-serif);
  -webkit-font-smoothing: antialiased;
}
.kp-ot.on { display: flex; }
.kp-ot:focus { outline: none; }        /* 键盘巡览靠行内橙条表达选中，不套系统焦点环 */

/* ═══ 移动端：底部浮层（A 方案，2026-10-25 用户定档「40% 半屏」）═══
   历史：2026-10-24 曾是「全屏铺满抽屉」。用户看过草图后定为浮层，理由是
   对象树的高频动作就是「点 ⌖ 看完那根线」—— 全屏抽屉必须手动关掉才看得见图。
   两档：
     · 展开 = **高度自适应，40dvh 只是上限**（2026-10-30 方案 2 用户定档）+
       顶部圆角 + 手柄。原先写死 height:40dvh，1 条画线也照样占满 40% ——
       屏占比与内容量脱钩，用户反馈「屏占比太大」即此。
     · 定位后自动收档 .kp-ot-peeking = 96px（手柄 10 + 头 38 + 定位行 44 = 92，
       三个数字要一起改），只留「已定位 · <名称> + 展开」，竖标与坐标标签完整露出。
   ⚠️ 为什么仍然保留 z-index 1100：浮层从底部升起，正常不会压顶栏；但底栏 tab
      （#kp-status-bar）也在底部，浮层必须压过它，否则底栏压住列表底部。
      1100 只高过顶栏(1000)/底栏，仍低于内容抽屉(9991+)与弹窗(9999+)，不抢层级。
   ⚠️ 高度写 dvh 并有 vh 兜底：移动端浏览器地址栏收起/展开会改 vh，
      只用 vh 时 iOS Safari 实测展开瞬间会跳 60px 左右。
   ⚠️ position:fixed 而不是 absolute：#chart-area 的 position:relative **只在
      `body.kp-mobile` 下存在**（kp-main.css 的 kp-mobile 段），而本文件的媒体查询
      只看宽度 —— 桌面窗口缩到 600px 时 kp-mobile 未必挂上，absolute 就会掉到
      更上层（body）去。fixed 不依赖任何祖先的定位上下文，两头都成立。 */
@media (max-width: 760px) {
  .kp-ot.on {
    position: fixed;
    left: 0; right: 0; bottom: 0; top: auto;
    /* ★ 2026-10-30 方案 2：height 由 40dvh 改为 auto，40dvh 只当上限。
       1 条画线时浮层只有内容那么高（外壳 49 + 列表），长列表才顶到 40%。
       ⚠️ 必须留 vh 兜底行：老 iOS 不认 dvh，只写 dvh 时 max-height 整条被丢弃
          → 没有上限，长列表会把 K 线盖满。
       ⚠️ 顺带删掉 transition: height .2s ease —— height 变成 auto 后浏览器无法插值
          （auto 不是可过渡值），留着只会让后读的人以为还有展开动画。
          收档两档（96px / 52px）本来就是硬切。 */
    height: auto;
    max-height: 40vh;
    max-height: 40dvh;
    width: auto;
    flex-basis: auto;
    border-left: none;
    border-top: 1px solid var(--border-default);
    border-radius: 14px 14px 0 0;
    box-shadow: 0 -14px 36px rgba(0, 0, 0, .6);
    overflow: hidden;
    z-index: 1100;
  }
  .kp-ot.on.kp-ot-peeking { height: 96px; max-height: 96px; }
  /* ★ 2026-10-29 审计收拢：两个收档档位（peeking 96px / dpanel 52px）都要藏
     列表；原先在下面 .kp-ot-dpanel 段又各写了一遍（同一件事两份真相，
     再多一个档位就要改两处）。合成一条 :is() —— 加档位只需往括号里加类名。
     ★ 2026-10-30 方案 2：统计条整行已并入头部（.kp-ot-bar 元素删除），
     所以这里改成藏**计数与「全部隐藏」两颗**，头部只留图标+标题+✕。
     ⚠️ 放在 peeking 段而不是 dpanel 段是安全的：dpanel 段的 height 仍排在
        peeking 之后（同权重靠源序胜出），而 display:none 两档完全一致，
        放前面不会盖掉任何一档；:is() 权重取最高项，仍是 (0,4,0)，与原选择器相同。 */
  .kp-ot.on:is(.kp-ot-peeking, .kp-ot-dpanel) .kp-ot-cnt,
  .kp-ot.on:is(.kp-ot-peeking, .kp-ot-dpanel) .kp-ot-mini,
  .kp-ot.on:is(.kp-ot-peeking, .kp-ot-dpanel) .kp-ot-list { display: none; }
}

/* 拖拽手柄 + 「已定位」收档行：只在移动端浮层里出现
   （桌面是右侧并排抽屉，没有浮层也没有收档语义，故默认 display:none） */
.kp-ot-grip { display: none; flex: none; height: 10px; place-items: center; cursor: grab; }
.kp-ot-grip span { width: 34px; height: 4px; border-radius: 2px; background: #3B3F48; }
.kp-ot-peek {
  display: none; align-items: center; gap: 8px;
  height: 44px; padding: 0 11px;
  border-bottom: 1px solid var(--border-default); flex: none;
}
.kp-ot-peek-txt {
  font-size: 12px; color: var(--text-secondary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.kp-ot-peek-more {
  border: 1px solid var(--border-default); background: var(--bg-2);
  color: var(--text-secondary); font: inherit; font-size: 11px;
  padding: 4px 10px; border-radius: 7px; cursor: pointer; flex: none;
}
.kp-ot-peek-more:hover { color: var(--text-primary); border-color: rgba(232, 89, 12, .55); }

/* ★ 移动端把上面两个 display:none 打开
   ⚠️ 必须写在这里（默认规则的**后面**），不能写进文件顶部那个媒体查询：
      媒体查询不加权重，`.kp-ot-grip{display:none}`(0,1,0) 与这里同权重 →
      由**源序**决定胜负。第一版写在顶部，实测 grip 计算样式仍是 none、
      手柄看不见（与文件底部 `⌖` 按钮那次一模一样的坑）。
      改的时候别为了"集中"又搬回顶部。 */
@media (max-width: 760px) {
  .kp-ot-grip { display: grid; }
  .kp-ot.on.kp-ot-peeking .kp-ot-peek { display: flex; }
}

/* ═══ 与「图形样式面板」抢底部（2026-10-29）═══
   两者在窄屏都是底部 sheet：样式面板 .kp-dpanel = 56dvh（矮屏 64dvh，见
   kp-main.css 的对应媒体查询），本浮层 = 40dvh。面板 z-index 9998 > 本浮层 1100
   → 面板一开就把整棵树盖住：看不见，也点不到（不是叠在上面，是完全失效）。

   这里在面板可见时收到「手柄 14 + 头部 38 = 52px」只留标题行当把手，
   并抬到面板顶边之上。上抬量由 JS 按面板**实测高度**写进 --kp-ot-lift ——
   不在 CSS 里复述 56/64dvh：那两个数字在 kp-main.css，复述一份就是第二份真相，
   而「矮屏 64dvh」正是复述时最容易漏的那条分支。

   ⚠️ 这一档连收档行（.kp-ot-peek）一起藏：它的文案是「已定位 · …」，
      面板打开时未必刚定位过，摆着会误导。
   ⚠️ 必须写在上面 .kp-ot-peeking 那段**之后**：媒体查询不加权重，两者同权重
      (0,3,0) 时由源序决定胜负 —— 写前面会被 peeking 的 display:flex 盖回来。
      （与文件里 grip 那次同一个坑，别为了"集中"又搬到顶部。） */
@media (max-width: 760px) {
  .kp-ot.on.kp-ot-dpanel {
    height: 52px;
    max-height: 52px;
    bottom: var(--kp-ot-lift, 0px);
  }
  /* bar / list 的隐藏已合并到文件上方 peeking 段的 :is() 规则（2026-10-29 审计收拢）。
     这里只留本档独有的一项：收档行也一起藏 —— 它的文案是「已定位 · …」，
     面板打开时未必刚定位过，摆着会误导。 */
  .kp-ot.on.kp-ot-dpanel .kp-ot-peek { display: none; }
  .kp-ot.on.kp-ot-dpanel .kp-ot-title::after {
    content: ' · 样式面板打开中';
    font-weight: 400;
    color: var(--text-muted);
  }
}

/* ═══ 头部 ═══ */
.kp-ot-head {
  display: flex; align-items: center; gap: 8px;
  height: 38px; padding: 0 8px 0 11px;
  border-bottom: 1px solid var(--border-default);
  flex: none;
}
.kp-ot-ico { width: 16px; height: 16px; color: #E8590C; display: grid; place-items: center; flex: none; }
.kp-ot-ico svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
/* ⚠️ 标题与计数都 flex:none + nowrap（2026-10-30 方案 2 合并行后新增）：
   两者都宁可挤 .kp-ot-grow 的空白，也不要换行 —— 头部固定 38px，换行会把
   「对象树」压成两截。实测 375 宽下整行约 261px，320 宽也放得下。 */
.kp-ot-title { flex: none; white-space: nowrap; font-size: 13px; font-weight: 600; letter-spacing: .3px; }
.kp-ot-grow { flex: 1; }
.kp-ot-x { width: 24px; height: 24px; border: 0; background: none; border-radius: 6px; display: grid; place-items: center; color: var(--text-muted); cursor: pointer; }
.kp-ot-x:hover { background: var(--bg-4); color: var(--text-primary); }
.kp-ot-x svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 1.6; stroke-linecap: round; }

/* ═══ 统计条 ═══
   ★ 2026-10-30 方案 2：`.kp-ot-bar` 这个**独立一行**已删除，计数与「全部隐藏」
     并入 .kp-ot-head 同一行（省 38px 外壳）。原来它是「图标+标题 / 计数+全部隐藏」
     两行 38+38 = 76px，窄屏 375 下实测只需 261px 一行就放得下（图标16 + 标题约40 +
     计数约80 + 全部隐藏58 + ✕24 + 间距/内边距），所以没必要两行。
   ⚠️ 别再给它加回 border-bottom：头部那条 (object-tree.css 的 .kp-ot-head) 已经是分隔线。 */
.kp-ot-cnt { flex: none; white-space: nowrap; font-size: 11px; color: var(--text-muted); }
.kp-ot-cnt b { color: var(--text-secondary); font-weight: 600; }
.kp-ot-mini { flex: none; border: 0; background: none; font: inherit; font-size: 11px; color: var(--text-secondary); padding: 3px 7px; border-radius: 6px; cursor: pointer; }
.kp-ot-mini:hover { background: var(--bg-4); color: var(--text-primary); }
.kp-ot-mini[disabled] { opacity: .4; cursor: default; }

/* ═══ 列表 ═══ */
.kp-ot-body { flex: 1; overflow: auto; padding: 6px 6px 16px; min-height: 0; }
.kp-ot-body::-webkit-scrollbar { width: 8px; }
.kp-ot-body::-webkit-scrollbar-thumb { background: #2a2f3a; border-radius: 4px; }

.kp-ot-root { display: flex; align-items: center; gap: 8px; padding: 7px 8px; border-radius: 7px; font-size: 12.5px; }
.kp-ot-root .kp-ot-cico { color: var(--text-secondary); display: grid; place-items: center; flex: none; }
.kp-ot-root .kp-ot-cico svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.kp-ot-root .kp-ot-sym { font-weight: 600; }
.kp-ot-root .kp-ot-nm2 { color: var(--text-muted); font-size: 11px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ═══ 分组标题（2026-10-27 多窗口）═══
   多窗口下对象树会按「主图 / 窗口 1 / 窗口 2 …」分组列出各格的画线，
   否则用户看到一堆线却不知道是哪个窗口画的。 */
.kp-ot-grp {
  display: flex; align-items: center; gap: 6px;
  padding: 9px 8px 3px; font-size: 11px; color: var(--text-muted);
}
.kp-ot-grp-lbl { font-weight: 600; letter-spacing: .02em; }
.kp-ot-grp-n {
  margin-left: auto; font-size: 10.5px; opacity: .75;
  background: var(--bg-4); border-radius: 8px; padding: 1px 6px;
}

/* ═══ 行（2026-10-29：画线行改为**双行卡片**，34 → 47px）═══
   第一行 = 名称 + 落盘周期；第二行 = 完整年月日坐标 + 锚点数。
   为什么必须加高：桌面面板固定 320px，减掉图标/边距/4 颗按钮第一行只剩约 178px，
   单行放「垂直线 + 2026-02-17 08:00」会把名称截成「垂直…」（草图 A 变体实测图
   html-sketches/object-tree-row-redesign/shots/a-compact.png）。
   代价：一屏可见行数约 13 → 10（面板高度不变，行高 +13px）。 */
.kp-ot-row {
  display: flex; align-items: center; gap: 8px;
  padding: 0 8px; height: 47px; border-radius: 7px;
  cursor: default; position: relative; user-select: none;
}
.kp-ot-row:hover { background: var(--bg-4); }
.kp-ot-row.sel { background: rgba(232, 89, 12, .14); }
.kp-ot-row.sel::before { content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 2px; background: #E8590C; border-radius: 2px; }
/* ═══ 删除确认态（移动端行内两步删除的第一步）═══
   ⚠️ 这三条必须写在 `.kp-ot-row.sel*` 之后：媒体查询/状态类都不加权重，
      `.armed` 与 `.sel` 同为 (0,2,0) 打平 → 由**源序**决定。挪到前面会被橙色选中态盖成橙色。 */
.kp-ot-row.armed { background: rgba(255, 77, 79, .10); }
.kp-ot-row.armed::before { content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 2px; background: #E5544B; border-radius: 2px; }
.kp-ot-row.armed .kp-ot-r-ico { color: #E5544B; }
.kp-ot-row.armed .kp-ot-r-nm { color: #FFC9C9; }
/* 「再点一次删除」6 个红字：11px × 6 ≈ 66px，行里替代被拿掉的三颗按钮 */
.kp-ot-ask { flex: none; font-size: 11px; line-height: 1; color: #E5544B; white-space: nowrap; }
.kp-ot-btn.kp-ot-del.armed { background: rgba(255, 77, 79, .20); color: #E5544B; }
.kp-ot-row.hid .kp-ot-r-nm { color: var(--text-muted); text-decoration: line-through; text-decoration-color: #3a4150; }
.kp-ot-r-ico { width: 16px; height: 16px; flex: none; color: var(--text-secondary); display: grid; place-items: center; }
.kp-ot-row.sel .kp-ot-r-ico { color: #E8590C; }
.kp-ot-r-ico svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.kp-ot-r-nm { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12.5px; }
.kp-ot-r-nm input {
  width: 100%; box-sizing: border-box;
  background: var(--bg-0, #0A0A0C); border: 1px solid #E8590C; border-radius: 4px;
  color: var(--text-primary); font: inherit; padding: 1px 4px; outline: none;
}
/* ── 双行文本块 ──
   display:flex + column，让 l1/l2 在行高内垂直居中；min-width:0 是 ellipsis 能生效的前提
   （flex item 默认 min-width:auto，会撑破父级而不截断）。 */
.kp-ot-r-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
/* 指标行没有第二行（指标依附视口，没有"落盘坐标"）→ 单行模式。
   gap:0 + 行高由 .kp-ot-row 的 align-items:center 兜住，视觉上两者等高。
   .single 同时是调试/探针识别"这一行没有坐标第二行"的钩子。 */
.kp-ot-row.single .kp-ot-r-main { gap: 0; }
.kp-ot-r-l1 { display: flex; align-items: center; gap: 6px; min-width: 0; }
.kp-ot-r-l2 {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  font-size: 10.5px; line-height: 1.35; color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
/* 坐标串（2026-10-29：完整年月日）。超宽只截尾并保留 title，不要换行 ——
   换行会把行高顶开，同一列表里出现两种高度。 */
.kp-ot-r-ts { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kp-ot-r-ts.none { opacity: .55; }
.kp-ot-r-pin { font-size: 10.5px; color: var(--text-muted); flex: none; font-variant-numeric: tabular-nums; }

/* ═══ 周期标注（画线当时落盘的周期）═══
   ★ 为什么要一颗独立胶囊，而不是并进 .kp-ot-r-pin：
     同一个位置已经有「2 锚点」，两者是不同性质的信息（一个是形态、一个是坐标系）。
     周期是**跨行比对**才用得上的信息（哪几条是别的周期的）→ 固定宽度 + 等宽数字，
     让整列对得齐，扫一眼就能看出 15m/1h 混在一起。
   ★ 颜色语义：有色 = 有快照；灰「—」= 老数据没记录（**不是**"当前周期"）。
     不要为了"好看"给它填当前周期，那会把"不知道"伪装成"知道"。 */
.kp-ot-iv {
  flex: none; min-width: 30px; text-align: center;
  font: 600 10px/1.5 var(--font-ui, -apple-system, 'PingFang SC', sans-serif);
  font-variant-numeric: tabular-nums;
  padding: 0 4px; border-radius: 4px;
  color: #7fb0ff; background: rgba(127, 176, 255, .12);
  box-shadow: inset 0 0 0 1px rgba(127, 176, 255, .26);
}
.kp-ot-iv.none { color: var(--text-muted); background: none; box-shadow: none; opacity: .55; }
.kp-ot-row.hid .kp-ot-iv { opacity: .45; }

/* ═══ 「未上图」胶囊 —— 2026-10-31 用户定案：**整段删除** ═══
   旧实现（2026-10-30）：琥珀色胶囊 + 整行降亮度，语义是"这条线有问题，请处理"。
   ❌ 语义错位：对象树回答的是「我画了哪些线 / 什么周期 / 什么日期」，
      「当前窗口有没有实例」是**图**的状态，不是清单的状态。
      用户反馈：看到「未上图」会以为画线出现 bug。
   ✅ 现在：超窗线与普通线**长得完全一样**，零提示。
      周期胶囊（.kp-ot-iv，蓝）+ 日期区间已经足够表达。
   ⚠️ 内部判据 `it.unplaced`（JS 侧）全部保留 —— 那是"图上没有实例"的事实，
      动作闸靠它拦：显隐 / 全部隐藏 / 菜单 / 复制坐标 / 删除。
      删掉 CSS 不会让那些闸失效，但**别把 JS 里的 unpl 分支一起删了**。 */
.kp-ot-btn { width: 22px; height: 22px; flex: none; border: 0; background: none; border-radius: 5px; display: grid; place-items: center; color: var(--text-muted); cursor: pointer; padding: 0; }
/* 按钮组：4 颗并排要省宽度 → 组内 gap 2px（行级 gap 是 8px，直接铺开要多占 18px） */
.kp-ot-acts { display: flex; align-items: center; gap: 2px; flex: none; }
.kp-ot-row:hover .kp-ot-btn { color: var(--text-secondary); }
.kp-ot-btn:hover { background: var(--bg-2, #131316); color: var(--text-primary); }
/* ★ 2026-10-29：⌖ 不再靠 hover 显形（原来 opacity:0 + :hover/:sel → 1）。
   改成常驻后行内是「⌖ ✎ 🗑 👁」四颗，触屏没有 hover 也照样点得到 ——
   顺带消掉了那条「媒体查询不加权重、与桌面 opacity 规则打平看源序」的老坑（见下方窄屏段）。 */
.kp-ot-btn svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
/* 删除确认态那颗（两步删除的第二步入口）：宽度交给文字，别用 22px 把「再点删除」挤掉 */
.kp-ot-btn.kp-ot-delarm {
  width: auto; min-width: 56px; padding: 0 8px;
  font-size: 11px; line-height: 20px; white-space: nowrap;
  color: #E5544B; background: rgba(255, 77, 79, .20);
}
.kp-ot-btn.kp-ot-delarm:hover { color: #FFC9C9; background: rgba(255, 77, 79, .32); }

/* ═══ 移动端：触摸尺寸 ═══
   ★ 2026-10-29 方案 B 定案：行 46px、按钮 40px。
     依据：手指标称触摸目标 ≥44px（行整块可点，46 达标）；按钮 40px 是
     在 390 宽下「两颗并排仍不挤压名字」与「指腹不带误触」之间的实测折中。
   ★ 2026-10-30 用户定案改回行内 4 颗（原先窄屏画线行 0 颗、全进动作胶囊）：
     40px × 4 = 166px，375 视口下名字列只剩约 155px（长名字被截成省略号）→
     按钮降到 28px，四颗 118px，名字列回到约 200px。
     代价（**已知且用户已接受**）：单颗 28px < 44px 触控推荐值；删除仍是两步确认，
     误触只会把行切成红字「再点删除」态，可以退出，不会直接删掉。
   ★ 双行卡片（名称 + 完整年月日）在 46px 里也放得下：l1 约 17px + l2 约 14px，
     所以窄屏**不用**跟着桌面涨到 47px，两套断点可以各用各的高度。
   ⚠️ 历史坑（保留备查）：桌面的 ⌖ 曾是 `opacity:0` + `:hover` 显形，而媒体查询**不加权重**，
      与 `.kp-ot-btn.kp-ot-loc{opacity:0}` 同为 (0,2,0)，胜负由**源序**决定 ——
      第一版把窄屏规则写在文件顶部，实测 size/color 生效而 opacity 仍是 0（414px 下定位入口点不到）。
      2026-10-29 起行内按钮改为**常驻**，那条 `opacity` 规则已删除，坑本身消失；
      但「同权重靠源序」这条规律仍适用于本文件，新增断点规则时照样别往顶部搬。 */
@media (max-width: 760px) {
  .kp-ot-row { height: 46px; }
  .kp-ot-btn { width: 28px; height: 28px; }
  .kp-ot-btn > svg { width: 15px; height: 15px; }
}

/* ═══ 紧凑 1「挤干白给」（2026-10-30，用户定案）═══
   三条都不是功能改动，纯粹是**去掉从来没被需要过的尺寸**：
   ① 列表底部 16px 内边距 —— 列表本来就能滚，那 16px 只是让最后一行的下沿
      多出一截空白，滚到底时看得最明显。
   ② 根行「XAUUSD · 现货黄金 · 1d」33px —— 这串信息顶栏与周期栏**都已经有**，
      它只是给列表起个锚点，用不着和可操作的行一样高。
   ③ 指标行 46px —— 指标依附视口，没有"落盘坐标"，所以内容是**单行**；
      而 .kp-ot-row 的 46px 是为画线的双行卡片定的（见文件上方双行卡片段）。
      `.kp-ot-row.single .kp-ot-r-main { gap:0 }` 只归零了 gap，行高仍由
      .kp-ot-row 决定 → 每条指标行上下各白扔约 13px（2 条 26px、8 条 104px）。
   实测（headless Chrome 375×667 dpr2，1 画线 + 2 指标，方案 2 已落地为前提）：
   浮层 159px = 手柄 10 + 头 38 + 列表内容 100 + 上内边距 6 + 底内边距 4 + 描边 1；
   其中根行 20、画线行 46、指标摘要行 34。
   改前同一场景内容实际 284px（14+38+38+193+1），已超过 40dvh 上限 267 → 当时就在内部滚动；
   改后内容 159px，屏上占用 267 → 159。三段账：外壳 42（统计条 38 + 手柄 4）、
   本段三条 25（底内边距 12 + 根行 13）、指标折叠 58（2×46 → 34），合计 125。
   ⚠️ 只做窄屏：桌面面板是右侧并排抽屉，高度不紧张，47px 的双行卡片是有意留的；
      要动桌面请单独评估（`_paintSelection` 之外没有依赖行高的逻辑）。
   ⚠️ 本段必须排在上面 `.kp-ot-row` / `.kp-ot-root` 基线**之后**：媒体查询不加权重，
      同权重靠源序决胜（本文件已踩两次：grip 的 display、窄屏 ⌖ 的 opacity）。 */
@media (max-width: 760px) {
  .kp-ot-body { padding-bottom: 4px; }
  .kp-ot-root { height: 20px; padding: 0 8px; font-size: 11px; line-height: 1; }
  .kp-ot-root .kp-ot-cico svg { width: 12px; height: 12px; }
  .kp-ot-row.single { height: 34px; }
}

/* ═══ 紧凑 3：指标折叠成一行摘要（2026-10-30，用户定案）═══
   [为什么要它] 指标行是纯属性行（名称 + 主图/副图 + 三颗按钮），一条指标换不来
     一屏 34px 的独立行。指标 ≥ 2 条时折成一行「指标 [N] 名称…」，要在单条上
     操作再展开。8 条指标实测省 238px。
   [判据] 折叠只对**同一个分组内 ≥ 2 条**生效（object-tree.js 的 IND_FOLD_MIN）：
     1 条指标折成摘要行反而多占一行，纯亏。
   [取舍] 折叠态下「⚙ 指标设置 / 🗑 删除 / 👁 显隐」要展开才能点 —— 这是方案自带的
     代价，用户已确认接受。为把信息损失压到最小，折叠态仍显示：条数、
     每条名称、副图标记「(副)」、以及已隐藏的指标名称划线（与行内 .hid 同语义）。
   ⚠️ 类名用 .kp-ot-sum 而**不是** .kp-ot-row：列表的 click/dblclick/contextmenu/
      长按重命名四个委托监听全按 `.kp-ot-row` 找宿主，套用会被当成一个"对象"去
      选中/改名/定位。摘要行不是对象，必须换类名，让那四个入口自然落空。 */
.kp-ot-sum {
  display: flex; align-items: center; gap: 6px;
  height: 34px; padding: 0 8px; border-radius: 7px;
  cursor: pointer; user-select: none;
  color: var(--text-secondary); font-size: 11.5px;
}
.kp-ot-sum:hover { background: var(--bg-4); }
.kp-ot-sum-caret {
  flex: none; width: 14px; height: 14px; display: grid; place-items: center;
  color: var(--text-muted); transition: transform .15s ease;
}
.kp-ot-sum.open .kp-ot-sum-caret { transform: rotate(90deg); }
.kp-ot-sum-caret svg { width: 12px; height: 12px; stroke: currentColor; fill: none; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.kp-ot-sum-lbl { flex: none; font-weight: 600; color: var(--text-secondary); }
/* 条数徽标：与分组标题的 .kp-ot-grp-n 同一套视觉，避免列表里出现两种"徽标" */
.kp-ot-sum-n {
  flex: none; font-size: 10.5px; opacity: .75;
  background: var(--bg-4); border-radius: 8px; padding: 1px 6px;
}
.kp-ot-sum-nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-muted); }
.kp-ot-sum-nm.hid { text-decoration: line-through; text-decoration-color: #3a4150; opacity: .6; }

/* ═══ 动作胶囊（2026-10-29 方案 C）═══
   [它是什么] 在**图上点到一条线**后浮出的一条统一动作条：
             图标 + 名称 + 周期胶囊 + ⌖ ✎ 👁 🗑。
   [为什么要它] 它解决的是行内按钮解决不了的那个入口：手指点中图上的线时，
             树里那一行未必在可视区（要滚），没有它就只能"去树里找"。锚点是命中点。
   ★ 2026-10-30 变更：窄屏行内恢复常驻 4 颗（按钮 28px，见上方移动端段），
     **点树里的行不再弹胶囊**（object-tree.js 的 select）。理由：同一组动作在行里和
     图上各出一份就是重复 UI，且胶囊会横在 K 线上挡图。
     所以现在胶囊的唯一来源 = 图上点选；行内按钮 = 树里操作。两条入口分工，不重叠。
   [为什么 position:fixed] 它要贴在图上命中点，且必须浮在窄屏 40dvh 的树浮层(1100)
             之上 → 1150。低于样式面板 9998 是有意的：面板一开胶囊让位
             （见 object-tree.js 的 _syncAgainstPanel）。z-index 改小会被树浮层盖住，
             改大会盖住样式面板 —— 别动。
   ⚠️ 定位（left/top）由 JS 的 _capPlace 写内联值，这里只管样子，别写死位置。 */
.kp-ot-cap {
  position: fixed; z-index: 1150; box-sizing: border-box;
  display: flex; align-items: center; gap: 2px;
  max-width: calc(100vw - 16px);
  padding: 4px 6px 4px 10px; border-radius: 999px;
  background: rgba(18, 20, 26, .97);
  box-shadow: 0 6px 24px rgba(0, 0, 0, .55), inset 0 0 0 1px var(--border-default, #23252b);
  font: 500 12px/1 var(--font-ui, -apple-system, 'PingFang SC', sans-serif);
  color: var(--text-primary);
}
.kp-ot-cap[hidden] { display: none; }
.kp-ot-cap-ico {
  width: 16px; height: 16px; flex: none; display: grid; place-items: center;
  color: #E8590C;
}
.kp-ot-cap-ico svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.kp-ot-cap-nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kp-ot-cap-grow { flex: 1 1 auto; min-width: 4px; }
/* 隐藏态（👁 关掉的线）：名字划掉 —— 与行里的 .kp-ot-row.hid 同一套语义 */
.kp-ot-cap.hid .kp-ot-cap-nm { color: var(--text-muted); text-decoration: line-through; text-decoration-color: #3a4150; }
.kp-ot-cap.hid .kp-ot-cap-ico { color: var(--text-muted); }
.kp-ot-cbtn {
  width: 34px; height: 34px; flex: none; border: 0; background: none; border-radius: 50%;
  display: grid; place-items: center; color: var(--text-secondary);
  cursor: pointer; padding: 0;
}
.kp-ot-cbtn:hover { background: var(--bg-2, #131316); color: var(--text-primary); }
.kp-ot-cbtn svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
/* 删除与另外三颗拉开：隔一条细线，且常态就是红色语义 —— 别把它做"低调" */
.kp-ot-cbtn.kp-ot-cdel { color: #E5544B; margin-left: 2px; box-shadow: inset 1px 0 0 var(--border-default, #23252b); border-radius: 0 50% 50% 0; }
.kp-ot-cap.armed .kp-ot-cdel { background: rgba(255, 77, 79, .20); color: #E5544B; }

/* ⚠️ 这一段必须写在上面 .kp-ot-cbtn 基线**之后**：媒体查询不加权重，
   两处同为 (0,1,0) → 由源序决定。写到基线之前，40px 会被后面的 34px 盖掉
   （本文件第 247 行那段注释记录过同一个坑，⌖ 的 opacity 就这么白调过一次）。 */
@media (max-width: 760px) {
  .kp-ot-cbtn { width: 40px; height: 40px; }
  .kp-ot-cbtn > svg { width: 17px; height: 17px; }
}

.kp-ot-empty { padding: 26px 16px; text-align: center; color: var(--text-muted); font-size: 12px; line-height: 1.9; }
.kp-ot-empty b { color: var(--text-secondary); font-weight: 500; }

/* ═══ 开合按钮（周期栏右端 chip，与「周期 / 指标」同排） ═══
   ★ 2026-10-25 C1 定案：原先 position:absolute 浮在 #chart-wrap 右上角
     （top:6px; right:76px），移动端会压住指标数值；而「避开价格刻度」只能靠硬编码
     right:76px —— 刻度宽度随市场变（股票 6 位小数 vs crypto 2 位），再调也是猜。
     现在位置交给 DOM：period-bar.js 里 #object-tree-slot 排在 #indicator-bar-slot
     之后，是顶栏那一行的第三个功能入口，完全不占画布，也不再需要 top/right/z-index。
   ⚠️ 这里只写「差量」：描边 / 浅底 / hover / 开合高亮全部复用 kp-main.css 的
     .pb-entry + .pb-entry-filled（同一套变体2 分层）。别在这儿再复制一份颜色规则 ——
     两份一定会漂移，换主题色时只会改到其中一处。
   ⚠️ #kp-ot-toggle 是 ID 选择器(1,0,0)，会盖掉 .pb-entry 的同名属性：不要在这里
     重复 padding/font-size，除非确实要和周期/指标不同。 */
#kp-ot-toggle .ico svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
/* 计数徽标：这是「共几条」的静态读数，不是未读提醒 → 中性底，不跟开合高亮抢注意力 */
#kp-ot-toggle .kp-ot-badge {
  min-width: 14px; padding: 1px 4px; border-radius: 7px;
  background: var(--bg-4); color: var(--text-secondary);
  font: 600 10px/1.35 var(--font-ui, -apple-system, 'PingFang SC', sans-serif);
  font-variant-numeric: tabular-nums; text-align: center;
}
/* ★ 2026-10-27 A2 移动端瘦身：本按钮在移动端的**尺寸/图标/徽标全部搬到 kp-main.css** 的
   「A2 移动端瘦身」段统一管（选择器带 #period-bar → 特异性 (0,2,1,2)，稳压本文件的 (0,1,1,1)）。
   原先这里自带 padding:3px 8px / font-size:11px / icon 11px，A2 把三颗入口改成
   30×26 纯图标方盒后两份规则会打架 —— 本文件这份必输，留着就是「看着像生效、实际没用」
   的死规则，日后改这里的人会白调半天（正是 AGENTS.md 反复点名的分叉陷阱）→ 整段删掉。
   ⚠️ 只删「按钮尺寸」这一条链；本文件其它 @media (max-width:760px) 规则不动 ——
     那些管的是「窄屏怎么摆」（如 .kp-ot 的 position:fixed），与「是不是移动端形态」无关。 */
/* 多窗口「紧凑策略」模式：kp-main.css 那里把「周期 / 指标」一起 display:none 掉了
   （周期栏压到 32px 高、opacity .78，只留周期按钮），本入口跟队 ——
   否则 320px 窄栏会多挤一个 chip。语义在该文件，选择器放这里，两边不交叉改。 */
body.mw-multi.mw-pb-strategy-compact #kp-ot-toggle { display: none !important; }

/* ═══ 定位竖标（DOM）═══
   形状照抄 goto-modal.js 的 #goto-marker（同样是 #chart-wrap 的绝对定位子元素）：
   top:0;bottom:0;width:0 + border-left → 高度自动，不必算主图/副图分界。
   ⚠️ canvas 里画竖标在全页截图下存在合成时序不可见的坑（草图阶段实测），故用 DOM。

   ★ 2026-10-29 用户定案「贴顶 + 自动翻面」= **固定贴底**（`bottom:8px`）。
     为什么没有真的写翻面逻辑：顶部那两行文字（OHLC 行情行 0~28px、EMA 图例行 28~48px）
     是 klinecharts 画在 **canvas** 里的，`#chart-wrap` 内没有任何文字 DOM 节点 →
     拿 `getBoundingClientRect` 求交做不到，只能扫像素（getImageData + DPR 换算 + tainted 风险）。
     而这两行**常驻**占满顶部 0~48px，任何时刻标签贴顶都落在其中 → 翻面判定恒为真，
     逻辑等价于固定贴底。既然等价，就只留 CSS 一行，不引入像素扫描。
     ⚠️ 若将来顶部文字可关（如指标全关），这里要重新评估，别当成"永远贴底"的定论。 */
#kp-ot-mk {
  position: absolute; top: 0; bottom: 0; left: 0; width: 0;
  border-left: 1px dashed #f59e0b; opacity: .9;
  /* ⚠️ 2026-10-30 用户报「定位线压到弹窗上面了」：本条原为 z-index:4，与 #kp-ot-prog
     同值 → 同层叠上下文里按 DOM 顺序决胜，而竖标是**后创建**的（index 5 > 卡片 4），
     于是虚线直接穿过卡片文字。实测证据（真浏览器）：
       sameParent=true / mkIndex=5 / progIndex=4 / mkZ=4 / progZ=4 / crossesCard=true
     改 2：压在卡片(4)、高亮框(3) 之下，仍在 K 线 canvas（auto）与水印(1) 之上。 */
  display: none; pointer-events: none; z-index: 2;
}
#kp-ot-mk .kp-ot-mk-label {
  position: absolute; top: auto; bottom: 8px; left: 0; transform: translateX(-50%); white-space: nowrap;
  background: rgba(245, 158, 11, .96); color: #1a1204;
  font: 600 11px/1.4 var(--font-ui, -apple-system, 'PingFang SC', sans-serif);
  padding: 2px 8px; border-radius: 4px;
  /* 描边 + 更重的投影：底部是暗色 K 线底，单靠 0 2px 10px 会糊成一团（2026-10-29 实拍） */
  box-shadow: 0 0 0 2px rgba(245, 158, 11, .35), 0 3px 14px rgba(0, 0, 0, .65);
}
/* 朝上小三角：把标签和那条竖线"指"起来，避免贴底后看起来像图例而不是提示。
   画在标签上边缘外侧（top:-5px），尖端朝上对准竖线。 */
#kp-ot-mk .kp-ot-mk-label::after {
  content: ''; position: absolute; left: 50%; top: -5px; margin-left: -5px;
  width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent;
  border-bottom: 5px solid rgba(245, 158, 11, .96); border-top: 0;
}
/* ★ 2026-10-29 窄屏：贴底 8px 会掉进「已定位」收档浮层的地盘。
   实测（390×844）：定位后 `.kp-ot.on.kp-ot-peeking` 占底部 96px（见本文件 :73 那条），
   顶上还压着 `#kp-ot-cap` 工具条（fixed / z-index 1150）；标签 bottom 落在 795，
   而收档浮层顶边 748、cap top 784 → 文字下半截被吃掉。
   改成「从收档浮层上沿算起」：96 + 8 = 104px。
   ⚠️ 不写成 JS 量 cap 再抬升：cap 与 label 的出现顺序**不稳定**（实测先 showCap 再
     locate 时 cap 会被 locate 藏掉，反过来又会盖住标签），量一次拿不准；
     固定预留这块高度才不依赖时序。104 = 96 + 8，96 的真源在本文件 :73 那条 ——
     那边改了，这里必须跟着改（否则标签要么被压、要么白飘一截）。 */
@media (max-width: 760px) {
  #kp-ot-mk .kp-ot-mk-label { bottom: 104px; }
}

/* ═══ 定位高亮框（需求①：点定位后目标闪一下）═══
   与竖标同一宿主（#chart-wrap 或多窗口那一格），所以用绝对定位 + 相对宿主的 left/top。
   闪 2 下后淡出：0→1→.25→1→.15→0，总时长由 JS 的 FLASH_MS(1500ms) 决定并同步删除。
   ⚠️ 时长改这里必须同步 object-tree.js 的 FLASH_MS，否则框会在动画中途被抹掉（或拖尾）。 */
#kp-ot-flash {
  position: absolute; left: 0; top: 0; width: 0; height: 0;
  display: none; pointer-events: none; z-index: 3;
  border: 1.5px solid rgba(245, 158, 11, .95);
  border-radius: 6px;
  box-shadow: 0 0 0 1px rgba(245, 158, 11, .25), 0 0 14px 2px rgba(245, 158, 11, .35);
  box-sizing: border-box;
}
@keyframes kp-ot-flash-blink {
  0%   { opacity: 0; transform: scale(1.03); }
  12%  { opacity: 1; transform: scale(1); }
  30%  { opacity: .22; }
  48%  { opacity: 1; }
  66%  { opacity: .22; }
  84%  { opacity: .9; }
  100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  /* 前庭敏感 / 省电动画偏好：只保留一次淡入淡出，不做二次闪烁 */
  @keyframes kp-ot-flash-blink {
    0% { opacity: 0; }
    20% { opacity: 1; }
    100% { opacity: 0; }
  }
}

/* ═══ 行级菜单 ═══ */
#kp-ot-menu {
  position: fixed; z-index: 90; display: none; min-width: 186px;
  background: #171b22; border: 1px solid #2d3340; border-radius: 9px; padding: 5px;
  box-shadow: 0 14px 40px rgba(0, 0, 0, .6), 0 0 0 1px rgba(255, 255, 255, .02);
  backdrop-filter: blur(8px);
  font: 13px/1.5 var(--font-ui, -apple-system, 'PingFang SC', sans-serif);
}
#kp-ot-menu button {
  display: flex; align-items: center; gap: 8px; width: 100%; height: 29px; padding: 0 8px;
  border: 0; background: none; border-radius: 6px; font: inherit; font-size: 12.5px;
  color: #d8dce4; text-align: left; cursor: pointer;
}
#kp-ot-menu button:hover { background: #1A1A1F; }
#kp-ot-menu button.danger:hover { background: rgba(248, 81, 73, .14); color: #ff7b73; }
#kp-ot-menu button:disabled { color: #5c6472; cursor: default; }
#kp-ot-menu button:disabled:hover { background: none; }
#kp-ot-menu button .kp-ot-k { margin-left: auto; font-size: 10px; color: #5c6472; }
#kp-ot-menu button svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; opacity: .8; flex: none; }
#kp-ot-menu .kp-ot-sep { height: 1px; background: #232833; margin: 4px 2px; }

/* 色块：取实装 80 色板的前 32 色，8 列 × 4 行，默认折叠只露 2 行 */
.kp-ot-sw { display: grid; grid-template-columns: repeat(8, 1fr); gap: 4px; padding: 5px 6px 3px; }
.kp-ot-sw i {
  width: 16px; height: 16px; border-radius: 4px; cursor: pointer; display: block;
  /* 色板含 #131722 / #000000，深底上会“消失”→ 描边要够亮才读得出是个格子 */
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .30);
}
.kp-ot-sw i:hover { transform: scale(1.14); }
.kp-ot-sw.fold i:nth-child(n+17) { display: none; }
.kp-ot-morecolor {
  display: flex; align-items: center; justify-content: center; gap: 5px; margin: 0 2px 3px;
  padding: 3px 6px; border: 0; background: none; font: inherit; font-size: 11px;
  color: #8b93a1; border-radius: 5px; cursor: pointer; width: calc(100% - 4px);
}
.kp-ot-morecolor:hover { background: #1A1A1F; color: #f59e0b; }
.kp-ot-morecolor .kp-ot-caret { font-size: 9px; transition: transform .16s; }
.kp-ot-morecolor.open .kp-ot-caret { transform: rotate(180deg); }

/* ═══════════════════════════════════════════════════════════════════
   对象树「定位」进度浮层（2026-10-30 二版，B 方案：居中卡）

   [为什么要有它] goto 的进度原先只写进一个**没被打开**的弹窗，对象树点定位时
   图上从第 0 秒到第 15 秒毫无变化 —— 用户原话「点了半天没反应，也没有个进程动态表示」。
   本块只负责"看得见"，阶段判定全部在 goto-module.js（单一真源）。

   [为什么从 A（底部 3px 条 + 左下胶囊）换成 B] 用户 2026-10-30 拍板换 B。A 的两个硬伤：
     ① 进度条躺在图表最底边、胶囊在左下角，都在视野外圈，宽图里 34px 的小条几乎不引起注意；
     ② 成功/失败态的文案与「定位竖标」的标签落在同一条 8~13px 带上（草图阶段真撞过一次，
        `object-tree.js` 的 `_progRender` success 分支注释里有记录）—— 只能靠"标题里不写时间"去躲。
     居中卡放在视线焦点上：一句话结论 + 副标题 + 进度条 + 目标时间 + 动作，同框读完。

   [为什么卡片本体 pointer-events:none] 它盖在 K 线正中间，开着的那几秒里用户仍可能拖动/缩放。
   只有脚部两颗按钮（跳到最早 / 取消）单独开 pointer-events:auto，其余点击穿透。

   [配色依据] Binance Square 深炭磨砂 + 暗金：轨道 #1b2230，进度渐变 #f59e0b → #fbbf24，
   成功 #26a69a、失败 #ef4444（与图表涨跌色同族，语义读起来最快）。
   [范围] 全部选择器以 #kp-ot-prog 起头，不碰任何全局选择器，也不新增 :root 变量。
   ═══════════════════════════════════════════════════════════════════ */
#kp-ot-prog {
  /* 整卡缩放系数：桌面 1，窄屏 .7（见文件末尾 @media 段）。
     用 transform 缩放而不是逐条改 width/padding/font —— 后者要同步 10 个声明，
     漏一个就不再是「同一个设计缩小」，而缩放天然保持全部比例。 */
  --kp-ot-prog-s: 1;
  position: absolute; left: 50%; top: 46%;
  /* ⚠️ 居中靠 transform → 淡入动画必须沿用同一个 transform（见 kp-ot-prog-pop）。
     直接复用旧的 kp-ot-prog-fadeup（translateY(6px)）会在动画期间把卡片从居中位置
     拽偏 6px 再弹回来，窄屏上肉眼可见一次跳动。 */
  transform: translate(-50%, -50%) scale(var(--kp-ot-prog-s));
  width: 286px; max-width: calc(100% - 24px);
  z-index: 4;                 /* 高于定位竖标(2)、#kp-ot-flash(3)、水印(1)；低于窄屏胶囊(1150，挂 body) */
  border-radius: 10px;
  background: rgba(17, 21, 31, .97); border: 1px solid rgba(245, 158, 11, .34);
  box-shadow: 0 20px 52px rgba(0, 0, 0, .68);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  overflow: hidden;
  pointer-events: none;       /* 卡片本体不挡图；只有脚部按钮单独打开 */
  opacity: 0; transition: opacity .2s ease-out;
}
#kp-ot-prog.show { opacity: 1; animation: kp-ot-prog-pop .2s ease-out; }

/* ── 头部：转圈点 + 一句话结论 ── */
#kp-ot-prog .kp-ot-prog-hd { display: flex; align-items: center; gap: 9px; padding: 13px 14px 0; }
#kp-ot-prog .kp-ot-prog-dot {
  width: 16px; height: 16px; flex: 0 0 auto; border-radius: 50%;
  border: 2px solid rgba(245, 158, 11, .22); border-top-color: #fbbf24;
  animation: kp-ot-prog-spin .85s linear infinite;
}
#kp-ot-prog .kp-ot-prog-t {
  min-width: 0; font-size: 12.5px; font-weight: 650; line-height: 1.45;
  color: #e6ebf5; letter-spacing: .15px;
}

/* ── 副标题：这一阶段在干什么。文案一律取 goto 阶段快照的 sub，空则整行收起 ── */
#kp-ot-prog .kp-ot-prog-sub {
  padding: 6px 14px 0; font-size: 11px; line-height: 1.6; color: #7d879b;
}
#kp-ot-prog .kp-ot-prog-sub[hidden] { display: none; }

/* ── 进度条（5px，卡内留白 14px；不复用旧的 3px 贴边条）── */
#kp-ot-prog .kp-ot-prog-bar {
  position: relative; margin: 11px 14px 0; height: 5px;
  border-radius: 3px; background: #1b2230; overflow: hidden;
}
#kp-ot-prog .kp-ot-prog-fill {
  position: absolute; left: 0; top: 0; bottom: 0; width: 0; border-radius: 3px;
  background: linear-gradient(90deg, #f59e0b, #fbbf24);
  transition: width .42s cubic-bezier(.4, 0, .2, 1);
}
/* 流光：让"还在动"这件事在 200ms 内就能看出来（静态进度条看不出有没有在跑） */
#kp-ot-prog .kp-ot-prog-fill::before {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .8), transparent);
  background-size: 100px 100%; background-repeat: no-repeat;
  animation: kp-ot-prog-stream 1.4s linear infinite;
}

/* ── 脚部：目标时间片 + 动作 ── */
#kp-ot-prog .kp-ot-prog-ft {
  display: flex; align-items: center; gap: 8px; padding: 11px 14px 13px;
}
#kp-ot-prog .kp-ot-prog-when {
  font-size: 11px; color: #fbbf24; font-variant-numeric: tabular-nums; white-space: nowrap;
  background: rgba(245, 158, 11, .12); border: 1px solid rgba(245, 158, 11, .3);
  border-radius: 4px; padding: 2px 6px;
}
#kp-ot-prog .kp-ot-prog-grow { flex: 1; }

#kp-ot-prog .kp-ot-prog-jump {
  height: 22px; flex: 0 0 auto; padding: 0 9px;
  border-radius: 5px; cursor: pointer; font-size: 11px; font-family: inherit;
  background: rgba(245, 158, 11, .16); border: 1px solid rgba(245, 158, 11, .55);
  color: #fbbf24; white-space: nowrap;
  pointer-events: auto;       /* 卡片本体是 none，按钮必须自己开 */
}
#kp-ot-prog .kp-ot-prog-jump:hover { background: rgba(245, 158, 11, .26); }
#kp-ot-prog .kp-ot-prog-jump[hidden] { display: none; }

#kp-ot-prog .kp-ot-prog-x {
  height: 22px; flex: 0 0 auto; padding: 0 9px;
  border-radius: 5px; cursor: pointer; font-size: 11px; font-family: inherit;
  background: #1c2434; border: 1px solid #2f3a4e; color: #b9c3d4;
  white-space: nowrap; pointer-events: auto;
}
#kp-ot-prog .kp-ot-prog-x:hover { border-color: #4a5875; color: #e2e8f3; }

/* ── 阶段变体 ── */
#kp-ot-prog.ph-success { border-color: rgba(38, 166, 154, .5); }
#kp-ot-prog.ph-success .kp-ot-prog-fill { background: #26a69a; }
#kp-ot-prog.ph-success .kp-ot-prog-fill::before { animation: none; opacity: 0; }
#kp-ot-prog.ph-success .kp-ot-prog-dot {
  border: 0; animation: none; width: auto; height: auto;
  color: #26a69a; font-size: 14px;
}
#kp-ot-prog.ph-success .kp-ot-prog-dot::before { content: '✓'; }
#kp-ot-prog.ph-success .kp-ot-prog-t { color: #5fd3c4; }

#kp-ot-prog.ph-error { border-color: rgba(239, 83, 80, .5); }
#kp-ot-prog.ph-error .kp-ot-prog-fill { background: #ef4444; }
#kp-ot-prog.ph-error .kp-ot-prog-fill::before { animation: none; opacity: 0; }
#kp-ot-prog.ph-error .kp-ot-prog-dot {
  border: 0; animation: none; width: auto; height: auto;
  color: #ef5350; font-size: 13px;
}
#kp-ot-prog.ph-error .kp-ot-prog-dot::before { content: '!'; }
#kp-ot-prog.ph-error .kp-ot-prog-t { color: #ff8b88; }
#kp-ot-prog.ph-error .kp-ot-prog-sub { color: #c9a2a1; }
/* 失败态脚部有三个元素（时间片 + 跳到最早 + 关闭），286px 内挤不下 →
   时间片独占一行，按钮另起一行右对齐。 */
#kp-ot-prog.ph-error .kp-ot-prog-ft { flex-wrap: wrap; row-gap: 9px; }
#kp-ot-prog.ph-error .kp-ot-prog-when { flex: 1 0 100%; }
#kp-ot-prog.ph-error .kp-ot-prog-grow { display: none; }
#kp-ot-prog.ph-error .kp-ot-prog-jump { margin-left: auto; }

#kp-ot-prog.ph-info .kp-ot-prog-fill { background: #4b5563; }

/* ── 窄屏（与 _isNarrow() 同一断点 760px，改一处要改两处）──
   2026-10-30 用户报「移动端这个框占比太大，一点都不美观」→ 整卡缩到 0.7。
   358px 视口下原来 286px ≈ 80% 宽，缩小后约 200px，图面留出可看的 K 线。
   ⚠️ 只缩视觉不缩布局盒（transform 不参与排版）→ 失败态脚部换行判据仍按 286 算，
     三种态的行结构不会因为缩放而改变。 */
@media (max-width: 760px) {
  #kp-ot-prog { --kp-ot-prog-s: .7; }
  /* 缩放后 26px 会变成 18px 的触控区，低于可点的下限 → 窄屏按钮先放大到 30px（视觉 21px） */
  #kp-ot-prog .kp-ot-prog-jump,
  #kp-ot-prog .kp-ot-prog-x { height: 30px; }
  #kp-ot-prog.ph-error .kp-ot-prog-jump,
  #kp-ot-prog.ph-error .kp-ot-prog-x { flex: 1 1 auto; }
}

@keyframes kp-ot-prog-stream { 0% { background-position: -120px 0; } 100% { background-position: 420px 0; } }
@keyframes kp-ot-prog-spin { to { transform: rotate(360deg); } }
/* 淡入必须自带居中 transform，见 #kp-ot-prog 段头。
   scale 里带 `var(--kp-ot-prog-s)`：不带的话窄屏 .7 的卡在淡入的 200ms 里会闪成 1.0 大小。 */
@keyframes kp-ot-prog-pop {
  from { opacity: 0; transform: translate(-50%, -50%) scale(calc(var(--kp-ot-prog-s) * .96)); }
  to { opacity: 1; transform: translate(-50%, -50%) scale(var(--kp-ot-prog-s)); }
}

/* 省电 / 前庭敏感：只留一次淡入，不转圈、不流光 */
@media (prefers-reduced-motion: reduce) {
  #kp-ot-prog.show { animation: none; }
  #kp-ot-prog .kp-ot-prog-dot { animation: none; }
  #kp-ot-prog .kp-ot-prog-fill::before { animation: none; opacity: 0; }
}
