/* ================================================================
 * geek.css — 全站极客风格统一主题（app.html 风格外推的单一来源）
 *
 * 结构：
 *   ⓪ 字体加载：Inter (400-800) + JetBrains Mono (400,700)，display=swap 防 FOIT
 *   ① 令牌（量子钛白极客浅色 = 默认基座 / 黑青极客深色 = html.dark）
 *   ② 基础重置与全局
 *   ③ 顶栏 `.topbar/.tb/.navs/.navgroup` 分组下拉导航（桌面悬停 + 移动抽屉）
 *   ④ 2.5D 浮层卡：.d3/.hero/.st/.mod/.goal/.quick/.chart3d/.feed/.mtable
 *   ⑤ 弹层 .mo / 表单 .f / 按钮 .btn / Toast / 脚注
 *   ⑥ 移动端汉堡 + 右侧抽屉 .m-drawer/.m-mask/.m-nav
 *   ⑦ theme.css 令牌桥接（--primary/--surface/--text/… 双态）
 *   ⑧ tailwind 中性色映射 + .plain-card/.glass-input 地极客化
 *   ⑨ 背景装饰 / 滚动条 / 动效
 *   ⑩ 管理员模拟视图 UI
 *   ⑪ 玻璃拟态层（仅浅色；四档材质 + 令牌桥接 + 背景光斑 + 降级开关）
 *
 * 约定：
 *   - 默认(无类/light) = 浅色；html.dark = 深色；html.light = 显式浅色别名。
 *   - 浅色 = 玻璃拟态（⑪），深色 = 纯色半透明遮罩（黑青极客，不引入毛玻璃）。
 *   - 弹窗/下拉仅内部按钮关闭。
 * ================================================================ */

/* ⓪ 字体：Inter (拉丁正文/数字) + JetBrains Mono (等宽数字/代码)
   不使用 CSS @import（会阻塞级联解析 + display=swap 导致 reflow）；
   字体加载由 sidebar.js 在 <head> 最前注入 <link rel="preconnect"> +
   <link rel="stylesheet" ...&display=optional> 动态前置，保证首屏不阻塞、字体就绪即生效 */


/* ==================== ① 令牌 · 浅色（默认基座） ==================== */
/* ① 令牌由 haven-tokens.css 统一提供。
   此处不再声明颜色、圆角、阴影或主题变量。 */

/* ==================== ② 基础重置与全局 ==================== */
/* 仅对 sidebar.js 注入的极客构件做盒模型统一；不做全站 margin/padding 清空，
   以免破坏数据密集业务页的既有间距与排版 */
* { box-sizing: border-box; }
.topbar, .tb, .navs, .navgroup, .navgroup .menu, .op, .ib, .av, .nb,
.m-drawer, .m-drawer-hd, .m-nav, .m-actions, .logo, .brand { box-sizing: border-box; }
html, body {
  min-height: 100%;
  font-family: var(--font);
  color: var(--tx);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}
html {
  overflow-x: hidden;
  scrollbar-width: thin; scrollbar-color: var(--c1) transparent;
  transition: background-color .3s ease, color .3s ease;
}
body { overflow-x: hidden; }
html.dark body { -webkit-font-smoothing: antialiased; }
/* 兜底：业务页 theme.css 的 body.dark/body.light 色需让位于 geek 令牌，保证深浅一致 */
body.dark, body.light { background-color: var(--bg); color: var(--tx); }
input, textarea, select { user-select: text; -webkit-user-select: text; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: linear-gradient(var(--c1), var(--c2)); border-radius: var(--radius-99px); }
button { font-family: inherit; cursor: pointer; }
a { color: inherit; text-decoration: none; }
.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
/* 内容区图标槽（2026-10-01）：配合 sidebar.js 的 havenIcon()/data-hico 使用。
   内联 SVG 跟文字基线走、不参与 flex 收缩；尺寸由 SVG 自身 width/height 决定。 */
.ic-i { display: inline-flex; align-items: center; justify-content: center; vertical-align: -0.16em; flex: none; }
.ic-i > svg { display: block; }
/* tailwind 工具类覆盖：统一走 theme.css / geek.css 的 --font / --mono 令牌
   （geek.css 在 tailwind 之后加载，同特异性即可覆盖，无需 !important） */
.font-sans  { font-family: var(--font);  }
.font-mono  { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.font-serif { font-family: var(--font);  }
.font-monospaced { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.hidden { display: none !important; }
:focus-visible { outline: 2px solid var(--c1); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; } }

/* 布局容器：与 /app 同宽同 padding */
.wrap { max-width: 1240px; margin: 0 auto; padding: 0 24px; }
@media (max-width: 520px) { .wrap { padding: 0 14px; } }

/* ==================== ③ 顶栏 + 分组下拉导航 ==================== */
.topbar { position: sticky; top: 0; z-index: 60; background: var(--topbar-bg); border-bottom: 1px solid var(--topbar-border); }
.tb { max-width: 1240px; margin: 0 auto; padding: 0 24px; height: 62px; display: flex; align-items: center; gap:var(--space-14px); position: relative; }
/* 品牌 logo：透明直出（2026-09-17）
   ⚠️ 原为 `background: var(--logo-bg)` 的青→靛→粉渐变方块 + 白色通用图标，
      白色细线压在中段靛蓝上几乎看不见，用户反馈「logo 与渐变色高度重叠看不清」。
      现去掉渐变底，改用**品牌 mark（内联 SVG）跟随主题色**，与 app.html / 子页统一。 */
.logo {
  width: 36px; height: 36px; border-radius: var(--radius-11px); display: grid; place-items: center;
  font-weight: 700; color: var(--brand-mark); background: transparent; flex: none;
}
html.dark .logo { box-shadow: var(--shadow-none); }
.logo svg { display: block; width: 26px; height: 26px; filter: var(--brand-halo); }
.brand { min-width: 0; }
.brand b { font-size: var(--fs-sm); letter-spacing: -0.2px; font-weight: 700; color: var(--tx); display: flex; align-items: center; gap:var(--space-6px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.brand .os { font-family: var(--mono); font-size: var(--fs-2xs); color: var(--tx3); letter-spacing: .18em; }
.grow { flex: 1; }

/* 分组按钮导航（居中） */
.navs { display: flex; gap:var(--space-6px); position: absolute; left: 50%; transform: translateX(-50%); }
.navs a.nb { display: inline-flex; align-items: center; }
.nb {
  padding:var(--space-7px-13px); border-radius: var(--radius-9px); border: 1px solid transparent;
  background: none; color: var(--nb-c); font-size: var(--fs-sm); font-weight: 600;
  transition: .15s; white-space: nowrap; display: inline-flex; align-items: center; text-decoration: none;
}
.nb:hover { background: var(--nb-hover-bg); color: var(--nb-hover-c); }
.nb.on { background: var(--nb-on-bg); color: var(--nb-on-c); }
.nb.pri { background: var(--pri-gradient); color: var(--logo-fg); }
.nb.pri:hover { background: var(--pri-hover); }

/* 右操作区 */
.op { display: flex; gap:var(--space-8px); align-items: center; }
.ib {
  width: 34px; height: 34px; border-radius: var(--radius-9px); border: 1px solid var(--line2);
  background: var(--panel); color: var(--tx2); display: grid; place-items: center;
  transition: .15s; flex: none; cursor: pointer; font-size: var(--fs-sm); line-height: 1;
}
.ib:hover { color: var(--c1); border-color: var(--c1); }
.ib svg { display: block; }
/* 主题按钮：日间显示月亮(切夜间)，夜间显示太阳(切日间) */
#btn-theme .ic-moon { display: none; }
html.light #btn-theme .ic-sun { display: none; }
html.light #btn-theme .ic-moon { display: block; }
.av {
  width: 34px; height: 34px; border-radius: var(--radius-9px); display: grid; place-items: center;
  font-size: var(--fs-base); border: 1px solid var(--line2); background: var(--panel); flex: none;
  overflow: hidden; background-size: cover; background-position: center;
}

/* 下拉分组 */
.navgroup { position: relative; }
.navgroup::after { content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 8px; }
.navgroup > .nb { display: inline-flex; align-items: center; gap:var(--space-6px); }
.navgroup > .nb .caret { font-size: var(--fs-sm); opacity: .7; transform: translateY(1px); }
.navgroup .menu {
  position: absolute; top: calc(100% + 8px); left: 50%;
  transform: translate(-50%, -4px); min-width: 238px;
  background: var(--menu-bg); border: 1px solid var(--menu-border); border-radius: var(--radius-14px);
  padding:var(--space-7px); box-shadow: 0 26px 56px -18px color-mix(in srgb, var(--black) 35%, transparent);
  opacity: 0; visibility: hidden; transition: opacity .18s ease, visibility 0s linear .18s, transform .18s ease; z-index: 90;
  transform-origin: top center;
}
html.dark .navgroup .menu { box-shadow: 0 26px 56px -18px color-mix(in srgb, var(--black) 90%, transparent); }
.navgroup.open .menu { opacity: 1; visibility: visible; transition: opacity .18s ease, visibility 0s linear, transform .18s ease; transform: translate(-50%, 0); }
.menu a, .menu .mi {
  display: flex; gap:var(--space-11px); align-items: center; padding:var(--space-8px-10px); border-radius: var(--radius-10px);
  color: var(--tx2); font-size: var(--fs-sm); font-weight: 600; width: 100%; text-align: left;
  border: none; background: none; font-family: inherit; cursor: pointer; transition: .12s; text-decoration: none;
}
.menu a:hover, .menu .mi:hover { background: color-mix(in srgb, var(--primary) 8%, transparent); color: var(--c1); }
html.dark .menu a:hover, html.dark .menu .mi:hover { background: color-mix(in srgb, var(--dark-primary) 13%, transparent); color: var(--white); }
.menu i {
  width: 28px; height: 28px; flex: none; border-radius: var(--radius-8px); display: grid; place-items: center;
  font-style: normal; font-size: var(--fs-sm); background: var(--panel); border: 1px solid var(--line2);
}
.menu .tx { min-width: 0; }
.menu .tx b { display: block; font-size: var(--fs-xs); white-space: nowrap; }
.menu .tx span { display: block; font-size: var(--fs-2xs); color: var(--tx3); margin-top:var(--space-2px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.menu .sep { height: 1px; background: var(--line2); margin:var(--space-6px-4px); }
/* 菜单项当前页高亮（sidebar.js 渲染 .mi.act / .mi.active） */
.menu .mi.act, .menu .mi.active, .menu a.act, .menu a.active { background: color-mix(in srgb, var(--primary) 10%, transparent); color: var(--c1); }
html.dark .menu .mi.act, html.dark .menu .mi.active, html.dark .menu a.act, html.dark .menu a.active { background: color-mix(in srgb, var(--dark-primary) 14%, transparent); color: var(--white); }
/* 导航内 SVG 统一尺寸（sidebar.js 图标库为 viewBox 无显式宽高） */
.nb svg, .navs .nb svg { width: 15px; height: 15px; display: block; flex: none; }
.menu i svg { width: 15px; height: 15px; display: block; }
.m-nav .mi i svg { width: 16px; height: 16px; display: block; }

@media (max-width: 860px) { .navs { display: none; } }
@media (min-width: 861px) and (max-width: 1139px) { .navs { position: static; transform: none; } }
@media (max-width: 520px) { .tb { padding: 0 14px; } }

/* ★ V5.1.3：窄屏把顶栏空间让给**页头标题**。
   实测（`_ui-quality-audit.js` @390）：`.op` 占 202px、两个 `.grow` 占位白吃 gap，
   `.brand` 被挤到只剩 68px ⇒ 标题「时间里程碑 MILESTONES」溢出 94px 被 ellipsis 截断。
   移动端 navs 已收进抽屉 ⇒ `.grow` 占位无意义；`.brand` 应优先取剩余空间。
   ⚠️ 仅作用于 `.tb` 内的 `.grow`，不影响页面里其它用 .grow 的布局。 */
@media (max-width: 560px) {
  .tb { gap: 8px; }
  .tb .grow { display: none; }
  .brand { flex: 1 1 auto; min-width: 0; }
}

/* ==================== ④ 2.5D 浮层卡 / 概览 / 模块 ==================== */
.d3 {
  position: relative; background: var(--d3-bg); border-radius: var(--radius-16px);
  border: 1px solid var(--d3-border); box-shadow: var(--d3-shadow);
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease, background .16s ease;
}
/* V5.1.6：hover 收敛 —— 去掉「上浮 2px + 换大阴影」，只留边框提亮。
   22 个卡片同时带位移+阴影时整个页面"都在飘"，观感杂；边框反馈足够且更安静。 */
.d3:hover { border-color: var(--border-strong) !important; }

/* ★ 2026-10-01：hero「带」内边距与标题规格统一（原 44/44/40，与其它页 hero 的 26/28、28/30、32 各异）。
   取值见 haven-tokens.css 的 --space-28px-32px / --space-22px-18px。 */
.hero { padding:var(--space-28px-32px); display: grid; grid-template-columns: 1fr auto; gap:var(--space-30px); align-items: center; position: relative; overflow: hidden; border-radius: var(--radius-20px); }
@media (max-width: 860px) { .hero { grid-template-columns: 1fr; padding:var(--space-22px-18px); } }
.hero .halo { position: absolute; right: -140px; top: -140px; width: 420px; height: 420px; border-radius: var(--radius-50); background: radial-gradient(circle, var(--glow-primary), transparent 62%); filter: blur(30px); pointer-events: none; }
.eyebrow { display: inline-flex; gap:var(--space-8px); align-items: center; font-family: var(--mono); font-size: var(--fs-xs); font-weight: 600; letter-spacing: .2em; color: var(--c1); }
/* V5.1.6：去掉眉标圆点的绿色发光（0 0 10px）—— 持续发光是页面"脏"感来源之一，圆点本身保留 */
.eyebrow i { width: 6px; height: 6px; border-radius: var(--radius-50); background: var(--ok); }
/* hero 标题统一规格：weight 700 / letter-spacing -1px / line-height 1.1 / margin 12 0 8（与各页 hero 对齐）。 */
.hero h1 { font-size: var(--fs-display); font-weight: 700; letter-spacing: -1px; margin: 12px 0 8px; line-height: 1.1; color: var(--tx); }
.hero h1 .g { background: linear-gradient(90deg, var(--c1), var(--c2)); -webkit-background-clip: text; background-clip: text; color: transparent; }
html.dark .hero h1 .g { background: linear-gradient(90deg, var(--white), var(--dark-primary-soft) 60%, var(--dark-accent-soft)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.hero p { color: var(--tx2); font-size: var(--fs-base); line-height: var(--lh-body); max-width: 600px; }
.hero .tags { display: flex; gap:var(--space-10px); margin-top:var(--space-20px); flex-wrap: wrap; }
.tag { display: inline-flex; align-items: center; gap:var(--space-7px); padding:var(--space-7px-13px); border-radius: var(--radius-99px); font-size: var(--fs-2xs); font-weight: 600; background: var(--panel); border: 1px solid var(--line2); color: var(--tx2); }
.tag b { color: var(--c1); font-family: var(--mono); }

.clock { text-align: right; position: relative; }
@media (max-width: 860px) { .clock { text-align: left; } }
.clock .t { font-family: var(--mono); font-size: var(--fs-2xl); font-weight: 700; letter-spacing: 2px; color: var(--tx); }
html.dark .clock .t { text-shadow: 0 0 22px color-mix(in srgb, var(--accent) 50%, transparent); }
.clock .d { font-family: var(--mono); font-size: var(--fs-2xs); color: var(--tx3); margin-top:var(--space-6px); }
/* 2026-10-01：恢复时钟下的「流动光带」（V5.1.6 曾以「装饰噪音」为由整条停用，用户要求恢复）。
   原定义取自 .workbuddy/backups/fontscale-2026-09-30T07-35-53/HTML/public/geek.css，已按当前令牌体系改写。
   app / finance 两页 hero 同时生效；§② 的 prefers-reduced-motion 全局兜底会把它降为静态。 */
.clock .line { height: 2px; margin-top:var(--space-14px); border-radius: var(--radius-2px); width: 220px; margin-left: auto; position: relative; overflow: hidden; background: color-mix(in srgb, var(--text) 12%, transparent); }
html.dark .clock .line { background: linear-gradient(90deg, var(--dark-primary), var(--accent), transparent); }
@media (max-width: 860px) { .clock .line { margin-left: 0; } }
.clock .line::after { content: ''; position: absolute; top: 0; left: 0; width: 55%; height: 100%; background: linear-gradient(90deg, transparent, var(--c1), var(--c2), transparent); animation: clock-flow 2.6s linear infinite; }
@keyframes clock-flow { from { transform: translateX(-110%); } to { transform: translateX(330%); } }

.sec { display: flex; align-items: baseline; gap:var(--space-12px); margin: 46px 0 20px; }
.sec .no { font-family: var(--mono); font-size: var(--fs-2xs); color: var(--c1); letter-spacing: .1em; }
.sec h2 { font-size: var(--fs-lg); font-weight: 700; letter-spacing: -0.2px; color: var(--tx); }
.sec p { margin-left: auto; font-size: var(--fs-base); color: var(--tx3); }

.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap:var(--space-18px); }
@media (max-width: 1000px) { .stats { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .stats { grid-template-columns: 1fr; } }
.st { padding:var(--space-20px); border-radius: var(--radius-16px); position: relative; overflow: hidden; }
/* 图标块底色：2026-09-23（V2.0.1）浅色改为「中性冷灰」。
   原因：图标本身是蓝色系（渐变描边），容器底色也是蓝色系 ⇒ 同色相 + 亮度接近，视觉上糊成一块。
   改中性灰后色相拉开，且底色压到 var(--gray-150)，使图标（var(--primary-strong)~var(--primary)）对比度达 4.5~6.1:1。
   ⚠️ 深色保持原浅蓝底不动（本次只调浅色，深色零变化）⇒ 基座留原值，浅色另起一条覆盖。
   ⚠️ 只改「有底色」的图标块；直接压在白卡上的图标（[data-ico] / 顶栏按钮 / 空态）不动。 */
/* V5.1.6：图标块收敛 —— 去掉 1px 描边与 inset 高光（渐变底 + 描边 + 内高光三层叠加是"脏"主源），
   保留中性底色（V2.0.1 的对比度设计不变：图标蓝压浅灰底 4.5~6.1:1）；圆角归到 12px 标准档。 */
.st .ic { width: 40px; height: 40px; border-radius: var(--radius-12px); display: grid; place-items: center; font-size: var(--fs-md); background: linear-gradient(150deg, var(--text), var(--blue-100-soft)); color: var(--c1); }
html:not(.dark) .st .ic { background: linear-gradient(150deg, var(--gray-100), var(--gray-150)); }
.st .v { font-family: var(--mono); font-size: var(--fs-xl); font-weight: 700; margin-top:var(--space-16px); letter-spacing: -0.2px; }
.st .l { font-size: var(--fs-sm); font-weight: 700; margin-top:var(--space-2px); color: var(--tx); }
.st .d { font-size: var(--fs-xs); color: var(--tx3); margin-top:var(--space-5px); line-height: var(--lh-ui); }
.st .chg { font-family: var(--mono); font-size: var(--fs-2xs); font-weight: 700; position: absolute; right: 16px; top: 20px; max-width: 46%; text-align: right; }
.ok { color: var(--ok); } .warn { color: var(--warn); } .bad { color: var(--bad); }

.split { display: grid; grid-template-columns: 1.15fr .85fr; gap:var(--space-18px); align-items: stretch; }
@media (max-width: 900px) { .split { grid-template-columns: 1fr; } }
.chart3d { padding:var(--space-22px); position: relative; }
.chart3d h4, .chart3d h3 { font-size: var(--fs-md); font-weight: 700; color: var(--tx); display: flex; justify-content: space-between; align-items: baseline; }
.chart3d h4 .mono, .chart3d h3 .mono { font-size: var(--fs-2xs); color: var(--tx3); }
/* 2026-10-01: .bar/.bars 收进 .chart3d 作用域（图表专用类名原本写成裸类选择器，误伤全站 class="bar"）。
   受害：网盘侧栏存储条 #driveApp .bar 继承这里的 flex-direction:column + gap:8px，
   被压成竖排 52px 再被 height:6px 裁掉 -> 只看得见第 1 段，其余 3 段不可见；备忘录进度条同理。 */
.chart3d .bars { display: flex; align-items: flex-end; gap:var(--space-10px); height: 170px; margin-top:var(--space-22px); }
.chart3d .bar { flex: 1; display: flex; flex-direction: column; align-items: center; gap:var(--space-8px); min-width: 0; }
.chart3d .bar i { width: 100%; border-radius: var(--radius-6px-6px-3px-3px); background: linear-gradient(180deg, var(--c1), var(--c1)); min-height: 4px; animation: up .9s cubic-bezier(.2,.9,.3,1) both; }
.chart3d .bar.hot i { background: linear-gradient(180deg, var(--ok), var(--ok)); }
html.dark .chart3d .bar i { background: linear-gradient(180deg, var(--dark-primary), var(--blue-800-deep)); }
html.dark .chart3d .bar.hot i { background: linear-gradient(180deg, var(--accent), var(--cyan-700)); box-shadow: 0 0 16px -2px color-mix(in srgb, var(--accent) 70%, transparent); }
html.dark .chart3d .bar.down i { background: linear-gradient(180deg, var(--pink-400), var(--rose-800)); box-shadow: 0 0 16px -2px color-mix(in srgb, var(--pink-400) 60%, transparent); }
.chart3d .bar span { font-family: var(--mono); font-size: var(--fs-2xs); color: var(--tx3); }
@keyframes up { from { height: 4px !important; } }
.legend { display: flex; gap:var(--space-18px); margin-top:var(--space-18px); font-size: var(--fs-2xs); color: var(--tx3); align-items: center; flex-wrap: wrap; }
.legend span { display: inline-flex; gap:var(--space-6px); align-items: center; }
/* V5.1.6：图例点改正圆、去 2px 微圆角（8×8 的小点用正圆更干净） */
.legend i { width: 8px; height: 8px; border-radius: var(--radius-50); background: var(--ok); }
.legend i.c2 { background: var(--c1); }
.legend i.r { background: var(--bad); }

.feed { padding:var(--space-22px); overflow: hidden; }
.feed h4, .feed h3 { font-size: var(--fs-md); font-weight: 700; color: var(--tx); margin-bottom:var(--space-10px); display: flex; justify-content: space-between; }
.feed h4 .more, .feed h3 .more { font-size: var(--fs-2xs); color: var(--c1); font-weight: 600; cursor: pointer; }
.fitem { display: flex; gap:var(--space-13px); padding:var(--space-10px-12px); border-radius: var(--radius-12px); align-items: center; transition: background .15s; }
.fitem:hover { background: color-mix(in srgb, var(--text) 5%, transparent); }
html.dark .fitem:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.fitem:hover .fi { border-color: var(--c1); color: var(--c1); }
.fitem .fi { width: 36px; height: 36px; border-radius: var(--radius-10px); flex: none; display: grid; place-items: center; font-size: var(--fs-base); background: var(--panel); border: 1px solid var(--line2); }
.fitem .fbody { min-width: 0; flex: 1; }
.fitem .fbody b { font-size: var(--fs-sm); display: block; color: var(--tx); }
.fitem .fbody span { font-size: var(--fs-2xs); color: var(--tx3); display: block; margin-top:var(--space-2px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fitem .ft { font-family: var(--mono); font-size: var(--fs-2xs); color: var(--tx3); margin-left: auto; flex: none; }
.empty { text-align: center; padding:var(--space-34px-10px); color: var(--tx3); font-size: var(--fs-base); line-height: var(--lh-body); }

/* Bento 模块 */
.mods { display: grid; grid-template-columns: repeat(6, 1fr); gap:var(--space-16px); }
/* ★ V5.1.5：`.mod--big` 默认 `span 4`，而 ≤1000px 时网格只剩 2 列 ⇒
   span 4 **超出显式列数，浏览器会新建 2 个 auto 隐式列**（列宽由内容决定），
   实测 900px 下同行出现「154px + 672px」的参差、大卡被拉到 672px。
   ⇒ 断点里**同时**把 `.mod` / `.mod--big` 收敛为 span 1，两列等宽。（≤600px 原本已这么做） */
@media (max-width: 1000px) { .mods { grid-template-columns: repeat(2, 1fr); } .mod, .mod--big { grid-column: span 1 !important; } }
@media (max-width: 600px) { .mods { grid-template-columns: 1fr; } .mod { grid-column: span 1 !important; } .mod--big { grid-column: span 1 !important; } }
.mod { padding:var(--space-20px); min-height: 150px; display: flex; flex-direction: column; justify-content: space-between; grid-column: span 2; overflow: hidden; cursor: pointer; }
.mod .top { display: flex; justify-content: space-between; align-items: center; }
.mod .ic { width: 42px; height: 42px; border-radius: var(--radius-12px); display: grid; place-items: center; font-size: var(--fs-lg); background: linear-gradient(150deg, var(--text), var(--blue-100-soft)); }
html:not(.dark) .mod .ic { background: linear-gradient(150deg, var(--gray-100), var(--gray-150)); }
.mod .cnt { font-family: var(--mono); font-size: var(--fs-2xs); font-weight: 700; color: var(--c1); background: var(--panel); border: 1px solid var(--line2); padding:var(--space-3px-10px); border-radius: var(--radius-8px); white-space: nowrap; }
.mod b { font-size: var(--fs-md); font-weight: 700; margin-top:var(--space-6px); letter-spacing: -0.2px; display: block; color: var(--tx); }
.mod p { font-size: var(--fs-sm); color: var(--tx2); line-height: var(--lh-ui); margin-top:var(--space-4px); }
.mod--big { grid-column: span 4; min-height: 170px; }
/* V5.1.6：icon 描边已删 ⇒ 该条 border-color 规则空转，删除；hover 位移同步去掉（与 .d3:hover 口径一致） */
.mod--big:hover, .mod:hover { border-color: var(--border-strong) !important; }

.duo { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); gap:var(--space-18px); align-items: stretch; }
@media (max-width: 1140px) { .duo { grid-template-columns: 1fr !important; } }
.goal { padding:var(--space-22px); }
.goal .row { display: flex; align-items: center; gap:var(--space-14px); padding: 11px 0; border-bottom: 1px solid var(--line2); }
.goal .row:last-child { border: none; }
.goal .gp { flex: none; width: 64px; text-align: right; }
.goal .gv { font-family: var(--mono); font-weight: 700; font-size: var(--fs-base); color: var(--c1); font-variant-numeric: tabular-nums; }
.goal .gl { min-width: 0; }
.goal .gl b { display: block; font-size: var(--fs-xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--tx); }
.goal .gl span { font-size: var(--fs-2xs); color: var(--tx3); }
.goal .days { margin-left: auto; flex: none; font-family: var(--mono); font-size: var(--fs-2xs); font-weight: 700; color: var(--c1); background: var(--panel); border: 1px solid var(--line2); padding:var(--space-3px-9px); border-radius: var(--radius-99px); white-space: nowrap; }
.goal .days.warn { color: var(--warn); }
.goal .days.bad { color: var(--bad); }

/* ★ V5.1.5：快捷导航卡栅格 —— 按「容器实际可用宽度」分档定列数。
   原实现只在 ≤680px 才降 2 列 ⇒ **≥1024 与 768–1023 都是 4 列**：
   实测 1440（`.duo` 右列仅 685px）卡宽 **154px**、768 视口 **160px** —— 卡过窄、
   标题（16px）大面积被 ellipsis 截断，卡宽与文字比例失衡；且 7 张卡必现 4+3 的末行缺口。
   分档：>1140（duo 右列）3 列 ｜ ≤1140（duo 塌陷为全宽）4 列 ｜ 768–1023 3 列
        ｜ <768 2 列 ｜ ≤420 1 列（390 视口下 2 列只剩 157px，1 列 290px 更可读）。
   ★ 2026-10-01：换成 flex-wrap（每卡等宽 = (100% - 间距)/列数）。
   为什么从 grid 换过来：卡片数由用户增删、不固定 ⇒ grid 的列数必须靠断点写死，
   而末行缺口位置会随卡数变化；flex-wrap 下每张卡按同一 flex-basis 排，行内左起依次摆放。
   ⚠️ 对齐口径（用户指定）：**左侧对齐 + 顶端对齐** —— justify-content/align-items/align-content 全部 flex-start：
   · justify-content:flex-start ⇒ 不满的最后一行仍从左端起（末行 1 张时靠左，不居中）；
   · align-items:flex-start + align-content:flex-start ⇒ 行内顶端对齐、多行整体贴容器顶部（.duo 是 stretch，
     .quick 会被拉到与左列等高，多出来的空白留在下方而不是把行撑开居中）。
   .q-bar 与「+添加」卡用 flex:0 0 100% 独占整行，行为与原 grid-column:1/-1 一致。
   断点列数不变：>1140 三列｜≤1140 四列｜≤1023 三列｜≤767 两列｜≤420 一列。 */
.quick { width: auto !important; min-width: 0; display: flex !important; flex-wrap: wrap !important; justify-content: flex-start !important; align-content: flex-start !important; align-items: flex-start !important; gap:var(--space-13px) !important; padding:var(--space-18px) !important; }
.quick > .q-bar, .quick > .q.add { flex: 0 0 100% !important; }
.quick > .q { flex: 0 0 calc((100% - 2 * var(--space-13px)) / 3) !important; min-width: 0; }
@media (max-width: 1140px) { .quick { width: 100% !important; } .quick > .q { flex-basis: calc((100% - 3 * var(--space-13px)) / 4) !important; } }
@media (max-width: 1023px) { .quick > .q { flex-basis: calc((100% - 2 * var(--space-13px)) / 3) !important; } }
@media (max-width: 767px)  { .quick > .q { flex-basis: calc((100% - var(--space-13px)) / 2) !important; } }
@media (max-width: 420px)  { .quick > .q { flex-basis: 100% !important; } }
.quick .q { display: flex; gap:var(--space-10px); align-items: center; justify-content: flex-start; text-align: left; padding:var(--space-11px-13px); min-height: 64px; border-radius: var(--radius-13px); background: var(--panel); border: 1px solid var(--line2); transition: .15s; position: relative; cursor: pointer; overflow: hidden; }
.quick .q:hover { border-color: var(--c1); box-shadow: 0 8px 22px var(--glow-primary); }
.quick .q .qi { width: 40px; height: 40px; border-radius: var(--radius-12px); flex: none; display: grid; place-items: center; background: var(--panel); border: 1px solid var(--line2); }
/* flex:1 + min-width:0 是「文字区能被压缩 ⇒ ellipsis 才生效」的前提（缺一不可） */
.quick .q .qt { display: flex; flex-direction: column; align-items: flex-start; flex: 1 1 auto; min-width: 0; }
.quick .q .qt b { font-size: var(--fs-base); line-height: var(--lh-tight); font-weight: 700; letter-spacing: -0.2px; display: block; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--tx); }
/* min-height 占位：描述为空的卡（用户新建时可不填说明）也要占满一行 ⇒ 全区域卡片等高 */
.quick .q .qt .sub { display: block; font-size: var(--fs-sm); line-height: var(--lh-ui); min-height: 1.45em; color: var(--tx3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; margin-top:var(--space-2px); }
/* 管理模式：卡片右上角浮出「编辑 / 删除」两个按钮 ⇒ 右侧留位，避免压住标题与说明 */
body.manage .quick .q { padding-right: 52px; }
.quick .q.add { grid-column: 1/-1; justify-content: center; gap:var(--space-7px); border-style: dashed; border-color: var(--line2); color: var(--tx3); font-size: var(--fs-2xs); min-height: 46px; }
.quick .q.add:hover { color: var(--c1); }
.ic svg, .qi svg, .fi svg, .menu i svg, .st .ic svg { display: block; }
body.manage .quick .q { cursor: grab; }
.quick .q.dragging { opacity: .35; }
.chip-act { position: absolute; top: 4px; right: 4px; display: none; gap:var(--space-4px); z-index: 5; }
body.manage .chip-act { display: flex; }
.ca { width: 20px; height: 20px; border-radius: var(--radius-6px); border: 1px solid var(--line2); background: var(--panel); color: var(--tx2); font-size: var(--fs-2xs); line-height: 1; display: grid; place-items: center; padding: 0; cursor: pointer; }
.ca:hover { border-color: var(--c1); color: var(--c1); }
.ca.del:hover { background: var(--danger-soft); border-color: var(--danger); color: var(--danger); }

/* 数据表 */
.mtable { width: 100%; border-collapse: collapse; font-size: var(--fs-2xs); }
.mtable th { text-align: left; font-size: var(--fs-2xs); letter-spacing: .08em; color: var(--tx3); font-weight: 700; padding:var(--space-6px-8px); border-bottom: 1px solid var(--line); background: var(--panel-2); }
html.dark .mtable th { color: var(--slate-400); }
.mtable td { padding:var(--space-6px-8px); border-bottom: 1px solid var(--line2); vertical-align: middle; color: var(--tx2); font-variant-numeric: tabular-nums; }
.mtable td, .mtable .inp { font-variant-numeric: tabular-nums; }
.mtable .inp { background: var(--input-bg); border: 1px solid var(--line2); color: var(--tx); border-radius: var(--radius-9px); padding:var(--space-7px-9px); font-size: var(--fs-input); font-family: inherit; outline: none; width: 100%; }
.mtable .inp:focus { border-color: var(--c1); }
.mtable td .sel-color { min-width: 130px; }
.sw-wrap { overflow-x: auto; max-height: 56vh; }

/* ==================== ⑤ 弹层 / 表单 / 按钮 / Toast / 脚注 ==================== */
.mo {
  position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center;
  padding:var(--space-20px); background: color-mix(in srgb, var(--text) 35%, transparent); opacity: 0; visibility: hidden; transition: .18s;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
}
html.dark .mo { background: color-mix(in srgb, var(--haven-night-950) 60%, transparent); }
.mo.show { opacity: 1; visibility: visible; }
.mo .box {
  width: 100%; background: var(--panel); border: 1px solid var(--border-cyber); border-radius: var(--radius-18px);
  box-shadow: 0 30px 70px -24px color-mix(in srgb, var(--text) 45%, transparent); padding:var(--space-22px); transform: translateY(10px) scale(.98);
  transition: .18s; max-height: calc(100vh - 40px); max-height: calc(100dvh - 40px); overflow-y: auto; -webkit-overflow-scrolling: touch;
}
html.dark .mo .box { background: linear-gradient(165deg, color-mix(in srgb, var(--haven-night-700) 99%, transparent), color-mix(in srgb, var(--haven-night-950) 99%, transparent)); box-shadow: 0 30px 70px -24px color-mix(in srgb, var(--black) 95%, transparent); }
.mo.show .box { transform: none; }
.mo .box.wide { max-width: 820px; }
.mo .box.narrow { max-width: 420px; }
.box .hdr { display: flex; align-items: center; justify-content: space-between; margin-bottom:var(--space-18px); }
.box .hdr h3 { font-size: var(--fs-sm); font-weight: 700; letter-spacing: -0.2px; color: var(--tx); }
.box .hdr .x { border: none; background: none; color: var(--tx3); font-size: var(--fs-lg); line-height: 1; cursor: pointer; width: 28px; height: 28px; border-radius: var(--radius-8px); }
.box .hdr .x:hover { color: var(--tx); background: var(--panel); }
.f-grid { display: grid; grid-template-columns: 1fr 1fr; gap:var(--space-14px); }
@media (max-width: 560px) { .f-grid { grid-template-columns: 1fr; } }
.f { display: flex; flex-direction: column; gap:var(--space-7px); margin-bottom:var(--space-14px); }
.f label { font-size: var(--fs-2xs); font-weight: 700; color: var(--tx2); letter-spacing: .02em; }
.f .inp { background: var(--input-bg); border: 1px solid var(--line2); color: var(--tx); border-radius: var(--radius-11px); padding:var(--space-10px-12px); font-size: var(--fs-input); font-family: inherit; outline: none; width: 100%; transition: .15s; }
html.dark .f .inp { background: color-mix(in srgb, var(--primary) 6%, transparent); border-color: var(--line); }
.f .inp:focus { border-color: var(--c1); box-shadow: 0 0 0 3px var(--glow-accent); }
.f .inp::placeholder { color: var(--tx3); }
.f .hint { font-size: var(--fs-2xs); color: var(--tx3); }
.acts { display: flex; justify-content: flex-end; gap:var(--space-10px); margin-top:var(--space-20px); }
.btn { border-radius: var(--radius-10px); padding:var(--space-8px-16px); font-size: var(--fs-sm); font-weight: 700; font-family: inherit; border: 1px solid var(--line2); background: transparent; color: var(--tx2); transition: .15s; cursor: pointer; }
.btn:hover { color: var(--c1); border-color: var(--c1); }
.btn.pri { background: var(--pri-gradient); color: var(--white); border-color: transparent; font-weight: 700; }
.btn.pri:hover { background: var(--pri-hover); color: var(--white); }
.btn.ghost { background: transparent; color: var(--tx2); }
.btn.danger { color: var(--danger); border-color: color-mix(in srgb, var(--rose-700) 35%, transparent); }
.btn.danger:hover { background: var(--danger-soft); color: var(--danger); border-color: var(--danger); }
.btn:disabled { opacity: .5; cursor: default; }

/* ---------- ui.js 语义按钮（.btn-primary / -secondary / -danger）----------
   ⚠️⚠️ 2026-09-21 修复「确认弹窗的确定按钮颜色遮挡文字」（用户反馈，截图明确）。
   ui.js 的 makeBtn() 产出的是 **`.btn .btn-primary`** 这套「连字符」类名，
   但本文件（geek.css）只定义了 **`.btn.pri` / `.btn.ghost` / `.btn.danger`** 这套
   「短名」类名 —— 两者**完全不相交**。于是出现两个叠加的 bug：

   ① **常态就没有底色**：theme.css 的 `.btn-primary{background:var(--primary)}` 权重 (0,1,0)，
      被本文件上方 `.btn{background:transparent}` 的 (0,1,0) **同权重后加载**压掉
      ⇒ 按钮 computed backgroundColor = transparent（实测证实）。
   ② **hover 时文字与底色同色（视觉上就是"颜色遮挡文字"）**：
      本文件 `.btn:hover{color:var(--c1)}` 权重 (0,2,0) 压过 theme.css
      `.btn-primary{color:var(--white)}` 的 (0,1,0) ⇒ 文字变靛蓝；
      而 theme.css `.btn-primary:hover{background:var(--primary-strong)}` 权重 (0,2,0)
      **恰好与 .btn:hover 同级、且 theme.css 先加载** ⇒ 底色照常变靛蓝。
      结果：靛蓝字 + 靛蓝底。canvas 页实测 **对比度 1.68:1**（AA 正文要求 ≥4.5）。
      ⚠️ 这也解释了"只在 hover 时明显"——ui.js 的 confirm() 会 `okBtn.focus()`，
      鼠标恰好停在确定键上，截图那一刻正是 hover 态。

   修法（V1.9.3 最终版）：本文件**自己把这三个语义类补齐**，不再指望 theme.css。
   ⚠️ 为什么不能只"排除 .btn:hover"就完事（上一版就是这么写的，没通过验证）：
      本文件第 423 行 `.btn{background:transparent}` 用的是 **`background` 简写**，
      简写会把 `background-image` 一并重置为 `none`；而 geek.css 在 theme.css **之后**加载
      ⇒ theme.css 的 `.btn-primary{background:var(--primary)}` 被整体抹掉。
      实测（_varcheck.js）：light/dark 两态下按钮 `bgColor` 恒为 `transparent`、
      `bgImage` 恒为 `none` —— **它在两个主题里都根本没有底色**。
      （深色态一度"看着没问题"，只是因为 --logo-fg 深字压在弹窗深底上碰巧可读。）
   ⇒ 所以必须**在 geek.css 里重新声明 background**，且用 `background:` 简写
      （而不是 background-color + background-image 分开写，避免简写再次互踩）。
      选择器用 `.btn.btn-xxx`（0,2,0），稳压第 423 行的 `.btn`（0,1,0）。 */
.btn.btn-primary { background: var(--pri-gradient); color: var(--white); border-color: transparent; font-weight: 700; }
.btn.btn-primary:hover { background: var(--pri-hover); color: var(--white); border-color: transparent; }
.btn.btn-secondary { background: var(--surface); color: var(--text); border-color: var(--border); }
.btn.btn-secondary:hover { background: var(--surface-2); color: var(--c1); border-color: var(--c1); }
/* 危险按钮：theme.css 的 --danger 是纯色，统一改成渐变，与 .btn.pri 同一语言
   2026-09-23：亮端 var(--haven-red-600) 上白字仅 4.38:1，改用 var(--danger)（5.58:1） */
.btn.btn-danger { background: linear-gradient(135deg, var(--danger), var(--rose-700)); color: var(--white); border-color: transparent; font-weight: 700; }
.btn.btn-danger:hover { background: linear-gradient(135deg, var(--rose-800), var(--rose-700)); color: var(--white); border-color: transparent; }
/* 上面那条 `.btn.btn-*:hover{color:inherit}` 是给 ".btn:hover 污染"兜底的，
   这里已经被更具体的规则覆盖，保留它只为说明历史；下面两条确保它不会反噬： */
.btn.btn-primary:hover,
.btn.btn-danger:hover { color: var(--white); border-color: transparent; }

/* ---------- 深色主题：主色按钮的文字必须用**深色** ----------
   ⚠️ 2026-09-21 补修。深色主题的 --primary 是**亮色**（geek.css 569 行 var(--dark-primary-2)，
   theme.css 66 行 var(--accent)），白字压在上面只有 2.41:1 / 1.81:1 —— 等于看不见。
   实测（白字 vs 近黑字）：
     白字 on var(--dark-primary-2) = 2.41:1  ❌      近黑字(var(--dark-bg-deep)) on var(--dark-primary-2) = 8.35:1  ✅
     白字 on var(--accent) = 1.81:1  ❌      近黑字 on var(--accent)          = 10.90:1 ✅
   `.nb.pri`（187 行）与 `.btn.pri`（425 行）本来就用 `--logo-fg`
   （深色态 = var(--dark-bg-logo)）解决过同一个问题，只有 ui.js 这套 `.btn-primary` 漏了。
   ⚠️ 深色态主色是浅色渐变 ⇒ 用 --pri-gradient 打底也不该配白字，故文字统一 --logo-fg。
      危险按钮保持白字（var(--danger) 系是深红，白字 5.58:1 达标）。 */
html.dark .btn.btn-primary,
html.dark .btn.btn-primary:hover { color: var(--logo-fg); }

/* 键盘可达性：ui.js 会 okBtn.focus()，焦点环不能省 */
.btn.btn-primary:focus-visible,
.btn.btn-secondary:focus-visible,
.btn.btn-danger:focus-visible { outline: 2px solid var(--c1); outline-offset: 2px; }

#toast {
  position: fixed; bottom: calc(26px + env(safe-area-inset-bottom)); left: 50%; transform: translate(-50%, 80px);
  z-index: 1200; background: var(--pri-gradient); color: var(--white); font-size: var(--fs-2xs); font-weight: 700;
  padding:var(--space-10px-18px); border-radius: var(--radius-12px); box-shadow: 0 18px 40px -12px color-mix(in srgb, var(--text) 40%, transparent);
  opacity: 0; transition: .25s; pointer-events: none; white-space: normal; text-align: center; max-width: calc(100vw - 40px);
}
#toast.show { opacity: 1; transform: translate(-50%, 0); }
#toast.err { background: var(--danger); color: var(--white); }

.foot { margin: 46px 0 30px; padding-top:var(--space-16px); border-top: 1px solid var(--line2); display: flex; justify-content: space-between; font-family: var(--mono); font-size: var(--fs-2xs); color: var(--tx3); flex-wrap: wrap; gap:var(--space-8px); }

/* ==================== 5.5 业务页既有动作按钮地极客化 ==================== */
/* 业务页 header 内 .site-btn（主题/返回/退出等）由 sidebar.js 归位进 .op 后套用此观感 */
.op .site-btn,
.site-btn {
  display: inline-flex; align-items: center; justify-content: center; gap:var(--space-6px);
  padding:var(--space-7px-13px); border-radius: var(--radius-9px); border: 1px solid var(--line2);
  background: transparent; color: var(--tx2); font-size: var(--fs-xs); font-weight: 600;
  transition: .15s; cursor: pointer; text-decoration: none; line-height: 1; white-space: nowrap;
}
.op .site-btn svg { display: block; }
.site-btn:hover { color: var(--c1); border-color: var(--c1); }
.site-btn.icon { width: 34px; height: 34px; padding: 0; border-color: var(--line2); background: var(--panel); }
.site-btn.icon svg { width: 15px; height: 15px; }
.site-btn.danger:hover { color: var(--danger); border-color: var(--danger); background: var(--danger-soft); }
.site-btn.primary { background: var(--pri-gradient); color: var(--white); border-color: transparent; font-weight: 700; }
.site-btn.primary:hover { background: var(--pri-hover); color: var(--white); }

/* ==================== ⑥ 移动端汉堡 + 右侧抽屉 ==================== */
html { -webkit-text-size-adjust: 100%; }
* { -webkit-tap-highlight-color: transparent; }
button, .nb, .mod, .q, .mi, .topnav-item, .menu a { -webkit-touch-callout: none; touch-action: manipulation; }

.menu-btn { display: none; }
@media (max-width: 860px) {
  .menu-btn { display: grid; }
  .tb #btn-batch-manage, .tb #btn-manage-mode, .tb #btn-admin-panel { display: none !important; }
}

/* 遮罩（视觉压暗，不响应点击关闭）
   ★ V4.0.1（用户反馈）：「菜单展开时，左侧空白处不允许对后方页面滑动」。
     遮罩本身能挡点击，但它不是滚动容器 ⇒ 手指按在遮罩上滑动，触链会一路冒泡到 html/body，
     后方页面照样被滚。这里补两层：`.show` 时 `touch-action:none`（吃掉该元素上的所有触摸手势）
     + `overscroll-behavior:contain`（不把滚动传给祖先）；同时 sidebar.js 会给 <html> 打
     `m-drawer-open` 冻结文档滚动（下一条）。 */
.m-mask { position: fixed; inset: 0; z-index: 190; background: color-mix(in srgb, var(--text) 40%, transparent); opacity: 0; visibility: hidden; transition: .2s; pointer-events: none; }
html.dark .m-mask { background: color-mix(in srgb, var(--haven-night-950) 55%, transparent); }
.m-mask.show { opacity: 1; visibility: visible; pointer-events: auto; touch-action: none; overscroll-behavior: contain; }

/* 抽屉展开期间冻结文档滚动（类由 sidebar.js 的 lockPageScroll() 打 / 撤，关闭时精确移除）。
   ⚠️ 只冻结 html/body —— 页面内部的滚动容器不在此列，但它们都被遮罩盖住（且遮罩 touch-action:none）。 */
html.m-drawer-open, html.m-drawer-open body { overflow: hidden; }

/* 右侧抽屉 */
.m-drawer {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(84vw, 320px); z-index: 200;
  background: linear-gradient(165deg, color-mix(in srgb, var(--white) 98%, transparent), color-mix(in srgb, var(--haven-slate-50) 99%, transparent));
  border-left: 1px solid var(--topbar-border);
  box-shadow: -30px 0 60px -30px color-mix(in srgb, var(--text) 35%, transparent);
  transform: translateX(calc(100% + 80px)); transition: transform .26s cubic-bezier(.4, 0, .2, 1);
  /* ★ V4.0.1：底部安全区**只由最后一个子元素（.m-foot）承担一次**。
     原来这里 + `.m-actions` + `.m-foot` 三处都加 `env(safe-area-inset-bottom)` ⇒ 刘海机底部
     会多出约两倍安全区的空白。本行不再自带 padding-bottom。 */
  display: flex; flex-direction: column;
}
html.dark .m-drawer { background: linear-gradient(165deg, color-mix(in srgb, var(--haven-night-900) 98%, transparent), color-mix(in srgb, var(--haven-night-950) 99%, transparent)); box-shadow: -30px 0 60px -30px color-mix(in srgb, var(--black) 80%, transparent); }
.m-drawer.open { transform: none; }
.m-drawer-hd { display: flex; align-items: center; justify-content: space-between; padding:var(--space-14px-16px); border-bottom: 1px solid var(--line2); flex: none; }
.m-drawer-hd b { font-size: var(--fs-sm); font-weight: 700; letter-spacing: -0.2px; color: var(--tx); }
.m-drawer-hd .x { border: none; background: none; color: var(--tx3); font-size: var(--fs-lg); line-height: 1; width: 34px; height: 34px; border-radius: var(--radius-10px); display: grid; place-items: center; cursor: pointer; }
.m-drawer-hd .x:active { background: var(--line2); color: var(--tx); }
.m-nav { flex: 1; overflow-y: auto; padding:var(--space-10px-12px-6px); -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }
.m-nav .mg { margin-bottom:var(--space-8px); }
.m-nav .mg-l { font-family: var(--mono); font-size: var(--fs-2xs); letter-spacing: .14em; color: var(--tx3); padding:var(--space-10px-8px-4px); }
.m-nav .mi { display: flex; align-items: center; gap:var(--space-12px); padding:var(--space-11px-10px); border-radius: var(--radius-11px); color: var(--tx2); font-size: var(--fs-sm); font-weight: 600; text-decoration: none; }
.m-nav .mi:active { background: var(--line2); color: var(--tx); }
.m-nav .mi.on { color: var(--c1); }
.m-nav .mi i { width: 30px; height: 30px; flex: none; border-radius: var(--radius-9px); display: grid; place-items: center; font-style: normal; background: var(--panel); border: 1px solid var(--line2); }
/* ★ V4.0.1：左右 padding 与 `.m-nav`（10px 12px）对齐 —— 原来 14px 会让底部按钮比上方导航项右移 2px。 */
.m-actions { border-top: 1px solid var(--line2); padding:var(--space-12px); display: flex; flex-direction: column; gap:var(--space-8px); flex: none; }
/* ★ V4.0.1：底部动作区与上方导航项**同规格**（行高 / 圆角 / 左右留白 / 30px 图标槽一致）。
   原来是 `10px 12px` 且无图标，与 `.m-nav .mi`（30px 图标 + 11px 行距 + 11px 圆角）看起来是两套语言。 */
.m-actions .nb { width: 100%; display: flex; align-items: center; gap:var(--space-12px); padding:var(--space-11px-10px); border-radius: var(--radius-11px); font-size: var(--fs-sm); font-weight: 600; text-align: left; color: var(--tx2); }
.m-actions .nb i { width: 30px; height: 30px; flex: none; border-radius: var(--radius-9px); display: grid; place-items: center; font-style: normal; background: var(--panel); border: 1px solid var(--line2); }
.m-actions .nb i svg { width: 16px; height: 16px; display: block; }
.m-actions .nb:active { background: var(--line2); color: var(--tx); }
.m-foot { padding:var(--space-12px-16px-2px); font-family: var(--mono); font-size: var(--fs-2xs); color: var(--tx3); letter-spacing: .12em; flex: none; }

/* ==================== ⑦ Theme Token Bridge ====================
 * 令牌唯一真源已迁移到 haven-tokens.css。
 * 本段不再重复声明颜色/圆角/阴影，避免 geek.css 反向覆盖 theme.css。
 * ============================================================ *//* ==================== ⑧ tailwind 中性色映射 + 卡片地极客化 ==================== */
/* 深色 */
html.dark .text-neutral-900, html.dark h1, html.dark h2, html.dark h3, html.dark h4, html.dark .text-neutral-800 { color: var(--text); }
html.dark .text-neutral-700, html.dark .text-neutral-600 { color: var(--text-2); }
html.dark .text-neutral-500, html.dark .text-neutral-400 { color: var(--text-3); }
html.dark .bg-white, html.dark .bg-white\/70, html.dark .bg-white\/80 { background-color: var(--surface); }
html.dark .bg-neutral-100, html.dark .bg-neutral-50 { background-color: var(--surface-2); }
html.dark .bg-neutral-200, html.dark .bg-neutral-200\/60 { background-color: var(--border-2); }
html.dark .border-neutral-200, html.dark .border-neutral-300 { border-color: var(--border); }
html.dark .border-neutral-100 { border-color: var(--border-2); }
/* 浅色（地极客化中性色） */
html:not(.dark) .text-neutral-900, html:not(.dark) h1, html:not(.dark) h2, html:not(.dark) h3, html:not(.dark) h4, html:not(.dark) .text-neutral-800 { color: var(--text); }
html:not(.dark) .text-neutral-700, html:not(.dark) .text-neutral-600 { color: var(--text-2); }
html:not(.dark) .text-neutral-500, html:not(.dark) .text-neutral-400 { color: var(--text-3); }
html:not(.dark) .bg-white, html:not(.dark) .bg-white\/70, html:not(.dark) .bg-white\/80 { background-color: var(--surface); }
html:not(.dark) .bg-neutral-100, html:not(.dark) .bg-neutral-50 { background-color: var(--surface-2); }
html:not(.dark) .bg-neutral-200, html:not(.dark) .bg-neutral-200\/60 { background-color: var(--border-2); }
html:not(.dark) .border-neutral-200, html:not(.dark) .border-neutral-300 { border-color: var(--border); }
html:not(.dark) .border-neutral-100 { border-color: var(--border-2); }

/* ==================== ⑨ tailwind slate / rose 语义色桥接（V5.1.3） ====================
 * ★ 为什么补这段：`neutral` 系列早先已桥接，但 **slate / rose 没接** ⇒ 直接用 tailwind 原值，
 *   实测在浅色下**对比度不足**（`_ui-quality-audit.js` 实测）：
 *     · `text-slate-400` = #94a3b8 对白底 **2.56:1**（需 4.5）—— tools 页 14 处
 *     · `text-rose-500`  = #f43f5e 对 #fff1f2 **3.34:1** —— tools / milestones 徽标
 *   桥接到令牌后：slate-400 → --text-3 = **6.22:1**，rose-500 → --danger = **5.7:1**。
 * ⚠️ 安全性已核：tools 页的 `bg-slate-800/900` 深底配的是 `text-white`，不受本段影响。
 * ⚠️ 与 neutral 同构：**必须同时写 dark 与 not(.dark) 两组**，否则深色模式会掉回 tailwind 原值。 */
html:not(.dark) .text-slate-900, html:not(.dark) .text-slate-800 { color: var(--text); }
html:not(.dark) .text-slate-700, html:not(.dark) .text-slate-600 { color: var(--text-2); }
html:not(.dark) .text-slate-500, html:not(.dark) .text-slate-400, html:not(.dark) .text-slate-300 { color: var(--text-3); }
html:not(.dark) .text-rose-600, html:not(.dark) .text-rose-500, html:not(.dark) .text-rose-400 { color: var(--danger); }
html.dark .text-slate-900, html.dark .text-slate-800 { color: var(--text); }
html.dark .text-slate-700, html.dark .text-slate-600 { color: var(--text-2); }
html.dark .text-slate-500, html.dark .text-slate-400, html.dark .text-slate-300 { color: var(--text-3); }
html.dark .text-rose-600, html.dark .text-rose-500, html.dark .text-rose-400 { color: var(--danger); }

/* ★ tailwind **任意值字号**兜底：`.text-[10px]` / `.text-[11px]` 全站共 44 处（admin 为主，tools/account 亦有），
   都**低于我们定的 12px 下限**。这里统一抬到 `--fs-2xs`(12px)，不改标记。
   （用 `html ` 前缀提权，免受 tailwind 加载顺序影响；`\[` `\]` 需转义。） */
html .text-\[10px\], html .text-\[11px\] { font-size: var(--fs-2xs); }

/* 卡片 / 输入框 / 渐变文字（供 account/admin/finance/travel 等复用） */
.plain-card { background-color: var(--surface); border: 1px solid var(--border); box-shadow: var(--shadow); border-radius: var(--radius); }
.glass-input { background-color: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-1rem); transition: all 0.3s; width: 100%; padding: 0.75rem 1rem; font-size: var(--fs-input); color: var(--text); }
.glass-input:focus { border-color: var(--primary); outline: none; }
.brand-gradient { background: var(--gradient); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
.gradient-text { background: var(--gradient); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; color: transparent; }
.badge { background: var(--surface-2); color: var(--text-2); border: 1px solid var(--border); }
.badge-purple { background: var(--primary-soft); color: var(--primary); border-color: transparent; }

/* ==================== ⑨ 背景装饰 / 滚动条 ==================== */
/* 极客网格 + 光晕背景（深色）/ 矩阵 + 靛蓝光(浅色) */
.bgfx { position: fixed; inset: 0; z-index: -2; overflow: hidden; pointer-events: none; }
/* ⚠️ 2026-09-23（V2.0.1）网格线已上移到 body::after 统一承担。
   原因：网格原本只写在 .bgfx 上，而全站只有 finance / milestones / update-log 三页有 <div class="bgfx">，
   其余 8 页（备忘录 / 画布 / 日记 / 联系人 / 网盘 / 市场 / 账户 / 后台 / 工具箱 / 旅行）根本没有网格。
   这里清空 .bgfx 的浅色背景，避免它和 body::after 的网格叠加成双倍浓度（.055 叠 .055 ≈ .107）。 */
html:not(.dark) .bgfx { background: none; }
/* 降级态（html.no-glass）会隐藏 body::after，此时由 .bgfx 把网格接回来 */
html.no-glass:not(.dark) .bgfx {
  background:
    linear-gradient(color-mix(in srgb, var(--primary) 6%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--primary) 6%, transparent) 1px, transparent 1px);
  background-size: 48px 48px, 48px 48px;
}
/* 同上（2026-09-23 V2.0.1）：深色网格也已上移到 body::after，这里清空避免双倍浓度 */
html.dark .bgfx { background: none; }
/* 背景模糊光球：浅深均停用（底色光晕统一由浅色「无」+ 深色「body::after/gl-blobs」承担） */
.bgfx i { display: none; }

/* 既有 .bg-orb 全量停用（底色光晕统一由浅色「无」+ 深色「gl-blobs/body::after 夜空 orbs」承担） */
.bg-orb { display: none; }
.orb-1 { width: 600px; height: 600px; background: radial-gradient(circle, var(--brand-mark-dark) 0%, transparent 70%); top: -200px; left: -100px; }
.orb-2 { width: 500px; height: 500px; background: radial-gradient(circle, var(--brand-mark-dark) 0%, transparent 70%); bottom: -150px; right: -100px; }

/* ==================== ⑩ 管理员模拟视图 UI（切换数据按钮 / 横幅 / 弹窗） ==================== */

/* 切换数据悬浮按钮（fixed 左下角） */
/* V5.1.6：FAB 发光收敛 —— 品牌色大范围 glow 换成常规投影（按钮仍醒目，但不再"发光"） */
.auth-view-fab {
  position: fixed; left: 20px; bottom: 20px; z-index: 2147482999;
  padding:var(--space-8px-16px); border-radius: var(--radius-10px); font-weight: 700;
  box-shadow: 0 4px 14px color-mix(in srgb, var(--black) 18%, transparent);
  transition: transform 0.18s cubic-bezier(.2,.9,.3,1.5), box-shadow 0.18s;
}
.auth-view-fab:hover { transform: translateY(-1px); box-shadow: 0 6px 18px color-mix(in srgb, var(--black) 24%, transparent); }
.auth-view-fab:active { transform: translateY(0) scale(0.97); }
html.dark .auth-view-fab { box-shadow: 0 4px 16px color-mix(in srgb, var(--black) 45%, transparent); }
html.dark .auth-view-fab:hover { box-shadow: 0 6px 20px color-mix(in srgb, var(--black) 55%, transparent); }

/* 模拟视图顶部横幅 */
.auth-view-banner {
  position: fixed; top: 0; left: 0; right: 0; z-index: 2147483000;
  background: var(--pri-gradient);
  color: var(--white);
  font: 13px/1.5 var(--font);
  padding:var(--space-8px-16px);
  display: flex; align-items: center; justify-content: center; gap:var(--space-14px);
  box-shadow: 0 2px 14px color-mix(in srgb, var(--primary) 32%, transparent);
  flex-wrap: wrap;
}
html.dark .auth-view-banner { box-shadow: 0 2px 18px color-mix(in srgb, var(--dark-primary) 40%, transparent); }
.auth-view-banner-text { display: inline-flex; align-items: center; gap:var(--space-8px); }
.auth-view-banner-text b { font-weight: 700; border-bottom: 1px dashed color-mix(in srgb, var(--white) 55%, transparent); padding-bottom:var(--space-1px); }
.auth-view-banner-exit {
  background: color-mix(in srgb, var(--white) 18%, transparent);
  color: var(--white);
  border: 1px solid color-mix(in srgb, var(--white) 35%, transparent);
  border-radius: var(--radius-999px);
  padding:var(--space-4px-14px);
  font-size: var(--fs-2xs);
  font-weight: 600;
  cursor: pointer;
  min-height: 28px;
  font-family: var(--font);
  transition: background 0.15s;
}
.auth-view-banner-exit:hover { background: color-mix(in srgb, var(--white) 28%, transparent); }

/* 弹窗遮罩 */
.auth-view-overlay {
  position: fixed; inset: 0; z-index: 2147483100;
  background: color-mix(in srgb, var(--text) 45%, transparent);
  display: flex; align-items: center; justify-content: center; padding:var(--space-16px);
}
html.dark .auth-view-overlay { background: color-mix(in srgb, var(--haven-night-950) 72%, transparent); }

/* 弹窗盒子 — 2.5D 极客卡片风格 */
.auth-view-box {
  width: min(420px, 94vw);
  max-height: 80vh;
  overflow: auto;
  background: var(--panel-solid);
  border: 1px solid var(--border-cyber);
  border-radius: var(--radius-16px);
  padding:var(--space-20px);
  box-shadow: var(--d3-shadow);
  font-family: var(--font);
}
html.dark .auth-view-box { background: var(--d3-bg); border-radius: var(--radius-18px); }

/* 弹窗头部 */
.auth-view-head { display: flex; align-items: center; justify-content: space-between; margin-bottom:var(--space-14px); }
.auth-view-title {
  margin: 0; font-size: var(--fs-sm); font-weight: 700; color: var(--tx);
  display: inline-flex; align-items: center; gap:var(--space-8px);
}
.auth-view-title::before {
  content: ''; width: 3px; height: 14px; border-radius: var(--radius-2px);
  background: var(--pri-gradient);
}
.auth-view-close {
  background: transparent; border: none; border-radius: var(--radius-8px);
  color: var(--tx3); cursor: pointer;
  width: 32px; height: 32px; display: grid; place-items: center;
  transition: all 0.15s;
}
.auth-view-close:hover { background: color-mix(in srgb, var(--primary) 8%, transparent); color: var(--tx); }
html.dark .auth-view-close:hover { background: color-mix(in srgb, var(--primary) 12%, transparent); color: var(--white); }

/* 弹窗提示 */
.auth-view-hint {
  margin: 0 0 12px; font-size: var(--fs-2xs); color: var(--tx3);
  line-height: var(--lh-ui);
}

/* 用户列表 */
.auth-view-list { display: flex; flex-direction: column; gap:var(--space-8px); }

/* 用户行 — 极客风格 */
.auth-view-row {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; padding:var(--space-12px-16px);
  border: 1px solid var(--border);
  border-radius: var(--radius-12px);
  background: var(--panel-solid);
  cursor: pointer;
  font: 14px var(--font);
  color: var(--tx);
  text-align: left;
  transition: all 0.18s;
}
.auth-view-row:hover {
  background: color-mix(in srgb, var(--text) 4%, transparent);
  border-color: var(--border-cyber);
  transform: translateX(2px);
}
.auth-view-row:active { transform: translateX(2px) scale(0.99); }
html.dark .auth-view-row { background: color-mix(in srgb, var(--haven-night-900) 50%, transparent); border-color: var(--line); }
html.dark .auth-view-row:hover {
  background: color-mix(in srgb, var(--dark-primary) 8%, transparent);
  border-color: color-mix(in srgb, var(--dark-primary) 35%, transparent);
}

.auth-view-row-name { font-weight: 500; }

/* 用户角色徽章 */
.auth-view-badge {
  font-size: var(--fs-2xs); padding:var(--space-3px-10px); border-radius: var(--radius-999px); font-weight: 600;
  background: var(--surface-2); color: var(--tx3);
  border: 1px solid var(--border);
}
.auth-view-badge.admin {
  background: color-mix(in srgb, var(--primary) 12%, transparent);
  color: var(--c1);
  border-color: color-mix(in srgb, var(--primary) 26%, transparent);
}
html.dark .auth-view-badge { background: color-mix(in srgb, var(--primary) 8%, transparent); color: var(--tx2); border-color: var(--line); }
html.dark .auth-view-badge.admin {
  background: color-mix(in srgb, var(--dark-primary) 18%, transparent);
  color: var(--c1);
  border-color: color-mix(in srgb, var(--dark-primary) 40%, transparent);
}

/* 空状态 / 错误提示 */
.auth-view-empty { text-align: center; color: var(--tx3); padding:var(--space-20px); font-size: var(--fs-xs); }

/* ==================== ⑪ 玻璃拟态层（glassmorphism · 仅浅色） ==================== */
/* 依据：DEMO/玻璃拟态组件库Demo.html v2.4 + design-file/docs/玻璃拟态全站应用方案.md
 *
 * 设计约定（改动前务必读一遍）：
 *   1. 只作用于浅色 html:not(.dark)。深色态（黑青极客）一字不动，保持原样。
 *   2. 玻璃公式固定，只换基色：rgba(var(--g-base),α) + blur + saturate + 1px 亮边 + 顶部内高光 + 柔投影。
 *   3. 只给「叶子面板」加 backdrop-filter。绝不给 body / 主容器加 —— backdrop-filter 会创建
 *      包含块，使内部所有 position:fixed 子元素的坐标基准从视口变成该容器（后果：右键菜单错位、
 *      弹窗偏移、副屏浮层跑到主屏）。
 *   4. 浮层（弹窗/下拉/抽屉/Toast）单独走「浮层档」高不透明度 .94，保证压在玻璃卡片上仍然可读。
 *   5. html.no-glass 一键回退；不支持 backdrop-filter 的环境自动回退不透明底。
 *
 * 排查提示：某处 fixed 浮层开始错位时，先查它是不是被放进了带 backdrop-filter/transform 的容器。
 * 四档材质：薄 .40/10px · 标准 .86/18px · 厚 .92/26px · 浮层 .94/40px */

/* 浅色卡片 hover：默认边已改成 --d3-border，hover 渐进到 --d3-border-hover，
   不再跳到 --border-strong（原实现是「白边 1.05:1 → 灰边 1.27:1」的瞬间出现感） */
html:not(.dark) .d3:hover { border-color: var(--d3-border-hover) !important; }

/* 骨架屏：theme.css 用 --surface-2/--border-2 做条纹，浅色下两者几乎同色，占位块看不出边界 */
html:not(.dark) .skeleton {
  background: linear-gradient(90deg, color-mix(in srgb, var(--text) 5%, transparent) 25%, color-mix(in srgb, var(--text) 11%, transparent) 50%, color-mix(in srgb, var(--text) 5%, transparent) 75%);
  background-size: 200% 100%;
}

/* ---- 背景层：渐变底 + 5 个彩色光斑（用 body::after，给 login 自己的 body::before 让位） ----
   ⚠️ 关键：body 自身必须透明，否则它的不透明底会盖住下面这层光斑。
   层叠顺序（根层叠上下文）：html 背景 → body::after(z-index:-2) → body 背景 → 正文内容。
   原写法 html,body 同时上底 → body 背景落在"正文块背景"阶段，正好压住负 z-index 的伪元素。
   所以这里把底移到 html 上，body 让空。 */
html:not(.dark) { background: var(--haven-slate-100); }
html:not(.dark) :is(body, body.light, body.dark) { background: transparent; }
/* ⚠️ travel.html 的 body 带 id="travelPage"，它自己的规则是 **id 选择器**：
     #travelPage { background-color: var(--bg); background-image: 30% 淡光晕… }
   (1,0,0) 的权重会压过上面那条 (0,2,2) 的透明化规则 —— 表现是整页彩色光斑被一层不透明底盖住
   （实测：其余 9 页 body 都是 transparent，只有 travel 是 var(--travel-bg)）。
   这里用 `body#travelPage` 把权重提到 (1,1,2) 按回来；顺带清掉它自带那层淡光晕——
   现在统一由 §⑪ 的光斑层 + .gl-blobs 负责背景，留着会与新光斑叠加。
   ★ 以后新增页面若出现"背景还是旧的、光斑看不见"，先查 body 有没有被 id 选择器设了底。 */
html:not(.dark) body#travelPage { background: transparent; }
html:not(.dark) body::after {
  content: ''; position: fixed; inset: 0; z-index: -2; pointer-events: none;
  /* 柔和雾蓝背景层（V2.0.0 全站风格改·对齐 demo）：
     3 团弥散柔光（顶部白 / 右上淡蓝 / 左中淡蓝）+ 自顶向下近白微蓝渐变。
     ★ 2026-09-23（V2.0.1）：极客网格线并入本层 —— 原先挂在 .bgfx 上，但全站只有
     finance / milestones / update-log 三页有 <div class="bgfx">，其余页面根本没有网格。
     放在 body::after 里对全部页面一次生效，零 DOM 改动。
     网格必须排在最前（绘制在最上）：本层最后一档渐变是不透明底色，排在后面会被它盖掉。 */
  background:
    linear-gradient(color-mix(in srgb, var(--primary) 6%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--primary) 6%, transparent) 1px, transparent 1px),
    radial-gradient(900px 520px at 50% -12%, var(--white) 0%, transparent 62%),
    radial-gradient(1000px 620px at 108% -4%, var(--blue-75-soft) 0%, transparent 58%),
    radial-gradient(900px 640px at -8% 24%, var(--blue-75-calm) 0%, transparent 56%),
    linear-gradient(180deg, var(--blue-50-warm) 0%, var(--haven-slate-100) 60%, var(--bg) 100%);
  background-size: 48px 48px, 48px 48px, auto, auto, auto, auto;
  background-repeat: repeat, repeat, no-repeat, no-repeat, no-repeat, no-repeat;
}

/* ---- 深色底色：深邃夜空 + 弥散光晕（参考 深色-demo：body 底色 var(--bg) + 4 个 orbs） ----
   为了让光晕漏出来，深色下把底从 body 移到 html（与浅色同构）：
   html 承载 var(--bg)，body 让空 → gl-blobs(-1) / 静态 body::after(-2) 都能透出。 */
html.dark :is(body, body.light, body.dark) { background: transparent; }
/* 静态兜底光晕（JS 未注入或降级态时由它提供夜空氛围；JS 注入后 .gl-blobs 的浮动 orbs 叠加其上，
   二者色值同源（极光青/靛紫/霓虹紫/翡翠绿），叠起来只是光晕强度略增、不会突兀。勿用 gl-blobs-on
   把它关掉——notes 等页也会隐藏 .gl-blobs 容器但仍打 gl-blobs-on，关掉会让那些页面失去光晕。 */
html.dark body::after {
  content: ''; position: fixed; inset: 0; z-index: -2; pointer-events: none;
  /* 2026-09-23（V2.0.1）：深色也补上极客网格线，与浅色同构 ——
     原先同样只挂在 .bgfx 上，没有该元素的页面在深色下也没有网格。
     ★ V5.1.6 光斑收敛：色族 4 → 3（**去掉翡翠绿 orb**，青色 25%→14%）——
       深色夜空只保留「青 / 靛 / 紫」品牌同族渐变，绿斑与品牌色不同族、
       是深色下"背景发脏"的主因（实测截图左上大片青绿斑）。 */
  background:
    linear-gradient(color-mix(in srgb, var(--primary) 4%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--primary) 4%, transparent) 1px, transparent 1px),
    radial-gradient(520px 520px at -80px -100px, color-mix(in srgb, var(--cyan-500) 14%, transparent), transparent 70%),
    radial-gradient(600px 600px at calc(100% + 120px) 40px, color-mix(in srgb, var(--indigo-500) 22%, transparent), transparent 70%),
    radial-gradient(480px 480px at 30% calc(100% + 80px), color-mix(in srgb, var(--violet-500) 16%, transparent), transparent 70%);
  background-size: 48px 48px, 48px 48px, auto, auto, auto;
  background-repeat: repeat, repeat, no-repeat, no-repeat, no-repeat;
}

/* ---- 工具类：新写的模块可直接套用 ---- */
.glass {
  background: var(--surface-glass);
  -webkit-backdrop-filter: blur(var(--g-blur)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur)) saturate(var(--g-sat));
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.glass-thin   { background: var(--surface-glass-thin);
  -webkit-backdrop-filter: blur(var(--g-blur-thin)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur-thin)) saturate(var(--g-sat)); }
.glass-strong { background: var(--surface-glass-strong);
  -webkit-backdrop-filter: blur(var(--g-blur-strong)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur-strong)) saturate(var(--g-sat)); }
.glass-pop    { background: var(--surface-overlay);
  -webkit-backdrop-filter: blur(var(--g-blur-pop)) saturate(180%);
  backdrop-filter: blur(var(--g-blur-pop)) saturate(180%);
  border: 1px solid var(--glass-border); }
/* 模态档工具类：有正文要读的对话框用这个，不要用 .glass-pop（那是给菜单/气泡的） */
.glass-modal  { background: var(--surface-overlay);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border: 1px solid var(--glass-border); }
html.dark .glass-modal { background: var(--surface); }
/* 凹面玻璃：只有半透明底 + 内阴影 + 亮边，不含 blur —— 用在"玻璃里再套一层"的场景，避免模糊互相抵消 */
.inset-glass {
  background: var(--surface-glass-strong);
  border: 1px solid var(--glass-border);
  box-shadow: inset 0 2px 6px color-mix(in srgb, var(--shadow-lg) 18%, transparent);
}

/* 内容卡：统一实底。玻璃只保留给顶栏、筛选浮层、弹窗和菜单。 */
:is(.card, .plain-card, .mk-card, .app-card, .metacard,
    .analysis-stat-card, .plan-chart-card, .mk-table-wrap,
    .d3, .mod, .goal, .quick, .feed, .chart3d) {
  background-color: var(--surface);
  background-image: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.hero-banner :is(.stat-card, .card) {
  background: color-mix(in srgb, var(--surface) 14%, transparent);
  background-image: none;
}
/* ---- 给既有组件补 backdrop-filter（它们本身的半透明底已由令牌换好，这里只补模糊） ---- */
/* 顶栏：走厚档。不能只吃 --surface 的 .46 —— 太透会让滚动内容从顶栏后面透出来干扰阅读 */
html:not(.dark) .site-header {
  background: var(--surface-glass-strong);
  -webkit-backdrop-filter: blur(var(--g-blur-strong)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur-strong)) saturate(var(--g-sat));
}
/* ⚠️ 例外：**Hero 横幅内部的卡片**不参与玻璃化。
   它们（如 travel 总览的四张统计卡）本来的设计是 theme.css 里的
     .hero-banner .stat-card { background: color-mix(in srgb, var(--white) 14%, transparent) }  ← 渐变上的克制小片
   而它们同时带 `.card` 类，会被上面两条 sheen / 模糊规则命中：
   55% 白的斜向高光叠上去 → 原本克制的 14% 白被洗成亮粉色药丸，白图标也一起糊掉。
   这里把这几项按「改造前」的值精确还原（含原先的灰边与柔投影），
   保证 hero 上的统计卡与"上一个版本"一致。作用域是 .hero-banner，站点里所有 hero 统计卡一并受益。

   ⚠️ **不要把 `.stat-icon` 写进这个列表** —— 我一开始写进去了，结果把图标小片的
   `border-color`（原本 var(--border-neutral)）和 `box-shadow`（原本 color-mix(in srgb, var(--black) 28%, transparent) 0 8px 20px -6px，
   一个让图标"浮起来"的深色投影）一并覆盖成了卡片的灰边与柔投影，图标就平了。
   图标自己本来就没有 backdrop-filter、也没有 background-image，不需要这里兜。 */
html:not(.dark) .hero-banner :is(.card, .stat-card) {
  background-image: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border-color: var(--blue-150-soft);                          /* 改造前的 --border */
  box-shadow: 0 8px 32px color-mix(in srgb, var(--text) 6%, transparent);   /* 改造前的 --shadow */
}
/* ---- 表单控件：一律实底（**不透底**）----
   用户反馈：输入框透出背景光斑后，文字与占位符跟背景糊在一起，打字时视觉混乱。
   策略：
     · 控件本身 与「字段外壳」（.search / .input-icon-wrap 这类容器型字段）**都**改实底 ——
       两者同为白后合起来就是一个干净的实底输入区，不会出现"玻璃框里套白块"。
     · 同时去掉输入控件上的 backdrop-filter：实底不需要它，还省一层模糊开销。
     · 排除 checkbox / radio / range / color / file / hidden —— 它们不是"文字输入区"，
       改背景色反而会让原生控件变怪。
   ⚠️ 注意这里**没有**动 `.sheet`（notes 弹出面板）：它是对话框面板不是输入区，保持玻璃；
      它内部的 `.ta` 文字区按本规则变实底，正是想要的效果。
   ⚠️ `:is(...)` 与后面的 `:not(...)` 必须**紧连不能换行** —— 中间只要出现空白，就成了后代选择器。 */
html:not(.dark) :is(input, textarea, select, .input, .glass-input, .app-input, .ui-modal-input, .inp, .search, .input-icon-wrap, .ta):not([type=checkbox]):not([type=radio]):not([type=range]):not([type=color]):not([type=file]):not([type=hidden]) {
  background-color: var(--white);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
/* 页面内联样式里直接用 --panel-solid 的容器（login 主卡片、主题开关、admin 用户行等） */
html:not(.dark) :is(.auth-card, .theme-fab, .auth-view-row, .auth-view-badge,
                    .site-logo, .brand-mark, .stat-card, .hover-card, .chip, .meta-card) {
  -webkit-backdrop-filter: blur(var(--g-blur)) saturate(var(--g-sat));
  backdrop-filter: blur(var(--g-blur)) saturate(var(--g-sat));
}
/* 模态：2026-09-23 删除了原先的「模态档玻璃」规则（--g-alpha-modal / 30px 模糊）。
   它下面的全站实底规则 `:is(...){background:var(--modal-bg,var(--white))!important;backdrop-filter:none!important}`
   带 !important 且位置更靠后，本条自写入起从未生效 —— 浅色弹窗一直是纯白实底。
   决策：保留实底（弹窗承载正文，实底最可读），删除死规则，避免注释与实现继续打架。 */
/* 全站约定：弹出窗主体必须是实底，不能透出背后的卡片/光斑造成文字融合。
   ⚠️ 只命中弹窗「内容框」，不命中管理页 .modal.fixed 这类全屏遮罩。 */
:is(.ui-modal-box, .modal-box, .modal:not(.fixed), .dialog, .mo .box, .auth-view-box,
    .glass-modal, .mk-modal, .trip-modal-box, .plan-modal-box, .plan-drawer,
    .edit-modal-box, .review-modal-box, .form-modal,
    #data-modal .plain-card, #reset-modal .plain-card, #new-diary-modal .app-card) {
  background: var(--modal-bg, var(--white)) !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}
/* 浮层：走浮层档（.94 / 40px）—— 短命、面积小、压在所有内容之上，用高白度换可读性
   ⚠️ 这里刻意【不】包含 `.menu` —— 顶栏导航下拉 `.navgroup .menu` 在每页都存在 4 个，
      默认 opacity:0/visibility:hidden。它的白度已经由 --menu-bg 拉到 .94，
      再叠 40px 模糊既看不出来、又白占 4 个模糊图层（审计实测每页因此多 4 个 ~41k px² 的大块）。 */
html:not(.dark) :is(.ui-toast, .topnav-menu, .topnav-drawer) {
  background: rgba(var(--g-base), var(--g-alpha-pop));
  border-color: rgba(var(--g-line), .98);
  -webkit-backdrop-filter: blur(var(--g-blur-pop)) saturate(180%);
  backdrop-filter: blur(var(--g-blur-pop)) saturate(180%);
}
/* 模态遮罩改为带模糊（浮层压在玻璃上是常态，靠高白度而非改结构解决） */
html:not(.dark) .ui-modal-overlay {
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

/* ---- 配套修补：玻璃化会顺带打破这两处 ---- */
/* 滚动条拇指原本用 --border（已变成白色），浅色底上会看不见 */
html:not(.dark) *::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--text) 18%, transparent); }
html:not(.dark) *::-webkit-scrollbar-thumb:hover { background: var(--c1); }
html:not(.dark) { scrollbar-color: color-mix(in srgb, var(--text) 22%, transparent) transparent; }
/* 进度条/占比条轨道原本用 --border-2（已变白）→ 白底白轨等于看不见，换成淡靛轨道 */
html:not(.dark) :is(.progress-track, .strength-bar, .rec-dim-bar, .fp-rank-bar) {
  background: color-mix(in srgb, var(--text) 8%, transparent);
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--text) 6%, transparent);
}
/* 页面加载遮罩必须保持不透明，否则底下的光斑会透出来造成闪烁 */
html:not(.dark) .fl-page-loader { background: var(--haven-slate-100); }

/* ---- 降级与开关 ---- */
/* ① 浏览器不支持 backdrop-filter：回到接近不透明的底，布局零变化 */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  html:not(.dark) {
    --g-alpha: .92; --g-alpha-thin: .85; --g-alpha-strong: .95;
    --g-alpha-pop: .98; --g-alpha-modal: .97;
  }
}
/* ② 无玻璃降级 / 低性能档：html.no-glass 或 html.perf-low（可由 sidebar.js 或控制台设置） */
html.no-glass:not(.dark) {
  --g-alpha: 1; --g-alpha-thin: 1; --g-alpha-strong: 1;
  --g-alpha-pop: 1; --g-alpha-modal: 1;
  --g-blur: 0px; --g-blur-thin: 0px; --g-blur-strong: 0px;
  --g-blur-pop: 0px; --g-blur-modal: 0px;
  --overlay: color-mix(in srgb, var(--text) 42%, transparent);
}
html.no-glass:not(.dark) body::after { display: none; }
/* 降级时也要把 JS 注入的光斑层一起关掉，否则"关了玻璃反而更花" */
html.no-glass:not(.dark) .gl-blobs { display: none; }
html.perf-low:not(.dark) :is(.card, .plain-card, .mk-card, .app-card, .metacard,
                             .analysis-stat-card, .plan-chart-card, .mk-table-wrap, .filter-bar,
                             .d3, .mod, .goal, .quick, .feed, .chart3d, .input, .glass-input) {
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
html.perf-low:not(.dark) { --g-blur: 12px; --g-blur-strong: 14px; --g-blur-pop: 24px; --g-blur-modal: 20px; }

/* ---- 动效可访问性：玻璃依赖漂浮光斑与位移入场，对眩晕敏感用户不友好 ---- */
@media (prefers-reduced-motion: reduce) {
  html:not(.dark) body::after { display: none; }
  /* ★ 2026-09-23：只降模糊，不再降不透明度。
     原实现把 --g-alpha 压到 .62，卡底变 var(--haven-slate-75) 更透 —— --tx3 从 3.56 掉到 3.43。
     动效敏感不该以可读性为代价，白度保持标准档。 */
  html:not(.dark) {
    --g-blur: 10px; --g-blur-strong: 12px; --g-blur-pop: 20px; --g-blur-modal: 16px;
    --g-alpha-pop: .96; --g-alpha-modal: .88;
  }
}

.auth-view-empty.err { color: var(--bad); }
/* ==================== ⑫ 命令面板 / 光斑漂浮层 / 低性能开关（2026-09-15） ====================
 * 这三件都由 sidebar.js 驱动，样式集中在这里：
 *   · .cmdk-*   命令面板（V3.0 可见入口 · 全站搜索）
 *   · .gl-blobs 浅色光斑漂浮层（JS 注入，见 sidebar.js 的 buildBlobs）
 *   · .ib.on    低性能模式开关的"已开启"态
 * 两态通用：面板底色复用 --menu-bg（V2.0.3 起全实体：var(--white) / var(--surface)），所以深浅自动成立。 */

/* ---- 命令面板 ---- */
.cmdk-mask {
  position: fixed; inset: 0; z-index: 9999;
  display: none; align-items: flex-start; justify-content: center;
  padding: 12vh 16px 16px;
  background: var(--overlay);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.cmdk-mask.show { display: flex; animation: cmdk-fade .18s ease; }
@keyframes cmdk-fade { from { opacity: 0; } to { opacity: 1; } }
.cmdk {
  width: min(560px, 94vw); max-height: 62vh; display: flex; flex-direction: column;
  background: var(--menu-bg); border: 1px solid var(--menu-border);
  border-radius: var(--radius-16px); box-shadow: 0 26px 60px -18px color-mix(in srgb, var(--text) 45%, transparent);
  overflow: hidden;
  animation: cmdk-in .2s cubic-bezier(.22,1,.36,1);
}
@keyframes cmdk-in { from { opacity: 0; transform: translateY(-8px) scale(.98); } to { opacity: 1; transform: none; } }
.cmdk-hd { display: flex; align-items: center; gap:var(--space-10px); padding:var(--space-13px-16px); border-bottom: 1px solid var(--line2); }
.cmdk-ico { display: flex; flex: none; }
.cmdk-ico svg { width: 17px; height: 17px; }
.cmdk-hd input {
  flex: 1; min-width: 0; border: 0; outline: 0; background: transparent;
  font-family: inherit; font-size: var(--fs-base); font-weight: 500; color: var(--tx);
}
.cmdk-hd input::placeholder { color: var(--tx3); font-weight: 400; }
.cmdk kbd {
  font-family: var(--mono); font-size: var(--fs-2xs); color: var(--tx3);
  background: var(--surface-2); border: 1px solid var(--border);
  padding:var(--space-2px-6px); border-radius: var(--radius-6px); flex: none;
}
.cmdk-list { overflow-y: auto; padding:var(--space-6px); }
.cmdk-item {
  display: flex; align-items: center; gap:var(--space-10px); padding:var(--space-9px-11px); border-radius: var(--radius-11px);
  text-decoration: none; color: var(--tx2); transition: background .14s, color .14s;
}
.cmdk-item i { display: flex; flex: none; }
.cmdk-item i svg { width: 17px; height: 17px; }
.cmdk-tx { flex: 1; min-width: 0; display: flex; flex-direction: column; gap:var(--space-1px); }
.cmdk-tx b { font-size: var(--fs-xs); font-weight: 700; color: var(--tx); }
.cmdk-tx em { font-style: normal; font-size: var(--fs-2xs); color: var(--tx3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cmdk-grp { flex: none; font-size: var(--fs-2xs); font-weight: 700; letter-spacing: .04em; color: var(--tx3); }
.cmdk-item.on { background: var(--nb-on-bg); }
.cmdk-item.on .cmdk-tx b, .cmdk-item.on .cmdk-tx em, .cmdk-item.on .cmdk-grp { color: var(--c1); }
.cmdk-item.on i { color: var(--c1); }
.cmdk-empty { padding:var(--space-22px-12px); text-align: center; font-size: var(--fs-2xs); color: var(--tx3); }
.cmdk-ft {
  display: flex; align-items: center; justify-content: space-between; gap:var(--space-10px);
  padding:var(--space-9px-14px); border-top: 1px solid var(--line2);
  font-size: var(--fs-2xs); color: var(--tx3);
}
.cmdk-ft b { color: var(--c1); font-weight: 700; }
.cmdk-keys { display: inline-flex; align-items: center; gap:var(--space-3px); }
.cmdk-x {
  width: 30px; height: 30px; flex: none; border: 0; border-radius: var(--radius-8px); cursor: pointer;
  background: transparent; color: var(--tx3); font-size: var(--fs-lg); line-height: 1;
}
.cmdk-x:hover { background: var(--surface-2); color: var(--tx); }
.cmdk-sec {
  padding:var(--space-9px-11px-5px); color: var(--tx3); font-size: var(--fs-2xs); font-weight: 700;
  letter-spacing: .08em;
}
.cmdk-state { padding:var(--space-18px-12px); text-align: center; font-size: var(--fs-2xs); color: var(--tx3); }

/* ---- 光斑漂浮层（浅色已停用 → 取消底色光晕；深色承载 demo 深邃夜空弥散光晕）----
   ★★ 层叠顺序是这个功能的命门：body::after 与 .gl-blobs 若同为 z-index:-2，
      则**树序**决定谁在上，而 ::after 永远排在最后 → ::after 会盖住 .gl-blobs。
      实测后果：body::after 那层不透明渐变把光斑全挡了，整站背景变成近白（用户报的就是这个）。
      所以这里刻意用 **-1**：负值越大越先画，-2（底+噪点）在下、-1（光斑）在上，
      两者都仍在"负 z-index"阶段 → 依旧在所有正文内容之下，不影响可点性。 */
html:not(.dark) .gl-blobs { display: none; } /* 底色光晕已取消（2026-09-22）：浅色不再浮动光斑 */

/* 深色：深邃夜空弥散光晕（参考 深色-demo 经典极光调色盘）
   JS 注入 5 个 <i>，深色下只启用其中 4 个充当 demo 的 4 个 orb */
/* V5.1.6：浮动 orbs 同步收敛 —— 绿 orb 停用、青/靛/紫降不透明度（.35/.35/.25 → .20/.26/.18），
   与静态层同族同强度；blur 100px 不变。光晕仍是"深邃夜空"，只是不再五颜六色。 */
html.dark .gl-blobs { position: fixed; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; }
html.dark .gl-blobs i { position: absolute; border-radius: var(--radius-50); filter: blur(100px); will-change: transform; }
html.dark .gl-blobs i:nth-child(1) { width: 520px; height: 520px; left: -80px; top: -100px; opacity: .20; background: radial-gradient(circle, var(--cyan-500), transparent 70%); animation: gl-fl1 26s ease-in-out infinite; }
html.dark .gl-blobs i:nth-child(2) { width: 600px; height: 600px; right: -120px; top: 40px; opacity: .26; background: radial-gradient(circle, var(--indigo-500), transparent 70%); animation: gl-fl2 32s ease-in-out infinite; }
html.dark .gl-blobs i:nth-child(3) { width: 480px; height: 480px; left: 30%; bottom: -80px; opacity: .18; background: radial-gradient(circle, var(--violet-500), transparent 70%); animation: gl-fl3 29s ease-in-out infinite; }
html.dark .gl-blobs i:nth-child(4) { display: none; }
html.dark .gl-blobs i:nth-child(5) { display: none; }
@keyframes gl-fl1 { 0%, 100% { transform: translate(0, 0) scale(1); } 50% { transform: translate(60px, 45px) scale(1.1); } }
@keyframes gl-fl2 { 0%, 100% { transform: translate(0, 0) scale(1); } 50% { transform: translate(-75px, 60px) scale(.92); } }
@keyframes gl-fl3 { 0%, 100% { transform: translate(0, 0) scale(1); } 50% { transform: translate(50px, -55px) scale(1.07); } }

/* JS 注入光斑层后，body::after 只保留「柔光渐变底」，避免与 .gl-blobs 叠加过曝 */
/* ⚠️ 本条与上面 `html:not(.dark) body::after` 是**同一层的两套取值**，改背景必须两处一起改。
   注：sidebar.js 在非 reduced-motion 下几乎总会给 <html> 挂 .gl-blobs-on，
   所以实际生效的多数是这一条 —— 网格漏掉这里就等于「大部分页面仍然没有网格」。 */
html:not(.dark).gl-blobs-on body::after {
  background:
    linear-gradient(color-mix(in srgb, var(--primary) 6%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--primary) 6%, transparent) 1px, transparent 1px),
    radial-gradient(900px 520px at 50% -12%, var(--white) 0%, transparent 62%),
    radial-gradient(1000px 620px at 108% -4%, var(--blue-75-soft) 0%, transparent 58%),
    radial-gradient(900px 640px at -8% 24%, var(--blue-75-calm) 0%, transparent 56%),
    linear-gradient(180deg, var(--blue-50-warm) 0%, var(--haven-slate-100) 60%, var(--bg) 100%);
  background-size: 48px 48px, 48px 48px, auto, auto, auto, auto;
  background-repeat: repeat, repeat, no-repeat, no-repeat, no-repeat, no-repeat;
}

/* ---- 低性能模式开关的开启态 ---- */
.ib.on { color: var(--c1); border-color: var(--border-glow); background: var(--glow-primary); }

@media (prefers-reduced-motion: reduce) {
  html:not(.dark) .gl-blobs { display: none; }
  .cmdk-mask.show, .cmdk { animation: none; }
}

@media (max-width: 860px) {
  .cmdk-mask { padding: 0; align-items: stretch; }
  .cmdk { width: 100%; height: 100dvh; max-height: none; border: 0; border-radius: var(--radius-0); }
  .cmdk-hd { min-height: 58px; padding: max(12px, env(safe-area-inset-top)) 14px 12px; }
  .cmdk-list { flex: 1; padding: 6px 8px max(8px, env(safe-area-inset-bottom)); }
  .cmdk-item { min-height: 48px; padding:var(--space-10px-12px); }
  .cmdk-ft { padding-bottom: max(10px, env(safe-area-inset-bottom)); }
}

/* ==================== ⑬ 移动端基座（≤860px）· 2026-09-17 新增 ====================
 * 依据 `HTML/无需上传/_mobile-audit.js` 在真机视口（390×844 / 360×800，CDP 设备模拟）下的实测：
 *   · 页面主体布局**本身没有横向溢出**（13 条越界里 9 条是装饰光球）→ 底子是好的，别大改
 *   · 真正的三类问题：① iOS 输入框自动放大 27 处 ② 触控目标 <44px 200+ 处 ③ 小字 <12px 多处
 * 全部只在移动端生效，桌面端（≥861px）零影响。
 * 改这里之后请重跑 `node HTML/无需上传/_mobile-audit.js` 验证，别凭感觉。
 */
@media (max-width: 860px) {
  /* ① iOS 输入框自动放大 —— 控件字号 <16px 时 iOS 会把**整页**强制放大，
     点一下输入框页面就"跳"，手机端最难受的问题之一（实测 12 处，tools 最集中）。
     ⚠️ 必须 !important：各页输入框字号由 #id / Tailwind 类 / 内联样式设定，特异性高于本层。 */
  input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=color]):not([type=file]):not([type=hidden]),
  select,
  textarea { font-size: var(--fs-input) !important; }

  /* ② 触控目标 ≥44px —— 只扩**热区**、不动视觉尺寸（直接放大图标会改变顶栏观感）。
     34×34 的图标按钮 inset:-5px 后热区正好 44×44。
     ⚠️ 探针 `_mobile-probe.js` 已按「含 ::after 负 inset 的有效热区」计算，别只量元素 rect。 */
  /* ⚠️★ 这里**不能再列 `#auth-view-fab`**（V1.9.9 修）：auth.js 给「切换数据」浮动按钮写的是
     `.auth-view-fab{position:fixed;left:20px;bottom:20px}`（注释原文「左下角固定切换数据按钮」），
     而本条用的是 **ID 选择器**（特异性 1,0,0）⇒ 不管加载顺序都能压过 auth.js 的类选择器（0,1,0），
     把它变成 `position:relative`。后果有两层：
       ① `bottom:20px` 从「距视口底 20px」变成「相对自身流内位置**上移** 20px」；
       ② 它回到普通流 ⇒ 每个页面末尾凭空多出 **31px** 文档高度（实测 de.scrollHeight 844 → 875），
          于是**手机端每一页都能被拖出 31px**；在独立 App（standalone）里就是整页橡皮筋回弹、
          上下露出空白背景 —— 用户 2026-09-21 报的「空备忘录上下滑动出现空白」正是它。
     它本来也不需要 relative：`position:fixed` 同样会给绝对定位的 `::after` 提供包含块（见下一行）。 */
  .ib, .av, .logo, .m-drawer-hd .x { position: relative; }
  .ib::after, .av::after, .logo::after, .m-drawer-hd .x::after { content: ''; position: absolute; inset: -5px; }
  /* 模拟查看浮动按钮（auth.js 全站注入）只有 31px 高，单独多扩一点 */
  #auth-view-fab::after { content: ''; position: absolute; inset: -7px; }

  /* ② b) 横向「条状」控件（tab / 导航项 / 抽屉按钮 / 标签）直接给足 44px 高：
     它们本来就是整条可点，加高不破坏排版，也比扩热区更可预期。 */
  .fin-tab, .travel-tab, .site-btn, .tgchip, .tool-nav-link,
  .m-actions .nb, .m-nav .mi { min-height: 44px; }

  /* ③ 小字下限 12px —— 实测这些类族在手机上分别只有 8.5 / 10.5 / 11px，确实难读 */
  .brand .os, .m-foot, .mg-l, .sc-sub, .sec .no, .tgchip, .field label { font-size: var(--fs-2xs) !important; }

  /* ④ iOS 安全区 —— 配合各页 viewport 的 viewport-fit=cover。
     顶栏是 position:sticky;top:0，不补 padding 会被状态栏/刘海压住。 */
  .site-header.topbar { padding-top: env(safe-area-inset-top, 0px); }
  .m-drawer { padding-top: env(safe-area-inset-top, 0px); }
  /* ★ V4.0.1：底部安全区**只在最后一个子元素（.m-foot）补一次**。
     原来 `.m-actions` 也补了一次，且 `.m-drawer` 自身还有一层 ⇒ 刘海机底部空白约三倍。 */
  .m-drawer .m-foot { padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)); }
}

/* ★ 版本号 `.brand .os` 在 ≤520px 隐藏 —— **与 app.html 保持一致**
 * app.html 里早就有这条（`@media(max-width:520px){.brand .os{display:none}}`），
 * 而子页（sidebar.js 生成的顶栏）没有 → 手机上「有的页面写版本号、有的没写」，用户反馈为不一致。
 * 选「统一隐藏」而不是「统一显示」的理由：手机实测（390px）显示时版本号会**折成两行**，
 * 把品牌区撑到 3 行高、页面标题被挤扁（见 design-file/docs/version-v1.10.0/ 的截图）；
 * 顶栏空间宝贵而版本号信息价值低。**桌面端（≥521px）与移动抽屉的 `.m-foot` 仍保留。**
 * ⚠️ 改这里的断点要同步 app.html 的同名规则，否则又会不一致。 */
@media (max-width: 520px) {
  .brand .os { display: none; }
}

/* ==================== ⑮ 输入框边框（V2.0.3）：非聚焦统一加深，聚焦仍走主题色 ====================
   用户反馈浅色下输入框未聚焦时边框太浅、看不清输入范围 ⇒ 统一为雾蓝 color-mix(in srgb, var(--primary) 32%, transparent)。
   !important：finance-modules 等模块会在页面内后注入 <style>，同特异性下会压过本文件，需加 !important 兜底。
   仅命中非聚焦、非原生控件；html.dark 与 :focus 完全不受影响。 */
html:not(.dark) :is(input, textarea, select, .inp, .input, .glass-input, .search, .app-input, .ui-modal-input):not(:focus):not([type=checkbox]):not([type=radio]):not([type=range]):not([type=color]):not([type=file]):not([type=hidden]) {
  border-color: color-mix(in srgb, var(--primary) 32%, transparent) !important;
}
