/* ============================================================
 * style.css — 全部样式
 * 三处主题块：:root（浅色）/ @media 深色 / :root[data-theme="dark"]（显式深色）
 * 新增或修改的 CSS 变量务必三处同步定义。
 * ============================================================ */

:root {
  --bg: #f5f7fb;
  --bg-glow: radial-gradient(1100px 520px at 50% -12%, rgba(99, 102, 241, 0.14), transparent 62%);
  --card: #ffffff;
  --card-hover: #fdfdff;
  --fg: #171a21;
  --fg-muted: #5c6472;
  --fg-faint: #686f7f;
  --border: #e4e7ee;
  --border-strong: #d2d7e1;
  /* 工具类图标按钮（.theme-btn）的描边色。
   * 它对页面底 --bg 的对比度需 ≥3:1（WCAG 1.4.11 控件边界）：#7d8cae ↔ #f5f7fb = 3.14:1。
   * 深色对应 #5f708f（↔ #0e1117 = 3.78:1）。改此值必须同步另外两处主题块。 */
  --ctl-border: #7d8cae;
  --input-bg: #ffffff;
  --accent: #4f46e5;
  --accent-2: #06b6d4;
  --accent-soft: rgba(79, 70, 229, 0.10);
  /* 徽标文字色（浅色下与 --accent 相同；深色下换成更亮的色以保证 AA） */
  --tag-fg: #4f46e5;
  /* 传输分类气泡：UDP / TCP / Argo 三色成对（浅色一套）
   * Argo 文字用深橙 #9a3412（用户拍板保留橙色系）：#FF9500 在 #ffe9c7 上对比度仅 1.86，
   * #9a3412 实测 6.17 ≥ 4.5 达标。改此值必须同步另外两处主题块。 */
  --tag-udp-fg: #0e7490;
  --tag-udp-bg: #cffafe;
  --tag-tcp-fg: #4338ca;
  --tag-tcp-bg: #e0e7ff;
  --tag-argo-fg: #9a3412;
  --tag-argo-bg: #ffe9c7;
  /* 推荐指数星星（图形对象，需 ≥3:1）。⚠️ 必须按**最差底色**选色，不能只看白底：
   * 面板行在 :hover / :focus-visible 时会叠一层 --accent-soft（合成后 ≈ rgb(237,237,252)）。
   *   --star-on  #c2670a ↔ #ffffff = 4.01:1，↔ 叠加后的 rgb(237,237,252) = 3.46:1 ✓
   *   --star-off #7c8698 ↔ #ffffff = 3.67:1，↔ 叠加后 = 3.17:1 ✓
   * （#b45309 偏褐、用户反馈「不像星星」故提亮；但不能更亮：#d97706 在叠加底色上仅 2.75:1、
   *   #f59e0b 白底仅 2.15:1，都会破本项目对比度底线。）
   * 深色对应 #fbbf24 / #778293。改此值必须同步另外两处主题块。 */
  --star-on: #c2670a;
  --star-off: #7c8698;
  --danger: #dc2626;
  --danger-soft: rgba(220, 38, 38, 0.09);
  --warn: #92400e;
  --warn-soft: rgba(146, 64, 14, 0.10);
  --ok: #059669;
  /* 「复制命令」按钮专用绿色系（**纯色，禁止渐变**）。默认态为透明底 + 绿描边/绿字/绿图标；
   * 成功态 .is-copied 换成实心绿底 + --ok-on 文字。对比度按 **.cmd-block 的 --card 底**实测：
   *   --ok-fg #047857 ↔ 白 = 5.48:1（12.5px 文字需 ≥4.5、描边/图标需 ≥3，均达标）；
   *   hover 叠 --ok-soft 后 ≈ rgb(235,244,242) → 4.90:1 仍达标；
   *   成功态实心底 #047857 + #fff 文字 = 5.48:1。
   * （原 --ok #059669 ↔ 白仅 3.76:1，12.5px 文字会破线，故另起 --ok-fg 深一档。）
   * 深色对应 --ok-fg #34d399 / --ok-soft rgba(52,211,153,.14) / --ok-on #0e1117。
   * 改此三值必须同步另外两处主题块。 */
  --ok-fg: #047857;
  --ok-soft: rgba(4, 120, 87, 0.08);
  --ok-on: #ffffff;
  --shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 8px 24px rgba(16, 24, 40, 0.06);
  --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.05);
  --cmd-bg: #f3f5fa;
  --t-var: #4338ca;
  --t-eq: #64748b;
  --t-val: #047857;
  --t-bash: #7c3aed;
  --focus: 0 0 0 3px rgba(79, 70, 229, 0.35);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0e1117;
    --bg-glow: radial-gradient(1100px 520px at 50% -12%, rgba(99, 102, 241, 0.20), transparent 62%);
    --card: #171b23;
    --card-hover: #1b2029;
    --fg: #e8ebf2;
    --fg-muted: #a3acbb;
    --fg-faint: #7c8698;
    --border: #262c38;
    --border-strong: #333b49;
    --ctl-border: #5f708f;
    --input-bg: #0f131a;
    --accent: #7c86ff;
    --accent-2: #22d3ee;
    --accent-soft: rgba(124, 134, 255, 0.16);
    --tag-fg: #a5b4fc;
    --tag-udp-fg: #67e8f9;
    --tag-udp-bg: rgba(34, 211, 238, 0.18);
    --tag-tcp-fg: #a5b4fc;
    --tag-tcp-bg: rgba(124, 134, 255, 0.18);
    --tag-argo-fg: #FF9500;
    --tag-argo-bg: rgba(148, 163, 184, 0.20);
  --star-on: #fbbf24;
  --star-off: #778293;
    --danger: #f87171;
    --danger-soft: rgba(248, 113, 113, 0.14);
    --warn: #fbbf24;
    --warn-soft: rgba(251, 191, 36, 0.14);
    --ok: #34d399;
    --ok-fg: #34d399;
    --ok-soft: rgba(52, 211, 153, 0.14);
    --ok-on: #0e1117;
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 10px 30px rgba(0, 0, 0, 0.35);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
    --cmd-bg: #0b0f16;
    --t-var: #a5b4fc;
    --t-eq: #94a3b8;
    --t-val: #6ee7b7;
    --t-bash: #c4b5fd;
    --focus: 0 0 0 3px rgba(124, 134, 255, 0.45);
  }
}

:root[data-theme="dark"] {
  --bg: #0e1117;
  --bg-glow: radial-gradient(1100px 520px at 50% -12%, rgba(99, 102, 241, 0.20), transparent 62%);
  --card: #171b23;
  --card-hover: #1b2029;
  --fg: #e8ebf2;
  --fg-muted: #a3acbb;
  --fg-faint: #7c8698;
  --border: #262c38;
  --border-strong: #333b49;
  --ctl-border: #5f708f;
  --input-bg: #0f131a;
  --accent: #7c86ff;
  --accent-2: #22d3ee;
  --accent-soft: rgba(124, 134, 255, 0.16);
  --tag-fg: #a5b4fc;
  --tag-udp-fg: #67e8f9;
  --tag-udp-bg: rgba(34, 211, 238, 0.18);
  --tag-tcp-fg: #a5b4fc;
  --tag-tcp-bg: rgba(124, 134, 255, 0.18);
  --tag-argo-fg: #FF9500;
  --tag-argo-bg: rgba(148, 163, 184, 0.2);
  --star-on: #fbbf24;
  --star-off: #778293;
  --danger: #f87171;
  --danger-soft: rgba(248, 113, 113, 0.14);
  --warn: #fbbf24;
  --warn-soft: rgba(251, 191, 36, 0.14);
  --ok: #34d399;
  --ok-fg: #34d399;
  --ok-soft: rgba(52, 211, 153, 0.14);
  --ok-on: #0e1117;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 10px 30px rgba(0, 0, 0, 0.35);
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
  --cmd-bg: #0b0f16;
  --t-var: #a5b4fc;
  --t-eq: #94a3b8;
  --t-val: #6ee7b7;
  --t-bash: #c4b5fd;
  --focus: 0 0 0 3px rgba(124, 134, 255, 0.45);
}

* { box-sizing: border-box; }

html, body { margin: 0; padding: 0; }

body {
  background-color: var(--bg);
  background-image: var(--bg-glow);
  background-repeat: no-repeat;
  color: var(--fg);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
    "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", Roboto, Helvetica, Arial, sans-serif;
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
}

.wrap {
  max-width: 720px;
  margin: 0 auto;
  padding: 40px 20px 64px;
}

/* ---------- header ---------- */
.top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 28px;
}
/* 头部文字列占满可用宽度（工具栏固定靠右），避免副标题被挤成多行 */
.top > div:first-child { flex: 1 1 auto; min-width: 0; }

h1.title {
  margin: 0 0 6px;
  font-size: 26px;
  line-height: 1.3;
  font-weight: 800;
  letter-spacing: -0.01em;
  /* 标题里「小钢炮脚本」是不可拆的词：窄屏（工具栏加了 ALL 按钮后，320px 下标题可用宽度收窄）
   * 默认的中文逐字断行会把「小钢炮」拦腰断成「Sing-box 小钢 / 炮脚本」。
   * keep-all 取消 CJK 逐字断点 → 优先在空格处换行；overflow-wrap:anywhere 兜底，
   * 万一整段真的放不下仍可断行，绝不横向溢出。 */
  word-break: keep-all;
  overflow-wrap: anywhere;
  background: linear-gradient(96deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.subtitle {
  margin: 0;
  color: var(--fg-muted);
  font-size: 14px;
  max-width: 620px;
}

.toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
}

.link-btn {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--fg-faint);
  font: inherit;
  font-size: 13px;
  padding: 6px 8px;
  border-radius: 8px;
  cursor: pointer;
  transition: color .15s ease, background .15s ease;
}
.link-btn:hover { color: var(--fg); background: var(--accent-soft); }
.link-btn:focus-visible { outline: none; box-shadow: var(--focus); }

/* ---------- theme icon button ---------- */
/* 浅色下「不明显」的成因：原为透明底 + border:1px solid var(--border)(#e4e7ee)，
 *   该边框对页面底色 --bg(#f5f7fb) 的对比度仅 1.15:1（远低于 UI 控件边界所需的 3:1），
 *   图标虽可见（5.96:1），但整块读不出「这是个按钮」，与相邻的高对比红描边重置按钮也不成对。
 * 对策：①提升为「实心卡片式」控件（卡面底色 + --shadow-sm 投影），靠「面 + 投影 + 描边」共同界定控件范围；
 *       ②描边改用 --ctl-border（浅色 #7d8cae ↔ #f5f7fb = 3.14:1，深色 #5f708f ↔ #0e1117 = 3.78:1），
 *         使边界本身也满足 WCAG 1.4.11 的 3:1。
 * 图标沿用 --fg-muted（浅色 5.96:1、深色 7.54:1）不变。
 * 说明：--ctl-border 已在三处主题块同步定义；其余复用现有 token。 */
.theme-btn {
  appearance: none;
  border: 1px solid var(--ctl-border);
  background: var(--card);
  color: var(--fg-muted);
  box-shadow: var(--shadow-sm);
  width: 36px;
  height: 36px;
  border-radius: 10px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
/* hover 的 border-color 改为靛色微混：底色已提到 --ctl-border，否则 hover 的边框变化会变成空操作 */
.theme-btn:hover {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 50%, var(--ctl-border));
  background: var(--accent-soft);
}
.theme-btn:focus-visible { outline: none; box-shadow: var(--focus); }
.theme-btn svg { display: block; }

/* ---------- reset icon button（描边红：透明底 + 红边框 + 红图标；与主题按钮同尺寸 / 同圆角） ---------- */
.reset-btn { flex: 0 0 auto; }
#resetBtn {
  background: transparent;
  border-color: var(--danger);
  color: var(--danger);
  /* 该按钮的红色描边本身已达 4.5:1，不需要投影，保持纯描边观感（勿删） */
  box-shadow: none;
}
#resetBtn:hover {
  background: var(--danger-soft);
  border-color: var(--danger);
  color: var(--danger);
}
#resetBtn svg { display: block; }

/* ---------- all-select button「ALL」（描边强调：透明底 + 靛色边框/文字，同尺寸 / 同圆角） ----------
 * 位置：#themeBtn 与 #resetBtn 之间（HTML 顺序即视觉顺序）。
 * 语义：一键启用**全部直连协议**节点（全选）；不动 Argo（Argo 是 CDN 中转隧道，默认已启用）。
 * 三态工具栏语言：主题=实心中立卡片式、ALL=描边靛（正向批量操作）、重置=描边红（危险）。
 * 对比度：浅色 --accent #4f46e5 ↔ --bg #f5f7fb = 5.88:1；深色 #7c86ff ↔ #0e1117 = 6.04:1
 *        （同时满足正文 4.5:1 与控件边界 3:1）。
 * ⚠️ 下面用 ID 选择器覆写 box-shadow: none —— ID 特异性高于 `.theme-btn:focus-visible`，
 *    若不显式补回焦点环，键盘聚焦时看不见焦点（见文件末尾的 :focus-visible 补丁）。 */
.all-btn { flex: 0 0 auto; }
#allBtn {
  background: transparent;
  border-color: var(--accent);
  color: var(--accent);
  box-shadow: none;
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  line-height: 1;
}
#allBtn:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}

/* 焦点环补丁：ID 选择器（#allBtn / #resetBtn）的 `box-shadow: none` 会盖掉
 * `.theme-btn:focus-visible` 的焦点环，这里用同等或更高的特异性补回，保证键盘可达性。 */
#allBtn:focus-visible,
#resetBtn:focus-visible { outline: none; box-shadow: var(--focus); }

/* ---------- uuid section ---------- */
.uuid-section {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 14px 16px;
  margin-bottom: 14px;
  box-shadow: var(--shadow-sm);
}

.uuid-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }

.uuid-tag {
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1;
  padding: 5px 8px;
  border-radius: 999px;
  color: var(--tag-fg);
  background: var(--accent-soft);
  white-space: nowrap;
}

.uuid-input {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  letter-spacing: 0.01em;
  flex: 1 1 auto;
  min-width: 0;
}

/* ---------- node cards ---------- */
.cards { display: flex; flex-direction: column; gap: 12px; }

.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 16px 16px 14px;
  box-shadow: var(--shadow-sm);
  transition: box-shadow .18s ease, transform .18s ease, border-color .18s ease;
  animation: rise .22s ease both;
}
.card:hover { box-shadow: var(--shadow); border-color: var(--border-strong); transform: translateY(-1px); }

@keyframes rise {
  from { transform: translateY(6px); }
  to   { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px 12px;
  flex-wrap: wrap;
}
.card-info { min-width: 0; flex: 1 1 200px; }

/* 端口组：可见 label（「端口：」）+ 内嵌随机图标的输入框 */
.card-port { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.port-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--fg-muted);
  white-space: nowrap;
}
.port-field { position: relative; display: inline-flex; align-items: center; }
.card-port .port-input { width: 128px; min-width: 0; }

/* 输入框内嵌「随机」图标按钮（命中区域 24×24，不溢出边框） */
.port-reroll {
  position: absolute;
  top: 50%;
  right: 4px;
  transform: translateY(-50%);
  width: 24px;
  height: 24px;
  min-width: 24px;
  padding: 0;
  appearance: none;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--fg-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: color .15s ease, background .15s ease;
}
.port-reroll svg { display: block; }
.port-reroll:hover { color: var(--accent); background: var(--accent-soft); }
.port-reroll:focus-visible { outline: none; box-shadow: var(--focus); }

.card-title-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.card-name {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
  word-break: break-word;
}

.tag {
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1;
  padding: 5px 8px;
  border-radius: 999px;
  color: var(--tag-fg);
  background: var(--accent-soft);
  white-space: nowrap;
}
/* 传输分类分色：UDP / TCP / Argo（尺寸 / 圆角 / 字号与 .tag 完全一致） */
.tag-udp { color: var(--tag-udp-fg); background: var(--tag-udp-bg); }
.tag-tcp { color: var(--tag-tcp-fg); background: var(--tag-tcp-bg); }
.tag-argo { color: var(--tag-argo-fg); background: var(--tag-argo-bg); }

/* ---------- 推荐指数星星（.stars）----------
 * 纯 SVG，5 颗；半星 = 前景层宽度 50% + overflow:hidden 裁剪（见 app.js 的 buildRating）。
 * 尺寸约定：单颗 11×11、间距 2px → 整组 63px。**改尺寸要同步 app.js 的 STAR_SIZE 与本文件的
 * .star 宽高**，否则前景层会比背景星大一圈、半星也会错位。 */
.stars {
  /* 紧贴着前面的元素（气泡）靠左排，不再顶到容器右端 —— 间距由父容器的 gap 给，
   * 所以卡片 .card-title-row 与面板 .add-item-head 里都是「名称 · 气泡 · 星星」连排。 */
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex: 0 0 auto;
  color: var(--star-on);
}
.star {
  position: relative;
  display: block;
  width: 11px;
  height: 11px;
  flex: 0 0 auto;
}
.star svg { display: block; }
/* 前景层：宽度由状态类给 —— is-full = 100%、is-half = 50%（半星就靠这条），
 * overflow:hidden 负责把右半边裁掉。刻意不用内联 style，与 .is-copied / .invalid 等既有状态类一致。 */
.star-fill {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  overflow: hidden;
  color: var(--star-on);
}
.star.is-full .star-fill { width: 100%; }
.star.is-half .star-fill { width: 50%; }
.star.is-empty { color: var(--star-off); }

.card-desc { margin: 3px 0 0; font-size: 13px; color: var(--fg-muted); }

.card-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }

/* 卡片按钮（「移除节点」）：描边红 —— 透明底 + 红边框 + 红字 */
.card-btn {
  appearance: none;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1;
  height: 36px;
  padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: transparent;
  color: var(--fg-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  cursor: pointer;
  white-space: nowrap;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.card-btn svg { display: block; flex: 0 0 auto; }
.card-btn-text { white-space: nowrap; }
.card-btn:focus-visible { outline: none; box-shadow: var(--focus); }
.card-btn-del { color: var(--danger); border-color: var(--danger); }
.card-btn-del:hover { background: var(--danger-soft); border-color: var(--danger); color: var(--danger); }

/* 独立描边控件：自包含 36px 高（不再叠加 .btn，避免多类优先级互相覆盖）。
 * 字号 / 内边距 / 间距 / 圆角与 .card-btn 完全一致 —— 两者文本都是 4 个汉字 + 图标，
 * 指标一致才能保证「随机生成」与「移除节点」等宽（实测均 98px）。 */
.icon-btn {
  appearance: none;
  border: 1px solid var(--border-strong);
  background: transparent;
  color: var(--fg-faint);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  height: 36px;
  padding: 0 12px;
  border-radius: 10px;      /* 与相邻的 UUID 输入框、卡片按钮一致 */
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex: 0 0 auto;
  transition: color .15s ease, border-color .15s ease, background .15s ease, transform .12s ease;
}
.icon-btn:active { transform: translateY(1px); }
.icon-btn svg { display: block; flex: 0 0 auto; }
.icon-btn-text { white-space: nowrap; }
.icon-btn:hover { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, var(--border)); background: var(--danger-soft); }
.icon-btn:focus-visible { outline: none; box-shadow: var(--focus); }

/* UUID 行里的「随机生成」保持中性色（不触发删除红） */
.port-row .icon-btn { color: var(--fg-muted); }
.port-row .icon-btn:hover { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); background: var(--accent-soft); }

/* ---------- collapsible (Argo 高级设置) ---------- */
.adv-toggle {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--fg-faint);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  margin-top: 12px;
  padding: 4px 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.adv-toggle:hover { color: var(--accent); }
.adv-toggle:focus-visible { outline: none; box-shadow: var(--focus); border-radius: 6px; }
.adv-toggle::after {
  content: "";
  width: 6px; height: 6px;
  border-right: 1.6px solid currentColor;
  border-bottom: 1.6px solid currentColor;
  transform: rotate(45deg);
  transition: transform .18s ease;
  margin-top: -3px;
}
.adv-toggle[aria-expanded="true"]::after { transform: rotate(-135deg); margin-top: 2px; }

.adv-panel {
  margin-top: 10px;
  padding-top: 12px;
  border-top: 1px dashed var(--border);
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.adv-panel[hidden] { display: none; }

.adv-groups { display: flex; flex-direction: column; gap: 16px; }
.field-group { display: flex; flex-direction: column; gap: 8px; }
.group-title {
  margin: 0;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--fg-muted);
  letter-spacing: 0.01em;
}

.field { display: flex; flex-direction: column; gap: 6px; }

.field-label { font-size: 13px; font-weight: 600; color: var(--fg); }

/* ---------- 开关（role="switch"，UUID 卡片高级设置里的 SHOW_LOG）----------
 * 形态：原生 <button role="switch">（轨道）+ ::after（滑块）。选原生 <button> 的理由：
 *   浏览器对 <button> 天然支持 Enter / Space 触发 click（无需自己写键盘处理），
 *   且 role="switch" + aria-checked 是 ARIA 规范里开关的标准表达；视觉态完全由 [aria-checked] 驱动。
 * 颜色（全部复用既有 token，**不新增 CSS 变量** → 免去三处主题块同步风险），按最差底色实测：
 *   关闭态：轨道底 --card + 1px --ctl-border 描边（浅 3.37:1 / 深 3.45:1，均 ≥3:1）；
 *           滑块 --fg-faint（浅 5.04:1 / 深 4.70:1 vs 轨道底，均 ≥3:1）。
 *   开启态：轨道 --accent，滑块 #fff（浅 6.29:1 / 深 3.13:1 vs 轨道，均 ≥3:1）。
 * ⚠️ 不许给**开关本体**（.switch）或**开关行**（.switch-row）引入第二种底色（即任何 background 着色）。
 *    QA 负对照实测（结论保留、归因更正）：
 *    · 把 hover 底色加在 **.switch-row** 上**不会**破线 —— 因为 .switch 自身有不透明的
 *      `background: var(--card)`，会把行底色整个盖住（描边实测仍 3.37:1(浅) / 3.45:1(深)）。
 *    · 真正会破线的是把底色叠到**开关本体**上（即给开关的 :hover 态写一处 background 叠层，
 *      并把描边改回 --ctl-border）→ 关闭态描边掉到 **2.91:1（浅）/ 2.74:1（深）**，跌破 WCAG 1.4.11 的 3:1。
 *    故：开关本体保持不透明 --card 底、hover 只改描边 / 滑块；开关行只设 cursor、绝不设 background。
 * 焦点环：用类选择器 .switch:focus-visible（本项目禁忌是「ID 选择器写 box-shadow 吃掉焦点环」，
 *    此处不存在 ID 的 box-shadow 覆写，故无需补丁）。 */
/* 开关字段组（.field-group.switch-field）：标题独占第一行，第二行是「开关 + 注释」。
 * 「整行可点」的 click 绑在**本组**（.switch-field）上，而非 .switch-row —— 因为标题已移到
 * .switch-row 之外，若仍绑在行上、点标题就没反应了（功能回退）。故本组 cursor: pointer。
 * **禁止**给本组 / .switch-row 加 :hover 规则或任何 background —— QA 有静态断言守着
 * 「.switch-row 不得有 :hover 规则 / 不得拥有背景」，避免引入第二种底色。 */
.switch-field { cursor: pointer; }
/* 第二行：开关在左、注释紧挨其右并占据剩余宽度（flex:1）。
 * 去掉了旧版的 justify-content: space-between（那会把注释顶到最右端、离开关很远）；
 * 改为 gap 8px 紧凑排列；开关与注释**垂直居中**对齐（注释折行时开关居中于整块注释）。 */
.switch-row { display: flex; align-items: center; gap: 8px; cursor: pointer; }

.switch {
  appearance: none;
  position: relative;
  flex: 0 0 auto;
  width: 44px;
  height: 24px;
  padding: 0;
  border: 1px solid var(--ctl-border);
  border-radius: 999px;
  background: var(--card);
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease;
}
.switch::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--fg-faint);
  transition: transform .15s ease, background-color .15s ease;
}
.switch:hover { border-color: var(--accent); }
.switch:hover::after { background: var(--accent); }
.switch[aria-checked="true"] { background: var(--accent); border-color: var(--accent); }
.switch[aria-checked="true"]:hover { border-color: var(--accent); }
.switch[aria-checked="true"]:hover::after { background: #fff; }
.switch[aria-checked="true"]::after { transform: translateX(20px); background: #fff; }
/* ⚠️ 深色下白滑块 vs --accent（#7c86ff）= 3.13:1，距 WCAG 1.4.11 的 3:1 底线仅高 0.13，余量极薄。
 *    若改 --accent（任一处主题块）必须重测此项；一旦跌破 3:1 即破线。
 *    （刻意保留白滑块而非 var(--card)：亮轨道 + 白滑块是通用开关观感，深色滑块会让小白认不出这是开关。） */
.switch:focus-visible { outline: none; box-shadow: var(--focus); }

/* 开关右侧小字注释：视觉参数对齐既有的 .argo-hint（12.5px / --fg-faint）。
 * flex:1 1 auto + min-width:0 让它吃掉开关右侧的全部剩余宽度（紧挨开关），并在窄屏**正常换行**
 * （320px 下会折成 2 行，允许；因 min-width:0 不会把 .switch-row 撑出横向溢出）。
 * ⚠️ 新起语义化类名 .switch-note，**不复用**任何早期的常驻提示行类名
 *    （那些旧名字在别处有静态断言守着，写进注释也会把它们连累红）。 */
.switch-note { margin: 0; font-size: 12.5px; color: var(--fg-faint); flex: 1 1 auto; min-width: 0; }

.text-input {
  width: 100%;
  font: inherit;
  font-size: 14px;
  color: var(--fg);
  background: var(--input-bg);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  padding: 9px 12px;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.text-input::placeholder { color: var(--fg-faint); }
.text-input:focus { outline: none; border-color: var(--accent); box-shadow: var(--focus); }
.text-input.invalid { border-color: var(--danger); background: var(--danger-soft); }
.text-input.invalid:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 35%, transparent); }

.port-row { display: flex; gap: 8px; align-items: stretch; }
.port-input { font-variant-numeric: tabular-nums; letter-spacing: 0.02em; min-width: 0; }
.port-row .icon-btn { flex: 0 0 auto; white-space: nowrap; }

/* 输入框：统一高度 36px，与卡片按钮（移除节点）/ UUID 行按钮严格相等 */
.card-port .port-input,
.adv-panel .text-input,
.uuid-input {
  height: 36px;
  padding-top: 0;
  padding-bottom: 0;
}
.card-port .port-input { padding-right: 34px; }

/* ---------- 错误行（仅在有错误时出现，无错误时不占高度） ---------- */
.field-error {
  margin: 6px 0 0;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--danger);
}
.field-error[hidden] { display: none; }

/* Argo 固定隧道联动提示（非错误行，可含普通提示 / 警示两种态） */
.argo-hint { margin: 0; font-size: 12.5px; color: var(--fg-faint); }
.argo-hint[hidden] { display: none; }
.argo-hint.hint-warn {
  color: var(--warn);
  font-weight: 600;
  background: var(--warn-soft);
  border-left: 3px solid var(--warn);
  border-radius: 8px;
  padding: 8px 10px;
}

/* ---------- add node ---------- */
.add-wrap { position: relative; margin-top: 16px; }

.btn {
  appearance: none;
  font: inherit;
  font-weight: 600;
  border-radius: 11px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform .12s ease, box-shadow .18s ease, background .18s ease, border-color .15s ease, color .15s ease;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.btn:focus-visible { outline: none; box-shadow: var(--focus); }
.btn:active { transform: translateY(1px); }

/* 主按钮（渐变底）：
 * - 外发光必须「负扩张够深 + 透明度够低」，否则光晕会贴着按钮边缘形成 1–2px 亮圈
 *   （深色主题下尤其明显，曾表现为「边角漏光」，故 spread 由 -6 收紧到 -12）；
 * - 顶部 1px 内高光做玻璃质感，同时弱化圆角抗锯齿带来的毛边。 */
.btn-primary {
  color: #fff;
  background: linear-gradient(96deg, var(--accent), var(--accent-2));
  /* ⚠️ 必须保留：让渐变铺满整个 border box，消除「边缘漏光」（边缘镜像色带）。
   * 成因：.btn 有 `border:1px solid transparent`，而浏览器初始值
   *   background-origin:padding-box + background-clip:border-box + background-repeat:repeat
   *   → 渐变图（尺寸 = padding box）在 border box 内平铺，左右各 1px 环带取到「渐变对侧」颜色
   *   （左环带 = 渐变终点色、右环带 = 渐变起点色）。
   * 修法唯一有效解：background-origin:border-box（只加 no-repeat 无效，反而让透明边框露出页面底色）。
   * 位置很关键：必须写在这里、且排在 `background:` 简写「之后」—— 因为 background 简写会把
   *   background-origin 重置回初始值 padding-box；若只写在 .btn 上会被本规则的简写悄悄覆盖而失效。 */
  background-origin: border-box;
  background-repeat: no-repeat;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 10px 28px -12px rgba(79, 70, 229, 0.32);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 10px 28px -12px color-mix(in srgb, var(--accent) 40%, transparent);
}
.btn-primary:hover {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 12px 32px -12px rgba(79, 70, 229, 0.42);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 12px 32px -12px color-mix(in srgb, var(--accent) 52%, transparent);
}

.btn-secondary {
  color: var(--fg);
  background: transparent;
  border-color: var(--border-strong);
}
.btn-secondary:hover { background: var(--accent-soft); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }

.btn-add {
  width: 100%;
  padding: 14px 18px;
  font-size: 15px;
  letter-spacing: 0.01em;
}
.btn-add .plus { font-size: 18px; line-height: 1; font-weight: 700; margin-top: -1px; }

.add-panel {
  position: absolute;
  top: calc(100% + 10px);
  left: 0;
  right: 0;
  z-index: 30;
  background: var(--card);
  border: 1px solid var(--border-strong);
  border-radius: 14px;
  box-shadow: var(--shadow);
  padding: 8px;
  animation: rise .16s ease both;
}
.add-panel[hidden] { display: none; }

.add-item {
  appearance: none;
  width: 100%;
  text-align: left;
  font: inherit;
  border: 0;
  background: transparent;
  color: var(--fg);
  border-radius: 10px;
  padding: 10px 12px;
  cursor: pointer;
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "head tag" "desc tag";
  column-gap: 10px;
  align-items: center;
  transition: background .13s ease;
}
.add-item:hover:not(:disabled), .add-item:focus-visible:not(:disabled) { background: var(--accent-soft); outline: none; }
.add-item:disabled { cursor: default; opacity: 0.55; }

/* 标题行：名称 + 传输类型气泡 —— 与上方「已选节点」卡片同构，
 * 气泡直接复用 `.tag` / `.tag-udp` / `.tag-tcp` / `.tag-argo`（颜色 token 完全一致），
 * 因此不再需要「UDP，…」这种把传输类型写进简介的前缀文案。 */
.add-item-head { grid-area: head; display: flex; align-items: center; gap: 8px; min-width: 0; flex-wrap: wrap; }
.add-item-name { font-weight: 700; font-size: 14px; }
.add-item-desc { grid-area: desc; font-size: 12.5px; color: var(--fg-muted); }
.add-item-tag {
  grid-area: tag;
  font-size: 12px;
  font-weight: 700;
  color: var(--tag-fg);
  padding: 4px 8px;
  border-radius: 999px;
  background: var(--accent-soft);
  white-space: nowrap;
}
.add-item:disabled .add-item-tag { color: var(--fg-faint); background: transparent; }

/* ---------- command blocks（安装命令 / 停止命令，共用同一套样式） ---------- */
.cmd-block {
  margin-top: 30px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 18px;
  box-shadow: var(--shadow-sm);
}
.cmd-block + .cmd-block { margin-top: 14px; }

/* 命令区标题行：只放「标题 + 小字提示」（复制按钮已移到右下角的 .cmd-foot）。
 * 标题与提示同行、基线对齐；窄屏（可用宽度不足时）小字提示自然换行到标题下方。 */
.cmd-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 8px;
  margin-bottom: 12px;
}
.cmd-title { font-size: 14px; font-weight: 700; letter-spacing: 0.01em; }
/* 「安装命令」标题后的说明性小字：字号小于标题（14px），颜色用 --fg-faint
 * （对 --card 浅色 5.04:1 / 深色 4.70:1，达标），不抢标题。新起语义化类名 .cmd-note，
 * **不得**复活本项目早年移除的那些常驻提示行类名（它们在别处有静态断言守着，
 * 连写进注释都会把套件连累红 —— 故此处刻意不点名）。 */
.cmd-note { margin: 0; font-size: 12.5px; color: var(--fg-faint); }

/* 底部行：左「复制反馈」右「复制命令」按钮 —— 让按钮落在 .cmd-block 的**右下角**，
 * 且右边缘与命令框右边缘对齐（两者同处 .cmd-block 的内容宽度右端）。
 * 反馈行 flex:1 吃掉左侧空间，按钮 flex:0 0 auto 保持自身宽度、不被反馈文字挤压。 */
.cmd-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 10px;
}
.cmd-foot .copy-feedback { margin: 0; flex: 1 1 auto; min-width: 0; }
.cmd-foot .btn-copy { flex: 0 0 auto; }

/* 复制按钮 = 绿色描边 36px 按钮（指标复刻 .card-btn「移除节点」，仅换色为绿）。
 * **纯色**（透明底 + 绿描边/绿字/绿图标），**不含 linear-gradient** —— 彻底避开本项目历史上
 * 反复出现的「渐变按钮边缘镜像色带（用户叫『漏光』）」那类 bug，也因此本按钮**不再需要**
 * `.btn`/`background-origin` 那套渐变宿主补丁（它是独立控件，不再叠加 .btn / .btn-primary）。
 * 对比度（12.5px 文字需 ≥4.5、描边/图标 ≥3，底色为 .cmd-block 的 --card）：
 *   浅色 --ok-fg #047857 ↔ #ffffff = 5.48:1；hover 叠 --ok-soft ≈ rgb(235,244,242) → 4.90:1；
 *   深色 --ok-fg #34d399 ↔ #171b23 = 8.97:1；hover 叠 --ok-soft ≈ rgb(27,53,52) → 6.80:1。
 * 成功态 .is-copied = 实心绿底（--ok-fg）+ --ok-on 文字/图标（图标随 currentColor）：
 *   浅色 #047857 + #fff = 5.48:1；深色 #34d399 + #0e1117 = 8.97:1（两组均达标）。
 * 成功态还换 Lucide check 图标 + 文案「已复制」——**不只靠颜色**传达状态（无障碍）。 */
.btn-copy {
  appearance: none;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1;
  height: 36px;
  padding: 0 12px;
  border: 1px solid var(--ok-fg);
  border-radius: 10px;
  background: transparent;
  color: var(--ok-fg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  cursor: pointer;
  white-space: nowrap;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.btn-copy svg { display: block; flex: 0 0 auto; }
.btn-copy:hover { background: var(--ok-soft); border-color: var(--ok-fg); color: var(--ok-fg); }
.btn-copy:focus-visible { outline: none; box-shadow: var(--focus); }
/* 成功态：实心绿底 + --ok-on 文字/图标。`.is-copied:hover` 单独列出，避免被上面 `.btn-copy:hover`
 * 的淡绿底盖掉（点完按钮鼠标仍悬停其上，两态会同时命中）。 */
.btn-copy.is-copied,
.btn-copy.is-copied:hover { background: var(--ok-fg); border-color: var(--ok-fg); color: var(--ok-on); }
.btn-copy[disabled] { cursor: not-allowed; opacity: 0.5; filter: grayscale(0.3); box-shadow: none; transform: none; }

.cmd-box {
  margin: 0;
  padding: 14px;
  border-radius: 12px;
  background: var(--cmd-bg);
  border: 1px solid var(--border);
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  font-size: 13px;
  line-height: 1.7;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
  overflow-x: hidden;
  max-width: 100%;
  min-height: 48px;
}
.cmd-box.is-empty { color: var(--fg-faint); font-style: normal; }

.t-var { color: var(--t-var); font-weight: 600; }
.t-eq { color: var(--t-eq); }
.t-val { color: var(--t-val); }
.t-sep { color: var(--t-eq); }
.t-bash { color: var(--t-bash); font-weight: 600; }

.warn-box { margin: 10px 0 0; display: flex; flex-direction: column; gap: 6px; }
/* 空的警告容器必须真正消失：它有 margin-top:10px，只存在于「安装命令」区，
 * 若空着也占位，会让该区的「命令框 → 复制命令按钮」比「停止命令」区多 10px
 * （用户 2026-09-18 报告两区按钮上下间距不一致即此因）。app.js 在无警告时置 hidden。 */
.warn-box[hidden] { display: none; }
.warn-line {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--danger);
  background: var(--danger-soft);
  border-left: 3px solid var(--danger);
  padding: 8px 10px;
  border-radius: 8px;
}

.copy-feedback, .toast { margin: 8px 0 0; font-size: 13px; font-weight: 600; color: var(--ok); min-height: 1.2em; }

/* ---------- footer（单行小字） ---------- */
.site-footer {
  margin-top: 30px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
  font-size: 12px;
  line-height: 1.6;
  color: var(--fg-faint);
}
.site-footer p { margin: 0; }
.site-footer a {
  color: var(--fg-muted);
  text-decoration: none;
  border-bottom: 1px solid var(--border-strong);
}
.site-footer a:hover { color: var(--accent); border-bottom-color: currentColor; }

/* ---------- toast ---------- */
.toast {
  position: fixed;
  left: 50%;
  bottom: 22px;
  transform: translate(-50%, 12px);
  background: var(--card);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow);
  color: var(--fg);
  padding: 9px 16px;
  border-radius: 999px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s ease, transform .2s ease;
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  z-index: 60;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }

/* ---------- 窄屏卡片头部：两行网格 ----------
 * 实测（真实 Chrome，最宽协议名 hysteria2 + 128px 输入框）：
 *   三元素单行排布需要 .card-head 最小宽度 W ≈ 496px（对应视口 ≈ 570px）；
 *   而 481–569px 区间 flex-wrap 会把「移除节点」挤到第二行单独占一行。
 * 断点取 620px（= 570 + 50 缓冲），把上述换行区间连同 480px 以下的纵向堆叠态一并收进网格，
 * 保证不存在任何「按钮单独占一行」的宽度区间。
 * 不改 DOM 顺序（info → port → actions），仅用 grid-area 重排，
 * 因此桌面端 DOM / 视觉 / Tab 顺序完全不变。
 */
@media (max-width: 620px) {
  .card-head {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "info    actions"
      "port    port";
    align-items: start;
    column-gap: 12px;
    row-gap: 10px;
  }
  .card-info { grid-area: info; min-width: 0; }
  /* 标题行与「移除节点」按钮同为 36px 高，配合按钮 align-self:start 实现视觉齐平 */
  .card-title-row { min-height: 36px; }
  .card-actions { grid-area: actions; align-self: start; justify-content: flex-end; }
  /* 端口独占第二行、横跨两列，输入框撑满剩余宽度（右端仍留内嵌随机图标位） */
  .card-port { grid-area: port; }
  .card-port .port-field { flex: 1 1 auto; }
  .card-port .port-input { width: 100%; }
}

/* ---------- responsive ---------- */
@media (max-width: 480px) {
  .wrap { padding: 26px 14px 48px; }
  /* 窄屏头部：标题与两个按钮同处第一行（按钮靠右上角），副标题独占第二行、撑满整行。
   * 沿用卡片头的 grid-area 重排思路，不用绝对定位；不改 DOM 顺序。 */
  .top {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "title tools"
      "sub   sub";
    column-gap: 12px;
    row-gap: 6px;
    align-items: start;
  }
  /* 把 h1.title / p.subtitle 提升为网格项：标题与工具栏同行，副标题独占整行 */
  .top > div:first-child { display: contents; }
  h1.title { grid-area: title; margin: 0; font-size: 21px; }
  .subtitle { grid-area: sub; }
  .toolbar { grid-area: tools; flex-direction: row; align-items: center; }
  .uuid-section { border-radius: 14px; padding: 14px; }
  .card { border-radius: 14px; padding: 14px; }
  .btn-add { padding: 12px 14px; }
  .cmd-block { padding: 14px; border-radius: 14px; }
  /* 「复制命令」按钮始终停在 .cmd-block 右下角、不拉满整行（用户要求），故这里**不再**对
   * .cmd-head 做纵向堆叠、也**不再**给 .btn-copy 写 width:100% —— .cmd-head 的 flex-wrap
   * 已能让「标题 + 小字提示」在窄屏自然换行。 */
  .cmd-box { font-size: 12px; padding: 12px; }
  .add-panel { left: 0; right: 0; }
}
