/* controller-tool.css — 手柄配置工具样式（flybing 式卡片布局 + 炽壳高对比双主题）
   设计铁律：accent(橙 #FF6B35) 仅作图形（描边/填充/CTA底/进度条/状态点）；
   选中/点亮 = 加粗 + 主色文字 + 中性底，绝不用橙色文字。顶栏/页脚恒定深色作品牌锚点，不随主题翻转。 */

/* ═══════════ 主题令牌（深色为默认，浅色由 data-theme 覆盖）═══════════
   ⚠ **必须同时挂在 .ct-modal-overlay 上**（2026-08-28 修）：弹窗是
   `document.body.appendChild(overlay)` 挂的（macro.js），**在 .ct-wrap 之外**，
   只写 .ct-wrap 时弹窗一个令牌都拿不到。后果分两种，都难察觉：
     · 写了 fallback 的（大多数）→ 退回暗色字面量，深色主题下碰巧正常，
       **浅色主题下整个弹窗仍是暗的**；
     · 没写 fallback 的 → 整条声明作废。手柄线稿 `fill: var(--ct-pad-line)` 退回默认黑，
       在深色弹窗上**完全隐形** —— 宏预览"看不到手柄图"就是这么来的。
   不提升到 :root：浅色令牌会漏给必须恒暗的站点 nav/页脚（见本文件面包屑栏那段）。
   新增任何挂在 body 上的浮层，都要加进下面两个选择器。 */
.ct-wrap,
.ct-modal-overlay {
  color: var(--text-primary, #eef2f8);
  --bg-page: #161b24;
  --bg-card: #1e2530;
  --bg-card-hover: #262f3d;
  --bg-inset: #12161e;         /* 内嵌轨道 / 输入底：比卡片更深 */
  --text-primary: #eef2f8;
  --text-secondary: rgba(233,240,249,.90);
  --text-tertiary: rgba(233,240,249,.60);
  --border-light: rgba(255,255,255,.10);
  --border-strong: rgba(255,255,255,.18);
  /* ⚠ --color-accent 此前**从未定义**（2026-08-27 补）：CSS 里每处都写了 fallback #FF6B35
     所以看不出问题，但 viz.js 的 SVG 属性用的是不带 fallback 的 var(--color-accent) ——
     属性值非法即被忽略：stroke 退化成 none（校准曲线折线整条不画）、fill 退化成黑
     （摇杆实时光点、扳机实时条在深色底上完全隐形）、控制点失去橙色描边。
     令牌缺失在 CSS 侧被 fallback 静默兜住，在 SVG 侧却是硬失败 —— 同一个缺陷两种表现。 */
  --color-accent: #FF6B35;
  --color-accent-tint: rgba(255,107,53,.16);
  --ct-danger: #ff5a4d;
  --ct-ok: #35c759;
  /* 死区专用中性灰：死区 = "这段推动无效"，用 accent(橙) 会读成"这段是有效行程"，语义正好反了。
     故死区一律灰，主色/绿色留给实时值 —— 灰底 + 绿值同图，才能一眼看出当前是否已越过死区。 */
  /* 有效区专用色：不要复用 --color-accent-tint（.16）—— 那是给「选中态底色」用的弱强度，
     压在深色卡上读成暗红，比旁边 .22 的浅灰死区还闷，「无效」反而比「有效」显眼。
     这里要的是「这一圈是活的」，强度必须压过死区灰。2026-08-27 用户指出配色反了。 */
  --dz-active: rgba(255,107,53,.34);
  --dz-fill: rgba(233,240,249,.22);
  --dz-line: rgba(233,240,249,.42);
  --shadow-card: 0 1px 3px rgba(0,0,0,.28), 0 6px 20px rgba(0,0,0,.22);
  /* 手柄线稿令牌（figma 线稿全 fill=black 细带 → CSS fill 覆盖即整体染色）*/
  --ct-pad-bg: #202634;
  --ct-pad-line: #b8c4d6;
  --ct-pad-dp: #171c26;
  --ct-new: #4aa3ff;
}
/* ── 浅色主题：覆盖令牌（作用域须与上方一致，否则弹窗在浅色下不跟随）── */
html[data-theme="light"] .ct-wrap,
html[data-theme="light"] .ct-modal-overlay {
  --bg-page: #eef1f5;
  --bg-card: #ffffff;
  --bg-card-hover: #eef2f7;
  --bg-inset: #eef1f5;
  --text-primary: #16273a;
  --text-secondary: #33414f;
  --text-tertiary: #5a6875;
  --border-light: rgba(20,40,70,.12);
  --border-strong: rgba(20,40,70,.22);
  --color-accent: #FF6B35;
  --color-accent-tint: rgba(255,107,53,.14);
  --ct-danger: #e0362a;
  --dz-active: rgba(255,107,53,.20);
  --dz-fill: rgba(20,40,70,.17);
  --dz-line: rgba(20,40,70,.42);
  --shadow-card: 0 1px 3px rgba(40,70,110,.10), 0 8px 26px rgba(40,70,110,.10);
  --ct-pad-bg: #f4f6f9;
  --ct-pad-line: #33485e;
  --ct-pad-dp: #ffffff;
}

/* hidden 属性优先于任何 class 的 display（修 .ct-devcard/.ct-hero 等 flex 覆盖 [hidden] 的特异性平手问题）*/
[hidden] { display: none !important; }

/* 整页背景随主题；body 竖向撑满让页脚沉底 */
body { min-height: 100vh; display: flex; flex-direction: column; margin: 0; }
html[data-theme="dark"] body  { background: #12161e; }
html[data-theme="light"] body { background: #eef1f5; }

/* ═══════════ 站点栏让位 + 面包屑栏（2026-08-28 收编为官网栏目）═══════════
 *
 * 页头改用官网的 .ck-nav（style.css），工具自己的操作项下沉到本条面包屑栏。
 * 原 .ct-topbar / .ct-brand* / .ct-home 已删 —— 它们是工具做孤岛页时的自造品牌区，
 * 收编后由官网导航承担品牌与栏目归属，留着就是同一职责的第二份实现。
 *
 * 站点栏与页脚**不需要**额外锁主题：本文件的浅色令牌挂在
 * `html[data-theme="light"] .ct-wrap`（只作用于内容容器），而 .ck-nav / .ck-footer 是
 * .ct-wrap 的兄弟节点，拿不到那批令牌，始终从 :root 继承暗色 —— 这正是我们要的效果。
 * ⚠ 若将来把浅色令牌上提到 `html[data-theme="light"]`，页脚会立刻坏掉：
 *   style.css 的 .ck-footer 背景硬编码 #06060A、前景却取 var(--text-primary)，
 *   令牌一旦变成浅色语义就成了「深色字压近黑底」。上提前先在这两个子树根上钉回暗色值。
 */

.ct-crumbbar {
  position: sticky; top: var(--nav-height, 60px); z-index: 40;
  margin-top: var(--nav-height, 60px);
  background: #131923;
  border-bottom: 1px solid rgba(255,255,255,.08);
  box-shadow: 0 1px 0 rgba(255,107,53,.55);   /* 橙品牌细线，沿用原顶栏的图形语言 */
}
.ct-crumbbar-inner {
  max-width: 1200px; margin: 0 auto; padding: 6px 20px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
}
.ct-crumb { font-size: var(--fs-sm); color: rgba(233,240,249,.5); min-width: 0; }
.ct-crumb a { color: rgba(233,240,249,.72); text-decoration: none; }
.ct-crumb a:hover { color: #fff; }
.ct-crumb-sep { margin: 0 7px; color: rgba(233,240,249,.3); }
.ct-crumb-now { color: #f3f6fb; font-weight: 600; }
.ct-crumb-sub { font-size: var(--fs-sm); color: rgba(233,240,249,.5); margin-left: 8px; }
.ct-crumb-ops { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* 鼠标下用紧凑控件，让这条栏保持在 ~44px；触摸设备恢复 44px 触摸目标（宁可栏变高）*/
.ct-crumbbar .ct-btn { padding: 6px 14px; font-size: var(--fs-sm); border-radius: var(--radius-sm); }
.ct-crumbbar .ct-theme-btn { padding: 5px 11px; font-size: var(--fs-sm); border-radius: var(--radius-sm); }
.ct-crumbbar .ct-status { font-size: var(--fs-sm); }
@media (pointer: coarse) {
  .ct-crumbbar .ct-btn { padding: 11px 18px; font-size: var(--fs-lg); }
  .ct-crumbbar .ct-theme-btn { padding: 10px 14px; font-size: var(--fs-base); }
}

.ct-theme-btn {
  padding: 7px 13px; border-radius: var(--radius-sm); cursor: pointer; white-space: nowrap;
  font-size: var(--fs-base); font-weight: 600;
  background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.14); color: #dfe6ef;
  transition: background .2s;
}
.ct-theme-btn:hover { background: rgba(255,255,255,.12); }
.ct-status { font-size: var(--fs-base); font-weight: 700; white-space: nowrap; padding-left: 2px; }
.ct-status--connected { color: var(--ct-ok, #35c759); }
.ct-status--connecting { color: #ffd27a; }
.ct-status--disconnected { color: rgba(233,240,249,.5); }

/* ═══════════ 主体容器 ═══════════ */
.ct-wrap { flex: 1; max-width: 1200px; width: 100%; margin: 0 auto; padding: 24px 20px 48px; box-sizing: border-box; }

/* ═══ 卡片节奏标准化（2026-08-29，用户「卡片的间距也需要标准化」）═══
   **卡片之间的间距一律由容器 gap 给，绝不由卡片自己的 margin 给。**
   改之前：测试工具的 #gt-app 没有任何容器样式，卡片靠各自 margin-bottom 撑开 ——
   .ct-devcard 14px / .ct-maintcard 20px / .ct-card 与 .ct-scard 0，于是同一页里
   相邻两卡的距离取决于**上面那张卡是谁**，看起来就是"忽宽忽窄"。
   靠 margin 排版还有两个老问题：相邻 margin 会合并（塌陷），首尾会多出一截空白。

   取 16px：本文件早已声明的**卡片级**标准（见 .ct-settings-page 与连击分组处注释），
   刻度 4/8/12/16/20 中 12 为子元素级、16 为卡片级。这里只是把它推到所有卡片堆叠处。 */
#gt-app, #ct-app { display: flex; flex-direction: column; gap: 16px; }

/* ═══════════ 按钮系列 ═══════════ */
.ct-btn {
  padding: 9px 18px; border-radius: var(--radius-base); border: none; cursor: pointer;
  background: var(--color-accent, #FF6B35); color: #fff; font-size: var(--fs-lg); font-weight: 700;
  transition: background .2s, transform .05s; white-space: nowrap;
}
.ct-btn:hover { background: var(--color-accent-hover, #ff8255); }
.ct-btn:active { transform: translateY(1px); }
.ct-btn:disabled { background: rgba(140,150,165,.5); cursor: not-allowed; }
.ct-btn-lg { padding: 13px 30px; font-size: var(--fs-lg); border-radius: var(--radius-base); }
.ct-btn-ghost {
  background: transparent; border: 1px solid var(--border-strong, rgba(255,255,255,.18));
  color: var(--text-secondary, rgba(255,255,255,.7)); font-weight: 600;
}
.ct-btn-ghost:hover { background: var(--bg-card-hover, rgba(255,255,255,.08)); color: var(--text-primary, #fff); }
/* 顶栏（恒定深色）内的 ghost 按钮需浅色描边 */
.ct-crumbbar .ct-btn-ghost { border-color: rgba(255,255,255,.2); color: #dfe6ef; }
.ct-crumbbar .ct-btn-ghost:hover { background: rgba(255,255,255,.1); color: #fff; }
.ct-btn-danger {
  background: transparent; border: 1px solid var(--ct-danger, #ff5a4d);
  color: var(--ct-danger, #ff5a4d); font-weight: 700;
  padding: 9px 18px; border-radius: var(--radius-base); cursor: pointer; font-size: var(--fs-lg); transition: background .2s, color .2s;
}
.ct-btn-danger:hover { background: var(--ct-danger, #ff5a4d); color: #fff; }
.ct-btn-danger:disabled { opacity: .5; cursor: not-allowed; }

/* ═══════════ 通用卡片 ═══════════ */
.ct-card {
  background: var(--bg-card, #1e2530);
  border: 1px solid var(--border-light, rgba(255,255,255,.10));
  /* 内边距与 .ct-scard / .ct-devcard 统一为 20px 22px：原 24px 不在本文件的
     4/8/12/16/20 刻度上，且同页三种卡片三种内边距，边框对不齐一眼看得出来。 */
  border-radius: var(--radius-lg); padding: 20px 22px; box-shadow: var(--shadow-card);
}
.ct-unsupported { margin-top: 24px; }
.ct-unsupported h2 { color: var(--text-primary, #fff); margin-top: 0; }
.ct-unsupported p { color: var(--text-secondary, rgba(255,255,255,.7)); line-height: var(--lh-base); }

/* ═══════════ Hero（未连接引导）═══════════ */
.ct-hero {
  background: var(--bg-card, #1e2530);
  border: 1px solid var(--border-light, rgba(255,255,255,.10));
  border-radius: var(--radius-lg); padding: 56px 32px; margin-bottom: 20px;
  display: flex; flex-direction: column; align-items: center; text-align: center;
  box-shadow: var(--shadow-card);
  background-image: radial-gradient(120% 80% at 50% -10%, var(--color-accent-tint, rgba(255,107,53,.16)), transparent 60%);
}
.ct-hero-icon {
  width: 78px; height: 78px; border-radius: var(--radius-lg); display: grid; place-items: center;
  color: var(--color-accent, #FF6B35);
  background: var(--bg-inset, #12161e); border: 1px solid var(--border-light, rgba(255,255,255,.10));
  margin-bottom: 20px;
}
.ct-hero-title { font-size: var(--fs-2xl); font-weight: 800; margin: 0 0 10px; color: var(--text-primary, #fff); }
.ct-hero-sub { font-size: var(--fs-lg); line-height: var(--lh-base); color: var(--text-secondary, rgba(255,255,255,.75)); max-width: 460px; margin: 0 0 24px; }
.ct-hero-note { font-size: var(--fs-sm); color: var(--text-tertiary, rgba(255,255,255,.5)); margin: 16px 0 0; }

/* ═══════════ 设备状态大卡（连接后常驻，跨 Tab）═══════════ */
.ct-devcard {
  display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
  background: var(--bg-card, #1e2530);
  border: 1px solid var(--border-light, rgba(255,255,255,.10));
  border-radius: var(--radius-lg); padding: 20px 22px; box-shadow: var(--shadow-card);   /* 间距由容器 gap 给 */
}
.ct-devcard-ico {
  width: 52px; height: 52px; flex-shrink: 0; border-radius: var(--radius-base); display: grid; place-items: center;
  color: var(--color-accent, #FF6B35);
  background: var(--bg-inset, #12161e); border: 1px solid var(--border-light, rgba(255,255,255,.10));
}
.ct-devcard-id { min-width: 0; }
.ct-devcard-name { font-size: var(--fs-xl); font-weight: 800; color: var(--text-primary, #fff); line-height: var(--lh-tight); }
.ct-devcard-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 5px; }
.ct-fw-badge {
  font-size: var(--fs-sm); font-weight: 700; font-family: var(--font-mono);
  padding: 2px 9px; border-radius: 999px;
  background: var(--color-accent-tint, rgba(255,107,53,.16)); color: var(--text-primary, #fff);
  border: 1px solid var(--border-light, rgba(255,255,255,.10));
}
.ct-devcard-usb { font-size: var(--fs-sm); font-family: var(--font-mono); color: var(--text-tertiary, rgba(255,255,255,.5)); }
/* 电池：四格图标 + 状态文字（协议只有 4 档，见 app.js batteryInfo）*/
.ct-devcard-batt {
  margin-left: auto; display: flex; align-items: center; gap: 8px;
  --ct-batt-on: var(--color-accent, #FF6B35);
}
.ct-devcard-batt.low { --ct-batt-on: var(--ct-danger, #ff5a4d); }
.ct-devcard-batt.charging { --ct-batt-on: #2fbf5b; }
.ct-batt-lab { font-size: var(--fs-sm); font-weight: 600; color: var(--text-tertiary, rgba(255,255,255,.55)); letter-spacing: .3px; }
.ct-devcard-batt.charging .ct-batt-lab { color: #2fbf5b; display: inline-flex; align-items: center; }
.ct-batt-svg { flex: none; display: block; }
.ct-batt-shell { fill: none; stroke: var(--border-strong, rgba(255,255,255,.18)); stroke-width: 2; }
.ct-batt-cap { fill: var(--border-strong, rgba(255,255,255,.18)); }
/* 未点亮的格子保留一层极淡底色：空槽位可见，才能一眼看出"共 4 格、亮了几格" */
.ct-batt-cell { fill: var(--text-primary, #fff); opacity: .12; transition: fill .3s ease, opacity .3s ease; }
.ct-batt-cell.on { fill: var(--ct-batt-on); opacity: 1; }
.ct-devcard-raw { font-size: var(--fs-sm); font-family: var(--font-mono); color: var(--text-tertiary, rgba(255,255,255,.5)); }
.ct-devcard-act { display: flex; align-items: center; }

/* ═══════════ 设备维护卡（恢复出厂）═══════════ */
.ct-maintcard {
  background: var(--bg-card, #1e2530);
  border: 1px solid var(--border-light, rgba(255,255,255,.10));
  border-radius: var(--radius-lg); padding: 20px 22px; box-shadow: var(--shadow-card);   /* 同上 */
}
.ct-maint-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px 20px; align-items: start; }
@media (max-width: 640px) { .ct-maint-grid { grid-template-columns: 1fr; } }
.ct-maint-row { display: flex; flex-direction: column; gap: 12px; padding: 16px; border-radius: var(--radius-base); background: var(--bg-inset, rgba(255,255,255,.03)); box-shadow: inset 0 0 0 1px var(--border-light, rgba(255,255,255,.07)); }
.ct-maint-ctrl { width: 100%; }
.ct-maint-row .ct-btn-danger { align-self: flex-start; }
.ct-maint-txt { min-width: 0; }
.ct-maint-title { font-size: var(--fs-lg); font-weight: 700; color: var(--text-primary, #fff); }
.ct-maint-sub { font-size: var(--fs-sm); color: var(--text-tertiary, rgba(255,255,255,.55)); margin-top: 3px; }

/* ═══════════ 大 Tab（测试 / 设置）分段控制 ═══════════ */
.ct-tabs {
  display: inline-flex; gap: 4px; padding: 4px; margin-bottom: 20px;
  background: var(--bg-card, #1e2530); border: 1px solid var(--border-light, rgba(255,255,255,.10));
  border-radius: var(--radius-base); box-shadow: var(--shadow-card);
}
.ct-tabs button {
  padding: 9px 22px; border: none; cursor: pointer; border-radius: var(--radius-sm);
  background: transparent; color: var(--text-secondary, rgba(255,255,255,.65)); font-size: var(--fs-lg); font-weight: 600;
  transition: background .15s, color .15s;
}
.ct-tabs button:hover { color: var(--text-primary, #fff); }
.ct-tabs button.active {
  background: var(--bg-card-hover, #262f3d); color: var(--text-primary, #fff); font-weight: 800;
  box-shadow: 0 1px 3px rgba(0,0,0,.2);
}

/* ═══════════ 空态（设置 Tab 未连接）═══════════ */
.ct-panel { min-height: 120px; }
.ct-empty { color: var(--text-secondary, rgba(255,255,255,.65)); padding: 32px; text-align: center; font-size: var(--fs-lg); }
.ct-empty-state {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  padding: 56px 24px; background: var(--bg-card, #1e2530);
  border: 1px solid var(--border-light, rgba(255,255,255,.10)); border-radius: var(--radius-lg); box-shadow: var(--shadow-card);
}
.ct-empty-state-icon {
  width: 62px; height: 62px; border-radius: var(--radius-lg); display: grid; place-items: center; margin-bottom: 16px;
  color: var(--text-tertiary, rgba(255,255,255,.5));
  background: var(--bg-inset, #12161e); border: 1px solid var(--border-light, rgba(255,255,255,.10));
}
.ct-empty-state-title { font-size: var(--fs-xl); font-weight: 700; color: var(--text-primary, #fff); margin: 0 0 6px; }
.ct-empty-state-sub { font-size: var(--fs-base); color: var(--text-tertiary, rgba(255,255,255,.55)); max-width: 360px; margin: 0; line-height: var(--lh-base); }

/* ═══════════ 设置分区：两栏（左导航 + 右内容）═══════════ */
.ct-settings-body { display: flex; gap: 20px; align-items: flex-start; }
.ct-nav {
  width: 176px; flex-shrink: 0; display: flex; flex-direction: column; gap: 3px;
  position: sticky; top: 74px;
  background: var(--bg-card, #1e2530); border: 1px solid var(--border-light, rgba(255,255,255,.10));
  border-radius: var(--radius-lg); padding: 8px; box-shadow: var(--shadow-card);
}
.ct-nav-item {
  display: block; width: 100%; text-align: left; padding: 10px 13px; border-radius: var(--radius-sm); font-size: var(--fs-lg);
  background: transparent; border: none; cursor: pointer; color: var(--text-secondary, rgba(255,255,255,.65));
  transition: background .15s, color .15s;
}
.ct-nav-item:hover { background: var(--bg-card-hover, #262f3d); color: var(--text-primary, #fff); }
.ct-nav-item.on { background: var(--bg-card-hover, #262f3d); color: var(--text-primary, #fff); font-weight: 800; }
.ct-nav-item.on::before {
  content: ""; display: inline-block; width: 3px; height: 14px; border-radius: 2px; vertical-align: -2px;
  background: var(--color-accent, #FF6B35); margin-right: 9px; margin-left: -4px;
}
.ct-settings-content { flex: 1; min-width: 0; }

/* 页头（大标题 + 描述）*/
.ct-page-head { margin-bottom: 14px; }
.ct-page-title { font-size: var(--fs-2xl); font-weight: 800; color: var(--text-primary, #fff); margin: 0; }
.ct-page-desc { font-size: var(--fs-base); color: var(--text-tertiary, rgba(255,255,255,.55)); margin: 5px 0 0; line-height: var(--lh-base); }

/* SettingCard（设置模块卡）*/
.ct-scard {
  background: var(--bg-card, #1e2530); border: 1px solid var(--border-light, rgba(255,255,255,.10));
  border-radius: var(--radius-lg); padding: 20px 22px; box-shadow: var(--shadow-card);
}
.ct-scard-body > h3:first-child { margin-top: 0; }

/* ═══════════ 确认弹窗（恢复出厂等）═══════════ */
.ct-modal-overlay {
  position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 24px;
  background: rgba(8,10,15,.6); backdrop-filter: blur(3px);
}
.ct-modal {
  width: 100%; max-width: 400px; background: var(--bg-card, #1e2530);
  border: 1px solid var(--border-strong, rgba(255,255,255,.18)); border-radius: var(--radius-lg); padding: 24px;
  box-shadow: 0 20px 60px rgba(0,0,0,.5); color: var(--text-primary, #fff);
}
.ct-modal-title { font-size: var(--fs-xl); font-weight: 800; margin: 0 0 10px; }
.ct-modal-msg { font-size: var(--fs-base); line-height: var(--lh-base); color: var(--text-secondary, rgba(255,255,255,.8)); margin: 0 0 22px; }
.ct-modal-acts { display: flex; justify-content: flex-end; gap: 10px; }

/* ═══════════ 校准向导 ═══════════
   分步引导 + 每步实时完成度。步骤状态三态：待办 / 进行中(is-current) / 已完成(is-done)。
   ⚠ 进度文字必须**一直占位**，不能完成才出现 —— 否则每步完成时整个列表跳动，
     用户正在转摇杆看不清自己完成到哪。故 .ct-wiz-prog 恒定渲染（未开始时显示"等待…"）。 */
/* 向导比普通弹窗宽：每行是「步骤名 + 实时进度」两段，460px 会把
   "已转 2.4 / 3 圈，还需推到四个方向的极限" 这类进度文字挤到换行，
   而它每帧都在变 —— 一换行整个列表就跳动，用户正在转摇杆根本看不清。 */
.ct-wizard { max-width: 620px; }
.ct-wiz-steps { list-style: none; margin: 4px 0 18px; padding: 0; display: grid; gap: 8px; }
.ct-wiz-step {
  display: grid; grid-template-columns: 26px minmax(9em, auto) 1fr; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: var(--radius-base);
  background: var(--bg-inset); border: 1px solid transparent;
  color: var(--text-tertiary); font-size: var(--fs-base); transition: background .15s, border-color .15s, color .15s;
}
.ct-wiz-step.is-current { border-color: var(--color-accent); background: var(--color-accent-tint); color: var(--text-primary); }
.ct-wiz-step.is-done { color: var(--text-secondary); }
.ct-wiz-mark {
  width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center;
  font-size: var(--fs-sm); font-weight: 700;
  background: var(--bg-card); border: 1px solid var(--border-light); color: var(--text-tertiary);
}
.ct-wiz-step.is-current .ct-wiz-mark { background: var(--color-accent); border-color: var(--color-accent); color: #fff; }
.ct-wiz-step.is-done .ct-wiz-mark { background: var(--ct-ok); border-color: var(--ct-ok); color: #fff; }
.ct-wiz-label { line-height: var(--lh-tight); }
.ct-wiz-prog { font-size: var(--fs-sm); color: var(--text-tertiary); font-variant-numeric: tabular-nums; text-align: right; }
.ct-wiz-step.is-current .ct-wiz-prog { color: var(--color-accent); }
.ct-wiz-step.is-done .ct-wiz-prog { color: var(--ct-ok); }
.ct-wiz-tip { font-size: var(--fs-sm); line-height: var(--lh-base); color: var(--text-secondary); margin: 0 0 18px; min-height: 2.4em; }

/* 完成态 */
.ct-wiz-ok { text-align: center; }
.ct-wiz-ok-ico {
  width: 52px; height: 52px; margin: 4px auto 14px; border-radius: 50%;
  display: grid; place-items: center; font-size: var(--fs-xl); font-weight: 800;
  background: var(--ct-ok); color: #fff;
}
.ct-wiz-ok-msg { font-size: var(--fs-base); line-height: var(--lh-base); color: var(--text-secondary); margin: 0 0 20px; }
.ct-wiz-ok .ct-modal-acts { justify-content: center; }

/* ═══════════ 诊断日志 ═══════════ */
.ct-diag { margin-top: 26px; }
.ct-diag summary { cursor: pointer; color: var(--text-tertiary, rgba(255,255,255,.5)); font-size: var(--fs-base); }
.ct-diag pre {
  background: #0b0e14; color: #6cf06c; padding: 12px; border-radius: var(--radius-base);
  max-height: 280px; overflow: auto; font-size: var(--fs-sm); white-space: pre-wrap; margin-top: 8px;
}

/* ═══════════ Toast ═══════════ */
.ct-toast {
  position: fixed; bottom: 26px; left: 50%; transform: translateX(-50%);
  background: var(--bg-card, #1e2530); border: 1px solid var(--border-strong, rgba(255,255,255,.18));
  color: var(--text-primary, #fff); padding: 11px 22px; border-radius: var(--radius-base);
  box-shadow: 0 10px 34px rgba(0,0,0,.5); z-index: 1100; font-size: var(--fs-lg); font-weight: 600;
}
html[data-theme="light"] .ct-toast { color: #16273a; }
.ct-toast--err { border-color: var(--ct-danger, #ff5a4d); }
.ct-toast--ok { border-color: var(--ct-ok, #35c759); }

/* ═══════════ 设置模块通用控件（保留：读取失败 / 空提示 / 滑杆 / 数字 / 次级按钮）═══════════ */
.ct-module {
  background: var(--bg-card, #1e2530); border: 1px solid var(--border-light, rgba(255,255,255,.10));
  border-radius: var(--radius-lg); padding: 18px; margin-bottom: 14px;
}
.ct-module h3 { margin: 0 0 12px; font-size: var(--fs-xl); }
/* 模块内分组：灯组 / 摇杆左右轴（卡内小节分隔）*/
.ct-lamp-group, .ct-axis-side { padding: 14px 0; border-bottom: 1px solid var(--border-light, rgba(255,255,255,.08)); }
.ct-lamp-group:first-child, .ct-axis-side:first-child { padding-top: 4px; }
.ct-lamp-group:last-child, .ct-axis-side:last-child { border-bottom: none; padding-bottom: 0; }
.ct-lamp-group h4, .ct-axis-side h4 { margin: 0 0 10px; font-size: var(--fs-lg); font-weight: 700; color: var(--text-primary, #fff); }
.ct-row { display: flex; align-items: center; gap: 12px; margin: 12px 0; }
/* 行标签：`<label>`（selectRow 等生成）与 `.ct-row-label`（分段/网格行手工建的 span）**同款**。
   ⚠ 原先只有元素选择器 `.ct-row label`，而 common.js 的 selectRow 建的是 label、
   体感分区手工建的是 span.ct-row-label —— 后者不命中任何规则，落到裸样式：
   字号继承 body 的 16px（比同卡 sliderRow 的标签更亮、比马达页 selectRow 的 18px 小一档）。
   `.ct-row-label` 全仓有 4 个使用点却零条 CSS 规则，机器门禁查不到这种（它查的是
   自定义属性有没有定义，不查 class 有没有对应规则）。两种写法并列，避免下次又分叉。 */
.ct-row label, .ct-row-label { min-width: 104px; color: var(--text-secondary, rgba(255,255,255,.75)); font-size: var(--fs-lg); }


/* 承载 .ct-keymap-target 的行：给 inset 底色，让按钮的 bg-card 浮起来
   （与 .ct-keychange-row 同一手法）。只在需要"可点选择器"提示的行上加。 */
.ct-keyrow-inset { background: var(--bg-inset); border: 1px solid var(--border-light); border-radius: var(--radius-base); padding: var(--space-2) var(--space-3); }.ct-row input[type=range] { flex: 1; accent-color: var(--color-accent, #FF6B35); }
.ct-row .ct-val { min-width: 48px; text-align: right; font-family: var(--font-mono); font-weight: 700; color: var(--text-primary, #fff); }
.ct-sub { color: var(--text-secondary, rgba(255,255,255,.6)); }
.ct-err { color: var(--ct-danger, #ff6b6b); font-size: var(--fs-lg); margin: 8px 0; }
.ct-hint { color: var(--text-tertiary, rgba(255,255,255,.5)); font-size: var(--fs-lg); margin: 8px 0; }
/* ⚠ 选择器**不要再收窄回 .ct-row 内**：宏编辑器的循环时间与步骤时长输入框不在 .ct-row 里，
   曾因此落到浏览器默认白底，在深色弹窗上刺眼到一眼就看出不对（2026-08-28 截图实证）。 */
.ct-row input[type=number],
.ct-macro-bar-cyc input[type=number],
.ct-macro-row-time input[type=number] {
  width: 96px; padding: 7px 9px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong, rgba(255,255,255,.18));
  background: var(--bg-inset, #12161e); color: var(--text-primary, #fff); font-size: var(--fs-lg);
}
.ct-btn-reset {
  padding: 7px 15px; border-radius: var(--radius-sm); cursor: pointer; font-size: var(--fs-base); margin: 10px 0;
  border: 1px solid var(--border-strong, rgba(255,255,255,.18)); background: transparent;
  color: var(--text-secondary, rgba(255,255,255,.7)); transition: background .2s;
}
.ct-btn-reset:hover { background: var(--bg-card-hover, #262f3d); color: var(--text-primary, #fff); }

/* ───── 连击（每键行 = 键名 + 关闭/手动/自动 三选一）───── */
/* 每个「键名 + 三选一」是一个**有边框的分组**（沿用 .ct-macro-step 的子面板语言：
   1px --border-light + 圆角 12）。边框既是分组边界，也替代了原来的行分隔线 ——
   原方案 border-bottom + :last-child 在多列网格下失效：:last-child 只命中整个网格的
   最后一格，去不掉各列末行的线（实测 18 项 5 列时末行 3 格只有 1 格没线）。
   有了边框，justify-content: space-between 反而是对的：边框圈住了这一组，
   键名靠左、控件靠右不会再被误读成「控件属于下一个键名」。 */
.ct-turbo-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 14px;
  border: 1px solid var(--border-light, rgba(255,255,255,.10));
  border-radius: var(--radius-base);
  background: var(--bg-inset, #12161e);
}
.ct-turbo-name { font-weight: 700; color: var(--text-primary, #fff); font-size: var(--fs-lg); flex: none; }
/* 三段连成一体（相邻边框合并），视觉上才是"一个控件的三段"而不是三个按钮 */
/* `.ct-turbo-seg` 已于 2026-09-02 并入下方通用 `.ct-seg-group` —— 两者曾逐条同规则
   （同语义两份实现，编码铁律 4 ①）。合并后 turbo 的圆角从字面量 8px 换成
   var(--radius-sm)，实测两者同值（style.css: --radius-sm: 8px），观感等价。
   ⚠ 通用组多一条 flex:none 的差异：原 .ct-turbo-seg 有、.ct-seg-group 用 inline-flex
   已达到同等"不被拉伸"的效果，故不再单列。 */

/* 通用分段组（与 .ct-turbo-seg 同一套连排规则，供任意「少量互斥选项」的行使用）。
   ⚠ 容器不能叫 .ct-seg —— segBtn 生成的按钮自己就带这个类名，父子同名会被样式互相吃掉
   （keytest.js:130 已踩过）。 */
.ct-segrow { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.ct-seg-group { display: inline-flex; }
.ct-seg-group .ct-seg { border-radius: 0; margin-left: -1px; }
.ct-seg-group .ct-seg:first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); margin-left: 0; }
.ct-seg-group .ct-seg:last-child { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
.ct-seg-group .ct-seg--on { position: relative; z-index: 1; }

/* ═══════════ 死区圆盘三栏（左=死区参数 / 中=圆盘 / 右=实时读数）═══════════
   ⚠ **两侧列宽必须固定** —— 右列的值每帧在变（"—" → "-127, -127"），
   若列宽随内容走（auto / fit-content / 1fr），圆盘会被挤得左右横跳。
   这是本项目反复出现的一类缺陷（用户 2026-09-02：「这个问题你经常犯，
   因为数值变了，把相邻组件挤得移位」），已升全局规范 rules/60-ui-design.md。

   两条缺一不可：
     ① 固定列宽，按**最长可能值**预留 —— 坐标最长 "-127, -127"、半径最长 "180 / 127"；
     ② .ct-dz-val 上的 tabular-nums —— 否则同位数的 1 和 8 宽度都不同，
        逐帧刷新时数字自身就在抖，即使列宽固定也一样难看。 */
.ct-dz {
  display: grid; grid-template-columns: 8.5em minmax(0, 1fr) 8.5em;
  align-items: center; gap: var(--space-3);
}
.ct-dz > svg { width: 100%; height: auto; }
.ct-dz-side { display: grid; gap: var(--space-2); min-width: 0; }
.ct-dz-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2); }
.ct-dz-key { font-size: var(--fs-sm); color: var(--text-tertiary); white-space: nowrap; }
.ct-dz-val {
  font-size: var(--fs-base); color: var(--text-primary); font-weight: 600;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* 扳机竖条：上=行程范围 / 中=竖条 / 下=实时+占比。复用 .ct-dz-row/key/val 三个原语，
   与圆盘共用同一套读数样式（含固定宽度 + tabular-nums 的防挤动措施）。 */
.ct-dzbar { display: grid; gap: var(--space-2); justify-items: stretch; }
.ct-dzbar > svg { width: 100%; height: auto; }
.ct-dzbar-head, .ct-dzbar-foot { display: grid; gap: var(--space-1); }

/* 窄屏回落成上下堆叠：三栏在 <420px 时圆盘会被压得过小 */
@media (max-width: 480px) {
  .ct-dz { grid-template-columns: 1fr; gap: var(--space-2); }
  .ct-dz-side { grid-template-columns: 1fr 1fr; }
}
.ct-slider-ends { display: flex; justify-content: space-between; margin-top: 6px; font-size: var(--fs-sm); color: var(--text-tertiary, rgba(255,255,255,.5)); }

/* ───── 改键（重构：原键 → 目标 → 修改按钮，改动橙描边 + Modal 网格点选）───── */
/* 每个「原键 → 目标键」是一个带边框分组，与 .ct-turbo-row 同一套语言。
   同样**不用** border-bottom + :last-child：多列网格下 :last-child 只命中整个网格的
   最后一格，去不掉各列末行的线（连击那边已实测踩过）。*/
.ct-keychange-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 14px;
  border: 1px solid var(--border-light, rgba(255,255,255,.10));
  border-radius: var(--radius-base);
  background: var(--bg-inset, #12161e);
}
/* 原键与箭头是一体的（「A →」读作一个短语），故用 margin-right:auto 把它俩一起顶到左边，
   剩余空间全给右侧 —— 否则 space-between 会把三个子元素平均推开，箭头孤零零飘在中间。*/
.ct-key-src { min-width: 56px; font-weight: 700; color: var(--text-primary, #fff); font-family: var(--font-mono); }
.ct-key-arrow { color: var(--text-tertiary, rgba(255,255,255,.4)); margin-right: auto; }
/* 目标键即触发器（2026-08-27 起是 <button>，原为 <span> + 旁边一个「修改」按钮）：
   要改的就是它，故需可点的观感 —— hover 描边加强，focus 可见（键盘可达）。*/
.ct-keymap-target {
  /* 原为 flex:1，吃满整行剩余宽度（实测约 420px）—— 里面只放一个键名，白占五分之四。
     改为定宽约 1/5（84px），键名最长 RIGHT/Select 也放得下。*/
  flex: none; min-width: 84px;
  padding: 7px 12px; border-radius: var(--radius-sm); font-size: var(--fs-base); font-family: var(--font-mono);
  border: 1px solid var(--border-light, rgba(255,255,255,.1)); background: var(--bg-card, #1e2530); color: var(--text-secondary, rgba(255,255,255,.8));
  text-align: center; cursor: pointer; transition: border-color .12s, background .12s;
}
.ct-keymap-target:hover { border-color: var(--border-strong, rgba(255,255,255,.28)); }
.ct-keymap-target:focus-visible { outline: 2px solid var(--color-accent, #FF6B35); outline-offset: 2px; }
.ct-keymap-target.ct--changed { border-color: var(--color-accent, #FF6B35); color: var(--text-primary, #fff); font-weight: 700; background: var(--color-accent-tint, rgba(255,107,53,.16)); }
.ct-btn-sm { padding: 6px 14px; font-size: var(--fs-sm); }
.ct-modal--lg { max-width: 440px; width: calc(100% - 40px); }
/* 固定 4 列：选择器按手柄物理分区排布（面键 / 肩键扳机 / 方向键 / 摇杆功能键，
   每区正好 4 个，见 keychange.js 的 PICKER_ROWS）。auto-fill 会按宽度自行断行，
   把分区打散。表外的键在这 4 行之后接着排，仍是 4 列。*/
.ct-key-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin: 6px 0 20px; }
.ct-key-cell {
  padding: 11px 8px; border-radius: var(--radius-base); font-size: var(--fs-sm); font-weight: 700; cursor: pointer; font-family: var(--font-mono);
  border: 1px solid var(--border-light, rgba(255,255,255,.1)); background: var(--bg-inset, #12161e); color: var(--text-secondary, rgba(255,255,255,.8));
  transition: border-color .12s, background .12s;
}
.ct-key-cell:hover { border-color: var(--border-strong, rgba(255,255,255,.28)); }
.ct-key-cell.on { border-color: var(--color-accent, #FF6B35); color: var(--text-primary, #fff); background: var(--color-accent-tint, rgba(255,107,53,.16)); }

/* 下拉：跟随本文件既有控件的圆角/边框/底色语言（同 .ct-keymap-target）*/
.ct-selrow { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 0; }
.ct-select {
  min-width: 128px; padding: 7px 12px; border-radius: var(--radius-sm); font-size: var(--fs-sm); cursor: pointer;
  border: 1px solid var(--border-strong, rgba(255,255,255,.18));
  background: var(--bg-inset, #12161e); color: var(--text-primary, #fff);
}
.ct-select:focus-visible { outline: 2px solid var(--color-accent, #FF6B35); outline-offset: 2px; }

/* 双手柄区间滑竿 = noUiSlider（vendored，见 js/vendor/nouislider.min.js）。
   这里只做**配色与尺寸的令牌化覆盖**，几何/交互一律交给组件 ——
   本控件手搓过两版都栽在「手柄与填充对不齐」，那正是组件已经处理干净的部分。
   ⚠ 本文件必须在 nouislider.min.css **之后**加载（见 controller-config/index.html）。*/
.ct-rangerow .ct-range { margin: 10px 0 26px; }
.ct-range.noUi-target {
  height: 4px; border: 0; border-radius: 999px; box-shadow: none;
  background: var(--bg-inset, #12161e);
  border: 1px solid var(--border-light, rgba(255,255,255,.10));
}
.ct-range .noUi-connect { background: var(--color-accent, #FF6B35); }
.ct-range .noUi-handle {
  width: 15px; height: 15px; right: -7.5px; top: -6.5px;
  border-radius: 50%; border: 2px solid var(--bg-card, #1e2530);
  background: var(--color-accent, #FF6B35); box-shadow: none; cursor: grab;
}
.ct-range .noUi-handle::before, .ct-range .noUi-handle::after { display: none; }  /* 去掉默认的两道竖纹 */
.ct-range .noUi-handle:focus-visible { outline: 2px solid var(--color-accent, #FF6B35); outline-offset: 3px; }
.ct-range .noUi-active { cursor: grabbing; transform: scale(1.15); }
/* 数值标在手柄**下方**（组件默认在上方），与固件商工具一致 */
.ct-range .noUi-tooltip {
  bottom: auto; top: 18px;
  border: 0; background: transparent; padding: 0;
  font-size: var(--fs-sm); font-family: var(--font-mono);
  color: var(--text-secondary, rgba(255,255,255,.75));
}

/* ───── 宏 ───── */
.ct-macro-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 11px 0;
  border-bottom: 1px solid var(--border-light, rgba(255,255,255,.08));
}
.ct-macro-name { font-size: var(--fs-lg); }
.ct-macro-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.ct-macro-title { font-weight: 700; font-size: var(--fs-lg); }
.ct-seg {
  padding: 7px 15px; border-radius: var(--radius-sm); cursor: pointer; font-size: var(--fs-base);
  border: 1px solid var(--border-strong, rgba(255,255,255,.18)); background: transparent;
  color: var(--text-secondary, rgba(255,255,255,.7));
}
.ct-seg--on { background: var(--bg-card-hover, #262f3d); color: var(--text-primary, #fff); border-color: var(--color-accent, #FF6B35); font-weight: 800; }
/* 紧凑变体：三段并排要塞进网格单元，用原尺寸会撑破列宽 */
/* 一排两个后分组变宽，段内文字随之升一档（字号刻度 12→13→14→15，见本文件既有取值）*/
.ct-seg--sm { padding: 6px 12px; font-size: var(--fs-sm); }
.ct-seg:disabled { opacity: .38; cursor: not-allowed; }
.ct-macro-step {
  border: 1px solid var(--border-light, rgba(255,255,255,.10));
  border-radius: var(--radius-base); padding: 12px; margin: 10px 0;
}
/* 新增步骤后的一次性提示（1.2s 后由 JS 摘掉 .is-new）。
   只是"看这里"，不是状态 —— 故用动画淡出而非常驻描边，免得被读成"这一步被选中了"。
   :focus 不画轮廓：卡片是 tabindex=-1 的**编程焦点落点**，浏览器默认的蓝色焦点环
   会让它看起来像个可点控件，也会盖掉这里的橙色提示。 */
.ct-macro-step.is-new { animation: ctStepIn 1.2s ease-out; }
.ct-macro-step:focus, .ct-macro-step:focus-visible { outline: none; }
@keyframes ctStepIn {
  0%   { border-color: var(--color-accent, #FF6B35); background: var(--color-accent-tint, rgba(255,107,53,.16)); }
  100% { border-color: var(--border-light, rgba(255,255,255,.10)); background: transparent; }
}
@media (prefers-reduced-motion: reduce) { .ct-macro-step.is-new { animation: none; } }
.ct-macro-step-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.ct-macro-step-ctrls { display: flex; gap: 4px; }
.ct-icon-btn {
  padding: 5px 9px; border-radius: var(--radius-sm); cursor: pointer; font-size: var(--fs-base);
  border: 1px solid var(--border-strong, rgba(255,255,255,.18)); background: transparent;
  color: var(--text-secondary, rgba(255,255,255,.7));
}
.ct-icon-btn:hover:not(:disabled) { background: var(--bg-card-hover, #262f3d); color: var(--text-primary, #fff); }
/* 禁用态此前**完全没有样式**（2026-08-29 补）：`clearBtn.disabled = !n` 早就在设，
   但按钮看起来与可用的一模一样，点下去毫无反应 —— 用户只会以为界面卡了。
   一个不能用却看不出不能用的控件，比没有这个控件更糟。 */
.ct-icon-btn:disabled { opacity: .35; cursor: not-allowed; }
.ct-macro-keys { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
.ct-chip {
  padding: 5px 11px; border-radius: 999px; cursor: pointer; font-size: var(--fs-base);
  border: 1px solid var(--border-strong, rgba(255,255,255,.18)); background: transparent;
  color: var(--text-secondary, rgba(255,255,255,.7));
}
.ct-chip--on { background: var(--bg-card-hover, #262f3d); color: var(--text-primary, #fff); border-color: var(--color-accent, #FF6B35); font-weight: 800; }

/* ═══════════ 测试：胶囊分栏（保留）═══════════ */
.ct-pills { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
.ct-pill { font-size: var(--fs-lg); padding: 8px 16px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--border-light, rgba(255,255,255,.10)); background: var(--bg-card, #1e2530); color: var(--text-secondary, rgba(255,255,255,.7)); }
.ct-pill:hover { color: var(--text-primary, #fff); }
.ct-pill.on { background: var(--bg-card-hover, #262f3d); color: var(--text-primary, #fff); border-color: var(--color-accent, #FF6B35); font-weight: 800; }

/* ═══════════ 测试：手柄即界面实时可视化（spec §5，保留）═══════════ */
.ct-test-stage { display: grid; grid-template-columns: minmax(0,1.35fr) minmax(300px,.85fr); gap: 20px; align-items: stretch; }
@media (max-width: 860px){ .ct-test-stage { grid-template-columns: 1fr; } }

.ct-pad-wrap { position: relative; padding: 20px 22px; display: flex; flex-direction: column;
  background: var(--ct-pad-bg); border: 1px solid var(--border-light, rgba(255,255,255,.10)); border-radius: var(--radius-lg); overflow: hidden; min-height: 460px; }
.ct-pad-hint { position: relative; z-index: 2; font-size: var(--fs-base); font-weight: 600; color: var(--text-primary, #fff); letter-spacing: .3px; margin-bottom: 10px; }
.ct-pad-base { position: relative; z-index: 1; width: 100%; max-width: 780px; margin: auto; }
.ct-pad-base svg { width: 100%; display: block; }
.ct-pad-base svg path { fill: var(--ct-pad-line); transition: fill .12s, stroke .12s; }
/* 高亮粗细必须与方向键一致（用户 2026-08-29）。两者结构不同，同一个 stroke-width 数值
   给不出同样的视觉宽度：
     · 方向键 .dpkey 是**描边矩形** —— 橙色带宽 = stroke-width = 3.5px
     · 圆键/胶囊键 .kp 是**填充轮廓**（线稿线宽实测 1.7px @748px 渲染宽）——
       原先再加 3.5 描边，会在轮廓的**内外两条边**各扩 1.75，合并后带宽 1.7+3.5 = 5.2px，
       比方向键粗 49%；且中间夹着灰色填充，看起来发虚、发糊。
   改为「把轮廓本身染成强调色 + 2px 描边」：带宽 ≈ 1.7+2 = 3.7px，与方向键 3.5px 对齐。
   （线稿线宽随渲染尺寸缩放而描边不缩放，故只能在常用尺寸区间对齐，取 2 是折中。）
   实测方法：环形 path 的 getTotalLength() = 2π(R+r)，配合 bbox 外径反解内径求线宽。 */
.ct-pad-base svg .kp.on { fill: var(--color-accent, #FF6B35)!important; stroke: var(--color-accent, #FF6B35)!important; stroke-width: 2; vector-effect: non-scaling-stroke; }
.ct-pad-base svg .dpkey { fill: var(--ct-pad-dp); stroke: var(--ct-pad-line); stroke-width: 2; vector-effect: non-scaling-stroke; transition: fill .12s, stroke .12s; }
.ct-pad-base svg .dpkey.on { stroke: var(--color-accent, #FF6B35)!important; stroke-width: 3.5; }
.ct-pad-base .ct-err { color: #c0410f; }
.ct-live-nub { position: absolute; width: 15px; height: 15px; border-radius: 50%; transform: translate(-50%,-50%);
  background: rgba(255,107,53,.35); box-shadow: 0 0 0 2px rgba(255,107,53,.18); transition: .05s linear; pointer-events: none; z-index: 3; }
.ct-live-nub.on { background: var(--color-accent, #FF6B35); box-shadow: 0 0 0 3px rgba(255,107,53,.28), 0 0 10px var(--color-accent, #FF6B35); }

/* ── 手柄图可点选态（编辑宏用；预览/录制不加此类，保持纯展示）── */
.ct-macro-preview-pad.is-pickable .kp,
.ct-macro-preview-pad.is-pickable .dpkey,
.ct-macro-preview-pad.is-pickable .ct-live-nub { cursor: pointer; }
/* hover 用**加粗描边**而不是换色：换色会和"已选中=橙描边"撞语义，
   让人分不清"鼠标在上面"和"已经选上了"。 */
.ct-macro-preview-pad.is-pickable .kp:not(.on):hover,
.ct-macro-preview-pad.is-pickable .dpkey:not(.on):hover {
  stroke: var(--text-secondary, rgba(233,240,249,.9)); stroke-width: 2.5; vector-effect: non-scaling-stroke;
}
.ct-macro-preview-pad.is-pickable .ct-live-nub:hover { box-shadow: 0 0 0 3px rgba(233,240,249,.28); }
.ct-pad-status { position: relative; z-index: 2; margin-top: 12px; font-size: var(--fs-base); color: var(--text-secondary, rgba(255,255,255,.6)); font-family: var(--font-mono); }
.ct-pad-status--live { color: var(--text-primary, #fff); font-weight: 600; }

.ct-test-readout { padding: 20px 22px; display: flex; flex-direction: column; gap: 16px;   /* 与其余卡片同一节奏 */
  background: var(--bg-card, #1e2530); border: 1px solid var(--border-light, rgba(255,255,255,.10)); border-radius: var(--radius-lg); }
.ct-test-readout h3 { margin: 0 0 4px; font-size: var(--fs-xl); color: var(--text-primary, #fff); }
.ct-ro-sticks { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.ct-stick-card { text-align: center; }
.ct-stick-disc { position: relative; width: 108px; height: 108px; margin: 0 auto 8px; border-radius: 50%;
  background: radial-gradient(circle at 50% 42%, var(--bg-card-hover, #262f3d), var(--bg-page, #161b24));
  border: 1px solid var(--border-light, rgba(255,255,255,.10)); box-shadow: inset 0 2px 8px rgba(0,0,0,.4); }
.ct-stick-grid { position: absolute; inset: 9px; border-radius: 50%;
  background-image: linear-gradient(rgba(255,255,255,.07) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.07) 1px,transparent 1px);
  background-size: 18px 18px; -webkit-mask-image: radial-gradient(circle,#000 66%,transparent); mask-image: radial-gradient(circle,#000 66%,transparent); }
.ct-stick-cx::before,.ct-stick-cx::after { content: ""; position: absolute; background: rgba(255,255,255,.1); }
.ct-stick-cx::before { left: 50%; top: 12%; bottom: 12%; width: 1px; transform: translateX(-.5px); }
.ct-stick-cx::after { top: 50%; left: 12%; right: 12%; height: 1px; transform: translateY(-.5px); }
.ct-stick-nub { position: absolute; left: 50%; top: 50%; width: 14px; height: 14px; border-radius: 50%;
  background: var(--color-accent, #FF6B35); box-shadow: 0 0 0 3px var(--color-accent-dim, rgba(255,107,53,.12)); transform: translate(-50%,-50%); transition: .05s linear; z-index: 2; }
.ct-stick-meta { display: flex; flex-direction: column; gap: 2px; font-size: var(--fs-base); font-weight: 600; color: var(--text-primary, #fff); }
/* 左右扳机并排于同一张卡（2026-08-29，原为 flex-direction:column 的上下两张卡）。
   两条读数是同一次动作的左右对照，横排才好比 —— 竖排要来回扫视，还多占一倍高度。 */
.ct-ro-trigger { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
.ct-ro-trigger .ct-trig2 { padding: 4px 0; }
.ct-ro-section { font-size: var(--fs-lg); font-weight: 700; color: var(--text-primary, #fff); letter-spacing: .5px; }
.ct-ro-keys { display: flex; gap: 6px; flex-wrap: wrap; min-height: 30px; }
.ct-ro-key { font-size: var(--fs-lg); font-weight: 700; padding: 5px 12px; border-radius: var(--radius-sm); font-family: var(--font-mono);
  background: var(--bg-card-hover, #262f3d); color: var(--text-primary, #fff); border: 1px solid var(--border-strong, rgba(255,255,255,.18)); }
.ct-ro-none { font-size: var(--fs-base); color: var(--text-secondary, rgba(255,255,255,.6)); }
.ct-ro-raw summary { font-size: var(--fs-base); font-weight: 600; color: var(--text-primary, #fff); cursor: pointer; }
.ct-ro-raw pre { background: #0b0e14; color: #6cf06c; padding: 10px; border-radius: var(--radius-sm); font-size: var(--fs-base); white-space: pre-wrap; max-height: 140px; overflow: auto; margin: 8px 0 0; }
.ct-mono { font-family: var(--font-mono); font-weight: 700; font-size: var(--fs-lg); color: var(--text-primary, #fff); }

/* ═════════ 测试：gobing 式多模式测试套件（6 模式，保留）═════════ */
/* 设计铁律：accent(橙) 仅作图形（描边/填充/CTA 底/nub/进度条）；选中/点亮=加粗+主色文字+中性底，绝不用橙色文字 */
.ct-test2 { display: flex; flex-direction: column; }
.ct-gpbar {
  font-size: var(--fs-lg); font-family: var(--font-mono); font-weight: 600;
  color: var(--text-secondary, rgba(255,255,255,.6));
  background: var(--bg-card, #1e2530); border: 1px solid var(--border-light, rgba(255,255,255,.10));
  border-radius: var(--radius-base); padding: 10px 16px; margin-bottom: 16px;
}
.ct-gpbar--live { color: var(--text-primary, #fff); }
.ct-mode-body { min-height: 400px; }

/* ── 模式 1：按键测试 —— 功能分组徽章网格 + 覆盖率 ── */
.ct-badge-cover { font-size: var(--fs-sm); color: var(--text-tertiary, rgba(255,255,255,.55)); margin-bottom: 12px; }
.ct-badge-groups { display: flex; flex-direction: column; gap: 12px; margin: 4px 0 16px; }
.ct-badge-group-title { display: block; font-size: var(--fs-sm); font-weight: 700; color: var(--text-tertiary, rgba(255,255,255,.55)); margin-bottom: 6px; letter-spacing: .3px; }
.ct-badge.tested { border-color: var(--ct-ok, #35c759); }   /* 已测：绿边（中性标记，非橙）*/
.ct-badge-grid { display: grid; grid-template-columns: repeat(auto-fill,minmax(58px,1fr)); gap: 8px; margin: 0; }
.ct-badge {
  display: flex; align-items: center; justify-content: center; padding: 9px 6px;
  font-size: var(--fs-lg); font-family: var(--font-mono); text-align: center;
  border: 1px solid var(--border-strong, rgba(255,255,255,.18)); border-radius: var(--radius-base);
  background: transparent; color: var(--text-secondary, rgba(233,240,249,.90));
  transition: background .1s, color .1s, border-color .1s;
}
.ct-badge.on {
  /* 选中态只加粗+主色+橙边（字号保持 15px 不变，等宽字体加粗不改字宽）→ 尺寸恒定，按键时不挤压布局 */
  background: var(--bg-card-hover, #262f3d); color: var(--text-primary, #fff);
  border-color: var(--color-accent, #FF6B35); font-weight: 700;
}

/* ── 模式 2：摇杆测试 —— 大圆坐标图 ── */
.ct-stick2-wrap { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
@media (max-width: 720px){ .ct-stick2-wrap { grid-template-columns: 1fr; } }
.ct-stick2 { display: flex; flex-direction: column; align-items: center; padding: 20px; }
.ct-stick2-head { font-size: var(--fs-lg); font-weight: 700; color: var(--text-primary, #fff); margin-bottom: 14px; }
.ct-stick2-plot {
  position: relative; width: 100%; max-width: 280px; aspect-ratio: 1/1; margin: 0 auto; border-radius: 50%;
  background: radial-gradient(circle at 50% 42%, var(--bg-card-hover, #262f3d), var(--bg-page, #161b24));
  border: 1px solid var(--border-light, rgba(255,255,255,.12)); box-shadow: inset 0 2px 12px rgba(0,0,0,.30);
}
.ct-stick2-trail { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: 50%; pointer-events: none; z-index: 1; }
.ct-stick2-ring { position: absolute; left: 50%; top: 50%; width: 24%; height: 24%; transform: translate(-50%,-50%);
  border: 1px dashed var(--text-tertiary, rgba(255,255,255,.4)); border-radius: 50%; z-index: 2; }
.ct-stick2-cross::before, .ct-stick2-cross::after { content: ""; position: absolute; background: var(--border-light, rgba(255,255,255,.14)); z-index: 2; }
.ct-stick2-cross::before { left: 50%; top: 8%; bottom: 8%; width: 1px; transform: translateX(-.5px); }
.ct-stick2-cross::after { top: 50%; left: 8%; right: 8%; height: 1px; transform: translateY(-.5px); }
.ct-stick2-nub {
  position: absolute; left: 50%; top: 50%; width: 18px; height: 18px; border-radius: 50%;
  transform: translate(-50%,-50%); z-index: 3; transition: .04s linear;
  background: var(--color-accent, #FF6B35); box-shadow: 0 0 0 3px var(--color-accent-tint, rgba(255,107,53,.16));
}
.ct-stick2-nub.on { box-shadow: 0 0 0 4px var(--color-accent-tint, rgba(255,107,53,.24)), 0 0 12px var(--color-accent, #FF6B35); }
.ct-stick2-vals { display: flex; gap: 18px; margin-top: 16px; }

/* ── 模式 3：扳机测试 —— 大模拟量竖条 ── */
.ct-trig2-wrap { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; max-width: 520px; margin: 0 auto; }
.ct-trig2 { display: flex; flex-direction: column; align-items: center; padding: 22px 18px; }
.ct-trig2-head { font-size: var(--fs-lg); font-weight: 700; color: var(--text-primary, #fff); margin-bottom: 16px; }
.ct-trig2-bar {
  position: relative; width: 56px; height: 220px; border-radius: var(--radius-lg); overflow: hidden;
  background: var(--bg-inset, #12161e); border: 1px solid var(--border-light, rgba(255,255,255,.12));
  display: flex; align-items: flex-end;
}
.ct-trig2-fill { width: 100%; height: 0; border-radius: 0 0 13px 13px;
  background: linear-gradient(0deg,var(--color-accent, #FF6B35),var(--color-accent-hover, #ff8255)); transition: height .05s linear; }
/* 扳机百分比：**宽度必须恒定**，否则 0% → 100% 时字符数从 2 变 4，
   整块读数会随扣扳机左右跳动（用户 2026-09-01 提出）。
   · min-width: 4ch = 三位数字 + 一个 % —— `ch` 是字符「0」的宽度，字号改了也不用重算；
   · text-align: right 让个位对齐，否则只是不跳、但 0% 与 100% 的个位仍错位；
   · tabular-nums 是保险：等宽字体本就数字等宽，但一旦有人换掉 --font-mono 仍不塌。 */
.ct-trig2-pct { font-size: var(--fs-2xl); font-weight: 800; color: var(--text-primary, #fff); margin-top: 16px; font-family: var(--font-mono);
  min-width: 4ch; text-align: right; font-variant-numeric: tabular-nums; }
.ct-trig2-raw { font-size: var(--fs-base); color: var(--text-secondary, rgba(255,255,255,.9)); margin-top: 4px; }
.ct-trig2-raw b { font-family: var(--font-mono); font-weight: 700; color: var(--text-primary, #fff); }

/* ── 模式 4：方向键串键 ── */
.ct-dpad2-wrap { display: flex; align-items: center; gap: 32px; flex-wrap: wrap; justify-content: center; padding: 28px; }
.ct-dpad2-cross { display: grid; grid-template-columns: repeat(3,64px); grid-template-rows: repeat(3,64px); gap: 6px; }
.ct-dpad2-cell {
  display: flex; align-items: center; justify-content: center; font-size: var(--fs-2xl); font-weight: 700;
  color: var(--text-secondary, rgba(255,255,255,.9)); border-radius: var(--radius-base);
  background: var(--bg-card-hover, #262f3d); border: 1px solid var(--border-light, rgba(255,255,255,.14));
  transition: background .08s, border-color .08s, color .08s;
}
.ct-dpad2-up { grid-column: 2; grid-row: 1; }
.ct-dpad2-left { grid-column: 1; grid-row: 2; }
.ct-dpad2-right { grid-column: 3; grid-row: 2; }
.ct-dpad2-down { grid-column: 2; grid-row: 3; }
.ct-dpad2-cell.on {
  border-color: var(--color-accent, #FF6B35); background: var(--color-accent-tint, rgba(255,107,53,.16));
  color: var(--text-primary, #fff); font-weight: 800;
}
.ct-dpad2-info { max-width: 300px; }
.ct-dpad2-count { font-size: var(--fs-xl); font-weight: 700; color: var(--text-primary, #fff); }
.ct-dpad2-count b { font-size: var(--fs-2xl); font-family: var(--font-mono); }
.ct-dpad2-sub { font-size: var(--fs-base); line-height: var(--lh-base); color: var(--text-secondary, rgba(255,255,255,.9)); margin-top: 8px; }

/* ═══════════ 页脚（恒定深色工业底）═══════════ */
/* 原 .ct-footer 单行页脚已删（2026-08-28）：页脚改用官网 .ck-footer 四栏，见上方令牌钉暗说明。*/

/* ═══════════ 响应式 ═══════════ */
@media (max-width: 820px) {
  .ct-settings-body { flex-direction: column; }
  .ct-nav {
    width: 100%; flex-direction: row; overflow-x: auto; position: static; gap: 4px;
    -webkit-overflow-scrolling: touch;
  }
  .ct-nav-item { white-space: nowrap; }
  .ct-nav-item.on::before { display: none; }
  .ct-devcard-batt { margin-left: 0; }
}
@media (max-width: 560px) {
  .ct-crumbbar-inner { padding: 6px 16px; }
  .ct-wrap { padding: 20px 16px 40px; }
  .ct-hero { padding: 40px 22px; }
  .ct-tabs { display: flex; }
  .ct-tabs button { flex: 1; padding: 9px 12px; }
  .ct-devcard-act { width: 100%; }
  .ct-devcard-act .ct-btn { width: 100%; }
}

/* ═══════════ 设置页卡片体系（重构：一页多卡 + 分区 + 可视化控件；橙仅图形，文字走 --text-*）═══════════ */
.ct-settings-page { display: flex; flex-direction: column; gap: 16px; }

/* 页头拆 main / actions（右侧放恢复默认等） */
.ct-page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.ct-page-head-main { min-width: 0; }
.ct-page-head-actions { flex-shrink: 0; display: flex; align-items: center; gap: 10px; }

/* 设置卡三段式（.ct-scard 已有 padding:20/22，head/foot 用 margin 不再加 padding） */
.ct-scard-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
.ct-scard-title { font-size: var(--fs-lg); font-weight: 700; color: var(--text-primary, #fff); margin: 0; }
.ct-scard-status { font-size: var(--fs-base); font-weight: 700; font-family: var(--font-mono); color: var(--text-secondary, rgba(255,255,255,.8));  border: 1px solid var(--border-light, rgba(255,255,255,.1)); border-radius: 999px; padding: 3px 11px; white-space: nowrap; }
.ct-scard-status[hidden] { display: none; }
.ct-scard-foot { display: flex; justify-content: flex-end; gap: 10px; margin-top: 16px; padding-top: 15px; border-top: 1px solid var(--border-light, rgba(255,255,255,.08)); }
.ct-scard-foot:empty { display: none; }
.ct-scard-section { padding: 15px 0; border-top: 1px solid var(--border-light, rgba(255,255,255,.08)); }
.ct-scard-section:first-child { border-top: 0; padding-top: 0; }
.ct-section-title { font-size: var(--fs-sm); font-weight: 700; color: var(--text-tertiary, rgba(255,255,255,.55)); letter-spacing: .3px; margin: 0 0 10px; }
.ct-card-hint { font-size: var(--fs-sm); line-height: var(--lh-base); color: var(--text-tertiary, rgba(255,255,255,.55)); margin: 0 0 14px; }

/* 滑块行（顶行 label+val / 轨道 = 深底 + 橙填充 + 透明 range 叠加保 a11y） */
.ct-slrow { margin: 15px 0; }
.ct-slrow:first-child { margin-top: 2px; }
.ct-slrow:last-child { margin-bottom: 2px; }
.ct-slrow-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 9px; }
.ct-slrow-head label { color: var(--text-secondary, rgba(255,255,255,.75)); font-size: var(--fs-base); min-width: 0; }
.ct-slrow-head .ct-val { font-family: var(--font-mono); font-weight: 700; color: var(--text-primary, #fff); font-size: var(--fs-base); white-space: nowrap; }
.ct-slider { position: relative; height: 22px; display: flex; align-items: center; }
.ct-slider::before { content: ""; position: absolute; left: 0; right: 0; height: 6px; border-radius: 3px; background: var(--bg-inset, #12161e); box-shadow: inset 0 0 0 1px var(--border-light, rgba(255,255,255,.08)); }
.ct-slider-fill { position: absolute; left: 0; height: 6px; border-radius: 3px; background: var(--color-accent, #FF6B35); pointer-events: none; }
.ct-slider-input { position: relative; width: 100%; margin: 0; background: transparent; -webkit-appearance: none; appearance: none; height: 22px; cursor: pointer; }
.ct-slider-input::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 18px; height: 18px; border-radius: 50%; background: #fff; border: 2px solid var(--color-accent, #FF6B35); box-shadow: 0 1px 3px rgba(0,0,0,.3); cursor: pointer; }
.ct-slider-input::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: #fff; border: 2px solid var(--color-accent, #FF6B35); box-shadow: 0 1px 3px rgba(0,0,0,.3); cursor: pointer; }
.ct-slider-input:focus-visible { outline: none; }
.ct-slider-input:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px var(--color-accent-tint, rgba(255,107,53,.24)); }

/* 控件行整体禁用 —— 用于「设备物理支持、但当前固件版本用不了」的能力
   （灯光 Data6 需扩展协议 V02、扳机振动 Data5 同理）。
   ⚠ 真正的禁用靠 input/select 自身的 disabled 属性；本类只负责让它**看起来也是禁用的**。
     必须有：原生 :disabled 不改 range 的外观，橙色填充与白圆点照常鲜亮，
     一个点不动却看着完全可操作的滑块，比直接不显示更让人困惑。
   `[事故形态]` 本类此前被 settings/motor.js 引用，而全仓**零条 CSS 规则** ——
     与 2026-09-02 审查抓到的 .ct-row-label 同型：用了但没定义，不报错、只是没效果。
     补这条规则同时把那处一并救活。 */
.ct-slrow.is-disabled, .ct-row.is-disabled { opacity: .45; }
.ct-slrow.is-disabled .ct-slider-input,
.ct-row.is-disabled .ct-select { cursor: not-allowed; }

/* 开关行 + 自绘开关（role=switch，键盘/读屏可达） */
.ct-swrow { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 15px 0; }
.ct-swrow label { color: var(--text-secondary, rgba(255,255,255,.75)); font-size: var(--fs-base); }
.ct-switch { position: relative; width: 44px; height: 26px; border-radius: 999px; border: none; background: var(--bg-inset, #12161e); box-shadow: inset 0 0 0 1px var(--border-strong, rgba(255,255,255,.18)); cursor: pointer; padding: 0; transition: background .18s; flex-shrink: 0; }
.ct-switch--on { background: var(--color-accent, #FF6B35); box-shadow: none; }
.ct-switch-thumb { position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.35); transition: transform .18s; }
.ct-switch--on .ct-switch-thumb { transform: translateX(18px); }
.ct-switch:focus-visible { outline: 2px solid var(--color-accent, #FF6B35); outline-offset: 2px; }

/* 灯光颜色行（色块纯图形用色，不涉及橙文字约束） */
.ct-color-row { display: flex; align-items: center; gap: 12px; margin: 2px 0 15px; }
.ct-color-lab { color: var(--text-secondary, rgba(255,255,255,.75)); font-size: var(--fs-base); margin-right: auto; }
.ct-color-swatch { width: 26px; height: 26px; border-radius: var(--radius-sm); border: 1px solid var(--border-strong, rgba(255,255,255,.18)); flex-shrink: 0; }
.ct-color-text { font-family: var(--font-mono); font-size: var(--fs-sm); font-weight: 700; color: var(--text-secondary, rgba(255,255,255,.8)); }
.ct-color-input { width: 42px; height: 30px; border: 1px solid var(--border-light, rgba(255,255,255,.1)); border-radius: var(--radius-sm); background: var(--bg-inset); cursor: pointer; padding: 2px; }

/* 死区语义说明（滑块调"大小"、图上标"位置"，两者不是同一个数，需要一句桥接）*/
.ct-dz-note {
  margin: 0; font-size: var(--fs-sm); line-height: var(--lh-base); color: var(--text-tertiary);
}
/* 帮助卡（整页最末，对齐官方软件布局）：标题用主色，与上方几张"要动手改的"卡区分开 ——
   它不是设置项，是读物；同样的边框同样的底色会让人以为里面还有可调的东西。 */
.ct-help-card { margin-top: 16px; }
.ct-help-card .ct-scard-title { color: var(--color-accent, #FF6B35); font-size: var(--fs-base); }
/* 死区说明改为「一条一个参数」：玩家扫一眼就知道该动哪个滑块，
   而不是从一整段里找。名称加粗做锚点，"调过头"另起颜色以示代价。 */
.ct-dz-item { margin-bottom: 4px; color: var(--text-secondary); }
.ct-dz-item b { color: var(--text-primary); font-weight: 600; margin-right: 2px; }
.ct-dz-over { color: var(--text-tertiary); margin-left: 4px; }
/* 参数范围：设置时要用，但不该挡在解释前面，故降为末行 */
.ct-dz-spec { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--border-light); }
/* 死区超规范时的纠正提示（读到历史非法值 → 夹紧并写回，必须让用户看见改了什么） */
.ct-dz-fixed {
  margin: 0 0 10px; padding: 8px 10px; border-radius: var(--radius-sm);
  background: var(--color-accent-tint); border: 1px solid var(--border-light);
  font-size: var(--fs-sm); line-height: var(--lh-base); color: var(--text-secondary);
}
/* 摇杆/扳机可视化容器（圆盘/竖条/曲线 SVG 居中，touch-action:none 防触屏拖拽时页面滚动） */
.ct-viz-wrap { display: flex; justify-content: center; padding: 6px 0 14px; }
.ct-viz-wrap svg { max-width: 190px; width: 100%; height: auto; touch-action: none; }

/* 左右轴卡横向并排（摇杆/扳机 左右一屏对比）；窄屏回落单列 */
.ct-axis-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
@media (max-width: 720px) { .ct-axis-pair { grid-template-columns: 1fr; } }

/* 马达 4 路滑块 2×2 网格（两排）；窄屏回落单列 */
.ct-motor-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 20px; align-items: start; }
.ct-motor-grid > .ct-slrow, .ct-turbo-grid > .ct-turbo-row, .ct-keychange-grid > .ct-keychange-row { margin: 0; } /* grid 内消除 :first-child margin 差致同排不对齐 */
@media (max-width: 480px) { .ct-motor-grid { grid-template-columns: 1fr; } }

/* 连击按键分两排（第一排 ABXY，4 列网格）；窄屏回落 2 列 */
/* 三选一控件（约 139px）比原「开关 + ⚡自动」宽，原来的固定 4 列会把键名挤没。
   改用 auto-fill 自适应：窄屏自然落到 1 列，宽屏按容纳量铺 —— 不需要断点维护。
   最小列宽 260px = 三段控件 139 + 键名与间距的余量（实测 466px 卡片下键名仍余 47px）。*/
/* 列数固定而非 auto-fill：设备上报的连击键**恒为 8 个**（ABXY + LB/RB/LT/RT，
   实测见 doc/controller-tool-keylinker-dll-usage-v1.7.md §C.5）。auto-fill 在宽屏会铺 5 列，
   8 个键排成 5+3、第二行空一截；固定 4 列则是齐整的 4+4。
   列间距 40px 远大于组内的 12px —— 分组靠这个间距差表达（原用分隔线，多列下 :last-child 失效，已移除）。*/
/* 间距取 16px —— 本文件的**卡片级**标准（同 .ct-settings-page），不是随手定的：
   分组自身内边距是 10/14px，组间距若小于它（原写 10px），box 之间比 box 内部还挤，
   分组反而读不出来。间距刻度见本文件既有取值 4/8/12/16/20，12 为子元素级、16 为卡片级；
   这些带边框带底色的分组属卡片级。 */
/* 一排两个：8 个键（键码 1-8 = A B X Y LB RB LT RT，见 keymap.js）按设备上报顺序填格，
   自然排成 A|B / X|Y / LB|RB / LT|RT 四行 —— 左右手同名键成对，符合手柄的物理直觉。
   ⚠ 这个配对依赖设备 turboList 的顺序，**不要靠排序 buttons 去"保证"它** ——
   settings/turbo.js 的 commit() 按位置写槽，重排会让写入静默错位（那里有详细说明）。*/
.ct-turbo-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
@media (max-width: 560px) { .ct-turbo-grid { grid-template-columns: 1fr; } }

/* 改键一行两个（按功能配对分两列）；窄屏回落单列 */
/* 间距 16px：与连击同为卡片级分组（本文件刻度 4/8/12/16/20，16 为卡片级）*/
.ct-keychange-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: center; }
@media (max-width: 560px) { .ct-keychange-grid { grid-template-columns: 1fr; } }

/* 校准/灵敏度曲线加边框 + 底色（区别死区可视化，独立成图看得懂坐标系）；放大一倍、直接拖 2 控制点调（已去 X1/Y1/X2/Y2 滑块）*/
.ct-curve-wrap svg { max-width: 400px; border: 1px solid var(--border-light, rgba(255,255,255,.14)); border-radius: var(--radius-sm); background: var(--bg-inset, #12161e); padding: 6px; }

/* 窄屏页头堆叠 */
@media (max-width: 560px) {
  .ct-page-head { flex-direction: column; align-items: stretch; }
  .ct-page-head-actions { justify-content: flex-end; }
}

/* ═══════════ 宏（重构：列表卡 + 编辑/预览 Modal + 时间轴 + 播放；橙仅图形）═══════════ */
.ct-macro-list { display: flex; flex-direction: column; }
.ct-macro-list .ct-macro-row:last-child { border-bottom: 0; }
.ct-macro-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--border-light, rgba(255,255,255,.08)); }
.ct-macro-meta { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ct-macro-head { display: flex; flex-direction: row; align-items: center; gap: 8px; }
.ct-macro-name { font-weight: 700; color: var(--text-primary, #fff); font-size: var(--fs-lg); }
.ct-mbadge { font-size: var(--fs-sm); font-weight: 700; padding: 2px 9px; border-radius: 999px; background: var(--bg-inset, #12161e); border: 1px solid var(--border-light, rgba(255,255,255,.1)); color: var(--text-tertiary, rgba(255,255,255,.55)); }
.ct-mbadge--set { border-color: var(--ct-ok, #35c759); color: var(--text-secondary, rgba(255,255,255,.8)); }
.ct-macro-sub { font-size: var(--fs-sm); color: var(--text-tertiary, rgba(255,255,255,.55)); }
.ct-macro-acts { display: flex; gap: 6px; flex-shrink: 0; }   /* 4 个按钮后 4px 太挤，取刻度上的 6 */
/* 行内四个图标（预览/编辑/录制/删除）尺寸归一：IC_PLAY 的 svg 自带 width/height=22
   —— 那是给播放条用的尺寸，直接复用会比旁边 17px 的图标大一圈。
   CSS 优先级高于 SVG 的表现属性，所以在这里统一比改常量更省事，也不影响播放条那边。 */
.ct-macro-acts svg { width: 17px; height: 17px; }
.ct-icon-btn--danger:hover { color: var(--ct-danger, #ff5a4d); border-color: var(--ct-danger, #ff5a4d); }

/* 宏编辑 Modal */
/* 宏编辑器是**两栏**布局（左手柄图 / 右步骤列表），640px 下每栏仅 ~280px：
   手柄图被压成一小块，右栏每页只放得下 1 行 —— 7 步要翻 7 页（2026-08-28 截图实证）。
   放宽到 1060px 后两栏各 ~500px，手柄图可读、列表一页能放下十行以上。
   仍保留 width: calc(100% - 40px)，窄屏自动降级为单栏（见 .ct-macro-split 的媒体查询）。 */
/* ⚠ **只设 max-height，不设 height**：固定 height 会让内容不足时窗口被撑开，
   录制态实测下方空出约 600px 死区（2026-08-28 截图实证）。窗口按内容自适应，
   上限 92vh 兜住长内容（编辑态 42 步）。
   「窗口做高」真正该动的是**手柄图**：它是 SVG，高度由列宽决定 —— 故加宽弹窗
   （1060 → 1180）并给左栏更大份额（1.05 → 1.15），图从 ~493px 长到 ~580px，
   右侧列表的可用高度随之增加。 */
.ct-macro-modal { max-width: 1180px; width: calc(100% - 40px); display: flex; flex-direction: column; max-height: 92vh; overflow: hidden; }
.ct-macro-editor-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; }
.ct-macro-tabs { display: inline-flex; gap: 6px; }
.ct-mstep-badge { font-size: var(--fs-sm); font-weight: 700; padding: 3px 10px; border-radius: 999px; background: var(--bg-inset, #12161e); border: 1px solid var(--border-light, rgba(255,255,255,.1)); color: var(--text-secondary, rgba(255,255,255,.8)); }
/* ⚠ **不要再给 body 设 max-height**（原为 60vh）：modal 已是 flex 列 + max-height，
   body 是 flex:1 —— 再叠一个 60vh 会把窗口钉死在屏幕六成高，
   录制态左手柄图因此被压矮、右列表跟着变短（用户 2026-08-28「窗口适当做高一些」）。
   现在高度由 .ct-macro-modal 的 max-height 决定，body 只负责在超出时滚动。 */
.ct-macro-editor-body { flex: 1; min-height: 0; overflow-y: auto; padding-right: 4px; }
.ct-macro-field-label { font-size: var(--fs-sm); color: var(--text-tertiary, rgba(255,255,255,.55)); margin: 8px 0 6px; }
.ct-macro-burst { display: flex; gap: 8px; }
.ct-macro-burst .ct-seg { flex: 1; }
.ct-macro-steps-head { display: flex; justify-content: space-between; align-items: center; border-top: 1px solid var(--border-light, rgba(255,255,255,.08)); padding-top: 12px; margin-top: 14px; }

/* 编辑视图：**头部固定、只滚步骤列表**（2026-08-29）。
   原先「＋ 添加步骤」与卡片同在 .ct-macro-editor-body 里一起滚，往下翻两屏按钮就被推出
   可视区 —— **步骤一多就再也加不了**，而空态提示还写着"点右上角「＋」"，
   等于界面自己声明了那个按钮该在，却又把它滚走了。
   ⚠ 只在编辑视图加 .is-fixedhead：预览/录制视图仍要靠 body 自己滚
   （手柄图 + 时间轴会超高），全局 overflow:hidden 会把它们截断。 */
.ct-macro-editor-body.is-fixedhead { display: flex; flex-direction: column; overflow: hidden; padding-right: 0; }
.ct-macro-editor-body.is-fixedhead .ct-macro-steps-head { flex: 0 0 auto; margin-top: 0; }
.ct-macro-steps-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding-right: 4px; margin-top: 10px; }
.ct-macro-steps-scroll::-webkit-scrollbar { width: 8px; }
.ct-macro-steps-scroll::-webkit-scrollbar-thumb { background: var(--border-strong, rgba(255,255,255,.18)); border-radius: 4px; }
.ct-macro-empty-steps { border: 1px dashed var(--border-strong, rgba(255,255,255,.18)); border-radius: var(--radius-base); text-align: center; padding: 24px; color: var(--text-tertiary, rgba(255,255,255,.55)); }

/* 宏预览：手柄图 + 时间轴 + 播放 */
.ct-macro-preview-pad { background: var(--bg-inset, #12161e); border-radius: var(--radius-base); padding: 16px; display: flex; justify-content: center; position: relative; }
/* ── 宏级设置条：触发方式 + 循环时间同一排，位于 tab 之上 ──
   它们是整条宏的属性、不属于某个 tab；放进 tab 内会让人以为"编辑和录制各有一份"。 */
.ct-macro-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 12px; margin-bottom: 14px; border-radius: var(--radius-base);
  background: var(--bg-inset, #12161e); border: 1px solid var(--border-light, rgba(255,255,255,.10));
}
.ct-macro-bar-label { flex: none; font-size: var(--fs-sm); color: var(--text-tertiary, rgba(233,240,249,.6)); }
/* 两组设置之间加**竖线分隔**：只靠留白时，「松开触发」与「循环时间」读起来像同一组
   （用户 2026-08-28）。竖线用边框而不是插一个元素 —— 少一个 DOM 节点，且天然随行高。 */
.ct-macro-bar-label--gap { margin-left: 18px; padding-left: 18px; border-left: 1px solid var(--border-strong, rgba(255,255,255,.18)); align-self: stretch; display: flex; align-items: center; }
.ct-macro-bar .ct-macro-burst { display: inline-flex; gap: 6px; flex: none; }
.ct-macro-bar .ct-macro-burst .ct-seg { flex: none; }
.ct-macro-bar-cyc { display: inline-flex; align-items: center; gap: 5px; }
.ct-macro-bar-cyc input { width: 86px; }
.ct-macro-bar-badge { margin-left: auto; }

/* ── 左图右列表 ──
   左栏略宽：手柄线稿是横构图，压窄会同时压矮，右栏能放的行数也跟着少。 */
.ct-macro-split { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: 18px; align-items: start; }
.ct-macro-split-left { min-width: 0; }
.ct-macro-split-right { min-width: 0; display: flex; flex-direction: column; }
@media (max-width: 820px) { .ct-macro-split { grid-template-columns: 1fr; } }

/* ═══ 编辑态：按键按手柄物理分区分组 ═══
   18 个键排成一长串时要逐个认字找；分组后靠"它在手柄哪个位置"就能定位到组。
   标签列定宽，让各组的按钮左边缘对齐 —— 不对齐时分组反而更乱。 */
/* 按手柄左右手分两列：左手拇指管方向键与两个摇杆，右手管 ABXY 与功能键、食指管肩键扳机。
   分列后"找键的路径"与"手的位置"一致。窄屏塌成一列。 */
/* 两列 = 左手键组 / 右手键组。原为三列，第三列 auto 是给已删除的「＋ 停顿」槽留的位。 */
.ct-macro-keys-grouped { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px 22px; margin: 8px 0 4px; }
.ct-macro-keys-col { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
@media (max-width: 720px) { .ct-macro-keys-grouped { grid-template-columns: 1fr; } }
.ct-macro-keys-row { display: flex; align-items: center; gap: 10px; }
.ct-macro-keys-label {
  flex: none; width: 4.5em; text-align: right; font-size: var(--fs-sm);
  color: var(--text-tertiary, rgba(233,240,249,.6));
}
.ct-macro-keys-row .ct-macro-keys { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; }
/* 摇杆八向：定宽等分，八个并排成一条 —— 不等宽时「正上」和「右上」宽度不同，
   一行八个会参差，读起来不像"一组方向"。 */
/* 摇杆八向用箭头图标：方形等宽、八个并排成一条。
   ⚠ 字号要比文字 chip 大一档 —— 箭头字形本身比汉字瘦小，同字号下会显得比旁边的按钮弱。 */
.ct-macro-dirs .ct-chip--dir {
  min-width: 2.2em; padding: 4px 6px; text-align: center;
  font-size: var(--fs-lg); line-height: var(--lh-tight); font-family: var(--font-mono);
}

/* ═══ 分段选择器：两段相连成一个整体，而不是两个独立按钮 ═══
   用于「宏编辑 / 宏录制」。相连的形态本身就在说"这是二选一"，
   而两个分开的按钮读起来像两个可分别触发的动作。 */
.ct-segset {
  display: inline-flex; padding: 3px; border-radius: var(--radius-base); gap: 0;
  background: var(--bg-inset, #12161e);
  border: 1px solid var(--border-light, rgba(255,255,255,.10));
}
.ct-segset .ct-seg { border: none; background: transparent; border-radius: var(--radius-sm); padding: 7px 20px; font-weight: 600; }
.ct-segset .ct-seg--on {
  background: var(--bg-card-hover, #262f3d); color: var(--text-primary, #eef2f8);
  box-shadow: 0 1px 3px rgba(0,0,0,.3);
}

/* 添加步骤：只留一个「＋」，方形、与视图切换同高 */
/* 添加步骤：只用一个「＋」，但要**显眼**（用户 2026-08-28）——
   它是这一页唯一的增加内容入口，做成幽灵按钮时和旁边的图标按钮混成一片。
   故用主色实心；.ct-btn-ghost 的描边样式在 JS 侧已不再附加。 */
.ct-macro-add {
  min-width: 40px; height: 34px; padding: 0 12px; border: none;
  font-size: var(--fs-xl); line-height: 1; font-weight: 700;
  background: var(--color-accent, #FF6B35); color: #1A0A00; border-radius: var(--radius-sm); cursor: pointer;
}
.ct-macro-add:hover { background: var(--color-accent-hover, #ff8255); }

/* 行内删除：常态很淡，hover 才转危险色 —— 每行都有它，常态显眼会把列表变成一片红点 */
.ct-macro-row-del { flex: none; opacity: .35; transition: opacity .12s; }
.ct-macro-row:hover .ct-macro-row-del { opacity: 1; }

/* 右栏顶部：视图切换 + 添加步骤 同一行 */
/* ⚠ 视图切换在这里**不拉满**：它继承自 .ct-macro-burst（那里 .ct-seg 是 flex:1，
   适合"触发方式"那种占满一排的二选一），但在右栏顶部与「添加步骤」同行时，
   拉满会把后者挤没，且视觉分量重过它实际的重要性。 */

/* 步骤列表滚动容器：高度上限由 JS 按左栏（手柄图）实测高度写入 inline style。
   为什么不在 CSS 里写死 max-height：手柄图是 SVG、高度随容器宽度变，
   写死在窄屏会溢出、宽屏会浪费 —— 而约束正是"不超过左边"，只能实测。
   ⚠ 曾用分页实现该约束，被推翻：分页下第 3 步与第 12 步永远不同屏，
   拖动合并这个手势就没有落点（2026-08-28）。 */
.ct-macro-rows--scroll { overflow-y: auto; overscroll-behavior: contain; padding-right: 4px; }
.ct-macro-rows--scroll::-webkit-scrollbar { width: 8px; }
.ct-macro-rows--scroll::-webkit-scrollbar-thumb { background: var(--border-strong, rgba(255,255,255,.18)); border-radius: 4px; }

/* ═══ 底部动作条：独占一行、钉在窗体最下、除按钮外无他物 ═══
   flex: none 使它不参与 body 的伸缩 —— body 是 flex:1 + overflow:auto，
   内容再长也只让 body 滚，动作条恒在视野底部（用户 2026-08-28）。
   上方一条分隔线把"内容"与"收尾动作"划开：没有它时按钮像浮在手柄图旁边。 */
.ct-macro-foot-spacer { flex: 1; }
.ct-macro-modal .ct-modal-acts {
  flex: none; align-items: center; gap: 10px;
  margin-top: 18px; padding-top: 16px;
  border-top: 1px solid var(--border-light, rgba(255,255,255,.10));
}
/* 三个按钮统一高度与内距，靠**分量**区分主次而非堆颜色：
   取消 = 无边框纯文字（最弱）／预览 = 描边（中）／保存 = 实色主色（最强）。 */
.ct-macro-modal .ct-modal-acts button {
  min-height: 38px; padding: 0 22px; border-radius: var(--radius-base);
  font-size: var(--fs-base); font-weight: 600; cursor: pointer;
}
.ct-macro-modal .ct-modal-acts .ct-btn-ghost:first-child {
  border: none; background: transparent; color: var(--text-tertiary, rgba(233,240,249,.6)); font-weight: 500;
}
.ct-macro-modal .ct-modal-acts .ct-btn-ghost:first-child:hover { color: var(--text-primary, #eef2f8); }

/* ── 宏步骤行（编辑·手柄图模式 与 录制 共用）────────────────────────────
   为什么用行而不是卡片：录制态可能有 42 步，卡片式一屏放不下两三个，
   而这里要做的恰恰是"通盘看一遍 + 拖着合并"。行高压到一行，42 步也能扫完。 */
.ct-macro-rows { display: flex; flex-direction: column; gap: 4px; margin-top: 10px; }
.ct-macro-row {
  display: flex; align-items: center; gap: 10px; padding: 7px 10px;
  background: var(--bg-inset, #12161e); border: 1px solid var(--border-light, rgba(255,255,255,.10));
  border-radius: var(--radius-sm); cursor: default; transition: border-color .12s, background .12s;
}
.ct-macro-row-no {
  flex: none; min-width: 24px; text-align: center; font-family: var(--font-mono); font-size: var(--fs-sm);
  color: var(--text-tertiary, rgba(233,240,249,.6));
}
.ct-macro-row-keys { flex: 1; min-width: 0; font-size: var(--fs-sm); color: var(--text-primary, #eef2f8);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ct-macro-row-time { flex: none; display: flex; align-items: center; gap: 4px; }
.ct-macro-row-time input { width: 68px; }
/* 选中态（编辑·手柄图模式）：手柄图作用于选中的这一步，必须一眼可辨 */
.ct-macro-row.is-sel { border-color: var(--color-accent, #FF6B35); background: var(--color-accent-tint, rgba(255,107,53,.16)); }
.ct-macro-row[draggable="true"] { cursor: grab; }
.ct-macro-row.is-dragging { opacity: .45; }
/* 落点提示用**上边框加粗**而不是整行变色：整行变色与 is-sel 撞语义 */
.ct-macro-row.is-drop { border-color: var(--color-accent, #FF6B35); box-shadow: inset 0 2px 0 var(--color-accent, #FF6B35); }

.ct-macro-hint { font-size: var(--fs-sm); color: var(--text-tertiary, rgba(233,240,249,.6)); margin: 8px 0 0; line-height: var(--lh-base); }

.ct-macro-preview-info { display: flex; justify-content: space-between; font-size: var(--fs-sm); color: var(--text-secondary, rgba(255,255,255,.8)); margin: 8px 0; }
.ct-macro-timeline { display: flex; gap: 3px; align-items: center; height: 6px; margin: 4px 0; }
.ct-macro-tl-seg { height: 6px; border-radius: 999px; cursor: pointer; background: var(--border-light, rgba(255,255,255,.12)); border: none; padding: 0; }
.ct-macro-tl-seg.is-past { background: var(--color-accent-tint, rgba(255,107,53,.24)); }
.ct-macro-tl-seg.is-active { background: var(--color-accent, #FF6B35); }
.ct-macro-play { display: flex; justify-content: center; align-items: center; gap: 12px; margin-top: 10px; }
.ct-macro-play-btn { display: flex; align-items: center; justify-content: center; border-radius: 50%; background: var(--color-accent, #FF6B35); color: #fff; border: none; padding: 10px; cursor: pointer; }
.ct-macro-play-btn:hover { background: var(--color-accent-hover, #e85d2a); }

/* 宏录制态（按下沿记步；橙仅用于录制指示点） */
.ct-macro-rec-bar { display: flex; align-items: center; gap: 12px; margin: 4px 0 12px; }
.ct-macro-rec-status { display: inline-flex; align-items: center; gap: 6px; }
.ct-macro-rec-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--color-accent, #FF6B35); display: none; }
.ct-macro-rec-status.is-rec .ct-macro-rec-dot { display: inline-block; animation: ctRecPulse 1s ease-in-out infinite; }
.ct-macro-rec-text { color: var(--text-secondary, rgba(255,255,255,.8)); font-size: var(--fs-base); }
.ct-macro-rec-status.is-rec .ct-macro-rec-text { font-weight: 700; }
.ct-macro-rec-empty { color: var(--text-tertiary, rgba(255,255,255,.55)); font-size: var(--fs-sm); }
@keyframes ctRecPulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }

/* ═══════════ 传感器校准（六轴 IMU 自动校准）═══════════ */
.ct-sensor { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 26px 20px 20px; gap: 13px; }
.ct-sensor-ico { color: var(--color-accent, #FF6B35); line-height: 0; }
.ct-sensor-ico--ok { color: var(--ct-ok, #35c759); }
.ct-sensor-ico--err { color: var(--ct-danger, #ff5a4d); }
.ct-sensor-title { font-size: var(--fs-xl); font-weight: 700; color: var(--text-primary, #fff); margin: 0; }
.ct-sensor-desc { font-size: var(--fs-base); line-height: var(--lh-base); color: var(--text-secondary, rgba(255,255,255,.75)); max-width: 380px; margin: 0; }
.ct-sensor-hint { color: var(--color-accent, #FF6B35); font-weight: 600; }
.ct-sensor .ct-btn { margin-top: 6px; }
/* 校准脉冲（放平静止采样的视觉反馈 · signature）*/
.ct-sensor-pulse { color: var(--color-accent, #FF6B35); position: relative; line-height: 0; margin: 6px 0; }
.ct-sensor-pulse::before { content: ""; position: absolute; inset: -12px; border-radius: 50%; border: 2px solid var(--color-accent, #FF6B35); animation: ct-sensor-ping 1.5s cubic-bezier(0,0,.2,1) infinite; }
@keyframes ct-sensor-ping { 0% { transform: scale(.65); opacity: .75; } 100% { transform: scale(1.45); opacity: 0; } }
/* 采样进度条 */
.ct-sensor-bar { width: 100%; max-width: 300px; height: 6px; border-radius: 3px; background: var(--bg-inset, #12161e); box-shadow: inset 0 0 0 1px var(--border-light, rgba(255,255,255,.08)); overflow: hidden; margin-top: 2px; }
.ct-sensor-bar-fill { height: 100%; width: 0; border-radius: 3px; background: linear-gradient(90deg, var(--color-accent, #FF6B35), var(--color-accent-hover, #ff8255)); transition: width linear; }
@media (prefers-reduced-motion: reduce) { .ct-sensor-pulse::before { animation: none; } .ct-sensor-bar-fill { transition: none; } }

/* ═══════════ 入口拆分新增（2026-08-16）═══════════ */
/* 两个入口互相导流的链接（顶栏） */
.ct-crosslink { font-size: var(--fs-sm); color: rgba(238,242,248,.72); text-decoration: none;
  padding: 5px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,.18); white-space: nowrap; }
.ct-crosslink:hover { color: #fff; border-color: rgba(255,255,255,.34); }
.ct-btn-sm { font-size: var(--fs-sm); padding: 6px 12px; }

/* 门禁 / 后端不可用：与降级卡同族，但带警示色边 */
.ct-gate { border-color: var(--ct-danger); }
.ct-gate .ct-code { font-family: var(--font-mono);
  background: var(--bg-inset); border: 1px solid var(--border-light); border-radius: 4px; padding: 1px 6px; }
.ct-gate-sub { font-size: var(--fs-base); color: var(--text-tertiary); }
.ct-gate-acts { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 4px; }

/* 测试工具：导流卡 + 标准接口读不到的特有键徽章 */
.ct-devcard-sub { font-size: var(--fs-base); color: var(--text-tertiary); margin-top: 4px; }
.ct-badge--na { opacity: .42; border-style: dashed; cursor: help; }
.ct-chip--warn { color: var(--ct-danger); border-color: var(--ct-danger); }

/* ═══════════ gobing 参考测试项（2026-08-16）═══════════ */
/* 方向键串键测试 */
.ct-dpadtest { margin-top: 14px; padding: 16px 18px; }
.ct-dpadtest-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.ct-dpadtest-title { font-size: var(--fs-lg); font-weight: 700; color: var(--text-primary); }
.ct-dpadtest-opts { display: flex; gap: 6px; }
.ct-dpadtest-prog { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--text-secondary); margin-left: auto; }
.ct-dpadtest-grid { display: grid; grid-template-columns: repeat(10, 1fr); gap: 4px; margin-bottom: 14px; }
@media (max-width: 640px) { .ct-dpadtest-grid { grid-template-columns: repeat(5, 1fr); } }
.ct-dpadtest-cell { position: relative; aspect-ratio: 1; display: grid; place-items: center;
  border: 1px solid var(--border-light); border-radius: 6px; background: var(--bg-inset);
  font-size: var(--fs-lg); color: var(--text-tertiary); }
.ct-dpadtest-cell i { position: absolute; top: 2px; left: 4px; font-style: normal; font-size: var(--fs-sm); opacity: .45; }
.ct-dpadtest-cell.ok  { border-color: var(--ct-ok); color: var(--ct-ok); }
.ct-dpadtest-cell.bad { border-color: var(--ct-danger); color: var(--ct-danger); }
.ct-dpadtest-tablewrap { overflow-x: auto; }
.ct-dpadtest-table { border-collapse: collapse; width: 100%; min-width: 460px; font-size: var(--fs-sm); }
.ct-dpadtest-table th, .ct-dpadtest-table td { border: 1px solid var(--border-light); padding: 7px 10px; text-align: center; }
.ct-dpadtest-table th { background: var(--bg-inset); color: var(--text-secondary); font-weight: 600; }
.ct-dpadtest-table td { font-family: var(--font-mono); color: var(--text-primary); }
.ct-dpadtest-hint { font-size: var(--fs-sm); line-height: var(--lh-base); color: var(--text-tertiary); margin: 12px 0 0; }

/* 扳机线性度 / 摇杆回中 判定块 */
.ct-trig2-diag, .ct-stick2-deadzone { margin-top: 10px; display: flex; flex-direction: column; gap: 6px; }
/* [2026-08-31] 原为 .ct-trig2-verdict, .ct-stick2-verdict 共用；
   扳机页的结论行已移除（遮挡百分比读数，且数据行已给出全部原始值），
   摇杆页 .ct-stick2-verdict 仍在用（圆度/回中判定），故只摘掉 trig2 那一半。 */
.ct-stick2-verdict { font-size: var(--fs-sm); font-weight: 700; color: var(--text-secondary); }
.ct-stick2-verdict.ok { color: var(--ct-ok); }
.ct-stick2-verdict.warn { color: var(--ct-danger); }
.ct-trig2-rows { display: flex; gap: 12px; flex-wrap: wrap; font-size: var(--fs-sm); color: var(--text-tertiary); }
.ct-trig2-rows b { font-family: var(--font-mono); color: var(--text-primary); }

/* 摇杆测试控制条 + 中心死区圈 + 画圆读数 */
.ct-stick-ctl { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 12px 16px; margin-bottom: 12px; }
.ct-stick-dz { display: flex; align-items: center; gap: 8px; font-size: var(--fs-sm); color: var(--text-secondary); }
.ct-stick-dz input[type="number"] { width: 68px; padding: 4px 8px; border-radius: 6px;
  border: 1px solid var(--border-strong); background: var(--bg-inset); color: var(--text-primary);
  font-family: var(--font-mono); }
.ct-stick-dz input[type="number"]:disabled { opacity: .45; }
.ct-stick-dz span { font-size: var(--fs-sm); color: var(--text-tertiary); }
.ct-stick-ctl .ct-btn-ghost { margin-left: auto; }
/* 死区圈：以绘图区中心为心，宽高=占满行程百分比 */
.ct-stick2-dzring { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  border: 1.5px dashed var(--ct-danger); border-radius: 50%; pointer-events: none; opacity: .8; }
.ct-stick2-circle { margin-top: 10px; display: flex; flex-direction: column; gap: 6px;
  padding-top: 10px; border-top: 1px solid var(--border-light); }

/* 未映射按钮探测（用于就地测出 ML/MR 等键的按钮索引） */
.ct-unmapped { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 12px;
  padding: 10px 12px; border: 1px dashed var(--ct-new); border-radius: var(--radius-sm); background: var(--bg-inset); }
.ct-unmapped-title { font-size: var(--fs-sm); font-weight: 700; color: var(--ct-new); }
.ct-unmapped-chip { font-family: var(--font-mono); font-size: var(--fs-sm); padding: 3px 8px;
  border-radius: 5px; border: 1px solid var(--border-strong); color: var(--text-primary); }
.ct-unmapped-hint { flex-basis: 100%; font-size: var(--fs-sm); color: var(--text-tertiary); }

/* 按键测试里 SVG 摇杆的「中心→当前位置」连线（与摇杆测试画法一致） */
.ct-live-line { position: absolute; height: 2px; transform-origin: 0 50%;
  background: var(--color-accent, #FF6B35); border-radius: 2px;
  opacity: 0; transition: opacity .12s; pointer-events: none; z-index: 2; }
.ct-live-line.on { opacity: .62; }

/* 测试说明（默认展开；参考 gobing 各测试页「测试说明」文案） */
.ct-help { margin: 10px 0 4px; border: 1px solid var(--border-light); border-radius: var(--radius-sm);
  background: var(--bg-card); overflow: hidden; }
.ct-help > summary { cursor: pointer; padding: 9px 14px; font-size: var(--fs-sm); font-weight: 700;
  color: var(--text-secondary); list-style: none; user-select: none; }
.ct-help > summary::-webkit-details-marker { display: none; }
.ct-help > summary::before { content: "▾ "; opacity: .6; }
.ct-help:not([open]) > summary::before { content: "▸ "; }
.ct-help > summary:hover { color: var(--text-primary); }
.ct-help > summary:focus-visible { outline: 2px solid var(--color-accent, #FF6B35); outline-offset: -2px; }
.ct-help-body { padding: 2px 16px 12px; border-top: 1px solid var(--border-light); }
.ct-help-body p { margin: 8px 0 0; font-size: var(--fs-sm); line-height: var(--lh-base); color: var(--text-tertiary); }
.ct-help-body b { color: var(--text-secondary); font-weight: 700; }


/* ── 按键测试（settings/keytest.js）────────────────────────────────
   诊断页，不是配置页：视觉上刻意比其他模块"素"，
   高亮只留给两件事 —— 当前按住的键、以及键名表里没有的未知码。 */
.ct-kt-ctl { display: flex; flex-direction: column; gap: 12px; }
.ct-kt-seg-wrap { display: flex; gap: 8px; flex-wrap: wrap; }
.ct-kt-hint { font-size: var(--fs-base); color: var(--text-secondary); line-height: var(--lh-loose); margin: 0; }

/* 两列栅格：普通组横跨整行，`--half` 组占一列 —— 相邻两个 half 自然并排（用户 2026-08-30）。
   用 grid 而不是给 half 组写 width:50%：后者要自己扣 gap，且最后一个落单的 half
   会缩成半宽留个洞；grid 里落单的那个照样只占一列，行为一致、无需特判。
   ⚠ gap/margin 沿用此前调好的 12px / 4px 0 16px，不借重构顺手改别人调过的间距。 */
/* 各组统一走**单列纵向排列**；组内格子用固定列宽，使「动作键 / 方向键 /
   肩键·摇杆 / 功能键 / 宏键」左边界与列宽全部对齐（用户 2026-08-30）。
   此前用 `1fr 1fr` 两列放半宽组，肩键只有 2 个键填不满、右半全成视觉空隙，
   且列宽与上下各组对不上 —— 症结是**列宽**不是列间距，调 gap 治不了。 */
/* ⚠ 键格列宽令牌必须**真的定义**：`repeat(4, var(--kt-cell-w))` 里若 var 求值失败，
   **整条 grid-template-columns 作废**、栅格退化成单列 —— 表现就是两个组名竖着排。
   CSS 变量缺失不报错、不回退到"上一次的值"，只是让整条声明失效。 */
.ct-kt-groups { display: grid; grid-template-columns: 1fr; gap: 14px; margin: 4px 0 16px; align-items: start; }
.ct-kt-group { min-width: 0; }
/* 肩键 + 摇杆合并组：四格一行，两个组名分别压在第 1 格与第 3 格正上方 */
.ct-kt-group--pair .ct-kt-pair-heads { display: grid; grid-template-columns: repeat(var(--kt-cols, 5), var(--kt-cell-w, 76px)); gap: var(--kt-gap, 8px); }
.ct-kt-group--pair .ct-kt-pair-heads h4 { grid-column: span 2; margin: 0 0 8px; }
@media (max-width: 560px) { .ct-kt-group--half { grid-column: 1 / -1; } }
.ct-kt-group h4 { font-size: var(--fs-base); color: var(--text-tertiary, rgba(255,255,255,.55));
  margin: 0 0 6px; font-weight: 700; letter-spacing: .3px; }
/* ⚠ 列宽/间距与 .ct-badge-grid 同规格（用户 2026-08-30：按键测试的按钮布局与样式参考测试工具）。
   改这里要连带改 .ct-badge-grid，两处是同一套视觉。 */
/* ⚠ **固定列宽是跨组对齐的前提**：用 `minmax(58px, 1fr)` 时轨道数由容器宽度决定
   （实测算出 6 条 61.83px 轨道），而「肩键/摇杆」的组名栅格是固定 4×76px ——
   两套栅格列宽不同，组名就压不到对应的键上（实测「摇杆」落在 1572、L3 在 1543，
   看起来像右对齐）。两处必须共用同一个 --kt-cell-w。 */
.ct-kt-cells {
  /* ⚠ 用**固定列数**而不是 auto-fill：后者的轨道数由容器宽度算出来，
     处在临界点时 1px 的差别就少排一格 —— 实测右栏 412px 恰好容 5 格，
     被 `border-left: 1px` 吃掉 1px 后变成 4 格，Turbo 掉到第二行。
     固定列数与 --kt-cols（右栏宽度的同一个基准）绑定，不再有临界。 */
  display: grid; grid-template-columns: repeat(var(--kt-cols, 5), var(--kt-cell-w, 76px));
  gap: var(--kt-gap, 8px); justify-content: start;
}

.ct-kt-cell {
  /* 规格与 .ct-badge 对齐：居中单行 + 等宽字体 + 透明底 + 1px 边框。
     差异仅两处，都是配置工具独有的信息：右上角计数角标、底部模拟量小字。 */
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; position: relative;
  padding: 9px 6px; border-radius: var(--radius-base);
  border: 1px solid var(--border-strong, rgba(255,255,255,.18));
  background: transparent;
  transition: background .1s, border-color .1s, color .1s;
}
.ct-kt-cell b { font-size: var(--fs-lg); font-family: var(--font-mono); font-weight: 400;
  color: var(--text-secondary, rgba(233,240,249,.90)); text-align: center; }
/* 按住态：整格反白。用边框+背景双通道，色觉障碍下也分得出 */
/* 按住态同 .ct-badge.on：等宽字体加粗不改字宽，故尺寸恒定、按键时不挤压布局 */
.ct-kt-cell--on { background: var(--bg-card-hover, #262f3d); border-color: var(--color-accent, #FF6B35); }
.ct-kt-cell--on b { color: var(--text-primary, #fff); font-weight: 700; }
.ct-kt-cell--on small { color: var(--text-primary, #fff); }
/* 未在键名表里的码：本页的主要产出，虚线框区别于"已知但未按" */
.ct-kt-cell--novel { border-style: dashed; border-color: var(--color-accent); }
.ct-kt-count {
  position: absolute; top: 3px; right: 5px;
  font-size: var(--fs-sm); font-style: normal; color: var(--text-tertiary, rgba(255,255,255,.55));
  font-variant-numeric: tabular-nums;
}
.ct-kt-cell--on .ct-kt-count { color: #fff; }
/* 触发条件提示（目前只有 FN「需双击」）。放在键名下方的小字，与 .ct-kt-count 角标分区，
   两者不会互相遮挡。用 <s> 只是取一个短标签名，故显式去掉默认删除线。 */
/* 震动测试卡：两个按钮并排 + 一行状态。状态行常驻（不靠 hidden 切换），
   否则点按钮时卡片高度会跳 —— 与扳机百分比宽度那条同源：别让内容变化挤动布局。 */
.ct-kt-vib-row { display: flex; gap: 10px; flex-wrap: wrap; margin: 10px 0 8px; }
.ct-kt-vib-state {
  display: block; min-height: var(--lh-base);
  font-size: var(--fs-sm); line-height: var(--lh-base);
  color: var(--text-tertiary, rgba(255,255,255,.55));
}
.ct-kt-hint {
  font-size: var(--fs-xs, 11px); font-style: normal; text-decoration: none;
  color: var(--text-tertiary, rgba(255,255,255,.55)); line-height: var(--lh-tight);
}
.ct-kt-cell--on .ct-kt-hint { color: var(--text-primary, #fff); }
.ct-kt-unknown:empty { display: none; }

.ct-kt-logbar { display: flex; gap: 8px; margin-bottom: 10px; }
.ct-kt-log {
  font-family: var(--font-mono, ui-monospace, Menlo, monospace);
  font-size: var(--fs-base); line-height: var(--lh-base);
  white-space: pre; overflow: auto; max-height: 320px;
  padding: 12px; border-radius: var(--radius-base);
  background: var(--bg-page); color: var(--text-secondary);
  border: 1px solid var(--border-color, rgba(255,255,255,.08));
}

/* 舞台：左线稿 + 右读数（对齐 gamepad-test 的 .ct-test-stage） */
/* 线稿与键位列表**合为一组**（用户 2026-08-30：与它顶部和底部对齐）：
   共用一个描边容器、等高拉伸。`align-items: start` 会让两栏各自收缩到内容高度、
   底边参差；改 stretch 后右栏跟着线稿一起到底，两者才读得出是同一组。 */
.ct-kt-stage {
  /* 键格列宽与每行格数是**同一套基准**：右栏宽度由它们算出来，不写死。
     最宽的一组是「功能键」5 格（Back/Start/HOME/FN/Turbo），故 --kt-cols: 5。
     ⚠ 右栏此前用 `1.15fr` 按比例分宽，于是**总能多出小半格空位** ——
       Turbo 右边那块空白就是它（用户 2026-08-30 指出）。
       改为按内容精确取宽，余下宽度全部让给线稿：空位消失，线稿同时变大。
     ⚠ 改 --kt-cell-w 时右栏宽度自动跟随，无需两处对着调。 */
  --kt-cell-w: 76px;
  --kt-cols: 5;
  --kt-gap: 8px;
  --kt-pad: 14px;
  display: grid;
  grid-template-columns:
    minmax(0, 1fr)
    calc(var(--kt-cols) * var(--kt-cell-w) + (var(--kt-cols) - 1) * var(--kt-gap) + var(--kt-pad) * 2 + 1px);   /* +1px = readout 的 border-left */
  gap: 0; align-items: stretch;
  border: 1px solid var(--border-color, rgba(255,255,255,.08));
  border-radius: var(--radius-base); overflow: hidden;
}
.ct-kt-padwrap { padding: var(--kt-pad); }
.ct-kt-readout { padding: var(--kt-pad); border-left: 1px solid var(--border-color, rgba(255,255,255,.08)); }
.ct-kt-padwrap .ct-pad-base { flex: 1; display: flex; align-items: center; }
.ct-kt-padwrap .ct-pad-base svg { margin: auto; }
@media (max-width: 900px) { .ct-kt-stage { grid-template-columns: 1fr; } }
.ct-kt-padwrap { display: flex; flex-direction: column; gap: 10px; }
.ct-kt-padhint { font-size: var(--fs-base); color: var(--text-secondary); }
.ct-kt-readout { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
/* 模拟量一行：左摇杆 右摇杆 | 左扳机 右扳机（用户 2026-08-30 指定摇杆在左）。
   四个都是模拟量读数，排成一行才好横向比对；分两行会把同类信息割开、还多占一倍高度。
   窄栏放不下时整体降为两行，摇杆仍在扳机之前。 */
/* 模拟量读数：与上方「线稿 | 键位」同构 —— **两组、共用一个外框、中间一条竖分隔**
   （用户 2026-08-30）。此前是一条横分隔线 + 四项平铺，与上方的分组框语言不一致，
   看起来像两块无关的东西堆在一起。现在整页只有一种分组表达方式。 */
.ct-kt-analog {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 0; align-items: stretch; margin-top: 16px;
  border: 1px solid var(--border-color, rgba(255,255,255,.08));
  border-radius: var(--radius-base); overflow: hidden;
}
.ct-kt-analog-sticks, .ct-kt-analog-trigs {
  display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
  align-items: start; padding: var(--kt-pad, 14px);
}
.ct-kt-analog-trigs { border-left: 1px solid var(--border-color, rgba(255,255,255,.08)); }
/* 仅保留**尺寸自适应**，不再动字号 */
.ct-kt-analog .ct-stick2, .ct-kt-analog .ct-trig2 { padding: 8px 4px; }
.ct-kt-analog .ct-stick2-plot { width: 100%; max-width: 132px; height: auto; aspect-ratio: 1; margin: 0 auto; }
.ct-kt-analog .ct-trig2-bar { height: 132px; grid-area: bar; }
/* 百分比移到进度条**右侧垂直居中**（用户 2026-08-30）。
   ⚠ 用 grid-area 重排，**不改共用模板的 DOM 顺序** —— 那份模板测试工具也在用，
     动 DOM 会连它一起改版；纯样式重排只影响本页作用域。 */
.ct-kt-analog .ct-trig2 {
  display: grid; align-items: center; justify-items: center;
  grid-template-columns: auto auto; grid-template-areas: "head head" "bar pct";
  column-gap: 6px; row-gap: 4px;
  /* justify-content 而不是 justify-items：后者把每个格子在**各自列内**居中，
     列比内容宽时就在条与百分比之间挤出一段空隙；前者是把整组在容器里居中，
     两者之间只剩真正的 column-gap（用户 2026-08-30 反馈间距过大）。 */
  justify-content: center;
}
.ct-kt-analog .ct-trig2-head { grid-area: head; }
.ct-kt-analog .ct-trig2-pct { grid-area: pct; margin-top: 0; }
/* 读数放在**百分比正下方、个位右对齐**（用户 2026-09-04：原位置"不对"）。
   ⚠ 原为 `grid-column: 1 / -1` 跨「条 + 百分比」两列再居中 —— 那是还有
     「校准 / 原始」两行时的布局：行够宽，居中看着是完整一行。
     去掉标签只剩一个数字后，它落在条与百分比**中间**，与上方任何元素都不对齐，
     视觉上悬空。现改为只占百分比那一列。
   ⚠ 为什么归到百分比列而不是条下方：`0%` 与 `0` 是**同一个值的两种表示**
     （百分比 vs 0-255 原始刻度），并成一列扫一眼就能同时读到两种刻度。
   ⚠ 宽度与 .ct-trig2-pct 同为 4ch 且同样 text-align:right —— 这样
     `0%`/`100%` 与 `0`/`255` 的**个位纵向对齐**；宽度不一致时两行会各自居中而错开。
   ⚠ 定宽 + 等宽数字不可删：不占位的话 0 → 90 → 255 每变一次位数就左右跳，
     而这一列正是用来盯着数字变化的，抖动比字小更影响判读。 */
.ct-kt-analog .ct-trig2 .ct-trig2-raw {
  grid-column: 2; justify-self: center;
  display: grid; grid-template-columns: 4ch; align-items: baseline;
}
.ct-kt-analog .ct-trig2 .ct-trig2-raw b {
  min-width: 4ch; text-align: right;
  font-variant-numeric: tabular-nums;   /* 等宽数字：1 与 8 同宽，数值跳动时不推挤 */
}
/* 摇杆读数：坐标等宽定宽，数值跳动时不左右抖 */
/* ⚠ 9ch 不是拍脑袋：坐标最宽形态是 "255, 255" = 8 个字符，留 1 位余量。
   7ch 时实测会折行成「125,」+「129」—— 一个读数被拆成两行，比字小更难读。
   ⚠ 单列（2026-09-04 去掉「校准」标签后）：留着 auto 那一列会让数值靠左偏出居中位。 */
.ct-stick2-row { display: grid; grid-template-columns: 9ch; gap: 8px; justify-content: center; align-items: baseline; white-space: nowrap; }
.ct-stick2-row b { min-width: 9ch; text-align: right; font-variant-numeric: tabular-nums; }
/* 读数再大一号：14 → 16（用户 2026-08-30「还是偏小」）。
   它是判读依据，不是附注，用 --fs-lg 与分组标题同级。 */
.ct-kt-analog .ct-trig2-raw, .ct-stick2-row { font-size: var(--fs-lg); }
/* 读数区：纵向排布（当前只有校准值一行，保留 column 布局以便将来加行） */
.ct-stick2-vals--rows { display: flex; flex-direction: column; gap: 4px; margin-top: 10px; }
.ct-stick2-row b { font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: var(--text-primary); }
@media (max-width: 900px) { .ct-kt-analog { grid-template-columns: repeat(2, 1fr); } }

/* 旧的横条扳机读数（.ct-kt-trigs/.ct-kt-trig/.ct-kt-bar）已删：
   2026-08-30 改为复用 .ct-ro-trigger + shared/trigger-readout.js 的竖条模板，
   与测试工具同一套视觉。删除而非保留 —— 零调用的样式必然腐烂。 */

/* 按键测试三态：未触发（常态）/ 触发过（橙描边+橙字，持久）/ 正按住（实心橙）。
   「触发过」持久保留是刻意的：逐键实测要一眼看出还剩哪些没按过。
   ⚠ **两条规则必须写在 `.ct-kt-cell--on` 之前**（或用组合选择器提高特异性）：
     `--tested b` 与 `--on b` 特异性相同，同分时**后写的赢** ——
     初版把本段追加在文件末尾，于是"按住时白字"被橙字盖掉，
     橙字压在橙底上**完全看不见**（实测：格子看着是纯橙无字，DOM 里名字其实都在）。
     这类冲突不报错、DOM 也正常，只有肉眼能发现。故此处用组合选择器锁死胜负关系。 */
/* 已测：绿边，同 .ct-badge.tested。⚠ 与"正按住"的橙必须是两个颜色——
   同色会让"按过了"和"正按着"看起来一样，逐键实测时分不出还剩哪些没按。 */
.ct-kt-cell--tested { border-color: var(--ct-ok, #35c759); }
.ct-kt-cell--tested b { color: var(--ct-ok, #35c759); }
.ct-kt-cell--tested .ct-kt-count { color: var(--ct-ok, #35c759); }
/* 按住态压过"触发过"：两个类同时存在时以实心橙 + 白字为准 */
.ct-kt-cell--tested.ct-kt-cell--on { border-color: var(--color-accent, #FF6B35); }
.ct-kt-cell--tested.ct-kt-cell--on b,
.ct-kt-cell--tested.ct-kt-cell--on .ct-kt-count { color: var(--text-primary, #fff); }

/* 帮助卡里的「注意：」行 —— 比普通条目更醒目，但不用告警红（它不是错误，是提示） */
.ct-help-note { margin-left: 18px; color: var(--text-tertiary, rgba(255,255,255,.6)); }
.ct-help-note b { color: var(--color-accent, #FF6B35); }

