/* ============================================================
   家书 FamilyBook · 界面样式
   配色取自图标：宣纸米 / 砖红封面 / 深褐书脊 / 纸白
   原则：扁平、留白充足、字重克制（正文 400，标题 500-600）
   ============================================================ */

:root {
  --paper:    #F7F3EA;
  --surface:  #FFFFFF;
  --ink:      #2C231D;
  --muted:    #7E6B5A;
  --faint:    #A8988A;
  --brand:    #A84A28;
  --brand-2:  #C96F45;
  --brand-ink:#6E3820;
  --green:    #6F9E5C;
  --red:      #C0533F;
  /* 🔴🔴 v94（用户 2026-10-25）：必填项的标记方式从「标题后面加红星星号」改成
     「标题本身**红色 + 加粗**」——用户原话：
       「必填项现在的做法是在后面加一个红色的星，但这样排版有时候不好看，对不齐。
         把所有必填项都改一下，改成红色加粗字体……一看前面的标题，
         只要是红色加粗的字体，就知道这是必填项。」
     ⇒ 需要两个变量：一个"必填红"、一个"必填字重"。
     ⚠️ **为什么单独立一个 `--req` 而不是直接用 `--red`**：
        `--red` (#C0533F) 是**动作红**（删除按钮的底色），它当文字色用在这个
        暖纸底 (#F7F3EA) 上对比度只有 ≈ 3.4:1 —— 13-14px 的标签字号下偏浅。
        这里取深一档的 #B03A2E（≈ 4.6:1），既明显是红，又能和正文墨色同等清晰。
        **改这个值会全站必填标签一起变。**
     ⚠️🔴 `--req-w` **必须是 700，不能写 650/600**：本站标签的基线字重
        本来就是 600（`.form-grid > .field > label`、`.card .field > label`、
        `.mv-row > .mv-lb`、`.dt-range-label` 四处都是 600）。写 650 的话
        浏览器只会取到最近的真字重 700 或合成加粗 —— 一旦落到 600 就等于
        **完全没加粗**，用户看到的只剩"变红"，而他明确说的是「红色加粗」。
        700 与 600 的差在 15px 中文黑体上是能一眼分出来的。 */
  --req:      #B03A2E;
  --req-w:    700;
  /* 🔴 v40（用户 2026-10-10）：「影院管理部分…编辑使用蓝色的背景，删除使用红色的背景。」
     ⇒ 新增一个中性的"操作蓝"（不是链接蓝、也不是模块品牌色）—— 它是**语义色**：
       蓝=编辑、红=删除，全站统一，跟着模块换肤也不该变。
     ⚠️ 暖纸底上要够暗才压得住白字：这里取 #2F6FB0（对比度 ≈ 4.9:1）。 */
  --blue:     #2F6FB0;
  /* 🔴 v41（用户 2026-10-10）：旅程详情页四种动作按钮各一种底色
     （计划=蓝 / 实际=绿 / 编辑=黄 / 删除=红）。蓝、绿（--green）、红已有；
     这里补"编辑黄"。
     ⚠️ 纯黄（#FFD400 之类）配白字几乎看不清（对比度 ≈ 1.3:1）。
        取偏琥珀的深黄 #B8860B（DarkGoldenrod 系），暖纸底上白字对比度 ≈ 3.6:1，
        且不会和品牌暖棕 (`--brand` #A84A28) 混淆 —— 后者明显更红。
     ⚠️ "实际=绿"用既有的 `--green` (#6F9E5C)，它在白字下对比度 ≈ 3.0:1，
        深一步更保险 —— 见 `.btn-actual` 里单独取的 #4E7C3F（≈ 4.6:1）。 */
  --yellow:   #B8860B;
  --line:     rgba(44, 35, 29, .10);
  --line-2:   rgba(44, 35, 29, .06);
  --radius:   16px;
  --radius-s: 10px;
  --shadow:   0 1px 2px rgba(44, 35, 29, .05);
  --shadow-l: 0 4px 16px rgba(44, 35, 29, .08);

  /* ---- 动作按钮的统一度量 ----
     用户提过三次「设置按钮和记一场电影按钮高度要一致」。为杜绝再犯，
     这里把按钮高度拆成 4 个变量，并算出唯一的高度：
       --act-h = 上下内边距 + 行高 + 上下边框 = 9*2 + 14*1.7 + 1*2 = 43.8px
     文字按钮靠 padding + line-height 自然撑到这个高度；
     **只有图标的方形按钮**（.btn-icon）显式写高度，但取的是同一个 --act-h，
     这样两边永远相等，改一处就同步。 */
  --act-pad-y: 9px;
  --act-pad-x: 16px;
  --act-font:  14px;
  --act-line:  1.7;
  --act-h: calc(var(--act-pad-y) * 2 + var(--act-font) * var(--act-line) + 1px * 2);
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 15px/1.7 -apple-system, BlinkMacSystemFont, "PingFang SC",
        "Microsoft YaHei", "Hiragino Sans GB", sans-serif;
  -webkit-font-smoothing: antialiased;
}

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

/* ---------------- 顶栏 ---------------- */
.topbar {
  position: sticky; top: 0; z-index: 20;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}
.topbar-inner {
  max-width: 1080px; margin: 0 auto; padding: 0 24px;
  height: 60px; display: flex; align-items: center; gap: 24px;
}
.brand { display: flex; align-items: center; gap: 10px; color: var(--ink); }
.brand:hover { text-decoration: none; }
.brand img { width: 30px; height: 30px; border-radius: 8px; display: block; }
.brand span { font-size: 16px; font-weight: 600; letter-spacing: .5px; }

.nav { display: flex; gap: 4px; margin-left: 8px; }
.nav a {
  padding: 6px 12px; border-radius: 999px; color: var(--muted);
  font-size: 14px;
}
.nav a:hover { background: var(--line-2); text-decoration: none; }
.nav a.on { background: var(--brand); color: #fff; }

.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.who { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 14px; }
/* 姓名 + 头像是个下拉菜单的「把手」——hover 时给点反馈，让人知道它可点 */
.who-menu { position: relative; }
.who-menu > summary { list-style: none; cursor: pointer; padding: 4px 6px; border-radius: 999px; }
.who-menu > summary::-webkit-details-marker { display: none; }
.who-menu > summary:hover { }   /* 统一段处理 */
.who-menu > summary:hover .who-name { color: var(--brand); }
.who-menu[open] > summary { background: var(--line-2); }
.who-menu[open] > summary .caret { transform: rotate(180deg); }
.who img { width: 30px; height: 30px; border-radius: 50%; object-fit: cover; }
.who img { transition: box-shadow .15s ease; }
.who-menu > summary:hover img { box-shadow: 0 0 0 2px var(--brand-2); }
/* 那个小三角：没有它，用户不知道"名字能点开" */
.caret {
  width: 0; height: 0; margin-left: -2px;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid var(--faint);
  transition: transform .15s ease;
}
.menu-list {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 30;
  min-width: 148px; padding: 6px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-s); box-shadow: var(--shadow-l);
  display: flex; flex-direction: column;
}
.menu-list a {
  padding: 8px 12px; border-radius: 8px; color: var(--ink);
  font-size: 14px; white-space: nowrap;
}
.menu-list a:hover { background: var(--line-2); text-decoration: none; color: var(--brand); }
/* 退出放在最下面并隔一条线：它是"离开"，和上面几个"改点什么"不是一类动作 */
.menu-list .menu-exit {
  margin-top: 4px; padding-top: 9px; border-top: 1px solid var(--line-2);
  border-radius: 0 0 8px 8px; color: var(--muted);
}
.menu-list .menu-exit:hover { color: var(--brand); }
/* ---- 观影页的「设置」下拉：图标按钮 + 永远贴右 ----
   用户明确要求（2026-10-01）：
     · 去掉「设置」二字，只留**三条横线**图标（☰）；
     · 在**所有页面都排在最右侧**（位置统一）；
     · 点开后的每条条目，**文字前都要有图标**；
     · 除设置外，其它按钮都要**有颜色**，跟设置区分开。
   贴右的做法：`margin-left:auto` 把它推到动作区最右端 —— 这样无论各页把片段
   require 在 actions 的哪个位置（有的写在最前、有的写在最后），它都贴右，
   不必逐页调顺序，也就不会有"某个页面漏了"的情况。 */
.movie-set { position: relative; margin-left: auto; }
.movie-set > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center;
  justify-content: center; gap: 6px;
}
.movie-set > summary::-webkit-details-marker { display: none; }
.movie-set[open] > summary { border-color: var(--brand); color: var(--brand); }

/* 方形图标按钮：只有图标、无文字。高度**必须**取 --act-h，
   与「记一场电影」等实心按钮同高（那个高度由 --act-pad-y/--act-font/--act-line 推出）。
   ⚠️ 别在这里另写一个数字（曾写死 40px，比文字按钮的 43.8px 矮了 3.8px，被用户连续投诉）。 */
.btn-icon {
  background: #fff; color: var(--ink); border: 1px solid var(--line);
  padding: 0 !important; width: var(--act-h); height: var(--act-h); min-width: var(--act-h);
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 1;
}
.btn-icon:hover { color: var(--brand); border-color: var(--brand); }   /* 底色交给统一段 */
.btn-icon svg { display: block; }

/* 观影设置菜单：从右上角改成**按钮下方右对齐**（按钮贴右，菜单也贴右才不会跑出屏外） */
.movie-set .menu-list { left: auto; right: 0; top: calc(100% + 8px); }
.movie-set .menu-list a { display: flex; align-items: center; gap: 10px; }
.movie-set .menu-list a .mi { flex: 0 0 16px; color: var(--muted); }
.movie-set .menu-list a:hover .mi,
.movie-set .menu-list a.on .mi { color: currentColor; }
.movie-set .menu-list a.on { color: var(--brand); background: var(--line-2); }

/* ---- 日程页的设置下拉（v24 用户 2026-10-03 第 4、5 条）----
   它比观影/任务那个 ☰ 内容多：里面有个**表单**（默认视图 + 显示哪些本子）。
   本子清单会随共享/新建变长，所以给列表一个最大高度 + 滚动，
   否则菜单会顶出屏幕、底下的「保存」按钮够不着。 */
.cal-set .menu-list { min-width: 260px; max-width: min(320px, 92vw); }
.cal-set-list { padding: 10px; gap: 0; }
.cal-set-h { font-size: 12px; color: var(--muted); margin-bottom: 6px; }
.cal-set-views { display: flex; gap: 12px; flex-wrap: wrap; }
.cal-set-view { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; cursor: pointer; }
.cal-set-view input { accent-color: var(--brand); }
/* 🔴 v72（用户 2026-10-21）—— 这里原有一段 `.cal-set-books / .cal-set-book* /
   .cal-set-page / .cal-set-form|block|foot|links` 的样式，随 `calendar/display`
   整页删除（显隐与管理合并进 `calendar/calendars`）**全部失去了使用者**，
   已在本轮清掉。留着的代价不只是"没用"：
     · 其中 `.cal-set-page .cal-set-books { max-height:none; overflow-y:visible; }`
       曾被 v51 的反向验证当成"守内滚动"的锚点 —— 规则在、页面没了 ⇒
       注入照旧命中文本、页面照旧全绿 ⇒ **那条反向验证变成空转**，
       在输出上却看起来"这条断言很结实"（实测踩中，见 `_b51_falsify.py` 注释）。
     · ⇐ 教训：**断言只能锚"当前真有使用者"的规则**；删模板时必须同步删死 CSS，
       否则验证脚本会静默退化成摆设。
   现在"日历本清单不套内滚动"这件事由 `.cal-books` 守（它才是页面上真实的清单容器）。 */

/* ============================================================================
   v72（用户 2026-10-21）：日历本「显隐 + 管理」合并成一张清单
   ----------------------------------------------------------------------------
   用户原话：「日历设置里面日历本显示和日历管理可以放在一起啊。就是把日历本全都
             列出来，然后显示不显示，加个复选框就行了。另外把下面别人共享给我的
             也提到上面来，把所有的日历都放在一块，只不过在后面标注出来，
             这个是别人谁谁谁共享给我的？是只读的还是可写的，什么的就可以了呀。」

   ⇒ 每行从左到右读下来就是一整句答案：
        [✓] ■ 名字  [谁·只读/可写]              共享 导出 编辑 删除
   ⚠️ 用 grid 而不是 flex：归属那一列要**对齐**（否则每行标签长短不一时，
      后面的操作按钮会跟着左右乱跳，看着很脏）。
   ⚠️ 窄屏（<640px）改成两行：第一行勾选+色块+名字+归属，第二行操作。
      44 行日历本在手机上横着排会挤成碎片（用户可能在手机浏览器里管日历）。
   ========================================================================== */
.cal-books { display: flex; flex-direction: column; gap: 2px; }

/* 分组分隔线：从"我的"切到"共享给我的" */
.cal-books-sep {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin: 14px 0 6px; padding-top: 12px;
  border-top: 1px solid var(--line);
  font-size: 13px; font-weight: 600; color: var(--muted);
}
.cal-books-sep-note { font-size: 12px; font-weight: 400; color: var(--faint); }

.cal-book-row {
  display: grid;
  grid-template-columns: 22px 30px minmax(0, 1fr) auto auto;
  align-items: center; gap: 10px;
  padding: 8px 6px; border-radius: 8px;
}
.cal-book-row:hover { background: var(--line-2); }
/* 关掉的本子整行变淡 —— 一眼看出"这本现在不显示"，
   但**不 disable**：点一下就能重新勾上（disable 会让人以为"这行坏了"） */
.cal-book-row.is-off .cal-book-name,
.cal-book-row.is-off .cal-book-dot,
.cal-book-row.is-off .cp-trigger-dot { opacity: .45; }

.cal-book-show { display: inline-flex; align-items: center; cursor: pointer; }
.cal-book-show input { accent-color: var(--brand); width: 16px; height: 16px; cursor: pointer; }

.cal-book-dot {
  display: inline-block; width: 13px; height: 13px; border-radius: 3px;
  border: 1px solid var(--line); justify-self: center; flex: 0 0 auto;
}
.cal-book-name { font-size: 14px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; }

.cal-book-tags { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.cal-owner { font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.node-badge-quiet { opacity: .7; }
/* 可写 / 只读两个徽标要**一眼能分**：可写用品牌色，只读用中性灰。
   ⚠️ 别用"同色深浅"区分 —— 用户要的正是在列表里快速扫出"哪些我能动"。 */
.node-badge.is-write { background: var(--brand-soft, rgba(74, 124, 130, .12)); color: var(--brand-ink, var(--brand)); }
.node-badge.is-read { background: var(--line-2); color: var(--muted); }

.cal-book-acts { display: inline-flex; align-items: center; gap: 12px; flex-wrap: wrap; justify-content: flex-end; }
.cal-book-acts .link-quiet { background: none; border: 0; padding: 0; cursor: pointer; font: inherit; font-size: 13px; }
.cal-book-acts .link-quiet:hover { color: var(--brand); text-decoration: underline; }
.cal-book-acts .link-danger:hover { color: var(--danger, #C0533F); }
.cal-book-acts .inline-form { display: inline; margin: 0; }

/* 已共享给谁：第二行，靠名字下方 */
.cal-book-shares {
  grid-column: 3 / -1; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 4px; font-size: 12.5px; color: var(--muted);
}
.cal-book-shares-label { color: var(--faint); }
.cal-book-shares .inline-form { display: inline; margin: 0; }
.cal-book-shares .link-quiet { background: none; border: 0; padding: 0; cursor: pointer; font: inherit; font-size: 12.5px; }
.cal-book-shares .link-danger:hover { color: var(--danger, #C0533F); }

@media (max-width: 640px) {
  /* 窄屏：勾选+色块+名字+归属 一行，操作另起一行靠右 */
  .cal-book-row { grid-template-columns: 22px 26px minmax(0, 1fr); row-gap: 6px; }
  .cal-book-tags { grid-column: 3 / -1; }
  .cal-book-acts { grid-column: 1 / -1; justify-content: flex-start; padding-left: 2px; }
  .cal-book-shares { grid-column: 1 / -1; }
}

/* ============================================================================
   v72：内置调色器（替代 `<input type="color">`，不再弹 Windows 系统取色框）
   ----------------------------------------------------------------------------
   用户原话：「选颜色那个地方点了之后是弹出 Windows 的一个颜色选择框，这个不好，
             我们把它改成内置的，就是就是在网页上弹出一个模态框或者一个什么样的，
             然后选颜色，不要弹 Windows 的，那个一个体验也不好，另外一个也不好看。」
   ========================================================================== */
.cp-field { display: inline-flex; align-items: center; }

/* 触发按钮：小色块 + hex 值，长得像"一个可点的胶囊"而不是原生控件 */
.cp-trigger {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 3px 9px 3px 4px; border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface); cursor: pointer; font: inherit; color: var(--ink);
  transition: border-color .15s, box-shadow .15s;
}
.cp-trigger:hover { border-color: var(--brand); }
.cp-trigger:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.cp-trigger-dot {
  width: 18px; height: 18px; border-radius: 50%; flex: 0 0 auto;
  border: 1px solid rgba(0, 0, 0, .12);
}
.cp-trigger-hex {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11.5px; color: var(--muted); letter-spacing: .3px;
}
/* 行内（日历本清单里）那个调色器更紧凑一点，别把行撑高 */
.cal-book-row .cp-trigger { padding: 2px 4px; border-color: transparent; background: transparent; }
.cal-book-row .cp-trigger:hover { border-color: var(--line); background: var(--surface); }
.cal-book-row .cp-trigger-hex { display: none; }
.cal-book-row .cp-trigger-dot { width: 15px; height: 15px; }

/* 模态框：与 .cal-modal 同一套"遮罩 + 居中面板"，但独立命名
   —— 它可能在**没有日历**的页面上也用（影院管理等），别跟日历耦合。 */
.cp-modal {
  position: fixed; inset: 0; z-index: 2000;
  display: flex; align-items: center; justify-content: center;
  padding: 16px; background: var(--mask, rgba(20, 24, 26, .42));
}
.cp-modal[hidden] { display: none; }
.cp-modal-panel {
  width: min(420px, 100%); max-height: calc(100vh - 48px);
  display: flex; flex-direction: column;
  background: var(--surface); border-radius: 14px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, .22);
}
.cp-modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px 10px; font-size: 15px;
}
.cp-modal-body { padding: 0 16px 4px; overflow-y: auto; }
.cp-modal-foot {
  display: flex; gap: 10px; justify-content: flex-end;
  padding: 12px 16px 16px;
}

/* 大预览块：让用户看到"日程条上白字压在这个颜色上"的真实效果 */
.cp-preview {
  display: flex; align-items: center; justify-content: space-between;
  height: 62px; padding: 0 14px; border-radius: 10px; background: #4A7C82;
  color: #fff; transition: background .12s;
}
.cp-preview-chip { font-size: 13.5px; font-weight: 600; }
.cp-preview-hex {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px; opacity: .8;
}

.cp-swatches {
  display: grid; grid-template-columns: repeat(8, 1fr);
  gap: 8px; margin: 14px 0 12px;
}
.cp-swatch {
  aspect-ratio: 1 / 1; border-radius: 8px; border: 2px solid transparent;
  cursor: pointer; padding: 0; transition: transform .12s, border-color .12s;
}
.cp-swatch:hover { transform: scale(1.08); }
/* 选中态：白环 + 深色外环，任何底色上都看得见（只用深色环时，
   深色块上的环就"消失"了 —— 那正是"点了没反应"的观感来源之一） */
.cp-swatch.active {
  border-color: var(--surface);
  box-shadow: 0 0 0 2px var(--ink);
}
.cp-swatch:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.cp-custom { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding-bottom: 8px; }
.cp-custom label { font-size: 13px; color: var(--muted); }
.cp-hash { font-family: ui-monospace, monospace; color: var(--faint); }
.cp-hex-input { width: 96px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; text-transform: uppercase; }
.cp-hex-input.is-bad { border-color: var(--danger, #C0533F); }
.cp-bad { font-size: 12px; color: var(--danger, #C0533F); }

/* 改色成功/失败的小浮条 */
.cp-toast {
  position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%);
  z-index: 2100; padding: 9px 16px; border-radius: 999px;
  background: var(--ink); color: var(--surface); font-size: 13px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .2);
  transition: opacity .3s, transform .3s;
}
.cp-toast.is-err { background: var(--danger, #C0533F); }
.cp-toast.is-out { opacity: 0; transform: translateX(-50%) translateY(6px); }

/* 模态框开着时锁住背景滚动（否则鼠标滚轮会滚到背后的长清单） */
body.cp-lock { overflow: hidden; }

/* 点了日历空白处就建日程（能力保留，见 templates/calendar/index.php 的 data-new-at）。
   🔴 v48（用户 2026-10-14 第 3 条）：「现在鼠标放在视图上时，变成了一个十字。
      我感觉这个没必要，不用有这样的变化。」
   ⇒ **去掉十字光标**。原来给 `.rt-writable`（能写的人）挂 `cursor: crosshair`
      是想"让人看出这里能点"，但用户明确不要这个视觉变化。
   ⚠️ 只删光标、**不删能力**：点击建日程的 JS 与 `data-new-at` 一律不动
      （那是第 2 条要修好、而不是要撤掉的东西）。
   ⚠️ 连 `.rt-writable` 类本身也留着 —— 它还被别处引用，且是"这个人能不能写"
      这一状态在 DOM 上的标记位，删类名会牵连样式与断言。 */
.link-quiet { color: var(--muted); font-size: 14px; }
.link-quiet:hover { color: var(--brand); }
/* 长得像链接的**按钮**（v48）。`.link-quiet` 本来是给 <a>/<summary> 写的，
   直接套在 <button> 上会带上浏览器的灰底/边框/内边距 ⇒ 与旁边的链接不一致。
   这里把按钮的外观清成纯文字，只保留"可点"的观感（颜色/下划线走 .link-quiet）。 */
button.btn-inline {
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; line-height: inherit; cursor: pointer;
}

/* 顶栏里的文字一律不许换行 —— 一换行就是「首/页」「名/册」竖排，很难看 */
.nav a, .topbar-right a, .who-name { white-space: nowrap; }

/* ---------------- 布局 ---------------- */
.container { max-width: 1080px; margin: 0 auto; padding: 28px 24px 64px; }
.container-narrow { max-width: 560px; margin: 0 auto; padding: 28px 24px 64px; }

.page-head { display: flex; align-items: flex-end; gap: 16px; margin-bottom: 20px; }
.page-head h1 { font-size: 22px; font-weight: 600; margin: 0; }
.page-head .sub { color: var(--muted); font-size: 14px; }
.page-head .actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
/* ---- 页头动作区的完整规格见**文件靠后**的「页头动作区」一节 ----
   刻意放后面：.btn / .btn-ghost / .btn-icon 等基类定义在文件中部，
   CSS 同特异性「后写的赢」——放这里会被它们覆盖（踩过：设置按钮被染成品牌底、
   btn-ghost 仍是白的）。此处只留布局，样式集中在后面一处，便于对照。 */

.section { margin-top: 28px; }
.section-title {
  display: flex; align-items: center; gap: 10px;
  font-size: 15px; font-weight: 600; margin: 0 0 12px;
}
.section-title .hint { font-weight: 400; color: var(--muted); font-size: 13px; }

/* ---------------- 卡片 ---------------- */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.card-pad { padding: 20px; }
/* 相邻的两块之间一定要有缝 —— 影院管理页「添加影院」与「影院列表」、
   年度报告的几张卡，曾经紧贴成一个整块，看不出是两个东西。
   统一在这里给间距，而不是每个页面各写一次（写一次就会漏一处）。 */
.card + .card { margin-top: 18px; }
/* ⚠️ 上面**只写 .card + .card**：.section 自己有 margin-top:28px，
   若在这里再写 `.card + .section { margin-top:18px }`，特异性 0,2,0 会压过
   `.section` 的 0,1,0，反而把原本 28px 的间距改小 —— 修 A 不能顺手弄坏 B。 */
/* 🔴🔴 v24 第 25 批 用户第 3 条：**并排的两块不该再吃这条"相邻间距"**。
   用户原话：「日程和任务看板，两个模块，现在有点错位，一个高，一个低，
             应该完全一样才对。」

   根因（实测，不是猜）：`.home-duo` 里左右两块**都是 `.card`**、且在 DOM 里互为
   紧邻兄弟 → `.card + .card` 命中**第二块** → 给它加了 `margin-top:18px`。
   grid 的 `align-items: stretch` 让两块**高度**相等（那个数是对的），
   但第二块被整体推下去 18px —— 于是"一样高、却一高一低"。
   （我上一版只调了 `.home-duo` 的 align-items 与高度，没动这条，
     真机探针才把 18px 这个偏移量量出来 —— 光看代码是看不出来的。）

   ⇒ 并排容器里的 `.card` 一律不吃相邻间距（间距由 `gap` 统一给）。
      用 `:first-child` 之外的兄弟选择器把它们点掉，且**特异性要比 0,2,0 高**。 */
.home-duo > .card { margin-top: 0; }
.home-duo > .card + .card { margin-top: 0; }

/* 首页头图 */
.hero {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 26px 28px;
  display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
}
.hero-logo { width: 64px; height: 64px; border-radius: 16px; flex: 0 0 auto; }
.hero-main { flex: 1 1 240px; min-width: 0; }
.hero h1 { font-size: 24px; font-weight: 600; margin: 0 0 4px; }
.hero .motto { color: var(--muted); margin: 0; font-size: 14px; }
/* 那句小字到底是谁的：自己写的那句要标一下，否则用户会以为这是系统统一发的 */
.motto-from {
  display: inline-block; margin-left: 6px; padding: 0 7px;
  border-radius: 999px; background: var(--line-2);
  font-size: 11px; color: var(--faint);
}
.motto-edit { margin-left: 8px; font-size: 12px; color: var(--brand-2); white-space: nowrap; }
/* 详情页顶部引用本人的一句话 */
.motto-line {
  margin: -8px 0 16px; padding-left: 12px;
  border-left: 3px solid var(--brand-2);
  color: var(--muted); font-size: 14px;
}
/* ⚠️ .hero-stats / .stat 已随用户 2026-10-03 第 3 条撤掉首页那三个统计，
   这里保留规则本身（其它页面可能还在用 .stat 这个类名），不再由首页引用。 */
.hero-stats { margin-left: auto; display: flex; gap: 28px; }
.stat { text-align: center; }
.stat b { display: block; font-size: 24px; font-weight: 600; line-height: 1.2; }
.stat span { font-size: 12px; color: var(--muted); }

/* 用户第 3 条：标题下那句个人 slogan */
.hero-slogan { color: var(--muted); margin: 4px 0 0; font-size: 14px; }
/* 用户第 3 条：「我的资料 / 我的家谱」换成**图标按钮**（原来是一排文字按钮）。
   margin-left:auto 把它推到最右，与原来统计块的位置一致。 */
.hero-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 12px;
  border: 1px solid var(--line); background: var(--surface); color: var(--muted);
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.icon-btn:hover {
  color: var(--brand); border-color: var(--brand-2);
  /* 底色交给文末「全站悬停统一」段（中性薄雾） */
  background: var(--hov-veil, rgba(44,35,29,.05));
}
.icon-btn svg { display: block; }

/* ---- 用户第 3 条：首页的统计信息块（问候 + 天气 + 几个数字） ---- */
/* ⚠️ v24 第 24 批：这里原有 6 条 `.home-brief*` 旧规则（纵向排列的问候/天气/数字），
   本批把首页信息面板改成"左边内容 + 右边天气贴右端"的横排结构后，
   那 6 条**全部成了死样式**（模板里已经没有任何元素用 home-brief-hello /
   home-brief-weather / home-brief-stats / w-icon / w-temp / w-desc）。
   ⇒ 就地删掉，不留"两处定义同一个东西"：
     上批（第 23 批）就是因为"末尾追加、旧规则留着"，漏了 flex-direction，
     四个按钮竖排把标题行撑到 175px —— 那个坑的成因正是**旧规则没删**。
   新结构见文件末尾「v24 第 24 批 …首页信息面板」那一段（.hb-* / .hb-weather）。 */

/* ---- 用户第 3 条：首页的模块小结卡 ---- */
.mod-cards {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
}
.mod-card {
  display: flex; flex-direction: column; gap: 6px;
  padding: 14px 16px; border-radius: var(--radius);
  border: 1px solid var(--line); background: var(--surface);
  border-left: 3px solid var(--mc, var(--brand));
  color: var(--ink); text-decoration: none;
  transition: box-shadow .15s ease, border-color .15s ease;
}
.mod-card:hover { box-shadow: var(--shadow); text-decoration: none; }
.mod-card-head { display: flex; align-items: center; gap: 8px; color: var(--mc, var(--brand)); }
.mod-card-icon { display: inline-flex; flex: 0 0 auto; }
.mod-card-name { font-size: 13px; font-weight: 600; }
.mod-card-big { font-size: 20px; font-weight: 600; color: var(--ink); }
.mod-card-lines { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.mod-card-lines li { font-size: 12.5px; color: var(--muted); }

/* ---------------- 成员卡 ---------------- */
/* 用户 2026-10-03 第 4 条：名册从「卡片网格」改成**竖向列表**——
   一行一个人，左边头像、中间姓名+关系+手机号/生日、右边操作。
   列表形态下拖动排序也更直观（上下移动，不会像网格那样左右跳）。 */
.people-grid {
  display: flex; flex-direction: column; gap: 8px; margin-top: 4px;
}
/* ⚠️ 整行是**一条 flex**：[拖柄] [头像+信息（占满剩下的）] [右侧动作]。
   原来右侧的「编辑 / ⋯」各自写死 right:46px / right:8px 绝对定位，
   于是再往里加第三个按钮只能继续猜偏移、且「分组」按钮宽度随分组名变化必然重叠；
   改 flex 之后按钮自然排开，姓名那边也不用靠 padding-right 让位了。 */
.person {
  display: flex; align-items: stretch; color: var(--ink); position: relative;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-s);
  transition: border-color .15s ease, box-shadow .15s ease;
  /* v24 第 9 批 #5：千人名册不「卡死」的关键 —— 屏外的卡片浏览器直接跳过
     布局/绘制（4 万+ DOM 节点的排版是一次性成本的大头）。
     contain-intrinsic-size 给屏外卡片一个估算高度撑滚动条，
     auto 前缀让浏览器记住真实渲染高度，滚过后就精确了。
     ⚠️ 拖动排序用的是实时 getBoundingClientRect：目标被自动滚进视口时
     content-visibility 会立刻出真实布局，落点计算不受影响（drag probe 已验证）。 */
  content-visibility: auto;
  contain-intrinsic-size: auto 68px;
}
.person:hover { border-color: var(--brand-2); box-shadow: var(--shadow); }
/* 头像：列表里用小方图（正方形、cover 裁切），不再是整块大图 */
.person .ph {
  background: #F1E7D6; overflow: hidden;
  width: 52px; height: 52px; border-radius: 8px; flex: 0 0 auto;
}
.person .ph img { width: 100%; height: 100%; object-fit: cover; display: block; }
.person .person-main {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; color: var(--ink); text-decoration: none;
  flex: 1 1 auto; min-width: 0;   /* 剩下的宽度都给它，按钮那头不挤它 */
}
.person .pb { flex: 1 1 auto; min-width: 0; }
.person .pname { font-weight: 600; font-size: 15px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.person .pmeta { color: var(--muted); font-size: 12.5px; margin-top: 2px; }
/* 中间那几个"重要信息"：手机 / 生日 */
.person .pfacts { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 4px; font-size: 13px; }
.person .pfact { color: var(--ink); display: inline-flex; align-items: baseline; gap: 5px; }
.person .pfact-k {
  color: var(--muted); font-size: 11px; padding: 1px 5px; border-radius: 4px;
  background: var(--line-2);
}

/* ⚠️⚠️ 过滤用的行必须能真的藏起来。
   `[hidden]` 的 display:none 是**浏览器默认样式**，优先级最低 —— 只要作者样式里
   给同一个元素写了 display（.person 是 display:block、.pp-item 是 flex、
   .att-item 是 inline-flex…），就会把它盖掉：元素"标注了 hidden 却照样显示"，
   于是「输入即筛选」看着完全没反应（列表一条都不少）。
   这个坑本项目已经踩过两次（.att-item、.pp-item），所以统一写成一条通则：
   凡是靠 [hidden] 隐藏的列表行，都必须在作者样式里显式再声明一次。 */
[data-search][hidden],
.pick-row[hidden],
.person[hidden] { display: none !important; }

.badge {
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 500; line-height: 1.7;
  background: var(--line-2); color: var(--muted);
}
.badge-admin { background: #F6E3D7; color: var(--brand-ink); }
.badge-open  { background: #E6F0DC; color: #4A7226; }
.badge-me    { background: var(--brand); color: #fff; }

/* 生日条 —— 一个人可能同时出两条（农历与阳历不在同一天），
   落在同一天时并成一条、kinds 里挂多个标签，正好对应"要分别提醒"。 */
.bdays { display: flex; gap: 12px; flex-wrap: wrap; }
.bday {
  display: flex; align-items: center; gap: 12px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 10px 18px 10px 10px;
  box-shadow: var(--shadow);
  color: var(--ink); text-decoration: none;
  transition: transform .15s ease, box-shadow .15s ease;
}
.bday:hover { transform: translateY(-2px); box-shadow: var(--shadow-l); text-decoration: none; }
.bday img { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.bday b { font-weight: 500; }
.bday .d { color: var(--brand); font-size: 13px; }
.bday-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 3px; }
.bday-date { font-size: 12px; color: var(--faint); font-variant-numeric: tabular-nums; }
/* 「身份证生日 / 阳历生日 / 农历生日」的标签，首页与详情页共用 */
.bd-kind {
  display: inline-block; padding: 0 7px; border-radius: 999px;
  font-size: 11px; line-height: 1.8;
  background: var(--line-2); color: var(--muted);
}
.bd-kind-id    { background: #E6F0DC; color: #4A7226; }
.bd-kind-solar { background: #F6E3D7; color: var(--brand-ink); }
.bd-kind-lunar { background: #E8E4F2; color: #55477E; }

/* 详情页的三个生日 */
.bd-list { list-style: none; margin: 0; padding: 0; }
.bd-list li { display: flex; align-items: baseline; gap: 8px; margin-bottom: 3px; flex-wrap: wrap; }
.bd-list .bd-val { font-variant-numeric: tabular-nums; }

/* ---------------- 表单 ---------------- */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
/* ⚠️ min-width:0 是必需的（v11 实测踩到）：.field 是 grid 项，默认 min-width:auto
   会被内部 <select> 的**最长选项文字**撑开 —— 高级搜索页在 360/375px 下整页横向溢出
   （scrollW 390 > clientW 360），而「影院」下拉正是长选项。给了 0 之后列才能收进版心，
   下拉自身由 width:100% 跟着收。与 .hero-main / .person-move .select 同一套做法。 */
.field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.field.full { grid-column: 1 / -1; }
.field label { font-size: 13px; color: var(--muted); font-weight: 500; }
.field .tip { font-size: 12px; color: var(--muted); }
.input, .select, textarea.input {
  width: 100%; padding: 10px 12px; font: inherit; font-size: 14px;
  color: var(--ink); background: #fff;
  border: 1px solid var(--line); border-radius: var(--radius-s);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.input:focus, .select:focus, textarea.input:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(168, 74, 40, .12);
}
textarea.input { min-height: 76px; resize: vertical; }
.err { color: var(--red); font-size: 12px; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: var(--act-pad-y) var(--act-pad-x);
  font: inherit; font-size: var(--act-font); line-height: var(--act-line); font-weight: 500;
  border-radius: var(--radius-s); border: 1px solid transparent; cursor: pointer;
  background: var(--brand); color: #fff; transition: background .15s ease;
}
.btn:hover { text-decoration: none; }   /* 底色交给文末「全站悬停统一」段（同色系加深） */
.btn-ghost { background: #fff; color: var(--ink); border-color: var(--line); }
.btn-ghost:hover { }   /* 同上：中性薄雾 + 边框微显，见文末统一段 */
.btn-danger { background: #fff; color: var(--red); border-color: rgba(192,83,63,.35); }
.btn-danger:hover { }   /* 同上（危险类走薄雾带一点红） */
/* v24 第 19 批 用户第 7 条：行程行的动作按钮要**看得出颜色**——
   「修改是黄色背景，完成绿色，未成行红色」。
   ⚠️ 这三个是**实心**色块（不是 .btn-danger 那种淡红描边）：用户说的是背景色，
      要的是"一眼分得清哪个是哪个"。所以单独定义，不去改 .btn-danger 的既有语义
      （全站十来处「删除」都在用它，改成实心会到处变样）。 */
.btn-warn { background: #E8A33D; color: #fff; border-color: transparent; }
.btn-ok { background: #5E9E4E; color: #fff; border-color: transparent; }
.btn-del { background: #C0533F; color: #fff; border-color: transparent; }
/* ⚠️ 这三个的 hover 统一交给文件靠后的「第 20 批 全站悬停统一」那一段
   （有底色的一律 `filter: brightness(.92)` 同色系加深，不再各写一个色值）。 */
.btn-sm { padding: 5px 12px; font-size: 13px; }
.btn-row { display: flex; gap: 10px; margin-top: 20px; }

/* ---------------- 详情 ---------------- */
.detail { display: grid; grid-template-columns: 260px 1fr; gap: 24px; }
.detail-photo {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 16px; text-align: center;
  box-shadow: var(--shadow);
}
.detail-photo img {
  width: 100%; aspect-ratio: 1/1; object-fit: cover;
  border-radius: 12px; display: block;
}
.detail-photo .name { font-size: 18px; font-weight: 600; margin: 14px 0 4px; }
.detail-photo .sub { color: var(--muted); font-size: 13px; }

/* ---- v24 第 11 批 #3：详情页改「名片卡 + 全宽堆叠」----
   ⚠️ 旧的 .detail 是 260px + 1fr 两栏，头像卡比右栏矮得多 → 头像下面留一大片空白。
      现在头像与基本信息并排成一张名片，其余内容全宽往下排。 */
.pcard {
  display: flex; align-items: flex-start; gap: 18px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 18px 20px; margin-bottom: 18px;
}
.pcard-avatar { flex: 0 0 auto; }
.pcard-avatar img {
  width: 108px; height: 108px; object-fit: cover;
  border-radius: 12px; display: block; background: var(--line-2);
}
.pcard-main { flex: 1 1 auto; min-width: 0; padding-top: 2px; }
.pcard-name {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  font-size: 21px; font-weight: 600; margin-bottom: 8px;
}
.pcard-real { font-size: 13px; font-weight: 400; color: var(--muted); }
.pcard-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pcard-chip {
  display: inline-block; padding: 3px 10px; border-radius: 999px;
  background: var(--line-2); color: var(--ink); font-size: 12.5px;
}
.detail-stack { display: flex; flex-direction: column; gap: 18px; }
.detail-stack > div { min-width: 0; }
@media (max-width: 640px) {
  .pcard { flex-direction: column; align-items: center; text-align: center; }
  .pcard-name, .pcard-meta { justify-content: center; }
}

.kv { display: flex; flex-direction: column; }
.kv-row {
  display: grid; grid-template-columns: 96px 1fr; gap: 12px;
  padding: 12px 0; border-bottom: 1px solid var(--line-2);
}
.kv-row:last-child { border-bottom: 0; }
.kv-row dt { color: var(--muted); font-size: 13px; }
.kv-row dd { margin: 0; font-size: 14px; word-break: break-all; }
.mono { font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; letter-spacing: .5px; }

/* ---- 工作经历（详情页，用户 2026-10-03 第 4 条）----
   一个人可以有多段，逐条列出来；"现在还在那儿"的那段排最前并带徽标。 */
.job-history { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.job-history-item {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  word-break: normal;       /* ⚠️ .kv-row dd 上有 break-all，这里要收回来，
                               否则"中国海洋大学"会被从中间断开 */
}
.job-history-item .job-org { font-weight: 600; }
.job-history-item .job-title { color: var(--brand-ink); }
.job-history-item .job-span { font-variant-numeric: tabular-nums; }
/* 「我对他的私人备注」那一块：淡一点，别和"公开资料"抢注意力 */
.my-note-card { background: #FCFAF6; }

/* ---------------- 提示 ---------------- */
.flash { display: flex; flex-direction: column; gap: 8px; margin-bottom: 18px; }
.flash-item {
  padding: 10px 14px; border-radius: var(--radius-s); font-size: 14px;
  background: #E6F0DC; color: #3D6B1F;
}
.flash-item.err { background: #F8DDDA; color: #A1341F; }

/* ---- 🔴 v55（用户 2026-10-17 第 2 条）：任务详情「状态选完即改」的**轻提示** ----
   就地改完要给一句反馈（尤其循环任务会顺带排下一次，那句"下一次也排上了"不能丢）。
   它**不是**页首那条 `.flash`（那是一整块、会撑开版面）——
   这里是一个**浮在顶部**的小条：`position: fixed` 不占文档流，
   所以"改状态"不会让页面跳动一下（那正是就地改要避免的）。
   ⚠️ 配色沿用上面 `.flash-item` 那两套（绿=成功 / 红=失败），保持全站一致。 */
.tk-flash {
  position: fixed; top: 14px; left: 50%; transform: translateX(-50%);
  z-index: 300;                       /* 高于自建弹框（.cal-modal 是 200） */
  max-width: min(92vw, 460px);
  padding: 9px 16px; border-radius: 999px;
  font-size: 13.5px; line-height: 1.5; text-align: center;
  box-shadow: 0 3px 14px rgba(0,0,0,.14);
  background: #E6F0DC; color: #3D6B1F;
}
.tk-flash.is-err { background: #F8DDDA; color: #A1341F; }
.tk-flash[hidden] { display: none; }

.empty {
  background: var(--surface); border: 1px dashed var(--line);
  border-radius: var(--radius); padding: 40px 20px; text-align: center;
  color: var(--muted);
}
.empty b { display: block; color: var(--ink); font-weight: 500; margin-bottom: 4px; }

/* ---------------- 登录 / 安装 ---------------- */
.auth-wrap {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 40px 20px;
}
.auth-card {
  width: 100%; max-width: 400px; background: var(--surface);
  border: 1px solid var(--line); border-radius: 20px; box-shadow: var(--shadow-l);
  padding: 32px;
}
/* 图标 + 「用手机号登录你的家书」+ 家庭名，整块水平居中。
   ⚠️ 只有这一块居中：下面的表单仍然左对齐（label 左对齐才好读），
   「忘记密码」那行本来就是居中，所以视觉上不会歪。 */
.auth-head { text-align: center; }
.auth-card .logo {
  display: block; margin: 0 auto 14px;
  width: 56px; height: 56px; border-radius: 14px;
}
.auth-card h1 { font-size: 20px; font-weight: 600; margin: 0 0 4px; }
.auth-card .lead { color: var(--muted); font-size: 14px; margin: 0 0 24px; }
.auth-card .btn { width: 100%; margin-top: 20px; }
.auth-foot { margin-top: 18px; font-size: 13px; color: var(--muted); text-align: center; }

/* ---------------- 其他 ---------------- */
.muted { color: var(--muted); }
.small { font-size: 13px; }
.nowrap { white-space: nowrap; }
.search { display: flex; gap: 8px; margin-bottom: 16px; }
.search .input { max-width: 260px; }

/* ============================================================================
   日期 + 时:分 组合控件（templates/_datetime.php）—— v37 重做
   ----------------------------------------------------------------------------
   形态（用户 2026-10-09 第 4 次反馈点名要的）：
     日期 = 年 / 月 / 日 三个原生下拉；时间 = 时 : 分 两个原生下拉。
   起止两端默认同一行，放不下时自动换到下一行（flex-wrap，见 .dt-cols）。

   为什么把浏览器自带的 <input type=date> 换掉：
     它的日历弹层是浏览器自己画的，我们碰不到 DOM、也控制不了流程；
     Chrome 在"点顶部 2026年 10月 切到年月视图"之后要靠用户再点一次日期或
     按 Esc 才回得到日期视图，各家还不一致 → 用户遇到的就是"选完年月就卡住、
     选不了日"。三个原生下拉选完即生效，不需要任何确定按钮。

   这一整段不再有"未升级就藏原生控件"的逻辑（v24~v36 有过）。
   服务端给的就是最终形态，JS 只负责绑事件 —— 所以"JS 没跑 / 加载失败"时
   控件依然完整可用，不是"骨架占位"。
   ============================================================================ */
.dt-input { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
/* 🔴 v44：内联标签（`_datetime.php` 的 `$dtLabel`）—— 让"日期范围："与最新控件
   **同一行**。宽度与观影式 `.mv-lb`（62px）一致，右对齐、冒号由 `::after` 给，
   与「日历本」「标题」那几行的标签左缘对齐。
   ⚠️ 它是 `.dt-input` 的**兄弟节点**（不是子节点）—— 见 `_datetime.php` 里的说明：
      放进去会被"收起原生端点"那条规则一起 clip 掉。选择器用 `+`（紧随其后）。 */
.dt-inline-lab {
  flex: 0 0 auto; width: 62px; text-align: right;
  font-size: 14px; font-weight: 600; color: var(--ink);
  line-height: 1.4; word-break: keep-all;
}
/* ⚠️ 它同时带 `.mv-lb`（在 `.mv-row` 里时由那条规则给固定宽度与冒号）；
   这里再写一遍是为了**不在 `.mv-row` 里**（编辑整页那种两列排版）也成立。 */
.dt-inline-lab::after { content: '：'; font-weight: 400; color: var(--muted); }
.dt-inline-lab > i { font-style: normal; color: var(--red, #C0392B); margin-left: 1px; }
/* 范围组里带标签时：标签在前、控件在后，横向排（`.dt-range.dt-cols` 已是 flex row） */
.dt-range > .dt-inline-lab { flex: 0 0 62px; }
@media (max-width: 560px) {
  .dt-inline-lab { flex: 0 0 52px; width: 52px; font-size: 13px; }
}
.dt-input .dt-native { display: contents; }
.dt-input .dt-semi { display: none; flex: 1 1 220px; min-width: 0; }

/* ---- 日期：年 / 月 / 日 三个下拉 ---- */
.dt-date-sel { display: flex; align-items: center; gap: 4px; min-width: 0; }
.dt-input .dt-year,
.dt-input .dt-month,
.dt-input .dt-day {
  padding: 9px 6px; border: 1px solid var(--line); border-radius: var(--radius-s);
  background: var(--surface); color: var(--ink); font: inherit; font-size: 13.5px;
  font-variant-numeric: tabular-nums; cursor: pointer; min-width: 0;
}
/* 年最宽（4 位数 + 箭头），月/日短一些 —— 省下来的宽度留给时:分 */
.dt-input .dt-year  { flex: 0 1 86px; }
.dt-input .dt-month { flex: 0 1 66px; }
.dt-input .dt-day   { flex: 0 1 64px; }
/* 隐藏的 `*_date` 真正的字段（提交契约）—— 它必须留在 DOM 里 */
.dt-input input.dt-date[type="hidden"] { display: none; }
/* 旧「原生 date 框」形态（$dtDateType='native' 时才出现）保留样式 */
.dt-input .dt-date:not([type="hidden"]) {
  flex: 1 1 150px; min-width: 0; cursor: pointer;
  padding: 9px 11px; border: 1px solid var(--line); border-radius: var(--radius-s);
  background: var(--surface); color: var(--ink); font: inherit;
  accent-color: var(--brand);
}
.dt-input .dt-date:not([type="hidden"])::-webkit-calendar-picker-indicator { cursor: pointer; opacity: .65; }

/* ---- 时间：时 : 分 ---- */
.dt-time { display: flex; align-items: center; gap: 3px; flex: 0 0 auto; }
.dt-input .dt-hour,
.dt-input .dt-minute,
.dt-input .dt-second {
  padding: 9px 6px; border: 1px solid var(--line); border-radius: var(--radius-s);
  background: var(--surface); color: var(--ink); font: inherit; font-size: 13.5px;
  font-variant-numeric: tabular-nums; cursor: pointer;
}
.dt-input .dt-colon { color: var(--muted); font-weight: 600; }

/* 必填但没填时描红（由 app.js 的 guardRequired 打类） */
.dt-input.dt-missing .dt-year,
.dt-input.dt-missing .dt-month,
.dt-input.dt-missing .dt-day,
.dt-input.dt-missing .dt-date { border-color: #C0392B !important; }

/* ============================================================================
   起止范围（.dt-range）：两端一行优先，放不下才换行
   ============================================================================
   用户原话（2026-10-09 第 4 次反馈）：
     现在开始和结束时间分在了两行，导致日期这个特别特别的长，后面的时和分又
     非常非常的短，非常不协调。所以这两个日期如果能放一排，如果能放得下的话，
     就应该放一排。因为日期和时间，分和时和分的这个长度基本上是固定的，
     所以你可以判断一下它到底能不能一行，能不能放得下。如果能放下的话就放在一行。
     如果在手机上，比如说它这个页面非常窄，它就可以把它挤到下一行。
     但是现在不要让它默认显示在两行上，应该默认显示在一行上才对。

   ⇒ 做法：flex 容器 + flex-wrap。每端有一个最小的可用宽度（.dt-part 的
     min-width），浏览器在每次布局时自己判断放不放得下；放得下 → 一行；
     放不下 → 第二端自然掉到下一行。
     ⚠️ 不用 JS 量宽度：字体没加载完 / 容器动画中都会量错，窗口缩放还要重算；
        flex-wrap 是浏览器布局的一部分，永远是对的。
   ⚠️ .dt-range 的标签只在组上留一个（见 _event_fields.php 的说明）：
      两端是同一个范围的两个端点，各挂一个标签会变成"看着像两个字段"的错觉。 */
.dt-range { display: flex; flex-direction: column; gap: 8px; }
.dt-range-label { font-size: 14px; color: var(--ink); font-weight: 600; }
/* 升级（= app.js 挂上 .dt-cols）之后：两端并排、必要才换行 */
.dt-range.dt-cols {
  flex-direction: row; flex-wrap: wrap; align-items: center;
  gap: 10px 18px;
}
/* 两端的"最小可用宽度"：日期(年+月+日) + 时:分 摆得下。
   值取 250px —— 实测"年(86)+月(66)+日(64)+时:分(约 96)+间距"约 330px 才舒服，
   但最小可用是 250px（会各自压缩一点），这样在常见的 560px+ 容器里
   两端（250×2 + 18 间距 = 518）肯定放得下 → 默认就是一行。 */
.dt-range.dt-cols > .dt-input,
.dt-range.dt-cols > .field { flex: 1 1 250px; min-width: 250px; display: flex; }
.dt-range.dt-cols > .field { flex-direction: column; gap: 6px; }
/* 标签占满整行（不然它会跟第一端挤在一起） */
.dt-range.dt-cols > .dt-range-label { flex: 1 1 100%; }
/* 极窄屏：两端各自铺满一行（用户说的"手机上就挤到下一行"） */
@media (max-width: 560px) {
  .dt-range.dt-cols > .dt-input,
  .dt-range.dt-cols > .field { flex: 1 1 100%; min-width: 0; }
}

/* ============================================================================
   Semi 仅剩的用途：工作经历的「年月选择」（<input type="month"> 的升级）
   ----------------------------------------------------------------------------
   ⚠️ 日期/时间控件（.dt-input）v37 起已完全不用 Semi —— 上面那套自建下拉
      就是因为它给的弹层交互不可控（选完年月卡住）。这里保留的是另一处。
   ============================================================================ */
.dt-semi.dt-semi-month { display: none; }
.dt-semi.dt-semi-month.is-live { display: block; width: 100%; }
body[class] {
  --semi-color-primary: var(--brand, #A84A28);
  --semi-color-primary-hover: var(--brand-2, #C05F38);
  --semi-color-primary-active: var(--brand-ink, #8D3C20);
  --semi-color-focus-border: var(--brand, #A84A28);
}
body[class] .dt-semi .semi-input-wrapper {
  width: 100%; height: 40px;
  border: 1px solid var(--line); border-radius: var(--radius-s);
  background: var(--surface); color: var(--ink);
  display: flex; align-items: center;
}
body[class] .dt-semi .semi-input-wrapper:hover { border-color: var(--brand-2); background: var(--surface); }
body[class] .dt-semi .semi-input { font: inherit; color: var(--ink); }
body[class] .dt-semi .semi-input::placeholder { color: var(--muted); }
body[class] .dt-semi .semi-datepicker-input,
body[class] .dt-semi .semi-datepicker {
  background: transparent !important; height: auto !important;
  min-height: 0 !important; border: 0; padding: 0;
}
body[class] .dt-semi .semi-input-wrapper .semi-input {
  height: 100%; line-height: 40px; padding: 0 11px; background: transparent;
}
body[class] .semi-popover .semi-datepicker { background: var(--surface, #fff); }
body[class] .semi-datepicker-day-selected { background-color: var(--brand, #A84A28); }
body[class] .semi-datepicker-day-hoverday,
body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb, var(--brand, #A84A28) 12%, transparent); }

/* 「以哪个生日为准」的两块：被选中的那块描一圈，一眼看出填的是哪边 */
.bd-source { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
.bd-pane { border: 1px solid transparent; border-radius: var(--radius); padding: 10px 12px 2px; }
.bd-pane.is-primary { border-color: var(--brand-2); background: #FDF6F1; }
.bd-pane .bd-pane-k { font-size: 13px; font-weight: 600; margin: 0 0 8px; }
.bd-readonly {
  display: inline-block; padding: 6px 10px; border-radius: var(--radius-s);
  background: var(--line-2); color: var(--muted); font-size: 14px;
  font-variant-numeric: tabular-nums;
}

/* ---------------- 称谓标签 ---------------- */
.rel-label {
  display: inline-block; padding: 1px 9px; border-radius: 999px;
  background: #F6E3D7; color: var(--brand-ink);
  font-size: 11px; font-weight: 500; line-height: 1.8;
  margin-right: 2px;
}

/* ---------------- 家谱（以我为中心） ----------------
   v22：按 family-chart **官方用法**接（容器 class="f3" + 引 family-chart.css），
   库把 data[0].id 当 main_id，所以把「最高祖先」排第一位以画出尽可能多的血亲旁支；
   加载后再把镜头平移回「我」，兼顾"看到全树"与"以自己为中心"。

   ⚠️ 免费版 family-chart 的算法只画「树根的祖先链 + 后代 + 树根的兄弟姐妹 + 各人配偶」，
      **不会从配偶继续向上/向下 walk**。这一点已用**官方示例自己的数据**验证过：
      官方 data.json 里 13 人（含配偶 Andrea 的父母 Zadro/Zadra，格式完全按官方），
      库只画出 11 人 —— 配偶方那两位长辈一个都没画。
      所以「把数据按格式全塞进去就能画全」在姻亲这一项上不成立，是库的算法边界。 */

.kin-tools { display: flex; gap: 8px; justify-content: flex-end; margin: 0 0 10px; }

.kin-chart {
  position: relative;
  width: 100%;
  height: clamp(420px, 62vh, 720px);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
  cursor: grab;
  /* 拖拽/缩放由库接管，别让浏览器插手滚动 */
  touch-action: none;
}
.kin-chart:active { cursor: grabbing; }

/* 库的 `.f3` 会自带一套**深色主题**（--background-color: rgb(33,33,33) / --text-color: #fff）。
   家书是宣纸浅色，必须在这里把库的主题变量改掉；否则卡片文字会变白、看不出内容。
   只覆盖颜色变量，**不动**库的结构规则（那些是撑满/定位必需的）。 */
.kin-chart.f3 {
  --male-color:   #F1E7D6;   /* 库的男卡底色，改成纸色（我们自己的 .kin-card 会再盖一层） */
  --female-color: #F1E7D6;
  --genderless-color: #F1E7D6;
  --background-color: transparent;
  --text-color: var(--ink);
  font-family: inherit;
}

/* 隐藏 family-chart 自带的空导航栏（我们用自己的「看全部 / 回到我」按钮） */
.kin-chart .f3-nav-cont { display: none !important; }

/* 官方 CSS 已经给了 `.f3 svg.main_svg { width:100%; height:100% }`（撑满画布），
   这里只补一个 display:block，避免 svg 作为 inline 元素多出基线空隙。 */
.kin-chart svg.main_svg { display: block; z-index: 1; }

/* family-chart 的 HTML 卡片层 #htmlSvg 与 SVG 连线层由库的 onZoom 同步 transform。
   卡片容器 .card_cont 已经 absolute + translate(x,y)；里面再放我们的 .kin-card。 */
.kin-chart #htmlSvg { z-index: 2; }
.kin-chart .card_cont { pointer-events: none; }

/* ⚠️ 库的性别底色规则是 `.f3 div.card-male .card-inner`（3 段），
   比 `.kin-chart .card_cont .card-inner`（3 段）**同级**，靠先后顺序定胜负 ——
   所以这份覆盖必须写在**引库 CSS 之后**（app.css 在 layout 里就是这么排的）。
   把外层中转容器全部归零，只留我们自己的 .kin-card。 */
.kin-chart .card_cont > .card,
.kin-chart .card_cont .card-inner,
.kin-chart.f3 .card_cont .card-inner {
  background: transparent;
  padding: 0;
  border-radius: 0;
  min-height: 0;
  width: auto;
  height: auto;
  outline: none;
}
.kin-chart .card_cont .card-inner { display: block; }

/* v21：卡片尺寸与 setCardDim({w:140, h:120}) 对齐 */
/* 用户 2026-10-03 第 1 条：
     · 头像外框改**长方形**（宽 96 × 高 72），宽度基本就是头像显示宽度；
       img 用 object-fit: contain —— 既不裁切，也不会把外框撑变形。
     · 信息（姓名/称呼）在头像**下方**。
     · 卡片不再整张是链接：只有 .kin-nm 是 <a>，右下角是独立的 <button>，
       两者分开，避免"点按钮却进了成员页"的误触。
   卡片尺寸与 JS 的 setCardDim({w,h}) 必须一致（库按它算间距与连线落点）。 */
/* 卡片尺寸：**框宽 = 头像宽**（用户 2026-10-03：「外面那个框，宽度和头像图片一致，
   下面显示名字就行了」）。
   🔴 头像槽位是 **1:1（正方形）**（见 App\ImageSlot::SLOTS['avatar']）
   —— 所以"框宽 = 头像宽"其实要求框里的头像区**也是正方形**：
   之前写成 96×72 的长方形 + `object-fit: contain`，正方形照片被**缩到 72 宽**
   左右各留 ~12px 空白 → 看起来"框比头像宽很多"（用户第 8 条「还是比头像宽很多」）。
   现在头像区取正方形，照片 `object-fit: cover` 铺满 —— 框就是头像本身那么大。
   ⚠️ 这个 90/128 必须与 JS 里 setCardDim({w,h}) **保持一致**：
      库按它算卡片间距与连线落点，两边不一致连线就会歪。 */
.kin-card {
  --kin-w: 90px;
  --kin-h: 128px;
  --kin-ph: 88px;     /* 头像区（正方形）：卡宽 − 左右各 1px 边框 */
  position: absolute;
  display: flex; flex-direction: column; align-items: center;
  gap: 0;
  width: var(--kin-w);
  height: var(--kin-h);
  box-sizing: border-box; padding: 5px 0 4px;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 1px 3px rgba(43,38,32,.10);
  color: var(--ink);
  transition: border-color .15s ease, box-shadow .15s ease;
  overflow: hidden;
  pointer-events: auto;                 /* 父 .card_cont 是 none，这里恢复 */
}
/* ⚠️⚠️ 居中**绝不能**用 transform、也不能用 `inset:0 + margin:auto`。
   两次踩坑记录（都是用户先发现的：一次"鼠标移上去框会动"，一次"线又错位了"）：
     · 用 `transform: translate(-50%,-50%)`：库自带
       `.f3 div.card:hover > div { transform: translate(0,-2px) }` 会把居中 transform
       **整份覆盖** → 鼠标一进卡片整张卡跳 53×58px（半张卡）。
     · 用 `inset: 0` + `margin: auto`：**在这里不成立** —— 含块是库的 `div.card`，
       在 Cartesian 模式下它只有 **1.8px 宽**（宽度由内部一个空 div 撑出来），
       auto 外边距在一个比卡片还窄的盒子里根本分不出空间，结果卡片整体**右移了 53.1px**
       （实测每张卡的中心都偏在节点右边 53.1px）→ 连线与卡片错位。
   正解：`left/top: 50%` + **半个自身尺寸的负外边距** —— 位置完全由布局决定，
   不经过 transform，库那条 :hover 规则怎么覆盖都影响不到。
   ⚠️ 负外边距必须写 `calc(var(--kin-*) / -2)`，与上面的尺寸共用同一个变量；
      写成死数字（-59/-66）迟早会和尺寸脱节。 */
.kin-card {
  left: 50%; top: 50%;
  margin-left: calc(var(--kin-w) / -2);
  margin-top: calc(var(--kin-h) / -2);
}
/* ⚠️ 还要把库那条 `:hover` 位移**归零**（用户反馈「鼠标放上去框会动」的另一半）。
   库的 family-chart.css:517 有：
     `.f3 div.card:hover > div { transform: translate(0, -2px); }`
   它除了覆盖居中，还会给卡片一个纯视觉的"上浮 2px"（实测 dy = -1.8px）。
   我们自绘的宣纸卡不需要库那套悬停装饰，直接归零。
   ⚠️ 不去改 vendor 里那份文件（升级库会整份覆盖、改动静默消失）：
      在 app.css 覆盖即可 —— 它加载在 family-chart.css **之后**，
      同特异性下后写的赢，**不需要 !important**。 */
.kin-chart.f3 div.card:hover > div { transform: none; }
.kin-chart.f3 div.card > div { transition: none; }
.kin-card:hover {
  box-shadow: var(--shadow-l); border-color: var(--brand-2);
}
/* 头像区：**正方形、铺满**（见上面 --kin-ph 的说明）。
   用户 2026-10-03 第 8 条：「宽度和头像图片一致，下面显示名字就行了」。 */
.kin-card .kin-ph {
  width: var(--kin-ph); height: var(--kin-ph);
  border-radius: 8px; overflow: hidden;
  background: #F1E7D6; flex: 0 0 auto;
  display: block;
}
/* 头像槽位是 1:1，所以这里的容器也是 1:1 —— cover 不会裁掉任何内容
   （照片本来就是按 1:1 裁好的）。⚠️ 别改成 contain：那会在框里留出空白，
   看起来就是"框比头像宽"。 */
.kin-card .kin-ph img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.kin-card .kin-ph:hover { box-shadow: inset 0 0 0 2px var(--brand-2); }
/* 姓名：用户 2026-10-03 第 7 条起，点姓名 = **以他为中心**重画家谱
   （点头像才是进资料页）。
   ⚠️ 第 8 条明确要求**不要再显示"资料 / 以他为中心"那两行悬停提示**（没必要）——
      所以 .kin-hint 这套已整个删掉，卡片上头像下面只有名字。 */
.kin-card .kin-nm {
  display: block;
  font-size: 12.5px; font-weight: 600; line-height: 1.25; max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  margin-top: 5px; color: var(--ink); text-decoration: none;
}
.kin-card .kin-nm:hover { color: var(--brand); text-decoration: underline; }
/* 称呼在姓名下面一行，用品牌色的浅字（参考实现是「(称呼)」） */
.kin-card .kin-lb {
  font-size: 10.5px; color: var(--brand); line-height: 1.2; max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.kin-card .kin-mark { font-size: 10px; color: var(--faint); }

/* 「我」：品牌色描边 + 光圈 */
.kin-card.is-me {
  border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(168, 74, 40, .12), var(--shadow);
}
.kin-card.is-me .kin-lb { color: var(--brand-ink); font-weight: 600; }
/* 「待确认」（只填了称呼、没对应到真人）：虚线 */
.kin-card.is-ph { border-style: dashed; background: #FCFAF6; }

/* family-chart 默认连线颜色改淡，与之前自研版一致。
   库在 path 上写了 inline stroke="#fff"，必须用 !important 覆盖。 */
.kin-chart svg.main_svg .links_view path.link {
  fill: none;
  stroke: #C9BDA9 !important;
  stroke-width: 1.6;
}

.tree-legend { font-size: 12px; color: var(--muted); margin: 14px 2px 0; }

/* 详情页里的迷你家谱 */
.mini-tree { display: flex; flex-direction: column; gap: 8px; }
.mini-gen { display: flex; flex-wrap: wrap; gap: 8px; }
.mini-node {
  display: flex; align-items: center; gap: 7px;
  padding: 5px 12px 5px 5px; border-radius: 999px;
  background: #FAF7F1; border: 1px solid var(--line);
  color: var(--ink); font-size: 13px;
}
.mini-node:hover { text-decoration: none; border-color: var(--brand); }
.mini-node img { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; }
.mini-node .mn-label { font-size: 11px; color: var(--brand); }
.mini-node.is-me { border-color: var(--brand); background: #F6E3D7; }

/* ---------------- 亲属关系编辑 ---------------- */
.rel-summary {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px; margin-bottom: 18px;
}
.rel-sum-item {
  background: #FAF7F1; border: 1px solid var(--line-2);
  border-radius: var(--radius-s); padding: 10px 14px;
}
.rel-sum-k { display: block; font-size: 12px; color: var(--muted); margin-bottom: 3px; }
.rel-sum-v { font-size: 14px; }
.rel-sum-v a { margin-right: 6px; }

.rel-add { border-top: 1px solid var(--line-2); padding-top: 16px; }
.rel-add-title { font-size: 14px; font-weight: 600; margin: 0 0 12px; }
.rel-form {
  display: grid; grid-template-columns: 1fr 1fr 1.4fr auto;
  gap: 12px; align-items: end;
}
.rel-list { margin-top: 16px; font-size: 13px; }
.rel-list summary { cursor: pointer; color: var(--muted); }
.rel-list ul { margin: 10px 0 0; padding-left: 18px; }
.rel-list li { margin-bottom: 6px; }
.rel-list .btn { margin-left: 6px; }

/* ---------------- 三角色 / 存储设置 ---------------- */
.role-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
.role-card { background: #FAF7F1; border: 1px solid var(--line-2); border-radius: var(--radius-s); padding: 14px 16px; }
.rc-name { font-weight: 600; margin-bottom: 5px; }
.rc-desc { margin: 0; font-size: 13px; color: var(--muted); line-height: 1.65; }

.radio-row { display: flex; flex-direction: column; gap: 10px; }
.radio-item {
  display: flex; gap: 10px; align-items: flex-start;
  background: #FAF7F1; border: 1px solid var(--line-2);
  border-radius: var(--radius-s); padding: 12px 14px; cursor: pointer;
}
.radio-item input { margin-top: 3px; }
.check-item { display: flex; gap: 8px; align-items: flex-start; font-size: 13px; cursor: pointer; }
.check-item input { margin-top: 3px; }

.oss-box {
  border: 1px solid var(--line-2); border-radius: var(--radius-s);
  padding: 12px 16px; background: #FCFAF6;
}
.oss-box summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--muted); }

.plain-list { margin: 0; padding-left: 18px; font-size: 14px; }
.plain-list li { margin-bottom: 7px; }

@media (max-width: 720px) {
  .rel-form { grid-template-columns: 1fr; }
}

@media (max-width: 720px) {
  /* 390px 宽度放不下整套顶栏：只留 图标 + 导航 + 头像 + 退出 */
  .topbar-inner { gap: 6px; padding: 0 10px; height: 54px; }
  .brand img { width: 26px; height: 26px; }
  .nav { gap: 0; }
  .nav a { padding: 6px 8px; font-size: 13px; }
  .topbar-right { gap: 6px; }
  .who-name, .who-pwd { display: none; }  /* 名字与「改密码」收起，设置页里都有 */
  .who img { width: 26px; height: 26px; }
  .container, .container-narrow { padding: 20px 16px 48px; }
  .form-grid { grid-template-columns: 1fr; }
  .detail { grid-template-columns: 1fr; }
  .hero { padding: 20px; gap: 14px; }
  .hero h1 { font-size: 20px; }
  .hero-stats { margin-left: 0; gap: 20px; }
  .page-head { flex-wrap: wrap; align-items: center; }
  .page-head .actions { margin-left: 0; width: 100%; }
  /* ⚠️ 窄屏下动作区**必须允许换行**：
     一行里可能有 6 个按钮（月/周/天 + 上一周/今天/下一周 + 新建 + ☰），
     360px 根本放不下。不换行的后果不只是横向溢出（整页被拖走），
     还会把按钮**挤扁**成高低不一（实测 91/68/44 三种高度，同排按钮参差不齐）——
     而"页头按钮同高"是用户提过三次的明确要求。
     换行之后每个按钮保持自己的尺寸，高度自然一致。 */
  .page-head .actions { flex-wrap: wrap; }
  .page-head .actions > .movie-set { margin-left: 0; }
  .kv-row { grid-template-columns: 84px 1fr; }
  .search { flex-wrap: wrap; }
  .search .input { max-width: none; flex: 1 1 auto; }
}

/* ---------------- 「我怎么称呼家人」（视角属性，见 app/Nickname.php） ---------------- */
.nick-list { display: flex; flex-direction: column; gap: 8px; }
.nick-row {
  display: grid; grid-template-columns: minmax(120px, auto) 1fr auto;
  gap: 10px; align-items: center;
  padding: 8px 10px; border-radius: var(--radius-s);
  background: #FAF7F1; border: 1px solid var(--line-2);
}
.nick-who { display: flex; align-items: center; gap: 8px; font-size: 14px; min-width: 0; }
.nick-who img { width: 28px; height: 28px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.nick-who span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 详情页上的单个"我叫他什么"输入行 */
.nick-inline { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.nick-inline .input { flex: 1 1 220px; max-width: 340px; }

@media (max-width: 720px) {
  .nick-row { grid-template-columns: 1fr; }
  .nick-inline .input { max-width: none; }
}

/* ---------------- 连通性测试与诊断（设置页） ---------------- */
.test-actions { display: flex; flex-direction: column; gap: 10px; }
.test-actions form { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }

.oss-diag {
  margin-top: 16px; border-radius: var(--radius-s); overflow: hidden;
  border: 1px solid var(--line);
}
.oss-diag-head {
  display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
  padding: 10px 14px; font-size: 13px;
  background: var(--line-2);
}
.oss-diag.is-ok  .oss-diag-head { background: #E6F0DC; color: #3F6021; }
.oss-diag.is-bad .oss-diag-head { background: #F6E3D7; color: var(--brand-ink); }
.oss-diag-table { width: 100%; border-collapse: collapse; font-size: 13px; background: var(--surface); }
.oss-diag-table th,
.oss-diag-table td { padding: 8px 12px; text-align: left; vertical-align: top; border-top: 1px solid var(--line-2); }
.oss-diag-table th { font-weight: 500; color: var(--muted); font-size: 12px; }
.oss-diag-table td:nth-child(1) { white-space: nowrap; }
.oss-diag-table td:nth-child(3) { white-space: nowrap; }
.oss-diag-table tr.row-ok  td:nth-child(2) { color: #4A7226; }
.oss-diag-table tr.row-bad td:nth-child(2) { color: var(--red); }

@media (max-width: 720px) {
  .oss-diag-table th:nth-child(3),
  .oss-diag-table td:nth-child(3) { display: none; }
}

/* ============================================================
   以下都是 2026-09-30 那一批反馈加的东西
   ============================================================ */

/* 只给读屏用的标签：视觉上不占地，但无障碍名还在（别用 display:none，那会一起藏掉）
   🔴 v29 用户第 2 条：「任务完成复选框的提示文字还在」——
      真根因：`_task_check.php` 与任务看板用的是 `class="visually-hidden"`，
      而**本文件里从来没有这个类**（项目一直只定义 `.sr-only`）。
      CSS 里查不到的类 = 没有任何样式 → 那段文字是**普通 inline span，直接显示出来**，
      就贴在复选框旁边（用户看到的就是「标记完成：待办」）。
      ⇒ 修法：把两个名字指向同一条规则（两种写法都认）。
        为什么不是"把模板改成 sr-only 了事"：这个类名在下游（别的模板、以后新写的）
        同样可能被用到，名字不再是隐患；而只改调用点的话，下次谁再写
        `visually-hidden` 就又漏了。这里是"给能力补上定义"，不是"给某一处打补丁"。*/
.sr-only,
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* 区块之间的小间隔（比写 style="margin-top:20px" 好读） */
.stack-gap { height: 20px; }

/* ---------------- 名册：按关系分组分栏 ---------------- */
.group-block { margin-top: 24px; }
.group-head {
  display: flex; align-items: baseline; gap: 8px;
  margin: 0 0 10px; padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
  font-size: 15px; font-weight: 600;
}
.group-head .group-title { color: var(--ink); }
/* 分组名前面那道砖红短线，让分栏在长页面上更容易一眼定位 */
.group-head .group-title::before {
  content: ''; display: inline-block; vertical-align: middle;
  width: 3px; height: 13px; margin-right: 8px; border-radius: 2px;
  background: var(--brand);
}
.group-head .group-n { margin-left: auto; font-weight: 400; font-size: 12px; color: var(--faint); }

/* 卡片改成 div > a + details：表单不能套在链接里（非法 HTML，浏览器会把 DOM 拆开） */
.person-main { display: block; color: var(--ink); }
.person-main:hover { text-decoration: none; }

/* ---- 行右侧的动作区（分组 / 编辑 / ⋯）----
   ⚠️ 2026-10-04 之前这里有一条 `.person-more { display: none; }`，
      而 `<details>` 默认 display 是 block，被它一压**整个 ⋯ 按钮都不见了**
      —— 用户报的「名册没有地方可以更改分组了」就是这条（它把唯一的入口藏了）。
      现在的做法是：动作区本身是 flex，三个按钮自然排开，谁都不用写死偏移。 */
.person-actions {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 10px 8px 0; flex: 0 0 auto;
}
.person-edit {
  padding: 3px 10px; font-size: 13px; white-space: nowrap;
}
.person-more-btn {
  padding: 3px 9px; font-size: 15px; line-height: 1.3;
  color: var(--muted); white-space: nowrap;
}
.person-more-btn:hover { color: var(--brand); }

/* ---------------- 名册卡片动作的**共享浮层**（v24 第 10 批 #6）----------------
   「分组」「移出名册」两份表单全页只渲染一份（原来每卡各一份，1184 人 =
   2369 个 form / 27234 个 input，密码管理器逐个扫描把页面拖死）。
   这里给共享浮层一个定位容器，面板由 JS 贴合触发按钮摆放。 */
/* ---- v24 第 11 批 #2：名册卡片的动作**居中模态框** ----
   （第 10 批是"贴按钮的浮层"，用户反馈分组"点第二次失效"且要求"像选观影人一样弹框"，
     于是改成遮罩 + 居中面板，与 _person_picker 同一形态。） */
.rm-modal { position: fixed; inset: 0; z-index: 60; display: flex;
  align-items: center; justify-content: center; }
.rm-modal[hidden] { display: none; }
.rm-mask { position: absolute; inset: 0; background: rgba(47, 36, 30, .34); }
.rm-panel {
  position: relative; z-index: 1;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 14px; box-shadow: var(--shadow-l);
  width: min(360px, 92vw); max-height: min(78vh, 620px);
  display: flex; flex-direction: column;
}
.rm-panel[hidden] { display: none; }
.rm-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px 10px; border-bottom: 1px solid var(--line-2);
}
.rm-head b { font-size: 14.5px; font-weight: 600; }
.rm-x {
  margin-left: auto; border: 0; background: transparent; cursor: pointer;
  font-size: 15px; line-height: 1; color: var(--muted); padding: 4px 6px; border-radius: 6px;
}
.rm-x:hover { background: var(--line-2); color: var(--ink); }
.rm-body { padding: 8px 6px; overflow-y: auto; }
.rm-foot {
  display: flex; gap: 8px; justify-content: flex-end;
  padding: 10px 14px; border-top: 1px solid var(--line-2);
}
.rm-warn {
  margin: 0; padding: 10px 12px; font-size: 13px; line-height: 1.7;
  background: #FDF2F0; border: 1px solid #F0D6D1; border-radius: 8px; color: #7A2A20;
}
.rm-modal .pg-menu { display: contents; }
.rm-modal .pd-form { display: contents; }
.rm-modal .person-move { display: block; padding: 6px 14px 10px; }
.rm-modal .person-move.is-kin { padding: 6px 14px 10px; }

/* ---- v24 第 11 批 #2：详情页底部「删除」= 大按钮（与「开通账号」同规格，红色底）---- */
.del-big { display: flex; flex-direction: column; gap: 12px; max-width: 420px; }
.del-big-tip { margin: 0; font-size: 13px; line-height: 1.75; color: var(--muted); }
.btn-block-lg {
  display: flex; align-items: center; justify-content: center;
  width: 100%; padding: 12px 16px; font-size: 14.5px;
}
.del-big-confirm {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 10px 12px; background: #FDF2F0;
  border: 1px solid #F0D6D1; border-radius: 8px; font-size: 13px; color: #7A2A20;
}
.del-big-confirm[hidden] { display: none; }

/* ---- v24 第 12 批 #1：删除按钮紧挨「开通账号」放在同一列 ----
   用户原话：「删除按钮，就放在开通账号按钮下面，紧挨着，长度大小，
              和开通账号一样，就是颜色改成红色。」
   所以这里只做两件事：① 与账号区的按钮**左对齐同宽**（都以账号卡片内容宽为准）；
   ② 上方留一条细分隔线，让人一眼看出"这是同一个区块里的第二件事"。
   ⚠️ 尺寸一致性靠 .btn-block-lg（100% 宽 + 同 padding），不在这里写死宽度，
      否则窄屏下会与「开通账号」对不齐。 */
.del-inline {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px dashed var(--line);
}
.del-inline-tip {
  margin: 0 0 10px;
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--muted);
}
.person-move { display: flex; gap: 6px; padding: 8px 12px; flex-wrap: wrap; }
.person-move .select { flex: 1 1 auto; min-width: 0; padding: 6px 8px; font-size: 13px; }
.person-move .btn { flex: 0 0 auto; }

/* ---- 「分组」按钮（用户 2026-10-04 第 1 条）----
   按钮上**直接写着当前分组**，点开就是一张分组列表。
   没分过组时写「分组」两个字并压暗（is-none），跟"已经归了组"区分开。 */
.person-group-btn {
  white-space: nowrap; max-width: 130px; overflow: hidden; text-overflow: ellipsis;
  list-style: none;
}
.person-group-btn::-webkit-details-marker { display: none; }
.person-group-btn.is-none { color: var(--faint); }
.person-group[open] {
  position: absolute; right: 8px; top: 8px; z-index: 31;
}
.pg-menu {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 10px; box-shadow: var(--shadow-l);
  min-width: 200px; max-height: 60vh; overflow: auto;
  padding: 8px 0; margin-top: 2px;
}
.pg-title {
  margin: 0 0 6px; padding: 0 12px; font-size: 12px; color: var(--faint);
}
.pg-item {
  display: flex; align-items: center; gap: 8px; cursor: pointer;
  padding: 7px 12px; font-size: 13.5px; color: var(--ink);
}
.pg-item:hover { background: var(--line-2); }
.pg-item input { flex: 0 0 auto; }
.pg-menu .pg-save { margin: 6px 12px 0; }

/* ---------------- 名册：拖动排序 / 拖到别栏改分组 ----------------
   ⚠️ 2026-10-04 起这一段是 **Pointer Events**（不再用 HTML5 拖放）——
      原因写在 app.js 那段注释里（Chrome 下"只有第一组能拖"就是 HTML5 DnD 的锅）。
      没有它，行上的「分组」按钮照样能改分组，功能不缺。 */
.roster.is-droppable .person { cursor: grab; }
/* ⚠️ 拖动中被拖的那张卡必须 pointer-events:none：落点是用 elementFromPoint
      现算的，卡自己压在指针底下就只能算到自己，永远插不进别人中间。 */
.roster.is-droppable .person.is-dragged { opacity: .55; cursor: grabbing; pointer-events: none; }
.drag-handle {
  position: absolute; left: 3px; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 22px; height: 22px; line-height: 22px; text-align: center;
  border-radius: 6px; font-size: 14px;
  color: var(--muted); background: rgba(255, 255, 255, .82);
  border: 1px solid var(--line-2);
  /* 🔴 v24 第 25 批 用户第 5 条：手柄是**拖动名册的唯一入口**。
     用户原话：「点击列表中的人，是直接查看这个人的信息，拖动的时候才是调整数序。」
     → 整行不再参与拖动，**只有按在这个 ⠿ 上才算**（鼠标与触摸同一判据）。
     所以它必须一眼看得出来"这里是能抓的"：
       · `cursor: grab`（抓着时 JS 加 .is-dragged 的卡片会变 grabbing，见下）
       · `touch-action: none` —— 不写它，手指在柄上一划会被浏览器当成滚页面，永远拖不起来
       · 悬停加一点底色，别让它看着像一个装饰性的字符 */
  touch-action: none; cursor: grab;
}
.drag-handle:hover { background: var(--brand-soft, rgba(168,74,40,.10)); color: var(--brand); }
.drag-handle-inline { color: var(--muted); }
/* 列表里每行要留出左侧拖柄的位置（否则⠿会压在头像上） */
.roster.is-droppable .person { padding-left: 18px; }
/* 正被拖的那张卡：手柄变抓握态（与 cursor:grab 配成一对） */
.person.is-dragged .drag-handle { cursor: grabbing; }

/* 空栏平时收起来（七个预置分组可能只用到三个，摊开会显得名册破破烂烂）——
   但**拖动时必须露出来**，否则"想拖到一个现在没人的分组里"就没地方放。 */
.group-block.is-empty { display: none; }
.roster.is-dragging .group-block.is-empty {
  display: block; opacity: .9;
  border: 1px dashed var(--line);
  border-radius: var(--radius); padding: 10px; margin-top: 18px;
  background: rgba(250, 247, 241, .6);
}
.group-drop-hint { display: none; margin-left: auto; font-weight: 400; font-size: 12px; color: var(--brand); }
.roster.is-dragging .group-block .group-drop-hint { display: inline; }
/* 拖动时把"有多少人"让出位置给落点提示，两者都挤在右上角会很乱 */
.roster.is-dragging .group-block .group-n { display: none; }
.roster.is-dragging .people-grid { min-height: 24px; }

/* 窄屏：一行上「分组 + 编辑 + ⋯」三个按钮，分组名会吃掉姓名的空间 ——
   把分组名的上限压一压，先保住姓名能读（分组点开照样看得到全名）。 */
@media (max-width: 640px) {
  .person-group-btn { max-width: 76px; }
  .person-actions { gap: 4px; padding-right: 6px; }
  .person-edit { padding: 3px 7px; }
}

/* 拖完/没拖成的轻提示（2 秒自己消失，不打断操作） */
.roster-note {
  position: fixed; left: 50%; bottom: 28px; transform: translate(-50%, 12px);
  z-index: 200; padding: 8px 16px; border-radius: 999px;
  background: rgba(44, 35, 29, .88); color: #fff; font-size: 13px;
  opacity: 0; pointer-events: none; transition: opacity .18s ease, transform .18s ease;
}
.roster-note.is-show { opacity: 1; transform: translate(-50%, 0); }
.roster-note.is-err { background: rgba(178, 45, 45, .92); }

/* ---------------- 分享名片（成员详情页） ---------------- */
.share-wrap { display: inline-block; }
.share-wrap > summary { list-style: none; cursor: pointer; display: inline-block; }
.share-wrap > summary::-webkit-details-marker { display: none; }
.share-wrap .inline-form { margin-top: 10px; }
/* 分享改用弹窗选人组件后，里面的 chips + 按钮要能在这个窄条里排开；
   组件默认按"整页宽"设计（.pp-chips 自带 flex-wrap），这里给它一个合理上限，
   免得点开"分享他的名片给…"时弹窗按钮被挤成半行。 */
.share-wrap .share-to { flex: 1 1 220px; min-width: 0; }

/* ---------------- 「从家里已有的人里挑」 ---------------- */
.pick-list { list-style: none; margin: 0 0 4px; padding: 0; display: grid; gap: 8px; }
.pick-row { margin: 0; }
.pick-item {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  padding: 8px 12px; border-radius: var(--radius-s);
  background: #FAF7F1; border: 1px solid var(--line-2);
}
.pick-item:hover { border-color: var(--brand); }
.pick-item input[type="checkbox"] { width: 16px; height: 16px; flex: 0 0 auto; accent-color: var(--brand); }
.pick-ph { width: 34px; height: 34px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; background: #F1E7D6; }
.pick-name { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* ---------------- 个人设置：分组顺序 ---------------- */
.order-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.order-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 12px; border-radius: var(--radius-s);
  background: #FAF7F1; border: 1px solid var(--line-2);
}
.order-no {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
  background: var(--brand); color: #fff;
  font-size: 12px; line-height: 22px; text-align: center;
}
.order-name { flex: 1 1 auto; min-width: 0; font-size: 14px; }
.order-count { flex: 0 0 auto; font-size: 12px; color: var(--faint); }
.order-move { flex: 0 0 auto; display: flex; gap: 4px; }
.order-move .btn { padding: 2px 8px; line-height: 1.4; }
.order-move .btn[disabled] { opacity: .35; cursor: default; }

/* ---- 🔴 v63（用户 2026-10-19 第 5 条）：公共标签的颜色 ----
   一行 = 序号 · 色块 · 名字 · N人 · ▲▼ · 编辑。 */
.pg-dot {
  flex: 0 0 auto; width: 12px; height: 12px; border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.12);   /* 浅色底上也要看得见边界 */
}
/* 色板（弹框里）：点一下就选。⚠️ 选中态用 `is-on` 描一圈，
   不与 `.is-on` 的通用含义冲突（本页没有别的 .is-on）。 */
.pg-swatches { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 6px; }
.pg-sw {
  width: 26px; height: 26px; border-radius: 7px; cursor: pointer;
  border: 2px solid transparent; padding: 0;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.12);
}
.pg-sw.is-on { border-color: var(--ink); }
.pg-sw:hover { transform: scale(1.06); }
/* 自定义取色器：与色块同尺寸，外观上就是色板里的第 13 格 */
.pg-sw-custom {
  width: 26px; height: 26px; border-radius: 7px; overflow: hidden; cursor: pointer;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.12);
  display: inline-flex;
}
.pg-sw-custom input[type="color"] {
  width: 40px; height: 40px; margin: -7px; border: 0; padding: 0; cursor: pointer; background: none;
}
/* 新建时那个颜色下拉（窄屏缩一半，别把"加一个"按钮挤到下一行） */
.pg-swatch-pick { flex: 0 0 auto; max-width: 96px; }
/* 弹框底栏：删除在左、取消/完成在右 —— 与"危险动作离确认按钮远一点"的通行做法一致 */
.rm-foot-gap { flex: 1 1 auto; }

/* ---------------- 个人设置：关系分类管理 ---------------- */
.inline-form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.inline-form .input { flex: 1 1 200px; max-width: 320px; }
.group-manage { list-style: none; margin: 16px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.group-manage-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 12px; border-radius: var(--radius-s);
  background: #FAF7F1; border: 1px solid var(--line-2);
}
.group-rename { display: flex; gap: 8px; align-items: center; flex: 1 1 240px; min-width: 0; }
.group-rename .input { flex: 1 1 auto; min-width: 0; max-width: 220px; padding: 6px 10px; font-size: 13px; }
.group-manage-row .group-count { flex: 0 0 auto; }
.del-wrap > summary { list-style: none; cursor: pointer; }
.del-wrap[open] { flex: 1 1 100%; }
.del-confirm { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 8px 0 2px; }
/* ---- 页头动作区：统一尺寸 + 除设置外都要「有颜色」 ----
   ⚠️ 本节刻意放在**文件靠后**：.btn / .btn-ghost / .btn-danger / .btn-icon
      的定义都在前面，CSS 同特异性时「后写的赢」——放前面会被它们覆盖
      （实测过：放前面时 .btn-icon 白底被 .btn 的品牌底盖掉、.btn-ghost 也还是白的）。
   用户明确提过三次：
     ·「设置按钮和记一场电影按钮高度要一致」
     ·「返回等按钮和记一场电影等按钮的大小高度的统一问题」
     ·「设置按钮…只保留图标，图标换成三条横线；除设置，其他按钮都要有颜色」 */
.page-head .actions > .btn,
.page-head .actions > .movie-set > summary.btn,
.page-head .actions > form > .btn,
.page-head .actions > form > button.btn {
  padding: var(--act-pad-y) var(--act-pad-x); font-size: var(--act-font);
}
/* 层 1：主按钮（裸 .btn，无修饰类）—— 品牌实心。
   ⚠️ `.btn` 同时出现在 `<a class="btn">` 和 `<form><button class="btn">`，
      两条路径的选择器都要写出来，且**特异性必须相等**，否则后面层 2/3
      按类名匹配的规则压不住它（下层靠"同特异性 + 后写"取胜）。 */
.page-head .actions > .btn:not(.btn-icon),
.page-head .actions > form > .btn:not(.btn-icon),
.page-head .actions > form > button.btn:not(.btn-icon) {
  background: var(--brand); color: #fff; border-color: transparent;
}
.page-head .actions > .btn:not(.btn-icon):hover,
/* 底色交给文末统一段（同色系加深） */
.page-head .actions > form > button.btn:not(.btn-icon):hover { }

/* 层 2：次要按钮（.btn-ghost）—— 品牌描边 + 极浅品牌底，比主按钮轻一档。
   a / button 两种形态各写一条、特异性对齐（见层 1 的警告）。 */
.page-head .actions > .btn-ghost:not(.btn-icon),
.page-head .actions > a.btn-ghost:not(.btn-icon),
.page-head .actions > form > .btn-ghost:not(.btn-icon),
.page-head .actions > form > button.btn-ghost:not(.btn-icon) {
  background: #FDF7F2; color: var(--brand); border-color: rgba(168, 74, 40, .45);
}
.page-head .actions > .btn-ghost:not(.btn-icon):hover,
.page-head .actions > a.btn-ghost:not(.btn-icon):hover,
.page-head .actions > form > .btn-ghost:not(.btn-icon):hover,
.page-head .actions > form > button.btn-ghost:not(.btn-icon):hover {
  background: var(--hov-veil, rgba(44,35,29,.05)); border-color: var(--brand);
}

/* 层 3：危险按钮（.btn-danger）—— 淡红底 + 红字，同样要有颜色、但不能看着像主按钮。
   ⚠️ 实测踩过：只写 `.actions > form > .btn-danger`（0,4,0）会被层 1 的
      `.actions > form > button.btn`（0,4,1）压掉 —— 详情页的「删除」按钮
      在浏览器里显示成品牌砖红实心，跟「编辑」几乎分不出来。补上 `button.`
      形态后特异性升到 (0,4,2)，才真正生效。 */
.page-head .actions > .btn-danger:not(.btn-icon),
.page-head .actions > a.btn-danger:not(.btn-icon),
.page-head .actions > form > .btn-danger:not(.btn-icon),
.page-head .actions > form > button.btn-danger:not(.btn-icon) {
  background: #FDF1EE; color: var(--red); border-color: rgba(192, 83, 63, .45);
}
.page-head .actions > .btn-danger:not(.btn-icon):hover,
.page-head .actions > a.btn-danger:not(.btn-icon):hover,
.page-head .actions > form > .btn-danger:not(.btn-icon):hover,
.page-head .actions > form > button.btn-danger:not(.btn-icon):hover { background: rgba(192, 83, 63, .09); }

/* 层 4：**实心**底色按钮（v40 用户要求）。
   原话（2026-10-10）：「删除按钮就用红色背景颜色…影院管理部分…编辑使用蓝色的背景，
   删除使用红色的背景。」
   ⇒ 这两个要的是**实心色块**（一眼分得清），而不是层 3 那种淡色描边。
   ⚠️ 为什么不直接改 `.btn-danger`：全站十来处「删除」都在用它，改成实心会到处变样；
      而且**页头动作区的层 1**（`.actions > form > button.btn`，特异性 0,4,1）
      会盖过基类 `.btn-del`（0,1,0）—— 必须在同一层里再写一条才生效（与层 3 同一个坑）。 */
.page-head .actions > .btn-del:not(.btn-icon),
.page-head .actions > a.btn-del:not(.btn-icon),
.page-head .actions > form > .btn-del:not(.btn-icon),
.page-head .actions > form > button.btn-del:not(.btn-icon) {
  background: var(--red); color: #fff; border-color: transparent;
}
.page-head .actions > .btn-edit:not(.btn-icon),
.page-head .actions > a.btn-edit:not(.btn-icon),
.page-head .actions > form > .btn-edit:not(.btn-icon),
.page-head .actions > form > button.btn-edit:not(.btn-icon) {
  background: var(--blue); color: #fff; border-color: transparent;
}
/* 「设置」图标按钮：**唯一无色的**（白底描边、方形），与上面所有彩色按钮区分。
   padding 归零、做成正方形，但高度必须仍是 --act-h（= 文字按钮的 43.8px）——
   写死过 40px，比旁边按钮矮 3.8px，是用户反复反馈的那个问题。 */
.page-head .actions > .movie-set > summary.btn-icon,
.page-head .actions > .movie-set > summary.btn {
  padding: 0;
  width: var(--act-h); height: var(--act-h); min-width: var(--act-h); line-height: 1;
  background: #fff; color: var(--ink); border: 1px solid var(--line);
}
.page-head .actions > .movie-set > summary.btn-icon:hover,
.page-head .actions > .movie-set > summary.btn:hover {
  border-color: var(--brand); color: var(--brand);
  /* 底色交给文末统一段（中性薄雾），别在这里另糊一个 #FAF7F1 */
}
.movie-set[open] > summary.btn-icon,
.movie-set[open] > summary.btn { border-color: var(--brand); color: var(--brand); }
.btn.danger { color: var(--red); border-color: rgba(192, 83, 63, .35); }
.btn.danger:hover { }   /* 统一段处理 */

/* ---------------- 图片裁剪浮层 ---------------- */
/* 覆盖层默认不占空间；打开时铺满整屏，把底下的页面锁住（裁剪时不该能点到别处） */
/* ---- 图片裁剪（v24 第 11 批 #5：改用 Cropper.js）----
   用户的四条硬要求（原话见 assets/js/app.js 那段注释）在这里逐条落地：
     ① 弹框**不要有任何滚动条** → 面板 overflow:hidden；画布尺寸固定，由 Cropper 缩放图片去适应。
     ② 图片无论多大都要缩放到框内 → 交给 Cropper 的 viewMode:1（container 自适应）。
     ③ **不要缩放条** → 原来那根 .crop-zoom-row 整块删掉了，改滚轮缩放。
     ④ 裁剪框要比弹框小、**所有触点都露出来** → 画布四周留出 22px 内边距，
        Cropper 的四个角手柄画成实心白点，永远落在面板可见区内。 */
.crop-overlay { display: none; }
.crop-overlay.is-open {
  /* 🔴🔴 v43（用户 2026-10-11 第 3 条）：「新建行程的时候，如果把地图拖进去，
     它不是开始裁剪吗？但是这个弹出的框把裁剪那个框又盖住了，裁剪的框应该放到前面来。」
     真根因：裁剪层是 **100**，而行程/观影那些弹框用的 `.cal-modal` 是 **200**
     ⇒ 从弹框里拖图触发的裁剪层被整块盖在下面 —— 看不见、也点不到。
     （与 v41 那个「新建影院弹框被记一场电影盖住」是**同一个坑**，当时把 .cn-modal
       提到了 260；这里漏了。）
     ⇒ 提到 **260**：高于所有自建弹框（.cal-modal 200 / .pp-modal 等），
       与 .cn-modal 同层；低于灯箱(300)、预览层(320) —— 那两者是"看大图"的顶级层。 */
  display: flex; position: fixed; inset: 0; z-index: 260;
  align-items: center; justify-content: center;
  padding: 20px; background: rgba(44, 35, 29, .55);
}
.crop-panel {
  background: var(--surface); border-radius: 14px;
  box-shadow: var(--shadow-l); padding: 16px;
  max-width: calc(100vw - 24px); max-height: calc(100vh - 24px);
  /* ① 不许出滚动条：面板自己 overflow:hidden，画布尺寸按视口算死在下面 */
  overflow: hidden;
  display: flex; flex-direction: column;
}
.crop-head {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 15px; font-weight: 600; margin-bottom: 12px; flex: 0 0 auto;
}
.crop-head .crop-shape { font-size: 12px; font-weight: 400; color: var(--muted); }
.crop-x {
  margin-left: auto; align-self: center; border: 0; background: transparent;
  font-size: 15px; line-height: 1; color: var(--muted);
  cursor: pointer; padding: 4px 6px; border-radius: 6px;
}
.crop-x:hover { background: var(--line-2); color: var(--ink); }
/* 画布：**固定尺寸**（不是 max-height），这样 Cropper 一定会把图片缩进来，
   而不会让容器去滚动。宽高都换算成"视口内还能放下的最大方块"。
   ⚠️ 留 22px 内边距是给裁剪框四个角的手柄留位（要求④）。 */
.crop-canvas {
  flex: 0 0 auto; position: relative;
  width: min(560px, calc(100vw - 88px));
  height: min(66vh, 460px);
  border-radius: var(--radius-s); overflow: hidden;
  background: #2C231D;
}
.crop-canvas img { display: block; max-width: 100%; }
.crop-tip { color: var(--muted); font-size: 12px; margin: 12px 0 0; flex: 0 0 auto; }
.crop-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 14px; flex: 0 0 auto; }

/* ④ 触点全露出：Cropper 默认的手柄太细、颜色偏灰，在深色画布上不好抓。
   这里加粗、改成白底品牌边，并**保证它们不被画布裁掉**（Cropper 自己会留 margin）。 */
.cropper-view-box,
.cropper-face { border-radius: 2px; }
.cropper-line { background-color: rgba(255, 255, 255, .9); }
.cropper-point {
  width: 12px; height: 12px; opacity: 1;
  background-color: #fff; border: 2px solid var(--brand);
  border-radius: 50%;
}
.cropper-point.point-se { width: 14px; height: 14px; }
.cropper-bg { background-image: none; background-color: #2C231D; }
.cropper-modal { background-color: rgba(20, 14, 10, .55); }


@media (max-width: 720px) {
  .order-move .btn { padding: 4px 10px; }
  .group-manage-row { align-items: flex-start; }
  .crop-panel { padding: 12px; }
}

/* ===== 补充联系方式（可多条）：标注 + 内容 一行两组，窄屏摞起来 ===== */
.ct-row { display: flex; gap: 8px; margin-bottom: 8px; }
.ct-row .ct-label { flex: 0 0 140px; }
.ct-row .input { flex: 1; }
@media (max-width: 720px) {
  .ct-row { flex-direction: column; gap: 6px; }
  .ct-row .ct-label { flex: none; }
}

/* ===== 观影 v9：瀑布流海报墙 + MovieLog 式详情/评价/影院 ===== */

/* ---- 搜索栏 ---- */
.search-bar {
  display: flex; gap: 10px; align-items: center;
  background: var(--surface); padding: 8px 16px;
  border-radius: 12px; box-shadow: var(--shadow);
  border: 1px solid var(--line); max-width: 500px; margin-bottom: 20px;
}
.search-bar input[type="text"] {
  flex: 1; border: none; outline: none; padding: 8px 10px;
  font-size: 15px; background: transparent; color: var(--ink);
}
.search-bar button {
  background: var(--brand); color: #fff; border: none;
  padding: 8px 20px; border-radius: 8px; font-weight: 600;
  cursor: pointer; white-space: nowrap;
}
.search-bar button:hover { }   /* 统一段处理（同色系加深，不换成 brand-2） */
.search-clear { color: var(--muted); font-size: 14px; padding: 8px 4px; white-space: nowrap; }

/* ---- 瀑布流海报墙 ---- */
.waterfall {
  display: grid; gap: 20px; padding: 4px 0;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
.movie-card {
  background: var(--surface); border-radius: var(--radius);
  overflow: hidden; box-shadow: var(--shadow); cursor: pointer;
  border: 1px solid var(--line); display: flex; flex-direction: column;
  text-decoration: none; color: var(--ink);
}
.movie-card:hover {
  transform: translateY(-6px); box-shadow: var(--shadow-l);
  border-color: var(--brand-2); text-decoration: none;
}
.card-image {
  width: 100%; aspect-ratio: 2 / 3; background: var(--line-2);
  overflow: hidden; display: flex; align-items: center; justify-content: center;
  position: relative; flex-shrink: 0;
}
.card-image img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .4s ease; }
.movie-card:hover .card-image img { transform: scale(1.03); }
.placeholder-poster {
  width: 100%; height: 100%; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 6px;
  background: linear-gradient(135deg, #4a3a30, #2C231D);
  color: rgba(255, 255, 255, .55); font-size: 42px; font-weight: 600;
}
.placeholder-poster .placeholder-title {
  font-size: 14px; text-align: center; padding: 0 12px;
  color: rgba(255, 255, 255, .75); font-weight: 500;
}
.card-body { padding: 14px 16px 16px; flex: 1; display: flex; flex-direction: column; gap: 6px; }
.card-title {
  font-size: 16px; font-weight: 600; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.card-meta {
  display: flex; flex-wrap: wrap; gap: 6px 12px;
  font-size: 13px; color: var(--muted);
}
.card-meta span { display: inline-flex; align-items: center; gap: 3px; }
.meta-rating { color: var(--brand); font-weight: 600; }
.meta-rating-max { color: var(--muted); font-weight: 400; }
.card-viewers { display: flex; flex-wrap: wrap; gap: 4px 8px; margin-top: auto; }
.viewer-tag {
  display: inline-flex; align-items: center; gap: 4px; font-size: 12px;
  padding: 2px 8px; border-radius: 12px; background: var(--line-2);
  color: var(--ink); text-decoration: none;
  /* 底色/字色/边线由 person_chip() 写成行内样式（每人一个颜色，来自他的头像主色）；
     这里只给**没有行内样式时的兜底**（旧的灰底标签）：1px 透明边，
     这样有边线和没边线的两种标签高度完全一致，不会一行高一行矮。 */
  border: 1px solid transparent;
}
.viewer-tag:hover { text-decoration: none; filter: brightness(.96); }
.viewer-tag .chip-av { width: 18px; height: 18px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.viewer-tag .chip-av-text {
  background: var(--faint); color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; line-height: 1;
}
.viewer-tag .chip-name { white-space: nowrap; }

/* ---- 详情页 ---- */
.detail-hero {
  display: grid; grid-template-columns: 300px 1fr; gap: 32px;
  background: var(--surface); border-radius: var(--radius);
  padding: 28px; box-shadow: var(--shadow); margin-bottom: 20px;
  border: 1px solid var(--line);
}
.detail-poster {
  border-radius: 12px; overflow: hidden; background: var(--line-2);
  aspect-ratio: 2 / 3; display: flex; align-items: center; justify-content: center;
}
.detail-poster img { width: 100%; height: 100%; object-fit: cover; }
.detail-poster .placeholder-poster { font-size: 48px; }
.detail-poster .placeholder-poster .placeholder-title { font-size: 16px; }
.detail-info { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.detail-info h1 { font-size: 26px; font-weight: 700; margin: 0 0 4px; color: var(--ink); }
.detail-info .meta-row { display: flex; flex-wrap: wrap; gap: 8px 16px; font-size: 14px; color: var(--muted); }
.detail-info .meta-row span { display: inline-flex; align-items: center; gap: 4px; }
.info-text { color: var(--ink); line-height: 1.7; font-size: 15px; }
.remark-text {
  background: var(--line-2); padding: 12px 16px; border-radius: 8px;
  color: var(--ink); border-left: 4px solid var(--brand); font-size: 14px;
}
.detail-section {
  background: var(--surface); border-radius: var(--radius);
  padding: 24px 28px; box-shadow: var(--shadow); margin-bottom: 20px;
  border: 1px solid var(--line);
}
.detail-section h3 {
  font-size: 17px; font-weight: 600; margin: 0 0 16px; color: var(--ink);
  display: flex; align-items: center; gap: 10px;
}
.detail-section h3 .badge {
  font-size: 13px; font-weight: 400; background: var(--line-2);
  padding: 2px 12px; border-radius: 20px; color: var(--muted);
}
.empty-hint { color: var(--muted); font-size: 14px; margin: 0; }
/* 🔴 工单 #20：首页「近期任务」的空态提示是**一直渲染、靠 hidden 切换**的
   （服务端有任务时给它 `hidden`，前端把最后一条删掉后再显示出来）。
   ⚠️ 浏览器默认的 `[hidden]{display:none}` 优先级最低 —— 只要哪天有人给
      `.empty-hint` 加上 `display:flex` 之类，这句提示就会在"有任务时"也冒出来。
      所以在这里显式再声明一次（与上面第 601 行那条注释是同一个约定）。 */
.empty-hint[hidden] { display: none !important; }
.viewers-list { display: flex; flex-wrap: wrap; gap: 12px; }
.viewers-list .viewer-tag { padding: 4px 12px 4px 6px; font-size: 14px; }
.viewers-list .viewer-tag .chip-av { width: 28px; height: 28px; }
.viewers-list .viewer-tag .chip-av-text { font-size: 13px; }
.cinema-info {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px 24px; font-size: 15px;
}
.cinema-info .label { color: var(--muted); }
.cinema-info .value { color: var(--ink); font-weight: 500; }

/* ---- 影像记录 ---- */
.gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 16px; }
.gallery-item {
  border-radius: 10px; overflow: hidden; background: var(--line-2);
  aspect-ratio: 4 / 3; display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: transform .2s; position: relative;
  border: 1px solid var(--line);
}
.gallery-item:hover { transform: scale(1.02); }
.gallery-item img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gallery-item .label-badge {
  position: absolute; bottom: 8px; left: 8px;
  background: rgba(0, 0, 0, .7); color: #fff; font-size: 11px;
  padding: 2px 12px; border-radius: 12px;
}

/* ---- 图片灯箱 ---- */
.lb-openable { cursor: zoom-in; }
.lb-openable:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.lightbox {
  position: fixed; inset: 0; z-index: 300; display: none;
  align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .88); overscroll-behavior: contain;
}
.lightbox.open { display: flex; }
.lb-stage { display: flex; align-items: center; justify-content: center; max-width: 92vw; max-height: 82vh; }
.lb-img {
  max-width: 92vw; max-height: 78vh; width: auto; height: auto;
  object-fit: contain; border-radius: 8px; box-shadow: 0 20px 60px rgba(0, 0, 0, .5);
  user-select: none; -webkit-user-drag: none;
}
.lb-btn {
  position: absolute; border: none; cursor: pointer; color: #fff;
  background: rgba(255, 255, 255, .12); display: flex;
  align-items: center; justify-content: center;
  transition: background-color .2s ease, filter .2s ease;
  font-family: inherit;
}
.lb-btn:hover { }   /* 深底上不能用薄雾：走文末统一段的 brightness 提亮 */
.lb-btn[hidden] { display: none; }
.lb-close { top: 14px; right: 16px; width: 40px; height: 40px; border-radius: 50%; font-size: 18px; }
/* 下载按钮在关闭按钮左边，同高、同一套视觉（v24 第 3 条：预览层右上角要有下载） */
.lb-dl {
  top: 14px; right: 66px; height: 40px; padding: 0 16px; border-radius: 20px;
  display: inline-flex; align-items: center; font-size: 13px; font-weight: 600;
  background: #fff; color: #1a1a1a; text-decoration: none;
}
.lb-dl:hover { background: #eee; }
.lb-nav { top: 50%; transform: translateY(-50%); width: 48px; height: 48px; border-radius: 50%; font-size: 28px; padding-bottom: 4px; }
/* 🔴🔴 v41（用户 2026-10-10）：「观影照片的左右按钮，鼠标悬停之后位置会变一下，
   一不小心就会点错。」
   真根因：文末「全站悬停统一」段给 `.lb-btn:hover` 加了
   `transform: translateY(var(--hov-lift))`（= 上移 1px 的"轻抬升"手感）——
   而 `.lb-nav` 是靠 `transform: translateY(-50%)` 做**垂直居中**的，
   hover 那条**把居中的 transform 整个覆盖掉** ⇒ 鼠标一碰，按钮从"居中"跳到
   "距顶 50%"，**位移接近半个按钮高度**（48px 的一半），确实会点错。
   ⚠️ 与 v21 家谱那次 `.f3 div.card:hover > div` 覆盖库的居中 transform 是**同一个坑**。
   ⇒ 修：`.lb-nav:hover` 显式把它自己的居中 transform 写回去（只保留提亮，
     不再抬升）。位置在 hover 前后**一字不动**，鼠标停在哪就点在哪。
   ⚠️ 必须写在统一 hover 段**之后**（同特异性下后者胜）—— 见文末那段里的同名规则。 */
.lb-prev { left: 14px; }
.lb-next { right: 14px; }
.lb-bar {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 14px 20px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  color: #fff; font-size: 14px;
  background: linear-gradient(to top, rgba(0, 0, 0, .6), rgba(0, 0, 0, 0));
}
.lb-caption { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-counter { flex: 0 0 auto; opacity: .75; font-variant-numeric: tabular-nums; }

/* ---- 评价与评论 ---- */
.rv-composer > summary { list-style: none; cursor: pointer; }
.rv-composer > summary::-webkit-details-marker { display: none; }
.rv-open-btn {
  display: inline-block; padding: 8px 18px; border-radius: 10px;
  background: linear-gradient(135deg, var(--brand), var(--brand-2));
  color: #fff; font-size: 14px; font-weight: 600; user-select: none;
}
.rv-open-btn:hover { text-decoration: none; box-shadow: var(--shadow-l); }
.rv-composer[open] > .rv-open-btn { background: var(--line-2); color: var(--muted); }
.rv-act-btn {
  display: inline-block; padding: 4px 12px; border: 1px solid var(--line);
  border-radius: 20px; background: var(--surface); color: var(--muted);
  font-size: 12.5px; user-select: none; cursor: pointer;
}
.rv-act-btn:hover { border-color: var(--brand); color: var(--brand); text-decoration: none; }
.rv-composer[open] > .rv-act-btn { border-color: var(--brand); background: rgba(168, 74, 40, .08); color: var(--brand); font-weight: 600; }
.rv-form { background: var(--line-2); border-radius: 12px; padding: 16px 18px; margin-top: 10px; }
.rv-composer > .rv-form { margin-bottom: 0; }
.rv-form-inline { padding: 12px 14px; }
.rv-rating-row { display: flex; align-items: center; gap: 8px 14px; flex-wrap: wrap; margin-bottom: 12px; }
.rv-rating-label { font-size: 14px; font-weight: 600; color: var(--ink); }
/* ---- 10 星评分（v10）：空心 ☆ 灰、点亮 ★ 金黄。
      DOM 按 10..1 倒序渲染 + row-reverse，视觉即 1..10 从左到右；
      「点亮的星星」由 JS 加 .lit（含悬停预览），.no-js 兜底用 :has() 只亮已选中那一档。 ---- */
.rv-scale { display: flex; gap: 2px; }
.rv-stars { flex-direction: row-reverse; justify-content: flex-end; }
.rv-star { position: relative; cursor: pointer; line-height: 1; }
.rv-star input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.rv-star .st-glyph { font-size: 28px; line-height: 1; }
.rv-star .st-off { color: #c9cdd4; }
.rv-star .st-on { display: none; color: #f5b301; }
.rv-star.lit .st-on { display: inline; }
.rv-star.lit .st-off { display: none; }
.rv-star:hover .st-off { color: #aeb4be; }
.rv-star:focus-within { outline: 2px solid var(--brand); outline-offset: 1px; border-radius: 4px; }
/* 无 JS 兜底：至少选中的那一档及其左边（视觉）会亮 */
.rv-stars.no-js .rv-star:has(input:checked) .st-on,
.rv-stars.no-js .rv-star:has(input:checked) ~ .rv-star .st-on { display: inline; }
.rv-stars.no-js .rv-star:has(input:checked) .st-off,
.rv-stars.no-js .rv-star:has(input:checked) ~ .rv-star .st-off { display: none; }
/* 评价列表里的静态星级展示 */
.stars-disp { display: inline-flex; gap: 1px; font-size: 17px; line-height: 1; vertical-align: -2px; }
.stars-disp .d-on { color: #f5b301; }
.stars-disp .d-off { color: #d3d7de; }
/* ---- 财务 / 账本（v24 第 15 条） ---- */
/* 金额列右对齐 + 等宽数字（表格里数字不齐会很难扫） */
.table .num, .table th.num { text-align: right; font-variant-numeric: tabular-nums; }
.table tfoot th { border-top: 2px solid var(--line); border-bottom: 0; color: var(--ink); }
.led-sum-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; }
.led-total { display: flex; flex-wrap: wrap; gap: 18px; margin: 12px 0 4px; }
.led-total-item { display: flex; flex-direction: column; gap: 2px; }
.led-total-lb { font-size: 12px; color: var(--muted); }
.led-total-num {
  font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.led-total-item.led-net .led-total-num { color: var(--brand); }
/* 数字颜色：**收入/资产用红、负债用绿**（与 A 股涨红跌绿同一套约定，不要反） */
.led-asset { color: #B0342C; }
.led-debt  { color: #1F7A4D; }
.led-kinds { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.led-kind-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 999px; font-size: 12px;
  background: var(--line-2); color: var(--ink); white-space: nowrap;
}
.led-kind-chip b { font-variant-numeric: tabular-nums; }
.led-kind-chip.is-debt { background: #E7F2EC; }
.led-shares { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 10px; }
.led-share-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 8px; border: 1px solid var(--line); border-radius: 999px;
}
.led-ops { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px; }
.led-ops .inline-form { flex-wrap: wrap; align-items: center; }
.led-ops .input { min-width: 140px; }
.led-edit summary { cursor: pointer; }
.led-edit .node-form { min-width: min(520px, 86vw); }

/* ---- 财务：变化折线图 + 设置下拉（v24 用户 2026-10-03 第 7、8 条）---- */
.led-charts { margin-bottom: 0; }
.ck-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.ck-card {
  border: 1px solid var(--line-2); border-radius: var(--radius-s);
  background: #FDFBF7; padding: 10px 12px;
  color: var(--ink);            /* SVG 用 currentColor 画基线与日期，靠这里继承 */
}
.ck-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
.ck-head b { font-size: 14px; }
.ck-svg { display: block; margin-top: 2px; }

/* ---- v24 第 11 批 #7：账目行的两个弹框（全页各一份）----
   原来每行内联一套（form + 选人组件），账目一多就是几千个 form
   —— 用户报的"账目页也卡、还是 Bitwarden 冲突"就是这个。
   现在行上只有按钮，表单集中到 #ledPanels。 */
.led-modal {
  position: fixed; inset: 0; z-index: 75;
  display: flex; align-items: center; justify-content: center;
}
.led-modal[hidden] { display: none; }
.led-mask { position: absolute; inset: 0; background: rgba(47, 36, 30, .34); }
.led-panel-lg {
  position: relative; z-index: 1;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 14px; box-shadow: var(--shadow-l);
  width: min(460px, 92vw); max-height: min(86vh, 660px);
  display: flex; flex-direction: column;
}
.led-panel-lg[hidden] { display: none; }
.led-ph {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px 10px; border-bottom: 1px solid var(--line-2);
}
.led-ph b { font-size: 14.5px; }
.led-x {
  margin-left: auto; border: 0; background: transparent; cursor: pointer;
  font-size: 15px; color: var(--muted); padding: 4px 6px; border-radius: 6px;
}
.led-x:hover { background: var(--line-2); color: var(--ink); }
.led-pb { padding: 12px 14px; overflow-y: auto; }
.led-pb .field { margin-bottom: 12px; }
.led-pb .field > label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 5px; }
.led-pf {
  display: flex; gap: 8px; justify-content: flex-end;
  padding: 10px 14px; border-top: 1px solid var(--line-2);
}
/* 弹框里的选人组件别撑破面板 */
.led-pb .pp { max-width: none; }
.led-panel-lg .led-hist { margin-top: 4px; }

/* ---- v24 第 11 批 #7：图例（只显示"哪个颜色代表哪个账目"）----
   用户原话：「图下面的条目不用显示详情，只需要显示每个账目的颜色，
              就是说哪个颜色代表哪个账目，就可以了。」
   所以从"每账目一张小卡"改成一行色块图例。 */
.ck-legend {
  display: flex; flex-wrap: wrap; gap: 6px 16px;
  margin: 10px 2px 0; font-size: 13px; color: var(--ink);
}
.ck-lg-item { display: inline-flex; align-items: center; gap: 6px; }
.ck-lg-item[hidden] { display: none; }
.ck-lg-dot {
  width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12);
}
/* 多线图：比单条小图高一点，才看得出差异 */
.ck-multi { margin-top: 4px; }

/* ---- v24 第 10 批 #3：总金额图在最前（跨整行），下面才是逐条账目 ---- */
.ck-total { margin-bottom: 14px; }
.ck-total .ck-head b { font-size: 15px; }
/* 类别筛选条：图表一多就是一坨，把筛选权交给用户（而不是少画几条骗自己） */
.ck-filter { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 12px; }
.ck-chip {
  padding: 4px 11px; border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface); color: var(--ink); font: inherit; font-size: 13px;
  cursor: pointer;
}
.ck-chip:hover { border-color: var(--brand-2); }
.ck-chip.is-on { background: var(--brand); border-color: var(--brand); color: #fff; }
.ck-chip.is-debt:not(.is-on) { color: #1F7A4D; }

/* ---- v24 第 10 批 #3：每行的「盘点 / 修改」两个按钮 + 就地展开的面板 ---- */
.led-acts { display: flex; gap: 6px; align-items: center; white-space: nowrap; }
.led-panel {
  /* 作为 <tr> 的兄弟插进来（见模板里的 JS）：表格列宽会把表单切碎，格子外才排得开 */
  display: block; background: #FDFBF7;
  border: 1px solid var(--line); border-radius: var(--radius-s);
  padding: 12px 14px; margin: 0 0 8px;
}
.led-panel .field { margin-bottom: 10px; }
.led-panel .field > label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 4px; }
.led-panel .input, .led-panel .select { width: 100%; max-width: 360px; }
.led-panel .btn-row { margin-top: 4px; }
.led-panel[hidden] { display: none; }
/* 「修改」面板里嵌了选人组件，给它一个不夸张的最大宽度 */
.led-panel .pp { max-width: 420px; }
/* 盘点面板与「修改」不再同处一个 details，分隔线也就不需要了 */
.led-recon { padding-bottom: 0; border-bottom: 0; margin-bottom: 0; }
.led-form-h { font-size: 12.5px; color: var(--muted); font-weight: 500; margin: 0 0 8px; }
.led-hist { margin-top: 6px; }
.led-hist summary { cursor: pointer; }
.led-hist-list { list-style: none; margin: 6px 0 0; padding: 0; font-size: 13px; }
.led-hist-list li {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 5px 0; border-top: 1px solid var(--line-2);
}
.led-hist-list li b { font-variant-numeric: tabular-nums; }

/* 账本设置下拉：比观影那个内容多，给个上限 + 内部滚动 */
.led-set .menu-list { min-width: 280px; max-width: min(340px, 94vw); }
.led-set-list { padding: 10px; gap: 0; max-height: 70vh; overflow-y: auto; }
.led-set-block { padding: 8px 2px; }
.led-set-block + .led-set-block { border-top: 1px solid var(--line-2); margin-top: 4px; }
.led-set-shares { list-style: none; margin: 0 0 8px; padding: 0; }
.led-set-shares li {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 4px 0;
}
.led-set-shares li .node-badge { flex: 0 0 auto; }
.led-set-form-in { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.led-set-form-in .input { flex: 1 1 120px; min-width: 0; }
.led-set-form-in .pp { flex: 1 1 100%; }
.led-add { margin-bottom: 18px; }
/* ---- 纪念日（v24 第 13 条） ---- */
.ann-up { list-style: none; margin: 0; padding: 0; }
.ann-up-item {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
  padding: 8px 2px; border-bottom: 1px solid var(--line-2); font-size: 14px;
}
.ann-up-item:last-child { border-bottom: 0; }
.ann-days {
  flex: 0 0 auto; min-width: 62px; font-weight: 600; color: var(--brand);
  font-variant-numeric: tabular-nums;
}
.ann-date { flex: 0 0 auto; color: var(--muted); font-variant-numeric: tabular-nums; }
.ann-title { flex: 1 1 auto; color: var(--ink); min-width: 0; }
/* ---- 纪念日的「关联人」列（v24 第 25 批 用户第 2 条）----
   用户原话：「再加一列，关联人。列表中，人的前面，都加头像。」

   ⚠️ 列里画的是全站统一的 person_chip（`.viewer-tag`，自带头像 + 底色），
      所以这里只需要处理**排布**，不要另造一套人名样式（会与其它模块不一致）。
   ⚠️ 关联多个人时要能换行 —— `.viewer-tag` 是 `inline-flex`，
      不写 flex-wrap 的话 5 个人会把表格撑宽（手机上直接横向溢出）。
   ⚠️ 「改/删」两个按钮要排成一行：`.res-inline` 那个 form 是 inline 的，
      但两个按钮之间得靠 gap 分开，否则会贴在一起、看着像一个双色按钮。 */
.ann-people { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; max-width: 260px; }
.ann-acts { display: flex; gap: 6px; align-items: center; }
.ann-acts .btn { flex: 0 0 auto; }
@media (max-width: 767px) {
  /* 窄屏：关联人那一列不再限宽（表格本来就会横向滚动），
     但两个按钮要保持不换行 —— 换行后「删除」会掉到「修改」下面，
     在表格行里看着像两张卡片。 */
  .ann-people { max-width: none; }
}
.chk-inline { display: flex; align-items: center; gap: 6px; font-size: 14px; }
/* ---- 任务设置（v24 第 9、10 条）：看板/分类的增删改列表 ---- */
.kb-boards { margin-bottom: 12px; }
.set-list { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 0; }
/* 一行 = 一个「改名」表单 + 一个「删除」表单，靠 flex 排成一条 */
.set-row { flex: 1 1 auto; display: flex; align-items: center; gap: 8px; margin: 0; }
.set-row .input { flex: 1 1 auto; min-width: 0; }
.set-tail { flex: 0 0 auto; }
/* 「已完成」归档列表 */
.done-list { list-style: none; margin: 0; padding: 0; }
.done-item {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 9px 2px; border-bottom: 1px solid var(--line-2); font-size: 14px;
}
.done-item:last-child { border-bottom: 0; }
.done-title { color: var(--ink); text-decoration: none; }
.done-title:hover { color: var(--brand); text-decoration: underline; }
/* ---- 附件缩略图墙 + 全局预览（v24 第 3 条） ----
   规则：图片/视频给缩略图，PDF/Word/Excel 给**同尺寸**图标；
        可预览的点开在本页这一层里看，不可预览的点了直接下载。 */
.att-wall {
  display: flex; flex-wrap: wrap; gap: 12px; margin: 8px 0 2px;
}
.att-cell { width: 132px; }
/* ⚠️ 缩略图与文档图标**必须是同一个盒子**：用户要的是"图标跟预览图一样大小"，
   所以尺寸只在这里写一次，图标靠 flex 撑满，不另给一套尺寸。 */
.att-thumb {
  display: flex; align-items: center; justify-content: center;
  width: 132px; height: 96px; border-radius: 8px; overflow: hidden;
  border: 1px solid var(--line); background: var(--line-2); position: relative;
}
.att-thumb:hover { border-color: var(--brand); }
.att-thumb img, .att-thumb video {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.att-thumb video { object-fit: cover; pointer-events: none; }
.att-play {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  color: #fff; font-size: 22px; text-shadow: 0 2px 8px rgba(0, 0, 0, .6); pointer-events: none;
}
.att-doc {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; width: 100%; height: 100%; padding: 6px;
  font-size: 12px; font-weight: 700; letter-spacing: .5px; color: #fff;
  text-align: center; box-sizing: border-box;
}
.att-doc::before {
  content: ""; width: 34px; height: 34px; border-radius: 6px; background: rgba(255, 255, 255, .22);
  flex: 0 0 auto;
}
/* v24 第 19 批 用户第 6 条「如果是文件，则在图标上显示文件名」：
   文件名压着色块显示 —— 文件就是**读名字**的，名字才是最该看清的东西。
   ⚠️ 两行封顶 + 省略号：文件名可以很长（「学位学历证书_博士学位证书_2022-07-08-17-05-53.jpeg」），
      不截就会把图标撑变形、把相邻的格子挤歪。完整名字仍能从悬停 title / 下载看到。 */
.att-doc-name {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; word-break: break-all; line-height: 1.35;
  max-width: 100%; font-size: 11px; font-weight: 600; letter-spacing: 0;
}
.att-doc-pdf { background: #B0342C; }
.att-doc-doc { background: #2C5AA0; }
.att-doc-xls { background: #1F7A4D; }
.att-doc-ppt { background: #B4622A; }
.att-doc-text { background: #5F6672; }
.att-doc-file { background: #5F6672; }
.att-meta {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 6px;
  margin-top: 4px; font-size: 12px; color: var(--muted);
}
/* v24 第 19 批 用户第 6 条：缩略图下面**不再放文件名**（那串是上传时的原始名，
   图片/视频是"看"的，一串哈希既认不出内容也不比缩略图本身多信息）。
   `.att-fname` 的规则保留 —— 万一将来又要在某处显示一行名字，样式不用重写。 */
.att-fname {
  flex: 1 1 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ink);
}
.att-fsize { flex: 0 0 auto; }
/* ---- 拖动上传（v24 第 19 批 用户第 1 条）----
   用户原话：「为所有的图片视频附件上传处，提供拖动上传的功能，就是允许用户，
              通过把文件拖动到那里进行上传。」
   ⚠️ 类名 `.dropzone` 由 app.js **自动给页面上每个 file 控件挂上**，
      并且给「`data-pick-input` 指向该控件的预览图」也挂一份 —— 题图/头像那类
      控件的 input 是 sr-only（看不见），往旁边那张图上拖才是最自然的动作。
      全站十几处上传点一次性都获得这个能力（不改任何模板）。
   ⚠️ 落在哪儿由 `.is-over` 高亮体现；真正的落文件逻辑在 app.js 里
      （DataTransfer → input.files → 手动派发 change）。
   ⚠️ 曾经想过再加一句常驻的「也可以把文件拖到这里」小字，**去掉了**：
      十几个上传点各多一行灰字，页面会明显变吵，而"拖动"本身不是必须被教导的能力。 */
.dropzone { position: relative; }
.dropzone.is-over {
  outline: 2px dashed var(--brand); outline-offset: 3px;
  background: #FDF3EC; border-radius: var(--radius-s);
}
.att-meta form { display: inline; margin: 0; }
/* ---- 全局预览层 ---- */
.pv {
  position: fixed; inset: 0; z-index: 320; display: flex; flex-direction: column;
  background: rgba(0, 0, 0, .9); overscroll-behavior: contain;
}
/* ⚠️ .pv 自己声明了 display:flex，必须补这条，否则 preview.js 里的
   box.hidden = true 藏不住它（与 .pp-item 同一个坑）。 */
.pv[hidden] { display: none; }
.pv-head {
  flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; color: #fff;
}
.pv-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 14px;
}
.pv-meta { flex: 0 0 auto; font-size: 12px; opacity: .7; }
/* 下载按钮恒在右上角（用户第 3 条点名要求） */
.pv-dl {
  margin-left: auto; flex: 0 0 auto; padding: 6px 14px; border-radius: 8px;
  background: #fff; color: #1a1a1a; font-size: 13px; font-weight: 600; text-decoration: none;
}
.pv-dl:hover { background: #eee; }
.pv-close {
  flex: 0 0 auto; border: 0; background: rgba(255, 255, 255, .16); color: #fff;
  width: 34px; height: 34px; border-radius: 50%; font-size: 16px; cursor: pointer;
}
.pv-close:hover { background: rgba(255, 255, 255, .28); }
.pv-stage {
  flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center;
  padding: 0 16px 16px;
}
.pv-img {
  max-width: 92vw; max-height: 100%; width: auto; height: auto;
  object-fit: contain; border-radius: 8px;
}
/* 🔴 2026-11-05 修：视频打开后是黑的（用户：「点开之后是黑的，没办法播放」）。
   原因：`max-height: 100%` 依赖父级的**确定高度**，而 `.pv-stage` 是
        flex + `align-items:center`，百分比高度解析不出具体像素
        ⇒ `<video>` 按 0 高度渲染 ⇒ 只剩 background:#000 一块黑。
   改成 `vh`（相对视口，永远是确定值）+ `object-fit: contain`（竖屏不变形）。 */
.pv-video {
  max-width: 92vw;
  max-height: 82vh;
  width: auto;
  height: auto;
  object-fit: contain;
  background: #000;
}
.pv-pdf {
  width: 92vw; height: 100%; border: 0; border-radius: 8px; background: #fff;
}
/* ---- 左右翻页箭头（v24 第 19 批 用户第 6 条）----
   用户原话：「预览的时候，左右有箭头，可以直接查看下一张。」
   ⚠️ 左右各一个圆形按钮，垂直居中、压在内容两侧。禁用态（第一张/最后一张）
      要**看得出来**（半透明 + 禁止光标），否则用户会以为"点了没反应"。 */
.pv-nav {
  position: fixed; top: 50%; transform: translateY(-50%); z-index: 5;
  width: 46px; height: 46px; border-radius: 50%; border: 0; cursor: pointer;
  background: rgba(255, 255, 255, .2); color: #fff; font-size: 30px; line-height: 1;
  display: flex; align-items: center; justify-content: center; padding: 0 0 4px;
}
.pv-nav:hover:not(:disabled) { background: rgba(255, 255, 255, .34); }
.pv-nav:disabled { opacity: .28; cursor: default; }
/* ⚠️ 与 .pv 同一个老坑：这里自己声明了 display:flex，不补 [hidden] 就藏不住 —— 
   只有一个可预览项时箭头必须消失，靠的就是这条。 */
.pv-nav[hidden] { display: none; }
.pv-prev { left: 14px; }
.pv-next { right: 14px; }
@media (max-width: 560px) {
  .pv-nav { width: 40px; height: 40px; font-size: 26px; }
  .pv-prev { left: 6px; }
  .pv-next { right: 6px; }
}
@media (max-width: 560px) {
  .att-cell, .att-thumb { width: 108px; }
  .att-thumb { height: 80px; }
}
/* ---- 全局人员选择器（v24 第 11 条）：chips + 弹窗 + 拼音首字母联想 ----
   ⚠️ .att-* 是这套组件早年在观影页的版本，已由 .pp-* 取代；
      保留只为历史样式，新页面一律用 _person_picker.php。 */
.pp-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.pp-chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 6px 3px 4px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--surface);
  font-size: 13px;
}
.pp-chip img {
  width: 22px; height: 22px; border-radius: 50%; object-fit: cover; flex: 0 0 auto;
  background: #F1E7D6;
}
.pp-chip .pp-x {
  border: 0; background: none; color: var(--muted); font-size: 15px;
  line-height: 1; padding: 0 2px; cursor: pointer;
}
.pp-chip .pp-x:hover { color: #c0392b; }
.pp-chip-empty { color: var(--muted); border-style: dashed; padding: 3px 10px; }
.pp-modal {
  position: fixed; inset: 0; z-index: 60; display: flex; align-items: center;
  justify-content: center; background: rgba(0, 0, 0, .35); padding: 16px;
}
.pp-modal[hidden] { display: none; }
.pp-modal-box {
  background: var(--surface); border-radius: 12px; box-shadow: 0 12px 40px rgba(0, 0, 0, .2);
  width: min(420px, 100%); max-height: 70vh; display: flex; flex-direction: column;
  padding: 14px 16px; box-sizing: border-box;
}
.pp-modal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.pp-close {
  border: 0; background: none; font-size: 20px; line-height: 1;
  color: var(--muted); cursor: pointer; padding: 2px 6px;
}
.pp-modal-box .input { margin-bottom: 8px; }
.pp-list { overflow-y: auto; border: 1px solid var(--line); border-radius: 8px; }
.pp-item {
  display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: pointer;
  border-bottom: 1px solid var(--line-2); font-size: 14px;
}
/* ⚠️ 这条不能省：.pp-item 自己写了 display:flex，作者样式压过浏览器
   [hidden] { display:none }，缺了它「过滤」就成了摆设 —— 名字和拼音都搜不动。
   弹窗 .pp-modal 同理（上面已有），凡是**自己声明了 display** 的元素要藏都得补一条。 */
.pp-item[hidden] { display: none; }
.pp-item:last-child { border-bottom: 0; }
.pp-item:hover { background: var(--line-2); }
.pp-item input { accent-color: var(--brand); flex: 0 0 auto; }
/* v24 第 19 批 用户第 8 条：「前面的复选框就没用了吧，去掉吧。」
   ⚠️ 多选时复选框**确实没用**了 —— 第 18 批起"选谁"靠点头像/名字（点击整行），
      勾选与否只由暂存区的 chip 表达，一个小小的方框反而是干扰。
      JS 给多选组件加 `.pp-compact` 才隐藏；**单选不隐藏**（单选的圆点是他唯一
      能看见"现在选的是谁"的地方）。
   ⚠️ 只是 `display:none`，input **仍在 DOM 里** —— 表单提交全靠它。
      千万别改成 remove()（那就变成"选了保存后没人"）。 */
.pp-compact .pp-item input { display: none; }
.pp-item .pp-av {
  width: 30px; height: 30px; border-radius: 50%; object-fit: cover; flex: 0 0 auto;
  background: #F1E7D6;
}
.pp-item .pp-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pp-item .pp-initials { margin-left: auto; color: var(--muted); font-size: 12px; }
.pp-item-none .pp-name { color: var(--muted); }
.pp-empty { padding: 10px 12px; color: var(--muted); font-size: 13px; }
.pp-empty[hidden] { display: none; }
/* ---- 豆瓣自动补充（v10）：片名行 + 下拉候选 + 海报预览 ---- */
.db-title-row { display: flex; gap: 8px; align-items: center; }
.db-title-row .input { flex: 1 1 auto; }
.db-title-row .btn { flex: 0 0 auto; white-space: nowrap; }
.db-suggest {
  margin-top: 6px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface); overflow: hidden; max-height: 300px; overflow-y: auto;
}
.db-suggest[hidden] { display: none; }
.db-item {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 12px;
  border: 0; border-bottom: 1px solid var(--line-2); background: none;
  text-align: left; cursor: pointer; font: inherit; color: var(--ink);
}
.db-item:last-child { border-bottom: 0; }
.db-item:hover { background: var(--line-2); }
.db-item img { width: 32px; height: 45px; object-fit: cover; border-radius: 4px; background: var(--line-2); flex: 0 0 auto; }
.db-item-tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.db-item-tx b { font-size: 14px; }
.db-item-tx small { color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.db-poster-preview { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.db-poster-preview[hidden] { display: none; }
.db-poster-preview img {
  width: 60px; height: 90px; object-fit: cover; border-radius: 6px;
  border: 1px solid var(--line); background: var(--line-2);
}
/* 详情页英雄区里的豆瓣信息块 */
.db-meta {
  display: flex; flex-wrap: wrap; gap: 4px 14px; align-items: baseline;
  margin: 8px 0 2px; padding: 8px 12px; border-radius: 10px;
  background: var(--line-2); font-size: 13px; color: var(--ink);
}
.db-meta .db-rating { color: #f5b301; font-weight: 600; }
.db-meta .db-rating b { font-size: 16px; }
.db-meta .db-line { display: inline-flex; gap: 6px; align-items: baseline; min-width: 0; }
.db-meta .db-label { color: var(--muted); flex: 0 0 auto; }
.db-meta .db-link { margin-left: auto; color: var(--brand); text-decoration: none; white-space: nowrap; }
.db-meta .db-link:hover { text-decoration: underline; }
.rv-word { font-size: 13px; color: var(--muted); }
.rv-word.is-set { color: var(--brand); font-weight: 600; }
.rv-textarea {
  width: 100%; min-height: 92px; resize: vertical; padding: 12px 14px;
  border: 1px solid var(--line); border-radius: 10px; font-size: 15px;
  font-family: inherit; line-height: 1.7; background: var(--surface);
  color: var(--ink); box-sizing: border-box;
}
.rv-textarea:focus { outline: none; border-color: var(--brand); }
.rv-textarea-sm { min-height: 56px; font-size: 14px; }
.rv-form-foot { display: flex; align-items: center; justify-content: flex-end; gap: 12px; margin-top: 10px; flex-wrap: wrap; }
.rv-form-foot .rv-hint { margin-right: auto; }
.rv-hint { font-size: 12px; color: var(--muted); }
.rv-pick-label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
.rv-empty { color: var(--muted); font-size: 14px; }
.rv-list { display: flex; flex-direction: column; gap: 18px; }
.rv-item { border-top: 1px solid var(--line-2); padding-top: 16px; }
.rv-item:first-child { border-top: none; padding-top: 0; }
.rv-head { display: flex; align-items: center; gap: 10px; }
.rv-avatar { width: 34px; height: 34px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.rv-avatar-sm { width: 26px; height: 26px; }
.rv-avatar-text {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--line-2); color: var(--muted); font-weight: 600; font-size: 15px;
}
.rv-who { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.rv-name { font-size: 14px; font-weight: 600; color: var(--ink); }
/* ⚠️ 写着「我」的那个徽标（类名是「评价」区块 + 「我的」后缀）v11 删掉了：
   它在 .rv-who（flex column）里被 align-items:stretch 拉成整行宽的色带，
   星星旁边显出一条丑长的品牌色条。删除按钮已经表达了「这是我写的」，
   不需要徽标。别再把它加回来。 */
.rv-time { display: block; font-size: 12px; color: var(--muted); }
.rv-score { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px; }
.rv-score .stars-disp { font-size: 16px; }
.rv-score-max { font-size: 12px; font-weight: 500; color: var(--muted); font-variant-numeric: tabular-nums; }
.rv-del { margin-left: auto; }
.rv-del-btn {
  background: none; border: none; cursor: pointer; color: var(--muted);
  font-size: 14px; line-height: 1; padding: 4px 6px; border-radius: 6px;
}
.rv-del-btn:hover { color: var(--red); }   /* 底色交给统一段 */
.rv-content {
  margin: 8px 0 2px 44px; font-size: 15px; line-height: 1.75;
  color: var(--ink); word-break: break-word;
}
.rv-updated { margin-left: 44px; font-size: 12px; color: var(--muted); }
.rv-comments { margin: 10px 0 10px 44px; display: flex; flex-direction: column; gap: 8px; }
.rv-comment { display: flex; gap: 8px; background: var(--line-2); border-radius: 10px; padding: 8px 12px; }
.rv-c-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.rv-c-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rv-c-head .rv-name { font-size: 13px; }
.rv-c-text { font-size: 14px; line-height: 1.7; color: var(--ink); word-break: break-word; }
.rv-c-acts { margin-top: 6px; }
.rv-thread { display: flex; flex-direction: column; gap: 6px; }
.rv-comment-reply { margin-left: 22px; }
.rv-at { font-size: 12.5px; color: var(--muted); margin-bottom: 2px; }
.rv-at span { color: var(--brand); font-weight: 600; }
.rv-actions { display: flex; align-items: flex-start; gap: 8px 10px; flex-wrap: wrap; margin: 12px 0 0 44px; }
.rv-feelings { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 2px; }
.rv-item > .rv-feelings { margin-left: 44px; }
.rv-feeling-pick { margin-top: 8px; }
.feel-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.feel-chip { display: inline-flex; }
.feel-pick { position: relative; }
.feel-pick input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.feel-inner {
  display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px;
  border: 1px solid var(--line); border-radius: 20px; background: var(--surface);
  font-size: 13px; color: var(--muted); cursor: pointer; user-select: none;
}
.feel-pick:hover .feel-inner { border-color: var(--brand); }
.feel-pick input:checked + .feel-inner { border-color: var(--brand); background: rgba(168, 74, 40, .10); color: var(--brand); font-weight: 600; }
.feel-pick:focus-within .feel-inner { outline: 2px solid var(--brand); outline-offset: 1px; }
.feel-emoji { font-size: 14px; }
.feel-chip.is-static {
  align-items: center; gap: 4px; padding: 2px 10px; border-radius: 20px;
  background: rgba(168, 74, 40, .10); color: var(--brand); font-size: 12px;
}

/* ---- 影院管理页 ---- */
.cinema-h2 { font-size: 17px; font-weight: 600; margin: 0 0 14px; }
.cinema-select-row { display: flex; gap: 10px; align-items: center; }
.cinema-select-row .select { flex: 1; }
.color-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.color-picker { width: 60px; height: 40px; padding: 2px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); cursor: pointer; }
.color-hex { font-size: 14px; color: var(--muted); font-family: monospace; }
.preset-palette { display: flex; flex-wrap: wrap; gap: 6px; }
.preset-color {
  display: inline-block; width: 28px; height: 28px; border-radius: 50%;
  cursor: pointer; border: 2px solid transparent; transition: transform .2s, border-color .2s;
}
.preset-color:hover { transform: scale(1.1); }
.preset-color.active { border-color: var(--ink); }
.color-preview {
  display: inline-block; width: 14px; height: 14px; border-radius: 3px;
  margin-right: 6px; vertical-align: middle; border: 1px solid var(--line);
}
.table-wrap { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th { text-align: left; padding: 10px 8px; border-bottom: 2px solid var(--line); color: var(--muted); font-weight: 600; }
.table td { padding: 10px 8px; border-bottom: 1px solid var(--line-2); }
.table tr:hover td { background: var(--line-2); }

/* ---- 观影响应式 ---- */
@media (max-width: 720px) {
  .waterfall { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
  .card-body { padding: 10px 12px 12px; }
  .card-title { font-size: 14px; }
  .card-meta { font-size: 11px; gap: 4px 10px; }
  .detail-hero { grid-template-columns: 1fr; padding: 20px; }
  .detail-poster { max-width: 240px; margin: 0 auto; }
  .cinema-info { grid-template-columns: 1fr; }
  .gallery-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
  .detail-section { padding: 16px 18px; }
  .detail-info h1 { font-size: 22px; }
  .rv-content, .rv-updated, .rv-comments, .rv-actions, .rv-item > .rv-feelings { margin-left: 0; }
  .rv-comment-reply { margin-left: 10px; }
  .lb-nav { width: 40px; height: 40px; font-size: 24px; }
  .lb-prev { left: 6px; }
  .lb-next { right: 6px; }
  .lb-img { max-width: 88vw; max-height: 70vh; }
}

/* ============================================================
   v11：观影统计 / 高级搜索 / 年度报告（从 MovieLog 迁入）
   版式沿用家书的暖纸配色；图表全用 CSS（不引 CDN 图表库）。
   ============================================================ */

/* ---- 统计仪表板 ---- */
.stats-grid {
  display: grid; gap: 14px; margin-bottom: 16px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.stat-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px 18px; text-align: center; box-shadow: var(--shadow);
}
.stat-card .number { font-size: 30px; font-weight: 700; color: var(--brand); line-height: 1.2; font-variant-numeric: tabular-nums; }
.stat-card .label { font-size: 13px; color: var(--muted); margin-top: 2px; }
.stats-span {
  display: flex; align-items: baseline; gap: 12px; margin-bottom: 16px;
}
.stats-span .label { font-size: 13px; color: var(--muted); }
.stats-span .value { font-size: 16px; font-weight: 600; }

.chart-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px 22px; box-shadow: var(--shadow); margin-bottom: 16px;
}
.chart-card h3 { font-size: 16px; font-weight: 600; margin: 0 0 14px; display: flex; align-items: baseline; gap: 8px; }
.chart-card h3 .hint { font-size: 12px; color: var(--faint); font-weight: 400; }
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 720px) { .two-col { grid-template-columns: 1fr; } }

.bar-chart { display: flex; flex-direction: column; gap: 8px; }
.bar-item { display: flex; align-items: center; gap: 12px; }
.bar-item .bar-label {
  width: 150px; flex: 0 0 auto; font-size: 13px; color: var(--ink);
  display: flex; align-items: center; gap: 6px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bar-item .bar-label .bar-sub { color: var(--faint); font-size: 11px; }
.bar-item .bar-avatar { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.bar-item .color-dot { display: inline-block; width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }
.bar-item .bar-track {
  flex: 1; height: 20px; background: var(--line-2); border-radius: 6px; overflow: hidden;
}
.bar-item .bar-fill {
  display: block; height: 100%; border-radius: 6px; background: var(--brand);
}
.bar-item .bar-fill-alt { background: var(--brand-2); }
.bar-item .bar-fill-green { background: var(--green); }
.bar-item .bar-count {
  width: 40px; flex: 0 0 auto; text-align: right; font-size: 13px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 720px) {
  .bar-item .bar-label { width: 92px; font-size: 12px; }
  .bar-item .bar-count { width: 30px; }
  .chart-card { padding: 14px 16px; }
}

/* ---- 高级搜索 ---- */
.adv-form { margin-bottom: 18px; }
.date-range { display: flex; align-items: center; gap: 8px; }
.date-range .to { color: var(--muted); font-size: 13px; flex: 0 0 auto; }
/* ⚠️ 原生 <input type="date"> 的 min-width:auto 约 150px（带日历图标 + 占位数字）——
   两个日期框 + 「至」在 360px 下会横向溢出（实测 scrollW 390 > clientW 360）。
   给 0 并让它们等比收缩；这是继 .field（select）之后同一页的第二个溢出源。 */
.date-range .input { flex: 1 1 0; min-width: 0; }
.results-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin: 0 2px 12px;
}
.results-head h3 { margin: 0; font-size: 17px; }
.results-head .sub { color: var(--muted); font-size: 13px; }

/* ---- 年度报告 ---- */
.year-pick { display: inline-block; }
.report-grid {
  display: grid; gap: 14px; margin-bottom: 16px;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}
.report-grid.two { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.report-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px 20px; box-shadow: var(--shadow);
}
.report-card .n { font-size: 32px; font-weight: 700; color: var(--brand); line-height: 1.15; font-variant-numeric: tabular-nums; }
.report-card .l { font-size: 13px; color: var(--muted); }
.report-card .v { font-size: 19px; font-weight: 600; margin-top: 2px; }
.report-card .s { font-size: 12px; color: var(--faint); margin-top: 4px; }

/* 月份竖柱图（纯 CSS） */
.vgrid {
  display: grid; grid-template-columns: repeat(12, 1fr); gap: 6px;
  align-items: end; height: 160px; margin-top: 4px;
}
.vcol { display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; gap: 3px; }
.vcol .vbar {
  width: 68%; min-height: 2px; background: linear-gradient(var(--brand-2), var(--brand));
  border-radius: 5px 5px 2px 2px; transition: height .4s ease;
}
.vcol .vlab { font-size: 11px; color: var(--muted); }
.vcol .vnum { font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums; }

.kw-cloud { display: flex; flex-wrap: wrap; gap: 8px; }
.kw-cloud .kw {
  font-size: 14px; padding: 4px 12px; border-radius: 999px;
  background: var(--line-2); color: var(--brand-ink); border: 1px solid var(--line);
}

/* ==================== v12：旅程 / 日程 / 资源 / 任务 ==================== */
/* 四个新模块共用一套"卡片骨架"（.card + .card-pad + .cinema-h2 的小标题），
   下面只写各模块**特有**的部件，避免又造一套并行的卡片。 */

/* ---- 旅程 ---- */
.trip-list { display: flex; flex-direction: column; gap: 12px; }
.trip-card {
  display: flex; gap: 16px; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 14px; text-decoration: none; color: inherit;
  transition: transform .18s ease, box-shadow .18s ease;
}
.trip-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-l); text-decoration: none; }
.trip-cover {
  width: 132px; height: 96px; flex: 0 0 auto; border-radius: var(--radius-s);
  overflow: hidden; background: #F1E7D6; display: flex; align-items: center; justify-content: center;
}
.trip-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.trip-cover-empty { font-size: 34px; opacity: .55; }
.trip-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.trip-title { font-size: 17px; font-weight: 600; margin: 0; }
.trip-meta { display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 13px; color: var(--muted); }
.trip-sum { margin: 0; font-size: 14px; color: var(--muted); line-height: 1.6; }
/* 老数据（只有 events.note、还没搬成 summary）时的一句提示 ——
   见 templates/trips/show.php 里第 19 批 第 4 条的说明。 */
.trip-sum-legacy {
  margin: 0; font-size: 13px; color: var(--muted);
  background: #FDF3EC; border: 1px dashed rgba(168, 74, 40, .3);
  border-radius: var(--radius-s); padding: 8px 10px;
}
.trip-sum-legacy a { font-weight: 600; }
.trip-people { display: flex; flex-wrap: wrap; gap: 4px 8px; }

.trip-hero { display: flex; gap: 20px; flex-wrap: wrap; }
/* ⚠️ v24 第 16 批 用户第 1 条：「题图应该按照 16:9 显示，因为添加的时候，
   是按照这个比例裁剪的，现在的显示比例不对」—— 原来是 4:3 把 16:9 的图裁掉了两边。
   这个数字必须与 ImageSlot 的 'cover' 槽位比例**一致**（部署断言专门比对两处）。 */
.trip-hero-cover {
  width: 420px; max-width: 100%; aspect-ratio: 16 / 9; flex: 0 0 auto;
  border-radius: var(--radius-s);
  overflow: hidden; background: #F1E7D6; display: flex; align-items: center; justify-content: center;
}
.trip-hero-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.trip-hero-body { flex: 1 1 320px; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.trip-loc { margin: 0; font-size: 14px; color: var(--muted); }
.cover-now { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.cover-now img { width: 96px; height: 64px; object-fit: cover; border-radius: 8px; display: block; }

.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 10px; }
.photo-cell { margin: 0; background: #F1E7D6; border-radius: var(--radius-s); overflow: hidden; }
.photo-cell a { display: block; }
.photo-cell img { width: 100%; height: 120px; object-fit: cover; display: block; }
.photo-del { padding: 6px; text-align: center; }

/* ---- 行程节点 ---- */
.node-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.node-row {
  display: flex; gap: 12px; align-items: flex-start; padding: 12px 14px;
  border: 1px solid var(--line-2); border-radius: var(--radius-s); background: #FDFBF7;
}
.node-row.status-cancelled { opacity: .62; }
.node-row.status-done .node-head b { color: var(--muted); }
.node-ico { font-size: 20px; flex: 0 0 auto; line-height: 1.4; }
.node-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.node-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.node-head b { font-size: 15px; }
.node-badge {
  font-size: 11px; padding: 1px 8px; border-radius: 999px;
  background: var(--line-2); color: var(--muted); white-space: nowrap;
}
.node-row.status-done .node-badge { background: #E6F0DC; color: #4A7226; }
.node-place, .node-time { font-size: 12px; color: var(--muted); }
.node-detail { margin: 0; font-size: 14px; line-height: 1.65; }
/* v24 第 19 批 用户第 7 条：动作按钮**横着排列**、放在行程**右上角**。
   「计划行程的三个按钮，修改、完成、未成行，横着排列，放在行程右上角」
   「实际行程的编辑和删除按钮，也放在行程的右上角」
   ⚠️ 原来是 `flex-direction: column`（竖着一列、贴右下），用户要的是横排靠上。
      改成 row + align-self:flex-start（贴着行的顶部对齐），窄屏再允许换行。 */
.node-ops {
  flex: 0 0 auto; display: flex; flex-direction: row; flex-wrap: wrap;
  gap: 6px; align-items: flex-start; justify-content: flex-end;
  align-self: flex-start;
}
.node-edit > summary { cursor: pointer; }
.node-add { margin-top: 14px; border-top: 1px solid var(--line-2); padding-top: 12px; }
.node-add > summary { cursor: pointer; font-size: 14px; }
.node-form { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
.nf-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.nf-grid .field.full { grid-column: 1 / -1; }
.nf-members { display: flex; flex-wrap: wrap; gap: 6px 16px; }

/* ---- 附件行（旅程节点 / 资源 / 任务共用） ---- */
.attach-row { display: flex; flex-wrap: wrap; gap: 8px; }
.attach-item {
  display: inline-flex; align-items: center; gap: 5px; font-size: 13px;
  padding: 4px 10px; border-radius: 999px; border: 1px solid var(--line);
  background: var(--surface); color: var(--brand-ink); text-decoration: none;
}
.attach-item:hover { border-color: var(--brand); text-decoration: none; }

/* ---- 日程（月视图，群晖 Calendar 的外观） ---- */
.cal-wrap { display: grid; grid-template-columns: 230px 1fr; gap: 18px; align-items: start; }
/* 用户 2026-10-03 第 4 条：左侧「我的日历本」那一栏撤了，日历占满整宽。
   ⚠️ 用**单列**改写而不是删掉 .cal-wrap：`.cal-wrap` 还被别处（窄屏媒体查询）
   引用着，删掉会连带把那条声明变空。单列 = 视觉上就是"充满整个宽度"。 */
.cal-wrap-full { grid-template-columns: 1fr; }
.cal-side { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px 18px; }
.cal-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.cal-item { display: flex; flex-direction: column; gap: 4px; }
.cal-item > .node-badge { align-self: flex-start; }
.cal-toggle { display: flex; align-items: center; gap: 8px; font-size: 14px; cursor: pointer; }
.cal-toggle input { accent-color: var(--brand); }
.cal-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-note { font-size: 12px; color: var(--faint); line-height: 1.6; margin: 12px 0 0; }
.cal-main { min-width: 0; }
.cal-add { margin-bottom: 18px; }
.cal-grid-card { padding: 0; overflow: hidden; }
.cal-head { display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid var(--line); }
.cal-wd { padding: 8px 0; text-align: center; font-size: 12px; color: var(--muted); }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); }
.cal-cell {
  min-height: 104px; padding: 6px 6px 8px; border-right: 1px solid var(--line-2);
  border-bottom: 1px solid var(--line-2); display: flex; flex-direction: column; gap: 4px;
}
.cal-cell:nth-child(7n) { border-right: 0; }
.cal-cell:nth-last-child(-n+7) { border-bottom: 0; }
.cal-cell.is-out { background: #FCFAF6; }
.cal-cell.is-out .cal-dnum { color: var(--faint); }
.cal-cell.is-today { background: #FDF6F1; }
.cal-dnum { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; padding-left: 2px; }
.cal-cell.is-today .cal-dnum {
  color: #fff; background: var(--brand); border-radius: 999px;
  width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;
}
.cal-evs { display: flex; flex-direction: column; gap: 3px; }
.cal-ev {
  display: block; font-size: 12px; line-height: 1.35; color: #fff;
  padding: 2px 6px; border-radius: 5px; text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-ev:hover { text-decoration: none; filter: brightness(1.08); }
.cal-ev.is-allday { font-weight: 500; }
/* 🔴 v70（用户 2026-10-20 第 2 条）：「日历导入进去了，但是不显示」。
   原来这里是 `.cal-more` 一颗**纯文本 <span>** —— 点不动，于是被它折叠掉的条目
   （用户自己导入的日程全在里面）**永远看不到**。
   现在改成真正的按钮：可点、有热区、有 hover 反馈，点开是"当天全部条目"浮层。 */
.cal-more {
  display: block; width: 100%; margin: 0; padding: 1px 4px;
  border: 0; border-radius: 5px; background: transparent;
  font: inherit; font-size: 11px; line-height: 1.5; text-align: left;
  color: var(--faint); cursor: pointer;
}
.cal-more:hover { background: var(--hov-veil); color: var(--brand); }
.cal-more:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* ---- 当天全部条目浮层（v70）----
   ⚠️ 复用 `.cal-modal` 那一套（遮罩 / 居中面板 / 头尾不滚动），
      只补列表本身。为什么还要独立一份而不是套用条目详情层：
      那是"一条"的详情，这是"一天"的清单 —— 两者可以叠用（清单 → 某条详情）。 */
.cal-day-modal .cal-modal-panel { width: min(460px, 100%); max-height: calc(100vh - 80px); }
.cal-day-body { padding: 8px 10px 10px; }
.cal-day-item {
  display: flex; align-items: baseline; gap: 10px; width: 100%;
  padding: 9px 10px; margin: 0; border: 0; border-radius: 8px;
  background: transparent; font: inherit; text-align: left; cursor: pointer;
  color: var(--ink);
}
.cal-day-item + .cal-day-item { margin-top: 2px; }
.cal-day-item:hover { background: var(--hov-veil); }
.cal-day-when {
  flex: 0 0 auto; min-width: 74px; font-size: 12px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.cal-day-name { flex: 1 1 auto; font-size: 13.5px; line-height: 1.45; word-break: break-word; }
/* 派生条目（农历/节气/节假日）略淡：它们天天都有、信息密度低，
   视觉上让位给用户自己填的日程 */
.cal-day-item.is-derived .cal-day-name { color: var(--muted); }
/* 底栏三样：左「加一条」、中提示、右「在日视图里看」。
   名单长的时候底栏会被挤 → 允许换行，别让 HarfBuzz 把按钮压扁。 */
.cal-day-foot { flex-wrap: wrap; gap: 8px; }
.cal-day-foot .cal-modal-tip { flex: 1 1 auto; }
.ev-del { margin-top: 16px; }
.cal-share-name { font-size: 13px; color: var(--muted); }

@media (max-width: 860px) {
  .cal-wrap { grid-template-columns: 1fr; }
  .cal-cell { min-height: 78px; }
}

/* ---- 资源 ---- */
.res-list { display: flex; flex-direction: column; gap: 8px; }
.res-item { border: 1px solid var(--line-2); border-radius: var(--radius-s); background: #FDFBF7; }
.res-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 12px 14px; cursor: pointer; list-style: none;
}
.res-head::-webkit-details-marker { display: none; }
.res-head:hover { background: #FAF7F1; }
.res-title { font-size: 15px; font-weight: 600; }
.res-n { font-size: 12px; color: var(--faint); }
.res-body { padding: 0 14px 14px; display: flex; flex-direction: column; gap: 10px; }
.res-inline { display: inline; margin: 0; }
.res-shares { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.res-label { font-size: 12px; color: var(--muted); }
/* 🔴 v24 第 23 批：这条规则**必须改而不是在后面再覆盖一条**。
   旧值是 `flex-direction: column` —— 那是给「展开区里四个内联表单竖着排」用的布局，
   而第 23 批把四个动作搬进了弹框，`.res-ops` 变成了**标题行右端那一排按钮**。
   本轮实测：只在文件末尾追加 `display/gap/margin-left`，**没写 `flex-direction`**，
   于是四个按钮仍然**竖着排**，把标题行从 40px 撑到 **175px 高**
   （真机几何实测 ops.top=382.9 / title.top=445.6 —— 差 63px，一眼就看出不在同一排）。
   ⚠️ 这也是"两处定义同一个东西"的经典代价：覆盖式修法天生容易漏子属性，
     而下一次改的人只会看到其中一处。**改类名语义时，旧规则要删/改，不要叠。** */
.res-ops { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* ---- 资源列表：搜索在上（用户 2026-10-03 第 6 条）、排序与截断提示（第 10 条）---- */
.res-search-card { display: flex; flex-direction: column; gap: 10px; }
.res-search { margin-bottom: 0; }
/* 搜索框给宽一点：资源标题长（「不动产证 / 学区房合同」），窄了看不全 */
.res-search .input { max-width: none; flex: 1 1 auto; min-width: 0; }
.res-sortbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.res-sortbar .seg { margin: 0; }
.res-showall { margin-left: auto; }
.res-more {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line-2);
}

/* ---- 任务看板 ----
   ⚠️ 列数不再固定为 3：v24（用户 2026-10-03 第 7 条）起**一列 = 用户自己的一个看板**，
   看板能加能删，写死 repeat(3, 1fr) 的话加第四个看板就直接掉到第二行去
   （看起来像"加了个看板结果没显示"）。
   改成"横向自动排 + 可横向滚动"：几列都放得下，列多了也不挤。 */
.kanban {
  display: grid; grid-auto-flow: column;
  grid-auto-columns: minmax(230px, 1fr);
  gap: 14px; align-items: start; overflow-x: auto; padding-bottom: 2px;
}
.kb-col { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.kb-head {
  display: flex; align-items: center; gap: 8px; padding: 12px 16px;
  border-bottom: 1px solid var(--line-2); font-size: 14px;
}
.kb-head .kb-n { margin-left: auto; font-size: 12px; color: var(--faint); }
.kb-body { padding: 12px; display: flex; flex-direction: column; gap: 10px; min-height: 90px; }
.kb-body.is-over { background: #FDF6F1; outline: 2px dashed var(--brand-2); outline-offset: -4px; }
.kb-card {
  border: 1px solid var(--line-2); border-radius: var(--radius-s); background: #FDFBF7;
  padding: 10px 12px; display: flex; flex-direction: column; gap: 6px;
}
.kb-card.is-dragging { opacity: .45; }
.kb-card.is-overdue { border-color: rgba(192, 83, 63, .4); }
/* 🔴 v24 第 25 批 用户第 6 条：卡片加了「完成」复选框之后，它必须与标题**同一行**。
   作者样式是 `flex-direction: column` —— 不处理的话复选框会独占一行、
   标题被挤到第二行，一张卡凭空高一截（用户要的是"任务前面带一个复选框"，
   不是"任务上面带一个"）。做法：把标题包成一行（.kb-top），
   复选框 + 标题一起横排，剩下的 meta/foot 仍然竖着叠。 */
.kb-top { display: flex; align-items: flex-start; gap: 8px; }
.kb-top .kb-title { flex: 1 1 auto; min-width: 0; }
.kb-title { font-size: 14px; font-weight: 500; color: var(--ink); }
.kb-title:hover { color: var(--brand); }
.kb-meta { display: flex; flex-wrap: wrap; gap: 4px 8px; font-size: 12px; color: var(--muted); }
/* ---- 状态徽标（v24 第 14 批 用户第 2 条：每张卡都有，点一下推进到下一档 ----
   三档各有颜色：待办灰 / 进行中品牌色 / 已完成绿 —— 不靠位置也能一眼看出做到哪一步。
   ⚠️ .is-static（只读账号）长得一样但不可点：不给可点的错觉。 */
.kb-st {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 8px; border-radius: 999px; border: 1px solid transparent;
  font-size: 11px; line-height: 1.7; white-space: nowrap; cursor: pointer;
}
.kb-st.is-todo   { background: var(--line-2); color: var(--muted); border-color: var(--line); }
.kb-st.is-doing  { background: rgba(168, 74, 40, .12); color: var(--brand-ink); border-color: rgba(168, 74, 40, .3); }
.kb-st.is-done   { background: rgba(111, 158, 92, .14); color: #4C7038; border-color: rgba(111, 158, 92, .35); }
.kb-st.is-static { cursor: default; }
.kb-st.is-doing:hover { border-color: var(--brand); }
/* .is-on = "当前就是这一档"（读书详情页的四个状态按钮用它标选中）：
   加一圈内描边而不是换底色 —— 底色已经表达"这一档是什么颜色"，
   再换一次就会分不清"选中"和"这一档"。 */
.kb-st.is-on { box-shadow: inset 0 0 0 1px currentColor; font-weight: 600; }
.kb-due.is-late, .kb-late { color: var(--red); }
.kb-ico { white-space: nowrap; }

/* ---- 看板色点（v24 第 26 批 用户第 3 条）----
   同一个色点两种用法：
     · `.kb-dot`     —— 看板页**列头**上（列本身就是一个看板）；
     · `.tk-board-dot` —— 首页任务行**看板徽标**里（名字前一个小圆点）。
   都用 `--bc`（board color）传色：色值由服务端按「用户设过的色，否则按名字派生」算好，
   CSS 不参与任何色值决策（否则"同一个看板在首页和看板页颜色不一样"迟早发生）。 */
.kb-dot,
.tk-board-dot {
  display: inline-block; width: 9px; height: 9px; border-radius: 50%;
  background: var(--bc, var(--line)); flex: 0 0 auto;
}
.kb-dot { width: 10px; height: 10px; }
/* 首页那个看板徽标：名字前带色点，整体仍是原来那颗浅色 pill */
.tk-board { display: inline-flex; align-items: center; gap: 5px; }
/* 看板设置页的颜色选择器：native color input 默认很宽，收窄到与输入框同高的一小块 */
.input-color {
  width: 34px; height: 30px; padding: 2px; border: 1px solid var(--line);
  border-radius: var(--radius-s); background: #fff; cursor: pointer; flex: 0 0 auto;
}
.input-color::-webkit-color-swatch-wrapper { padding: 0; }
.input-color::-webkit-color-swatch { border: 0; border-radius: 4px; }
.kb-sub { display: flex; align-items: center; gap: 8px; }
.kb-bar { flex: 1 1 auto; height: 5px; border-radius: 999px; background: var(--line-2); overflow: hidden; }
.kb-bar i { display: block; height: 100%; background: var(--green); }
.kb-sub-n { font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums; }
.kb-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.kb-move { margin-left: auto; display: flex; gap: 4px; }
.kb-empty { padding: 6px 2px; }
.hidden-form { display: none; }

/* ---- 任务详情 ---- */
.sub-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.sub-row {
  display: flex; align-items: center; gap: 10px; padding: 7px 10px;
  border: 1px solid var(--line-2); border-radius: var(--radius-s); background: #FDFBF7;
}
.sub-row.is-done .sub-title { color: var(--muted); text-decoration: line-through; }
/* ⚠️ `.sub-check`（旧的 ☐/☑ 字符按钮）已在 v24 第 25 批 用户第 6 条**删掉**：
   子任务改用全站统一的 `.tk-check`（见上面那一段）。留一份没人用的样式
   只会让下一个人以为还有第二条画法。 */
.sub-title { flex: 1 1 auto; min-width: 0; font-size: 14px; }
.cmt-list { list-style: none; margin: 0 0 12px; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.cmt-row { border-top: 1px solid var(--line-2); padding-top: 10px; }
.cmt-who { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cmt-time { font-size: 11px; color: var(--faint); }
.cmt-body { font-size: 14px; line-height: 1.7; margin: 6px 0; }
.cmt-body .mention { color: var(--brand-ink); background: #F6E3D7; border-radius: 4px; padding: 0 3px; }
.cmt-form { margin-top: 6px; }
.task-danger { margin-top: 18px; display: flex; justify-content: flex-end; }

/* ---- 「危险操作区」（成员详情页的删除，用户 2026-10-03 第 2 条）----
   刻意做成**页面最下方一块低调的折叠区**：破坏性操作不该一眼看到、更不该一键完成。
   视觉上只用一条虚线分隔 + 灰色摘要，不抢上面正常内容的注意力。 */
.danger-zone {
  margin-top: 28px; padding-top: 14px;
  border-top: 1px dashed var(--line);
}
.danger-zone > details > summary { cursor: pointer; font-size: 13px; }
.danger-zone .del-confirm {
  margin-top: 10px; padding: 12px 14px;
  border: 1px solid rgba(178, 45, 45, .25); border-radius: var(--radius-s);
  background: #FDF6F4;
}

/* ---- 表单标签更醒目（用户 2026-10-03 第 6 条）----
   「每一个输入的名字，都不够醒目，例如姓名、性别，这些字应该变大一点，加粗，
     不能跟提示一样大小」—— 提醒（.tip）是 12px 的灰字，而标签原来是 13-14px，
   两者太接近，扫一眼分不出"哪一行在哪"。这里把标签统一抬到 15px / 600 字重 /
   深一点的墨色，与 .tip / .hint 明确拉开层级。 */
.form-grid > .field > label,
.form-grid > .field > .field-label {
  font-weight: 600; font-size: 15px; color: var(--ink);
}
/* 「我的资料 / 编辑成员」那张表里也有不在 form-grid 直接子级的 label（嵌套块），
   一并覆盖；用 :not(.job-lb) 排除工作经历行里的那些小标签（见下）。 */
.card .field > label:not(.job-lb):not(.check-item):not(.chk-inline) {
  font-weight: 600; font-size: 15px; color: var(--ink);
}
/* ⚠️ 但 .tip / .hint / .small 这些"说明文字"必须保持小灰字，别被上面那条带大 */
.card .field > label.tip,
.card .field > label.hint,
.card .field > label.small { font-weight: 400; font-size: 12.5px; color: var(--muted); }

/* ---- 头像选择器（用户第 6 条：头像放最前 + 去掉「选择文件」按钮）----
   点那张（默认）头像就是选文件 —— 图本身就是按钮，不再另起一行控件。
   默认头像用 .avatar-default 叠一个"＋"角标，提示这里可点。 */
.avatar-field { margin-bottom: 4px; }
.avatar-picker { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.avatar-picker #avatar-preview {
  width: 96px; height: 96px; border-radius: 12px; object-fit: cover;
  border: 1px solid var(--line); background: #F1E7D6; cursor: pointer;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.avatar-picker #avatar-preview:hover { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(168,74,40,.12); }
.avatar-picker #avatar-preview[data-has-avatar="0"] { border-style: dashed; }
.avatar-hint { line-height: 1.7; }

/* ---- 旅程题图（v24 第 14 批 用户第 3 条）：与头像同一套裁剪，比例 16:9 ----
   预览块本身就是"选文件"的点击区（与头像一样，省掉一整行重复的文件控件）。
   ⚠️ 比例写死成 16/9 是**故意**的：CSS 不读 ImageSlot，而槽位里的比例就是 16:9，
      两边各写一份会漂移 —— 部署脚本有一条断言比对 app.js 与 CSS 里的比例数字。 */
.cover-picker { display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap; }
.cover-picker #cover-preview {
  width: 240px; max-width: 100%; aspect-ratio: 16 / 9;
  object-fit: cover; border-radius: 12px;
  border: 1px solid var(--line); background: #F1E7D6; cursor: pointer;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.cover-picker #cover-preview:hover { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(168,74,40,.12); }
.cover-picker #cover-preview[data-has-cover="0"] { border-style: dashed; }
.trip-people-block { display: flex; flex-direction: column; gap: 6px; margin: 8px 0; }

/* ---- 工作经历（用户第 4 条：可以有多个）---- */
.job-list { display: flex; flex-direction: column; gap: 8px; }
.job-row {
  display: grid; gap: 8px; align-items: end;
  /* 单位 / 职务 / 职称 各占一份，起止各一份窄的，最后是删除按钮 */
  grid-template-columns: 1.4fr 1fr 1fr 130px 130px auto;
  padding: 10px 12px; border: 1px solid var(--line-2);
  border-radius: var(--radius-s); background: #FDFBF7;
}
.job-cell { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.job-lb { font-size: 12px; color: var(--muted); font-weight: 500; }
.job-cell .input { padding: 7px 9px; font-size: 14px; }
.job-del { align-self: end; }
@media (max-width: 860px) {
  /* 窄屏：一段经历折成两列，别硬塞六列（会挤成没法填） */
  .job-row { grid-template-columns: 1fr 1fr; }
  .job-row .job-cell:first-child { grid-column: 1 / -1; }
  .job-del { grid-column: 1 / -1; justify-self: end; }
}

@media (max-width: 860px) {
  .kanban { grid-template-columns: 1fr; }
  .nf-grid { grid-template-columns: 1fr; }
}

/* ---- 个人设置里的「模块」两块 ---- */
.mod-order .mod-row { cursor: grab; }
.mod-order .mod-row.is-dragged { opacity: .45; cursor: grabbing; }
.mod-order .mod-row.is-drop-before { box-shadow: inset 0 3px 0 var(--brand); }
.mod-order .mod-row.is-drop-after { box-shadow: inset 0 -3px 0 var(--brand); }
.mod-pick { display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0; cursor: pointer; }
.mod-pick input { accent-color: var(--brand); }
.mod-swatches { display: flex; flex-wrap: wrap; gap: 8px 18px; }
.mod-swatch { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--muted); }
.mod-colors { display: flex; flex-direction: column; gap: 10px; }
.mod-color-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.mod-color-name { flex: 0 0 76px; font-size: 14px; }
.mod-preset { display: flex; flex-wrap: wrap; gap: 4px; }
.mod-preset .preset-color { cursor: pointer; }

/* ---- 纪念日设置下拉（v24 用户 2026-10-03 第 9 条）---- */
.ann-set .menu-list { min-width: 260px; max-width: min(320px, 92vw); }
.ann-set-list { padding: 10px; gap: 0; }
.ann-kind-list { list-style: none; margin: 0; padding: 0; max-height: 46vh; overflow-y: auto; }
.ann-kind-list li {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 5px 2px; border-bottom: 1px solid var(--line-2);
}
.ann-kind-list li:last-child { border-bottom: 0; }
.ann-kind-name { font-size: 13.5px; font-weight: 500; }
.ann-kind-list li .small { margin-left: auto; }

/* ---- 首页 dashboard ---- */
.hero-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.dash-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.dash-row {
  display: flex; align-items: baseline; gap: 10px; padding: 8px 2px;
  border-top: 1px solid var(--line-2); flex-wrap: wrap;
}
.dash-row:first-child { border-top: 0; }
.dash-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.dash-when { flex: 0 0 96px; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
/* 🔴 v54（用户 2026-10-16 第 1 条）：「首页**近期任务**那个地方要注意截断：
   如果任务标题太长，会把这个任务**挤成两行**。」
   真根因：这一条只有 `flex:1 1 auto` + `min-width:0` —— 它能**收缩**，
   但行内文字**没有禁止换行** ⇒ 收缩之后标题在框内绕成第二行，把这一行撑高、
   把旁边的状态/看板徽标挤到下一行。缺的正是这三句。
   ⚠️ 三句缺一不可：`nowrap` 让它不绕行、`hidden` 让溢出被裁、
      `ellipsis` 给出"后面还有"的信号（不然会像被截断的错字）。
   ⚠️ 全名走 `title=` 悬停（模板里加的），点开也会弹详情浮层 —— 短的看不全不是问题。
   ⚠️ 这条**同时**管「往年的今天」那一块（同一个 class）：那边的标题也可能长，
      一并对齐成"一行 + 省略号"比一处换行一处截断更整齐。 */
.dash-what {
  flex: 1 1 auto; min-width: 0; font-size: 14px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dash-tag { flex: 0 0 auto; font-size: 11px; color: var(--faint); }
/* ---- v24 第 11 批 #4：影院下拉（带色点）+ 新建影院弹框 ----
   原生 <option> 画不了色块，所以自己做一个"按钮 + 弹出列表"：
   值仍写进隐藏的 input[name=cinema_id]，服务端与既有测试都不用改。 */
.cinema-select-row { display: flex; align-items: flex-start; gap: 8px; }
.cs { position: relative; flex: 1 1 auto; min-width: 0; }
.cs-btn {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 10px 12px; font: inherit; font-size: 14px; color: var(--ink);
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius-s);
  cursor: pointer; text-align: left;
}
.cs-btn:hover { border-color: var(--brand-2); }
.cs-btn:focus { outline: none; border-color: var(--brand); }
.cs-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-caret { flex: 0 0 auto; color: var(--muted); font-size: 11px; }
.cs-dot {
  flex: 0 0 auto; width: 12px; height: 12px; border-radius: 3px;
  background: var(--line-2); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12);
}
.cs-dot.is-none { background: transparent; box-shadow: inset 0 0 0 1px var(--line); }
.cs-menu {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 40;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 10px; box-shadow: var(--shadow-l);
  padding: 4px; max-height: 280px; overflow-y: auto;
}
.cs-menu[hidden] { display: none; }
/* 🔴 v39（用户 2026-10-10）：「影院那个地方应该有一个搜索的功能，下拉的地方上面
   有一个搜索筛选的功能。」
   ⇒ 弹出列表顶部一条搜索框。⚠️ 它要**吸顶**（列表长时往下滚，它跟着走）——
      不然滚两下搜索框就没了，"搜索筛选"等于白加。 */
.cs-search {
  position: sticky; top: -4px; z-index: 2;
  margin: -4px -4px 4px; padding: 6px 6px 6px;
  background: var(--surface); border-bottom: 1px solid var(--line-2);
}
.cs-search-input {
  width: 100%; box-sizing: border-box;
  padding: 7px 10px; font: inherit; font-size: 13px; color: var(--ink);
  background: var(--paper, #FDFBF7); border: 1px solid var(--line);
  border-radius: 7px;
}
.cs-search-input::placeholder { color: var(--muted); }
.cs-search-input:focus { outline: none; border-color: var(--brand); }
/* 没有匹配项时的提示（没搜过 / 有结果时都 hidden，不占高度） */
.cs-empty { padding: 10px 12px; font-size: 13px; color: var(--muted); text-align: center; }
.cs-empty[hidden] { display: none; }
.cs-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px 10px; border: 0; border-radius: 7px; background: transparent;
  font: inherit; font-size: 13.5px; color: var(--ink); cursor: pointer; text-align: left;
}
/* ⚠️ 过滤用的 `hidden` 必须写出来：`.cs-item` 自己声明了 `display:flex`，
   它会**盖过**浏览器默认的 `[hidden]{display:none}` ——
   这正是第 11 批"选人过滤一直是坏的"的同一个坑，别再踩第二次。 */
.cs-item[hidden] { display: none; }
.cs-item:hover, .cs-item:focus { background: var(--line-2); outline: none; }
.cs-item.is-disabled { opacity: .5; cursor: not-allowed; }
.cs-item.is-disabled:hover { background: transparent; }

/* ---- 🔴 v44：日程「日历本」下拉 ----
   用户 2026-10-12 第 1 条：「把颜色放在下拉框里面，就跟电影的影院选择一样，
   让日历本这三个字和下拉框在一条线上。」
   ⇒ 复用观影那套 `.cs`（按钮 + 色点 + 弹出列表 + 搜索），只补两点：
     ① 这个下拉在 `.mv-row` 里，宽度由 `> *:not(.mv-lb)` 那条 `flex:1` 接管，够用；
     ② **无 JS 兜底**：那个原生 `<select id="ce-cal">` 只做视觉收起 ——
        用 `position:absolute; width:1px`（**不是 display:none**，保留可聚焦、可提交），
        与 `_datetime.php` 的"隐藏 date 当值载体"同一套思路。
        ⚠️ 断言里按 `#ce-cal` 取 option / 值的写法必须继续成立 —— 它在 DOM 里。 */
.cal-book-cs { width: 100%; }
.cal-book-native {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  border: 0; padding: 0; margin: -1px;
}

/* 新建影院：居中弹框（与名册的动作弹框同一形态，只是更宽） */
.cn-modal {
  /* 🔴🔴 v41（用户 2026-10-10）：「观影模块，第一场电影，新建影院这个弹窗不显示，
     被记一场电影这个弹窗盖住了。」
     真根因：`.cn-modal` 是 **70**，而「记一场电影」用的 `.cal-modal` 是 **200** ——
     影院弹框是**从观影弹框里**打开的子层，层级却更低 ⇒ 被整块盖住、看不见也点不到。
     ⇒ 提到 260：必须**高于 .cal-modal(200)**，也高于 antd 的下拉面板
       （`.ant-picker-dropdown` 走 popper，默认 1050 之外的那一层我们没碰；
        这里只需保证"压过所有自建弹框"即可）。 */
  position: fixed; inset: 0; z-index: 260;
  display: flex; align-items: center; justify-content: center;
}
.cn-modal[hidden] { display: none; }
.cn-mask { position: absolute; inset: 0; background: rgba(47, 36, 30, .34); }
.cn-panel {
  position: relative; z-index: 1;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 14px; box-shadow: var(--shadow-l);
  width: min(420px, 92vw); max-height: min(84vh, 620px);
  display: flex; flex-direction: column;
}
.cn-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px 10px; border-bottom: 1px solid var(--line-2);
}
.cn-head b { font-size: 14.5px; }
.cn-x {
  margin-left: auto; border: 0; background: transparent; cursor: pointer;
  font-size: 15px; color: var(--muted); padding: 4px 6px; border-radius: 6px;
}
.cn-x:hover { background: var(--line-2); color: var(--ink); }
.cn-body { padding: 12px 14px; overflow-y: auto; }
.cn-body .field { margin-bottom: 12px; }
.cn-body .field > label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 5px; }
.cn-foot {
  display: flex; gap: 8px; justify-content: flex-end;
  padding: 10px 14px; border-top: 1px solid var(--line-2);
}
.cn-presets { display: inline-flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.cn-preset {
  width: 20px; height: 20px; border-radius: 5px; border: 1px solid rgba(0, 0, 0, .12);
  cursor: pointer; padding: 0;
}
.cn-preset:hover { transform: scale(1.08); }

/* ---- v24 第 11 批 #1：日历本改「实心色块」----
   原来 .dash-tag 是 --faint 的 11px 灰字，压在宣纸底上几乎看不见（用户原话"看不清"）。
   现在：底色 = 该日历本的颜色（由模板内联），文字白色 —— 与左侧那个小圆点同一个颜色，
   扫一眼就知道"这条是哪本日历的"。
   ⚠️ 颜色是用户自选的，浅色（如 #97A630）上白字会发虚 → 加一点文字阴影保底，
      比"强制加深颜色"好：本子的颜色是用户定的，不该被我们改。 */
.dash-cal {
  flex: 0 0 auto; align-self: center;
  padding: 2px 9px; border-radius: 999px;
  color: #fff; font-size: 11.5px; font-weight: 500; line-height: 1.7;
  white-space: nowrap; max-width: 40%;
  overflow: hidden; text-overflow: ellipsis;
  text-shadow: 0 1px 1px rgba(0, 0, 0, .22);
}
@media (max-width: 620px) {
  .dash-when { flex: 0 0 auto; }
}

/* ---- v14：导航条从 4 个模块变成最多 8 个，窄屏必须能横向滑动 ----
   ⚠️ 这是本轮**实测出来的回归**：模块从 4 个加到 8 个之后，
      375px 视口下 scrollWidth=472 > clientWidth=375，**整页横向溢出**
      （顶栏把 body 撑宽了）。以前 4 个模块刚好塞得下，所以从没有人给 .nav 写过窄屏规则。
   修法用「导航自己横向滚动」而不是「换行」：
      换行会把顶栏从 60px 撑成两行，sticky 顶栏一高一矮很难看，而且滚动时页面会跳。
   ⚠️ 断点必须是 **720px**（站点既有的"窄屏留白 16px"断点），不能用 860：
      746px 宽时若只改顶栏留白，就会出现「顶栏 x=16、正文 x=24」错位 8px
      （几何检查会直接报出来）。窄屏规则只在站点真正切到小留白的那一档生效。 */
/* ==================== 月 / 周 / 天 三选一的分段控件 ==================== */
/* 三个按钮连成一条（相邻边各留一半边框，拼起来才是一整条外框），
   当前那个用模块主题色填底 —— 一眼看出自己在哪种看法上。 */
.seg { display: inline-flex; }
.seg-btn {
  padding: 6px 14px; font-size: 13px; line-height: 1;
  color: var(--muted); background: var(--surface);
  border: 1px solid var(--line); text-decoration: none;
}
.seg-btn + .seg-btn { border-left: 0; }
.seg-btn:first-child { border-radius: var(--radius-s) 0 0 var(--radius-s); }
.seg-btn:last-child  { border-radius: 0 var(--radius-s) var(--radius-s) 0; }
.seg-btn:hover { text-decoration: none; }   /* 原来那层 #FDFBF7 在米色底上像脏了一块 */
.seg-btn.is-on {
  background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600;
}

/* ---- 日程页头的「跳到某一天」（v24 第 11 批 #6 重做）----
   用户要求：「跳转日期那个地方，不要那么长，只需要和设置一样，一个按钮即可」，
   且日期选择要用 Semi（与全站统一）。
   所以现在是**一个方图标按钮**（与 ☰ 的 .btn-icon 同规格）:
   点开弹一个只含 Semi 日期选择器的轻浮层，选中即跳。 */
.cal-jump { display: inline-flex; margin: 0; }
.cal-jump-btn { display: inline-flex; align-items: center; justify-content: center; cursor: pointer; }
/* ---- v24 第 12 批 #3：跳转日期改成**按钮下方的下拉日历** ----
   用户原话：「选择这个日期，不用弹窗，直接把日历选择，弹出在这个按钮下面就可以了。」
   所以把原来"全屏遮罩 + 居中弹窗"（.cal-jump-host/.cal-jump-pop）换成
   贴着按钮下方展开的下拉面板（.cal-jump-drop）。 */
.cal-jump { position: relative; }
.cal-jump-drop {
  position: absolute; z-index: 70;
  top: calc(100% + 8px); left: auto; right: 0;   /* 贴右：与按钮同一列，不会跑出屏外 */
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: var(--shadow-l);
  padding: 8px;
  /* 下拉里放的是 Semi 的**内联**日历面板，去掉它自己的浮层阴影/描边避免双层框 */
}
.cal-jump-drop[hidden] { display: none; }
/* ---- v24 第 14 批 #1：下拉里是**自绘**的日历（不再挂 Semi 的浮层面板）----
   「上一月/上一年」只重画面板、绝不提交（这正是不引第三方控件的原因：
   它把"翻月"与"选中"混在同一个 onChange 里，于是翻月也会跳走）。 */
.cjd-head {
  display: flex; align-items: center; gap: 4px; margin-bottom: 6px;
}
.cjd-title { flex: 1; text-align: center; font-weight: 600; font-size: 14px; }
.cjd-nav {
  width: 28px; height: 28px; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface); color: var(--ink); font-size: 14px; line-height: 1;
  cursor: pointer;
}
.cjd-nav:hover { background: var(--line); }
.cjd-grid {
  display: grid; grid-template-columns: repeat(7, 32px); gap: 2px;
}
.cjd-wd {
  text-align: center; font-size: 11px; color: var(--muted); padding: 2px 0 4px;
}
.cjd-day {
  width: 32px; height: 30px; padding: 0;
  border: 0; border-radius: 8px; background: transparent; color: var(--ink);
  font-size: 13px; cursor: pointer;
}
.cjd-day:hover { background: var(--line); }
.cjd-day.is-out { color: var(--muted); opacity: .55; }
.cjd-day.is-today { font-weight: 700; box-shadow: inset 0 0 0 1px var(--line); }
.cjd-day.is-on { background: var(--brand); color: #fff; }
.cjd-foot {
  display: flex; align-items: center; gap: 8px;
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line);
}
.cjd-foot .cjd-sel { flex: 1; }

/* ==================== 周 / 天：按小时铺的时间网格 ==================== */
/* 结构：左边一条"小时刻度"沟槽（tg-gutter），右边 N 个日期列（tg-col）。
   列高固定 = 24 小时 × 每小时 44px，日程块按 top/height 百分比**绝对定位**，
   所以"09:15 的 20 分钟"就是一小条，不会跟 9 点整的会挤成一样高。 */
.cal-tg-card { padding: 0; overflow: auto; }
.tg-head { display: flex; position: sticky; top: 0; z-index: 3;
           background: var(--surface); border-bottom: 1px solid var(--line); }
.tg-gutter {
  flex: 0 0 56px; width: 56px; border-right: 1px solid var(--line);
  display: flex; flex-direction: column; align-items: flex-end;
}
.tg-dayname {
  flex: 1 1 0; min-width: 0; padding: 8px 6px; text-align: center;
  border-left: 1px solid var(--line-2);
}
.tg-dayname:first-of-type { border-left: 0; }
.tg-dayname.is-today { background: #FDF6F1; }
.tg-wd { display: block; font-size: 11px; color: var(--faint); }
.tg-dd { display: block; font-size: 16px; font-weight: 600; font-variant-numeric: tabular-nums; }
.tg-dayname.is-today .tg-dd { color: var(--brand); }

.tg-allday { display: flex; border-bottom: 1px solid var(--line); background: #FDFBF7; }
.tg-allday .tg-gutter { padding: 6px 6px; }
.tg-tag { font-size: 11px; color: var(--faint); }
.tg-adcell {
  flex: 1 1 0; min-width: 0; padding: 4px 4px 6px; border-left: 1px solid var(--line-2);
  display: flex; flex-direction: column; gap: 3px;
}
.tg-adcell:first-of-type { border-left: 0; }

.tg-body { display: flex; position: relative; }
.tg-hours { padding: 0; }
.tg-hr {
  height: 44px; padding-right: 6px; padding-top: 2px;
  font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums;
  border-top: 1px solid var(--line-2); box-sizing: border-box;
}
.tg-hr:first-child { border-top: 0; }
.tg-col {
  flex: 1 1 0; min-width: 0; position: relative;
  height: 1056px;                      /* 24 × 44px，与 .tg-hr 的高度必须一致 */
  border-left: 1px solid var(--line-2); box-sizing: border-box;
}
.tg-col:first-of-type { border-left: 0; }
.tg-col.is-today { background: #FDF6F1; }
.tg-line { display: block; height: 44px; border-top: 1px solid var(--line-2); box-sizing: border-box; }
.tg-line:first-child { border-top: 0; }
/* ⚠️ v30 第 4 条：`left` 与 `width` 现在由**内联样式**给出（冲突分列后每条不同），
   所以这里**不能**再写 `left/right` —— 绝对定位下 left+right+width 三者同时存在是
   "过约束"，浏览器会丢掉 `right`（看着像是碰巧对）。留一个兜底 left:2px，
   万一某条没带内联 left（老数据/兜底路径）也不至于跑到列外去。 */
.tg-block {
  position: absolute; left: 2px;
  display: block; overflow: hidden; padding: 2px 5px;
  border-radius: 4px; color: #fff; font-size: 11px; line-height: 1.35;
  text-decoration: none; box-sizing: border-box;
  border: 1px solid rgba(255,255,255,.35);
}
.tg-block:hover { text-decoration: none; filter: brightness(1.1); z-index: 2; }
.tg-block b { font-weight: 600; margin-right: 3px; }

/* ============================================================
   跨天日程的分段（v88 / 工单 #35）
   ============================================================
   用户：「如果一个日程跨了三天，应该在日历本上怎么显示呢？在周视图和日视图，
          应该怎么显示？现在显示的有问题。」

   做法：一个跨天日程在**每一列各画一段**（`build_time_grid` 算好每段的
   top/height），视觉上要让它们看起来是**一条**，而不是三个互不相干的块。

   为什么不做跨列绝对定位层：`.tg-body` 左边有 56px 固定 gutter，
   窄屏还会横向滚动（`.cal-tg-card{overflow:auto}` + 每列 min-width 92px），
   `.tg-col` 又是 `overflow:hidden`。跨列层要自己算偏移、跟滚动跑、还不能被裁。
   在列内画段则天然对齐，代价只是段间有 1px 列边框缝 —— 用下面的样式糊上。

   三段的关系：
     · start（起始段）：上圆下平 —— 下方要接中间段
     · mid  （中间段）：上下都平 —— 两头都接
     · end  （结束段）：上平下圆 —— 上方接中间段
   ⚠️ 内侧不画 border：`.tg-block` 原本有 1px 白边，段与段之间会多出两条白线，
      看着像断开了。这里按段位取消对应的那条边。
   ⚠️ 左右内缩量要比单天块**小**（单天是 left:2px / width:-4px）：
      跨天段要尽量贴满整列，缝隙才窄，连起来才像一条。模板已给 1%/98%。 */
.tg-block.is-seg-start {
  border-top-left-radius: 4px; border-top-right-radius: 4px;
  border-bottom-left-radius: 0; border-bottom-right-radius: 0;
  border-bottom-color: transparent;      /* 下边不画，与中间段连上 */
  padding-bottom: 0;
}
.tg-block.is-seg-mid {
  border-radius: 0;
  border-top-color: transparent;
  border-bottom-color: transparent;
  padding-top: 1px; padding-bottom: 0;
}
.tg-block.is-seg-end {
  border-top-left-radius: 0; border-top-right-radius: 0;
  border-bottom-left-radius: 4px; border-bottom-right-radius: 4px;
  border-top-color: transparent;         /* 上边不画，与中间段连上 */
  padding-top: 1px;
}
/* 延续标记：这段不是从今天开始的，用一个小箭头代替开始时刻 */
.tg-cont {
  display: inline-block; margin-right: 2px;
  opacity: .75; font-size: 10px; line-height: 1;
}
/* 跨天段占满整列宽度，内边距跟着收一点，字才不会被挤 */
.tg-block[data-seg] {
  padding-left: 4px; padding-right: 4px;
  /* 🔴 v88：跨天段用 left:0 / width:100%（模板按段区分），
     必须配 box-sizing:border-box，否则 padding 会把宽度撑出列外。
     `.tg-block` 已有 box-sizing:border-box，这里显式再写一次是为了防止
     以后有人给单天块改 box-sizing 时顺手把这条也改掉。 */
  box-sizing: border-box;
}

/* 天视图只有一列，把列拉宽好读；周视图 7 列时列宽由 flex 平分 */
@media (max-width: 720px) {
  /* 窄屏留不住 56px 的刻度沟槽 + 7 列，改成整体可横向滚动（.cal-tg-card 已 overflow:auto），
     给每列一个最小宽度，滚动着看而不是把字挤成一团。 */
  .tg-col, .tg-dayname, .tg-adcell { min-width: 92px; }
  .cal-tg-card { -webkit-overflow-scrolling: touch; }
}

/* ⚠️ v24：导航从 7 个模块加到 9 个（纪念日 / 财务）之后，**746px 就放不下了**，
   而"放不下"的表现不是换行、是顶栏被撑宽 20.5px：
       `.nav` 内部产生横向滚动条（哪怕已用 scrollbar-width:none 隐藏），
       `.topbar-inner` 的可用宽度就比 `.container` 少一档，
       几何检查直接报「顶栏右 742.5 / 正文右 722.0」。
   所以**不能**只在 720px 断点里给 .nav 加快收缩 ——
   那样 721~1080 这一段区间没人管，正是出问题的地方。
   正确做法：把「导航可收缩 + 横向滚动」提到**无媒体查询的基态**，
   只在真的窄到需要缩留白时才改 padding（下面 720px 那段）。 */
.nav {
  /* flex:1 1 auto + min-width:0 才真的会"被压缩"；只写 min-width 时它仍是 auto 宽度，
     内容一多就把顶栏撑出去（这就是 746px 那 20.5px 的来源）。 */
  flex: 1 1 auto; min-width: 0;
  overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none;
}
.nav::-webkit-scrollbar { display: none; }
.nav a { flex: 0 0 auto; }
@media (max-width: 720px) {
  .topbar-inner { gap: 12px; padding: 0 16px; }
  /* min-width:0 是关键：flex 子项默认 min-width:auto，会拒绝被压缩，
     于是内部再 overflow-x 也撑不出滚动条，而是把父级一起顶宽。 */
  .nav {
    flex: 1 1 auto;
    margin-left: 0;
  }
  .nav a { padding: 6px 10px; }
  .topbar-right { margin-left: 0; gap: 6px; }
  /* 窄屏把「家书」两个字收起来，只留图标 —— 名字占的宽度正好够多放两个模块 */
  .brand span { display: none; }
  .who .who-name { display: none; }
}

/* ---- 地址五级（国家 / 省 / 市 / 区 / 详细地址，用户 2026-10-04 第 4 条）----
   一行排不下五个控件，用**自动换行的网格**：宽屏上「国家+省+市」一行、
   「区+详细」一行；窄屏自动每组一个。每个控件头上一行小标签（addr-k），
   比把「省 / 市」两个字塞进 placeholder 里好认 —— placeholder 一填字就没了。 */
.addr-grid {
  display: grid; gap: 8px 10px;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  margin-top: 2px;
}
.addr-cell { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.addr-cell-wide { grid-column: span 2; }
.addr-cell .addr-k { font-size: 12px; color: var(--faint); }
.addr-cell .input,
.addr-cell .select { width: 100%; }
@media (max-width: 560px) {
  .addr-cell-wide { grid-column: span 1; }
}

/* ==================== 旅程：行程状态机（v24 第 13 批）====================
   分三组：计划 / 实际 / 未成行。未成行用灰调（它"不算数"但要看得到）。 */
.trip-stage-card { margin-bottom: 18px; }
.stage-hint { margin: -6px 0 12px; font-size: 12.5px; color: var(--muted); }
.stage-skipped { opacity: .82; }
.stage-skipped .node-row { background: var(--line-2); }

.node-badge.nb-plan    { background: #EDE7DC; color: #6B5B45; }
.node-badge.nb-actual  { background: #DCEFE2; color: #2C6B45; }
.node-badge.nb-skipped { background: #EFEDEA; color: #6B6560; }
.rev-badge {
  display: inline-block; padding: 1px 7px; border-radius: 999px;
  background: #F1E6F5; color: #6B3F7A; font-size: 11.5px;
}
.actual-tag {
  display: inline-block; margin-left: 6px; padding: 1px 6px; border-radius: 999px;
  background: #DCEFE2; color: #2C6B45; font-size: 11px;
}

/* 修正面板（在行程行里就地展开） */
.rev-open > summary { cursor: pointer; list-style: none; }
.rev-open > summary::-webkit-details-marker { display: none; }
.rev-panel {
  margin-top: 10px; padding: 12px 14px;
  background: var(--surface-2, #FAF7F1);
  border: 1px solid var(--line); border-radius: 10px;
}
.rev-tip { margin: 0 0 10px; font-size: 12.5px; line-height: 1.7; color: var(--muted); }

/* 🔴🔴 v37：修正面板必须**浮**在按钮列之外，否则它只有 ~22px 宽、完全没法填。
   真机实测（1400px 视口、品心 1080px）：
     .dt-range 宽 **22px**、`.nf-grid` 宽 **22px** —— 整张修正表单被压成一条线。
     根因：`.node-ops` 是"一排小按钮"的容器
       `flex: 0 0 auto; justify-content: flex-end;`（见上面），
     而「修正」是 `<details class="rev-open">`：**展开的内容也长在这个按钮列里**
     ⇒ 列宽只有"修正"两个字那么宽（22px），里面的表单跟着一起被压扁。
   ⚠️ 这不只是"不好看"：日期控件在 22px 里根本点不到（用户报的就是这个）。
   修法：面板 `position: absolute` 脱离按钮列的宽度约束，浮到卡片上。
     · 宽度取 `min(720px, container 宽)` —— 720 刚好摆得下"两端各 304"（608+间距）。
     · `right: 0` 让它从按钮列右缘向左展开，不会超出卡片右边。
     · `z-index: 30` 盖在后面的行上面（它是临时展开的面板，本来就该在最上层）。
   ⚠️ 不动 templates/trips/show.php 的 DOM：
      `.node-list`/`.node-row` 的 flex 结构一动就会牵动"节点列表"的排版
      （那是清单页最核心的视觉），而这里只需要把它**浮出来**，纯 CSS 就够。 */
.rev-open { position: relative; }
.rev-panel {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
  width: min(720px, calc(100vw - 48px));
  max-height: min(76vh, 720px); overflow-y: auto;
  box-shadow: var(--shadow-l, 0 12px 32px rgba(0, 0, 0, .16));
}
.rev-form .btn-row { margin-top: 10px; }
/* 🔴 v50（用户 2026-10-14 第 3 条）：修正表单的按钮行**靠右**，并补一颗「取消」。
   ——「记录这次修正的按钮应该放在右边，另外应该加一个关闭/取消的按钮。」
   做法：`margin-left:auto` 作用在**按钮行里第一颗会被渲染的按钮**上，把整排推到右端。
   两类情况（有没有那句"已记过 N 次"的说明）各写一条 —— 只有第一颗儿子适合
   `margin-left:auto`（用 `:first-child` 精确命中），否则中间那些也会各带一个 auto、
   间距被拉散。
   ⚠️ 不用 `.cal-modal-foot .btn[type=submit]{margin-left:auto}` 那条（本表单不在模态里）。
   ⚠️ 窄屏允许换行，不然三样挤在一行会把按钮压变形。 */
.rev-form .rev-foot { align-items: center; flex-wrap: wrap; }
.rev-form .rev-foot > *:first-child { margin-left: auto; }

/* 修正历史对比表 */
.rev-hist { margin-top: 8px; }
.rev-hist > summary { cursor: pointer; font-size: 12.5px; }
.rev-table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 12.5px; }
.rev-table th, .rev-table td {
  padding: 5px 8px; border-bottom: 1px solid var(--line-2); text-align: left;
}
.rev-table th { color: var(--muted); font-weight: 500; }
.rev-table tr:first-child td { color: var(--ink); }   /* 原计划一行稍醒目 */

/* 行程表单里的"当前状态"只读展示 */
.node-stage-ro {
  padding: 9px 11px; border: 1px dashed var(--line); border-radius: var(--radius-s);
  background: var(--line-2); font-size: 13px;
}

/* ============================================================
   v24 第 14 批：读书模块（用户第 5 条）
   卡片骨架 / 统计图表一律复用现成的（.card.card-pad / .stats-grid /
   .stat-card / .chart-card / .bar-chart / .vgrid），这里只写读书**特有**的部件。
   图表仍然是纯 CSS：不引图表库，服务端画出来、没 JS 也看得见。
   ============================================================ */

/* ---- 书架：状态筛选条 ---- */
.book-filter { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.bf-btn {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 14px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface);
  font-size: 13px; color: var(--ink); text-decoration: none;
  transition: border-color .15s ease, background .15s ease;
}
.bf-btn:hover { border-color: var(--brand); text-decoration: none; }
.bf-btn.is-on { background: var(--brand); border-color: var(--brand); color: #fff; }
.bf-n { font-size: 11px; opacity: .72; font-variant-numeric: tabular-nums; }

/* ---- 书架：卡片网格 ----
   封面按 2:3（书的形状），宽度自适应；窄屏自动减列。 */
.book-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}
.book-card {
  display: flex; flex-direction: column; gap: 8px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px; box-shadow: var(--shadow);
}
/* ⚠️ 封面是 <a>（整块可点）：居中靠 flex，图自己铺满 —— 只写一次 display，
   写两遍后者会把前者悄悄改掉（这类"重复声明"最容易在改版时留成陷阱）。 */
.book-cover {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 2 / 3; border-radius: var(--radius-s);
  overflow: hidden; background: #F1E7D6;
}
.book-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.book-cover-empty { font-size: 32px; opacity: .5; }
.book-body { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.book-title {
  font-size: 14px; font-weight: 600; color: var(--ink); line-height: 1.45;
  text-decoration: none;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.book-title:hover { color: var(--brand); }
.book-author { font-size: 12px; color: var(--muted); }
.book-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; }
.book-rate { font-size: 11px; color: var(--brand-ink); }
.book-note-n { font-size: 11px; color: var(--muted); }
/* 读到第几章的细进度条 */
.bk-bar {
  height: 4px; border-radius: 999px; background: var(--line-2); overflow: hidden; margin: 2px 0;
}
.bk-bar i { display: block; height: 100%; background: var(--brand); }
.book-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: auto; }

/* ---- 一本书 ---- */
.book-hero { display: flex; gap: 20px; flex-wrap: wrap; }
.book-hero-cover {
  width: 150px; max-width: 46%; flex: 0 0 auto;
  aspect-ratio: 2 / 3; border-radius: var(--radius-s);
  overflow: hidden; background: #F1E7D6;
  display: flex; align-items: center; justify-content: center;
}
.book-hero-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.book-hero-body { flex: 1 1 300px; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.book-sub { margin: 0; font-size: 14px; color: var(--muted); }
.book-states { display: flex; flex-wrap: wrap; gap: 6px; }
.book-sum { margin-top: 4px; }
.book-toc-box { margin-top: 4px; }
.book-toc { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 3px; }
.book-toc li { font-size: 13.5px; display: flex; align-items: baseline; gap: 8px; }
.book-toc li.is-cur > a { color: var(--brand); font-weight: 600; }
.bk-file-now { margin: 0 0 6px; font-size: 13px; color: var(--muted); }

/* ---- 笔记 ---- */
.book-note-form { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.bn-row { display: flex; gap: 8px; flex-wrap: wrap; }
.bn-row .select { flex: 0 0 auto; width: auto; min-width: 96px; }
.bn-row .input { flex: 1 1 160px; min-width: 0; }
.book-notes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.book-note {
  border: 1px solid var(--line-2); border-radius: var(--radius-s);
  padding: 9px 12px; background: #FDFBF7;
}
.book-note.is-quote { border-left: 3px solid var(--brand-2); }
.book-note.is-review { border-left: 3px solid var(--green); }
.bn-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 5px; }
.bn-kind {
  font-size: 11px; padding: 1px 7px; border-radius: 999px;
  background: var(--line-2); color: var(--brand-ink);
}
.bn-ch, .bn-src, .bn-who { font-size: 11.5px; color: var(--muted); }
.bn-del { margin-left: auto; }
.bn-body { font-size: 14px; line-height: 1.75; color: var(--ink); white-space: normal; }

/* ---- 阅读器 ----
   目录在侧栏、正文在右边；窄屏侧栏折到上面（而不是横向挤成一条）。 */
.book-read { display: flex; gap: 18px; align-items: flex-start; }
.br-side {
  flex: 0 0 210px; max-height: 72vh; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px 14px;
}
.br-side-h { font-size: 13px; font-weight: 600; color: var(--muted); margin-bottom: 8px; }
.br-toc { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.br-toc li { font-size: 13px; }
.br-toc li a {
  display: block; padding: 4px 8px; border-radius: 6px; color: var(--ink); text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.br-toc li a:hover { background: var(--line-2); }
.br-toc li.is-cur a { background: rgba(168, 74, 40, .12); color: var(--brand-ink); font-weight: 600; }
.br-main {
  flex: 1 1 auto; min-width: 0;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 22px 26px; box-shadow: var(--shadow);
}
.br-ch-title { font-size: 19px; margin: 0 0 14px; }
.br-body { font-size: 16px; line-height: 1.95; color: var(--ink); }
.br-nav { display: flex; align-items: center; gap: 12px; margin-top: 22px; flex-wrap: wrap; }
.br-pos { margin-left: auto; }
.br-pdf object { width: 100%; height: 78vh; border: 0; display: block; }
@media (max-width: 860px) {
  .book-read { flex-direction: column; }
  .br-side { flex: 1 1 auto; width: 100%; max-height: 260px; }
  .br-main { padding: 16px 18px; }
  .br-body { font-size: 15.5px; line-height: 1.9; }
}

/* ---- 封面裁剪（书的封面是 2:3，与旅程题图的 16:9 不同）----
   与 .cover-picker 同一套结构，只是预览框换成竖的。 */
.book-cover-picker #book-cover-preview {
  width: 150px; aspect-ratio: 2 / 3;
}

/* ---- 导入读书笔记 ---- */
.imp-how { margin: 0 0 10px; padding-left: 20px; display: flex; flex-direction: column; gap: 5px; }
.imp-how li { font-size: 13.5px; line-height: 1.7; }
.imp-list { list-style: none; margin: 0 0 14px; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.imp-book {
  border: 1px solid var(--line-2); border-radius: var(--radius-s);
  padding: 10px 12px; background: #FDFBF7;
}
.imp-bh { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; }
.imp-t { font-size: 14.5px; font-weight: 600; }
.imp-n { margin-left: auto; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.imp-notes { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.imp-notes li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; font-size: 13px; }
.imp-notes li.is-quote .imp-txt { color: var(--muted); }
.imp-txt { flex: 1 1 240px; min-width: 0; line-height: 1.6; }
.imp-confirm { margin-top: 6px; }

/* ---- 柱状图：列数不定的（每年的本数 / 评分档位）----
   观影那套 .vgrid 是写死 12 列的（12 个月），这里列数随数据变。 */
.vgrid-auto { grid-template-columns: repeat(auto-fit, minmax(26px, 1fr)); }

/* ============================================================
   v24 第 15 批：活动（容器）/ 旅程（行程）两级语义
   术语改了（用户第 1 条），活动表单砍到三项（用户第 4 条）。
   ============================================================ */

/* ---- 活动表单里的"说明一句"（时间/城市从哪来）----
   用左边一条竖线而不是整块底色：它是**解释**、不是输入项，
   染成和输入区一样的底色会让人以为这里要填东西。 */
.form-note {
  margin: 0 0 14px; padding: 8px 0 8px 12px;
  border-left: 3px solid var(--line);
  font-size: 13px; line-height: 1.75; color: var(--muted);
}
.form-note b { color: var(--ink); font-weight: 600; }

/* ---- 「更多设置」折叠区：主流程只留三项，其余收在这里 ---- */
.more-set {
  margin: 4px 0 14px; border: 1px solid var(--line);
  border-radius: var(--radius-s); background: #FDFBF7;
}
.more-set > summary {
  cursor: pointer; padding: 10px 14px;
  font-size: 13.5px; color: var(--muted); font-weight: 500;
}
.more-set > summary:hover { color: var(--brand); }
.more-set[open] > summary { border-bottom: 1px solid var(--line-2); }
.more-set .form-grid { padding: 14px; margin: 0; }
/* 折叠区里的字段少一点内边距，看起来是"附带的"而不是主流程 */
.more-set .field { margin-bottom: 0; }

/* ============================================================
   v24 第 16 批：旅程（容器）/ 行程（单元）两级
   术语固定：旅程包含行程（用户第 9 条）。
   新增：题图 16:9、五项统计、行程独立页、设置三页、花销。
   ============================================================ */

/* ---- 题图右侧的信息区：时间 + 城市 ---- */
.trip-when {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 12px;
  font-size: 14px; color: var(--muted);
}
.trip-when-date { font-weight: 600; color: var(--ink); }
.trip-when-cities { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 5px; }
.city-chip {
  padding: 1px 9px; border-radius: 999px;
  background: var(--line-2); color: var(--brand-ink); font-size: 12.5px;
}
.city-sep { color: var(--faint); }

/* ---- 五项统计（用户第 2 条）---- */
.trip-stats {
  display: flex; flex-wrap: wrap; gap: 10px 26px;
  padding: 10px 0; border-top: 1px solid var(--line-2); border-bottom: 1px solid var(--line-2);
}
.ts-item { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; }
.ts-n {
  font-size: 19px; font-weight: 700; color: var(--brand);
  font-variant-numeric: tabular-nums; line-height: 1.2;
}
.ts-l { font-size: 12px; color: var(--muted); }
.trip-sum { margin: 0; }

/* ---- 行程行上的新元素：出行方式 / 交通细节 / 起终点 / 花销 / 角色 ---- */
.node-mode { font-size: 12px; color: var(--muted); white-space: nowrap; }
.node-traffic { display: flex; flex-wrap: wrap; gap: 5px; }
.node-traffic .ntag {
  font-size: 11.5px; padding: 1px 8px; border-radius: 5px;
  background: var(--line-2); color: var(--muted);
}
/* 🔴 2026-11-05 重做（用户：「这个位都没对齐呀…直接用个标签，然后右边一个箭头，
   到北京南站就得了，不用写从哪到哪」）。

   原来只是一个普通 div，里面的 <a> 与 <span class="arrow"> 各按自己的基线排
   ⇒ 字号不同就"没对齐"。

   现在：容器 flex + align-items:center，两个地名各自成胶囊标签，
   箭头只作窄分隔。视觉上就是「[青岛北站] → [北京南站]」，
   不再需要"从/到"这种字。 */
.node-route-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 12.5px;
  color: var(--muted);
  line-height: 1;
  margin-top: 2px;
}

/* 地名标签：浅底圆角，长名字省略 */
.node-route-line > a,
.node-route-line > span.route-stop {
  display: inline-flex;
  align-items: center;
  max-width: 46%;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--fill, #F1EAE1);
  color: var(--ink, #2C231D);
  font-size: 12px;
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.node-route-line > a:hover {
  background: var(--brand-soft, #F5E3DC);
}

/* 箭头：窄、淡，只做分隔 */
.node-route-line .arrow {
  color: var(--faint);
  margin: 0 1px;
  font-size: 12px;
  line-height: 1;
  flex: none;
}
.node-spent { font-size: 12.5px; color: var(--brand-ink); }
.node-member { display: inline-flex; align-items: center; gap: 4px; }
.node-role {
  font-size: 11px; padding: 0 6px; border-radius: 999px;
  background: rgba(168, 74, 40, .12); color: var(--brand-ink);
}

/* ---- 行程表单：参与人角色 ---- */
.role-set { margin-top: 6px; }
.role-grid { display: grid; gap: 6px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); margin-top: 8px; }
.role-row { display: flex; align-items: center; gap: 8px; }
.role-who { flex: 0 0 72px; font-size: 13px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.role-row .input { flex: 1 1 auto; min-width: 0; padding: 6px 9px; font-size: 13px; }

/* ---- 花销 ---- */
.exp-list { list-style: none; margin: 0 0 12px; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.exp-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 7px 11px; border: 1px solid var(--line-2); border-radius: var(--radius-s);
  background: #FDFBF7; font-size: 13.5px;
}
.exp-cat {
  font-size: 11.5px; padding: 1px 8px; border-radius: 999px;
  background: var(--line-2); color: var(--brand-ink);
}
.exp-title { flex: 1 1 140px; min-width: 0; }
.exp-amt { font-weight: 600; font-variant-numeric: tabular-nums; }
.exp-total { margin: 0 0 12px; font-size: 14px; }
.exp-form-row { display: flex; flex-wrap: wrap; gap: 8px; }
.exp-form-row .input { flex: 1 1 130px; min-width: 0; }
.exp-form-row .select { flex: 0 0 auto; width: auto; min-width: 96px; }

/* ---- 设置三页：车辆 / 出行方式 / 节点类型 ---- */
.set-sub { font-size: 14px; font-weight: 600; margin: 16px 0 8px; }
.veh-list { list-style: none; margin: 0 0 10px; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.veh-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 11px; border: 1px solid var(--line-2); border-radius: var(--radius-s);
  background: #FDFBF7;
}
.veh-ro { background: var(--line-2); }
.veh-ico { font-size: 17px; }
.veh-name { font-size: 14px; font-weight: 600; }
.veh-tag { font-size: 11.5px; padding: 1px 8px; border-radius: 999px; background: var(--surface); color: var(--muted); border: 1px solid var(--line); }
.veh-form { flex: 1 1 auto; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; min-width: 0; }
.veh-form .input { flex: 1 1 110px; min-width: 0; padding: 6px 9px; font-size: 13px; }
.veh-form .select { flex: 0 0 auto; width: auto; padding: 6px 9px; font-size: 13px; }
.veh-def { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; color: var(--muted); white-space: nowrap; }
.veh-add { margin-top: 4px; }

.mode-grid { display: flex; flex-direction: column; gap: 8px; }
.mode-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 11px; border: 1px solid var(--line-2); border-radius: var(--radius-s);
  background: #FDFBF7;
}
.mode-ico { font-size: 17px; }
.mode-name { flex: 0 0 64px; font-size: 14px; }
.mode-row .input { flex: 1 1 220px; min-width: 0; padding: 6px 9px; font-size: 13px; }
.mode-none { flex: 1 1 auto; }

.type-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.type-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 11px; border: 1px solid var(--line-2); border-radius: var(--radius-s);
  background: #FDFBF7;
}
.type-form { flex: 1 1 auto; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; min-width: 0; }
.type-form .input { flex: 1 1 140px; min-width: 0; padding: 6px 9px; font-size: 13px; }
.type-ico { flex: 0 0 52px !important; text-align: center; font-size: 16px !important; }
.type-tag { font-size: 11.5px; padding: 1px 8px; border-radius: 999px; background: var(--surface); color: var(--muted); border: 1px solid var(--line); }
.type-tag-custom { background: rgba(168, 74, 40, .1); color: var(--brand-ink); border-color: rgba(168, 74, 40, .25); }
.type-used { flex: 0 0 auto; }
.type-add { margin-top: 4px; }

/* ---- 窄屏 ---- */
@media (max-width: 860px) {
  /* 题图在窄屏铺满一行（信息区折到下面）—— 16:9 的比例保持不变 */
  .trip-hero-cover { width: 100%; }
  .trip-stats { gap: 8px 18px; }
  .ts-n { font-size: 17px; }
}

/* ============================================================
   v24 第 17 批：城市的聚合 / 高德地址 / 表单精简
   用户 10 条（城市聚合、类型照 TripLog、只有自驾选车、飞机铁路只记车次、
   高德地址、弹框选人、去角色标注、图片右侧显示聚合城市、时间到时分秒）。
   ============================================================ */

/* ---- 时间：精确到时分的那一行（比原来只到日期的那行略宽）---- */
.node-time { font-variant-numeric: tabular-nums; }

/* ---- 出发地 → 到达地：带导航链接 ---- */
.node-route-line a {
  color: var(--brand-ink); text-decoration: none;
  border-bottom: 1px dashed rgba(168, 74, 40, .35);
}
.node-route-line a:hover { border-bottom-style: solid; }

/* ---- 地址输入（高德联想）----
   可见文本框 + 隐藏的 addr/lng/lat/city；下面一行显示"详细地址"与导航链接。 */
.loc-field { display: flex; flex-direction: column; gap: 4px; }
.loc-label { font-size: 13px; color: var(--muted); display: inline-flex; align-items: center; gap: 5px; }
.loc-ico { font-size: 14px; }
.loc-input { width: 100%; }
.loc-extra { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; min-height: 18px; }
.loc-detail {
  font-size: 12px; color: var(--muted); flex: 1 1 140px; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.loc-detail:empty { display: none; }
.loc-nav {
  flex: 0 0 auto; font-size: 12px; color: var(--brand-ink);
  text-decoration: none; white-space: nowrap;
}
.loc-nav:hover { text-decoration: underline; }
.loc-nav[hidden] { display: none; }

/* ---- 修正表单：里面的字段少，用同一套 .field 网格 ---- */
.rev-form .field { margin-bottom: 0; }

/* ---- 窄屏：地址那两行不换行挤成一团 ---- */
@media (max-width: 620px) {
  .loc-detail { flex: 1 1 100%; }
}

/* ============================================================
   v24 第 18 批：选人弹框改「暂存 + 确定」两段式（用户第 18 条）
   上半部分是暂存区（点了头像/名字但还没确定的人），下面是候选清单，
   最下面「取消 / 确定」。⚠️ 只有多选（.pp-mode=multi）才渲染这两块。
   ============================================================ */

/* ---- 暂存区：在搜索框**下面**、候选清单**上面** ----
   ⚠️ 用户说的是"选择框的上半部分"。搜索框在最顶（要一直能打字搜下一个人），
      所以暂存区放在搜索框之下、清单之上 —— 视觉上仍然属于"上半部分"，
      而且不会因为暂存区变高把搜索框挤走（搜索是要反复用的）。
   ⚠️ v24 第 19 批 用户第 8 条：「暂存区太小了，高度太低，看不见人」——
      原来只有 148px 且一行能塞很多 chip，看着像一条空横条。
      现在：① 高度提到 min 132 / max 46vh（跟着弹框走，不再写死一个矮数）；
            ② chip 改成**大一点**（头像 28px、字号 13.5），并允许每行少放几个，
               让人**看得见是谁**；③ 有内容时才占位（没人暂存时不显示，见 JS）。 */
.pp-stage {
  border: 1px solid var(--line); border-radius: 8px;
  background: #FDFBF7; padding: 10px 12px; margin-bottom: 8px;
  min-height: 132px; max-height: 46vh; overflow-y: auto;
  display: flex; flex-direction: column;
}
.pp-stage[hidden] { display: none; }
.pp-stage-label {
  display: block; font-size: 12px; color: var(--muted); margin-bottom: 8px;
  flex: 0 0 auto;
}
.pp-stage-label b { color: var(--brand); font-variant-numeric: tabular-nums; }
.pp-stage-chips {
  display: flex; flex-wrap: wrap; gap: 8px; align-content: flex-start;
  flex: 1 1 auto;
}

/* ---- 底部按钮：取消在左、确定在右（主操作靠右，与全站一致）---- */
.pp-actions {
  display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px;
  padding-top: 10px; border-top: 1px solid var(--line-2);
}

/* ---- 暂存区里的 chip 比外面的紧凑一点（弹窗里空间有限）---- */
.pp-stage-chips .pp-chip { font-size: 13px; padding: 3px 8px 3px 4px; }
.pp-stage-chips .pp-chip img { width: 28px; height: 28px; }

/* ---- 窄屏：弹窗几乎占满，暂存区别太高（把清单挤没了就没法选了）---- */
@media (max-width: 480px) {
  .pp-modal-box { max-height: 88vh; }
  .pp-stage { min-height: 108px; max-height: 38vh; }
}

/* ============================================================
   v24 第 20 批 用户第 9 条：**全站按钮悬停统一**
   ------------------------------------------------------------
   用户原话：「很多按钮的悬停效果，非常奇怪，感觉纯粹是换了一个背景色，
              这个背景色，跟这个模块完全不协调，应该调整所有按钮的悬停效果，
              保持统一，但是不要单纯的换一个不协调的背景色。」
   ------------------------------------------------------------
   ⚠️ 改之前的问题（复现探针量出来 **15 种**互不相干的 hover 背景色）：
     #944022 / #FAF7F1 / #FBF7F1 / #F7EDE4 / #FDF1EE / #F7E4DF / #D79230 /
     #518B43 / #AC4835 / var(--line-2) / var(--surface-2,#FDFBF7) / rgba(192,83,63,.08)
     —— 同一行里「取消」和「保存」悬停变的是**两套不相干的色**；
        `.seg-btn` 那种更是直接糊一层 `#FDFBF7`，在米色底上看着像"脏了一块"。
     而且全是"只换背景"这一种表达 —— 这正是用户说的"纯粹是换了一个背景色"。

   统一后的**一套手法**（所有按钮共用同一套手感，底色只跟着自己的色系走）：
     ① **同色系加深**：有底色的一律 `filter: brightness(.92)` ——
        深色按钮（品牌红 / 黄 / 绿 / 红）自然变深一点，**不再跳到另一个色**；
     ② **描边留白按钮**（.btn-ghost / .btn-danger / .btn-icon 及各处白底按钮）：
        统一用**中性薄雾** `rgba(44,35,29,.05)` —— 落在宣纸米底上只是"轻轻按了一下"，
        不会再出现 #F7EDE4 那种偏色的脏块；
     ③ **边框微显 + 极轻抬升**：`border-color: rgba(44,35,29,.16)` + `translateY(-1px)`
        + 一层很浅的投影 —— 给的是"这个能按"的触感，而不是换色；
     ④ 统一 `transition`（背景 / 边框 / 颜色 / 投影 / 位移一起过渡），避免"啪一下变色"。

   ⚠️ 实现上放在**文件最靠后**并带 `!important` 收口：
      历史上有十几处散落的 hover（.btn:hover、.page-head 里那几层、.seg-btn:hover…）
      优先级各不相同，逐个改既容易漏、又会被后加的规则盖掉。
      收口之后**新加的按钮自动继承同一套手感**，作者不必再记一次。
   ⚠️ 深色浮层上的按钮（.lb-btn 在黑色遮罩上）不适用中性薄雾（深底上看不见），
      由浮层自己的规则覆盖；.pv-nav / .pv-dl 同理，见各自定义处。
   ============================================================ */
:root {
  /* 悬停手感三件套（改这里 = 全站按钮悬停一起变） */
  --hov-lift: -1px;
  --hov-ring: rgba(44, 35, 29, .16);
  --hov-veil: rgba(44, 35, 29, .05);
  --hov-dim:  brightness(.92);
  --hov-t:    background-color .15s ease, border-color .15s ease,
              color .15s ease, box-shadow .15s ease, transform .15s ease;
}

.btn, .btn-ghost, .btn-danger, .btn-warn, .btn-ok, .btn-del, .btn-sm, .btn-icon,
button.btn, a.btn, summary.btn, .seg-btn, .icon-btn, .rv-act-btn, .cs-btn, .bf-btn {
  transition: var(--hov-t);
}

/* ① 有底色的按钮：同色系加深（不换色）+ 轻抬升
   ⚠️ 这里**只加 filter，绝不能写 `background-color: inherit`** ——
      `inherit` 取的是**父元素**的背景（多半是透明），会把实心按钮变透明
      （实测：黄/绿按钮悬停后变成 alpha 0.008 的鬼影）。
      不改底色就"不写底色"，让按钮自己的 background 留着。 */
.btn:not(.btn-ghost):not(.btn-danger):not(.btn-icon):not(.btn-warn):not(.btn-ok):not(.btn-del):hover,
.btn-warn:hover, .btn-ok:hover, .btn-del:hover,
.search-bar button:hover {
  filter: var(--hov-dim);
  transform: translateY(var(--hov-lift));
  box-shadow: var(--shadow-l);
}

/* ② 描边留白按钮：中性薄雾 + 边框微显 + 轻抬升（不再各自糊一个偏色底） */
.btn-ghost:hover, .btn-icon:hover, .seg-btn:hover, .icon-btn:hover,
.who-menu > summary:hover,
.page-head .actions > .btn-ghost:not(.btn-icon):hover,
.page-head .actions > a.btn-ghost:not(.btn-icon):hover,
.page-head .actions > form > .btn-ghost:not(.btn-icon):hover,
.page-head .actions > form > button.btn-ghost:not(.btn-icon):hover {
  background: var(--hov-veil) !important;
  border-color: var(--hov-ring) !important;
  transform: translateY(var(--hov-lift));
  box-shadow: var(--shadow);
}
.page-head .actions > .movie-set > summary.btn-icon:hover,
.page-head .actions > .movie-set > summary.btn:hover {
  background: var(--hov-veil) !important;
  border-color: var(--hov-ring) !important;
}

/* ③ 危险 / 删除类：同一套手法，薄雾里带一点红（语义不丢，但不刺眼） */
.btn-danger:hover, .rv-del-btn:hover, .btn.danger:hover,
.page-head .actions > .btn-danger:not(.btn-icon):hover,
.page-head .actions > a.btn-danger:not(.btn-icon):hover,
.page-head .actions > form > .btn-danger:not(.btn-icon):hover,
.page-head .actions > form > button.btn-danger:not(.btn-icon):hover {
  background: rgba(192, 83, 63, .09) !important;
  border-color: rgba(192, 83, 63, .30) !important;
  color: var(--red) !important;
  transform: translateY(var(--hov-lift));
  box-shadow: var(--shadow);
}
/* ⚠️ .btn-del 是**实心红**（第 19 批 行程行那种）—— 上面那条 ③ 会把它拉成淡红，
      所以实心那一类要单独拉回来：不改底色（保住 #C0533F），只走"同色系加深"。 */
.btn-del:hover {
  background: #C0533F !important;
  color: #fff !important;
  border-color: transparent !important;
}

/* ④ 纯图标 / 文字小按钮：不占背景，只做"轻抬升 + 轻微加深" */
.person-more-btn:hover, .rv-act-btn:hover, .bf-btn:hover, .cs-btn:hover,
.more-set > summary:hover, .rv-open-btn:hover {
  filter: var(--hov-dim);
  transform: translateY(var(--hov-lift));
  text-decoration: none;
}

/* ⑤ 浮在深色遮罩上的按钮：深底上中性薄雾看不见 → 用"提亮"代替，
      但**同一套手法**（变亮 + 轻抬升），保持全局手感一致。 */
.lb-btn:hover, .pv-close:hover, .crop-x:hover {
  filter: brightness(1.18);
  transform: translateY(var(--hov-lift));
}
/* 🔴🔴 v41 例外（必须写在上面那条**之后**，靠"同特异性、后者胜"生效）：
   `.lb-nav`（照片左右切换）是**靠 transform 居中**的，一旦被上面的
   `translateY(var(--hov-lift))` 覆盖，hover 时按钮会从"居中"跳到"距顶 50%"，
   位移近半个按钮高 —— 用户报的就是这个（"悬停之后位置会变一下，一不小心就会点错"）。
   ⇒ 这里把它的居中 transform 显式写回，只保留提亮。位置前后一字不动。 */
.lb-nav:hover { transform: translateY(-50%); }

/* v24 第 20 批 第 3 条：`form="id"` 关联用的**表单外壳**。
   它们只承载字段、不出现在视觉里（按钮在原来的位置上，用 form= 指过来）。
   ⚠️ 必须 display:none —— 不藏的话页面底部会多出几块空的表单块。 */
.form-outside { display: none; }

/* ============================================================
   v24 第 20 批 第 5 条：添加行程的三处版面调整
   ============================================================ */

/* ---- ① 出发地 / 到达地**同一行**（用户原话："应该在同一行"）----
   ⚠️ 必须显式开一个"一行两列"的容器，不能靠两个 .field 相邻 ——
      .form-grid 是两列网格，每个 .field 各占一格；窄屏折行后顺序还会乱。 */
.loc-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 560px) {
  /* 窄屏放不下并排两个地址框（中文地名 + 高德联想候选），改成上下 */
  .loc-row { grid-template-columns: 1fr; }
}

/* ---- ② 「车辆」只在出行方式 = 自驾 时出现（第 17 批的**同一句原话**）----
   🔴 v24 第 20 批 第 5 条修的是这里：用户又报了一次「车辆，只在出行方式选择自驾时
      才出现，其他情况下，默认不出现」—— 说明**上一批的隐藏根本没生效**。
      根因：模板里写的是 `<div class="field" data-mode-vehicle hidden>`，
      而 `.field` 自己声明了 `display:flex` —— **作者样式优先于浏览器默认的
      `[hidden]{display:none}`**，所以 `hidden` 形同虚设、车辆永远显示着。
      （与 .pp-item / .pv 那几处是**同一个坑**，项目里已经栽过三次。）
      修法：给自己声明过 display 的元素补一条 `[hidden]` 规则。 */
.field[hidden],
.field.full[hidden],
[data-mode-vehicle][hidden],
[data-mode-ticket][hidden] { display: none !important; }

/* 🔴 v44（用户 2026-10-12 第 2 条）：重复那三行（结束条件 / 重复次数 / 重复到）
   在观影式排版下是 `.mv-row`（`display:flex`）⇒ 同上面 `.field` 那个坑：
   **作者样式优先于 `[hidden]` 的浏览器默认 `display:none`**，`hidden` 形同虚设。
   ⇒ 一并补上，否则"不重复时它们也显示着"。 */
.mv-row[hidden] { display: none !important; }

/* ============================================================
   v24 第 20 批 第 4 条：拖动即时上传的**暂存区** UI
   ------------------------------------------------------------
   `.stage-drop` 是那个"可以往里拖"的落点；`[data-stage-list]` 装卡片。
   卡片三态：.is-uploading（转圈感）/ 默认（缩略图）/ .is-failed（标红）。
   ============================================================ */
.stage-drop {
  border: 1px dashed var(--line); border-radius: var(--radius-s);
  background: #FDFBF7; padding: 10px 12px; margin-top: 6px;
}
.stage-drop.is-over { outline: 2px dashed var(--brand); outline-offset: 3px; background: #FDF3EC; }
.stage-drop-tip { display: block; font-size: 12px; color: var(--muted); margin-bottom: 8px; }
.stage-list { display: flex; flex-wrap: wrap; gap: 8px; }
/* ⚠️ .stage-list 自己声明了 display:flex → 要藏就得补 [hidden]（本项目老坑） */
.stage-list[hidden] { display: none; }
.stage-cell {
  position: relative; width: 84px; padding: 6px 4px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--surface);
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  font-size: 11px; text-align: center; box-sizing: border-box;
}
.stage-cell img { width: 72px; height: 54px; object-fit: cover; border-radius: 5px; display: block; }
.stage-cell .stage-ico {
  width: 72px; height: 54px; border-radius: 5px; background: #5F6672; color: #fff;
  display: flex; align-items: center; justify-content: center; font-weight: 700;
}
.stage-cell .stage-name {
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink);
}
.stage-cell .stage-meta { color: var(--muted); }
.stage-cell.is-uploading { opacity: .7; }
.stage-cell.is-uploading .stage-meta { color: var(--brand); }
.stage-cell.is-failed { border-color: var(--red); background: #FDF1EE; }
.stage-cell.is-failed .stage-meta { color: var(--red); }
.stage-cell .stage-x {
  position: absolute; top: -6px; right: -6px; width: 20px; height: 20px;
  border-radius: 50%; border: 1px solid var(--line); background: #fff; color: var(--muted);
  font-size: 13px; line-height: 1; cursor: pointer; padding: 0;
}
.stage-cell .stage-x:hover { color: #fff; background: var(--red); border-color: var(--red); }

/* ============================================================
   v24 第 20 批 第 6 条：周 / 日视图的「当前时间红线」
   ------------------------------------------------------------
   用户原话：「应该在视图中间显示一条红线，用来表示当前进行的时间，
              用一个不断从上到下的红线，来表示当前时间在视图上的刻度和位置。」
   ⚠️ 用**绝对定位**（top 由服务端/JS 给百分比）—— 与 .tg-block 同一套坐标，
      所以线正好落在时间刻度的位置上，不会与日程块错开。
   ⚠️ 只在**今天那一列**出现（`.is-today`），由模板决定渲染不渲染。
   ⚠️ `pointer-events: none`：这条线是"刻度提示"，不该挡住它下面的日程点击
      （用户点线附近想点的是日程，不是这条线）。
   ============================================================ */
.tg-now {
  position: absolute; left: 0; right: 0; height: 0;
  border-top: 2px solid #E0453B;   /* 红线（用户点名"红线"） */
  z-index: 4;                      /* 在日程块（z-index 2）之上，要看得见 */
  pointer-events: none;
}
/* 🔴 v82（用户 2026-10-23）：「在日程模块中，那个红线左侧是不是还有一个红点？
   如果有，去掉。」—— 确实有，就是这条 `::before`。
   ⚠️ 首页日程那条红线 v44 已经删过一次红点（`.ag-now-dot`），用户当时说
      「红点显示也不全，也不太好看。直接把红点去掉就行了，直接一根红线就可以了。」
      ⇒ 两处口径统一：**只有一根线，没有任何端点装饰**。
   ⚠️ 这条规则整条删掉，别只把 `content` 清空 —— 留个空规则在这儿，
      将来一定有人照着它把圆点"补回来"。 */
/* 过午夜（或跨天翻页）时线要能"走出去"，不要卡在格子边界上闪 */
.tg-col { overflow: hidden; }

/* ============================================================
   v24 第 20 批 第 7 条：重复日程删除的**范围选择**
   （只删这一次 / 这一次及以后 / 全部）
   ⚠️ 三档要**竖着列**并带说明文字：这是不可逆操作，横排挤成一行会看不清后果。
   ============================================================ */
.ev-del-scope { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.ev-del-head { margin: 0; font-size: 14px; color: var(--ink); }
.ev-del-picks { display: flex; flex-direction: column; gap: 6px; }
.ev-del-picks .chk-inline { align-items: flex-start; }

/* v24 第 20 批 第 8 条：提醒多选的排版（勾选框多了要能换行，别挤成一长条） */
.rem-picks { display: flex; flex-wrap: wrap; gap: 6px 14px; }
.rem-pick { font-size: 13px; }

/* ============================================================
   v24 第 21 批 第 2 条：观影编辑页的**已存图片**要缩成缩略图
   ------------------------------------------------------------
   用户原话：「观影模块，海报和票根以及照片都带进来了，但是没有缩放，
              把页面撑破了，应该缩放成缩略图。」

   🔴 根因：`.mv-slot` 这个类**在样式表里根本不存在**（第 20 批加模板时只写了
      结构没写样式）。于是里面的 <img> 按**原始像素**渲染 —— 海报是 2:3 竖图
      （上传时按 600×900 裁的），就在一个 236px 宽的格子里撑出
      600×900 + 942px 高，把表单顶破、还让整页横向溢出（实测 docW 1297 > vw 1280）。
      「带进来了」这个功能是对的，缺的是"把它当成缩略图看"这一层样式。

   现在的规格：左侧固定尺寸缩略图（海报 2:3 → 108×162；票根按同一高度裁切），
   右侧「删除这张」按钮，整体是"看得见、不占地方"的一张缩略图。
   ⚠️ `object-fit: cover` 是必须的：票根那种横长条塞进竖框里，
      不用它就会被压扁变形（看着像图坏了）。
   ⚠️ 缩略图高度**写死**、不能再由图片自身比例决定 —— 那正是撑破的原因。
   ============================================================ */
.mv-slot {
  display: flex; align-items: flex-start; gap: 10px;
  margin-bottom: 10px;
}
.mv-slot > a {
  display: block; flex: 0 0 auto;
  width: 108px; height: 162px; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--radius-s);
  background: #F1E7D6;
}
.mv-slot > a img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.mv-slot > .btn { flex: 0 0 auto; align-self: center; }
/* 窄屏：缩略图再小一点，别把"删除这张"挤出去 */
@media (max-width: 480px) {
  .mv-slot > a { width: 84px; height: 126px; }
}

/* ============================================================
   v24 第 21 批 第 7 条：「点击或拖动进行上传」的那个框
   ------------------------------------------------------------
   用户原话：「所有上传图片等附件的地方，都不再放按钮，而是放一个框，
              点击这个框实现点击上传按钮的功能，拖动图片进这个框里，
              实现拖动多个上传功能。框里面写一句话：点击或拖动进行上传，
              其他的功能和现在保持一致。」

   ⚠️ 这是**全新的统一上传入口**，取代了原来的 `<input class="input" type=file>`
      （那个原生控件长得像输入框、写着"选择文件 未选择任何文件"，
       与旁边那个拖放虚线框是**两个**入口 —— 用户看到的就是"到底点哪个"）。
      现在原生框整个 `.sr-only` 藏起来，页面上只有这**一个**框。

   ⚠️ 落点高亮 `.is-over` 由 app.js 的 bindZone 加（拖动经过时）——
      与既有的 `.dropzone.is-over` 是同一套动作，只是这个框自己有底色，
      高亮用"描边+底色变亮"表达（不用 outline，避免与自带虚线边重叠成双线）。
   ============================================================ */
.stage-drop-tip { display: none; }   /* 旧的提示位：文案已并进框里，这里留个空占位规则 */

.upbox {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; width: 100%; min-height: 96px; box-sizing: border-box;
  padding: 16px 14px; cursor: pointer; text-align: center;
  border: 1.5px dashed var(--line); border-radius: var(--radius-s);
  background: #FDFBF7; color: var(--muted);
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.upbox:hover { border-color: var(--brand-2); background: #FBF6F0; color: var(--brand); }
.upbox:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
/* 拖动经过时的高亮（app.js 的 bindZone 加 .is-over） */
.upbox.is-over {
  border-color: var(--brand); border-style: solid;
  background: #FDF3EC; color: var(--brand);
}
.upbox-ico { font-size: 22px; line-height: 1; font-weight: 300; }
.upbox-msg { font-size: 13.5px; }
/* 🔴 v30 第 1 条：「选中文件后，那个文件输入框，得有点反应才对。」
   非暂存框（导入型）选中文件后由 app.js 的 bindFileEcho() 加 `.is-picked`
   并把框里的话换成文件名 —— 这里把它从"虚线待放"变成"实线已选"，
   配色也换成品牌色的浅底，一眼能看出"这一步已经做完了"。
   ⚠️ 与 `.is-over`（拖动经过的高亮）区分开：那个是**临时**高亮，
      这个是**已选**的稳定态，两者同时命中时以 is-picked 为准（写在后面）。 */
.upbox.is-picked {
  border-style: solid; border-color: var(--brand);
  background: #FDF3EC; color: var(--brand-ink, var(--brand));
}
.upbox.is-picked .upbox-ico { font-size: 18px; font-weight: 600; }
.upbox.is-picked .upbox-msg { font-weight: 600; word-break: break-all; }
/* 窄屏：框再矮一点，别把表单撑太长 */
@media (max-width: 480px) {
  .upbox { min-height: 78px; padding: 12px; }
  .upbox-ico { font-size: 18px; }
}

/* 空的提示行（暂存清单下面那句"已上传/失败"）：没内容时不占地方 */
.stage-drop-tip:not([hidden]) { display: block; margin-top: 6px; font-size: 12px; color: var(--muted); }

/* ============================================================
   v24 第 21 批 第 7 条：新建日程的**弹框**
   ------------------------------------------------------------
   用户原话：「日历中新建日程，无论是点击按钮还是点击日历中间，都是采用弹框的形式
              进行添加，这样不影响当前日历的显示，添加完成后，动态修改当前视图，
              不要刷新。」
   ⚠️ 原来那张表单是**内联**在日历网格上方的卡片，一展开就把月视图推下去 ——
      正是"影响当前日历的显示"。改成浮层后，底下的日历**一动不动**。
   ⚠️ 视觉沿用站内既有的浮层语言（遮罩 rgba(44,35,29,.55) + 圆角面板 + 阴影），
      与选人弹窗 / 名册动作框 / 裁剪器是同一套，用户不用重新认。
   ============================================================ */
.cal-modal {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: flex-start; justify-content: center;
  padding: 24px 16px; overflow-y: auto;
  background: rgba(44, 35, 29, .55); overscroll-behavior: contain;
}
.cal-modal[hidden] { display: none; }
/* 🔴 v37：`max-height` 必须把 `.cal-modal`（本面板的父层）自己的 `padding: 24px`
   算进去 —— 只减 48 会让面板底部**刚好探出视口 4px**，
   于是 `.cal-modal-foot` 里那两个按钮有半截在屏幕外（用户"看不到按钮"的另一半原因）。
   ⇒ 减 **64px** = 上下各 24px 的遮罩内边距 + 16px 余量。 */
.cal-modal-panel {
  width: min(680px, 100%); margin: auto;
  background: var(--surface); border-radius: 14px;
  box-shadow: var(--shadow-l); display: flex; flex-direction: column;
  max-height: calc(100vh - 64px);
}
.cal-modal-head {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px 12px; border-bottom: 1px solid var(--line-2);
  flex: 0 0 auto;
}
.cal-modal-head b { font-size: 15.5px; font-weight: 600; }
.cal-modal-x {
  margin-left: auto; border: 0; background: transparent; cursor: pointer;
  font-size: 15px; line-height: 1; color: var(--muted);
  padding: 4px 7px; border-radius: 6px;
}
.cal-modal-x:hover { background: var(--hov-veil); color: var(--ink); }
.cal-modal-body { padding: 16px; overflow-y: auto; flex: 1 1 auto; }
.cal-modal-foot {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px 14px; border-top: 1px solid var(--line-2);
  flex: 0 0 auto;
}
/* 提交键贴右（次要动作在左、主要动作在右，与全站一致） */
.cal-modal-foot .btn[type="submit"] { margin-left: auto; }
.cal-modal-tip { font-size: 12.5px; color: var(--muted); }
.cal-modal-tip.err { color: var(--red); }

/* 日历本：色块 + 下拉（原生 option 画不出色块，见模板里的说明） */
.cal-book-row { display: flex; align-items: center; gap: 8px; }
.cal-book-dot {
  flex: 0 0 auto; width: 20px; height: 20px; border-radius: 6px;
  border: 1px solid rgba(44, 35, 29, .12);
}
.cal-book-row .select { flex: 1 1 auto; }

/* ============================================================
   v24 第 21 批 第 7 条：日程新建弹框的显隐（**必须放收尾**）
   ------------------------------------------------------------
   ⚠️ 为什么单独补这一条：本项目有一条全局兜底
      `.field[hidden], .field.full[hidden], [data-mode-vehicle][hidden], … { display:none !important }`
      （第 20 批为了修"车辆藏不住"加的）。而弹框的**面板里就有 `.field`** ——
      面板一旦被隐藏，CSS 的 `display:flex` 与那条 `!important` 会打架，
      加上 `hidden` 属性同时落在多个层级上，会出现"明明摘了 hidden 却还是不显示"。
      这里用 `:not([hidden])` 把"打开态"显式钉死（后写者赢 + 特异性更高），
      两种状态各归各的规则、互不牵连。
   ============================================================ */
.cal-modal:not([hidden]) { display: flex; }
.cal-modal[hidden] { display: none !important; }

/* ============================================================
   v24 第 22 批：CardDAV 卡的窄屏收口
   ------------------------------------------------------------
   ⚠️ 为什么必须补这一段：`.node-badge` 是 `white-space: nowrap`（徽标本来就不该折行，
      全站都在用），而这一卡的按钮行里放了一句说明性徽标 + 一个长地址输入框 ——
      在 360/375/390px 上直接把整页**撑出横向滚动**
      （真机几何实测：scrollW 412 > clientW 360，越界元素 = 这句徽标）。
      手机上"整页能左右拖"是最刺眼的一类问题，所以：
        · 窄屏让 `.btn-row` 能换行（否则按钮 + 徽标永远挤在一行）；
        · 徽标允许折行（它在这张卡里是**说明**，不是标签）。
     只作用于这张卡（`.card[id="carddav"]`），不动全站的 `.node-badge` ——
     改全站会把所有徽标都变成可折行，那些地方正是要靠 nowrap 保持一行。
   ============================================================ */
@media (max-width: 480px) {
  #carddav .btn-row { flex-wrap: wrap; }
  #carddav .node-badge { white-space: normal; }
}

/* ============================================================
   v24 第 23 批 用户第 1 条：资源页列表瘦身 + 右上角四色按钮 + 四个弹框
   ============================================================ */

/* ---- ① 标题行：按钮组吸附到最右侧（与标题同一排）----
   见上面 `.res-ops` 原位那条注释：**旧规则已经就地改掉了**，不是在这里再覆盖一遍。
   （这正是本轮踩的坑：先前只在这里追加，`flex-direction: column` 漏改，按钮竖排。）
   ⚠️ 按钮组必须是 <span>（phrasing content）：<summary> 里放 <div> 是非法 HTML。
      这里给 `display:inline-flex` 才能有 flex 布局（span 默认 inline）。 */
.res-caret { color: var(--faint); font-size: 12px; flex: 0 0 auto; }
/* 展开时把箭头翻过来（纯装饰，不影响语义） */
.res-item[open] .res-caret { transform: rotate(180deg); }
.res-caret { transition: transform .15s ease; }

/* ---- ② 四个按钮的颜色（用户点名：编辑黄、共享绿、附件蓝、删除红）----
   `.btn` 自带边框与底色，这里只覆盖这三样；文字色统一用白（黄底白字对比度偏低，
   所以黄色的字色用深棕而不是纯白 —— 与 `.btn-warn` 的既有做法一致）。 */
.btn-info { background: #2C6FB5; color: #fff; border-color: transparent; }   /* 附件：蓝 */
.res-op { font-size: 12.5px; }

/* ---- ③ 展开区：人员行（附件列完之后才显示）----
   用户原话：「附件列完后，列出来人员，所有者、共享给谁了。然后其他的都不要。」 */
.res-people {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding-top: 10px; border-top: 1px solid var(--line-2);
}

/* ---- ④ 弹框外壳 ----
   ⚠️ `[hidden]` 必须**自己声明**：下面这两个类都写了 `display:flex`，
      而 `[hidden]` 的浏览器默认 `display:none` 优先级更低 → 会被盖过 → 关不掉。
      项目在 `.pv` 与 `.pp-item` 上各栽过一次，所以这两行是硬要求（有 e2e 断言盯着）。 */
.res-modal {
  position: fixed; inset: 0; z-index: 78;
  display: flex; align-items: center; justify-content: center;
}
.res-modal[hidden] { display: none; }
.res-mask { position: absolute; inset: 0; background: rgba(47, 36, 30, .34); }
.res-panel-lg, .res-panel-sm {
  position: relative; z-index: 1;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 14px; box-shadow: var(--shadow-l);
  width: min(460px, 92vw); max-height: min(88vh, 700px);
  display: flex; flex-direction: column;
}
.res-panel-sm { width: min(400px, 92vw); }
.res-panel-lg[hidden], .res-panel-sm[hidden] { display: none; }
.res-ph {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px 10px; border-bottom: 1px solid var(--line-2);
}
.res-ph b { font-size: 14.5px; }
.res-x {
  margin-left: auto; border: 0; background: transparent; cursor: pointer;
  font-size: 15px; color: var(--muted); padding: 4px 6px; border-radius: 6px;
}
.res-x:hover { background: var(--line-2); color: var(--ink); }
.res-pb { padding: 12px 14px; overflow-y: auto; }
.res-pb .field { margin-bottom: 12px; }
.res-pb .field > label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 5px; }
.res-pf {
  display: flex; gap: 8px; justify-content: flex-end;
  padding: 10px 14px; border-top: 1px solid var(--line-2);
}
.res-pb .pp { max-width: none; }
.res-sub { font-size: 13px; color: var(--muted); margin: 14px 0 8px; font-weight: 600; }
.res-share-now { border-top: 1px solid var(--line-2); margin-top: 10px; }

/* ---- ⑤ "已共享给" / "已有附件" 两行（脚本按 data 属性现渲染）----
   ⚠️ 用 <span> + inline-flex 而不是 <div>/<table>：
      这些行要能塞进任意容器，且不需要表格语义（就是"一行一行的清单"）。 */
.rd-share-row, .rd-att-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 6px 0; border-bottom: 1px solid var(--line-2);
}
.rd-share-row:last-child, .rd-att-row:last-child { border-bottom: 0; }
.rd-share-name, .rd-att-name { flex: 1 1 auto; min-width: 0; word-break: break-all; font-size: 13.5px; }
/* 附件行里的缩略图：与展开区的附件墙同一个观感（小一号，弹框里放得下） */
.rd-att-thumb {
  width: 34px; height: 34px; flex: 0 0 auto;
  object-fit: cover; border-radius: 6px; border: 1px solid var(--line);
}
.rd-att-ico {
  width: 34px; height: 34px; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 6px; background: #5F6672; color: #fff;
  font-size: 10px; text-align: center; line-height: 1.1;
  padding: 2px; overflow: hidden; word-break: break-all;
}

/* ---- ⑥ 窄屏 ----
   四个按钮 + 标题 + 归属人 chip + 权限徽标挤在一行，360px 上必然溢出。
   让按钮组整体落到第二行（`.res-head` 本来就能 wrap），并收紧内边距。
   ⚠️ 这里**不**动全站 `.node-badge` 的 nowrap —— 上一批（CardDAV 卡）已经证明
      改全站会把所有徽标都变得可折行。 */
@media (max-width: 560px) {
  .res-head { padding: 10px 12px; row-gap: 6px; }
  .res-ops { margin-left: 0; width: 100%; justify-content: flex-start; }
  .res-caret { display: none; }
  .res-op { flex: 0 1 auto; }
}

/* ============================================================
   v24 第 23 批 用户第 2 条：财务页类别标签可点 + 上下两图联动
   ============================================================ */

/* ---- ① 概览卡里的标签条 ----
   原来是一排纯展示的 <span>（不可点），现在与图表卡那条 chip **并成一套筛选入口**。
   ⚠️ 元素从 <span> 换成了 <button>：它现在真的有行为了 ——
      用 span 加 JS 点击，键盘用户和屏幕阅读器都认不出"这是个能按的东西"。
      代价是浏览器会给 <button> 默认样式（灰底、系统字体、内边距），
      `.ck-chip` 里已经把这些全部覆盖掉了（它本来就是给 button 写的）。
   ⚠️ 两条既有规则都在抢 margin：`.led-kinds`（margin-top:6px）在前、
      `.ck-filter`（margin:0 0 12px）在后 → 后者赢，6px 那个上边距会被吃掉。
      所以这一条写在**文件末尾**，用双类选择器把意图压回来。 */
.led-kinds.ck-filter { margin: 8px 0 0; }
/* 标签上的金额（原来就有，改成 button 后需要重新确认字重/数字对齐） */
.led-kind-chip b { font-weight: 600; }

/* ---- ② 选中类别后的高亮条 ----
   回答用户那句"统计这个标签下的所有财务条目的总数"：
   看的是哪一类、合计多少、几笔 —— 并且给一个明确的出口（清除筛选）。
   ⚠️ 没有它的话，"图上少了几条线"会被读成"数据丢了"，而不是"我在筛选"。 */
.led-focus {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 10px; padding: 8px 12px;
  background: #FDF6EE; border: 1px solid #EBD9C4; border-radius: 8px;
  font-size: 13px; color: #7A4A20;
}
.led-focus[hidden] { display: none; }   /* 见 .res-modal 的同一提醒：自己声明了 display 就必须补这条 */
.led-focus-txt { flex: 1 1 auto; min-width: 0; }

/* ---- ③ 图下面的说明：整本账 / 只看某一类，两句话互斥显示 ---- */
.ck-note { margin: 8px 2px 0; }
.ck-note > span[hidden] { display: none; }

/* ============================================================
   v24 第 24 批 用户第 2、3 条：首页信息面板 + 日程/任务左右各半
   ============================================================ */

/* ---- ① 信息面板：左（问候/日子/数字） + 右（天气，**贴右端**）----
   ⚠️ 旧的 `.home-brief` 是 `flex-direction: column`，天气自然就落到了下一行。
      用户明确要求「天气放在这个信息面板的最右侧…靠右放」→ 改成横排 + margin-left:auto。
   ⚠️ 这里**必须**把旧规则的那几行一起改掉（v24 第 23 批：只在末尾追加、
      漏了 flex-direction，四个按钮竖排撑高标题行 —— 同一个坑不再踩第二次）。 */
.home-brief { display: flex; flex-direction: row; align-items: flex-start; gap: 18px; flex-wrap: wrap; }
.hb-left { flex: 1 1 320px; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.hb-hello { font-size: 19px; line-height: 1.3; }
.hb-hello b { font-weight: 700; }
.hb-date { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; color: var(--muted); font-size: 13.5px; }
.hb-date .hb-d { color: var(--ink); font-size: 15px; font-weight: 600; }
.hb-date .hb-lunar { color: var(--brand-ink); }
/* 日子标记：节气 / 法定假日 / 传统节日 / 纪念日 / 生日，各给一个色 */
.hb-marks { display: flex; flex-wrap: wrap; gap: 6px; }
.hb-mark {
  display: inline-flex; align-items: center; padding: 2px 9px; border-radius: 999px;
  font-size: 12.5px; white-space: nowrap; border: 1px solid transparent;
}
.hb-mark.is-holiday  { background: #FDECEC; color: #A8232B; border-color: #F3C9C9; }
.hb-mark.is-term     { background: #EDF4E9; color: #3F6B34; border-color: #CFE2C6; }
.hb-mark.is-festival { background: #FDF2E3; color: #96551A; border-color: #F0D9B8; }
.hb-mark.is-anniv    { background: #F2ECF7; color: #6B4A86; border-color: #DCCBE8; }
.hb-mark.is-birthday { background: #FDECF3; color: #A03060; border-color: #F3C9DB; }
.hb-stats { display: flex; flex-wrap: wrap; gap: 8px 20px; font-size: 13px; color: var(--muted); }
.hb-stats .hbs { display: inline-flex; align-items: baseline; gap: 4px; color: var(--muted); }
.hb-stats .hbs b { color: var(--ink); font-size: 16px; font-variant-numeric: tabular-nums; }
.hb-stats .hbs.is-late b { color: var(--red); }

/* ---- 天气卡：贴右端 + **彩色**图标（用户原话「彩色图标的形式」）----
   ⚠️ 颜色按天气**色调**（后端给的 tone）分档，而不是让 CSS 猜天气码 ——
      同一份判断写两遍必然飘。晴=琥珀、云=灰蓝、雨=天蓝、雪=青、雷=紫、雾=灰。 */
.hb-weather {
  margin-left: auto; flex: 0 0 auto;
  display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto;
  align-items: center; gap: 2px 12px; min-width: 168px;
  padding-left: 16px; border-left: 1px solid var(--line-2);
}
.hbw-icon {
  grid-row: 1 / span 2; font-size: 40px; line-height: 1;
  /* ⚠️ 不能靠 `-webkit-text-fill-color` 之类给**文字**上渐变（见 emoji-glyph 那课），
     这里就是普通的彩色文字 + 一点投影，稳定且不挑系统字体。 */
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .10));
}
.hb-weather.is-sunny   .hbw-icon { color: #E8A33D; }
.hb-weather.is-cloud   .hbw-icon { color: #7C8AA0; }
.hb-weather.is-rain    .hbw-icon { color: #3B7FBF; }
.hb-weather.is-snow    .hbw-icon { color: #5AA6B8; }
.hb-weather.is-storm   .hbw-icon { color: #7A5BB5; }
.hb-weather.is-fog     .hbw-icon { color: #8C8C8C; }
.hbw-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.hbw-temp { font-size: 24px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.1; }
.hbw-text { font-size: 13px; color: var(--muted); }
.hbw-wind { font-size: 12px; color: var(--faint); }
.hbw-place { grid-column: 2; font-size: 12px; color: var(--faint); }

/* ---- ② 日程 / 任务 左右各半 ----
   ⚠️ 高度**固定**（用户原话「为了好看高度需要固定」），滚动条**藏起来**
      （「鼠标放上去之后向下可以滚动，暂时不要出现滚动条太丑了」）。
      藏滚动条要三套写法（Firefox / IE-Edge / WebKit 各一套），少一套就在某个浏览器上露出来。

   🔴🔴 v24 第 25 批 用户第 3 条修的真 bug：「日程和任务看板，两个模块，
        现在有点错位，一个高，一个低，应该完全一样才对。」

   根因：这两块原来是 `align-items: start` + 各自内容撑高 ——
        左边日程的**刻度行数**由"今天有几件事"决定（掐头去尾之后 3~9 行不等），
        右边的任务条数也各不相同 ⇒ 两张卡片高度天然不等，一眼就是"一高一低"。

   修法（三步，缺一不可）：
     ① `align-items: stretch` —— 让两栏拉成同一行高（grid 的默认行为，
        但上一版显式写成了 `start`，那等于主动放弃对齐）；
     ② `.hd-pane` 给一个**固定总高**并 `display:flex; flex-direction:column`
        （它本来就有 flex 列），head 不收缩、body 吃满剩余；
     ③ `.hd-pane-body` 用 `flex: 1 1 auto` + `min-height: 0`
        取代原来的固定 `height: 360px`。
        ⚠️ `min-height: 0` 不能省：flex 子项的默认 `min-height:auto`
           会让它**不肯缩到内容以下**，于是内容一多就把卡片顶高、
           对齐又失效（这是"固定高度 + 内部滚动"这套写法的经典坑）。
     这样两块的**外框高度恒等**，与各自内容多少无关 —— 对齐不再依赖数据。 */
.home-duo { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: stretch; }
/* 🔴🔴 v54（用户 2026-10-16 第 1 条）—— **这条才是"标题太长挤成两行"的真根因**。
   症状两层，第一层在这里：
   `.home-duo` 是**两列网格**（`1fr 1fr`），而 grid/flex 子项的默认
   `min-width: auto` 意思是"我最小也得装下我的内容" ⇒ 只要右栏有一条**长标题**，
   那一列就被顶到 1700+px（实测：`home-duo` 1032px，里面 `hd-pane` 1763px），
   整个两列布局被撑破、横向溢出到 2089px。
   ⇒ 于是"截断"根本没机会发生：**容器自己先被撑爆了**，行宽 1721px，
      `scrollWidth === clientWidth`，`text-overflow: ellipsis` 看起来"没生效"。
   ⚠️ `min-width: 0` 是让 grid/flex 子项**允许收缩到内容以下**的唯一开关；
      缺它时，在子项里怎么写 nowrap/ellipsis 都没用（本项目第二次踩这个：
      v53 的 `.mv-grid` 是同类，那次是 `grid-column`，这次是 `min-width`）。
   ⚠️ 加在**两个**网格项上（两个 pane 都要能收缩，不只是有任务的那个）。 */
.home-duo > * { min-width: 0; }
.hd-pane { display: flex; flex-direction: column; height: 440px; min-height: 0; min-width: 0; }
.hd-pane-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; flex: 0 0 auto; min-width: 0; }
.hd-pane-head .cinema-h2 { margin: 0; flex: 1 1 auto; min-width: 0; }
/* ⚠️ v54：列表链上每一层都要 `min-width:0`（中间任何一层没给，
   收缩就断在那一层、下面的 nowrap 又白写）—— `.dash-list`/`.tk-list` 是 ul，
   `.tk-row` 是 li，都是 flex 项。 */
.dash-list { min-width: 0; }
.tk-list { min-width: 0; }
.tk-row { min-width: 0; }
.hd-pane-body {
  flex: 1 1 auto; min-height: 0; min-width: 0; overflow-y: auto; overscroll-behavior: contain;
  scrollbar-width: none;            /* Firefox */
  -ms-overflow-style: none;         /* 旧 Edge / IE */
}
.hd-pane-body::-webkit-scrollbar { width: 0; height: 0; }   /* Chrome / Safari */

/* ---- 日程：日视图形式的刻度网格 ----
   ⚠️ `.ag-row` 的高度就是"一小时"（44px）；日程块用**百分比 top** 定位，
      所以刻度数与日程块的比例天然一致（都在同一个容器里算）。

   🔴🔴 v80（用户 2026-10-22）：「首页，日程好像显示不全，右侧是缺一块的，
       是不是左侧有刻度，导致你算宽度的时候，没算对？」
       —— 用户猜的**完全正确**。真根因：
         · `.ag-grid` 宽 481，左边 42px 刻度 + 8px 间距 = 50px 被刻度吃掉，
           刻度线本身只有 431px 宽；
         · 而色块内联写的是 `left: calc(46px + X%)`、`width: calc(Y% - 4px)`，
           那个 **Y% 是按 481 算的，不是按 431** ⇒ 100% 时色块宽 477，
           右端落到 744，比卡片右边界（700）**多出 44px**，被 `overflow:hidden`
           整块裁掉 —— 用户看到的"右侧缺一块"，缺的正是日历本名那一截。
         · 日历页 `.tg-block` 没这个病：它左边刻度是**独立的一列**（`.tg-col`），
           百分比天然相对列宽。首页把刻度塞在同一 flex 行里，才留下这个残差。
       ⇒ 从结构上对齐日历页：**刻度区与"车道区"分开**。
         `.ag-lanes` 是一个绝对定位的层，左边界让出 `--ag-gutter`、右边留 2px，
         色块放在它里面，百分比就相对"真正可用的宽度"算了。
       ⚠️ `--ag-gutter` 是唯一真值源：`--ag-gutter` = 刻度列宽 + 行内 gap。
          以后改刻度宽度只改这一个变量，色块/红线/刻度会一起跟着动。 */
.ag-grid { position: relative; --ag-gutter: 50px; }
.ag-allday { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
/* 🔴 v24 第 26 批 用户第 2 条：全天区现在是 `.hd-pane` 的**直接子元素**（不在滚动容器内），
   必须显式声明"不参与伸缩、也不会被压扁"：
     · `flex: 0 0 auto` —— 它是固定高度的一块，绝不与下面的滚动区抢空间；
     · `max-height: 33%` + 内部滚动 —— 全天日程可以有很多条（节假日、生日都在里面），
       全铺开会把时段网格挤到看不见；给个上限，超了自己滚。
   ⚠️ 上限用**百分比**（相对 440px 的 pane）而不是固定 px：
      窄屏时 pane 只有 320px，固定 140px 会占掉近一半（见下面的媒体查询）。
   ⚠️ 这里也要藏滚动条（与 `.hd-pane-body` 同一理由：用户嫌丑）。 */
.ag-allday {
  flex: 0 0 auto; max-height: 33%; overflow-y: auto; overscroll-behavior: contain;
  scrollbar-width: none; -ms-overflow-style: none;
}
.ag-allday::-webkit-scrollbar { width: 0; height: 0; }
.ag-allday-item {
  display: flex; align-items: center; gap: 8px; padding: 5px 8px;
  background: var(--line-2); border-radius: var(--radius-s); font-size: 13.5px; color: var(--ink);
}
.ag-allday-item .ag-ad-t { font-size: 11.5px; color: var(--muted); flex: 0 0 auto; }
.ag-allday-item .ag-ad-w { flex: 1 1 auto; min-width: 0; font-weight: 600; }
.ag-grid { position: relative; }
.ag-row { display: flex; align-items: flex-start; gap: 8px; height: 44px; }
.ag-hour { flex: 0 0 42px; font-size: 11.5px; color: var(--faint); font-variant-numeric: tabular-nums; transform: translateY(-6px); }
.ag-line { flex: 1 1 auto; height: 1px; background: var(--line-2); margin-top: 0; }
/* 🔴 v80：车道层 —— 色块的定位参照系。左让刻度、右留 2px 视觉边，
   于是 `.ag-ev` 里那些 `left: X%` / `width: calc(Y% - 4px)` 的百分比
   相对的就是**真正可用的宽度**（不再是含刻度的整宽）。
   ⚠️ `pointer-events: none` + 子项 `auto`：这一层铺在刻度线上面，
      不能挡住刻度行本身的点击/悬浮；色块自己再收回事件。 */
.ag-lanes {
  position: absolute; left: var(--ag-gutter, 50px); right: 2px; top: 0; bottom: 0;
  pointer-events: none;
}
.ag-lanes > * { pointer-events: auto; }
/* 当前时刻红线（用户原话「需要在当前的时间或者是时刻那里显示一条红线」）
   🔴 v44（用户第 3 条）：左端那颗小圆点**删掉**了 ——
   用户：「这个红点显示也不全，也不太好看。直接把红点去掉就行了，直接一根红线就可以了。」
   ⇒ 直接一根 2px 红线；`.ag-now-dot` 这条规则随之删除（不再有那个元素）。
   🔴🔴 v82（用户 2026-10-23）：「首页上的日程，红色线不对，变短了左侧缺了一块。」
   真根因 = **双重缩进**：v80 把色块挪进 `.ag-lanes`（这一层自己已经
   `left: var(--ag-gutter)` 让出了刻度），红线在它**内部**却又写了一次
   `left: var(--ag-gutter, 50px)` ⇒ 实际左沿 = 刻度宽 + 刻度宽 = 100px，
   比车道左沿多出整整一份刻度宽（实测差 50.00px，正是 `--ag-gutter` 的值）。
   ⇒ 红线在车道内部，`left` 就必须是 `0`（相对车道），不能再吃 gutter。
   ⚠️ 别再"顺手写回去"：`.ag-ev` 的百分比也在这层里算，只要给它们加过 gutter，
      就是同一个 bug 再长一遍。外来元素要判断"我在谁里面"再决定 left。 */
.ag-now { position: absolute; left: 0; right: 0; height: 0; border-top: 2px solid var(--red); z-index: 2; }
/* 日程块 */
/* 🔴 v30 第 2 条：「首页上的今天的日程，用色块表示，和日程保持一致。」
   ⇒ 从原来的"白底卡片 + 左边 3px 色线"改成**与日历页 `.tg-block` 同款的实心色块**：
      底色 = 日历本色（内联给）、白字、圆角、无边框阴影。
   ⚠️ `left` 与 `width` 由**内联样式**给出（冲突分列的结果，见 templates/home.php）——
      这里不再写 left/right（绝对定位下 left+right+width 三者同时存在是过约束）。
   ⚠️ `transform: translateY(2px)` 那点微调去掉了：色块要精确落在刻度上，
      偏移 2px 会让"9:00 的块"看起来不在 9:00 那条线上。
   ⚠️ 白字压浅色本的兜底：加一层极轻的 text-shadow（不加深色罩，保持本色）。
   🔴 v80：`left`/`right` 那句**删掉**了 —— 现在色块的定位原点是 `.ag-lanes`
       （已经让出了刻度），内联的 `left: X%` 直接相对车道宽算，
       `width: calc(Y% - 4px)` 里的 4px 正好是"左右各 2px"的视觉留白。
      ⚠️ 千万别再在这里写 `left: 46px` 之类的绝对值：那就是 v80 之前
         "右侧缺一块"的直接成因（绝对偏移 + 相对整宽的百分比，两套账混算）。 */
.ag-ev {
  position: absolute;
  /* 🔴 v63（用户 2026-10-19 第 2 条）：日历本要显示在色块**右侧** ⇒ 本体必须是
     一行弹性容器。⚠️ 纵向排列时（`flex-direction: column`）`block` 子元素会各占一行，
     日历本必然掉到第二行；色块很矮时就整条看不见。 */
  display: flex; align-items: center; gap: 6px;
  overflow: hidden; padding: 3px 7px;
  background: var(--brand); border-radius: 4px;
  font-size: 12px; line-height: 1.35; color: #fff;
  text-decoration: none; box-sizing: border-box;
  border: 1px solid rgba(255,255,255,.35);
  text-shadow: 0 1px 1px rgba(0,0,0,.18);
}
.ag-ev:hover { text-decoration: none; filter: brightness(1.08); z-index: 2; }
/* 「时间 + 标题」这一坨占满剩余宽度；`min-width:0` 是标题能被省略号截断的前提
   （没有它，flex 子项按内容撑宽 ⇒ 整行溢出、日历本被挤出可视区）。 */
.ag-ev .ag-ev-main {
  display: flex; align-items: baseline; gap: 4px;
  flex: 1 1 auto; min-width: 0;
}
.ag-ev .ag-ev-t { flex: 0 0 auto; font-weight: 600; font-variant-numeric: tabular-nums; }
.ag-ev .ag-ev-w { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
/* 日历本名：色块**右侧**的极淡小字（色块面积小，不加大徽标）。
   ⚠️ 它 `flex: 0 0 auto`（**不参与收缩**）：用户要的就是"看得见日历本" ——
      让它跟着一起缩，色块一窄就又没了。改为**标题**让步（`.ag-ev-main` 可缩到 0）。
      同时给它 `max-width: 45%` 兜底：日历本名过长时自己截断，别把标题吃光。 */
.ag-ev .ag-ev-cal {
  flex: 0 0 auto; font-size: 10.5px; opacity: .82;
  max-width: 45%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---- 任务行：标题 + 状态 + **看板名** + 截止 ---- */
.tk-list { display: flex; flex-direction: column; }
/* 🔴 v54（用户 2026-10-16 第 1 条）：标题太长会把这行**挤成两行**。
   与 `.dash-what` 那三句（nowrap/hidden/ellipsis）配套：行本身**不许换行**，
   于是标题只能去收缩（它有 min-width:0，能一直缩到 0），徽标保持原样。
   ⚠️ 不复用 `.dash-row` 的 `flex-wrap:wrap` —— 那是"往年的今天"那一块要的
      （窄屏时日期徽标该绕到下一行）；任务行收紧成 nowrap 才有截断可言。 */
.tk-row { align-items: center; flex-wrap: nowrap; }
.tk-board {
  flex: 0 0 auto; font-size: 11.5px; color: var(--muted);
  background: var(--line-2); border-radius: 999px; padding: 1px 8px;
}
.tk-due { flex: 0 0 auto; font-size: 11.5px; color: var(--faint); }

/* ---- 任务的「完成」复选框（v24 第 25 批 用户第 6 条）----
   用户原话：「所有的任务，前面都带一个复选框，在任何地方，点这个复选框，
              这个任务就完成了。」

   ⚠️ 它是 `<button>` 不是 `<input type=checkbox>`（见 _task_check.php 的说明）：
      勾没勾**由服务端状态渲染的 class** 说了算，不是浏览器自己管 checked。
   ⚠️ 尺寸写死 18px 且 `flex: 0 0 auto`：卡片标题是长文本，
      不写死的话它会被 flex 压扁（同一行里"文字优先"）。
   ⚠️ 勾选态用**伪元素画对勾**而不是塞一个 ✓ 字符：
      字符在不同字体里基线不同，18px 的方块里会歪。 */
.tk-check {
  flex: 0 0 auto; width: 18px; height: 18px; padding: 0; margin: 0;
  border: 1.5px solid var(--line); border-radius: 5px; background: #fff;
  cursor: pointer; position: relative; transition: border-color .15s, background .15s;
}
.tk-check:hover { border-color: var(--brand); }
.tk-check::after {
  content: ""; position: absolute; left: 5px; top: 1.5px;
  width: 5px; height: 10px; border: solid #fff; border-width: 0 2px 2px 0;
  transform: rotate(45deg) scale(0); transition: transform .13s ease;
}
.tk-check.is-done { background: var(--ok, #5E9E4E); border-color: var(--ok, #5E9E4E); }
.tk-check.is-done::after { transform: rotate(45deg) scale(1); }
/* 只读账号：一颗不可点的静态方块 */
.tk-check.is-static { cursor: default; }
.tk-check.is-static:hover { border-color: var(--line); }
/* 已完成的那张卡：标题压灰划掉（一眼看出"这件做完了"） */
.kb-card.is-done .kb-title { color: var(--muted); text-decoration: line-through; }
/* ⚠️ 首页/详情那种"一行文字 + 一颗复选框"的场合：
   `.dash-row` 是 `align-items: baseline`，而复选框是 18px 的方块 ——
   按基线对齐会让它**沉到文字下面半截**（基线是文字的底边，方块没有基线）。
   所以带 .tk-check 的行改成 `center`，文字与方块的**中线**对齐，
   这才是"复选框在文字前面"该有的样子。
   （`.sub-row` 本来就是 center，不用管它。） */
.dash-row:has(.tk-check) { align-items: center; }
/* ⚠️ v24 第 26 批 用户第 5 条：**详情页页头的复选框已去掉**（那颗由「标记完成」按钮取代），
   所以这里原来那条 `.page-head h1:has(> .tk-check)` 一并删除 ——
   留一条没有使用者的规则，只会让下一个人以为"页头还该有复选框"。 */

/* 看板页顶上的「三个状态到底什么意思」（用户第 6 条直接问出来的那句话） */
.tk-legend {
  display: flex; flex-wrap: wrap; gap: 6px 18px; align-items: center;
  margin: 0 0 12px; padding: 9px 12px;
  background: var(--surface-2, rgba(0, 0, 0, .02));
  border: 1px solid var(--line); border-radius: var(--radius-s);
}
.tk-legend .tk-lg-item { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; }
.tk-legend .tk-lg-hint { color: var(--muted); }

/* ---- 窄屏：并排改成上下（各占一半宽度在手机上没法读）---- */
@media (max-width: 760px) {
  .home-duo { grid-template-columns: 1fr; }
  /* ⚠️ 窄屏仍要保持**固定高度**（用户要的是"高度固定、内部滚动"），
     只是比桌面矮一点。不写成 auto：那样内容一多整页就长得没法用。 */
  .hd-pane { height: 320px; }
  .hb-weather { margin-left: 0; padding-left: 0; border-left: 0; padding-top: 10px; border-top: 1px solid var(--line-2); width: 100%; }
}

/* ============================================================
   v24 第 24 批 用户第 4 条：个人导航**管理页**样式
   （展示页样式在 assets/css/nav-show.css —— 那份是从 SimpleNav 整份搬来的，
     这里只管登录后的管理界面，所以用家书自己的视觉语言。）
   ============================================================ */
.nav-grp-block { margin-bottom: 14px; }
.nav-sub { font-size: 13.5px; margin: 10px 0 6px; color: var(--muted); }
.nav-sortlist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.nav-sortrow {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 7px 10px; border: 1px solid var(--line-2); border-radius: var(--radius-s);
  background: #FDFBF7;
}
/* 隐藏中的条目淡一点，一眼能看出"它还在、只是不显示" */
.nav-sortrow.is-off { opacity: .55; }
.nav-sortrow.is-dragging { box-shadow: var(--shadow-l); border-color: var(--brand-2); }

/* ------------------------------------------------------------
   地址设置：「我的短语」表单（工单 #17）

   🔴 为什么不用 `.inline-form`（用户 2026-10-28：「输入框怎么那么高啊」）：
      `.inline-form .input { flex: 1 1 200px }` 是为「input 直接当水平 flex 子项」
      设计的。本表单要把 label / input / tip 竖直堆叠，input 必须包进 `.field`
      （`flex-direction: column`）—— 一包进去，`flex-basis: 200px` 就落在
      **垂直主轴**上，输入框被撑成 200px 高的一条。

   做法：外层横向 flex（左：输入区，右：按钮），只把**输入区**当 flex 子项；
   输入区内部仍走 `.field` 的纵向堆叠，并显式把 `.input` 的 flex 收回默认。
   ⚠️ 不要改 `.inline-form` 那条通用规则：全站另有 15 处在用，且都是
      "input 直接当子项"的正确用法，改它会波及那些页面。
   ------------------------------------------------------------ */
.nav-phrase-form {
  display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap;
  margin: 0;
}
/* 输入区是横向主轴上的那个伸缩项（宽度在这里定，不是让内部 input 定） */
.nav-phrase-form > .field { flex: 1 1 260px; min-width: 0; max-width: 380px; }
/* 🔴 关键：把 input 从"flex 子项"退化成普通块，否则 flex-basis 又跑到垂直轴上 */
.nav-phrase-form > .field > .input {
  flex: 0 0 auto; width: 100%; max-width: none;
  /* 单行高度：与全站 .input 一致（10px 上下 padding + 14px 字 + 1px 边框） */
  height: auto; min-height: 0;
}
/* 按钮跟输入框底对齐（不是与整块 field 居中）—— tip 在下方，居中会显得按钮浮起来 */
.nav-phrase-form > .btn-row { flex: 0 0 auto; margin: 0; align-self: flex-end; }
/* 窄屏：按钮换到下一行并靠左，不挤压输入框 */
@media (max-width: 480px) {
  .nav-phrase-form > .field { flex: 1 1 100%; max-width: none; }
  .nav-phrase-form > .btn-row { width: 100%; }
}
.nav-drag {
  flex: 0 0 auto; cursor: grab; color: var(--faint); font-size: 13px;
  letter-spacing: -1px; user-select: none; padding: 0 2px;
}
.nav-drag:active { cursor: grabbing; }
.nav-ico {
  flex: 0 0 auto; width: 20px; height: 20px; border-radius: 5px; object-fit: contain;
  border: 1px solid var(--line-2); background: #fff;
}
.nav-ico-letter {
  display: inline-flex; align-items: center; justify-content: center;
  color: #fff; font-size: 11px; border: 0;
}
/* 大号字母块：图标选择器里的预览位（与列表里的 20px 区分开） */
.nav-ico-lg { width: 40px; height: 40px; font-size: 18px; border-radius: 8px; }

/* ------------------------------------------------------------
   站点表单：自定义图标选择器（工单 #16）
   左侧预览、右侧文件选择 + 说明。预览用 40px 见方，
   和列表里的 20px 小图标区分开，让用户能看清传的是什么。
   ------------------------------------------------------------ */
.nav-icon-pick { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.nav-icon-prev {
  flex: 0 0 auto; width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-2); border-radius: 10px; background: #fff;
  overflow: hidden;
}
.nav-icon-prev img { width: 40px; height: 40px; object-fit: contain; display: block; }
.nav-icon-ctl { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
/* 原生 file 控件隐藏，用 <label for> 当按钮 —— 各浏览器原生按钮长相差异太大 */
.nav-icon-file { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.nav-icon-btn { cursor: pointer; margin: 0; }
/* 焦点可达性：file 输入被键盘聚焦时，让标签有个可见的焦点环 */
.nav-icon-file:focus-visible + .nav-icon-btn {
  outline: 2px solid var(--brand); outline-offset: 2px;
}
#navIconFname { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-sname { flex: 0 0 auto; font-size: 13.5px; font-weight: 600; }
.nav-surl { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-acts { margin-left: auto; display: inline-flex; gap: 6px; flex-wrap: wrap; }
@media (max-width: 560px) {
  .nav-acts { margin-left: 0; width: 100%; }
  .nav-surl { display: none; }
}

/* ============================================================
   v24 第 24 批 用户第 5 条：微信读书导入页（书清单 + 样章）
   ============================================================ */
.wr-list { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 14px; }
.wr-book {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 9px 12px; border: 1px solid var(--line-2); border-radius: var(--radius-s);
  background: #FDFBF7; cursor: pointer;
}
.wr-book:hover { border-color: var(--brand-2); }
.wr-book > input[type=checkbox] { margin-top: 3px; flex: 0 0 auto; accent-color: var(--brand); }
.wr-main { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.wr-title { font-size: 14px; font-weight: 600; }
/* 样章：原文用斜体、想法用普通体 —— 与读书详情页里笔记的排法一致 */
.wr-sample { flex: 1 1 100%; display: flex; flex-direction: column; gap: 2px; margin-left: 22px; }
.wr-q { font-size: 12.5px; color: var(--muted); line-height: 1.6; }
.wr-q i { font-style: normal; color: var(--ink); }
.wr-q em { font-style: normal; color: var(--brand-ink); }
.wr-q em::before { content: '💭 '; }

/* ---- 微信读书：封面（v24 第 25 批 用户第 8 条「封面图看不到」）----
   ⚠️ 尺寸必须写死（52×74 ≈ 2:3，与 bookcover 槽位的裁剪比例一致）：
      不写死的话，官方封面各尺寸不一，一行里高低参差（这正是"看起来乱"的原因）。
   ⚠️ `object-fit: cover` + 固定高宽：封面比例不一时居中裁，不拉伸变形。 */
.wr-cv {
  flex: 0 0 auto; width: 52px; height: 74px; border-radius: 6px;
  object-fit: cover; background: var(--line-2);
  box-shadow: 0 1px 4px rgba(0, 0, 0, .10);
}
.wr-cv-ph {
  display: flex; align-items: center; justify-content: center; font-size: 22px;
  color: var(--faint); box-shadow: none; border: 1px dashed var(--line);
}
/* 外链封面（统计块里的「读得最多的书」，直接来自官方 CDN，本站不下载） */
.wr-top-cv {
  flex: 0 0 auto; width: 34px; height: 48px; border-radius: 5px;
  object-fit: cover; background: var(--line-2);
}
.wr-top-ph { display: flex; align-items: center; justify-content: center; color: var(--faint); }

/* ---- 自动同步（用户第 8 条）---- */
.wr-sync { display: flex; flex-wrap: wrap; gap: 8px 22px; font-size: 13px; }
.wr-sync-item { color: var(--muted); }
.wr-sync-item b { color: var(--ink); font-weight: 600; }

/* ---- 阅读统计（用户第 8 条）----
   一排数字卡。⚠️ `flex: 0 1 auto` + `min-width` 而不是等宽：
      指标个数（时长/天数/日均/读过/读完/笔记）随官方返回而变，
      写死等宽会在某个指标缺失时留一块空。 */
.wr-stats { display: flex; flex-wrap: wrap; gap: 10px; margin: 2px 0 4px; }
.wr-stat {
  flex: 0 1 auto; min-width: 108px; padding: 10px 14px;
  background: var(--line-2); border-radius: var(--radius-s);
  display: flex; flex-direction: column; gap: 2px;
}
.wr-stat b { font-size: 17px; color: var(--ink); font-weight: 600; }
.wr-stat span { font-size: 12px; color: var(--muted); }
.wr-sub { margin: 18px 0 8px; font-size: 14px; font-weight: 600; color: var(--ink); }
/* 读得最多的书 */
.wr-top { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.wr-top-item { display: flex; align-items: center; gap: 10px; }
.wr-top-b { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.wr-top-t { font-size: 13.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wr-top-time { flex: 0 0 auto; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
/* 偏好分类：名称 + 条 + 数值 */
.wr-cats { display: flex; flex-direction: column; gap: 6px; }
.wr-cat { display: flex; align-items: center; gap: 10px; font-size: 13px; }
.wr-cat-n { flex: 0 0 84px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wr-cat-bar { flex: 1 1 auto; height: 7px; background: var(--line-2); border-radius: 999px; overflow: hidden; }
.wr-cat-bar i { display: block; height: 100%; background: var(--brand); border-radius: 999px; }
.wr-cat-v { flex: 0 0 auto; font-size: 12px; }

/* ============================================================
   v24 第 26 批 用户第 4 条：新增站点时的「可复用站点」提示块
   （跨用户复用：别人加过同一个 URL，你就能直接搬过来）
   ============================================================ */
.nav-reuse {
  margin: 0 0 12px; padding: 10px 12px;
  border: 1px solid var(--line-2); border-left: 3px solid var(--brand-2);
  border-radius: var(--radius-s); background: #FBF7F2;
}
.nav-reuse-h { font-size: 13px; color: var(--ink); margin-bottom: 8px; }
.nav-reuse-list { display: flex; flex-wrap: wrap; gap: 8px; }
.nav-reuse-item {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  padding: 5px 10px; border: 1px solid var(--line); border-radius: var(--radius-s);
  background: #fff; font-size: 13px; color: var(--ink);
}
.nav-reuse-item:hover { border-color: var(--brand-2); }
/* .is-on = 已选中（点一下即选中，再点别的会切换过去） */
.nav-reuse-item.is-on {
  border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand);
  background: var(--brand-soft, #FDF3EC);
}
.nav-reuse-item .nav-ico { width: 20px; height: 20px; border-radius: 4px; object-fit: contain; }
.nav-reuse-nm { white-space: nowrap; }
.nav-reuse-f { font-size: 12px; color: var(--muted); margin-top: 8px; }
/* 空 img（没有图标可复用）别占位成一个破图框 */
.nav-reuse-item img:not([src]) { visibility: hidden; }

/* ============================================================
   v27 个人笔记（用户 2026-10-07）
   参考 flomo（时间流卡片 + 标签）/ Obsidian（图文混排的直觉）/ 思源（分块），
   但一律取**轻**的那一层：块只有 段落/图片/清单，不做块引用、不做富文本。
   配色沿用宣纸米 + 砖红，不引入第三套色。
   ============================================================ */

/* ---- 列表页：左标签云 + 右卡片流 ---- */
.note-layout { display: grid; grid-template-columns: 220px 1fr; gap: 16px; align-items: start; }
.note-side { position: sticky; top: 16px; }
.note-main { min-width: 0; }
.note-search-card { margin-bottom: 12px; }
.note-search-card .search { margin: 0; }

.note-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.note-tag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 9px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line); background: #fff;
  font-size: 12.5px; color: var(--muted); text-decoration: none;
}
.note-tag:hover { border-color: var(--brand-2); color: var(--brand-ink); text-decoration: none; }
.note-tag.is-on { border-color: var(--brand); color: var(--brand-ink); background: var(--brand-soft, #FDF3EC); }
.note-tag b { font-weight: 600; color: var(--faint); font-variant-numeric: tabular-nums; }
.note-tag.is-on b { color: var(--brand-ink); }
.note-tags-inline { margin-top: 10px; }
.note-tags-suggest { margin-top: 8px; align-items: center; }

/* ---- 卡片 ---- */
.note-card { margin-bottom: 12px; }
.note-head { display: flex; align-items: center; gap: 8px; }
.note-meta { display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0; }
.note-ava { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.note-who { font-size: 13px; color: var(--ink); font-weight: 500; }
.note-time { font-size: 12px; color: var(--faint); margin-left: auto; padding-right: 4px; }
/* 共享/私有标记：一眼分清"这是我写给自己看的"还是"全家都能看的" */
.note-share, .note-private {
  font-size: 11px; padding: 1px 7px; border-radius: 999px; flex: 0 0 auto;
}
.note-share { color: #2F6B4F; background: #E7F2EC; }
.note-private { color: var(--faint); background: var(--line-2); }
.note-ops { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.note-ops .inline-form { display: inline; }

.note-title { margin: 10px 0 6px; font-size: 16px; font-weight: 600; color: var(--ink); }
.note-body { display: flex; flex-direction: column; gap: 8px; }
.note-text { margin: 0; font-size: 14.5px; line-height: 1.7; color: var(--ink); white-space: pre-wrap; }
.note-list { margin: 0; padding-left: 20px; font-size: 14.5px; line-height: 1.7; color: var(--ink); }
.note-list li { margin: 2px 0; }
/* 图片：等比缩到卡片宽，圆角与卡片一致；点开走全站预览层（data-prev） */
.note-img { display: block; }
.note-img img {
  max-width: 100%; max-height: 420px; border-radius: var(--radius-s);
  border: 1px solid var(--line); display: block;
}
.note-more { text-align: center; margin: 16px 0 8px; }

/* ---- 编辑页：分块 ---- */
.note-form .card { margin-bottom: 12px; }
.note-block-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.note-block-head .cinema-h2 { margin: 0; flex: 1 1 auto; }
.note-add { display: flex; gap: 6px; flex: 0 0 auto; }
.note-blocks { display: flex; flex-direction: column; gap: 8px; }
.note-block {
  display: flex; gap: 8px; align-items: flex-start;
  border: 1px dashed var(--line); border-radius: var(--radius-s);
  padding: 8px; background: #FDFCFA;
}
.note-block:focus-within { border-color: var(--brand-2); border-style: solid; }
.note-block-ops { display: flex; flex-direction: column; gap: 4px; flex: 0 0 auto; }
.nb-btn {
  width: 24px; height: 22px; line-height: 1; padding: 0;
  border: 1px solid var(--line); border-radius: 5px; background: #fff;
  color: var(--muted); cursor: pointer; font-size: 12px;
}
.nb-btn:hover { border-color: var(--brand-2); color: var(--brand-ink); }
.nb-del:hover { border-color: var(--red); color: var(--red); }
.note-block-body { flex: 1 1 auto; min-width: 0; }
/* 🔴 v52（用户第 3 条）：块**下方**的「＋段落 / ＋图片」。
   放在每段输入框的下方，写到哪一段就在哪儿加，不用划回顶部。
   ⚠️ 克制视觉：它是"随手加"的快捷入口，不该抢正文的注意力 ——
      小字、灰色、平时半透明，鼠标进到这一块才变清晰。 */
.note-block-add { display: flex; gap: 6px; margin-top: 6px; }
.nb-add {
  padding: 3px 10px; font-size: 12.5px; line-height: 1.5; cursor: pointer;
  border: 1px dashed var(--line); border-radius: 999px; background: transparent;
  color: var(--muted);
}
.note-block:hover .nb-add, .note-block:focus-within .nb-add { color: var(--brand-ink); }
.nb-add:hover { border-color: var(--brand-2); border-style: solid; background: #fff; }
.note-block-ta { width: 100%; resize: vertical; font-size: 14.5px; line-height: 1.7; }
.note-block-img {
  max-width: 100%; max-height: 320px; border-radius: var(--radius-s);
  border: 1px solid var(--line); display: block; margin-bottom: 6px;
}
/* 还没选图时：不要显示破图框（那是 <img src=""> 的默认表现，很难看） */
.note-block-img.is-empty { display: none; }
.note-img-pick {
  padding: 4px 10px; border: 1px solid var(--line); border-radius: var(--radius-s);
  background: #fff; color: var(--muted); cursor: pointer; font-size: 12.5px;
}
.note-img-pick:hover { border-color: var(--brand-2); color: var(--brand-ink); }
.note-share-toggle {
  display: flex; align-items: center; gap: 8px; margin-top: 12px;
  font-size: 13.5px; color: var(--muted); cursor: pointer;
}
.note-share-toggle input { width: 16px; height: 16px; accent-color: var(--brand); }
.note-actions { display: flex; gap: 8px; margin: 4px 0 20px; }

/* ---- 窄屏：标签云折到上面，卡片满宽 ---- */
@media (max-width: 760px) {
  .note-layout { grid-template-columns: 1fr; }
  .note-side { position: static; }
  .note-side .card { margin-bottom: 12px; }
  /* 长英文/URL 不要把卡片撑出横向滚动（全站反复栽在这条上） */
  .note-text, .note-list, .note-title { overflow-wrap: anywhere; }
}

/* ============================================================
   v28 用户第 2 / 3 条：首页两块右上角的「更多日程 / 全部任务」按钮
   要**用所属模块的主题色做背景**（原来一律 .btn-ghost 的白底灰字，
   与"日程=橙、任务=砖红"的分区色完全看不出关系）。
   做法：模板把模块色注入 `--hd-c`（见 home.php 的 style 属性），
   这里只负责"拿这个色当底 + 白字 + 悬停加深"三件事 ——
   不为日程/任务各写一套（那是两份迟早漂移的样式）。
   ⚠️ 用 color-mix 压暗悬停色而不是写死两个色：模块色在「个人设置」里可改，
      写死就会出现"用户换了主题色、按钮悬停还是原来的颜色"。
   ============================================================ */
.hd-more {
  background: var(--hd-c, var(--brand));
  border-color: var(--hd-c, var(--brand));
  color: #fff;
}
.hd-more:hover {
  background: color-mix(in srgb, var(--hd-c, var(--brand)) 82%, #000);
  border-color: color-mix(in srgb, var(--hd-c, var(--brand)) 82%, #000);
  color: #fff;
}
/* 老浏览器不认 color-mix 时的兜底：至少不要变成透明/消失 */
@supports not (background: color-mix(in srgb, red 50%, blue)) {
  .hd-more:hover { background: var(--hd-c, var(--brand)); filter: brightness(.88); }
}

/* ============================================================
   v28 用户第 5 / 6 条：名册「分组」改「标签」+ 卡片按钮要有底色
   ============================================================ */

/* ---- 顶部标签筛选条（替代原来的分栏）---- */
.roster-tagbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin: 0 0 12px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-s);
  background: var(--surface);
}
.roster-tagbar-k { font-size: 12.5px; color: var(--faint); margin-right: 2px; }
.roster-tagbar-clear { margin-left: auto; font-size: 12.5px; }

/* ---- 平铺列表：**仍然单列**（只是不再有"栏"的分隔线）----
   🔴🔴 v29 用户第 3 条：「名册页面完全错乱」—— 这个 bug 是 v28 **我自己引入的**。
      v28 我把这里写成了多列 grid（`repeat(auto-fill, minmax(320px,1fr))`），
      真实后果有两个，都实测到了：
        ① 卡片上有 `content-visibility: auto` + `contain-intrinsic-size: auto 68px`
           （v24 为"千人名册不卡死"加的）。它在**单列流**里是安全的，
           一旦卡片变成 grid item，屏外卡片的"估算高度"与网格轨道互相打架，
           布局当场乱掉；
        ② 每列只剩 ~339px（1280 宽 ÷ 3），而卡片里是
           [拖柄][头像 52px + 姓名/徽标][标签+编辑+删除 三个按钮 ≈213px] ——
           实测 `person-main` 只分到 **106px**（含 52px 头像 → 姓名区仅 42px），
           姓名、徽标全被挤碎，动作区还会折行。
      ⇒ 修法：**回到单列**（`.people-grid` 本来就是单列 flex，见文件上方 328 行），
        把 v28 那条 grid 规则整个删掉。名册人数从 3 到 1000+ 都是单列，
        与"分栏时代每一栏的卡片"同宽 —— 那才是这套卡片的原始设计宽度。
      ⚠️ 别再用"多列网格"来塞名册卡片：它的内容是**横向三段式**的，
         天生吃宽度；窄了就一定碎。 */

/* ---- 🔴 用户第 6 条：「名册，每个人后面的按钮，也是要有背景颜色的。」
   原来三颗按钮全是 .btn-ghost（白底 + 极浅描边），在纸白卡片上几乎看不见边界。
   给它们各自一个**语义底色**（编辑=品牌淡、标签=中性灰、删除=危险淡），
   而不是统一刷成一种 —— 一眼能分清哪个是危险动作。 ---- */
.person-edit,
.person-tag-btn,
.person-more-btn {
  background: var(--line-2);
  border-color: var(--line);
  color: var(--ink);
}
/* 🔴🔴 v50（用户 2026-10-14 第 4 条）：「名册列表里面的按钮也是一样的，
   编辑是**蓝色**的背景，删除是**红色**的背景。」
   —— 与旅程详情页/v40 影院管理页**同一套口径**：编辑蓝、删除红，都是**实心**色块。
   ⚠️ 用**作用域类**（`.person-edit` / `.person-del`），不去改全局 `.btn-edit`
      / `.btn-danger` 的语义 —— 本项目踩过"裸类改色波及别页"的坑
      （v41 把影院管理页的蓝色「编辑」改成了黄，见前面那段血泪注释）。
   ⚠️ 三处形态都写（裸类 / `a.` / `button.`）：`.btn`、`a.btn`、`button.btn`
      的特异性不同，漏一种就有一种是错的（v40 已验过这条规律）。
   ⚠️ `.person-del` 原来只是 `.btn-danger`（**淡红描边**）—— 用户这次要的是**实心红**，
      所以在这里覆盖它；`.btn-danger` 本身的语义不动。 */
.person-edit, a.person-edit, button.person-edit {
  background: var(--blue); color: #fff; border-color: transparent;
}
.person-del, a.person-del, button.person-del {
  background: var(--red); color: #fff; border-color: transparent;
}
/* hover：同色系加深（与文末「全站悬停统一」段同一手法）——
   ⚠️ 必须在这里补，因为那段是按类名白名单写的，`.person-edit/.person-del` 不在里面，
      不补的话 hover 会退回 `.btn` 的品牌色/或 `.person-edit` 上面那条淡底规则。 */
.person-edit:hover, .person-del:hover { filter: brightness(.92); }
/* 标签 = 中性灰底；有标签时稍深一点，一眼能看出"这个人是有标记的" */
.person-tag-btn { background: var(--line-2); color: var(--muted); }
.person-tag-btn:hover { background: var(--line); color: var(--ink); }
.person-tag-btn.is-none { color: var(--faint); }
/* ⋯ = 中性 */
.person-more-btn { background: var(--line-2); color: var(--muted); }
.person-more-btn:hover { background: var(--line); color: var(--brand); }
/* 删除已经是 .btn-danger（自带红底），不动 */

/* ---- 标签编辑浮层里的候选标签 ---- */
.pt-picks { display: flex; flex-wrap: wrap; gap: 6px; }
.pt-pick {
  display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
  padding: 4px 10px; border: 1px solid var(--line); border-radius: 999px;
  background: #fff; font-size: 12.5px; color: var(--muted);
}
.pt-pick:hover { border-color: var(--brand-2); color: var(--brand-ink); }
/* 勾上时整颗变实心（不是只动那个小方框）—— 点一下的反馈要一眼看得见 */
.pt-pick:has(input:checked) {
  border-color: var(--brand); background: var(--brand-soft, #FDF3EC);
  color: var(--brand-ink);
}
.pt-pick input { accent-color: var(--brand); }

/* ============================================================
   v28 用户第 7 条：资源的「编辑」弹框改成带页签（信息 / 附件 / 共享）
   ============================================================ */
.res-tabs {
  display: flex; gap: 4px; padding: 0 16px 10px;
  border-bottom: 1px solid var(--line); margin-bottom: 12px;
}
.res-tab {
  padding: 5px 14px; border: 1px solid transparent; border-radius: var(--radius-s);
  background: transparent; color: var(--muted); cursor: pointer; font-size: 13.5px;
}
.res-tab:hover { background: var(--line-2); color: var(--ink); }
/* 当前页签：品牌淡底 + 品牌字色（与全站分段控件同一套观感） */
.res-tab.is-on {
  background: var(--brand-soft, #FDF3EC);
  border-color: color-mix(in srgb, var(--brand) 30%, transparent);
  color: var(--brand-ink); font-weight: 500;
}

/* ============================================================
   v28 用户第 10 条：内置联想接口库（点一下填入表单）
   ============================================================ */
.sug-lib { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0; }
.sug-lib-item {
  display: inline-flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 6px 12px; border: 1px solid var(--line); border-radius: var(--radius-s);
  background: #fff; cursor: pointer; text-align: left;
}
.sug-lib-item b { font-size: 13px; font-weight: 500; color: var(--ink); }
.sug-lib-item span { font-size: 11.5px; color: var(--faint); }
.sug-lib-item:hover { border-color: var(--brand-2); }
/* 刚被点选的那一个：品牌底 + 品牌边，明确"我选了它" */
.sug-lib-item.is-picked {
  border-color: var(--brand); background: var(--brand-soft, #FDF3EC);
}
.sug-lib-item.is-picked b { color: var(--brand-ink); }

/* ============================================================
   v28 用户第 12 条：笔记 —— 富文本双模式 / 附件 / 多级清单 / 定尺寸卡片网格
   ============================================================ */

/* ---- 列表页：顶部条（搜索 + 标签），去掉左侧标签栏 ---- */
.note-bar { margin-bottom: 12px; }
.note-search { margin: 0 0 8px; }
.note-tagbar { margin-top: 2px; }

/* ---- 🔴 定尺寸卡片网格（用户："一行几个块，但是块大小要确定"）----
   高度**固定**：内容装不下就靠摘要 + 溢出裁切，卡片之间永远整齐。
   用 auto-fill + minmax 让"一行几个"随宽度自适应，而不是写死列数。 */
.note-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 12px;
}
.note-card {
  display: flex; flex-direction: column;
  /* 固定高度 = 用户要的"块大小确定"。240px 大约放得下：一行头 + 标题 + 3~4 行摘要 + 缩略图 */
  height: 240px; overflow: hidden;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px 14px;
  box-shadow: var(--shadow);
}
.note-card:hover { border-color: var(--brand-2); }
.note-head { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.note-meta { display: flex; align-items: center; gap: 6px; min-width: 0; flex: 1 1 auto; }
.note-time { font-size: 11.5px; color: var(--faint); white-space: nowrap; }
.note-ops { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }
.note-ops .inline-form { display: inline; }
.note-op {
  font-size: 12px; padding: 1px 7px; border-radius: 6px; cursor: pointer;
  border: 1px solid var(--line); background: var(--line-2); color: var(--muted);
  text-decoration: none; line-height: 1.6;
}
.note-op:hover { background: var(--line); color: var(--ink); text-decoration: none; }
.note-op-del:hover { border-color: var(--red); color: var(--red); background: #FDF2F0; }

/* 点卡片进编辑（整块可点，但不用 <a> 包全文 —— 里面还有表单与链接） */
.note-hit { display: block; flex: 1 1 auto; min-height: 0; overflow: hidden; }
.note-hit:hover { text-decoration: none; }
.note-title {
  margin: 8px 0 5px; font-size: 14.5px; font-weight: 500; color: var(--ink);
  /* 标题最多两行，超出省略 —— 固定高度下不能让它无限长 */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.note-ex {
  margin: 6px 0 0; font-size: 13px; line-height: 1.6; color: var(--muted);
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
}
.note-ex-empty { color: var(--faint); font-style: italic; }
/* 图片：等比缩放到卡片宽，放在摘要下方（用户："图片缩放后，放在下面"） */
.note-thumb { margin-top: 8px; flex: 0 0 auto; overflow: hidden; border-radius: var(--radius-s); }
/* 🔴 v29 第 6 条：「笔记，首页显示的时候，里面的图片，和行程的类似，
   把图片按照比例缩放到固定的高度或者宽度，放在下面就行了。」
   ⇒ 固定高度 + **等比完整显示**（contain），不再 cover 裁切。
   ⚠️ 从 cover 改 contain 是有意的取舍：
      cover 能让每张图铺满、看着"实"，但**看不到完整内容**（横图两端被切掉）——
      用户要的是"按比例缩小放在下面"，那就是要看到**整张**。
      代价是宽高比与格子不一致时两侧留浅底，所以给个底色让留白显得是有意的。 */
.note-thumb img {
  width: 100%; height: 110px; object-fit: contain; display: block;
  background: #F6F4F0;
  border: 1px solid var(--line); border-radius: var(--radius-s);
}
.note-card .note-tags-inline { margin-top: auto; padding-top: 8px; flex: 0 0 auto; }
.note-card .note-tag { font-size: 11.5px; padding: 2px 7px; }

/* ---- 编辑页：模式提示条 ---- */
.note-modebar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid var(--line-2);
}
.note-modebar code {
  font-size: 11.5px; background: var(--line-2); padding: 1px 5px;
  border-radius: 4px; color: var(--ink);
}
.note-live { margin-left: auto; display: inline-flex; align-items: center; gap: 5px;
             font-size: 12.5px; color: var(--muted); cursor: pointer; }
.note-live input { accent-color: var(--brand); }

/* ---- 段落的 Markdown 实时预览块 ---- */
.note-block-pv {
  margin-top: 6px; padding: 0 2px; font-size: 14px; line-height: 1.7; color: var(--ink);
}
.note-block-pv:not(.is-on) { display: none; }
/* 预览区左边一条细线，与输入区在视觉上分开（不然分不清哪块是输入、哪块是结果） */
.note-block-pv.is-on {
  border-left: 3px solid var(--brand-soft, #F3D9CB);
  padding-left: 10px;
}

/* ---- Markdown 渲染出来的元素（列表页/预览共用同一套）---- */
.md-p { margin: 0 0 8px; }
.md-p:last-child { margin-bottom: 0; }
/* 🔴🔴 v85（用户 2026-10-24）：「还有日记功能，markdown 代码都没解析，
   `##` 后面的是标题，现在显示的还是和正文一样。」
   ------------------------------------------------------------------
   渲染**一直是对的**（`## x` → `<h4 class="md-h">`），问题全在**视觉**：
   ① 旧规则 `h4.md-h { font-size: 15px }`，而阅读态正文
      `.note-read-block { font-size: 15px }` —— **两者完全相同**；
   ② 标题只有 `font-weight: 500`，而 .md-h 继承了正文的行高与颜色
      ⇒ 用户看到的"标题"和"正文"逐像素一致，当然认为"没解析"。
   ⇒ 三条一起改，缺一条都还是"看不出是标题"：
      ① **字号必须拉开 ≥2px 且各级之间也要分级**（h4 < h5 那种倒挂是旧 bug：
         `##` 给的是 h4、`###` 给的是 h5，而旧表里 h4=15px、h5=14px 只差 1px）；
      ② **字重加到 600**（500 在中文无衬线里与常规几乎无差）；
      ③ **加上下边距**，让标题与正文之间出现明确的段落呼吸。
   ⚠️ 为什么从 h4 起而不是 h1/h2：见 `Markdown::render()` 里的说明 ——
      h1/h2 留给页面本身，笔记里的标题不许盖过页面标题。
      所以这里必须把 **h4/h5/h6 这三级**都调大，光调 h4 不够。

   🔴🔴 v93（2026-10-25，全量套件抓出来的**上一版没做完的地方**）：
   上一版只把 h4（`##`）做到 17px，**h5（`###`）留在 15.5px、h6 留在 14.5px**
   —— 15.5 只比正文 15 大 0.5px，肉眼仍是"和正文一样"；h6 更是**比正文还小**。
   而上面这段注释自己写的是"三级都要调大、要拉开 ≥2px"，实现违背了自己的规则。
   ⇒ 阶梯整体重排，**最小的一级也必须 ≥ 正文 + 2px**：
        h3 `#`   → 22px
        h4 `##`  → 20px
        h5 `###` → 18px
        h6 `####`起（Markdown 里 h4 及更深都收敛到 h6）→ 17px，另配 --muted 灰
      四级字号**两两不同**（A9d 守的就是"同级同号、异级不同号"），
      且 h6 靠灰色与字号双重区分 —— 否则 `####` 与正文挤在一起。
   ⚠️ 正文基准是 `.note-read-block { font-size: 15px }`（见下面那条规则）。
      将来正文若改号，这里的四级要**一起**复核，别只改 h4。 */
.md-h {
  margin: 16px 0 8px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.45;
}
.md-h:first-child { margin-top: 0; }
h3.md-h { font-size: 22px; }
h4.md-h { font-size: 20px; }
h5.md-h { font-size: 18px; }
h6.md-h { font-size: 17px; color: var(--muted); }
.md-list { margin: 4px 0 8px; padding-left: 22px; }
.md-li { margin: 2px 0; }
/* 多级清单：子层用空心点，层级一眼可分（用户第 12 条"清单可以多级"） */
.md-list .md-list { list-style-type: circle; margin: 2px 0 2px; }
.md-list .md-list .md-list { list-style-type: square; }
.md-quote {
  margin: 6px 0; padding: 4px 12px; border-left: 3px solid var(--line);
  color: var(--muted); background: var(--line-2); border-radius: 0 6px 6px 0;
}
.md-code {
  background: #F6F4F0; border: 1px solid var(--line); border-radius: 6px;
  padding: 8px 10px; overflow-x: auto; font-size: 12.5px; line-height: 1.6;
  font-family: ui-monospace, Consolas, monospace; color: var(--ink);
}
.md-icode {
  background: var(--line-2); padding: 1px 5px; border-radius: 4px;
  font-size: 12.5px; font-family: ui-monospace, Consolas, monospace;
}
.md-link { color: var(--brand); text-decoration: underline; }
.md-hr { border: 0; border-top: 1px solid var(--line); margin: 10px 0; }
/* 🔴 v61（用户 2026-10-18）：表格与任务清单 —— 与 Notion/GFM 同款的块形态。
   表格必须 overflow-x 包一层的可能交给外层（笔记卡有 padding，窄屏横滚更稳）；
   边框用 --line，深色主题下自动跟色，不写死颜色。 */
.md-table {
  border-collapse: collapse; margin: 6px 0 10px; width: auto; max-width: 100%;
  font-size: 13.5px;
}
.md-table .md-th, .md-table .md-td {
  border: 1px solid var(--line); padding: 4px 10px; text-align: left;
  vertical-align: top;
}
.md-table .md-th { background: var(--line-2); font-weight: 600; }
/* 任务清单：去掉圆点，用只读 checkbox 顶位（ul 的 padding 提供缩进） */
.md-task { list-style: none; }
.md-task .md-check { margin: 0 6px 0 0; accent-color: var(--brand); vertical-align: -2px; }

/* ---- 编辑页底部附件区 ---- */
.note-files { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
.note-file {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px; border: 1px solid var(--line); border-radius: var(--radius-s);
  background: #FDFCFA;
}
.note-file-ico {
  font-size: 11px; padding: 1px 6px; border-radius: 4px;
  background: var(--line-2); color: var(--muted); flex: 0 0 auto;
}
.note-file-nm { flex: 1 1 auto; min-width: 0; font-size: 13.5px; color: var(--ink);
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.note-file-del {
  flex: 0 0 auto; border: 1px solid var(--line); background: #fff; border-radius: 6px;
  color: var(--muted); cursor: pointer; padding: 2px 8px; font-size: 12px;
}
.note-file-del:hover { border-color: var(--red); color: var(--red); }
/* 拖放区：虚线框，与全站其它模块的上传区同一观感 */
.note-att-drop {
  border: 1px dashed var(--line); border-radius: var(--radius-s);
  padding: 14px; text-align: center; background: #FDFCFA;
}
.note-att-drop.is-over { border-color: var(--brand); background: var(--brand-soft, #FDF3EC); }
.note-att-drop input[type=file] { display: none; }
.note-att-tip { font-size: 13px; color: var(--muted); }
.note-att-tip label { color: var(--brand); cursor: pointer; text-decoration: underline; }

/* ---- 窄屏：网格收成一列（固定高度在小屏上会显得很挤）---- */
@media (max-width: 640px) {
  .note-grid { grid-template-columns: 1fr; }
  .note-card { height: auto; min-height: 150px; }
}

/* ---- v28 修：名册卡片在窄屏被"标签按钮"撑破（真机几何探针抓到）----
   根因：卡片右侧的动作区是一条 flex，里面那颗「标签」按钮的文字是
   **所有标签拼起来**（`#小学同学 #初中同学 …`），且 `white-space: nowrap` ——
   标签一多它就无限变宽，把整行、整张卡、整张网格一起顶出视口
   （实测 360px 下 scrollW=2090，越界元素就是那一串 <a>/<button>）。
   ⇒ 三件事一起做，缺一不可：
     ① 动作区**允许换行**（标签多了折到第二行，而不是往外顶）；
     ② 标签按钮**限最大宽度 + 溢出截断**（再长的标签也不会撑破）；
     ③ 卡片主体 `min-width: 0`（flex/grid 子项默认 min-width:auto，不肯收缩）。 */
.person-actions { flex-wrap: wrap; justify-content: flex-end; }
.person-tag-btn {
  max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.person { min-width: 0; }
.person-main { min-width: 0; }
.person .pb { min-width: 0; }
/* 窄屏更严：动作区整体不参与撑宽，标签按钮再收一档 */
@media (max-width: 720px) {
  .person-actions { max-width: 100%; }
  .person-tag-btn { max-width: 110px; }
  /* 姓名那一栏允许收缩并截断，否则长名字也会顶破 */
  .pname { overflow-wrap: anywhere; }
}
/* 🔴 v29 第 3 条（窄屏半边）：360px 下卡片只有 328px，
   [拖柄 24][头像 52 + 姓名][标签+编辑+删除 ≈197] 硬塞在一行 → 姓名区只剩 ~40px。
   ⇒ 窄屏把动作区**整体折到第二行**（卡片改纵向），姓名那一行就宽裕了。
      用 flex-direction: column 而不是给动作区定宽：后者的宽度随标签数变化，
      定了也会被长标签顶开（v28 就是这么栽的）。 */
@media (max-width: 640px) {
  .person { flex-wrap: wrap; }
  .person .person-main { flex: 1 1 calc(100% - 40px); }
  .person-actions {
    flex: 1 1 100%; justify-content: flex-start;
    padding: 0 10px 10px 34px;       /* 左边缩进到与姓名对齐（躲开拖柄） */
  }
  .person-tag-btn { max-width: 46vw; }
}

/* ============================================================
   v29 用户第 7 / 8 / 9 条：笔记 —— 阅读态 / 工具条 / 斜线命令 / 图片调大小
   ============================================================ */

/* ---- 阅读态（第 8 条：点进来默认是"看"）----
   与编辑态的区别要**一眼看得出**：没有虚线框、没有按钮，就是一篇排好版的文档。 */
/* 🔴 v47（用户 2026-10-13）：「标题和内容这个块的宽度并没有撑满整个页面，
   它和下面那个共享给谁、还有权限那一块，宽度是不一致的。」
   ⇒ 原来这里写着 `max-width: 820px`，而下面的「共享」卡是满宽的 ⇒ 上下两块对不齐。
   ⚠️ 不要改成"给共享卡也加 820px"：用户要的是**撑满**，不是一起变窄；
      正文卡与其它页面的 .card 保持同宽，视觉上才是一列。
   ⇒ 直接放开限宽（.card 本身已有全页统一的内边距与最大宽度约束）。 */
.note-read { max-width: none; }
/* ⚠️ v55：`.note-read-title` 已删 —— 标题只在**页头**显示一次（用户 2026-10-17 第 1 条
   「标题已经变成这个笔记的题目了，那下面那个里面就不用再显示这个题目了」）。
   留一条没有使用者的规则，只会让下一个人以为"正文里还该有个标题"。 */
.note-read-tags { margin: 0 0 14px; }
.note-read-body { display: flex; flex-direction: column; gap: 14px; }
/* 🔴 v52（用户第 4 条）：详情页的**分段编辑** —— 每一块外面包的这个容器。
   ⚠️ `position: relative` 是必须的：那个"鼠标划过才出来的编辑按钮"按它绝对定位到右上角。
   ⚠️ 不能给它背景色：它不是一张卡，只是"一块内容的边界框"，加底色会让正文看起来像被框住。 */
.note-read-item { position: relative; }
.note-read-item.is-editing { padding: 0; }
/* 编辑按钮：平时不可见（但**不能用 display:none** —— 那样焦点/无障碍都拿不到它），
   用 opacity + pointer-events 收起；鼠标进这一块、或键盘 Tab 到它，就浮出来。 */
.note-item-edit {
  position: absolute; top: 2px; right: 2px;
  padding: 2px 10px; font-size: 12.5px; line-height: 1.6;
  border: 1px solid var(--line); border-radius: 999px;
  background: #fff; color: var(--muted); cursor: pointer;
  opacity: 0; pointer-events: none; transition: opacity .12s;
}
.note-read-item:hover .note-item-edit,
.note-read-item:focus-within .note-item-edit { opacity: 1; pointer-events: auto; }
.note-item-edit:hover { border-color: var(--brand-2); color: var(--brand-ink); }
.note-read-item.is-editing .note-item-edit { display: none; }
/* 就地编辑框：与正文同宽、留出"我在改"的视觉边界 */
.note-item-editor { display: flex; flex-direction: column; gap: 8px; }
.note-item-ta { width: 100%; resize: vertical; font-size: 15px; line-height: 1.8; }
.note-item-ops { display: flex; align-items: center; gap: 8px; }
.note-item-hint { margin: 0; font-size: 13px; color: var(--muted); }
.note-read-block { font-size: 15px; line-height: 1.8; color: var(--ink); }
/* 图片：默认满宽，可调（内联 style 给百分比）；圆角与卡片一致 */
.note-read-fig { margin: 0; }
.note-read-fig img {
  max-width: 100%; height: auto; display: block;
  border: 1px solid var(--line); border-radius: var(--radius-s);
}
.note-read-fig a { display: inline-block; max-width: 100%; }
.note-read-h2 {
  margin: 0 0 8px; font-size: 14px; font-weight: 600; color: var(--muted);
  letter-spacing: .5px;
}
/* 附件区：一条细线把它与正文分开（"在最下方"要看得出来是**另一块**） */
.note-read-files { margin-top: 20px; padding-top: 14px; border-top: 1px solid var(--line-2); }
.note-read .note-share-now { margin-top: 16px; }

/* ---- 编辑态：Markdown 工具条（第 7 条"要么有工具条"）---- */
.note-cmdbar {
  display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
  padding: 6px 8px; margin-bottom: 8px;
  border: 1px solid var(--line); border-radius: var(--radius-s);
  background: #FDFCFA;
}
.note-cmdbar .cmd {
  min-width: 28px; height: 26px; padding: 0 7px;
  border: 1px solid var(--line); border-radius: 5px; background: #fff;
  color: var(--ink); cursor: pointer; font-size: 13px; line-height: 1;
}
.note-cmdbar .cmd:hover { border-color: var(--brand-2); color: var(--brand-ink); background: var(--brand-soft, #FDF3EC); }
.note-cmdbar .cmd-sep { width: 1px; height: 18px; background: var(--line); margin: 0 4px; }
.note-cmdbar .note-live { margin-left: auto; }

/* ---- 斜线命令菜单（第 7 条"要么有代码提示"）----
   ⚠️ 绝对定位在**块内**（不是 body）：跟着块走，块删除/移动时不会留孤儿菜单。 */
.note-block-body { position: relative; }
.slash-menu {
  position: absolute; left: 0; top: 100%; z-index: 30; display: none;
  min-width: 190px; max-height: 220px; overflow-y: auto;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius-s);
  box-shadow: var(--shadow); padding: 4px;
}
.slash-menu.is-on { display: block; }
.slash-item {
  display: flex; align-items: baseline; gap: 8px; width: 100%;
  padding: 5px 8px; border: 0; border-radius: 5px; background: transparent;
  cursor: pointer; text-align: left; font-size: 13px; color: var(--ink);
}
.slash-item b { color: var(--brand); font-family: ui-monospace, Consolas, monospace; }
.slash-item span { color: var(--muted); font-size: 12px; }
.slash-item:hover, .slash-item.is-on { background: var(--brand-soft, #FDF3EC); }

/* ---- 图片块：尺寸档位（第 8 条"图文混排时，需要可以调整大小"）---- */
.note-img-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.note-img-sizes { display: inline-flex; gap: 3px; margin-left: auto; }
.note-img-size {
  padding: 2px 7px; border: 1px solid var(--line); border-radius: 5px;
  background: #fff; color: var(--muted); cursor: pointer; font-size: 11.5px;
}
.note-img-size:hover { border-color: var(--brand-2); color: var(--brand-ink); }
.note-img-size.is-on { border-color: var(--brand); background: var(--brand-soft, #FDF3EC); color: var(--brand-ink); }
/* 图上标着当前档位，编辑器里一眼能看出"这张被缩过了" */
.note-block[data-kind="image"] .note-block-img { transition: width .12s ease; }

/* ============================================================
   通知消息（v30 第 5 条）
   ------------------------------------------------------------
   用户原话：「引入通知模块，这个模块，不在导航显示，在个人头像点击那里显示，
              放在个人设置前面，名字：通知消息。」

   ⚠️ 三处共用这一套样式：头像下拉里的入口、?r=notifications 页、首页那一块。
      分开写三遍迟早长歪（本项目在"设置下拉"上刚吃过这个亏）。
   ⚠️ 未读用**左侧一条品牌色竖线 + 浅底**表达，不用加粗字重 ——
      字重变化会让整行宽度轻微跳动，一列通知看下来眼睛累。
   ============================================================ */
/* 头像下拉里的入口：文案 + 未读角标（角标推到最右） */
.menu-list a:has(.menu-badge),
.menu-list a .menu-badge {
  display: inline-flex; align-items: center;
}
.menu-list a .menu-badge {
  margin-left: auto; min-width: 18px; height: 18px; padding: 0 5px;
  border-radius: 999px; background: var(--red); color: #fff;
  font-size: 11px; line-height: 18px; justify-content: center; font-weight: 600;
}

.ntf-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.ntf-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: var(--radius-s);
  border-left: 3px solid transparent;
}
.ntf-row:hover { background: var(--line-2); }
/* 未读：左侧品牌色竖线 + 极浅底（见上面的说明：不用字重） */
.ntf-row.is-unread { border-left-color: var(--brand); background: var(--brand-soft, #FDF6F1); }
.ntf-av {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
  object-fit: cover; background: var(--line-2);
}
.ntf-main {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 10px;
  color: var(--ink); text-decoration: none;
}
.ntf-main:hover { text-decoration: none; color: var(--brand); }
.ntf-title { flex: 1 1 auto; min-width: 0; font-size: 14px; overflow: hidden;
             text-overflow: ellipsis; white-space: nowrap; }
.ntf-title b { font-weight: 600; margin-right: 2px; }
.ntf-time { flex: 0 0 auto; font-size: 12px; color: var(--faint); font-variant-numeric: tabular-nums; }
.ntf-kind { flex: 0 0 auto; font-size: 15px; line-height: 1; }
.ntf-del { flex: 0 0 auto; }
/* 旧首页那块用的紧凑行距类 —— v71c 之后**没人用了**（通知已改成卡片，走 `.hc-ntf`）。
   留着这条是为了让"线上老页面截屏/老缓存"不突然变松；**新代码不要再挂这个类**。 */
.ntf-list-home .ntf-row { padding: 6px 8px; }

/* ---- v71c（用户 2026-10-21）：通知消息变成首页卡片 ----
   用户原话：「通知消息这个，也是首页的卡片，可以调整顺序和大小。另外，这个样式也
             不好看，我看个人头像点击后，通知消息那个页面的样式就不错，直接复用就可以了。」
   ⇒ 结构与 `.ntf-*` **完全复用**（头像在前、kind 图标在右），这里只补"卡片比整页小"
     带来的三条差异：
     ① `.hc-ntf` 的作用域里**允许正文折行** —— 卡片可能只有 2 格宽（一排四张里的一张），
        单行省略会把「有人把你加进了观影「…」」全吃掉，只剩一个名字（那就是"不好看"）；
     ② 时间挪到正文下面自己一行（窄卡里横着排会把正文挤没）；
     ③ 未读数做成一条小注脚（原来它挂在卡片外的 `<h1>` 上，卡片里没有那个位置）。
   ⚠️ 只在 `.hc-ntf` 下改，**不影响**通知页那一整页（那里宽度足够，单行更好读）。 */
.hc-ntf .ntf-row { align-items: flex-start; }
.hc-ntf .ntf-main { flex-direction: column; align-items: stretch; gap: 1px; }
.hc-ntf .ntf-title {
  white-space: normal;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  line-height: 1.4; word-break: break-word;
}
.hc-ntf .ntf-time { font-size: 11.5px; }
.hc-ntf .ntf-kind { margin-top: 2px; }
.hc-ntf-note {
  margin: 0 0 6px; font-size: 12px; color: var(--brand-ink, var(--brand));
  font-variant-numeric: tabular-nums;
}

@media (max-width: 640px) {
  /* 窄屏把时间挪到下一行，别把标题挤成两三个字 */
  .ntf-main { flex-wrap: wrap; gap: 2px 10px; }
  .ntf-title { flex: 1 1 100%; }
}

/* ============================================================================
   v37(2026-10-09) —— 弹框：按钮位置 / 标签加粗 / 观影弹框宽度
   ----------------------------------------------------------------------------
   用户原话（4 条，都指向"弹框"）：
     ⑤「日程模块的新建日程里，弹出一个对话框。但是"加上"和"取消"两个按钮在
        右上角，离得特别远，根本看不到。应该把它们放在下方，或者放在"新建日程"
        标题的右侧，不要放那么远。
        另外，每个输入框前面的标题都太小了…比如"放在哪本""标题""地点""提醒"这些，
        都应该加粗、变大，这样跟里面的内容产生一些差异，看起来更醒目一些。」
     ⑥「新建任务也和新建日程一样，新建时弹出一个框…」
   ============================================================================ */

/* 🔴🔴 日程弹框"按钮跑到右上角、还根本看不到"的真根因（实测定位，不是猜）：
   `.cal-modal-panel` 是 flex 列（head / form / foot 三段），但**它下面的 <form>
   不是** —— form 是块级元素，`cal-modal-body{flex:1 1 auto}` 的 flex 上下文
   只到 form 为止。于是 form 的高度 = head + 内容**全部**高度，而面板 `max-height`
   又把外层卡住 ⇒ foot 被挤出面板可视区，出现在**面板底部之外**，
   而面板本身不滚（滚的是 body）→ 用户看到的就是"按钮飘在很远的地方 / 看不见"。
   ⇒ 让 form 也参与那个 flex 列、body 才能真正被压缩到可滚动。 */
.cal-modal-panel > form {
  display: flex; flex-direction: column;
  flex: 1 1 auto; min-height: 0;
  overflow: hidden;   /* 圆角裁切：body 滚动时不要顶出面板 */
}
/* ⚠️ v37 起"加上"主键改用 `form="calAddForm"` 关联 ——
      它不再长在 form 内部，`:not()` 那条老规则会失效，这里按 id 补回。 */
.cal-modal-foot .btn[type="submit"],
.cal-modal-foot #calAddSubmit { margin-left: auto; }

/* ---- 弹框里的字段标签：加粗、变大、深色 ----
   用户原话：「每个输入框前面的标题都太小了…都应该加粗、变大，这样跟里面的内容
              产生一些差异，看起来更醒目一些。」
   ⚠️ 只改**弹框里**的（.cal-modal / .pp-modal 等浮层面板内）：
      页面内的普通表单标签维持原样，避免"改一处动了全站"。
   ⚠️ 用 `.cal-modal .field > label` 而不是 `.cal-modal label`：
      后者会连"提醒"那排 checkbox 的每个小标签一起加粗，看起来一片糊。 */
.cal-modal .field > label,
.cal-modal .field > .dt-range-label,
.cn-panel .field > label,
.pp-modal .field > label {
  font-size: 14px; font-weight: 600; color: var(--ink);
}

/* ---- 观影"记一场电影"弹框 ----
   ⚠️ 字段比日程多得多（海报 + 观影照片 + 简介 + 同行人…），620px 太窄了，
      用 760px 让两列字段摆得开；高度放到 88vh，长表单才有足够的可视区。 */
.cal-modal-panel.mv-panel { width: min(760px, 100%); max-height: calc(100vh - 64px); }
.mv-form-inner { padding: 16px; }   /* 弹框里的内边距（编辑态用的是 .card-pad） */
/* 弹框形态下，页面上那条"记下这场电影 / 取消"一整行是多余的（foot 里已有），
   但它在**编辑态**必须留着 —— JS 没跑时它是唯一的提交入口。 */
.mv-foot-inner { display: none; }
@media (max-width: 640px) {
  .cal-modal-panel.mv-panel { width: 100%; }
}

/* ---- 窄屏：弹框字段全部单列（form-grid 是两列，手机上会把标签挤成两个字） ---- */
@media (max-width: 560px) {
  .cal-modal .form-grid { grid-template-columns: 1fr; }
}

/* ============================================================================
   v38(2026-10-10) 观影"记一场电影"弹框：标签内联排版 + Ant Design 日期控件
   ----------------------------------------------------------------------------
   用户原话（两条，一起改的）：
     「记录一场电影，这个弹框因为标题和下面的输入框都是两行的，所以导致弹框非常长。
       能不能排排版，让前面的标题后面加个冒号，后面的输入框直接跟在它后面，
       这样就变成一行。包括开始、结束时间、影院、同行的人、简介等等，能把它排成
       一行的都排成一行，但是一定要注意对齐的问题，如果不对齐的话可能不好看。」
     「我终于找到了一个我比较认可的日期选择插件
       https://ant-design.antgroup.com/components/date-picker-cn 现在观影这个模块
       进行测试吧…等这个模块测试完没问题了，再更新到其他的模块。」
   ============================================================================ */

/* ---- ① 「标签：控件」内联网格 ----
   🔴 对齐靠的是**固定宽度的标签列**（62px + 右对齐），不是靠调间距 ——
      标签长短差很多（片名/时间/影院/同行的人），但它们冒号后的控件左缘
      必须落在同一条竖线上，看起来才整齐。
   ⚠️ 标签**自带冒号**（CSS 的 ::after 给）—— 让每个模板都手写"："迟早漏一个。
   ⚠️ `> .mv-lb` 而**不是** `.mv-row label`：后者会把同行人那排 checkbox 的
      小标签一起卷进来（它们不是"字段标签"）。 */
.mv-grid { display: flex; flex-direction: column; gap: 12px; }
/* 🔴🔴 v53（用户 2026-10-16 第 3 条）：「点击一个日程，弹出简介，点击编辑，
   新的编辑框，**只有左侧有内容，右侧是空白的，好像分栏了一样**。这个样式不太对。」

   真根因：日程编辑页（`templates/calendar/event.php`）里，`.mv-grid`（观影式的**纵向**列表）
   被嵌在**两列**的 `.form-grid` 里：
     `<div class="form-grid">`  ← `grid-template-columns: 1fr 1fr`
       `<div class="mv-grid">`  ← 一个 grid 项，**只占第一列**
   在整页形态下不明显（页面宽，两列各 ~700px 还能看）；但**浮层里面板只有 620px**
   ⇒ 两列各 286px，而所有字段都挤在左边那一列 286px 里，右边整列空着 —— 就是用户看到的"分栏"。
   （实测：panel 620 / form-grid 588 / cols="286px 286px" / mv-grid w=286。）

   ⇒ 让 `.mv-grid` **跨满整行**（`grid-column: 1 / -1`）。
      它本身是 `<div>` 包着**纵向堆叠**的 `.mv-row`，语义上就是"一整块、占满宽度"，
      天生不该被塞进半列。这样两种形态（整页 / 浮层）都正确，
      而且不必去改 `event.php` 里"整页保留两列"的结构判断（那条注释说明整页形态是有意保留的）。 */
.form-grid > .mv-grid { grid-column: 1 / -1; }
.mv-row { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }
.mv-row > .mv-lb {
  flex: 0 0 62px; width: 62px;              /* 固定宽 = 对齐的基准 */
  text-align: right; padding-top: 9px;      /* 与 40px 高输入框的文字基线对齐 */
  font-size: 14px; font-weight: 600; color: var(--ink);
  line-height: 1.4; word-break: keep-all;
}
.mv-row > .mv-lb::after { content: '：'; font-weight: 400; color: var(--muted); }
/* 🔴 v94 起星号不再使用（见文件下方「必填项统一标记」那段）；这两条留着**不删**：
   万一还有没清干净的 `<i>*</i>`，它会渲染成紧贴的红色星号、不会露成一个灰星花，
   而静态断言会把它当失败报出来 —— 比"悄悄留个灰星花"好定位。 */
.mv-row > .mv-lb > i { font-style: normal; color: var(--red, #C0392B); margin-left: 1px; }
/* 控件区：占满剩余宽度（min-width:0 让它能被压缩，不会把标签挤出去） */
.mv-row > *:not(.mv-lb) { flex: 1 1 auto; min-width: 0; }
/* 片名那一行：输入框 + 「自动获取」按钮并排 */
.mv-row .db-title-row { display: flex; gap: 8px; align-items: center; }
.mv-row .db-title-row .input { flex: 1 1 auto; min-width: 0; }
.mv-row .db-title-row .btn { flex: 0 0 auto; }
/* 影院那一行：自定义下拉 + 「新建影院」并排 */
.mv-row .cinema-select-row { display: flex; gap: 8px; align-items: center; }
.mv-row .cinema-select-row .cs { flex: 1 1 auto; min-width: 0; }

/* 时间那一行是 `.mv-row.dt-range`：它既是"标签+控件"的行，又是**范围组**。
   ⚠️ 这两重身份会打架：`.dt-range.dt-cols` 是 `flex-direction: row`，
      会把标签也当成"一个端点"排进去。⇒ 这里把它拉回"标签一行、两端在下"，
      标签仍占固定 62px（与上面的字段对齐）。 */
.mv-row.dt-range.dt-cols { flex-direction: row; flex-wrap: wrap; align-items: flex-start; }
.mv-row.dt-range.dt-cols > .dt-range-label { flex: 0 0 62px; width: 62px; }
/* 🔴 v38 对齐修正（真机量出来的 8px 偏差）：
   `.dt-cols` 是 `gap: 10px 18px`（行距 10 / **列距 18**），而普通 `.mv-row`
   只有 `gap: 10px`。于是"标签 → 控件"这段在时间行是 18px、在别的行是 10px
   ⇒ 控件左缘差了 8px（实测 452 vs 444），一眼就看得出没对齐。
   ⇒ 时间行的列距拉回 10px，与别的行一致。 */
.mv-row.dt-range.dt-cols { column-gap: 10px; }
/* 两端并排（沿用 v37 的 .dt-cols 规则），并**从标签右边开始** */
.mv-row.dt-range.dt-cols > .dt-input {
  flex: 1 1 0; min-width: 220px; margin-top: 2px;
}
.mv-row.dt-range .err { flex: 1 1 100%; margin-left: 72px; }

/* 海报 / 观影照片：**与简介同一套 `.mv-row`**（标签在左、内容在右）。
   🔴🔴 v39（用户 2026-10-10）：「海报和观影照片为什么和上面的标题不一样呢？
      海报的字体和简介是一样的，也带一个冒号。然后把后面点击或拖动进行上传往后放，
      和简介后面的输入框保持一样大小。观影照片…也是加粗。和简介的字体保持一致，
      不带冒号。]

   ⇒ 关键认识：**这两块不需要任何特殊规则**，它们就是普通字段行 ——
      标签 = `.mv-lb`（继承上面那条：14px / 600 / var(--ink) / 固定 62px / 右对齐
      + `::after` 冒号），内容 = 行里唯一参与弹性布局的那一项（占满剩余、左缘 444）。
      v38 之所以把它们写成 `.mv-media`（另起一行 + 手填 `margin-left:72px`），
      是因为当时**探针取错了元素** —— 它按文档序取"第一个非标签子元素"，
      而 `.sr-only` 的文件框是 `position:absolute`（**脱离文档流、不参与 flex**），
      却被算成了"控件"，于是量出"1px 隐框把上传框推走 130px"这个**假结论**。
      ⇒ 这轮改用真机逐行量左缘来定，不再靠推断。

   ⚠️ 想让它别再"另起一行"：这里**不设** flex-direction —— `.mv-row` 本来就是
      `display:flex; align-items:flex-start`，标签与内容天然同一行。 */
.mv-grid-media { margin-top: 2px; gap: 14px; }
/* 内容区：纵向排（缩略图 + 上传框），并**占满**标签右侧的剩余宽度
   （`flex:1 1 auto` + `min-width:0` 与 `.mv-row > *:not(.mv-lb)` 那条一致，
   这里显式写一遍是为了让"它的宽度 = 简介输入框的宽度"这件事一眼可读）。 */
.mv-row-media > .mv-media-body {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; gap: 8px;
}
.mv-media-body .photo-grid { margin-top: 0; }
/* 观影照片的标签**不带冒号**（用户原话：「观影照片也是加粗。和简介的字体保持一致，
   不带冒号。」）—— 这类"有标签但不带冒号"的情况靠一个类开关，别去改 ::after 的全局规则。 */
.mv-lb.no-colon::after { content: none; }
/* ⚠️ `.sr-only` 是**绝对定位**的（见 .sr-only 的定义）；它不该影响任何布局。
   这里再钉一次"不参与 flex 分配"（万一将来有人把 .sr-only 的定义改了）。 */
.mv-media-body > .sr-only,
.mv-row > .sr-only { position: absolute; flex: 0 0 0; }

/* ---- ② Ant Design RangePicker 的外观对齐 ----
   目标是"看起来就是站内的一个输入框"，而不是"嵌进来一个外来控件"：
   同高(40px)、同圆角、同描边、同纸色。 */
.dt-antd { flex: 1 1 auto; min-width: 0; }
.dt-antd .ant-picker {
  width: 100%; height: 40px; padding: 0 11px;
  border: 1px solid var(--line); border-radius: var(--radius-s);
  background: var(--surface); box-shadow: none;
}
.dt-antd .ant-picker:hover { border-color: var(--brand-2); }
.dt-antd .ant-picker-focused { border-color: var(--brand); box-shadow: none; }
.dt-antd .ant-picker-input > input { font: inherit; font-size: 14px; color: var(--ink); }
/* 品牌色覆盖：让 antd 的选中/主色跟着模块色走（与 Semi 那次同一思路） */
.dt-antd .ant-picker-range-separator { color: var(--muted); }
.dt-antd .ant-picker-cell-in-view.ant-picker-cell-selected .ant-picker-cell-inner,
.dt-antd .ant-picker-cell-in-view.ant-picker-cell-range-start .ant-picker-cell-inner,
.dt-antd .ant-picker-cell-in-view.ant-picker-cell-range-end .ant-picker-cell-inner {
  background: var(--brand) !important;
}
/* 中间的"区间底色"用模块色的浅版 */
.dt-antd .ant-picker-cell-in-view.ant-picker-cell-in-range::before,
.dt-antd .ant-picker-cell-in-view.ant-picker-cell-range-hover::before {
  background: color-mix(in srgb, var(--brand) 12%, transparent) !important;
}
.dt-antd .ant-picker-time-panel-column > li.ant-picker-time-panel-cell-selected .ant-picker-time-panel-cell-inner {
  background: color-mix(in srgb, var(--brand) 14%, transparent) !important;
}
.dt-antd .ant-btn-primary { background: var(--brand); border-color: var(--brand); }
.dt-antd .ant-btn-primary:hover { background: var(--brand-2) !important; border-color: var(--brand-2) !important; }
/* 面板：跟着纸色（antd 默认是纯白 + 冷灰边） */
.dt-antd .ant-picker-dropdown .ant-picker-panel-container {
  border: 1px solid var(--line); box-shadow: var(--shadow-l, 0 10px 30px rgba(0,0,0,.14));
}
/* 面板整体跟着模块色（antd 的 CSS-in-JS 变量） */
.ant-picker-dropdown {
  --ant-color-primary: var(--brand, #A84A28);
  --ant-color-primary-hover: var(--brand-2, #C05F38);
}
/* ⚠️ 挂上 antd 之后：**原生那对下拉收起来**（它们仍然在 DOM 里当值载体）。
   ⚠️ 用 visibility+绝对定位而不是 display:none —— 见 v37 那次的教训：
      display:none 的控件在表单校验时不可聚焦；这里虽然已摘掉 required，
      但保持"可聚焦"更稳（免得将来再加必填时又踩同一个坑）。

   🔴🔴 v44（用户 2026-10-12 第 2 条）：「之前的日期选择控件还有，需要删除。」
      —— 用户在**日程**上看到 antd 控件与原生五个下拉**并排都在**。真根因是
      **结构性**的：日程那对是 `‹div.dt-range› ‹div.field.ev-dt› ‹div.dt-input›`
      （`.dt-input` 是**孙子**），而这条规则用的是**直接子**选择器 `> .dt-input`
      ⇒ **在日程上根本不命中**。
      （与 v43 纪念日那次同族：结构一变，直接子选择器静默失效、不报任何错。）
      ⇒ ① 把 `>` 放宽成**后代**选择器（`_event_fields.php` 那边的结构也已改回
            "与观影一致"的直接子，两道保险）；
         ② 用户要的"删除"就做成真删 —— 用 `display:none` 把**原生下拉那一块**
            从渲染里去掉。**注意：值载体不受影响** —— 隐藏的 `*_date` / `*_hour`
            / `*_minute` 是 `.dt-input` 的**直接子元素**，**不在 `.dt-native` 里**，
            所以删掉原生块**不碰提交契约**。
            （v37 那条"display:none 不可聚焦"讲的是**带 required 的可见字段**；
              这里被藏的是"已被 antd 取代的旧控件"，必填校验早已由
              `guardRequired` 的 `data-required` 自查接管 ⇒ 不适用那个顾虑。） */
.dt-range.is-antd .dt-input {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  border: 0; padding: 0; margin: -1px;
}
/* antd 真的挂上了 ⇒ 原生那几档下拉**彻底不渲染**（用户要的"删除"）。 */
.dt-range.is-antd .dt-input .dt-native { display: none !important; }

/* 🔴🔴 v44：**带内联标签的那个端点不收起**。
   根因（真机几何量出来的）：内联标签（「日期范围：」）是画在 `.dt-input` **里面**的，
   而上面那条收起规则把整个 `.dt-input` 绝对定位 + `clip` 掉了 ⇒
   **标签跟着一起被裁没了**，用户只看到一个没有标签的控件
   （量出来的证据：标签盒在 x=316/y=313 却被祖先 clip，`elementFromPoint`
   命中的是 `.ant-picker-range-separator` ⇒ 标签确实看不见）。
   ⇒ 两头都不牺牲：`.dt-input` 只藏**它里面的原生块**（上一条已 `display:none`），
      自己保持正常布局 ⇒ 它变成 `[标签 62px][最新控件]` 的一个横排。
      起止的**第二个**端点没有标签，继续按上一条被收起（它本来就该消失）。 */
.dt-range.is-antd > .dt-input.has-inline-label {
  position: static; width: auto; height: auto; overflow: visible;
  clip: auto; margin: 0; padding: 0; border: 0;
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
}

/* 🔴 v44（用户第 5 条）：**单个**带时刻的 antd 控件（任务「截止时间」）。
   与上面那对同一条思路 —— 挂上后把原生那几档下拉**真删掉**。
   选择器用 `.dt-input.is-antd`（单点，不是范围组）——
   `_datetime.php` 里 antd 挂载点仍是 `.dt-input` 自身。 */
.dt-input.is-antd > .dt-native { display: none !important; }
.dt-input.is-antd > .dt-antd { display: block; width: 100%; }
/* 窄屏：标签列收窄，别把输入框挤没了 */
@media (max-width: 560px) {
  .mv-row > .mv-lb,
  .mv-row.dt-range.dt-cols > .dt-range-label { flex: 0 0 52px; width: 52px; font-size: 13px; }
}

/* ---- 🔴 v39：撤销 v38 的"并排对" ----
   用户 2026-10-10：「简介和备注不要放在一行，也就是说一个字段它就占一行。
   所以简介后面是一行，备注后面是一行。」—— 简介/备注各占一整行。
   ⇒ `.mv-pair` 那套（grid 两列）整个删掉：它是 v38 为了压高度加的，
      但半宽的多行文本框反而每行都换行、更难读。海报/观影照片也不再并排
      （它们本来就只有一块，当时的"并排网格"里只有一个孩子，
       grid 让它只占半宽 —— 那正是"海报的上传框比简介窄"的原因）。
   ⚠️ 不留悬念：全站搜 `.mv-pair` 应该**零命中**（模板与 CSS 都清干净）。 */

/* ============================================================================
   🔴 v40（用户 2026-10-10）观影模块四处界面反馈
   ============================================================================ */

/* ---- ① 影院管理表格：操作列不再被挤成两行 ----
   用户原话：「影院管理部分，注意控制列的宽度，因为最右侧的操作、编辑和删除两个按钮，
   已经被挤成两行了，尝试让编辑和删除排在一行，宽度给它留够。」
   🔴 真因：`.table` 是 `width:100%` + 自动布局，浏览器按内容分配列宽 ——
      长地址那一列会把「操作」列压到只剩几十像素，两个按钮只好换行。
   ⇒ 两件事一起做才稳：
      ① 表格改 `table-layout: fixed`（列宽由我们给，不再被内容牵着走）；
      ② 「操作」列给一个**够放两个按钮的最小宽度**（编辑 48 + 间距 8 + 删除 48
         + 单元格左右 padding 16 ≈ 128，留到 132 更从容）。
   ⚠️ 为什么不只是 `white-space:nowrap`：那会把列撑成"刚好够"，
      但**表格还是按内容分配**，窗口一窄就又轮到别的列挤压它；
      固定布局才是真的把这件事钉死。 */
.cn-table { table-layout: fixed; }
.cn-table .cn-col-name  { width: 30%; }
.cn-table .cn-col-place { width: 15%; }
.cn-table .cn-col-addr  { width: 32%; }
.cn-table .cn-col-count { width: 10%; }
.cn-table .cn-col-ops   { width: 132px; }
.cn-table td.cn-addr { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cn-ops {
  display: flex; align-items: center; gap: 8px;
  /* 🔴 不换行是本条的正题 */
  flex-wrap: nowrap; white-space: nowrap;
}
.cn-ops > form { display: inline-flex; margin: 0; flex: 0 0 auto; }
.cn-ops > .btn { flex: 0 0 auto; }
/* 窄屏：地址列让位（它最长、也最不影响判断），操作列宽度保持不变 */
@media (max-width: 720px) {
  .cn-table .cn-col-name  { width: 34%; }
  .cn-table .cn-col-place { width: 18%; }
  .cn-table .cn-col-addr  { width: 22%; }
  .cn-table .cn-col-count { width: 12%; }
}

/* ---- ② 实心语义按钮：编辑=蓝、删除=红 ----
   ⚠️ 单独定义（不去改 `.btn-danger` 的既有语义）：`.btn-danger` 是**淡红描边**的
      "危险动作"，全站十来处「删除」都在用它；用户这次要的是**实心色块**，
      改 `.btn-danger` 会让所有页面一起变样。
   三处都要写：裸类 / `a.` / `button.` —— 与页头动作区那套同一个理由
   （`.btn`、`a.btn`、`button.btn` 特异性不同，漏一种就有一种是错的）。 */
.btn-edit, a.btn-edit, button.btn-edit {
  background: var(--blue); color: #fff; border-color: transparent;
}
.btn-del, a.btn-del, button.btn-del {
  background: var(--red); color: #fff; border-color: transparent;
}
/* ⚠️ hover 统一交给文件靠后的「全站悬停统一」段（有底色的一律同色系加深）——
   但那段是按 `.btn-ok/.btn-del/...` 的类名白名单写的，`.btn-edit` 是新的，
   要在这里补一条，否则它会退回 `.btn` 的默认（品牌色底），看着像变了个按钮。 */
.btn-edit:hover { filter: brightness(.92); }

/* ---- ③ 观影详情页 = 列表页的头部口径 ----
   用户原话：「观影详情页面左上角的观影记录，应该和观影的列表页面里面保持一致。」
   列表页是 `<h1>观影记录</h1>` + sub；详情页原来只有一个 `<span class="sub">`，
   没有 h1 ⇒ 左上角看起来"不像是同一件事"。模板已补上 h1，
   这里只处理**窄屏**：h1 与 sub 在页头会挤，sub 允许收缩并省略。 */
.page-head > .sub { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- ④ 高级搜索的日期范围：换成 AntD RangePicker（纯日期）----
   🔴 v40（用户 2026-10-10）：「高级搜索里面的日期范围，也使用我们最新的控件。」
   挂上之后把**原生那两个日期框收起来**（它们仍是 GET 字段，只是不显示）。
   ⚠️ 与原观影那对 `.dt-range.is-antd > .dt-input` 用同一套"clip 收起"手法：
      不用 `display:none`（保持可聚焦，避免将来加 required 时不可聚焦的坑）。 */
.date-antd-field .date-range.is-antd > .input,
.date-antd-field .date-range.is-antd > .to {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  border: 0; padding: 0; margin: -1px;
}
.date-antd-field .date-range.is-antd { display: block; }
/* 纯日期版控件与观影那边同款外观（同高、同圆角、同描边）—— 复用 .dt-antd 的规则，
   这里只补"它是块级、占满字段宽度"这一点（.dt-antd 原为 flex 子项）。 */
.dt-antd-dateonly { display: block; width: 100%; }

/* ============================================================================
   🔴 v41（用户 2026-10-10）旅程详情页的四个动作按钮：计划=蓝 / 实际=绿 / 编辑=黄 / 删除=红
   ============================================================================
   为什么四种都要在**页头动作区那一层**再写一遍：
     页头有一套"特异性更高"的规则（`.page-head .actions > .btn:not(.btn-icon)` 等，
     见文件前部的页头按钮层），它们会盖过基类的 `.btn-xxx` 底色。
     v40 的 `.btn-del` 就吃过这一脚（必须在页头层再补一条才生效）。
   ⚠️ 三处形态都要写（裸类 / a. / button.）：`.btn`、`a.btn`、`button.btn`
      的特异性不同，漏一种就有一种是错的（这条规律在 v40 已经验证过）。
   ⚠️ 设置按钮**不动**（用户："设置就用这个图标，不要变"）。 */
.btn-plan, a.btn-plan, button.btn-plan {
  background: var(--blue); color: #fff; border-color: transparent;
}
.btn-actual, a.btn-actual, button.btn-actual {
  /* 绿比 --green 深一档：白字对比度 3.0 → 4.6，暖纸底上看得清 */
  background: #4E7C3F; color: #fff; border-color: transparent;
}
/* 🔴🔴 2026-10-11 修（v40 套件报的**真回归**）：
   这里原来写的是**裸类** `.btn-edit`（全局改黄），直接把 v40 影院管理页的
   「编辑 = 蓝色实心」覆盖掉了 —— 影院那页量出来的实际色是 `rgb(184,134,11)`（黄），
   而用户对它的要求是**蓝色**（v40 原话：「编辑使用蓝色的背景，删除使用红色的背景」）。
   ⇒ 两处需求不同：**旅程详情页的「编辑」= 黄**（v41 第 4 条），
     **影院管理页的「编辑」= 蓝**（v40）。所以颜色必须**按作用域**给，不能裸类全局设。
   锚点选 `[data-trip-edit]`（旅程详情页那个按钮的专属属性）——
   它是最精确的标识，不依赖页头层级，也不会波及影院管理页的 `<a class="btn btn-edit">`。 */
.btn-edit[data-trip-edit] {
  background: var(--yellow); color: #fff; border-color: transparent;
}
.btn-del, a.btn-del, button.btn-del {
  background: var(--red); color: #fff; border-color: transparent;
}
/* hover：同色系加深（与文末"全站悬停统一"段同一手法：brightness 压低） */
.btn-plan:hover, .btn-actual:hover, .btn-edit:hover, .btn-del:hover { filter: brightness(.92); }
/* 页头动作区那一层（覆盖页头默认的"品牌色实心/ghost"） */
.page-head .actions > .btn-plan:not(.btn-icon),
.page-head .actions > .btn-actual:not(.btn-icon),
.page-head .actions > .btn-edit[data-trip-edit]:not(.btn-icon),
.page-head .actions > .btn-del:not(.btn-icon),
.page-head .actions > form > button.btn-del:not(.btn-icon) { color: #fff; border-color: transparent; }
.page-head .actions > .btn-plan:not(.btn-icon)   { background: var(--blue); }
.page-head .actions > .btn-actual:not(.btn-icon) { background: #4E7C3F; }
.page-head .actions > .btn-edit[data-trip-edit]:not(.btn-icon) { background: var(--yellow); }
.page-head .actions > .btn-del:not(.btn-icon),
.page-head .actions > form > button.btn-del:not(.btn-icon) { background: var(--red); }

/* ---- v41 第 8 条：交通方式的"标识"（车次号 / 车牌号）紧跟模式词 ----
   用户：「把火车的车次号直接放在"火车"这两个字后面。如果是自驾，
          就应该把自驾的车牌号放在后面。」
   ⇒ 同一个 chip 内、模式词之后，用略淡的字重区分（仍属同一个信息单元）。 */
.node-mode-no {
  margin-left: 6px; padding-left: 6px;
  border-left: 1px solid currentColor;
  opacity: .78; font-weight: 500;
  font-variant-numeric: tabular-nums;
}

/* ---- v41 第 2 条：题图的说明文字搬进框里（原来在框外另起一段）---- */
.cover-picker-hint {
  display: block; margin-top: 8px;
  font-size: 12.5px; line-height: 1.6; color: var(--muted);
}

/* ============================================================================
   🔴 v41（用户 2026-10-10 第 5/6 条）行程弹框的**观影式排版**
   ============================================================================
   用户原话：「编辑行程、加计划行程、加实际行程，全部都采用弹窗的形式。
             其排版的方式和添加电影是一样的，前面是标题冒号，后面是输入框，
             和观影模块一样，注意排版。」

   🔴🔴 为什么这里用 **grid** 而不是 flex（2026-10-10 血泪）：
     先按观影那套写了 flex（`display:flex; flex-wrap:wrap` + label 固定 62px），
     结果**控件被挤到下一行**（实测 label y=117 / 控件 y=187）。
     查了半天：`.input/.select` 全站有 `width: 100%`，在 flex 行里
     它的 auto 基准就是**整行宽** ⇒ 62 + 10 + 696 > 696 ⇒ 换行。
     把 width 改 auto、把 flex-basis 从 0% 试到 auto、加 min-width……
     控件宽度是对了（140），**换行依旧**（同一行里还有 `.tip` 声明着
     `flex: 0 0 100%`，wrap 算法会把它们的理想宽度一起算）。
     ⇒ 换 **grid**：`.field` 两列（62px + 1fr），标签在第 1 列、控件在第 2 列，
       `.tip/.err` 用 `grid-column: 2` 自动落在控件下面**且与控件左缘对齐**
       （连 margin-left 都不需要）。两列表单的标准写法，不受 width/flex 交互影响。

   ⚠️ 作用域用 `.nf-form`（表单自己的类）而不是 `.nf-wrap` / `.nf-panel`：
      片段形态（`?embed=1`）两者都不输出（壳由详情页容器给），
      只用它们的话排版在弹框里**完全不生效**（探针抓到过）。 */
.nf-form .form-grid {
  display: flex; flex-direction: column; gap: 12px;
}
.nf-form .form-grid > .field {
  display: grid; grid-template-columns: 62px minmax(0, 1fr);
  column-gap: 10px; row-gap: 6px; align-items: start;
}
/* 标签：固定第 1 列、右对齐、自动冒号（与观影那套同一组数字） */
.nf-form .form-grid > .field > label {
  grid-column: 1;
  text-align: right; padding-top: 9px;
  font-size: 14px; font-weight: 600; color: var(--ink);
  line-height: 1.4; word-break: keep-all;
}
.nf-form .form-grid > .field > label::after {
  content: '：'; font-weight: 400; color: var(--muted);
}
/* 控件：占第 2 列（宽度由 grid 给，不受它的 width:100% 影响） */
.nf-form .form-grid > .field > *:not(label) {
  grid-column: 2; min-width: 0;
}
/* 提示 / 错误：也在第 2 列 ⇒ **自动与控件左缘对齐** */
.nf-form .form-grid > .field > .tip,
.nf-form .form-grid > .field > .err {
  grid-column: 2;
}
/* 起终点是两列小网格，整段落在第 2 列 */
.nf-form .form-grid > .field > .loc-row { grid-column: 2; }
/* 「当前状态」是只读文本，对齐 40px 高的控件 */
.nf-form .node-stage-ro { padding-top: 9px; }
/* 窄屏：标签列压到 52px，避免控件被挤得太窄 */
@media (max-width: 560px) {
  .nf-form .form-grid > .field { grid-template-columns: 52px minmax(0, 1fr); }
}

/* ---- v41 第 8 条：交通方式的"标识"（车次号 / 车牌号）紧跟模式词 ----
   用户：「把火车的车次号直接放在"火车"这两个字后面。如果是自驾，
          就应该把自驾的车牌号放在后面。」
   ⇒ 同一个 chip 内、模式词之后，用略淡的字重区分（仍属同一个信息单元）。 */
.node-mode-no {
  margin-left: 6px; padding-left: 6px;
  border-left: 1px solid currentColor;
  opacity: .78; font-weight: 500;
  font-variant-numeric: tabular-nums;
}

/* ---- v41 第 2 条：题图的说明文字搬进框里（原来在框外另起一段）---- */
.cover-picker-hint {
  display: block; margin-top: 8px;
  font-size: 12.5px; line-height: 1.6; color: var(--muted);
}


/* ---- v41 第 10 条：纪念日弹框（观影式排版）里"月 / 日"那一行的两格 ---- */
.an-md { display: flex; align-items: center; gap: 6px; }
.an-md .input { width: 88px; flex: 0 0 auto; text-align: center; }
.an-md-sep { color: var(--muted); font-size: 13px; }
/* 观影式行里的"填充块"（选人组件那种自带布局的控件，占满控件位） */
.mv-row > .mv-fill { flex: 1 1 auto; min-width: 0; }

/* ---- v42（用户 2026-10-11 第 8 条）纪念日「日期」：AntD 单日期控件 ----
   与原观影/高级搜索那两处同一套做法：
     · `.dt-antd` 是 antd 的挂载点（宽度交给它）；
     · 挂上后（`.is-antd`）把**原生 `<input type="date">` 视觉收起** ——
       它仍是值载体（antd 通过 change 写它），只是不再显示给用户。
   ⚠️ 用 clip 收起（不是 display:none）：保留可聚焦、可被表单读到。
   🔴🔴 v43（用户 2026-10-11 第 1 条）：「日期那里多了一个控件，
      只保留我们最新的控件就行，之前那种控件删除」——真根因有两层：
      ① 纪念日的原生框当初**没包在 `.dt-native` 里** ⇒ 这条选择器根本不命中
         （模板已补上包装）；
      ② **更要命的一层**：`.dt-input .dt-native { display: contents }`（第 619 行）
         会让这个包装盒**自己不生成盒子**、子元素"提升"成 `.dt-input` 的直接布局项 ——
         此时给它 `position:absolute; width:1px` **完全无效**（它没有盒子可定位），
         原生框照样按 flex 项排出来 ⇒ 两个控件并排。
      ⇒ 修法：这里必须把 `display` **从 contents 改成 block**，定位才生效。
         （只写 position/width/height 是不够的 —— 这是我第一版的错，探针当场抓到。） */
.dt-input.is-antd > .dt-native {
  display: block; position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; padding: 0; margin: -1px;
}
.dt-input.is-antd > .dt-antd { display: block; width: 100%; }
.dt-input.dt-antd-single { display: block; width: 100%; }
/* 兜底：万一哪天原生框又变成 `.dt-input` 的**直接子元素**（不包 .dt-native 了），
   这条按直接子元素也能把它收起来。 */
.dt-input.is-antd > input[type="date"] {
  display: block; position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; padding: 0; margin: -1px;
}

/* ---- v42（用户第 6 条）简介为空时的那句可点占位 ----
   与 `.trip-sum` 同一视觉位置，只是颜色淡一档（它是要点进去补的提示，不是内容）。 */
.trip-sum-empty { margin: 10px 0 0; font-size: 13px; color: var(--muted); }
.trip-sum-empty a { color: var(--brand); }

/* ============================================================================
   🔴 v43（用户 2026-10-11 第 6 条）条目详情浮层
   ----------------------------------------------------------------------------
   与 .cal-modal 同一形态（遮罩 + 居中面板 + 底栏），只是层级更高一点：
   它可能从**另一个浮层**里打开（例如详情页弹框里点了条目），
   所以要在 200 之上；但低于"看图"的灯箱(300)/预览层(320)。
   ⚠️ 与 .cn-modal(260)、.crop-overlay(260) 同层 —— 它们不会同时出现。
   ⚠️ 面板用 flex 列 + body 滚动，长内容（编辑表单）才有足够的可视区。 */
.fb-entry-modal {
  position: fixed; inset: 0; z-index: 260;
  display: flex; align-items: flex-start; justify-content: center;
  padding: 24px 16px; overflow-y: auto;
  background: rgba(44, 35, 29, .55); overscroll-behavior: contain;
}
.fb-entry-modal[hidden] { display: none; }
.fb-entry-panel { width: min(620px, 100%); max-height: calc(100vh - 64px); }
/* 🔴 v49（用户 2026-10-14 第 2 条）：「点击详情…让模态框**变大**，显示更多信息」。
   加宽到 860px（620 的 1.39 倍）—— 详情里可能有多行"标签：值"与附件清单，
   宽一点才不至于每行都折行。窄屏仍占满。 */
.fb-entry-panel.is-wide { width: min(860px, 100%); }
/* 底栏右侧的按钮组（用户：「三个按钮，都放在下面的右侧」）。
   `margin-left:auto` 把整组推到右边；左端那句提示（.cal-modal-tip）留在原地。
   ⚠️ 不用 `.cal-modal-foot .btn[type=submit]{margin-left:auto}` 那条（它只管提交键）。
   ⚠️ 组内允许换行：窄屏三颗按钮挤不下时要能折到第二行，而不是撑破底栏。 */
.fb-entry-acts {
  margin-left: auto;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end;
}
/* 🔴🔴 v49：**必须显式声明 `[hidden]`**（与文件前部那条"凡靠 [hidden] 隐藏的都再写一次"
   同一条规律）。原因：`.btn { display: inline-flex }` 是**作者样式**，
   特异性与优先级都高于 `[hidden]` 的**浏览器默认** `display:none`
   ⇒ `hidden` 属性对 `.btn` **完全无效**。
   症状：底栏四态切换时，前面那些按钮"藏不掉"、全都挤在一条上
   （本轮实测：派生条目本该只有「详情」，却三颗都露着）。
   ⚠️ 用 `!important` 与文件前部 `.pick-row[hidden]` 那组的写法保持一致 ——
      底栏是"四态互斥"的强语义，宁可压过一切。 */
.fb-entry-acts .btn[hidden] { display: none !important; }
/* 三颗的配色（用户：「详细用蓝色，编辑用黄色，删除用红色」）。
   ⚠️ **作用域类**（fb-a-*），不去改全局 `.btn-edit` / `.btn-danger` 的语义 ——
      本项目踩过"裸类改色波及别页"的坑（v41 把影院管理页的蓝色编辑改成了黄，见上面那段）。
   ⚠️ 三处形态都写（裸类 / a. / button.）：`.btn`、`a.btn`、`button.btn` 的特异性不同，
      漏一种就有一种是错的（这条规律 v40 已验过）。 */
.fb-a-detail, a.fb-a-detail, button.fb-a-detail {
  background: var(--blue); color: #fff; border-color: transparent;
}
.fb-a-edit, a.fb-a-edit, button.fb-a-edit {
  background: var(--yellow); color: #fff; border-color: transparent;
}
.fb-a-del, a.fb-a-del, button.fb-a-del {
  background: var(--red); color: #fff; border-color: transparent;
}
.fb-a-detail:hover, .fb-a-edit:hover, .fb-a-del:hover { filter: brightness(.92); }
/* 编辑态下正文里就是一张完整表单（内边距由它自己那份 CSS 给）——
   这里只保证它不双份留白。 */
.fb-entry-panel .cal-modal-body > .mv-form-inner,
.fb-entry-panel .cal-modal-body > .nf-wrap { padding: 0; }
/* 详情展开块：`?embed=detail` 吐的只读 dl（带 .ev-detail-block）。
   它自己那份 `.card-pad` 内边距在浮层里是多余的（面板已有 padding）。 */
.fb-entry-panel .ev-detail-block { padding: 0; }
@media (max-width: 640px) {
  .fb-entry-panel, .fb-entry-panel.is-wide { width: 100%; }
}

/* ============================================================
   v52（用户 2026-10-15 第 5 条）：「是不是这个人？」确认条
   ------------------------------------------------------------
   新增成员时若按 姓名/手机号/身份证号 在家里找到匹配的人，表单页顶部出现这一条。
   ⚠️ 它**在这张表单里面**（不是单独一页）—— 见 templates/people/form.php 的长注释：
      单独一页得把整张表（含数组字段）原样回传，漏一类就静默丢数据。
   ⚠️ 视觉上要"明显但不像出错"：它是个**问句**，不是错误 —— 所以用品牌淡底 + 左侧竖条，
      而不是红色告警（红色会让人以为"提交失败了"）。
   ============================================================ */
.person-confirm {
  border-left: 3px solid var(--brand-2);
  background: var(--brand-soft, #F3F7F7);
}
.person-hit {
  display: flex; align-items: center; gap: 14px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-s); padding: 12px 14px;
}
.person-hit-av { flex: 0 0 auto; }
.person-hit-av img,
.person-hit-av .avatar-fallback {
  width: 52px; height: 52px; border-radius: 12px; display: block;
  object-fit: cover; background: var(--line-2);
}
.person-hit-av .avatar-fallback {
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; color: var(--muted);
}
.person-hit-info { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.person-hit-ops { flex: 0 0 auto; }

/* ============================================================
   v52（用户 2026-10-15 第 7 条）：模块设置的**整合表**
   ------------------------------------------------------------
   一行一个模块，四列 = 顺序 / 模块 / 导航 / 首页 / 颜色。
   ⚠️ 原来那是三张卡（首页模块 / 导航模块 / 主题色），同一串模块名出现两遍。
      合成一张表后用户一次就能改完 —— 表格是这种"多列同构"最自然的形态。
   ⚠️ `min-width` + 外层 `overflow-x:auto`：窄屏（375px）五列放不下时
      表格能横向滚动，而不是把页面撑破（本项目在名册上栽过溢出）。
   ============================================================ */
.mod-table-wrap { overflow-x: auto; }
.mod-table { width: 100%; border-collapse: collapse; min-width: 560px; }
.mod-table th, .mod-table td {
  padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--line-2);
  font-size: 14px; vertical-align: middle;
}
.mod-table th { font-size: 12px; color: var(--muted); font-weight: 600; }
.mod-table .mod-th-on, .mod-table .mod-td-on { width: 62px; text-align: center; }
.mod-table .mod-th-color, .mod-table .mod-td-color { width: 72px; text-align: center; }
.mod-table .mod-th-order, .mod-table .mod-td-order { width: 108px; }
.mod-table .mod-td-order { white-space: nowrap; }
.mod-table .mod-td-name { display: flex; align-items: center; gap: 8px; border-bottom: 0; }
.mod-chk { display: inline-flex; cursor: pointer; }
.mod-chk input { accent-color: var(--brand); width: 16px; height: 16px; }
.mod-move { display: inline-flex; gap: 2px; margin-left: 4px; }
.mod-color-ro { width: 22px; height: 22px; border-radius: 5px; display: inline-block; }
.mod-table .mod-row.is-dragged { opacity: .5; }
.mod-table .mod-row.is-drop-before td { border-top: 2px solid var(--brand-2); }
.mod-table .color-picker { width: 34px; height: 24px; padding: 0; border: 1px solid var(--line); border-radius: 5px; background: #fff; cursor: pointer; }

/* ==========================================================================
   🔴 v54（用户 2026-10-16 第 3/4/5 条）：旅程 —— **列表 / 地图**两个视图
   ==========================================================================
   用户原话：「在旅程这个模块，**可以在地图上显示旅程数据**」（移植 TripLog）；
             「在旅程的「新建旅程」后面也加一个「设置」按钮」；
             「里面的设置让用户自己配置：**到底默认显示旅程列表，还是用地图显示**。」

   两个视图用**标签页**切换（不是两页）—— 因为"看列表"与"看地图"是同一份数据的
   两种看法，来回切不该刷新整页；也让"默认显示哪个"这个设置有意义
   （设置决定**进来时**先显示哪一个）。
   ⚠️ 标签页样式沿用本仓既有的"胶囊按钮 + on 态"口径（与日历视图切换一致）。 */

/* ---- 视图切换条 ---- */
.trip-views {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 14px;
}
.trip-views .tv-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 14px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface);
  color: var(--muted); font-size: 13.5px; cursor: pointer;
  text-decoration: none; line-height: 1.5;
}
.trip-views .tv-btn:hover { color: var(--ink); border-color: var(--line-3, var(--line)); }
.trip-views .tv-btn.is-on {
  background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600;
}
.trip-views .tv-btn svg { display: block; }
/* 右上角那颗「设为默认」小链接：与标签页同一行、靠右 */
.trip-views .tv-default {
  margin-left: auto; font-size: 12.5px; color: var(--faint);
  text-decoration: none; white-space: nowrap;
}
.trip-views .tv-default:hover { color: var(--brand); text-decoration: none; }
/* 「地图（0）」里那个小计数：还没位置时提示一下，别让人点进去看空地图 */
.trip-views .tv-dim { font-size: 11.5px; opacity: .8; margin-left: 2px; }

/* ---- 地图容器 ----
   ⚠️ **必须有固定高度**：高德地图在"高度为 0 的容器"里渲染出来是空白
      （这是高德最常见的坑，`.trip-map` 的 height 就是为它写的）。
   ⚠️ 高度跟视口走（屏幕越大看得越远），但给下限免得笔记本上太扁。 */
.trip-map-wrap { margin-top: 2px; }
.trip-map {
  width: 100%; height: clamp(380px, 62vh, 720px);
  border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; background: #EEF1F4;   /* 瓦片到位前不闪白 */
}
.trip-map-fallback {
  margin: 0; height: 100%; display: flex; align-items: center; justify-content: center;
  color: var(--muted); font-size: 13.5px; padding: 0 24px; text-align: center;
}
.trip-map-hint { margin: 8px 0 0; }

/* ---- 地图上的标记：小卡片（左文右无图，比 TripLog 那版更轻）----
   ⚠️ 卡片是**绝对定位在画布中心**（JS 里 left/top = C+dx / C+dy）：
      画布固定 260×260，卡片偏移不影响标记锚点（AMap 按画布中心对齐坐标）。 */
.trip-pin-stage { position: relative; pointer-events: none; }
.trip-pin {
  position: absolute; transform: translate(-50%, -50%);
  pointer-events: auto; cursor: pointer;
}
.trip-pin-card {
  display: flex; align-items: center; gap: 8px;   /* 🔴 v63：题图在左、文字在右 */
  background: var(--surface); border: 1px solid var(--line);
  border-left: 3px solid var(--brand);
  border-radius: 8px; padding: 6px 10px;
  box-shadow: 0 2px 8px rgba(0,0,0,.10);
  max-width: 220px;
}
/* 🔴 v63（用户 2026-10-19 第 3 条）：旅程题图。
   ⚠️ `object-fit: cover` 必须有：题图原图长宽比五花八门，默认 `fill` 会把人拉扁。
   ⚠️ 有题图时卡片最多 220px（原来是 190px），文字那一块要 `min-width:0`
      才能让 `.trip-pin-title` 的省略号真正生效（否则长标题把卡片撑破）。 */
.trip-pin-thumb {
  flex: 0 0 auto; width: 40px; height: 40px;
  border-radius: 5px; overflow: hidden; background: var(--line-2);
}
.trip-pin-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.trip-pin-body { display: block; min-width: 0; }
.trip-pin-title {
  font-size: 13px; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.trip-pin-sub { font-size: 11.5px; color: var(--muted); margin-top: 1px; }
.trip-pin:hover .trip-pin-card { border-color: var(--brand); box-shadow: 0 4px 14px rgba(0,0,0,.16); }
/* ⚠️ AMap 的标记包装层尺寸跟着 content（260×260）走、默认可命中，
   会把旁边卡片的点击整层挡住。整层禁掉命中、只留卡片本身可点
   （TripLog 也踩过这个，见其 `.amap-marker` 那条注释）。 */
#tripMapCanvas .amap-marker { pointer-events: none !important; }
#tripMapCanvas .amap-marker .trip-pin { pointer-events: auto !important; }

/* ---- 显示方式设置页的单选卡片（v54 第 5 条）----
   原生 radio + label 包一层卡片外观；`.is-on` 由服务端渲染时给
   —— 不依赖 JS 同步选中态（无 JS 时至少"当前项"是对的）。 */
.view-choice { display: flex; flex-direction: column; gap: 10px; max-width: 480px; }
.view-choice-item {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius-s);
  cursor: pointer; background: var(--surface);
}
.view-choice-item:hover { border-color: var(--brand-2); }
.view-choice-item.is-on { border-color: var(--brand); background: var(--brand-soft, #f2f7f3); }
.view-choice-item input { margin-top: 3px; flex: 0 0 auto; }
.view-choice-item .vci-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.view-choice-item .vci-body b { font-size: 14px; }

/* ==================================================================== */
/* v62（用户 2026-10-18 第 2 条）：分类图标                              */
/*                                                                      */
/* 分类徽标 = 图标 + 名字。图标是行内 SVG（AnnivIcon），颜色取 currentColor */
/* ⇒ 放在浅底徽标里就是灰字色，放在深色格子里自动变白，不需要第二套配色。   */
/* ==================================================================== */
.kind-badge {
  display: inline-flex; align-items: center; gap: 4px;
  vertical-align: middle;
  max-width: 100%;
}
.kind-badge .ai { flex: 0 0 auto; opacity: .9; }
.kind-badge .kind-name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 9em;                 /* 分类名最长 20 字，卡一行不撑破表格 */
}
.ann-ic { display: inline-flex; align-items: center; flex: 0 0 auto; color: var(--muted); }
.ann-ic .ai { display: block; }

/* ---- 分类设置页（days/settings）---- */
.kind-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.kind-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--radius-s);
  background: var(--surface);
  /* ⚠️ 行内每项都可能收缩（分类名输入框），flex 子项默认 min-width:auto 会顶破 */
  min-width: 0;
}
.kind-row.is-ro { gap: 10px; }
.kind-move { display: inline-flex; gap: 2px; flex: 0 0 auto; }
.kind-prev {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--line-2); color: var(--ink);
}
.kind-sel { flex: 0 0 auto; width: 190px; max-width: 46vw; }
.kind-name-in { flex: 1 1 140px; min-width: 0; }
.kind-name-ro { font-size: 14px; }
.kind-use { flex: 0 0 auto; font-size: 12px; color: var(--muted); min-width: 46px; text-align: right; }
/* 改过但没保存：保存按钮描一圈告警色 + 旁边一句小字（JS 加 .is-dirty / 去掉 hidden）
   ⚠️ 用 `.btn-warn` 同一个 #E8A33D（全站"待处理"的语义色），不要另发明一个黄。 */
.btn.is-dirty { box-shadow: 0 0 0 2px #E8A33D; }
.kind-dirty { color: #b57a19; margin-left: 10px; }
.kind-add {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 14px; padding: 10px; border-radius: var(--radius-s);
  background: var(--line-2);
}
.kind-add .select { flex: 0 0 auto; width: 190px; max-width: 46vw; }
.kind-add .input { flex: 1 1 160px; min-width: 0; }
.kind-add-lb { font-size: 13px; color: var(--muted); }
.kind-icon-bank { display: none; }

/* ==================================================================== */
/* v62（用户 2026-10-18 第 3 条）：首页卡片网格                          */
/*                                                                      */
/* 口径（与 app/HomeCards.php、templates/home_cards.php 的措辞一致）：    */
/*   · 一排 8 格；宽 2/4/6/8 → 一排 4 张 / 2 张 / 一大一小 / 独占一排     */
/*   · 高 2/4/6/8 行；一行 76px、间距 10px                              */
/*                                                                      */
/* ⚠️ 为什么用 `grid-template-columns: repeat(8, minmax(0,1fr))` 而不是  */
/*    flex + 百分比：8 格制下"宽 6"（一大一小）只有网格能自然表达；       */
/*    `minmax(0,1fr)` 里的 0 对应 flex 那条 `min-width:0` ——             */
/*    少了它，卡片里的长标题会把整列顶宽（v54 那个坑的同源）。             */
/* ⚠️ 高度用 `grid-row: span N` 而不是给每张卡片算像素：                   */
/*    这样"高 4"的两张卡片并排时天然等高，不依赖内容多少。                 */
/* ==================================================================== */
.hc-grid {
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  grid-auto-rows: 76px;
  gap: 10px;
  align-items: stretch;
}
.hc-card {
  display: flex; flex-direction: column;
  min-width: 0; min-height: 0;
  margin: 0;
  padding: 12px 14px;
  overflow: hidden;
}
/* ⚠️ 必须单独写这一条，而且选择器要**比 `.card + .card` 更重**：
   上面 `.hc-card { margin: 0 }` 只有 0,1,0，而全局相邻间距
   `.card + .card { margin-top: 18px }` 是 0,2,0 —— 权重更高，会把
   每一张**非首张**的卡片顶下去 18px（实测症状：高 2 期望 162 对得上，
   高 4/高 8 恒少 18px，因为网格首个子元素不命中 `+ .card`）。
   间距本来就由 grid 的 `gap` 统一给，这里把相邻 margin 彻底清掉。 */
.hc-grid > .card + .card { margin-top: 0; }
.hc-w2 { grid-column: span 2; }
.hc-w4 { grid-column: span 4; }
.hc-w6 { grid-column: span 6; }
.hc-w8 { grid-column: span 8; }
.hc-h2 { grid-row: span 2; }
.hc-h4 { grid-row: span 4; }
.hc-h6 { grid-row: span 6; }
.hc-h8 { grid-row: span 8; }

.hc-head {
  display: flex; align-items: center; gap: 10px;
  flex: 0 0 auto; min-width: 0; margin-bottom: 8px;
}
.hc-head .cinema-h2 { margin: 0; flex: 1 1 auto; min-width: 0; }
.hc-head .hd-more { flex: 0 0 auto; }
/* 卡片正文：**自己滚**，不撑卡片（用户给的高度就是最终高度）。 */
.hc-body {
  flex: 1 1 auto; min-height: 0; min-width: 0;
  overflow-y: auto; overscroll-behavior: contain;
  scrollbar-width: none; -ms-overflow-style: none;
}
.hc-body::-webkit-scrollbar { width: 0; height: 0; }
/* 概述卡：左右两栏（问候/数字 ｜ 天气），与原来的信息面板一致 */
.hc-body.home-brief { align-content: flex-start; }
/* 日程卡：内部还要再分"全天区 + 时间网格"，所以它自己是一个纵向 flex，
   滚动交给内层 `.hd-pane-body`（这样全天区才能钉在上面不动）。 */
.hc-body.hc-agenda { display: flex; flex-direction: column; overflow: hidden; }
/* 卡片里的 pane 不再有 440px 的固定高（那是 v24 双栏时代的值） */
.hc-card .hd-pane-body { flex: 1 1 auto; min-height: 0; }
.hc-card .ag-allday { max-height: 40%; }

/* ---- 纪念日卡片 ---- */
.hc-ann .ann-up-item { gap: 6px; padding: 7px 2px; }
.hc-ann .ann-days { min-width: 52px; }
.hc-ann .ann-title { font-size: 13.5px; }

/* ---- 观影卡片：海报 + 片名 ---- */
.hc-mv-list, .hc-bk-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.hc-mv, .hc-bk {
  display: flex; align-items: center; gap: 10px; min-width: 0;
  padding: 6px 2px; border-bottom: 1px solid var(--line-2);
}
.hc-mv:last-child, .hc-bk:last-child { border-bottom: 0; }
.hc-mv-poster, .hc-bk-cover {
  flex: 0 0 auto; width: 34px; height: 46px; border-radius: 4px; overflow: hidden;
  background: var(--line-2); display: inline-flex; align-items: center; justify-content: center;
}
.hc-bk-cover { width: 32px; height: 44px; }
.hc-mv-poster img, .hc-bk-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hc-mv-ph, .hc-bk-ph { font-size: 16px; opacity: .7; }
.hc-mv-main, .hc-bk-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.hc-mv-t, .hc-bk-t {
  font-size: 13.5px; color: var(--ink); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hc-bk-bar { display: block; height: 3px; border-radius: 2px; background: var(--line-2); overflow: hidden; }
.hc-bk-bar i { display: block; height: 100%; background: var(--brand); }
.hc-bk-st {
  flex: 0 0 auto; font-size: 11px; padding: 1px 7px; border-radius: 999px;
  background: var(--line-2); color: var(--muted);
}
.hc-bk-st.is-reading { background: #E7F0DA; color: #4A7226; }
.hc-bk-st.is-done    { background: #E3EAF5; color: #3A5B8C; }

/* ---- 联系人卡片：头像 + 名字，按卡片宽度自动折行 ---- */
.hc-people { display: flex; flex-wrap: wrap; gap: 10px 12px; }
.hc-person {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  width: 56px; min-width: 0; text-decoration: none;
}
.hc-person-av {
  width: 40px; height: 40px; border-radius: 50%; object-fit: cover;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 15px; font-weight: 600;
}
.hc-person-nm {
  font-size: 11.5px; color: var(--muted); max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hc-person:hover .hc-person-nm { color: var(--ink); }

/* ---- 窄屏：8 格 → 4 格 → 1 列 ---- */
@media (max-width: 1180px) {
  .hc-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .hc-w2 { grid-column: span 2; }
  .hc-w4, .hc-w6, .hc-w8 { grid-column: span 4; }
  .hc-h8 { grid-row: span 7; }
}
@media (max-width: 680px) {
  .hc-grid { grid-template-columns: minmax(0, 1fr); grid-auto-rows: 64px; }
  .hc-w2, .hc-w4, .hc-w6, .hc-w8 { grid-column: span 1; }
  .hc-h6, .hc-h8 { grid-row: span 6; }
}

/* ---- 首页卡片布局设置页 ---- */
.hc-set-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.hc-set-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-s);
  background: var(--surface); min-width: 0;
}
.hc-set-row.is-dragged { opacity: .5; }
.hc-set-row.is-drop-before { border-top: 2px solid var(--brand); }
.hc-set-order { display: inline-flex; align-items: center; gap: 2px; flex: 0 0 auto; }
.hc-set-on {
  display: flex; align-items: center; gap: 8px;
  flex: 1 1 220px; min-width: 0; cursor: pointer;
}
.hc-set-name { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.hc-set-name b { font-size: 14px; }
.hc-set-name .small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hc-set-dim { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; }
.hc-set-dim .select { width: 66px; }
.hc-set-n { width: 64px; }
.hc-set-null { width: 0; }

/* ============================================================
   v78（用户 2026-10-22）：首页「近期任务」的就地面板
   ------------------------------------------------------------
   用户原话：「网页端，首页点击一个任务后，除了展示信息，应该有相应的功能，
     可以修改状态：待办、进行中，可以挪动分组，可以拖动上传附件，可以添加评论，
     添加子任务等。其目地，是为了让用户不离开首页，就能对任务进行简单的处理。」

   这一段只画**面板本体**（壳、遮罩、底栏都沿用 `.cal-modal*` / `.fb-entry-panel`）。
   三块强调色都不新造：状态用既有的 `.kb-st`（三档颜色是它定义的唯一来源），
   看板 chip 用看板自己的 `--bc`（与看板列头、卡片上的点同一个色）。
   ============================================================ */
.tk-panel { display: flex; flex-direction: column; gap: 14px; }
.tk-sec { display: flex; flex-direction: column; gap: 8px; }
/* 段落之间的那条细线：面板里段落多（状态/看板/信息/子任务/附件/讨论），
   不划线会糊成一整块；用 line-2（比正文分隔线更淡）以免像个表单边框。 */
.tk-sec + .tk-sec { border-top: 1px solid var(--line-2); padding-top: 14px; }
.tk-sec-h {
  display: flex; align-items: center; gap: 6px;
  /* #82：小节标题改 **模块主题色 + 加粗 + 字距** ——
     与鸿蒙端 FbSectionTitle 一致（用户：「颜色也是用主题颜色」）。 */
  font-size: 13px; font-weight: 700; letter-spacing: .6px;
  color: var(--tk-accent, var(--brand));
}
.tk-sec-h .group-n { margin-left: 0; }
/* 「状态」「看板」这两段是"标签 + 一排可点的东西"，横排。
   ⚠️ 用 flex-wrap：看板可能是七八个，窄屏要能折行而不是撑出面板。 */
.tk-sec-inline { flex-direction: row; align-items: center; gap: 10px; flex-wrap: wrap; }
/* 🔴 #74：面板里的「截止时间」AntD 控件。
   `.dt-input` 是 `display:flex; flex-wrap:wrap`，放进 `.tk-sec-inline`
   （也是 flex-wrap）里会被当成一个弹性项 —— 不给宽度约束时它会按内容撑，
   在窄屏上可能把「截止」标签挤到上一行、或者控件自己溢出。
   `flex: 1 1 auto` + `min-width: 0` 让它占满标签右侧的剩余宽度，
   `max-width` 兜住极宽屏（AntD 的 picker 本身有固定宽度）。 */
.tk-sec-inline > .dt-input { flex: 1 1 auto; min-width: 0; max-width: 320px; }
/* 「还剩 3 天」那类小字：贴在控件右边，不与控件抢宽度 */
.tk-due-word { flex: 0 0 auto; font-size: 12.5px; color: var(--faint); white-space: nowrap; }
.tk-due-word.kb-late { color: var(--red); }

/* 🔴 #76：「更多信息」小节 —— 截止 / 负责人 / 分类 三行 + 保存。
   用户 2026-11-05：「没有保存按钮…负责人和分类都没法修改。」
   每行结构 = 固定宽标签 + 占满剩余宽度的控件，与 .mv-row 同一套观感
   （但标签用 tk-attr-lb，因为面板里字号比整页表单小一号）。 */
.tk-attrs { display: flex; flex-direction: column; gap: 14px; }
/* 🔴🔴 #82（用户 2026-11-05）：「网页版编辑在首页编辑任务的时候，那个输入框
   怎么那么短呢？所有输入框都应该跟弹出框一样宽。」
   「布局顺序也和鸿蒙版一致」「下面分成两行：第一行是任务标题，第二行是状态」

   ⇒ `.tk-attr-row` 从**横向**（标签左 / 控件右）改成**纵向两行**：
        ① 标签独占一行（主题色粗体）
        ② 控件独占一行，**占满整行宽度**（不再有 300px 上限）

   为什么去掉 300px 上限：
     那个上限是 #80 为"对齐"引入的，但它的代价是**输入框比面板窄一大截** ——
     面板 680 宽，控件只有 300，右边空着 300 多像素，看起来像没做完。
     现在改成上下两行之后，每行只有一个控件，天然左缘对齐，
     **不再需要靠限宽来对齐** ⇒ 直接 width:100%。 */
.tk-attr-row {
  display: flex; flex-direction: column; align-items: stretch; gap: 6px;
  /* 🔴 #80：这里原来有 `flex-wrap: wrap` —— 窄屏下控件（300px）加标签（46px）
     加间距（10px）放不进容器，控件就**掉到下一行**，左缘从 76 变成 0，
     与标签右缘完全错开，整列看着像塌了。
     ⇒ 不换行：让控件按 `flex:1 1 auto` + `min-width:0` 收缩到可用宽度。
       宽度不足时宁可控件变窄，也不要换行破坏对齐。
     ⚠️ 代价：极窄屏（< 200px）下控件会很窄 —— 但那时任何布局都放不下，
        保持对齐比保持宽度更重要。 */
}
.tk-attr-lb {
  /* #82：标签不再固定 46px（那是横排时的"对齐基准"），
     现在独占一行，按内容宽即可；左对齐（横排时是右对齐贴控件）。
     颜色从 faint 灰改成 **模块主题色 + 加粗** —— 与鸿蒙端的 fieldLb、
     以及本页的小节标题同一口径。 */
  font-size: 12.5px; font-weight: 700; letter-spacing: .6px;
  color: var(--tk-accent, var(--brand)); text-align: left;
}
/* 控件区占满标签右侧（min-width:0 让它能被压缩，不会把标签挤出去） */
.tk-attr-row > *:not(.tk-attr-lb) { flex: 1 1 auto; min-width: 0; }

/* 🔴 #80（用户 2026-11-05）：「标题和备注的输入框没有和其他输入框对齐。」

   根因：各行的**宽度上限不一致** ——
     · 截止（AntD picker）  max-width: 300px
     · 分类（select）        max-width: 300px
     · 负责人/协作人         max-width: 340px
     · **标题（input）/ 备注（textarea）完全没设上限** ⇒ 撑满整行
   于是标题那行比截止那行宽出一截，左右边缘都对不齐。

   ⇒ 统一成一个宽度变量。以后要调只改这一处，不会再有"某一行多一点"。

   ⚠️ `width: 100%` + `max-width` 而不是只给 `max-width`：
      只给 max-width 时，块级 input 默认 width:auto 会按内容收缩，
      反而比别的行窄。两个一起给才能"最多这么宽、但尽量占满"。
   ⚠️ 标签宽度（46px）+ gap（10px）= 56px，这与 `.tk-attr-actions` 的
      `padding-left: 56px` 是同一个数 —— 改标签宽度时必须同步改那两处。 */
:root { --tk-attr-w: 300px; }
/* #82：所有控件一律占满整行 —— 不再有 `--tk-attr-w` 这个 300px 上限。
   上下两行之后每行只有一个控件，宽度不一致反而不协调。 */
.tk-attr-row > .dt-input,
.tk-attr-row > select.select,
.tk-attr-row > .pp-root,
.tk-attr-row > input.input,
.tk-attr-row > textarea.input,
.tk-attr-row > .tk-attr-val { width: 100%; max-width: none; }
/* textarea 按行数撑高，别被 `.input` 的固定高度带偏 */
.tk-attr-row > textarea.input { resize: vertical; min-height: 46px; }
/* 保存按钮那一行：与上面的控件左缘对齐（= 46px 标签宽 + 10px gap） */
.tk-attr-actions {
  /* #82：取消 padding-left(56px) —— 那是给"横排时对齐控件左缘"用的。
     现在控件占满整行，按钮左缘就是容器左缘，再加 56 会缩进去一截。 */
  display: flex; align-items: center; gap: 10px;
}
/* 🔴 #83（用户 2026-11-05）：「保存按钮感觉太小了。要不也像鸿蒙版一样长长的一条？」

   原来 `.btn-sm` 是内联宽度（按文字撑），在 648px 宽的面板里显得又小又偏。
   ⇒ 面板里的保存按钮改成**整行长条**：与上面的输入框同宽、同高（42），
     视觉上就是"这一列的最后一步"。
   ⚠️ 用 `width:100%` 而不是 `flex:1`：`.tk-attr-actions` 是 flex 容器，
      `flex:1` 在有其它兄弟时会按比例分 —— 这里只有一个按钮，
      `width:100%` 更直接、不受后续增删影响。 */
.tk-attr-actions > .btn {
  width: 100%; height: 42px; justify-content: center;
  font-size: 14px; font-weight: 500; border-radius: var(--radius-s);
}
.tk-attr-tip { font-size: 12px; color: var(--green, #2E7D4F); }
/* #82：只读态的值也做成"框"的样子（浅底圆角），
   与可编辑态同宽同高 —— 否则只读用户看到的是一行裸文字，和编辑态差太多。 */
.tk-attr-val {
  display: block; min-height: 40px; box-sizing: border-box;
  padding: 10px 12px; font-size: 14px; color: var(--ink);
  background: var(--input-bg, #F7F4EF);
  border-radius: var(--radius-s); word-break: break-word;
}
/* #82：面板里的"点一下就能改"的横排段（状态/看板）改成上下两行：
   标签在上、chips 在下，与字段行同形态。 */
.tk-sec-inline { flex-direction: column; align-items: stretch; gap: 6px; }
.tk-sec-inline > .tk-sec-lb {
  font-size: 12.5px; font-weight: 700; letter-spacing: .6px;
  color: var(--tk-accent, var(--brand));
}

/* #78：面板里的选人组件（负责人单选 / 协作人多选）。
   宽度已在上面的 `--tk-attr-w` 统一，这里只管 chips 的间距。
   （原来这里单独写 max-width: 340px，比其他行宽 40px —— 正是 #80 对不齐的原因之一。） */
.tk-attr-row > .pp-root .pp-chips { margin-bottom: 6px; }
/* 标签在多行控件（备注、协作人）上方对齐，而不是垂直居中 */
.tk-attr-row-top { align-items: flex-start; }
.tk-attr-row-top > .tk-attr-lb { padding-top: 8px; }
/* 只读态的协作人 chips */
.tk-share-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.tk-share-chip {
  font-size: 12.5px; padding: 3px 10px; border-radius: 999px;
  background: var(--surface-2, #F2F0EC); color: var(--ink);
}

/* #78：评论里的「添加附件」。
   用原生 file input（视觉隐藏），点 label 触发 —— 与站内其它上传入口同一手法。 */
/* 🔴 #81（用户 2026-11-05）：「评论里面拖文件上传附件，那个地方太小了。」

   原来是一个 `padding:4px 10px` 的小胶囊（高约 26px）+ 一行小字，
   拖动落点太小：触屏上不好瞄，桌面上也容易拖偏。
   ⇒ 改用与「任务附件」同一套 `.upbox`（96px 高的虚线大框）。
      这里只做评论特有的微调，不去覆盖 `.upbox` 本体 ——
      那一套是站点级组件，改它会影响所有上传入口。 */
.cmt-attach-row {
  display: flex; flex-direction: column; gap: 8px;
  margin-top: 8px;
}
/* 评论里的上传框比「任务附件」矮一点：评论框本身已经在下方，
   96px 会把整块讨论区推得太长；72px 在触屏上仍然好点。 */
.cmt-upbox { min-height: 72px; padding: 10px 14px; gap: 3px; }
.cmt-upbox .upbox-ico { font-size: 18px; }
.cmt-upbox .upbox-msg { font-size: 13px; }
.cmt-upbox-sub {
  font-size: 11.5px; color: var(--faint); margin-top: 1px;
}
/* 拖拽经过时整框高亮（`.is-over` 由 task_panel.js 加在 `.cmt-attach-row` 上，
   而 `.upbox.is-over` 是站点级规则 —— 这里补一条子选择器把它传导下去） */
.cmt-attach-row.is-over .cmt-upbox {
  border-color: var(--brand); border-style: solid;
  background: #FDF3EC; color: var(--brand);
}
/* 已选文件列表：一行一个，前面带个圆点；超长名截断 */
.cmt-file-list {
  display: flex; flex-direction: column; gap: 3px;
  padding: 6px 10px; border-radius: var(--radius-s);
  background: var(--surface-2, #F2F0EC);
  font-size: 12px; color: var(--muted);
}
.cmt-file-list span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cmt-file-list span::before { content: '📎 '; }
.tk-sec-lb { flex: 0 0 34px; font-size: 12.5px; color: var(--faint); }
.tk-seg { display: flex; gap: 6px; flex-wrap: wrap; }
/* 面板里的状态徽标比卡片上的大一档（这里是要点的按钮，不是标签） */
.tk-seg .kb-st { padding: 3px 12px; font-size: 12.5px; }
.tk-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.tk-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 10px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface);
  font-size: 12px; color: var(--muted); cursor: pointer;
  max-width: 100%;
}
.tk-chip:hover { border-color: var(--brand-2); color: var(--brand-ink); }
/* 选中的看板：用**它自己的颜色**描边 + 淡底 —— 与看板列头那颗点是同一个色，
   用户不用读字就知道"这条现在在哪一摊事里"。 */
.tk-chip.is-on {
  border-color: var(--bc, var(--brand));
  background: color-mix(in srgb, var(--bc, var(--brand)) 12%, transparent);
  color: var(--ink); font-weight: 600;
}
/* ⚠️ color-mix 在老浏览器上整条声明无效 → 底色会是透明的。
   补一条纯 rgba 兜底（不指定色，统一淡灰），保证"选中"至少看得出来。 */
@supports not (background: color-mix(in srgb, red 10%, transparent)) {
  .tk-chip.is-on { background: var(--line-2); }
}
.tk-chip.is-static { cursor: default; }
.tk-chip-dot {
  width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto;
  background: var(--bc, var(--faint));
}
/* 只读那几行（截止/负责人/分类/备注）：面板里空间紧，行高比详情页收一点 */
.tk-kv .kv-row { padding: 6px 0; }
/* 加子任务那一行：输入框吃掉剩余宽度，按钮不缩 */
.tk-inline-form { display: flex; align-items: center; gap: 8px; }
.tk-inline-form .input { flex: 1 1 auto; min-width: 0; }
.tk-inline-form .btn { flex: 0 0 auto; }
/* 讨论里那条"删除"：长得像链接（`.btn-inline` 负责去掉按钮的灰底，
   `.link-quiet` 负责颜色），这里只把它推到本行最右。 */
.cmt-who .cmt-x { margin-left: auto; }
.tk-drop { margin-top: 4px; }
.tk-drop .upbox { min-height: 64px; }
@media (max-width: 640px) {
  .tk-sec-lb { flex: 0 0 auto; width: 100%; }
}

/* v78：卡片窄到"宽 2"时，标题 +「全部任务」+「新建任务」三样挤不下 ——
   允许折行，别把标题压成 0 宽（那会只剩两颗按钮，看不出这是哪张卡片）。 */
.hc-card.hc-w2 .hc-head { flex-wrap: wrap; }

/* ============================================================================
   🔴🔴 v94（用户 2026-10-25）：必填项统一标记 = **标签红色 + 加粗**，去掉红色星号
   ============================================================================
   用户原话：「必填项现在的做法是在后面加一个红色的星，但这样排版有时候不好看，
   对不齐。把所有必填项都改一下，改成红色加粗字体，就不用再考虑加红色星的问题了。
   这样用户习惯之后，一看前面的标题，比如"日程名称"这种，只要是红色加粗的字体，
   就知道这是必填项。我感觉这个比加红色的星更好一些。」

   ------------------------------------------------------------------
   为什么用 `.is-req` 这个**显式标记**，而不是"看有没有 `required` 属性"自动推断
   ------------------------------------------------------------------
   `required` 只出现在 `<input>` 上，而标签是它的**兄弟或父级之前的节点** ——
   CSS 没有"父选择器"能用它反推（`:has()` 也只在很新的内核里可用，本项目要照顾
   旧 WebView）。而且有几个必填项**根本没有 `required` 属性**（比如影院名称、
   板块名这些靠 JS 校验的），靠属性推断会漏掉。
   ⇒ 一律由模板显式加 `is-req` / `is-req` 放在 `<span class="mv-lb">` 上。
      「哪些是必填」这个事实写在模板里（离 `required` 最近），
      「红加粗长什么样」写在 CSS 里（一处改、全站生效）。

   ------------------------------------------------------------------
   ⚠️ 三条硬约束（都是本项目的实测坑）
   ------------------------------------------------------------------
   ① **特异性**：这里用 `.is-req`（0,1,0）去覆盖 `.mv-row > .mv-lb`
      （0,2,0）和 `.form-grid > .field > label`（0,2,1）—— 单靠一条肯定压不住。
      所以最后用 `!important` 打穿。这不是偷懒：颜色/字重是**语义标记**，
      不该被"某个面板想调一下标签样式"顺手盖掉，否则同一个"必填"在不同页面长得不一样。
   ② **冒号必须跟着变红**：`.mv-lb::after` / `.dt-inline-lab::after` 把「：」画成
      灰色 400 字重 —— 只改标签本体会出现"红字 + 灰冒号"的半截效果。这里一并覆盖。
   ③ **不能用 `color: inherit` 之类做减法**：`--red` 是动作红（删除按钮底色），
      当文字色在暖纸底上偏浅，见 `:root` 里 `--req` 的说明。

   ------------------------------------------------------------------
   覆盖的三种标签形态（本项目全部标签都落在这三种里，已逐个核过）
   ------------------------------------------------------------------
     a) `<label>`                         —— 多数表单
     b) `<span class="mv-lb">`            —— 观影式右对齐标签（含 ::after 冒号）
     c) `<span class="dt-inline-lab">`    —— 日期控件的内联标签（也带 ::after 冒号）
   ============================================================================ */

/* a) 裸 <label>（含 .form-grid / .card .field / .cal-modal 等各种宿主） */
label.is-req,
.field > label.is-req {
  color: var(--req) !important;
  font-weight: var(--req-w) !important;
}

/* b) 观影式 .mv-lb —— 冒号一起变红（`.mv-lb::after` 把「：」画成灰色 400，
   只改标签本体会剩"红字 + 灰冒号"的半截效果；冒号也得跟着 700，否则
   650/400 之间会断开一截） */
.mv-lb.is-req,
.mv-row > .mv-lb.is-req,
.dt-inline-lab.is-req {
  color: var(--req) !important;
  font-weight: var(--req-w) !important;
}
.mv-lb.is-req::after,
.mv-row > .mv-lb.is-req::after,
.dt-inline-lab.is-req::after {
  color: var(--req) !important;
  font-weight: var(--req-w) !important;
}

/* c) 日期范围那种独立标签（`<span class="dt-range-label">日期范围</span>`） */
.dt-range-label.is-req {
  color: var(--req) !important;
  font-weight: var(--req-w) !important;
}

/* 兜底：万一模板把 is-req 加在了包裹层（`.field` / `.mv-row`）而不是标签本身，
   让里面的标签也吃到红色 —— 这样即使某处写法不同，用户看到的仍是一致的"红加粗"。
   ⚠️ 只作用在直接子级的 label / .mv-lb / .dt-range-label 上：
      不能写成 `.field.is-req label`，那会把「提醒」那排 checkbox 的小标签一起染红。 */
.field.is-req > label,
.field.is-req > .field-label,
.mv-row.is-req > .mv-lb,
.field.is-req > .dt-range-label {
  color: var(--req) !important;
  font-weight: var(--req-w) !important;
}
.field.is-req > .dt-range-label::after,
.mv-row.is-req > .mv-lb::after {
  color: var(--req) !important;
  font-weight: var(--req-w) !important;
}

/* 深色/高对比主题下 `--req` 若偏暗就看不清 —— 这个站点目前只有暖纸一套，
   但 `.dark` 类保留着（见 :root 附近的说明）。这里给一条备用值，
   免得将来加暗色时必填标签糊进背景。 */
.dark label.is-req,
.dark .mv-lb.is-req,
.dark .dt-range-label.is-req,
.dark .field.is-req > label,
.dark .mv-row.is-req > .mv-lb {
  color: #F08A78 !important;
}


/* ============================================================
   #47（2026-11-05）行程状态动作按钮
   ------------------------------------------------------------
   用户：「做成两个按钮，颜色也尽可能保持一致…
   选中其中一个按钮之后…还加了一个那种椭圆的那种框那种背景，灰色的。」

   实现：胶囊（border-radius:999px）+ 浅底；
         `.is-on` 是"当前状态"，用实心主题色表示"正在被选中"，
         其余动作是浅底 —— 一眼能看出现在是什么、能改成什么。
   ============================================================ */
.node-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin: 4px 0 2px;
}

.node-act {
  display: inline-flex;
  align-items: center;
  border: 0;
  border-radius: 999px;
  padding: 4px 10px;
  font-size: 12px;
  line-height: 1.35;
  background: var(--fill, #F1EAE1);
  color: var(--muted, #8A7A6D);
  cursor: pointer;
  font-family: inherit;
  transition: background .12s ease, color .12s ease;
}

.node-act:hover { background: var(--brand-soft, #F5E3DC); }

/* 当前状态：实心 = 正在被选中 */
.node-act.is-on {
  background: var(--muted, #8A7A6D);
  color: #fff;
  cursor: default;
}

.node-act-go { color: #2F6FA8; }
.node-act-go:hover { background: #EAF2F9; }

.node-act-ok { color: #3F7D5A; }
.node-act-ok:hover { background: #EAF4EE; }

.node-act-no { color: #C0533F; }
.node-act-no:hover { background: #FBEDEA; }

.node-act-gray { color: var(--muted, #8A7A6D); }


/* #48（2026-11-05）补充：取消按钮（琥珀色，与"删除"的砖红区分开）。
   用户强调「取消和删除是不一样的」⇒ 视觉上也不能同色。 */
.node-act-warn { color: #B8862B; }
.node-act-warn:hover { background: #FDF6E7; }

/* 时间行：计划与实际并列时的区分 */
.node-time .t-plan { color: var(--muted, #8A7A6D); }
.node-time .t-actual {
  color: var(--brand, #A8574F);
  margin-left: 8px;
  font-weight: 500;
}


/* ============================================================
   #53（2026-11-05）视频解不了时的提示卡片
   ------------------------------------------------------------
   用户传的视频是 H.265/HEVC，Chrome/Firefox 解不了 ⇒ 黑屏。
   不给提示的话用户会以为"文件坏了/网站坏了"。
   这里把原因和出路直接写在画面里。
   ============================================================ */
.pv-fallback {
  max-width: 520px;
  padding: 28px 24px;
  border-radius: 12px;
  background: rgba(255, 255, 255, .08);
  border: 1px solid rgba(255, 255, 255, .18);
  text-align: center;
  color: #fff;
}
.pv-fallback-h {
  margin: 0 0 10px;
  font-size: 17px;
  font-weight: 600;
}
.pv-fallback-p {
  margin: 0 0 18px;
  font-size: 13px;
  line-height: 1.7;
  opacity: .82;
}
.pv-fallback-dl {
  display: inline-block;
  padding: 9px 22px;
  border-radius: 8px;
  background: #fff;
  color: #1a1a1a;
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
}
.pv-fallback-dl:hover { background: #eee; }

/* ══════════════════════════════════════════════════════════════════════
   #85 自绘下拉框（替换原生 select 的展开列表）
   ══════════════════════════════════════════════════════════════════════
   🔴 用户 2026-11-05：「网页版的下拉框也太丑了。你参考一下那个反馈系统
      （FeedLite）里面的下拉框到底是怎么做的？按它的做法做，它也有主题颜色，
      我们这个也有主题颜色，就按照一样的做法去做。」

   **为什么必须自绘**：原生 `<select>` 收起时能改样式，
   但**展开的选项列表由浏览器/系统绘制，CSS 完全改不动**（只能改 option 底色），
   各平台样式还不统一。所以想"好看"只有一条路：自己画。

   **但绝不重写 HTML**：原生 select 保留在 DOM 里参与表单提交，
   JS 在它旁边生成一个按钮 + 浮层。这样：
     · 全站所有 select 自动获得新样式，不用逐个改模板；
     · `onchange="this.form.submit()"` 这类内联逻辑照样工作（我们派发原生 change）；
     · 无 JS 时原生 select 仍可用（只是朴素），不会白屏。

   ⚠️ 浮层用 `position: fixed` 挂在 body 上 —— 面板/浮层里有很多
      `overflow: hidden`，放在 select 旁边会被裁掉（FeedLite 踩过）。
   ⚠️ 三档浅色由 layout.php 里 PHP 算好（--brand-tint-1/2/3），
      不用 `color-mix()`：那个配 var() 在 Chrome 里会解析成透明。
   ══════════════════════════════════════════════════════════════════════ */

/* 原生 select：视觉隐藏但仍参与提交与事件（**不能 display:none**，那样不参与提交） */
.sel-native {
  position: absolute !important;
  width: 1px !important; height: 1px !important;
  margin: 0 !important; padding: 0 !important;
  border: 0 !important; opacity: 0 !important;
  pointer-events: none !important;
  clip: rect(0 0 0 0) !important;
}

/* 外层：inline-flex，跟输入框一样参与行内布局 */
.sel { position: relative; display: inline-flex; vertical-align: middle; max-width: 100%; min-width: 0; }

/* 触发按钮：外观与普通输入框完全一致（同高同圆角同边框） */
.sel-btn {
  display: inline-flex; align-items: center; gap: 10px;
  height: 40px; padding: 0 12px;
  border: 1px solid var(--line); border-radius: var(--radius-s);
  background: #fff; color: var(--ink);
  font-family: inherit; font-size: 14px; line-height: 1;
  cursor: pointer; text-align: left;
  box-shadow: 0 1px 2px rgba(16,24,40,.04);
  transition: border-color .12s, box-shadow .12s, background .12s;
  max-width: 100%; min-width: 0;
}
.sel-btn:hover { border-color: var(--brand-2); }
.sel-btn:focus-visible { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-tint-1, rgba(168,74,40,.10)); }
.sel-txt { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 未选值（「未分类」「不循环」这类占位）→ 次要色，弱化存在感 */
.sel-btn.is-placeholder .sel-txt { color: var(--muted); }

/* 自绘箭头：默认灰；**打开时转 180° 并变主题色** */
.sel-caret {
  width: 11px; height: 7px; flex: none;
  background: currentColor; opacity: .55;
  -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='7' viewBox='0 0 11 7'%3E%3Cpath d='M1 1.2 5.5 5.8 10 1.2' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat;
          mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='7' viewBox='0 0 11 7'%3E%3Cpath d='M1 1.2 5.5 5.8 10 1.2' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat;
  transition: transform .16s, background .12s, opacity .12s;
}
.sel-btn.is-open .sel-caret { transform: rotate(180deg); background: var(--brand); opacity: 1; }
/* 打开态整体染主题色：浅主题底 + 主题边框 */
.sel-btn.is-open {
  border-color: var(--brand);
  background: var(--brand-tint-1, #FAF2EE);
  color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-tint-1, rgba(168,74,40,.10));
}
.sel-btn.is-open .sel-txt { color: var(--brand); font-weight: 600; }

/* 浮层：fixed，不被任何 overflow 容器裁切 */
.sel-panel {
  position: fixed; z-index: 9998;
  max-height: min(320px, calc(100vh - 32px));
  overflow-y: auto; overscroll-behavior: contain;
  padding: 5px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 11px;
  box-shadow: 0 10px 30px rgba(16,24,40,.16), 0 2px 6px rgba(16,24,40,.06);
  animation: selIn .12s ease-out;
}
@keyframes selIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.sel-panel.is-up { animation-name: selInUp; }
@keyframes selInUp { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

/* 列表项 */
.sel-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 0 10px; height: 34px;
  border: 0; border-radius: 7px;
  background: transparent; color: var(--ink);
  font-family: inherit; font-size: 14px; line-height: 1;
  cursor: pointer; text-align: left; white-space: nowrap;
  transition: background .1s, color .1s;
  -webkit-appearance: none; appearance: none;
}
.sel-item-txt { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* hover / 键盘高亮：主题色最浅一档 */
.sel-item:hover, .sel-item.is-active {
  background: var(--brand-tint-1, #FAF2EE);
  color: var(--brand);
}
/* 选中项：深一档 + 主题色文字 + 右侧对勾，一眼看出当前选的是哪个 */
.sel-item.on {
  background: var(--brand-tint-2, #F3E3DC);
  color: var(--brand);
  font-weight: 600;
}
.sel-item.on:hover, .sel-item.on.is-active { background: var(--brand-tint-3, #EDD5CB); color: var(--brand); }
.sel-item-ck {
  width: 13px; height: 10px; flex: none;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='10' viewBox='0 0 13 10'%3E%3Cpath d='M1.5 5.2 4.8 8.4 11.5 1.6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat;
          mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='10' viewBox='0 0 13 10'%3E%3Cpath d='M1.5 5.2 4.8 8.4 11.5 1.6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat;
}

/* 面板里的字段行：下拉要占满整行（与输入框同宽） */
.tk-attr-row > .sel { width: 100%; }
.tk-attr-row > .sel > .sel-btn { width: 100%; }

/* 窄屏：至少占满可用宽度，别挤成一小坨 */
@media (max-width: 640px) {
  .sel { flex: 1 1 auto; min-width: 0; }
  .sel-btn { width: 100%; }
}
