/* 实用工具百宝箱 —— 全站样式
 *
 * ⚠️ 这是一次**纯视觉层**的改版：类名、DOM 结构、脚本行为、路由与数据处理
 *    全部没动，改的只有颜色、间距、圆角、阴影、字号与断点。任何一条规则的
 *    增删都以「不改功能」为前提 —— 例如 .nav 仍然不能是 overflow-x: auto、
 *    .pomo-face 仍然必须 pointer-events: none，那些是行为约束不是外观。
 *
 * 视觉基调：向 apple.com 那种克制的语言靠拢。
 *   · 底色与文字用一套中性灰阶撑起 95% 的画面，蓝色只做那 5% ——
 *     按钮、链接、焦点环。颜色越少，留下的那点颜色越有指向性。
 *   · 留白优先：容器更宽、段间距更大、卡片内边距更松。宁可一屏少放两行。
 *   · 大圆角（10 / 14 / 20 / 28）+ 极浅的描边 + 大范围低透明度的扩散阴影，
 *     不用「深色描边 + 硬投影」那种后台系统的做法。
 *   · 顶栏、下拉、浮动控件共用同一档毛玻璃（--glass / --blur）。
 *
 * 组织方式：先定义设计变量，再写基础元素，然后是布局与组件。
 * 所有工具页共用同一套组件类，因此只要在这里调一次，92 个页面同时变。
 *
 * 分节编号**刻意不重排**：MEMORY 与历史日志里是按「site.css 第 N 节」
 * 指代的，一重排这些引用就全对不上号了。
 */

/* ============ 1. 设计变量 ============ */
:root {
  /* ---- 纸与墨 ----
   * 底色不是中性灰，而是一层没漂白过的暖白；正文也不是纯黑，
   * 是带一点褐的墨色。这两件事必须一起做 —— 只把其中一个改暖，另一个
   * 立刻显脏（暖底配冷黑、或冷底配暖字，都会像"屏幕没校色"）。
   *
   * 层级仍然只靠「一点点」：卡片比纸面亮一点点，嵌进去的块比卡片暗一点点。
   * 靠对比度硬堆出来的层次会显得脏，这也是模板感的一大来源。 */
  --ink: #241f1a; /* 标题与正文 */
  --ink-2: #4a423a; /* 次级文字：面板标题、表单标签 */
  --muted: #6e655a; /* 说明文字（暖纸底 5.35:1） */
  --muted-2: #988d7e; /* 最淡的一层：占位符、单位、图标（3.05:1，只给"不必逐字读"的地方） */
  --line: #e6dccf; /* 分割线（看得见的那一档） */
  --line-soft: #f0e9de; /* 发丝线（一组之内的分隔） */
  --surface: #ffffff; /* 卡片、面板的底 */
  --surface-2: #f5efe6; /* 嵌入块（结果区、分段轨道）的底 */
  --bg: #faf7f2; /* 页面底：暖纸 */
  --bg-warm: #f6f0e6; /* 比纸再沉一档，给首屏曲线收口用 */

  /* ---- 朱砂。全站唯一的有彩色，只出现在可点的东西与「印记」上 ----
   * 取的是印章、砖墙那一档红，不是信号红：饱和度压掉一截之后，
   * 它落在暖纸上像"墨里调出来的"，而不是屏幕自己在发光。 */
  --brand: #b8462f;
  --brand-strong: #9c3a25; /* hover：压深一档 */
  --brand-soft: #fbeee7; /* 极浅的底：次要按钮、选中的勾选项 */
  --brand-ink: #9c3a25; /* 压在 --brand-soft 上的字 */

  /* 主按钮自成一对变量。它与 --brand 分开，是因为深色主题下
   * 「链接要更亮」和「按钮底上的白字要够清楚」是互相打架的两个需求：
   * 链接跟着背景提亮到 #e08a63，按钮底却必须留在深红才压得住白字。 */
  --btn-bg: #b8462f;
  --btn-bg-hover: #9c3a25;
  --btn-ink: #ffffff;

  /* ---- 语义色：同样压一档饱和度，与暖底同处一个色温 ---- */
  --ok: #3f7a4f;
  --ok-soft: #eef6ef;
  --warn: #9c6a1a;
  --warn-soft: #fbf4e6;
  --err: #b23a2e;
  --err-soft: #fceeeb;

  /* 分类主题色，默认取主色，页面内联覆盖（registry.js 的 accent 字段） */
  --accent: var(--brand);
  --accent-soft: var(--brand-soft);

  /* ---- 圆角 ----
   * 比上一版整体收紧一档：20px 以上的圆角铺在暖底上会「软塌塌」，
   * 像一堆移动端卡片浮着；12~16px 既现代，又留得住一点印刷品的方正。
   * 只有真正浮起来的层（下拉、气泡、侧栏整块）才用大圆角。 */
  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 14px;
  --r-xl: 22px;

  /* ---- 阴影 ----
   * 一律掺一点暖褐而不是纯黑：纯黑投影落在暖纸上会发灰、显脏。
   * 第二段永远比第一段更散 —— 目标是「浮起来」，不是「描一圈黑边」。 */
  --shadow-sm: 0 1px 2px rgba(72, 52, 30, 0.05), 0 1px 1px rgba(72, 52, 30, 0.03);
  --shadow-md: 0 6px 20px -8px rgba(72, 52, 30, 0.18),
    0 2px 6px -2px rgba(72, 52, 30, 0.06);
  --shadow-lg: 0 24px 56px -20px rgba(56, 38, 20, 0.28),
    0 4px 14px -4px rgba(56, 38, 20, 0.1);

  /* ---- 动效：全站一条缓动曲线，位移短、时长 0.2s 上下 ---- */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);

  /* ---- 毛玻璃。顶栏、下拉、浮动控件共用同一档 ----
   * --glass 是底色（半透明），--blur 是模糊强度，两者必须配套改：
   * 只调透明度会变成「能看见字从底下穿过去」，比不透明还难看得多。 */
  --glass: rgba(252, 249, 244, 0.76);
  --blur: saturate(160%) blur(18px);

  /* ---- 两个「层」，各解决一类在深色下会失效的写法 ----
   *
   * --raised：浮起来的那一层（分段控件的选中药丸、主题开关的拇指）。
   *   它们压在自己的容器上，而容器本身就是 --surface —— 浅色下用白色
   *   正好"浮起"，深色下同样写 --surface 就等于隐形。所以单独给一个值。
   *
   * --hud / --hud-ink：反差层与它上面的字（轻提示、问号气泡）。
   *   浅色下是近黑底配暖白字，深色下换成浅一点的板 —— 拿 --surface 做这个，
   *   浅色下就是白底白字。 */
  --raised: #ffffff;
  --hud: #2a231d;
  --hud-ink: #faf5ee;

  /* ---- 字体 ----
   * UI 走系统无衬线：一个 @font-face 都不加，macOS 落到 SF Pro、
   * Windows 落到 Segoe UI / 苹方 / 雅黑，首屏不为字体多等一毫秒。
   *
   * --serif 是大标题专用的第二支：中文落到宋体一族，拉丁落到 Georgia。
   * 宋体在大字号下有"横细竖粗"的书卷气，而这正是模板感最缺的东西 ——
   * 全站一律无衬线，配色再好也像后台。
   * ⚠️ 衬线只用在大标题（≥24px）。小字号宋体在 Windows 上会被点阵化，
   *    笔画糊成一团，比无衬线难看得多。 */
  --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI",
    "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Source Han Sans SC",
    sans-serif;
  --serif: "Songti SC", "Source Han Serif SC", "Noto Serif CJK SC", "STSong",
    "SimSun", Georgia, serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", "Courier New", monospace;

  --wrap: 1200px; /* 比上一版宽 20px：多了侧栏，正文才不至于被挤窄 */
  --measure: 68ch; /* 说明文字的行长上限 */

  /* 告诉浏览器「这一屏是浅色界面」：滚动条、<select> 的下拉、日期选择器
   * 这些由浏览器原生绘制的部分随之取浅色。深色主题下会覆写为 dark。
   * 表单控件自身的外观由第 6 节的 .input 系列显式指定，不受它影响。 */
  color-scheme: light;
}

/* ---------- 深色主题 ----------
 *
 * 主题由 <html> 上的 data-theme 决定（"light" / "dark"）。这个属性由页面
 * <head> 里的一段内联脚本在**渲染之前**写上：先读 localStorage 里用户上次的
 * 选择，没有记录时才回落到系统偏好。右上角那个开关改的也是这个属性。
 *
 * 为什么这里不再写 @media (prefers-color-scheme: dark)：
 * CSS 按系统认、JS 按存储认，两套真值一旦并存，就会出现「用户明明选了浅色、
 * 系统是深色、结果页面还是深色」这种谁也说不清的场面。现在真值只有
 * data-theme 一个，系统偏好仅在「用户还没表过态」时参与决定，而且
 * chrome.js 会监听它的变化 —— 所以跟随系统的行为一点没少。
 *
 * 代价是无 JS 时不会自动变深色。但本站的首页与所有工具本来就靠 JS 渲染
 * （页面上就摆着 noscript 提示），这条退化路径没有实际损失。
 *
 * ⚠️ 深色不是把浅色反过来，也不是纯黑。这里给的是「暖炭」：
 *    纸变成深褐灰，墨变成暖白。纯黑配纯白那种反差会亮得刺眼，
 *    而且和浅色档的暖调完全不是一家人。
 */
:root[data-theme="dark"] {
  /* 深色页面里挂一条刺眼的浅色滚动条是最常见的低级破绽。
   * color-scheme 只影响浏览器原生绘制的部分，不改我们自己写的组件。 */
  color-scheme: dark;

  --ink: #f2ece3;
  --ink-2: #d8cec0;
  --muted: #a89e91; /* 暖炭底 7.01:1 */
  --muted-2: #8b8175; /* 4.83:1 */
  --line: #3a322a;
  --line-soft: #2b251f;
  --surface: #1f1a15; /* 卡片只抬一档：抬得越高越像贴在黑纸上的纸片 */
  --surface-2: #171310;
  --bg: #17130f;
  --bg-warm: #150f0c;

  /* 链接是"压在深色背景上的字"，必须提亮才有 7:1；
   * 按钮底是"白字压在上面"，反而不能提亮 —— 所以这里两套值分开写。 */
  --brand: #e08a63;
  --brand-strong: #f0a07c;
  --brand-soft: rgba(224, 138, 99, 0.16);
  --brand-ink: #f0b299;
  --btn-bg: #b8462f;
  --btn-bg-hover: #ca5539;
  --btn-ink: #ffffff;

  --ok: #4fa86a;
  --ok-soft: rgba(79, 168, 106, 0.14);
  --warn: #d9a441;
  --warn-soft: rgba(217, 164, 65, 0.14);
  --err: #e0604f;
  --err-soft: rgba(224, 96, 79, 0.14);

  /* 深色下的阴影只能靠「更黑」表达，透明度必须给足，否则等于没有 */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
  --shadow-md: 0 8px 24px -8px rgba(0, 0, 0, 0.7), 0 2px 6px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 26px 60px -20px rgba(0, 0, 0, 0.85),
    0 4px 14px -4px rgba(0, 0, 0, 0.6);

  --glass: rgba(23, 19, 15, 0.74);

  --raised: #3a322a;
  --hud: #2a231d;
  --hud-ink: #faf5ee;
}

/* ============ 2. 基础元素 ============ */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  /* 16px / 1.65：比"紧凑型后台"松半档，中文长句才不挤在一起。
   * 行高比上一版再放开 0.05 —— 读说明文字的时间比读标题长得多。 */
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* 上一版这里有一层 fixed 的顶部彩色光晕。这一版删掉了：
 * 首屏（首页）用自己的三层渐变画光，工具页用抬头那层分类色晕 ——
 * 页面最上面已经有两处"光"了，再叠一层全局的，就成了三个中心打架。
 * 顺带少一次全屏重绘，滚动也更省。 */

a {
  color: var(--brand);
  text-decoration: none;
}

/* 悬停除了下划线，颜色也跟着压深一档：只有下划线的话，正文里的链接要等到
 * 鼠标划上去、眼睛看到那条线，才知道它是链接。颜色变深是"立刻"发生的。
 * 深色主题下 --brand-strong 比 --brand 更亮，同一个动作方向相反，读起来一致。 */
a:hover {
  color: var(--brand-strong);
  text-decoration: underline;
}

h1,
h2,
h3,
h4 {
  margin: 0;
  /* 600 而不是 700：大字号的粗黑在中文里会发闷、笔画容易糊在一起。
   * 字距比上一版收得轻一些（-0.022em → -0.012em）——
   * 那是给无衬线大字调的，而现在大标题换成了衬线，收太紧笔画会撞上。 */
  font-weight: 600;
  letter-spacing: -0.012em;
  line-height: 1.2;
}

p {
  margin: 0;
}

code,
kbd,
pre,
textarea,
input,
select {
  font-family: var(--mono);
  font-size: 13px;
}

::selection {
  background: color-mix(in srgb, var(--brand) 24%, transparent);
}

/* 滚动条：细一点，不与内容抢视线 */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--muted-2) transparent;
}

*::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

*::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--muted-2) 55%, transparent);
  border: 3px solid transparent;
  background-clip: content-box;
  border-radius: 8px;
}

/* ============ 3. 布局骨架 ============ */
.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 28px;
  position: relative;
  z-index: 1;
}

/* 顶栏：压得住内容、但不抢戏的一条毛玻璃。
 * 底色取自页面纸色（半透明）而不是白色：白条压在暖纸上是两截色，
 * 而这条是「同一张纸的上沿」。
 *
 * 底部那道线只有 46% 的纸边色，几乎看不见 —— 它只在内容滚到下面时
 * 起分隔作用。不用投影：投影会让顶栏像卡片一样浮起来，压住首屏。 */
.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--glass);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  border-bottom: 1px solid color-mix(in srgb, var(--line) 46%, transparent);
}

/* 拿不到 backdrop-filter 时（老 Firefox 等）必须退回不透明底：
 * 半透明但没有模糊，会变成「能看见下面的字从顶栏穿过去」，比不加还难看。 */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header {
    background: var(--bg);
  }
}

.header-inner {
  display: flex;
  align-items: center;
  gap: 20px;
  height: 58px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
  font-size: 15.5px;
  letter-spacing: 0.01em;
  color: var(--ink);
  white-space: nowrap;
  flex-shrink: 0;
  /* 品牌标识现在是**唯一**的回首页入口（顶栏那条「首页」链接已移除），
   * 所以它必须看得出能点 —— 之前它只是众多入口之一，悬停什么都不变也无所谓。 */
  transition: opacity 0.2s var(--ease);
}

/* 悬停压暗一档就够：不新增颜色（顶栏只留一处有彩色），也不改盒模型
 * —— 加内边距做灰底会把手型和字位一起挪动，顶栏的横向对齐全靠
 * 「品牌宽度 + .nav 的 flex:1」撑着，挪一下整条就歪了。
 *
 * ⚠️ `text-decoration: none` 不能省：全局有 `a:hover { text-decoration: underline }`，
 *    标识块是「图标 + 站名」的组合，冒出一条下划线很难看。这条的优先级
 *    （.brand:hover = 0,2,0）高于全局那条（a:hover = 0,1,1），压得住。 */
.brand:hover {
  text-decoration: none;
  /* a:hover 的颜色比 .brand 的优先级高，不压回去的话站名会变红 */
  color: var(--ink);
  opacity: 0.72;
}

/* 键盘用户是唯一没法「先悬停看看」的人，而这里又是回首页的唯一入口 ——
 * 焦点环按站内统一写法给（outline + 强调色），不用浏览器默认那圈。 */
.brand:focus-visible {
  opacity: 0.72;
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: var(--r-sm);
}

/* 标识块：一枚小印。
 * 上一版是三段渐变的"App 图标"，那是模板感很重的做法。现在改成
 * 一块几乎平涂的朱砂，只留一道顶部的内高光 —— 它更像盖上去的，
 * 而不是被渲染出来的。 */
.brand-mark {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  display: grid;
  place-items: center;
  color: #fff;
  background: linear-gradient(
    168deg,
    color-mix(in srgb, var(--brand) 88%, #ffffff) 0%,
    var(--brand) 54%,
    color-mix(in srgb, var(--brand) 82%, #000000) 100%
  );
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22),
    0 2px 7px -3px color-mix(in srgb, var(--brand) 60%, transparent);
}

.brand-mark svg {
  width: 17px;
  height: 17px;
}

.nav {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  flex: 1;
  min-width: 0;
  /* 这里刻意不设横向滚动来兜底：下拉面板是绝对定位的子节点，
   * 父级一旦可滚动，面板就会被裁成一条（横向滚动会把纵向也带成可滚动）。
   * 顶栏收到最后只剩「工具汇总」这一项，320px 宽也放得下，本来不需要滚动。 */
  overflow: visible;
}

.nav-link {
  padding: 7px 14px;
  border-radius: 999px;
  color: var(--muted);
  font-size: 13.5px;
  font-weight: 400;
  letter-spacing: 0.01em;
  white-space: nowrap;
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease);
}

.nav-link:hover {
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  color: var(--ink);
  text-decoration: none;
}

/* 注意这里**没有** `.nav-link.is-active` 这类「当前所在项」的样式了。
 * 顶栏现在只有一个导航项（「工具汇总」触发器，它只有开关两态、不存在选中态），
 * 原来那条规则是给已被移除的「首页」链接用的，留着就是一条永远匹配不到任何
 * 元素的死规则 —— 而它看起来又很像「选中态已经有了」，下次真加了导航项的人
 * 会以为不用再写。所以删掉了，不留「备着以后用」的样式。
 * 分类的选中态在菜单里（`.nav-menu__item.is-active`），那才是它该在的地方。 */

main {
  padding-bottom: 88px;
}

/* ============ 4. 首页 ============ */
/* 首屏：一层暖光晕 + 一道很缓的曲线收口。
 *
 * 这里刻意不铺照片。参考站的首屏是一张大图，但那是因为它是博客 ——
 * 一篇一篇的东西值得一张封面；本站 93 个工具页共用同一套壳，
 * 一张大图会把首屏变成"首页专属"，还让手机端为看一眼多下 200KB。
 * 所以换成纯 CSS 的三层渐变：成本是 0 字节，效果是同样的光。
 *
 * 曲线用 mask-image 引一段内联 SVG（几百字节），把底边"缝"进页面纸色里，
 * 而不是硬切一刀。用 mask 而不是直接画一条 SVG 带 fill 的色块，
 * 是因为 mask 只提供形状、颜色来自 background: var(--bg) —— 深色主题下
 * 自动跟着换，不用维护两份 SVG。 */
.hero {
  position: relative;
  /* 上留白 88 → 112：原来标题上方还有一行引题（26px 高 + 24px 下边距），
   * 删掉它之后不加回来，标题就快贴到顶栏了。补回那 24px 的下边距那一档，
   * 首屏因此矮 26px（即那行引题自身的高度），这是减法该有的结果。 */
  padding: 112px 0 112px;
  background: radial-gradient(760px 340px at 10% -34%, #fffaf2, transparent 68%),
    radial-gradient(680px 360px at 92% -28%, #fdece3, transparent 64%),
    linear-gradient(180deg, #fdfaf4 0%, var(--bg) 58%, var(--bg-warm) 100%);
  overflow: hidden;
}

.hero::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 66px;
  background: var(--bg);
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 66' preserveAspectRatio='none'><path d='M0 24c140-16 280-16 420 0s280 16 420 0c120-13 240-14 360-4v46H0z' fill='%23000'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 66' preserveAspectRatio='none'><path d='M0 24c140-16 280-16 420 0s280 16 420 0c120-13 240-14 360-4v46H0z' fill='%23000'/></svg>");
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  pointer-events: none;
}

/* 深色下那两层"光"不能再用暖白，否则首屏会亮成一块灯箱：
 * 换成朱砂的低透明度晕，纸色本身来自变量。 */
:root[data-theme="dark"] .hero {
  background: radial-gradient(
      760px 340px at 10% -34%,
      color-mix(in srgb, var(--brand) 13%, transparent),
      transparent 68%
    ),
    radial-gradient(
      680px 360px at 92% -28%,
      color-mix(in srgb, var(--brand) 9%, transparent),
      transparent 64%
    ),
    linear-gradient(180deg, #1c1712 0%, var(--bg) 58%, var(--bg-warm) 100%);
}

/* 首屏右侧那枚巨大的「百」：水印/印章的做法，只有 5% 的墨色。
 * 它的作用是给右侧那片留白一点"纸的纹理" —— 不是装饰画，
 * 所以只在宽屏出现（窄屏上它会挤到标题后面去）。 */
.hero::before {
  content: "百";
  position: absolute;
  right: 3vw;
  top: 50%;
  transform: translateY(-52%);
  font-family: var(--serif);
  font-size: 240px;
  font-weight: 700;
  line-height: 1;
  color: color-mix(in srgb, var(--ink) 5%, transparent);
  pointer-events: none;
  user-select: none;
}

@media (max-width: 1023px) {
  .hero::before {
    display: none;
  }
}

/* 首屏文字**左对齐**。
 * 参考站是居中的，但"什么都居中"恰恰是最容易一眼看出是模板的做法：
 * 它让页面只剩一根对称轴，没有阅读起点。左对齐配一条看不见的基线，
 * 更像书的首页 —— 标题、说明、留白都从同一个点长出来。 */
.hero-inner {
  /* 它同时是 .wrap（1200px 居中 + 28px 内边距），所以这里不再收窄 ——
   * 一收窄，margin:auto 就把整块居中，标题又回到版面正中去了。
   * 要限行宽的是说明文字（.hero-sub 自带 600px），不是这一块。 */
  max-width: var(--wrap);
}

/* 首屏那行引题（.hero-kicker）连同它开头的印章一起删掉了 ——
 * 它与侧栏那两个数字说的是同一件事。相应地，.hero 的上留白上调了一档，
 * 否则标题会离顶栏太近（见下面 .hero 的 padding）。 */
.hero h1 {
  font-family: var(--serif);
  /* 大字号的衬线不能收字距：宋体一收紧，横画就撞到下一笔上，
   * 反而糊。给一点正字距，笔画之间才透得过气。 */
  font-size: clamp(38px, 6vw, 66px);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: 0.012em;
  color: var(--ink);
}

.hero-sub {
  margin: 22px 0 0;
  max-width: 600px;
  color: var(--muted);
  font-size: clamp(15px, 1.3vw, 17px);
  line-height: 1.8;
  letter-spacing: 0.005em;
}

/* ---------- 首页骨架：侧栏 + 正文 ----------
 * 这一版把「分类索引 + 搜索」挪进左侧一栏，正文只放工具。
 * 好处是 93 张卡片终于有了目录：滚到任何一段都能在左边看到自己在哪。
 * 侧栏在 1024px 以下折到正文上方（而不是抽屉或隐藏）——
 * 手机上搜索框必须留在第一屏，藏起来等于没有。 */
.home-layout {
  display: grid;
  grid-template-columns: 258px minmax(0, 1fr);
  gap: 44px;
  align-items: start;
  padding-top: 46px;
}

/* 正文栏是栅格子项，必须允许它收缩（min-width: 0）。
 * 少了这一条，里面那层 auto-fill 栅格会按"内容想要的宽度"撑开，
 * 整页就被推出横向滚动 —— 这是 CSS Grid 最常见的一个坑。 */
.home-main {
  min-width: 0;
}

.home-aside {
  position: sticky;
  top: 82px;
}

/* 侧栏整块。用大圆角 + 发丝边 + 一点点阴影，
 * 它比正文里的卡片"厚"一档，因为它是一个容器而不是一张条目。 */
.aside-panel {
  padding: 22px 20px 20px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-sm);
}

/* 侧栏的品牌区（.aside-brand / .aside-title / .aside-sub）整块删掉了：
 * 站名在顶栏和首屏 h1 已经各出现一次，副标语又与首屏 tagline 重复。
 * 于是上面那两个数字成了侧栏的第一行 —— 它们原本顶着一条分隔线（那线
 * 是用来把品牌区和数字隔开的），品牌没了，线也就没东西可隔，一并去掉。 */
.aside-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

.aside-stats > div {
  text-align: center;
}

.aside-stats > div + div {
  border-left: 1px solid var(--line-soft);
}

.aside-stats b {
  display: block;
  font-family: var(--serif);
  font-size: 23px;
  font-weight: 700;
  line-height: 1.1;
  color: var(--ink);
}

.aside-stats span {
  display: block;
  margin-top: 4px;
  font-size: 11.5px;
  letter-spacing: 0.04em;
  color: var(--muted-2);
}

/* 访问次数原来在这里（.aside-visits），2026-10-22 搬到页脚、改成
 * 「您是第 N 位访客」，全站每一页都能看到 —— 见下面的 .footer-visits。
 * 侧栏因此只剩「两个结构数字 + 搜索 + 分类目录」，不再显示会变动的数。 */

/* 搜索框：首页与工具页共用。
 * 在侧面卡片里它变小一号、改成"描边 + 浅底"，不再用阴影顶起来 ——
 * 卡片内部再叠一个带投影的胶囊，会有两层阴影打架。 */
.search {
  position: relative;
  margin: 18px 0 0;
}

/* ⚠️ 这里锚的是 .search-icon，不是 .search svg。
 * 搜索下拉框就在 .search 里面，每一条都带一个图标 —— 写成后代选择器
 * 会把这些图标一起染成 --muted-2、一起绝对定位，看起来是"一排空白方块"。
 * 类名由 chrome.js 的 UI.icon("search", "search-icon") 挂上。 */
.search-icon {
  position: absolute;
  left: 15px;
  top: 50%;
  transform: translateY(-50%);
  display: block;
  width: 16px;
  height: 16px;
  color: var(--muted-2);
  pointer-events: none;
}

.search-icon svg {
  display: block;
  width: 16px;
  height: 16px;
}

.search input {
  width: 100%;
  height: 44px;
  padding: 0 40px 0 42px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink);
  font-family: var(--font);
  font-size: 14px;
  letter-spacing: 0.005em;
  outline: none;
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease),
    background-color 0.2s var(--ease);
}

.search input::placeholder {
  color: var(--muted-2);
}

/* 聚焦时把底色提亮到纸白 + 一圈很淡的朱砂光环。
 * 只描边不加光环，在暖底上那 1px 根本看不出焦点在哪。 */
.search input:focus {
  background: var(--surface);
  border-color: color-mix(in srgb, var(--brand) 42%, var(--line));
  /* 光环窄一档、淡一档：4px/12% 在浅暖底上会泛出一圈明显的粉，
   * 而这只是一个输入框的焦点态，不该比它右边的按钮还抢眼。 */
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 9%, transparent);
}

.search-clear {
  position: absolute;
  right: 9px;
  top: 50%;
  transform: translateY(-50%);
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: 50%;
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  color: var(--muted);
  cursor: pointer;
  display: none;
  place-items: center;
  font-size: 14px;
  line-height: 1;
  transition: background-color 0.18s var(--ease), color 0.18s var(--ease);
}

/* 下拉里的条目图标：底色是指定宽高的浅色块，字形再压深一档，
 * 否则在 30px 的小方块里几乎看不出画的是什么。 */
.drop-item .tool-icon {
  color: color-mix(in srgb, var(--accent) 82%, var(--ink));
}

.search-clear:hover {
  background: color-mix(in srgb, var(--ink) 12%, transparent);
  color: var(--ink);
}

.search.has-value .search-clear {
  display: grid;
}

/* 原生搜索框自带一个"清除"小叉，和下面那个 .search-clear 并排会出现两个 ×。
 * 我们自己那个是按钮、能聚焦、能读屏，原生那个什么都不行 —— 把它抹掉。 */
.search input::-webkit-search-cancel-button,
.search input::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
}

/* 分类索引：每个分类一个圆点，颜色就是它的主题色。
 * 这是全站唯一一处把七个分类色并排摆出来的地方 ——
 * 它们的作用是"认路"，所以只做小圆点，不做色条、不做色块。 */
.aside-index {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--line-soft);
  display: grid;
  gap: 1px;
}

.aside-index__title {
  font-size: 11px;
  letter-spacing: 0.18em;
  color: var(--muted);
  margin-bottom: 8px;
}

.aside-index__item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 9px;
  border-radius: var(--r-sm);
  color: var(--ink-2);
  font-size: 13.5px;
  letter-spacing: 0.01em;
  transition: background-color 0.18s var(--ease), color 0.18s var(--ease);
}

.aside-index__item:hover {
  background: color-mix(in srgb, var(--ink) 5%, transparent);
  color: var(--ink);
  text-decoration: none;
}

.aside-index__dot {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--dot);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--dot) 16%, transparent);
}

.aside-index__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.aside-index__count {
  font-size: 11.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* ---------- 正文：分类分组 ---------- */
.cat-section {
  scroll-margin-top: 86px;
}

.cat-section + .cat-section {
  margin-top: 54px;
}

/* 分组抬头：一条发丝线从标题右边一直拉到容器边缘，像书里的分节线。
 * 这种做法比"色块标题"安静得多，但分节清清楚楚。 */
.cat-head {
  display: flex;
  align-items: baseline;
  gap: 11px;
  margin-bottom: 18px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line-soft);
}

/* 分类色只留一道 3px 的短竖线。它的作用是"这一组是什么颜色"，
 * 撤掉它七个分组会糊成一片，换成色块又会让每段开头多一块噪音。 */
.cat-head::before {
  content: "";
  align-self: center;
  width: 3px;
  height: 16px;
  border-radius: 2px;
  background: var(--accent);
}

.cat-title {
  font-size: 18.5px;
  font-weight: 600;
  letter-spacing: 0.01em;
}

/* 数量不做成小胶囊了：93 张卡上挂着 93 个灰底药丸，看起来像后台的筛选条。
 * 现在它就是标题后面一句话，跟着标题基线走。 */
.cat-count {
  font-size: 12.5px;
  color: var(--muted);
  letter-spacing: 0.02em;
}

.cat-desc {
  margin-left: auto;
  color: var(--muted);
  font-size: 12.5px;
  letter-spacing: 0.02em;
  text-align: right;
}

/* 卡片栅格：auto-fill 保证列数随宽度变化但卡片宽度一致 */
.tool-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(244px, 1fr));
  gap: 14px;
}

/* 卡片：白纸贴在暖纸上的感觉。
 * 上一版是"浮起的卡片"（阴影 + 20px 圆角），93 张并排会显得轻飘；
 * 这一版改成"压在纸上的白块"：没有阴影，只有一圈发丝边，
 * 悬停时才浮起来。安静得多，也才看得出排版的整齐。 */
.tool-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 17px 17px 19px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
  color: inherit;
  transition: transform 0.22s var(--ease), box-shadow 0.22s var(--ease),
    border-color 0.22s var(--ease), background-color 0.22s var(--ease);
}

/* 悬停三件事：抬 2px、边线染上分类色、底色掺 3% 的分类色。
 * 第三件几乎看不出来，但 93 张卡并排时，它让"这一类"在鼠标划过的瞬间
 * 有回应 —— 这就是分类色在卡片上的全部用处。 */
.tool-card:hover {
  transform: translateY(-2px);
  text-decoration: none;
  background-color: color-mix(in srgb, var(--accent) 3%, var(--surface));
  border-color: color-mix(in srgb, var(--accent) 32%, var(--line));
  box-shadow: var(--shadow-md);
}

/* 键盘用户要能看见自己停在哪张卡上。焦点环用卡片自己的分类色，
 * 扫过一遍卡片栅格时，颜色本身也在告诉他"这一类是哪一类"。
 * 鼠标点不会出环（:focus-visible 只在键盘导航时命中），所以它不碍事。 */
.tool-card:focus-visible,
.aside-index__item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  text-decoration: none;
}

.card-top {
  display: flex;
  align-items: center;
  gap: 11px;
}

.tool-icon {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  /* 兜底：即使图标节点异常，也不允许它溢出这个色块、撑破卡片版式 */
  overflow: hidden;
  color: var(--accent);
  background: var(--accent-soft);
  transition: background-color 0.24s var(--ease), color 0.24s var(--ease);
}

/* 同时约束直接子节点与深层 svg：图标外层还有一层无类名的 span 包裹 */
.tool-icon svg {
  width: 18px;
  height: 18px;
}

/* 深色下把分类色往白里提一档、底色改用半透明同色。
 * 分类色是按浅底调的中等明度，直接压在近黑的面板上会发闷、像褪了色。
 * ⚠️ 这里覆盖的是 background / color，**不是** --accent 本身 ——
 *    --accent 由脚本写在行内，覆盖变量会把它连同别处的用法一起改掉。 */
:root[data-theme="dark"] .tool-icon {
  color: color-mix(in srgb, var(--accent) 72%, #ffffff);
  background: color-mix(in srgb, var(--accent) 22%, transparent);
}

.tool-name {
  font-weight: 600;
  font-size: 15px;
  letter-spacing: 0.005em;
  color: var(--ink);
}

.tool-desc {
  color: var(--muted);
  font-size: 13px;
  line-height: 1.65;
  letter-spacing: 0.005em;
}

/* 搜索结果为空时的提示 */
.empty {
  grid-column: 1 / -1;
  padding: 64px 24px;
  text-align: center;
  color: var(--muted);
  background: var(--surface);
  border: 1px dashed var(--line);
  border-radius: var(--r-xl);
}

.empty strong {
  display: block;
  color: var(--ink);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.01em;
  margin-bottom: 8px;
}

/* ---------- 分类折叠（2026-10-21） ----------
 * 抬头整行可点。折叠只换类名，卡片一张不少地留在 DOM 里 ——
 * 这样搜索、计数、读屏、以及那几套按节点数断言的测试都不用跟着改。
 */

/* 折叠键本身是 button：要给键盘和读屏用。视觉上它必须完全"消失"，
 * 字距、字号、字重全部 inherit，否则分类名会比折叠前瘦一圈。 */
.cat-toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 0;
  margin: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  line-height: inherit;
  text-align: left;
  cursor: pointer;
}

.cat-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

/* 箭头：朝下 = 展开，朝右 = 收起。方向本身就是状态，不必再写「已收起」三个字 */
.cat-arrow {
  display: inline-flex;
  flex: 0 0 auto;
  color: var(--muted);
  transition: transform 0.2s var(--ease), color 0.2s var(--ease);
}

.cat-arrow svg {
  width: 12px;
  height: 12px;
}

.cat-section.is-collapsed .cat-arrow {
  transform: rotate(-90deg);
}

.cat-section.is-collapsed .tool-grid {
  display: none;
}

/* 收起后下面没有内容了，抬头那 18px 的下边距会空出一截 */
.cat-section.is-collapsed .cat-head {
  margin-bottom: 0;
}

/* 连续几组都收起时把间距收紧：此时这一列是「一份目录」，
 * 54px 的行距会让七行散成一屏，失去"随手扫一眼"的意义。
 * 前面那组是展开的就仍按原间距 —— 那是两个大块之间的停顿。 */
.cat-section.is-collapsed + .cat-section {
  margin-top: 26px;
}

/* 悬停给一点"这一行能点"的回应。只换颜色不动尺寸，
 * 七个分组同时悬停也不会抖动。 */
.cat-head:hover .cat-title {
  color: var(--brand);
}

.cat-head:hover .cat-arrow {
  color: var(--ink-2);
}

/* ---------- 卡片右上角的星标 ---------- */
/* 星标是 <a> 的兄弟而不是子节点（按钮不能嵌在链接里），
 * 所以外面要套一层定位容器，让星贴着卡片右上角。 */
.tool-card-wrap {
  position: relative;
  display: flex;
}

/* 让链接撑满这一格，卡片高度才由栅格决定而不是由星标撑出来 */
.tool-card-wrap > .tool-card {
  flex: 1;
}

.fav-star {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 7px;
  background: none;
  color: var(--muted);
  cursor: pointer;
  /* 平时不画：94 张卡上挂 94 颗星，会跟工具名抢注意力。
   * 悬停卡片、键盘聚焦、以及"已经固定"这三种情况才露出来。 */
  opacity: 0;
  transition: opacity 0.18s var(--ease), color 0.18s var(--ease),
    background-color 0.18s var(--ease), transform 0.22s var(--ease);
}

/* 卡片悬停抬起 2px，星标要跟着抬 —— 否则它看起来是钉在纸上不动的 */
.tool-card-wrap:hover .fav-star {
  transform: translateY(-2px);
}

.fav-star svg {
  width: 14px;
  height: 14px;
}

.tool-card-wrap:hover .fav-star,
.fav-star:focus-visible,
.fav-star.is-on {
  opacity: 1;
}

.fav-star:hover {
  background: color-mix(in srgb, var(--ink) 7%, transparent);
}

.fav-star:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* 已固定：填实 + 朱砂。空心与实心的差别是"有没有加进去"，
 * 光换颜色在色弱用户眼里是看不出来的。 */
.fav-star.is-on {
  color: var(--brand);
}

.fav-star.is-on svg path {
  fill: currentColor;
}

/* 触屏没有悬停：星标常显，但压淡一档，仍然不抢工具名 */
@media (hover: none) {
  .fav-star {
    opacity: 0.55;
  }

  .fav-star.is-on {
    opacity: 1;
  }
}

/* ---------- 常用工具条 ---------- */
/* 位置在正文最上方、所有分类之前。做法是一张安静的面板：
 * 它和下面的分类不是同一层东西（一个是"我的"，一个是"全部的"），
 * 所以给底色与圆角，让它在视觉上先分出去。 */
.quick {
  margin-bottom: 42px;
  padding: 16px 18px 15px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
}

.quick-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 12px;
}

/* 与侧栏那句「按分类浏览」同一套小字：它们是同一层的界面标签 */
.quick-title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.18em;
  color: var(--muted);
}

/* 这一栏的数字怎么来的，必须写出来 —— 否则「常用」就是一句没法核对的话 */
.quick-note {
  margin-left: auto;
  color: var(--muted);
  font-size: 12px;
  letter-spacing: 0.01em;
}

.quick-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.quick-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 11px 5px 6px;
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: 13px;
  letter-spacing: 0.005em;
  text-decoration: none;
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease),
    background-color 0.18s var(--ease);
}

.quick-chip:hover {
  color: var(--ink);
  text-decoration: none;
  border-color: color-mix(in srgb, var(--accent) 34%, var(--line));
  background: color-mix(in srgb, var(--accent) 5%, var(--surface));
}

.quick-chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  text-decoration: none;
}

/* 图标给一枚很淡的分类色圆底：一眼能看出这件工具属于哪一类，
 * 又不必在胶囊上再写一遍分类名。 */
.quick-chip__icon {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 13%, transparent);
}

.quick-chip__icon svg {
  width: 13px;
  height: 13px;
}

/* 深色下分类色往白里提一档，理由同 .tool-icon */
:root[data-theme="dark"] .quick-chip__icon {
  color: color-mix(in srgb, var(--accent) 72%, #ffffff);
  background: color-mix(in srgb, var(--accent) 22%, transparent);
}

/* ---------- 侧栏的「全部收起 / 展开」 ---------- */
.aside-index__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
}

/* 标题自己那 8px 下边距在这里交给 .aside-index__head 统管 */
.aside-index__head .aside-index__title {
  margin-bottom: 0;
}

.aside-fold {
  flex: 0 0 auto;
  padding: 2px 3px;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 11.5px;
  letter-spacing: 0.02em;
  /* 「全部收起」四个字被拆成两行过一次（390 宽下标题的 width:100% 把它挤成了
   * 一个 40px 的窄条）—— 这里连同下面那条 width:auto 一起钉住。 */
  white-space: nowrap;
  cursor: pointer;
  border-radius: 5px;
  transition: color 0.18s var(--ease), background-color 0.18s var(--ease);
}

.aside-fold:hover {
  color: var(--brand);
  background: color-mix(in srgb, var(--ink) 5%, transparent);
}

.aside-fold:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (max-width: 900px) {
  /* 手机上目录变成一排胶囊，标题与按钮仍占满一行 */
  .aside-index__head {
    width: 100%;
  }

  /* 上面那条给 .aside-index__title 的 width:100% 在手机上是给「按分类浏览」
   * 独占一行用的；它进了 .aside-index__head 之后会把按钮挤到 40px 宽，
   * 于是「全部收起」竖着折成两行。这里把它收回来。 */
  .aside-index__head .aside-index__title {
    width: auto;
  }
}

@media (max-width: 640px) {
  .quick {
    margin-bottom: 30px;
    padding: 14px;
  }

  /* 说明另起一行：390 宽时那两段话挤在同一行会把标题压掉 */
  .quick-head {
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
  }

  .quick-note {
    margin-left: 0;
  }

  .quick-chip {
    font-size: 12.5px;
  }
}

/* ============ 5. 工具页 ============ */
/* 工具页的抬头带一层分类色的极淡光：
 * 七个分类的页面因此各自有一点不同的气色，但都淡到不影响读标题 ——
 * 相当于每个工具都盖了本分类的那枚印。 */
.tool-page-head {
  position: relative;
  padding: 30px 0 32px;
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--accent) 11%, var(--bg)) 0%,
    var(--bg) 92%
  );
  border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
}

/* 工具页把分类主题色注入 --accent，整页图标与强调元素自动换色 */
.crumbs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 7px;
  font-size: 12px;
  color: var(--muted);
  letter-spacing: 0.06em;
  margin-bottom: 16px;
}

.crumbs a {
  color: var(--muted);
}

.crumbs a:hover {
  color: var(--brand);
  text-decoration: none;
}

/* 分隔符取 --line 而不是文字色：它是排版符号，不该有"内容"的重量 */
.crumbs .sep {
  color: color-mix(in srgb, var(--muted-2) 55%, transparent);
}

.tool-title-row {
  display: flex;
  align-items: center;
  gap: 16px;
}

/* 抬头里那枚图标：白底 + 发丝边 + 分类色线条。
 * 底色不再用分类色的浅色版 —— 放在带分类色晕的抬头里会糊成一片。 */
.tool-title-row .tool-icon {
  width: 48px;
  height: 48px;
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--line-soft);
  box-shadow: var(--shadow-sm);
}

.tool-title-row .tool-icon svg {
  width: 24px;
  height: 24px;
}

/* 工具名用衬线：与首页大标题同一支字，页面之间才连得起来。
 * 24px 是衬线的下限，再小宋体在 Windows 上就点阵化了。 */
.tool-page-head h1 {
  font-family: var(--serif);
  font-size: clamp(25px, 3.4vw, 34px);
  font-weight: 700;
  letter-spacing: 0.015em;
  line-height: 1.25;
}

/* 与标题文字左对齐：图标 48 + 间距 16 = 64。
 * 这个数是跟着上面两个值算出来的，改那两个数时这里要一起改。 */
.tool-lead {
  margin-top: 13px;
  margin-left: 64px;
  max-width: var(--measure);
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.7;
  letter-spacing: 0.005em;
}

.workspace {
  display: grid;
  gap: 18px;
  padding-bottom: 24px;
}

/* 抬头与正文之间留一道空气，否则面板像从标题上长出来的 */
.tool-page-head + .wrap .workspace {
  padding-top: 30px;
}

/* 默认单列；宽屏时用两栏，输入与结果并排看更直观 */
@media (min-width: 900px) {
  .workspace--split {
    grid-template-columns: 1fr 1fr;
    align-items: start;
  }
}

.panel {
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

/* 面板抬头不铺底色：只留一条发丝线和更实的字。
 * 灰底会把"面板"切成两块，而这里想要的是「一个整体，抬头只是它的一句话」。
 * 整行可点（折叠），所以给了手型光标并禁掉文字选中 —— 双击标题时
 * 不该留下一道蓝色选区，那看起来像"这行坏了"。 */
.panel-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 15px 20px;
  border-bottom: 1px solid var(--line-soft);
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
}

.panel-head h2,
.panel-head h3 {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: 0.012em;
}

.panel-head .spacer {
  flex: 1;
}

/* 抬头里那些按钮（「生成」「重置」之类）比正文里的小半档：
 * 同一屏里两个一样大的主按钮，用户分不清哪个是"这一页的主行动"。
 * 抬头是标题的地盘，按钮在这里只是顺手可点。 */
.panel-head .btn {
  padding: 7px 15px;
  font-size: 13px;
}

/* ---------- 面板折叠 ----------
 *
 * 箭头固定待在抬头最右端、所有面板一个位置。之所以不把它挪到"紧挨标题"，
 * 是因为七个面板叠在一起时（阴影设计器就是七个），扫一眼右边缘那条竖线
 * 就知道哪几栏还能收 —— 位置一致比位置贴近更重要。
 *
 * 尺寸上有个刻意的取舍：箭头视觉只有 32px，但用一层透明的伪元素把命中区
 * 撑到 44×44。这样既能满足触屏的最小目标，又不会把抬头撑高 12px ——
 * 抬头的行高是全站 300 多个面板共用的，撑高一点点就是全站变胖。
 */
.panel-fold {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  /* 上下各 -9px：让这枚 32px 的按钮在布局上只剩 14px 高，
   * 低于标题那一行的行盒（14px 字号在系统字体下约 17.8px），
   * 于是抬头的行高由标题决定、和加折叠之前一模一样。
   * ⚠️ 这个数字不能随手改：真浏览器量过，-6px 时抬头会从 49.8px 长到 53px，
   *    也就是全站 271 个面板一起变胖 3px —— jsdom 量不出高度，只有这里看得见。
   * 右侧刻意不留负外边距：让按钮右缘与正文的左内边距对齐（都是 20px）。
   * 之前为了"更贴边"给过 -8px，结果那 44px 命中区的右上角被卡片的
   * 20px 圆角切掉了一块 —— 按钮越靠角，圆角吃掉的就越多。 */
  margin: -9px 0 -9px 0;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted-2);
  cursor: pointer;
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease);
}

/* 透明命中区：44×44，但不参与布局、也不改变任何视觉 */
.panel-fold::after {
  content: "";
  position: absolute;
  inset: -6px;
}

.panel-fold:hover {
  background: color-mix(in srgb, var(--ink) 7%, transparent);
  color: var(--ink);
}

.panel-fold:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand) 18%, transparent);
}

.panel-caret {
  display: flex;
  transition: transform 0.24s var(--ease);
}

/* ⚠️ 这一条不能省。内联 <svg> 只带 viewBox、不带 width/height 时，
 * 在 flex 里会被算成 0×0 —— 图标干脆不显示，而按钮本身仍然是好的：
 * 命中区 44×44、点击能折叠、aria 全对，所有数值断言照样全绿。
 * （顶栏那枚 .nav-caret svg 也是这个尺寸，两处是一回事。） */
.panel-caret svg {
  width: 14px;
  height: 14px;
}

/* 收起时箭头转向 90°：状态不是靠颜色或文字表达的，
 * 所以深色浅色、有没有色觉障碍都读得出来 */
.panel-fold[aria-expanded="false"] .panel-caret {
  transform: rotate(-90deg);
}

/* 无标题面板的抬头：一条只有箭头的窄条，高度刚好容下那 44px 的命中区。
 * 这类面板（模式切换条、番茄钟表盘、时间戳主面板）以前压根没有抬头，
 * 所以它们确实会多出一条 44px 的条 —— 这是有意的：没有标题可点，
 * 那枚箭头就是唯一的折叠入口，它必须好点。 */
.panel-head--bare {
  padding: 15px 20px;
  border-bottom: 0;
}

.panel-head--bare .panel-fold {
  margin-right: 0;
}

/* 收起后正文整块退场。用 display:none 而不是高度动画：这些正文里有 canvas、
 * 表格和实时预览，给它们做高度动画既卡又容易在动画中途量错尺寸。
 * 抬头那条发丝线同时撤掉（只改颜色不改宽度），否则卡片底部会悬着一条没有下文的线。 */
.panel.is-collapsed > .panel-body {
  display: none;
}

.panel.is-collapsed > .panel-head {
  border-bottom-color: transparent;
}

@media (prefers-reduced-motion: reduce) {
  .panel-fold,
  .panel-caret {
    transition: none;
  }
}

.panel-body {
  padding: 20px;
}

.panel-body + .panel-body {
  border-top: 1px solid var(--line-soft);
}

.hint {
  color: var(--muted);
  font-size: 12.8px;
  line-height: 1.6;
}

/* 说明句里的轻量标记（ui.js 的 hint() 走 tipInline 渲染）：
 * `**加粗**` 强调「这条别忽略」，`` `命令` `` 换等宽字体。
 * 底色比浮层里那版浅得多 —— 这里是浅灰正文，压一块深色底会跳出来抢注意力。 */
.hint strong {
  color: var(--ink);
  font-weight: 600;
}

.hint code {
  padding: 1px 5px;
  border-radius: 5px;
  background: color-mix(in srgb, var(--ink) 7%, transparent);
  font-family: var(--mono);
  font-size: 0.94em;
}

/* ============ 5.05 可关闭的注释框 ============
 *
 * 页面上保留下来的说明（口径、风险、报错原因、免责、前置条件）都装在这里：
 * 默认可见，右上角一枚叉号，点一下连节点一起移除，且当前会话内不再出现。
 *
 * 底色只比纸面沉一档、左侧一根 2px 的色条 —— 它要被读到，但不能抢正文。
 * 警示态（文案里有 ⚠️，由 ui.js 自动加类）换成语义色的底与色条，
 * 一眼区别于中性说明。
 *
 * ⚠️ 叉号的命中区是 28×28：视觉字形只有 17px，但一行说明本来才 20px 高，
 * 撑到 44 会盖住上下相邻的内容（跟 .tip 同一个道理）。所以给的是
 * 「比看着大」，右侧留 32px 内边距，文字不会被叉号压住。
 */
.note-box {
  position: relative;
  margin-top: 10px;
  padding: 9px 32px 9px 12px;
  border: 1px solid var(--line);
  border-left: 2px solid var(--muted-2);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}

.note-box:first-child {
  margin-top: 0;
}

/* .hint 的行距是给正文里的说明句用的；进了框就由框来管间距 */
.note-box .hint {
  margin: 0;
}

.note-box--warn {
  border-color: color-mix(in srgb, var(--warn) 32%, var(--line));
  border-left-color: var(--warn);
  background: var(--warn-soft);
}

/* 贴在表单字段下面的一档：不加框、不加底，只留一根色条 */
.note-box--flat {
  margin-top: 4px;
  padding: 2px 30px 2px 10px;
  border: 0;
  border-left: 2px solid var(--line);
  background: none;
}

/* 已经关过：不占版面（aria-hidden 由 ui.js 一并带上） */
.note-box.is-off {
  display: none;
}

.note-x {
  position: absolute;
  top: 3px;
  right: 3px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.16s var(--ease), color 0.16s var(--ease);
}

.note-x:hover {
  background: color-mix(in srgb, var(--ink) 9%, transparent);
  color: var(--ink);
}

.note-x:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
  color: var(--ink);
}

@media (prefers-reduced-motion: reduce) {
  .note-x {
    transition: none;
  }
}

/* ============ 5.1 问号说明 ============
 *
 * 长得像一枚「小圆问号」，而不是一个小按钮：它出现在标题旁、字段名旁，
 * 一页上可能十几枚，做成按钮就有了十几个抢眼的方块。
 * 视觉只有 16px，但命中区用伪元素撑到 32px —— 44 在这里不成立：
 * 字段标签的行距只有 18px，撑到 44 会盖住上面那一行的标签和下面的输入框，
 * 点旁边的字反而弹出别人的说明。
 */
.tip {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  margin: 0;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font: inherit;
  line-height: 1;
  cursor: help;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease),
    background-color 0.2s var(--ease);
}

/* 透明命中区：32×32，不参与布局也不改变视觉 */
.tip::after {
  content: "";
  position: absolute;
  inset: -8px;
}

/* 圆里那个问号本身。字形的视觉重心比几何中心偏上一点，
 * 往下一像素才像"居中"；字号写在这里而不是 .tip 上，是因为
 * 它是这个按钮里唯一的一行字，尺寸跟着字形走最不容易改错。 */
.tip-glyph {
  display: block;
  font-size: 10.5px;
  font-weight: 600;
  line-height: 1;
  transform: translateY(0.5px);
}

.tip:hover,
.tip[aria-expanded="true"] {
  color: var(--brand);
  border-color: color-mix(in srgb, var(--brand) 42%, transparent);
  background: color-mix(in srgb, var(--brand) 9%, transparent);
}

.tip:focus-visible {
  outline: none;
  color: var(--brand);
  border-color: var(--brand);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 20%, transparent);
}

/* 标题后面那一枚：抬头的 gap 是 12px，问号跟标题之间要更紧，
 * 否则它看起来像另一件事的开头 */
.panel-head h2 + .tip,
.field-label > .tip {
  margin-left: -4px;
}

/* 浮层：全站唯一一个，位置由 ui.js 算。
 * 配色用反差层（--hud / --hud-ink），浅色下是深底白字、深色下是浅一点的板 ——
 * 拿 --surface 做这个，浅色下是白底白字，深色下也是同一个毛病。 */
.tip-pop {
  position: fixed;
  z-index: 120;
  top: 0;
  left: 0;
  max-width: min(340px, calc(100vw - 20px));
  padding: 9px 12px;
  border-radius: var(--r-md);
  background: var(--hud);
  color: var(--hud-ink);
  font-size: 12.8px;
  line-height: 1.65;
  letter-spacing: 0;
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transform: translateY(-3px);
  transition: opacity 0.16s var(--ease), transform 0.16s var(--ease);
  /* 浮层不接指针：否则鼠标从问号移向气泡的途中就已经离开按钮，
   * 气泡一闪一闪地追着鼠标跑 */
  pointer-events: none;
}

.tip-pop.is-on {
  opacity: 1;
  transform: none;
}

.tip-pop[data-side="above"] {
  transform: translateY(3px);
}

.tip-pop strong {
  font-weight: 600;
}

.tip-pop code {
  padding: 1px 5px;
  border-radius: 5px;
  background: color-mix(in srgb, var(--hud-ink) 14%, transparent);
  font-family: var(--mono);
  font-size: 0.94em;
}

/* 指向问号的箭头：位置跟着 --tip-anchor 走（按钮被夹回视口内时，
 * 箭头仍然咬在按钮正下方，而不是跟着气泡一起偏出去） */
.tip-pop::before {
  content: "";
  position: absolute;
  left: clamp(12px, var(--tip-anchor, 50%), calc(100% - 12px));
  width: 8px;
  height: 8px;
  margin-left: -4px;
  background: var(--hud);
  transform: rotate(45deg);
}

.tip-pop[data-side="below"]::before {
  top: -4px;
}

.tip-pop[data-side="above"]::before {
  bottom: -4px;
}

@media (prefers-reduced-motion: reduce) {
  .tip,
  .tip-pop {
    transition: none;
  }
}

/* ============ 6. 表单组件 ============ */
.field + .field {
  margin-top: 18px;
}

.field-label {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink-2);
  letter-spacing: -0.01em;
}

.field-label .opt {
  font-weight: 400;
  color: var(--muted-2);
  font-size: 12.5px;
}

.input,
.textarea,
select.input {
  width: 100%;
  padding: 11px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--ink);
  outline: none;
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}

/* 聚焦：边框直接跳成品牌色 + 一圈很宽很淡的光环。
 * 只把边框染蓝是不够的，近视/暗环境里那 1px 根本看不见。 */
.input:focus,
.textarea:focus {
  border-color: var(--brand);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand) 14%, transparent);
}

.textarea {
  display: block;
  min-height: 148px;
  padding: 13px 15px;
  resize: vertical;
  line-height: 1.7;
  font-size: 13px;
}

.textarea--tall {
  min-height: 260px;
}

.input::placeholder,
.textarea::placeholder {
  color: var(--muted-2);
}

.input[readonly],
.textarea[readonly] {
  background: var(--surface-2);
  cursor: default;
}

/* 数值与短文本输入：加宽点击区但不占满整行 */
.field-inline {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.field-inline > .field {
  margin-top: 0;
  flex: 1 1 150px;
  min-width: 130px;
}

/* 复选框 / 单选，统一成小胶囊，便于横向排布 */
.checks {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.check {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px 7px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 13.5px;
  color: var(--ink-2);
  cursor: pointer;
  user-select: none;
  background: var(--surface);
  transition: border-color 0.2s var(--ease), background-color 0.2s var(--ease),
    color 0.2s var(--ease);
}

.check:hover {
  border-color: var(--muted-2);
}

.check input {
  accent-color: var(--brand);
  margin: 0;
  cursor: pointer;
}

.check:has(input:checked) {
  border-color: color-mix(in srgb, var(--brand) 45%, transparent);
  background: var(--brand-soft);
  color: var(--brand-ink);
  font-weight: 600;
}

/* 分段切换：用于少量互斥选项（例如 秒 / 毫秒）。
 * 做成"灰色轨道 + 白色滑块"的样子，选中态靠浮起来表达而不是靠填色 ——
 * 填色的话，一个面板里出现两个分段控件就会有两块彩色，很吵。 */
.segmented {
  display: inline-flex;
  padding: 3px;
  gap: 3px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 999px;
}

.segmented button {
  padding: 6px 16px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-family: var(--font);
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease),
    box-shadow 0.2s var(--ease);
}

.segmented button:hover {
  color: var(--ink);
}

.segmented button.is-active {
  background: var(--raised);
  color: var(--ink);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
}

/* 滑块：用于长度、质量等连续参数 */
.range-row {
  display: flex;
  align-items: center;
  gap: 14px;
}

.range-row input[type="range"] {
  flex: 1;
  accent-color: var(--brand);
  min-width: 0;
}

.range-value {
  min-width: 56px;
  text-align: right;
  font-family: var(--mono);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--brand);
  font-variant-numeric: tabular-nums;
}

/* ============ 7. 按钮 ============ */
/* 全站按钮统一成胶囊形 —— 这是整套视觉里最省事、效果最明显的一处改动：
 * 方角按钮和圆角输入框并排时会显得是两种东西，胶囊形一下就"是一家人"了。 */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 10px 20px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font-family: var(--font);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.01em;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color 0.2s var(--ease), border-color 0.2s var(--ease),
    color 0.2s var(--ease), transform 0.12s var(--ease), box-shadow 0.2s var(--ease);
}

.btn svg {
  width: 15px;
  height: 15px;
}

.btn:hover {
  background: color-mix(in srgb, var(--ink) 4%, var(--surface));
  border-color: var(--muted-2);
  color: var(--ink);
}

/* 按下时整体缩一点点，而不是往下挪 1px —— 胶囊形配位移会像"跳了一下" */
.btn:active {
  transform: scale(0.975);
}

/* 键盘焦点单独做：鼠标点也给个方框会显得很旧。
 * 焦点环用 box-shadow 而不是 outline，才能跟着胶囊的圆角走。 */
.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand) 18%, transparent);
}

.btn--primary {
  background: var(--btn-bg);
  border-color: var(--btn-bg);
  color: var(--btn-ink);
  /* 一点同色的投影，让按钮像"压在纸上的一块朱砂"，
   * 而不是一片贴上去的色块。范围给得大、透明度给得低。 */
  box-shadow: 0 6px 18px -8px color-mix(in srgb, var(--btn-bg) 70%, transparent);
}

.btn--primary:hover {
  background: var(--btn-bg-hover);
  border-color: var(--btn-bg-hover);
  color: var(--btn-ink);
}

/* 次要按钮：极浅的品牌色底 + 品牌色字，没有描边。
 * 它和主按钮的差别是"要不要抢注意力"，不是"能不能点"。 */
.btn--soft {
  background: var(--brand-soft);
  border-color: transparent;
  color: var(--brand-ink);
}

.btn--soft:hover {
  background: color-mix(in srgb, var(--brand) 15%, transparent);
  border-color: transparent;
  color: var(--brand-ink);
}

.btn--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--muted);
}

.btn--ghost:hover {
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  border-color: transparent;
  color: var(--ink);
}

.btn--sm {
  padding: 6px 14px;
  font-size: 13px;
}

.btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  transform: none;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}

/* ============ 8. 结果区 ============ */
/* 结果块做成"嵌进去的一块"：底色比面板暗一档（浅色下是浅灰、深色下是深灰），
 * 不给描边。这样它一眼就能和面板本体分开，又不会多出一圈线。 */
.out {
  padding: 16px 18px;
  background: var(--surface-2);
  /* 透明描边而不是 0：下面三个语义变体只改 border-color，
   * 留着这条声明它们才不用各自重写 border。 */
  border: 1px solid transparent;
  border-radius: var(--r-md);
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.75;
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-word;
  overflow-x: auto;
  max-height: 420px;
}

.out--plain {
  background: transparent;
  border: 0;
  padding: 0;
  max-height: 560px;
}

.out--ok {
  background: var(--ok-soft);
  border-color: color-mix(in srgb, var(--ok) 28%, transparent);
  color: var(--ok);
  font-family: var(--font);
}

.out--err {
  background: var(--err-soft);
  border-color: color-mix(in srgb, var(--err) 28%, transparent);
  color: var(--err);
  font-family: var(--font);
}

.out--warn {
  background: var(--warn-soft);
  border-color: color-mix(in srgb, var(--warn) 28%, transparent);
  color: var(--warn);
  font-family: var(--font);
}

.out-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-top: 12px;
  font-size: 12.5px;
  color: var(--muted);
}

.out-meta b {
  color: var(--ink-2);
  font-family: var(--mono);
  font-weight: 600;
}

/* 键值对列表：用于解析类工具的字段展示 */
.kv {
  display: grid;
  gap: 1px;
  background: var(--line-soft);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  overflow: hidden;
}

.kv-row {
  display: grid;
  grid-template-columns: minmax(96px, 34%) 1fr;
  gap: 14px;
  padding: 11px 15px;
  background: var(--surface);
  font-size: 13px;
}

.kv-key {
  color: var(--muted);
  font-weight: 500;
}

.kv-val {
  color: var(--ink);
  font-family: var(--mono);
  word-break: break-all;
}

.kv-val.is-empty {
  color: var(--muted-2);
  font-style: italic;
}

/* 指标卡：用于 BMI、贷款等结果概览 */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
}

.stat {
  padding: 16px 18px;
  background: var(--surface-2);
  border: 1px solid transparent;
  border-radius: var(--r-md);
}

.stat-label {
  font-size: 12.5px;
  color: var(--muted);
  margin-bottom: 4px;
}

.stat-value {
  font-size: 22px;
  font-weight: 600;
  font-family: var(--mono);
  color: var(--ink);
  letter-spacing: -0.025em;
  /* 等宽数字：一排指标卡里数字位数不同时，右边缘才不会参差 */
  font-variant-numeric: tabular-nums;
}

.stat-value small {
  font-size: 12px;
  font-weight: 500;
  color: var(--muted);
  font-family: var(--font);
  margin-left: 3px;
}

.stat.is-accent {
  background: var(--accent-soft);
  border-color: color-mix(in srgb, var(--accent) 24%, transparent);
}

.stat.is-accent .stat-value {
  color: var(--accent);
}

/* 数据表格 */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
}

table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

table.data th,
table.data td {
  padding: 11px 15px;
  text-align: left;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: top;
}

table.data th {
  position: sticky;
  top: 0;
  background: var(--surface-2);
  color: var(--ink-2);
  font-weight: 600;
  font-size: 12.6px;
  letter-spacing: -0.005em;
  white-space: nowrap;
  z-index: 1;
}

table.data tbody tr:last-child td {
  border-bottom: 0;
}

table.data tbody tr:hover td {
  background: var(--surface-2);
}

table.data td.mono {
  font-family: var(--mono);
  white-space: nowrap;
}

/* 代码高亮：正则匹配、Diff 用 */
mark.hit {
  background: color-mix(in srgb, var(--warn) 34%, transparent);
  color: inherit;
  border-radius: 4px;
  padding: 0 2px;
}

mark.hit--alt {
  background: color-mix(in srgb, var(--brand) 24%, transparent);
}

.diff-line {
  display: block;
  padding: 0 8px;
  border-radius: 6px;
  white-space: pre-wrap;
  word-break: break-word;
}

.diff-line--add {
  background: color-mix(in srgb, var(--ok) 16%, transparent);
  color: var(--ok);
}

.diff-line--del {
  background: color-mix(in srgb, var(--err) 14%, transparent);
  color: var(--err);
}

.diff-line--same {
  color: var(--muted);
}

/* 颜色预览块 */
.swatch {
  height: 96px;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
}

/* Markdown 渲染结果 */
.md-body {
  font-size: 15px;
  line-height: 1.75;
  color: var(--ink);
  word-wrap: break-word;
}

.md-body > *:first-child {
  margin-top: 0;
}

.md-body h1,
.md-body h2,
.md-body h3,
.md-body h4 {
  margin: 1.4em 0 0.55em;
  line-height: 1.35;
}

.md-body h1 {
  font-size: 1.55em;
  padding-bottom: 0.3em;
  border-bottom: 1px solid var(--line-soft);
}

.md-body h2 {
  font-size: 1.3em;
  padding-bottom: 0.28em;
  border-bottom: 1px solid var(--line-soft);
}

.md-body h3 {
  font-size: 1.12em;
}

.md-body p {
  margin: 0.9em 0;
}

.md-body ul,
.md-body ol {
  margin: 0.9em 0;
  padding-left: 1.5em;
}

.md-body li + li {
  margin-top: 0.3em;
}

.md-body code {
  padding: 0.15em 0.44em;
  background: var(--surface-2);
  border-radius: 6px;
  font-size: 0.87em;
}

.md-body pre {
  margin: 1em 0;
  padding: 15px 17px;
  background: var(--surface-2);
  border-radius: var(--r-md);
  overflow-x: auto;
  line-height: 1.6;
}

.md-body pre code {
  padding: 0;
  background: transparent;
}

.md-body blockquote {
  margin: 1em 0;
  padding: 2px 0 2px 16px;
  border-left: 3px solid color-mix(in srgb, var(--brand) 45%, transparent);
  color: var(--muted);
}

.md-body a {
  color: var(--brand);
}

.md-body hr {
  margin: 1.7em 0;
  border: 0;
  border-top: 1px solid var(--line-soft);
}

.md-body table {
  width: 100%;
  border-collapse: collapse;
  margin: 1em 0;
  font-size: 0.95em;
}

.md-body th,
.md-body td {
  padding: 9px 13px;
  border: 1px solid var(--line-soft);
}

.md-body th {
  background: var(--surface-2);
}

/* ============ 9. 其它 ============ */
/* 轻提示：复制成功之类的即时反馈。
 * 用反差层（--hud / --hud-ink）而不是 --ink / --surface：
 * 它是瞬时反馈，出现时应该"压"在页面上。借用那对变量在浅色下碰巧是对的，
 * 深色下却会翻成一条发亮的小白条 —— 黑底页面上很扎眼。 */
.toast {
  position: fixed;
  left: 50%;
  bottom: 34px;
  display: flex;
  align-items: center;
  gap: 10px;
  /* 左边留 18px、右边留 10px：右边那个叉号自己带 4px 内边距，
   * 两者相加才和左边的视觉留白对上。 */
  padding: 9px 10px 9px 18px;
  background: var(--hud);
  color: var(--hud-ink);
  border-radius: 999px;
  font-size: 13.5px;
  letter-spacing: -0.01em;
  box-shadow: var(--shadow-lg);
  /* 多了一个叉号，窄屏上就更容不下长文案了 —— 给个上限让它换行，
   * 而不是整条撑出视口（320px 的机器上这很常见）。 */
  max-width: calc(100vw - 32px);
  opacity: 0;
  /* 出场时从下方 14px 处浮上来。这一句不能省：省掉之后 translate(-50%,0)
   * 是从「没有位移」动画过来的，看起来会先横着跳一下再淡入。 */
  transform: translate(-50%, 14px);
  /* 隐藏时不接收点击：这条提示浮在页面底部正中，一直拦着会让
   * 底下那一片按钮点不动。只在显示期间接管指针。 */
  pointer-events: none;
  transition: opacity 0.24s var(--ease), transform 0.24s var(--ease);
  z-index: 90;
}

.toast.is-on {
  opacity: 1;
  transform: translate(-50%, 0);
  pointer-events: auto;
}

/* 叉号：视觉上是一个小小的 ×，命中区 30×30。
 * 触屏上「点得到」比「看得清」更难做到，所以不跟着 13.5px 的字形缩水；
 * 负外边距是为了让它不把整条胶囊撑高 —— 它比行高更高，靠外扩消化掉。 */
.toast__x {
  flex: none;
  width: 30px;
  height: 30px;
  margin: -6px -4px -6px 0;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: inherit;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.62;
  transition: opacity 0.15s var(--ease), background 0.15s var(--ease);
}

.toast__x:hover,
.toast__x:focus-visible {
  opacity: 1;
  background: color-mix(in srgb, var(--hud-ink) 16%, transparent);
  outline: none;
}

/* 文字可以换行（长提示在窄屏上会折），叉号必须留在行内不被挤走 */
.toast__msg {
  min-width: 0;
}

/* 二维码：白底保证任何主题下都能被扫出来 */
.qr-box {
  display: inline-block;
  padding: 18px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  line-height: 0;
}

.qr-box svg {
  width: 100%;
  max-width: 260px;
  height: auto;
  display: block;
}

.spin {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid color-mix(in srgb, currentColor 28%, transparent);
  border-top-color: currentColor;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* 页脚：一条发丝线 + 一行备案号。字比正文小两档、颜色压到最淡 ——
 * 它只需要"在那儿"，不需要被读到。留白给足，别让收尾显得仓促。
 *
 * 页脚不再有说明性文字（站名 / 本机处理那句已按要求删掉），只剩法定备案行。
 * 万一两条备案号都没配，这里会剩一个空容器 —— 下面那条 :has 规则把整块收掉，
 * 免得页面上留一条什么都没有的发丝线。 */
.site-footer {
  border-top: 1px solid var(--line-soft);
  padding: 52px 0 56px;
  color: var(--muted-2);
  font-size: 12.5px;
  line-height: 1.7;
  letter-spacing: -0.005em;
  text-align: center;
}

.site-footer:not(:has(.footer-legal)) {
  display: none;
}

/* 备案那一行。字号比页脚再小半档、颜色反而提亮一档 —— 它是"法定要有"，
 * 不是"希望被读"。但**不能小到连号都看不清**：那是要供公众查询核对的。 */
.footer-legal {
  margin: 0;
  font-size: 12px;
}

/* 链接做成"看起来像文字、悬停才显形"：
 * 常驻下划线会把页脚变成一排蓝色链接，抢走收尾的安静感；
 * 而完全不给悬停反馈，用户就看不出这行字能点（这行恰恰是要求"能点"的）。
 *
 * ⚠️ 颜色用 --muted 而不是页脚那层的 --muted-2。--muted-2 在浅色底
 *    (#f5f5f7) 上只有 3.33:1，低于 WCAG AA 的 4.5:1 —— 页脚那句说明文字
 *    可以这么淡，但备案号是「供公众查询核对」的东西，看不清就失去意义了。
 *    --muted 在浅色 4.66:1、深色 8.1:1，两档都达标。 */
.footer-legal__link {
  display: inline-block;
  /* 纵向内边距把可点高度从 20px 撑到 32px；横向 3px 避免两个号挨太近 */
  padding: 6px 3px;
  color: var(--muted);
  text-decoration: none;
  border-radius: var(--r-sm);
  transition: color 0.2s var(--ease);
}

/* ⚠️ text-decoration: none 不能省：全局有 `a:hover { text-decoration: underline }`，
 *    不压掉的话，悬停会在页脚冒出一条下划线，与"压暗一档"的反馈打架。 */
.footer-legal__link:hover {
  color: var(--ink);
  text-decoration: none;
}

.footer-legal__link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* 分隔符：不加外边距，两侧的 3px 内边距已经隔开了 */
.footer-legal__sep {
  opacity: 0.55;
}

.muted {
  color: var(--muted);
}

.mono {
  font-family: var(--mono);
}

.mt-8 {
  margin-top: 8px;
}

.mt-12 {
  margin-top: 12px;
}

.mt-16 {
  margin-top: 16px;
}

.mb-12 {
  margin-bottom: 12px;
}

.is-hidden {
  display: none !important;
}

/* 时钟工具的专用大字号 */
.clock-face {
  font-family: var(--mono);
  font-size: clamp(44px, 12vw, 96px);
  font-weight: 600;
  letter-spacing: -0.04em;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: var(--ink);
}

.clock-sub {
  text-align: center;
  color: var(--muted);
  font-size: 15px;
  margin-top: 10px;
}

.clock-ms {
  color: var(--brand);
  font-size: 0.42em;
  margin-left: 6px;
  vertical-align: 0.34em;
}

/* 计算器键盘 */
.calc-keys {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 8px;
}

.calc-keys button {
  padding: 13px 4px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 14px;
  cursor: pointer;
  transition: background-color 0.16s var(--ease), border-color 0.16s var(--ease),
    color 0.16s var(--ease);
}

.calc-keys button:hover {
  background: color-mix(in srgb, var(--ink) 5%, transparent);
  border-color: var(--muted-2);
  color: var(--ink);
}

.calc-keys button.op {
  color: var(--brand);
  background: var(--brand-soft);
  border-color: transparent;
  font-weight: 600;
}

/* ---- 计算器的模式切换（科学 / 个税 / 五险一金 / 年化利率） ----
 *
 * 工具页的内容区是「栅格 + 两栏」，切换器本身也是一个栅格项，所以必须
 * 显式声明跨满整行，否则它会跟第一个面板挤在同一列里。
 */
.calc-mode-bar {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

.calc-mode-hint {
  color: var(--muted);
  font-size: 12.8px;
}

/* 五险一金的「险种 / 个人 % / 单位 %」比例表：
 * 用栅格对齐而不是表格，输入框才能贴着列头排成一条线。 */
.rate-grid {
  display: grid;
  gap: 8px;
}

.rate-row {
  display: grid;
  grid-template-columns: 1fr 78px 78px;
  gap: 8px;
  align-items: center;
}

.rate-row .rate-name {
  font-size: 13px;
  color: var(--ink-2);
}

.rate-row.rate-head {
  font-size: 12.2px;
  color: var(--muted);
}

.rate-row.rate-head .rate-col {
  text-align: center;
}

/* ============ 10. 小屏适配 ============
 *
 * 断点按「内容什么时候开始挤」定，而不是硬套设备宽度：
 *   1068px —— 两栏工具区开始显得局促，同时首屏留白该收了
 *    834px —— 横屏平板，卡片降到三列以下
 *    640px —— 大屏手机，一律单列、顶栏收窄
 *    420px —— 小屏手机，只再压字号
 * 收敛的是**外边距**；字号层级与配色在任何宽度下都不变。
 */
@media (max-width: 1068px) {
  .hero {
    padding: 74px 0 96px;
  }

  /* 侧栏收到 232px：再窄一行分类名加数字就挤成两行了 */
  .home-layout {
    grid-template-columns: 232px minmax(0, 1fr);
    gap: 32px;
  }

  .cat-section + .cat-section {
    margin-top: 46px;
  }
}

/* 侧栏折到上方：搜索框留在第一屏，分类目录变成一排可换行的胶囊。
 * 折叠点取 900px 而不是 640px —— 900 以下两栏各 300 多像素，
 * 卡片会全挤成单列，不如整块让位。 */
@media (max-width: 900px) {
  .home-layout {
    grid-template-columns: minmax(0, 1fr);
    gap: 30px;
    padding-top: 34px;
  }

  .home-aside {
    position: static;
  }

  .aside-panel {
    padding: 20px 18px;
    border-radius: var(--r-xl);
  }

  /* 手机上侧栏不该占掉一屏：数字收起，目录改成横排胶囊 */

  .aside-stats {
    display: none;
  }

  /* 访问次数那一行已随 2026-10-22 的改动搬到页脚（.footer-visits），
   * 手机端这里不再有需要贴左的小字。 */

  .aside-index {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 16px;
    padding-top: 14px;
  }

  .aside-index__title {
    width: 100%;
    margin-bottom: 0;
  }

  .aside-index__item {
    flex: 0 0 auto;
    gap: 8px;
    padding: 6px 12px;
    border: 1px solid var(--line);
    border-radius: 999px;
    font-size: 13px;
  }

  .aside-index__count {
    display: none;
  }
}

@media (max-width: 834px) {
  .wrap {
    padding: 0 20px;
  }

  .hero {
    padding: 54px 0 84px;
  }

  .hero::after {
    height: 46px;
  }

  .cat-section + .cat-section {
    margin-top: 40px;
  }

  .tool-grid {
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  }

  /* 标题那一行的图标与文字在窄屏上挨太近，说明文字改到下面另起一行 */
  .tool-lead {
    margin-left: 0;
    margin-top: 14px;
  }

  .tool-page-head {
    padding: 24px 0 26px;
  }
}

@media (max-width: 640px) {
  .wrap {
    padding: 0 16px;
  }

  .header-inner {
    height: 50px;
    gap: 12px;
  }

  /* ⚠️ 站名别在 640 一刀藏掉：实测 320px 下「印 + 站名(113) + 导航 + 主题开关」
     合计 296px，连 390 都空着 250px 没处放。藏了之后顶栏只剩一个图标和右边两个
     控件，中间一大段空白 —— 减法要用对地方。
     真正装不下的是 iPhone SE 一代那种 320 档（296 只剩 24px 余量，换个更长的站名
     就翻车），所以只在那里藏；360（最常见的 Android 窄档）以上一律显示。
     规则见文末 `max-width: 359px` 那个块。 */

  /* 同理补回引题留下的空（移动端那行是 22px 高 + 18px 下边距），
   * 但不照搬 40px 全额 —— 手机首屏寸土寸金，补一半就够。 */
  .hero {
    padding: 60px 0 72px;
  }

  .hero-sub {
    margin-top: 16px;
  }

  .tool-grid {
    grid-template-columns: 1fr;
  }

  .tool-card {
    padding: 18px;
  }

  /* 分类的说明文字整行让位给标题，否则标题会被挤成两行 */
  .cat-desc {
    display: none;
  }

  .cat-title {
    font-size: 19px;
  }

  main {
    padding-bottom: 56px;
  }

  .site-footer {
    padding: 34px 0;
  }

  .calc-keys {
    grid-template-columns: repeat(4, 1fr);
  }

  /* 比例表的两列输入框在窄屏收紧，给险种名留出空间 */
  .rate-row {
    grid-template-columns: 1fr 62px 62px;
    gap: 6px;
  }

  .kv-row {
    grid-template-columns: 1fr;
    gap: 3px;
  }
}

@media (max-width: 420px) {
  .hero h1 {
    font-size: 30px;
  }

  .tool-page-head h1 {
    font-size: 22px;
  }

  .btn {
    padding: 9px 16px;
    font-size: 13.5px;
  }
}

/* ============ 13. 独立页面入口（已移除，编号保留） ============ */

/* 这里原先是 `.nav-link--extra` —— 给导航末尾的「句子」入口加一道竖细线，
 * 免得它看起来像第六个分类。
 *
 * 「句子」后来并进「生活」成了一个正经工具（首页卡片、导航下拉、搜索都能
 * 找到它），顶栏不再需要那个位置，这族样式也就没有使用者了，故删除。
 * 编号特意**不重排**：MEMORY 与历史日志里是按「site.css 第 N 节」指代的，
 * 一重排这些引用就全对不上号了。 */

/* ============ 12. Linux 命令速查 ============
 *
 * 这个工具的组件比其它工具多一层：除了「输入框 + 结果」，还有
 * 可点击的结果列表、风险徽标与带复制按钮的案例块。
 * 颜色一律走变量，因此深色模式无需额外处理。
 */

/* 分组筛选与场景按钮：一排可换行的小胶囊 */
.cmd-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

.cmd-label {
  margin: 18px 0 9px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
}

.chip {
  padding: 6px 13px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font-family: inherit;
  font-size: 12.5px;
  cursor: pointer;
  transition: background 0.18s var(--ease), border-color 0.18s var(--ease),
    color 0.18s var(--ease);
}

.chip:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* 选中的那一枚填成反差色（深底亮字），而不是填一块分类色。
 * 分类色在这套视觉里只出现在小面积上（图标块、标题左侧那一道）；
 * 一排筛选条里填一块饱和的色，是整页最重的视觉噪音。
 * ⚠️ 深色下必须用 --hud 而不是 --ink —— 见 :root 里那段说明。 */
.chip.is-on {
  background: var(--hud);
  border-color: var(--hud);
  color: var(--hud-ink);
}

.chip--accent {
  border-color: color-mix(in srgb, var(--accent) 42%, var(--line));
  color: var(--accent);
}

/* 场景命中：一行「想问：xxx」加若干可点的命令 */
.cmd-scene {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
  padding: 12px 14px;
  border: 1px dashed color-mix(in srgb, var(--accent) 36%, var(--line));
  border-radius: var(--r-md);
  background: var(--accent-soft);
}

.cmd-scene-label {
  font-size: 12.5px;
  color: var(--ink-2);
}

/* 结果列表 */
.cmd-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 520px;
  overflow-y: auto;
}

.cmd-item {
  display: grid;
  grid-template-columns: auto auto 1fr auto;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 13px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  background: var(--surface);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.18s var(--ease), background 0.18s var(--ease);
}

.cmd-item:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* 选中项左侧那道 3px 色条用 inset 阴影画，而不是 border-left ——
 * 换边框会让整行内容横向跳 3px，inset 阴影不占布局。 */
.cmd-item.is-active {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: inset 3px 0 0 var(--accent);
}

.cmd-name {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  /* 不换行。列表是四列网格（名称 / 分组 / 一句话说明 / 风险徽标），
   * 名称一旦折成两行，整行高度就会参差不齐。
   * 「SQL 常用语句」复用这套样式后，名称里出现了中文长名（如「查看连接与当前进程」），
   * 更需要这条约束 —— 说明那一列本来就是 1fr + 省略号，会先让位。 */
  white-space: nowrap;
}

.cmd-group {
  padding: 2px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 5%, transparent);
  font-size: 11.5px;
  color: var(--muted);
  white-space: nowrap;
}

.cmd-summary {
  overflow: hidden;
  font-size: 12.5px;
  color: var(--muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 风险徽标：只读 绿、会改数据 琥珀、危险 红 */
.badge {
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
}

.badge--safe {
  background: var(--ok-soft);
  color: var(--ok);
}

.badge--caution {
  background: var(--warn-soft);
  color: var(--warn);
}

.badge--danger {
  background: var(--err-soft);
  color: var(--err);
}

/* 详情 */
.cmd-detail-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}

.cmd-detail-name {
  font-family: var(--mono);
  font-size: 15px;
  font-weight: 600;
  color: var(--accent);
}

.cmd-detail-summary {
  margin-bottom: 8px;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
}

.cmd-detail-text {
  margin-bottom: 14px;
  font-size: 13.5px;
  line-height: 1.75;
  color: var(--ink-2);
}

.cmd-sub {
  margin: 18px 0 9px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
}

/* 案例：一条命令配一句说明，命令可一键复制 */
.cmd-examples {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.cmd-example {
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface-2);
  overflow: hidden;
}

.cmd-example-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 11px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--surface);
}

.cmd-example-code {
  flex: 1;
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--ink);
  word-break: break-all;
}

.cmd-example-note {
  margin: 0;
  padding: 8px 11px;
  font-size: 12px;
  color: var(--muted);
}

/* 宽屏时结果与详情并排：左窄右宽，左列表自身可滚动 */
@media (min-width: 900px) {
  .cmd-split {
    display: grid;
    grid-template-columns: minmax(300px, 400px) 1fr;
    align-items: start;
    gap: 18px;
  }
}

@media (max-width: 899px) {
  .cmd-split {
    display: grid;
    gap: 18px;
  }

  .cmd-item {
    grid-template-columns: auto 1fr auto;
  }

  .cmd-summary {
    display: none;
  }
}

/* ============ 14. 「工具汇总」下拉 ============ */

/* 五个分类收进这里，触发器长得和普通导航项一样，可它是个 <button>。
 * 按钮不继承字体，不显式重置就会掉成系统按钮字体，一眼看出违和。
 * ⚠️ 这里**不能**写 font-size: inherit —— 那会绕过 .nav-link 的 14px，
 *    直接继承 body 的 16px，触发器会比品牌标识的字大一档。
 *    （这条在「首页」链接还在时写的是「比旁边导航项大一档」；那条链接删掉之后
 *     顶栏只剩这一项，参照物换成品牌，约束本身没变。） */
.nav-drop {
  position: relative;
  display: flex;
  align-items: center;
}

.nav-link--drop {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: inherit;
  line-height: inherit;
  border: 0;
  background: none;
  cursor: pointer;
}

.nav-caret {
  display: inline-flex;
  transition: transform 0.2s var(--ease);
}

.nav-caret svg {
  width: 13px;
  height: 13px;
}

/* 展开时箭头翻过来，给一个「它确实开了」的即时反馈 */
.nav-drop:hover .nav-caret,
.nav-drop:focus-within .nav-caret,
.nav-drop.is-open .nav-caret {
  transform: rotate(180deg);
}

/* 面板也做毛玻璃，但底色压到 88% 不透明 ——
 * 它是嵌在已经带模糊的顶栏里的，模糊再叠一层在个别浏览器上会不生效；
 * 底色留 12% 的透光只是加一点"玻璃"的感觉，就算模糊没作用也照样好看。 */
.nav-menu {
  position: absolute;
  top: calc(100% + 10px);
  /* 面板朝左展开：触发器现在靠右（.nav 是 justify-content:flex-end），
   * 再按 left:0 往右长就会顶出视口 —— 390px 下实测溢出 18px。
   * 锚右之后面板从触发器右边缘往左长，任何宽度都收得回来。 */
  right: 0;
  z-index: 60;
  min-width: 200px;
  padding: 8px;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  border: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 0.2s var(--ease), transform 0.2s var(--ease), visibility 0.2s;
}

/* 触发器和面板之间隔了 10px。鼠标从触发器移向面板时要跨过这段空隙，
 * 而 .nav-drop 的盒子只有触发器那么高 —— 少了这块透明「桥」，
 * 指针一进空隙就不再 :hover，菜单会在到达之前先关上。
 * ⚠️ 这个 -10px 与上面的 calc(100% + 10px) 是一对，改一个必须改另一个。 */
.nav-menu::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -10px;
  height: 10px;
}

/* 三种展开来源：鼠标悬停、键盘聚焦、触屏点击（脚本加的 .is-open）。
 * 写在一起，避免两边样式漂移。 */
.nav-drop:hover .nav-menu,
.nav-drop:focus-within .nav-menu,
.nav-drop.is-open .nav-menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.nav-menu__item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  color: var(--ink-2);
  font-size: 14px;
  letter-spacing: -0.01em;
  white-space: nowrap;
  transition: background-color 0.18s var(--ease), color 0.18s var(--ease);
}

.nav-menu__item:hover {
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  color: var(--ink);
  text-decoration: none;
}

/* 菜单里的当前分类用「一层中性灰底 + 更实的字」表达，而不是填一块品牌蓝。
 * 顶栏上只留一处蓝色（品牌标识），再填一块蓝，两处就互相抵消了 ——
 * 这是整站配色「只用一处有彩色」那条规则在顶栏上的落法。 */
.nav-menu__item.is-active {
  color: var(--ink);
  background: color-mix(in srgb, var(--ink) 7%, transparent);
  font-weight: 600;
}

/* 小圆点取该分类的主题色（由内联 --dot 给），与首页卡片角标同源 */
.nav-menu__dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--dot, var(--brand));
}

.nav-menu__name {
  flex: 1;
}

.nav-menu__count {
  color: var(--muted-2);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* ============ 15. 决策类工具（吃什么 / 送什么 / 叫什么） ============

   这一节的选择器大多是成对写的：`.eat-x, .gift-x`。
   两个工具在视觉上本来就是同一类东西 —— 都是「回答几个问题、拿一个决定」，
   共用同一套间距、标签与结论卡样式是对的。

   为什么不干脆让送礼直接穿 `.eat-*` 的衣服：类名得说得清自己是什么，
   `.gift-verdict` 上写着 eat 会让下一次改动的人多看两眼。
   为什么不复制一份规则：那样以后调间距就得记得改两处，一定会漏一处。
   并列写是这里最不坏的选择。

   `.eat-*` 独有的部分（场景卡、随机选餐舞台、食材补充输入）送礼没有，保持单写。 */

/* 场景二选一：整块可点的两张卡，不是两个小按钮 —— 这是整个工具
 * 最先要用户做的一个决定，值得占这么大地方。 */
.eat-scenes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
  margin-top: 16px;
}

.eat-scene {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 22px;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
  font-family: var(--font);
  cursor: pointer;
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease),
    transform 0.2s var(--ease);
}

.eat-scene:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

.eat-scene__title {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--ink);
}

.eat-scene__desc {
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ink-2);
}

.eat-scene__meta {
  font-size: 12px;
  color: var(--muted-2);
}

/* 顶部两个场景之间的快速切换 */
.eat-scenebar {
  display: flex;
  gap: 6px;
  margin-bottom: 14px;
}

.eat-scenetab {
  padding: 7px 16px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--muted);
  font-family: var(--font);
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.18s var(--ease), border-color 0.18s var(--ease),
    color 0.18s var(--ease);
}

.eat-scenetab:hover {
  border-color: var(--muted-2);
}

.eat-scenetab.is-on {
  background: var(--accent-soft);
  border-color: transparent;
  color: var(--accent);
  font-weight: 600;
}

/* 步骤指示：走过的实心、当前的那个拉长，位置一眼看得出 */
.eat-progress,
.gift-progress {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 14px;
  padding: 0 2px;
}

.eat-progress__dot,
.gift-progress__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--line);
  transition: width 0.2s var(--ease), background-color 0.2s var(--ease);
}

.eat-progress__dot.is-done,
.gift-progress__dot.is-done {
  background: var(--accent);
}

.eat-progress__dot.is-now,
.gift-progress__dot.is-now {
  width: 20px;
  border-radius: 999px;
  background: var(--accent);
}

.eat-progress__text,
.gift-progress__text {
  margin-left: 6px;
  font-size: 12px;
  color: var(--muted-2);
  font-variant-numeric: tabular-nums;
}

.eat-qbody,
.gift-qbody {
  margin-top: 8px;
}

.eat-group + .eat-group,
.kin-group + .kin-group,
.eat-field + .eat-field,
.gift-field + .gift-field {
  margin-top: 20px;
}

.eat-group__title,
.kin-group__title,
.eat-field__title,
.gift-field__title {
  margin-bottom: 9px;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--muted);
}

/* 多选小标签。选中态用分类主题色的淡底 + 主题色字，
 * 与首页卡片角标用的是同一套色，不另起一套。 */
.eat-chips,
.gift-chips,
.kin-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.eat-chip,
.gift-chip,
.kin-chip {
  padding: 7px 15px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font-family: var(--font);
  font-size: 13.5px;
  cursor: pointer;
  transition: background-color 0.18s var(--ease), border-color 0.18s var(--ease),
    color 0.18s var(--ease);
}

.eat-chip:hover,
.gift-chip:hover,
.kin-chip:hover {
  border-color: var(--muted-2);
}

.eat-chip.is-on,
.gift-chip.is-on,
.kin-chip.is-on {
  background: var(--accent-soft);
  border-color: transparent;
  color: var(--accent);
  font-weight: 600;
}

/* 预算、时间这些选项有 4-5 个，窄屏放不下一行，必须允许换行。
 * 送礼那边的「送给谁」有 11 项、「对方喜欢什么」有 20 项，更需要。 */
.eat-field .segmented,
.gift-field .segmented {
  display: flex;
  flex-wrap: wrap;
}

.eat-customrow {
  display: flex;
  align-items: center;
  gap: 8px;
}

.eat-customrow .input {
  flex: 1;
  min-width: 0;
}

.eat-customlog {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 8px;
}

.eat-customlog__line {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--muted);
}

.eat-customlog__line.is-ok {
  color: var(--ok);
}

.eat-customlog__line.is-miss {
  color: var(--warn);
}

.eat-optional,
.gift-optional {
  font-size: 12px;
  color: var(--muted-2);
}

.eat-nav,
.gift-nav,
.kin-nav {
  margin-top: 18px;
}

/* 结论卡。顶部一条主题色粗边，让它明显区别于普通面板。
 * 圆角给到最大的那一档：它是这一页的主角，理应比周围的面板更"松弛"。 */
.eat-verdict,
.gift-verdict,
.kin-verdict {
  padding: 26px 26px 22px;
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--ink) 6%, transparent);
  border-top: 3px solid var(--accent);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-sm);
}

.eat-verdict.is-decided,
.gift-verdict.is-decided,
.kin-verdict.is-decided {
  border-top-color: var(--ok);
}

/* 「对方不要礼物」时换的那条路。用 warn 而不是 ok：
 * 它不是一次成功的推荐，是一次改道，颜色上不该跟"搞定"长得一样。
 * 写在 is-decided 之后，两条同时成立时以它为准。 */
.gift-verdict.is-nogift {
  border-top-color: var(--warn);
}

.eat-verdict__label,
.gift-verdict__label,
.kin-verdict__label {
  margin: 0 0 8px;
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--muted-2);
}

/* 结论标题是整页最大的字：它就是答案本身，不该和面板标题一个分量 */
.eat-verdict__title,
.gift-verdict__title,
.kin-verdict__title {
  margin: 0 0 8px;
  font-size: 30px;
  font-weight: 700;
  letter-spacing: -0.032em;
  line-height: 1.15;
  color: var(--ink);
}

.eat-verdict__sub,
.gift-verdict__sub,
.kin-verdict__sub {
  margin: 0 0 22px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
}

.eat-block-title,
.gift-block-title,
.kin-block-title {
  margin: 0 0 11px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
}

.eat-reasons,
.gift-reasons,
.kin-reasons {
  display: flex;
  flex-direction: column;
  gap: 9px;
  margin-bottom: 20px;
}

.eat-reason,
.gift-reason,
.kin-reason {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 13.5px;
  line-height: 1.65;
  color: var(--ink-2);
}

.eat-reason__dot,
.gift-reason__dot,
.kin-reason__dot {
  flex: none;
  width: 6px;
  height: 6px;
  margin-top: 8px;
  border-radius: 50%;
  background: var(--accent);
}

.eat-action,
.gift-action {
  margin-top: 20px;
  padding: 16px 18px;
  background: var(--surface-2);
  border-radius: var(--r-md);
}

.eat-action__text,
.gift-action__text {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.75;
  color: var(--ink-2);
}

/* 备选刻意做得比主结论弱：虚线框 + 小字。
 * 这个工具要的是「一个决定」，备选只要在那儿就够了，不能跟主结论抢。 */
.eat-alt,
.gift-alt {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  margin-top: 16px;
  padding: 12px 15px;
  border: 1px dashed var(--line);
  border-radius: var(--r-md);
  font-size: 13px;
}

.eat-alt__label,
.gift-alt__label {
  flex: none;
  padding: 2px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 5%, transparent);
  color: var(--muted-2);
  font-size: 11.5px;
}

.eat-alt__title,
.gift-alt__title {
  font-weight: 600;
  color: var(--ink-2);
}

.eat-alt__note,
.gift-alt__note {
  color: var(--muted);
}

.eat-notes,
.gift-notes,
.kin-notes {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 16px;
}

.eat-notes__line,
.gift-notes__line,
.kin-notes__line {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.65;
  color: var(--muted-2);
}

/* 随机选餐的那块「舞台」。
 *
 * 快闪只换文字、不动背景：背景一亮一暗才是真正的频闪，既晃眼又对光敏人群不友好，
 * 只换文字则完全没有这个问题。
 * 刻意定高（min-height）：菜名长短不一，不定高的话每闪一下整块都会跟着抖一下。 */
.eat-roll {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 236px;
  margin-top: 16px;
  padding: 32px 26px 28px;
  text-align: center;
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--ink) 6%, transparent);
  border-top: 3px solid var(--accent);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-sm);
}

.eat-roll.is-rolling {
  border-top-color: var(--warn);
}

.eat-roll.is-locked {
  border-top-color: var(--ok);
}

.eat-roll__label {
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--muted-2);
}

.eat-roll__type {
  padding: 3px 12px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
}

.eat-roll.is-locked .eat-roll__type {
  background: var(--ok-soft);
  color: var(--ok);
}

.eat-roll__name {
  min-height: 48px;
  font-size: 38px;
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.2;
  color: var(--ink);
}

.eat-roll__name.is-idle {
  color: var(--muted-2);
}

.eat-roll__note {
  margin: 0;
  min-height: 22px;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--muted);
}

/* 锁定时给一下「落定」的收束：这是一次性的短动画，不是循环，也不是频闪。
 * 作用是把视线按在停下来的那道菜上 —— 快闪之后视线还在飘，这一下帮它刹住车。
 * 它不改变显示的是哪一道，只是把已经定下来的那道「亮一下」。 */
@keyframes eat-roll-settle {
  from {
    opacity: 0.4;
    transform: scale(1.06);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

.eat-roll.is-locked .eat-roll__name {
  animation: eat-roll-settle 0.24s ease-out both;
}

@media (prefers-reduced-motion: reduce) {
  .eat-roll.is-locked .eat-roll__name {
    animation: none;
  }
}

/* 锁定后才出现的说明区：与上面对齐成一段，不跟菜名抢中心 */
.eat-roll__why {
  width: 100%;
  margin-top: 20px;
  padding-top: 18px;
  border-top: 1px solid var(--line-soft);
  text-align: left;
}

/* ---------- 「叫什么」独有：一层层搭出关系链 ----------
 * 这个工具不像前两个那样有「问卷 → 一次出结论」的结构，
 * 它的交互是**边点边看**：每点一步，顶上那个大字立刻换掉。
 * 所以它多出来几样东西：顶部大字、路径面包屑、候选选择、通用建议块。 */

/* 关系按钮是两行的：上面「爸爸」，下面「外祖母」。
 * 下面那行解决的是「找姥姥得点两次妈妈，可我怎么知道要点第二次」——
 * 走到母亲那一层，按钮上直接写着外祖母，不用推理。
 * 所以这行小字不能省成 tooltip：手机上根本没有 hover。 */
.kin-chip {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 8px 16px;
  line-height: 1.3;
}

.kin-chip__to {
  font-size: 11px;
  font-weight: 400;
  color: var(--muted-2);
}

/* 「这位祖先的子女不止一个」—— 点下去不是直接给答案，而是让你认一位。
 * 用主题色描边，和普通按钮区分开：它不是「再往下走」，是「先定下来是谁」。 */
.kin-cands {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 2px;
}

.kin-cand {
  padding: 10px 20px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font-family: var(--font);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.18s var(--ease), border-color 0.18s var(--ease);
}

.kin-cand:hover {
  border-color: var(--muted-2);
}

/* 顶部大字：现在算到谁了。字号给大，是因为它就是答案本身。 */
.kin-head {
  text-align: center;
  padding: 6px 0 0;
}

.kin-head__label {
  margin: 0;
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--muted-2);
}

.kin-head__name {
  margin: 8px 0 0;
  font-size: 38px;
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.15;
  color: var(--brand);
}

/* 路径面包屑：我 › 爸爸 › 哥哥。
 * 每一段都是按钮 —— 点它就退回到那一步重新走，
 * 这是这个工具唯一的「撤销」手段，比一个单单的「上一步」好用得多。 */
.kin-path {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px 2px;
  margin: 16px 0 2px;
}

.kin-path__node {
  appearance: none;
  border: 0;
  background: transparent;
  padding: 4px 9px;
  border-radius: 999px;
  font: inherit;
  font-size: 14px;
  color: var(--ink-2);
  cursor: pointer;
  transition: background-color 0.18s var(--ease), color 0.18s var(--ease);
}

.kin-path__node:hover {
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  color: var(--ink);
}

/* 当前所在的那一段：高亮、且不可点（点了等于原地不动） */
.kin-path__node.is-now {
  color: var(--brand);
  font-weight: 600;
  cursor: default;
}

.kin-path__node.is-now:hover {
  background: transparent;
}

.kin-path__sep {
  color: var(--line);
  font-size: 14px;
  user-select: none;
}

/* 关系按钮按「长辈 / 同辈 / 晚辈」分三组竖排 */
.kin-groups {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* 「拿不准的时候」：与具体关系无关的通用建议。
 * 真实场景里有一半的尴尬不是算不出来，是算出来了也不好意思开口，
 * 所以这一块和结论卡一样重要，给的是底色而不是边角。 */
.kin-tips {
  margin-top: 18px;
  padding: 16px 18px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
  background: var(--surface-2);
}

.kin-tips__line {
  margin: 0 0 9px;
  font-size: 13.5px;
  line-height: 1.7;
  color: var(--muted);
}

.kin-tips__line:last-child {
  margin-bottom: 0;
}

/* 走不通、或者只能确定到某一步：用 warn 而不是 accent。
 * 它不是一次成功的换算，颜色上不该跟"算出来了"长得一样。 */
.kin-verdict.is-warn {
  border-top-color: var(--warn);
}

.kin-verdict.is-warn .kin-verdict__title {
  color: var(--warn);
}

@media (max-width: 640px) {
  .eat-verdict,
  .gift-verdict,
  .kin-verdict {
    padding: 20px 18px 16px;
  }

  .eat-verdict__title,
  .gift-verdict__title,
  .kin-verdict__title {
    font-size: 24px;
  }

  .eat-roll {
    min-height: 206px;
    padding: 24px 18px 20px;
  }

  .eat-roll__name {
    min-height: 40px;
    font-size: 29px;
  }
}

/* ============ 16. 番茄时钟 ============

   这个工具的主体是「一个进度环 + 一个数字」，和站里其它「左边输入、右边结果」
   的工具形状完全不同，所以单开一节，不去勉强套前面那些面板样式。

   阶段配色（专注 / 短休息 / 长休息）挂在 `.pomo[data-phase="…"]` 上，
   用 `--pomo` / `--pomo-soft` / `--pomo-glow` 三个**局部变量**往下传。
   ⚠️ 它们刻意不写进 `:root` —— 只在番茄时钟这一页有意义，写进根变量会跟着
   站内全部页面一起变。

   ⚠️ `.pomo-ring__bar` 的 `stroke-dashoffset` 由脚本每 250ms 写一次，
   这里的 transition 是用来把两次写入之间的跳变抹平的。删掉它，指针就会
   一格一格地跳 —— 看得出来，而且是那种"说不清哪里别扭"的不顺滑。 */

.pomo {
  display: flex;
  flex-direction: column;
  gap: 18px;
  width: 100%;
  max-width: 560px;
  /* 工作区是 grid，网格项默认 stretch；配 auto 外边距才会真正居中 */
  margin: 0 auto;
}

/* —— 阶段配色 —— */
.pomo[data-phase="focus"] {
  --pomo: #ff3b30;
  --pomo-soft: #fff0ef;
  --pomo-glow: rgba(255, 59, 48, 0.34);
}

.pomo[data-phase="short"] {
  --pomo: #0f9d58;
  --pomo-soft: #eefaf1;
  --pomo-glow: rgba(15, 157, 88, 0.34);
}

.pomo[data-phase="long"] {
  --pomo: #0071e3;
  --pomo-soft: #eaf3ff;
  --pomo-glow: rgba(0, 113, 227, 0.34);
}

.pomo-phasebar {
  display: flex;
  justify-content: center;
  margin-bottom: 20px;
}

/* —— 进度环 —— */
.pomo-ring-wrap {
  position: relative;
  width: min(300px, 74vw);
  margin: 0 auto;
}

.pomo-ring {
  display: block;
  width: 100%;
  height: auto;
}

.pomo-ring__track {
  fill: none;
  stroke: var(--line-soft);
  stroke-width: 10;
}

.pomo-ring__bar {
  fill: none;
  stroke: var(--pomo);
  stroke-width: 10;
  stroke-linecap: round;
  transition: stroke-dashoffset 0.35s linear;
}

/* 跑着的时候环上带一点光：屏幕边上扫一眼就知道它还在走 */
.pomo[data-running="1"] .pomo-ring__bar {
  filter: drop-shadow(0 0 7px var(--pomo-glow));
}

/* 表盘文字盖在环中间。pointer-events 关掉，免得挡住下面那圈环的点击区 */
.pomo-face {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 0 16%;
  text-align: center;
  pointer-events: none;
}

.pomo-face__phase {
  padding: 3px 11px;
  border-radius: 999px;
  background: var(--pomo-soft);
  color: var(--pomo);
  font-size: 12.4px;
  font-weight: 600;
  letter-spacing: 0.14em;
}

.pomo-face__clock {
  font-family: var(--mono);
  /* 上限压到 48px：「1:00:00」这种七位数字比「25:00」宽得多，
   * 字号再大一点就会顶出环外（时长的上限是 180 分钟，真能出现） */
  font-size: clamp(34px, 10vw, 48px);
  font-weight: 600;
  line-height: 1.06;
  letter-spacing: -0.02em;
  color: var(--pomo);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.pomo-face__tip {
  font-size: 12.2px;
  color: var(--muted);
}

.pomo-face__task {
  max-width: 100%;
  font-size: 12.8px;
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* —— 本轮到长休息的进度点 —— */
.pomo-dots {
  display: flex;
  justify-content: center;
  gap: 9px;
  margin-top: 20px;
}

.pomo-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1.5px solid var(--line);
  background: transparent;
  transition: background-color 0.25s var(--ease), border-color 0.25s var(--ease),
    transform 0.25s var(--ease);
}

.pomo-dot.is-done {
  background: var(--pomo);
  border-color: var(--pomo);
  transform: scale(1.12);
}

.pomo-dots__label {
  margin-top: 10px;
  text-align: center;
  font-size: 12.4px;
  color: var(--muted);
}

/* —— 控制按钮 —— */
.pomo-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 22px;
}

.pomo-btn-main {
  min-width: 136px;
}

.pomo-task {
  margin-top: 22px;
}

/* 全站把 p 的 margin 清成了 0，几条提示挨在一起会糊成一坨，这里补回间距 */
.pomo .hint + .hint {
  margin-top: 9px;
}

/* 统计卡跟随阶段色，而不是站内那个品牌蓝 —— 这一页的主角是番茄，不是分类色 */
.pomo .stat.is-accent {
  background: var(--pomo-soft);
  border-color: color-mix(in srgb, var(--pomo) 24%, transparent);
}

.pomo .stat.is-accent .stat-value {
  color: var(--pomo);
}

.pomo-hint-inline {
  align-self: center;
}

/* —— 设置（折叠） —— */
.pomo-settings {
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--ink) 6%, transparent);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.pomo-settings > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 16px 20px;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink-2);
  cursor: pointer;
  list-style: none;
  user-select: none;
}

.pomo-settings > summary::-webkit-details-marker {
  display: none;
}

/* 各家浏览器自带的三角长得都不一样，自己画一个；展开时转半圈 */
.pomo-settings > summary::after {
  content: "";
  margin-left: auto;
  width: 7px;
  height: 7px;
  border-right: 1.7px solid var(--muted-2);
  border-bottom: 1.7px solid var(--muted-2);
  transform: rotate(45deg) translate(-1px, -1px);
  transition: transform 0.2s var(--ease);
}

.pomo-settings[open] > summary::after {
  transform: rotate(-135deg) translate(-1px, -1px);
}

.pomo-settings__body {
  padding: 20px;
  border-top: 1px solid var(--line-soft);
}

.pomo-checks {
  margin-top: 16px;
  flex-direction: column;
  align-items: flex-start;
}

/* 深色下把三个阶段色换成浅一档的，压深的底色上用原色会发闷。
 * 这三个变量挂在 .pomo 自己的根节点上、刻意不写 :root（见第 16 节说明），
 * 所以选择器只能从 :root[data-theme] 往下走 —— 与其他深色规则同一个开关。 */
:root[data-theme="dark"] .pomo[data-phase="focus"] {
  --pomo: #ff6961;
  --pomo-soft: rgba(255, 59, 48, 0.16);
  --pomo-glow: rgba(255, 105, 97, 0.42);
}

:root[data-theme="dark"] .pomo[data-phase="short"] {
  --pomo: #30d158;
  --pomo-soft: rgba(48, 209, 88, 0.16);
  --pomo-glow: rgba(48, 209, 88, 0.42);
}

:root[data-theme="dark"] .pomo[data-phase="long"] {
  --pomo: #2997ff;
  --pomo-soft: rgba(41, 151, 255, 0.16);
  --pomo-glow: rgba(41, 151, 255, 0.42);
}

@media (max-width: 640px) {
  .pomo-ring-wrap {
    width: min(258px, 78vw);
  }

  .pomo-face__clock {
    font-size: 34px;
  }

  .pomo-actions {
    gap: 10px;
  }

  .pomo-btn-main {
    min-width: 116px;
  }
}

/* 关掉动效时一并去掉环的补间：不然 250ms 一次的写入会变成一顿一顿的 */
@media (prefers-reduced-motion: reduce) {
  .pomo-ring__bar,
  .pomo-dot,
  .pomo-settings > summary::after {
    transition: none;
  }
}

/* ============ 17. 端口查询 ============

   主角是那张「结论卡」：同一个端口在两台机器上可能是完全不同的东西，所以
   卡片要先说清「这是谁」（服务名）再说「什么意思」，最后才补区间与来源。

   配色只有两种状态，都取自站点语义色，不另立一套：
     命中 → 绿（--ok / --ok-soft）
     查不到 → 琥珀（--warn / --warn-soft）
   卡片把它们落到局部的 `--pc` / `--pc-soft` 上。⚠️ 与番茄时钟一样刻意不写进
   `:root` —— 只有这一页用得上，写进根变量会跟着全站 92 个页面一起变。
   深色模式下 --ok / --warn 这套语义色本来就有一套覆写，这里不用再管。 */

.port-query .port-proto {
  margin-top: 16px;
}

.port-query .btn-row {
  margin-top: 14px;
}

.port-query .hint {
  margin-top: 12px;
}

.port-proto {
  display: flex;
  align-items: center;
  gap: 12px;
}

.port-proto__label {
  font-size: 12.8px;
  font-weight: 600;
  color: var(--muted);
}

/* —— 结论卡 —— */
.port-card {
  --pc: var(--muted);
  --pc-soft: var(--surface-2);
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 20px;
  border: 1px solid color-mix(in srgb, var(--ink) 6%, transparent);
  /* 左侧那道色条是「一眼看出查没查到」的关键，比整块底色克制得多 */
  border-left: 3px solid var(--pc);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.port-card.is-hit {
  --pc: var(--ok);
  --pc-soft: var(--ok-soft);
}

.port-card.is-miss {
  --pc: var(--warn);
  --pc-soft: var(--warn-soft);
}

.port-card__top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.port-card__proto {
  padding: 2px 9px;
  border-radius: 999px;
  background: var(--pc-soft);
  color: var(--pc);
  font-family: var(--mono);
  font-size: 11.4px;
  font-weight: 700;
  letter-spacing: 0.1em;
}

.port-card__port {
  font-family: var(--mono);
  font-size: 20px;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* 徽标只描述「这个服务名是哪来的」，所以三态各有固定颜色，
   不跟随卡片底色 —— 绿色的命中卡里也可能挂着琥珀色的「厂商约定」 */
.port-card__badge {
  margin-left: auto;
  padding: 3px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--muted);
  font-size: 11.4px;
  font-weight: 600;
}

.port-card__badge.is-iana {
  border-color: transparent;
  background: var(--ok-soft);
  color: var(--ok);
}

.port-card__badge.is-defacto {
  border-color: transparent;
  background: var(--warn-soft);
  color: var(--warn);
}

/* 「未收录」用虚线的灰徽标：它不是一种登记来源，只是「没有」这件事的标记，
   和上面两种实底徽标混在一起会让人以为也是某类来源 */
.port-card__badge.is-none {
  border-style: dashed;
  border-color: var(--muted-2);
  background: transparent;
  color: var(--muted);
}

.port-card__svc {
  font-family: var(--mono);
  font-size: clamp(20px, 5vw, 26px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.2;
  color: var(--pc);
  word-break: break-all;
}

.port-card__name {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.012em;
  color: var(--ink);
}

.port-card__desc {
  font-size: 13.5px;
  line-height: 1.78;
  color: var(--ink-2);
}

/* —— 安全提示 —— */
.port-risk {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  border-radius: var(--r-md);
  background: var(--warn-soft);
}

.port-risk__tag {
  flex: none;
  padding: 2px 9px;
  border-radius: 999px;
  background: var(--surface);
  color: var(--warn);
  font-size: 11.4px;
  font-weight: 700;
}

.port-risk__text {
  font-size: 12.8px;
  line-height: 1.72;
  color: var(--ink-2);
}

/* —— 查不到时的自查命令 —— */
.port-cmds {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px;
  border: 1px dashed var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
}

.port-cmds__title {
  font-size: 12.4px;
  font-weight: 600;
  color: var(--ink-2);
}

/* 命令可能比卡片宽，让它自己横向滚，别把整页撑开 */
.port-cmd {
  display: block;
  padding: 8px 11px;
  border: 1px solid var(--line-soft);
  border-radius: 8px;
  background: var(--surface);
  font-family: var(--mono);
  font-size: 12.1px;
  color: var(--ink-2);
  white-space: pre;
  overflow-x: auto;
}

/* —— 表格里的端口按钮 —— */
.port-call {
  padding: 3px 10px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
  transition: background-color 0.18s var(--ease), border-color 0.18s var(--ease);
}

.port-call:hover {
  border-color: color-mix(in srgb, var(--accent) 34%, transparent);
  background: var(--accent-soft);
}

.port-call:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* 「这个端口有暴露风险」的小标记，塞在表格说明列的前面 */
.port-flag {
  display: inline-block;
  margin-right: 7px;
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--warn-soft);
  color: var(--warn);
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}

.port-common .hint {
  margin-top: 12px;
}

.port-howto {
  display: flex;
  flex-direction: column;
  gap: 11px;
  margin-top: 18px;
}

@media (max-width: 640px) {
  .port-card {
    padding: 16px;
  }

  .port-card__badge {
    margin-left: 0;
  }
}

/* ================================================================
 * 第八轮新增工具的样式
 *
 * 一律走 --brand / --ink / --line 这些变量，不写死颜色 ——
 * 深色模式只覆写变量，写死的颜色在深色下必然出问题。
 * 唯一的例外是网线色块：那是**物理颜色**（T568A/B 的线序就是那几种色），
 * 不跟随主题，否则深色模式下认不出线。
 * ================================================================ */

/* ---------- 通用：一点小间距 ---------- */
.ml-8 {
  margin-left: 8px;
}

/* ---------- T568A / T568B 线序色块条 ---------- */
.pin-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 14px;
}

.pin-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  min-width: 68px;
}

/* 色块：背景色由脚本内联写入（取自线序表），这里只管形状 */
.pin-swatch {
  width: 56px;
  height: 30px;
  border-radius: var(--r-sm);
  border: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
}

/* 表格里跟在芯号后面的小色块 */
.pin-swatch--inline {
  display: inline-block;
  width: 18px;
  height: 12px;
  margin-right: 6px;
  border-radius: 4px;
  vertical-align: -1px;
}

.pin-num {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}

.pin-color {
  font-size: 11.5px;
  line-height: 1.3;
  color: var(--muted);
  text-align: center;
}

/* ---------- 文件拖放区（文件识别、证书解析、文件校验和共用一个观感） ---------- */
/* 拖放区做得比一般输入框"厚"：它是个要人把文件拖过来的地方，
 * 面积和留白都得足够大，虚线框也说明"这里可以放东西进去"。 */
.drop-zone,
.ck-drop {
  padding: 32px 20px;
  border: 1px dashed var(--line);
  border-radius: var(--r-lg);
  background: var(--surface-2);
  color: var(--muted);
  text-align: center;
  cursor: pointer;
  transition: border-color 0.2s var(--ease), background-color 0.2s var(--ease),
    color 0.2s var(--ease);
}

.drop-zone:hover,
.drop-zone.is-over,
.ck-drop:hover,
.ck-drop.is-over {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--ink-2);
}

/* 指针拖进来时给个更明确的反馈 */
.drop-zone.is-over,
.ck-drop.is-over {
  border-style: solid;
}

.ck-drop__title {
  font-size: 15.5px;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--ink);
}

.ck-drop__sub {
  margin-top: 7px;
  font-size: 12.5px;
  color: var(--muted);
}

/* 真正的 file input 藏起来 —— 点击事件由上面那块区域代理 */
.ck-file {
  display: none;
}

/* ---------- 读取进度条 ---------- */
.ck-bar {
  height: 8px;
  border-radius: 999px;
  background: var(--line-soft);
  overflow: hidden;
}

.ck-bar__fill {
  width: 0;
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
  transition: width 0.12s linear;
}

.ck-bar__text {
  margin-top: 7px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
}

/* 算法名后面那个「已可构造碰撞」的小徽标 */
.ck-badge {
  margin-left: 8px;
}

/* ---------- 证书卡片抬头 ---------- */
.cert-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

/* ---------- 权限勾选面板 ---------- */
.perm-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 14px;
}

.perm-col {
  flex: 1 1 190px;
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 14px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface-2);
}

.perm-grid__head {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
}

.perm-cell {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--ink-2);
  cursor: pointer;
  user-select: none;
}

/* 特殊位单独一行，视觉上和普通权限位分开 —— 它们的危险等级完全不同 */
.perm-special {
  display: flex;
  flex-direction: column;
  gap: 9px;
  margin-top: 16px;
  padding: 14px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface-2);
}

@media (max-width: 640px) {
  .pin-swatch {
    width: 46px;
    height: 26px;
  }

  .perm-col {
    flex: 1 1 100%;
  }
}

/* ============ 18. 条形码生成 ============

   只有两块新样式：预览区，以及「暂无法生成」时的占位文字。
   条码本体是 SVG，由 barcode.js 拼出来，配色直接写在 SVG 上。

   ⚠️ 预览区**刻意不跟随深色模式**：条码的可读性依赖「深色条 + 浅色底」，
   白条黑底（反色）在不少扫码枪上默认是读不出来的。整个站点只有这一处
   不跟主题走，是有意为之，不是漏改。 */

.barcode-box {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 152px;
  padding: 20px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface-2);
  overflow-x: auto;
}

.barcode-box svg {
  display: block;
  width: auto;
  max-width: 100%;
  height: auto;
  border-radius: 6px;
  background: #fff;
}

.barcode-placeholder {
  font-size: 13px;
  color: var(--muted);
}

/* 提示行的警示态（条码生成里用于「按正确校验位重算并提示」这类情况）。
   基础 .hint 只定义了中性灰，这里补一个语义色变体。 */
.hint--warn {
  color: var(--warn);
}

/* ================================================================
 * 回到顶部 / 回到底部
 *
 * 两个按钮叠成一条竖着的胶囊，固定在右下角，只在页面确实「长到需要滚」的
 * 时候才露面（判定在 chrome.js 的 jumpState）。位置避开底部居中的提示条
 * （.toast）与右侧滚动条。做成毛玻璃是刻意的：它浮在内容上面，
 * 底下是文字而不是色块，模糊一层才不像贴上去的一张贴纸。
 * ================================================================ */

/* 顺滑滚动交给 CSS，JS 那边只赋 scrollTop —— 好处是「减少动态效果」的开关
 * 在本文件里覆盖一处就够，JS 完全不必知道这件事。顺带让导航里的 /#分类
 * 锚点跳转也变成平滑的。 */
html {
  scroll-behavior: smooth;
}

.jump {
  position: fixed;
  right: 24px;
  bottom: 24px;
  /* 高于顶栏（40）与导航下拉（60），低于提示条（90）—— 提示条是瞬时的
   * 操作反馈，它出现时该压在控件上面 */
  z-index: 70;
  display: flex;
  flex-direction: column;
  background: var(--glass);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  border: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
  border-radius: 999px;
  box-shadow: var(--shadow-md);
  overflow: hidden;
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity 0.22s var(--ease), transform 0.22s var(--ease),
    visibility 0.22s;
}

.jump.is-on {
  opacity: 1;
  visibility: visible;
  transform: none;
}

/* 同 .site-header：拿不到模糊就退回不透明底 */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .jump {
    background: var(--surface);
  }
}

.jump__btn {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.18s var(--ease), background-color 0.18s var(--ease);
}

/* 两个按钮之间一条发丝线，让它们读起来是一组而不是两个孤立的圆点 */
.jump__btn + .jump__btn {
  border-top: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
}

.jump__btn svg {
  width: 19px;
  height: 19px;
}

/* 悬停取当前分类的主题色（--accent），和页面里其它强调色是同一个来源 */
.jump__btn:hover:not(:disabled) {
  color: var(--accent);
  background: color-mix(in srgb, var(--ink) 6%, transparent);
}

.jump__btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* 已经在这一头就没什么可跳的了，灰掉比「点了没反应」清楚 */
.jump__btn:disabled {
  color: var(--muted-2);
  opacity: 0.45;
  cursor: default;
}

@media (max-width: 640px) {
  .jump {
    right: 14px;
    bottom: 14px;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .jump {
    transition: none;
  }
}

/* ================================================================
 * 主题开关（浅色 / 深色）
 *
 * 长在顶栏最右侧，是 .header-inner 的第三个子元素 —— .nav 是 flex:1，
 * 于是它自然被挤到右边，不需要任何 margin 技巧。
 *
 * 为什么做成「胶囊滑块」而不是一个图标按钮：图标按钮只能表达「现在是什么」，
 * 用户得先点一下才知道会变成什么；滑块两端各有一枚图标、拇指停在当前那一侧，
 * 「往另一边拨」这件事是看得出来的。
 *
 * 句子页上没有它 —— 那一页是固定的深色沉浸式、不引 chrome.js。
 * ================================================================ */

.theme-toggle {
  /* 尺寸只改这四个变量，算式会自动跟上：
   *   拇指行程 = 内宽 - 2 × 边距 - 拇指宽
   *   拇指上沿 = (内高 - 2 × 边距 - 拇指宽) / 2
   * 下面的 :root[data-theme="dark"] 与小屏段用的都是同一个算式，
   * 因此不存在「改了尺寸忘了改位移」导致拇指错位的可能。 */
  --tt-w: 56px;
  --tt-h: 30px;
  --tt-pad: 2px;
  --tt-thumb: 24px;

  position: relative;
  flex: 0 0 auto;
  width: var(--tt-w);
  height: var(--tt-h);
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
  border-radius: 999px;
  /* 轨道用半透明中性色而不是 --surface-2：它长在毛玻璃顶栏上，
   * 不透明底会和顶栏的模糊脱节，看起来像贴了一小块纸。 */
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  color: var(--muted-2);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: border-color 0.2s var(--ease), background-color 0.2s var(--ease);
}

.theme-toggle:hover {
  border-color: color-mix(in srgb, var(--brand) 45%, transparent);
}

.theme-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* 轨道两端的图标只是路标，不接收点击（点击一律由外层按钮接管） */
.tt-rail {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 calc(var(--tt-pad) + 5px);
  pointer-events: none;
}

/* 拇指上画着「当前模式」的那一枚图标 —— 于是现在是浅色还是深色，
 * 不必读任何文字，看拇指停在哪一侧、上面画的是什么就够了。 */
.tt-thumb {
  position: absolute;
  left: var(--tt-pad);
  top: calc((var(--tt-h) - 2 * var(--tt-pad) - var(--tt-thumb)) / 2);
  width: var(--tt-thumb);
  height: var(--tt-thumb);
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--raised);
  color: var(--ink-2);
  box-shadow: var(--shadow-sm);
  transition: transform 0.24s var(--ease);
}

:root[data-theme="dark"] .theme-toggle .tt-thumb {
  transform: translateX(
    calc(var(--tt-w) - 2px - 2 * var(--tt-pad) - var(--tt-thumb))
  );
}

.tt-ico {
  display: grid;
  place-items: center;
  width: 15px;
  height: 15px;
}

.tt-ico svg {
  display: block;
  width: 15px;
  height: 15px;
}

/* 拇指里两枚图标叠在同一格，显示哪一枚交给主题决定；
 * 轨道上的两枚则各占一端。 */
.tt-thumb .tt-ico {
  grid-area: 1 / 1;
}

.tt-thumb .tt-ico--moon,
:root[data-theme="dark"] .tt-thumb .tt-ico--sun {
  opacity: 0;
}

:root[data-theme="dark"] .tt-thumb .tt-ico--moon {
  opacity: 1;
}

/* 小屏：只改变量，算式与位移跟着走。顶栏在 640px 以下已经收起品牌文字，
 * 这里再收一点开关，320px 宽也放得下「首页 + 工具汇总 + 开关」三件东西。 */
@media (max-width: 640px) {
  .theme-toggle {
    --tt-w: 50px;
    --tt-h: 28px;
    --tt-thumb: 22px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .tt-thumb {
    transition: none;
  }
}

/* iPhone SE 一代那档（320px）才藏站名。360 / 375 / 390 / 414 都装得下，
   藏了反而让顶栏中间空一大段（实测见 640 块里的注释）。
   放在文件末尾是为了让它在 640 那个媒体块之后生效，条件更窄、顺序更后，稳。 */
@media (max-width: 359px) {
  .brand span.brand-text {
    display: none;
  }
}


/* ==========================================================================
   鱼盆模型（/tools/fishbowl.html）
   --------------------------------------------------------------------------
   这一页只有一张表：11 列，全是数字与代码。两个真正的难点。

   ① 390px 上怎么读。没有折成卡片列表 —— 这张表的用法是**横着比**：
      谁离盆沿最正、谁的名次在往上走，是要一眼扫过去的；折成五张竖排
      卡片就退化成「一个一个看」，那跟把这张表撕成五份没有区别。所以
      保留表格，靠 `.table-wrap` 横向滚动，同时把行内留白收窄，
      让滚动条少走一截。

   ② 红绿只有一套。正数收红、负数收绿、平盘不上色 —— 平盘不是第三种
      颜色，它就是没有颜色，所以除了 .fb-mv--up / .fb-mv--down 之外
      不需要第三个类，中性色直接继承正文色。
   ========================================================================== */

/* 表头日期线。它是「这份数属于哪一天」的唯一出处，压成一行紧贴表格：
   离开日期看数字，是这类表最容易被读错的方式。 */
.fb-band {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line-soft);
}

.fb-band__label {
  font-size: 12.5px;
  color: var(--muted);
}

.fb-band__date {
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}

/* 「已收盘 / 上一交易日」是状态不是内容，压一档就够 */
.fb-band__tag {
  padding: 1px 7px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 12px;
  color: var(--muted);
}

/* 「今日尚未收盘」是这一次数据的说明，与警示同色 —— 它不是错误，
   但也不能和日期一样被当成结论读。 */
.fb-band__note {
  font-size: 12px;
  color: var(--warn);
}

.fb-notices {
  margin-top: 12px;
}

.fb-notice {
  position: relative;
  /* 右边多留一截给叉号：它是绝对定位的，不占文档流，
   * 不留白的话长提示会从叉号底下穿过去。 */
  padding-right: 42px;
  margin-bottom: 8px;
}

/* 叉号：和轻提示那颗同一套手感（透明底、悬停才出现底），
 * 但颜色跟随警示块自己的前景色 —— 这里文字是 --warn，
 * 叉号用正文色会在橙底上显得比正文还重。 */
.fb-notice__x {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: inherit;
  font-family: var(--font);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.72;
  transition: opacity 0.15s var(--ease), background 0.15s var(--ease);
}

.fb-notice__x:hover,
.fb-notice__x:focus-visible {
  opacity: 1;
  background: color-mix(in srgb, currentColor 14%, transparent);
  outline: none;
}

/* 全部关掉之后容器不该继续占着那 12px 外边距 */
.fb-notices:empty {
  display: none;
}

.fb-table td {
  white-space: nowrap;
}

/* 指数名是这一行的锚点，横向滚动时它得认得出来 */
.fb-cell-name {
  font-weight: 600;
}

.fb-date {
  font-family: var(--mono);
  font-size: 12.6px;
  color: var(--ink-2);
}

/* 「取数失败」「—」是内容，不是占位符 —— 用 --muted 而不是 --muted-2
   （后者只给占位符与单位，对比度不够当正文读）。 */
.fb-missing {
  color: var(--muted);
}

/* 名次是一个序号，右对齐跟数字列保持一致 */
.fb-rank {
  text-align: right;
  color: var(--ink-2);
}

/* 持续天数也是一个计数（交易日个数），同样右对齐；它不是涨跌，不上色 ——
   天数的多少说明的是「信号还新不新」，不是「好不好」。 */
.fb-days {
  text-align: right;
  color: var(--ink-2);
}

/* 涨跌色，A 股习惯：正数收红、负数收绿。涨幅% / 偏离率 / 区间涨幅%
   三列共用这一对类 —— 全页只有一套红绿，不必再记「哪一列是反的」。 */
.fb-mv--up {
  color: var(--err);
}

.fb-mv--down {
  color: var(--ok);
}

.fb-cell-code {
  font-family: var(--mono);
}

/* 免责句：页面上唯一可见的说明文字。与表格拉开一点，但不抢视线 */
.fb-disclaim {
  margin-top: 14px;
}

@media (max-width: 640px) {
  /* 手机上的减法只减留白，不减信息：列一个不少，只是挤一点。 */
  .fb-table th,
  .fb-table td {
    padding: 9px 11px;
    font-size: 12.4px;
  }

  .fb-band {
    gap: 3px 8px;
  }
}

/* ================================================================
 * 二维码：顶栏入口 + 页脚大图
 *
 * 同一个文件、同一套「白底 + 圆角 + 描边」的外框，只有尺寸不同 ——
 * 两处必须看起来是同一个东西，否则访客会怀疑页脚那个是另一个码。
 *
 * ⛔ 白底是硬要求，不跟主题走：二维码靠黑白对比被识别，深色主题下翻成
 *    深底浅块扫不出来；而给图片套反色滤镜更糟 —— 码中间那幅彩色插画
 *    会跟着反色。所以这里写死 #fff，两档主题一个样。
 * ================================================================ */

.qr-entry {
  /* 浮层的定位基准。flex:0 0 auto：它是"一个入口"，不参与导航的弹性分配 */
  position: relative;
  display: flex;
  align-items: center;
  flex: 0 0 auto;
}

.qr-entry__link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  /* 左内边距小一点，让 34px 的方图不要被推得离导航太远 */
  padding: 4px 6px 4px 4px;
  border-radius: var(--r-sm);
  color: var(--muted);
  font-size: 13px;
  /* ⚠️ 全局有 a:hover{text-decoration:underline}，不压掉的话悬停会多一条下划线，
     与"压暗一档 + 浅底"这组反馈打架 */
  text-decoration: none;
  transition: color 0.2s var(--ease), background-color 0.2s var(--ease);
}

.qr-entry__link:hover,
.qr-entry:focus-within .qr-entry__link {
  color: var(--ink);
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  text-decoration: none;
}

/* 只在键盘聚焦时描边：鼠标点的时候已经有底色反馈，再描一圈是噪音 */
.qr-entry__link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* 缩略图 34px。这个尺寸**扫不出**，它的作用是「让你看出这是个二维码」；
   真要扫的时候浮层会把同一张图放大到 152px。做成二维码的样子（黑白方块）
   而不是一个抽象的"扫码"图标，就是为了这一点。 */
.qr-thumb {
  display: block;
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
  border-radius: 6px;
  background: #fff;
}

/* 浮层：默认不可见 —— 所以它既不占布局，也不遮挡导航。
   出现方式与顶栏的分类下拉同一套判据（:hover + :focus-within），
   键盘 Tab 到入口时同样弹出来。 */
.qr-pop {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 45;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 12px;
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  /* 三种状态一起过渡：只改 opacity 的话，键盘焦点移走后浮层还会"挡"在
     原地接走鼠标事件，所以 visibility 与 pointer-events 必须同时收 */
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  pointer-events: none;
  transition: opacity 0.18s var(--ease), transform 0.18s var(--ease),
    visibility 0.18s var(--ease);
}

.qr-entry:hover .qr-pop,
.qr-entry:focus-within .qr-pop {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  pointer-events: auto;
}

.qr-pop__img {
  display: block;
  width: 152px;
  height: 152px;
  border-radius: 8px;
  background: #fff;
}

.qr-pop__note {
  font-size: 12.5px;
  color: var(--muted);
  white-space: nowrap;
}

/* ---------- 页脚 ---------- */

.footer-qr {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  /* 锚点从顶栏跳过来时，上方有 58px 的粘顶栏 —— 不留这段就会被它盖住 */
  scroll-margin-top: 78px;
  /* 与下面那行备案号拉开：二维码是"最后一次邀请"，备案是收尾，层次要分得开 */
  margin-bottom: 26px;
}

.footer-qr__img {
  display: block;
  width: 124px;
  height: 124px;
  border: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
  border-radius: 10px;
  background: #fff;
  box-shadow: var(--shadow-sm);
}

/* 提示语用 --muted，不用页脚那一层的 --muted-2：这句是要被读完的，
   而 --muted-2 在浅色纸上只有 3.05:1（它只够给占位符与图标）。 */
.footer-qr__note {
  margin: 0;
  font-size: 13px;
  color: var(--muted);
}

/* 前半句提一档亮度，让"有建议或意见"先被看见 */
.footer-qr__lead {
  color: var(--ink-2);
}

/* ---------- 页脚：访客计数 ---------- */

/* 「您是第 N 位访客 · 本站累计访问 M 次」。
 *
 * 两个数排在同一行、用一枚分隔点隔开，而不是各占一行 ——
 * 页脚上面已经有一块二维码，再来两行就把收尾撑成正交了。
 *
 * 三点与页面其他小字不同的处理：
 *   ① 文字用 --muted 而不是页脚那层的 --muted-2 —— 后者 3.05:1，
 *      低于 AA；这句是要被读的，不是占位符。
 *   ② 数字不加衬线。--serif 只给 ≥24px 的大标题用，小字号宋体在
 *      Windows 上会点阵化（这条是全站的红线，见 MEMORY 的视觉约定）。
 *   ③ tabular-nums：数字位数变化时不做水平抖动。 */
.footer-visits {
  margin: 0 0 14px;
  text-align: center;
  font-size: 12.5px;
  line-height: 1.8;
  letter-spacing: 0.04em;
  color: var(--muted);
}

/* hidden 由 chrome.js 在拿到数字后移除。这条必须留着：
 * 现在 .footer-visits 没写 display，但哪天给它加了 flex/grid，
 * [hidden] 的默认 display:none 就会被覆盖，这一行会先闪两个 0。 */
.footer-visits[hidden] {
  display: none;
}

/* 两个数字同一档字号与字重 —— 它们是同一句话里的两个重点，
 * 谁都不该比谁更响 */
.footer-visits__n,
.footer-visits__m {
  margin: 0 2px;
  font-size: 15px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* 分隔点只做停顿，不对读屏播报（aria-hidden 在 chrome.js 里） */
.footer-visits__sep {
  margin: 0 0.55em;
  color: var(--muted-2);
}

/* 两个短句各自不许被拆开。「本站累计访问 1,286 次」在 320px 上是
 * 约 130px，装得下 —— 所以窄屏只会在分隔点处折成两行，不会半个词掉下去。 */
.footer-visits__item {
  white-space: nowrap;
}

@media (max-width: 640px) {
  /* 顶栏在 640 以下只剩方图：多两个字「微信」会把导航挤到换行。
     尺寸收到 28px —— 依据是实测（320 / 375 / 390 三档都不溢出），
     不是拍脑袋定的。 */
  .qr-entry__text {
    display: none;
  }

  .qr-entry__link {
    padding: 3px;
  }

  .qr-thumb {
    width: 28px;
    height: 28px;
  }

  /* 浮层也要收：152px 的图 + 24px 内边距在 320 屏上会顶到右边界外 */
  .qr-pop {
    padding: 10px;
  }

  .qr-pop__img {
    width: 132px;
    height: 132px;
  }

  .footer-qr__img {
    width: 112px;
    height: 112px;
  }
}

/* ==================================================================
 * 协议解析入口（/tools/protocol.html，2026-10-23）
 *
 * 这一页是一张「索引」：六份协议长图是独立 HTML，卡片只负责把人送过去。
 * 因此这里**只补卡片内部的两处排版**（右上角的「打开」提示、底部的全称），
 * 卡片的边框、悬停抬升、键盘焦点环全部仍然来自 .tool-card —— 一旦在这里
 * 复制一份 .tool-card 的样式，首页与本页就会开始各自漂移。
 * ================================================================== */

/* 六张卡：桌面三列两行（300px 起才放得下四列的一半，所以到 1200 宽仍是 3 列），
 * 900px 以下折两列，640px 以下单列。
 * ⚠️ 不用 repeat(3, 1fr)：卡片数量由 PROTOCOLS 表决定，将来加到 7、8 条时，
 *    固定列数会让最后一行孤零零一张卡撑满三分之一；auto-fill 让它们自然流动。 */
.proto-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 14px;
}

/* 协议名很短的（DHCP / DNS）与长的（HTTPS / TLS）要占同一档宽度，
 * 让「打开 ↗」在每张卡上落在同一条竖线上。 */
.proto-card .tool-name {
  flex: 1 1 auto;
  min-width: 0;
}

/* 进入新标签页的提示。它不是按钮（整张卡才是链接），所以只做颜色与位移，
 * 不做边框 —— 否则会让人以为卡片里还有第二个可点的东西。 */
.proto-open {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 12px;
  letter-spacing: 0.06em;
  white-space: nowrap;
  transition: color 0.18s var(--ease), transform 0.18s var(--ease);
}

.proto-card:hover .proto-open,
.proto-card:focus-visible .proto-open {
  color: var(--accent);
  transform: translateX(2px);
}

/* 卡片底部的中文全称。用 margin-top:auto 顶到底边，六张卡并排时这一行
 * 才会齐，而不是被长短不一的说明文字顶得七零八落。 */
.proto-meta {
  margin-top: auto;
  padding-top: 10px;
  border-top: 1px solid var(--line-soft);
  color: var(--muted);
  font-size: 12.5px;
  letter-spacing: 0.02em;
}

@media (max-width: 899px) {
  .proto-grid {
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  }
}

/* 窄屏单列：320px 下 260px 的两列根本塞不下，与其让它挤成一条，
 * 不如老老实实一列到底 —— 卡片本来就是"一句话 + 一个入口"，单列反而好读。 */
@media (max-width: 640px) {
  .proto-grid {
    grid-template-columns: 1fr;
  }

  .proto-open {
    font-size: 11.5px;
  }
}

/* 减少动效偏好：位移去掉，只留颜色 —— 与站内其它卡片一致 */
@media (prefers-reduced-motion: reduce) {
  .proto-open {
    transition: color 0.18s linear;
  }

  .proto-card:hover .proto-open,
  .proto-card:focus-visible .proto-open {
    transform: none;
  }
}

/* ==================================================================
 * 攻防知识（/tools/attack-defense.html，2026-10-26）
 *
 * 与「协议解析」同为文档入口：那份是六张卡片各自跳走，这里是一整篇，
 * 正文是 iframe 里那份独立的 HTML，本页只给外框与两个出口
 * （下载 PDF / 新标签页打开），所以样式也只有这两块。
 *
 * ⚠️ 框内是文档自己的版式，这里**不碰它的内容**：不设字体、不设内边距、
 *    不往里注入颜色 —— 那些都属于文档本身，改了就是"本站擅自改了文档"。
 * ================================================================== */

/* 两个出口。flex + wrap 而不是写死两列：窄屏时它们各自换行，
 * 而不是把「下载 PDF」挤成一个两行的窄按钮。 */
.doc-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 14px;
}

/* 文档框。
 * 高度用 vh 而非写死像素：大屏一屏能多读几段，小屏也不至于只剩一条缝。
 * min-height 是给「窗口很矮」兜底的 —— 没有它，500px 高的窗口里
 * 框会塌成一条读不了的细线。 */
.doc-frame {
  display: block;
  width: 100%;
  height: 78vh;
  min-height: 520px;
  border: 1px solid var(--line);
  border-radius: 10px;
  /* 文档自带底色，这里的白只在它加载出来的那一瞬露一下 ——
   * 不给的话深色主题下会先闪一块黑。 */
  background: #fff;
}

/* 窄屏：手机上一屏本来就不高，让框矮一档，上面那两个按钮才不会被顶出视口 */
@media (max-width: 640px) {
  .doc-frame {
    height: 70vh;
    min-height: 420px;
  }
}
