/* ============================================================
   Klog 样式 3/5 · 组件层
   装：排版工具 / 按钮 / 表单与输入 / 开关 / 徽章 / Toast / 卡片 / 表格 / 分段控件 /
       工具栏与空态 / 概览 / 备忘卡 / 纪念日卡 / 弹窗 dd-modal / 自定义下拉 /
       日期与日历 / 视图切换 / dock 席位 / 整页翻页 / 动效 keyframes
   这是「新页面应该复用的那一层」。做新界面前先在这里找现成外壳，
   找不到再考虑加——直接往 05 续一段就是 workbench.css 膨胀到 552KB 的原因。
   ─── 1008 拆分头结束 ─── */
/* ===================== Typography Utilities ===================== *//* (1008 死规则清理) *//* (1008 死规则清理) *//* (1008 死规则清理) */
.t-body { font-size: var(--fs-body); font-weight: var(--fw-regular); line-height: var(--lh-body); }/* (1008 死规则清理) */
.t-label { font-size: var(--fs-label); font-weight: var(--fw-regular); line-height: var(--lh-label); }
.t-micro { font-size: var(--fs-micro); font-weight: var(--fw-regular); line-height: var(--lh-micro); }
.font-mono { font-family: var(--font-mono); }
.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.muted { color: var(--text-4); }
.text-danger { color: var(--status-bad); }

/* ===================== Layout Utilities ===================== */
/* 1104 试点（见 DESIGN_SYSTEM.md §9）：重复 flex 行族抽工具类。只收单类成员（0,1,0）——
   复合选择器并入会降特异度被基础规则反杀；双类挂载同 .u-role-pill。 */
.u-row-3 { display: flex; align-items: center; gap: var(--sp-3); }
.u-row-2 { display: flex; align-items: center; gap: var(--sp-2); }

/* ===================== Buttons ===================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--meta-icon-gap);
  height: var(--control-h); line-height: 1; padding: 0 var(--control-px);
  border-radius: var(--radius-btn); font-size: var(--fs-caption); font-weight: var(--fw-regular);
  cursor: pointer; border: none; white-space: nowrap;
  transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}
.btn svg { width: 14px; height: 14px; }
.btn-primary { background: var(--primary); color: var(--on-primary); }
@media (hover: hover) { .btn-primary:hover { background: var(--primary-hover); } }
.btn-primary:active { transform: scale(0.97); background: var(--primary-hover); }
.btn-default { background: var(--btn-2-bg); color: var(--text-2); border: 1px solid var(--border); }
@media (hover: hover) { .btn-default:hover { border-color: var(--border-strong); background: var(--btn-2-bg-hover); color: var(--text-1); } }
.btn-default:active { transform: scale(0.97); background: var(--btn-2-bg-active); }
.btn-danger { background: var(--btn-2-bg); color: var(--status-bad); border: 1px solid var(--status-bad); }
@media (hover: hover) { .btn-danger:hover { background: var(--status-bad); color: var(--white); } }
.btn-danger:active { transform: scale(0.97); background: var(--status-bad); border-color: var(--status-bad); }
.btn:disabled { opacity: .5; cursor: not-allowed; pointer-events: none; transform: none; }
.btn-sm { height: var(--control-h-sm); padding: 0 var(--sp-3); }
.btn-link {
  border: none; background: transparent; padding: 0; cursor: pointer;
  color: var(--primary); font-size: var(--fs-caption);
  transition: color var(--transition-fast);
}
@media (hover: hover) { .btn-link:hover { text-decoration: underline; } }

/* ===================== Form / Input ===================== */
/* 1104 站主令：已填内容 --fld-ink（与占位同档的灰）+ 标题 --text-1 中粗＝全站唯一区分轴。
   搜索框不在此列——它是查询串不是表单字段，仍用主墨。 */
.form-input, .search-field-input {
  width: 100%; height: 36px; padding: 0 var(--sp-3); font-size: calc(13px * var(--fs-scale)); color: var(--text-1);
  background: var(--bg-page); border: 1px solid var(--border); border-radius: var(--radius-btn);
  outline: none; box-sizing: border-box; font-family: var(--font-sans); text-align: left;
}
.form-input { color: var(--fld-ink); }
.form-input::placeholder, .search-field-input::placeholder { color: var(--text-hint); }
@media (hover: hover) { .form-input:hover, .search-field-input:hover { border-color: var(--border-strong); } }
.form-input:focus, .search-field-input:focus { border-color: var(--primary); box-shadow: 0 0 0 2px var(--focus-ring); }
.form-input[readonly] { color: var(--fld-ink); background: var(--bg-disabled); cursor: default; }
select.form-input { padding: 0 var(--sp-2); cursor: pointer; }
textarea.form-input {
  height: auto; min-height: 72px; padding: var(--sp-2) var(--sp-3); line-height: 1.6; resize: vertical;
}
.form-input.date { width: 140px; }

.search-field { display: inline-flex; align-items: center; position: relative; width: min(320px, 100%); max-width: 100%; }
.search-field-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); width: 14px; height: 14px; color: var(--text-4); pointer-events: none; z-index: 1; }
.search-field-input { padding-left: 34px; padding-right: 30px; }
.search-field-input::-webkit-search-cancel-button { display: none; }
.search-clear {
  position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  width: 22px; height: 22px; border: none; border-radius: var(--radius-btn);
  background: transparent; color: var(--text-4); cursor: pointer;
  display: flex; align-items: center; justify-content: center; padding: 0;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.search-clear[hidden] { display: none; }
.search-clear svg { width: 10px; height: 10px; }
@media (hover: hover) { .search-clear:hover { background: var(--bg-hover); color: var(--text-1); } }

/* 下拉选择（原生 select 美化） */
.select-wrap { position: relative; display: block; }
.select-wrap::after {
  content: ""; position: absolute; right: 10px; top: 50%; transform: translateY(-35%);
  width: 0; height: 0; pointer-events: none;
  border-left: 3px solid transparent; border-right: 3px solid transparent;
  border-top: 4px solid var(--text-4);
}
.select-wrap select {
  appearance: none; -webkit-appearance: none;
  width: 100%; height: 36px; line-height: 1; padding: 0 30px 0 var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--radius-btn);
  background: var(--bg-page); font-size: calc(13px * var(--fs-scale)); color: var(--text-1);
  cursor: pointer; font-family: var(--font-sans);
  transition: border-color var(--transition-fast);
}
@media (hover: hover) { .select-wrap select:hover { border-color: var(--border-strong); } }
.select-wrap select:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 2px var(--focus-ring); }

/* 原生下拉展开后的列表：与站点设计系统对齐
   注：Chrome / Edge / Firefox 生效；Safari(macOS) 会忽略 option 自定义样式，
   此时依赖 html 上的 color-scheme 让浏览器自行适配深浅色 */
.select-wrap select option {
  background: var(--bg-card);
  color: var(--text-1);
  font-family: var(--font-sans);
  font-size: calc(13px * var(--fs-scale));
  padding: var(--sp-2);
}
.select-wrap select option:hover,
.select-wrap select option:focus {
  background: var(--bg-hover);
  color: var(--text-1);
}
.select-wrap select option:checked {
  background: var(--bg-muted);
  color: var(--text-1);
}
/* 占位选项（月 / 日 / 空值）弱化处理 */
.select-wrap select option[value=""] { color: var(--text-4); }

/* ---- Switch ---- */
.status-switch { position: relative; display: inline-block; width: 30px; height: 16px; cursor: pointer; flex-shrink: 0; }
.status-switch input { opacity: 0; width: 0; height: 0; }
.status-switch .switch-slider { position: absolute; inset: 0; background: var(--switch-track); border-radius: var(--radius-pill); transition: background var(--transition-fast); }
.status-switch .switch-slider::before { content: ''; position: absolute; width: 12px; height: 12px; left: 2px; top: 2px; background: var(--white); border-radius: var(--r-circle); transition: transform var(--transition-fast); } /* iOS 旋钮恒白，两档不翻 */
.status-switch input:checked + .switch-slider { background: var(--primary); }
.status-switch input:checked + .switch-slider::before { transform: translateX(14px); }

/* ===================== 徽章 / 标签 ===================== */
.badge {
  display: inline-flex; align-items: center; gap: var(--sp-2); height: 25px; padding: 0 var(--sp-2);
  border-radius: var(--radius-badge); font-size: var(--fs-caption); font-weight: var(--fw-medium);
  line-height: 1.4; color: var(--text-1); background: var(--bg-muted); border: 1px solid var(--border);
}
.badge::before { content: ""; display: inline-block; width: 6px; height: 6px; border-radius: var(--r-pill); flex-shrink: 0; background: var(--bdot, var(--text-disabled)); }/* (1008 死规则清理) */

/* 轻量标签（优先级 / 分类 / 状态） */
.tag {
  display: inline-flex; align-items: center; height: 20px; padding: 0 var(--sp-2);
  border-radius: var(--radius-badge); font-size: var(--fs-micro);
  line-height: 1; background: var(--bg-muted); color: var(--text-3); white-space: nowrap;
}
.tag-warn { background: var(--tag-warn-bg); color: var(--tag-warn-fg); }
.tag-bad  { background: var(--tag-bad-bg); color: var(--tag-bad-fg); }

/* 优先级：4 角闪烁星（1 / 2 / 3 颗） */
.prio { display: inline-flex; align-items: center; gap: 2px; line-height: 1; color: var(--text-4); }
.prio svg { width: 15px; height: 15px; flex-shrink: 0; }

/* ===================== Toast ===================== */
/* 左右通铺 + align-items 居中：若改用 left:50% + translateX(-50%) 定位，
   固定元素的 shrink-to-fit 可用宽度会被砍到视口一半，胶囊提前折行、max-width 形同虚设 */
.toast-container { position: fixed; top: calc(20px + var(--sat)); left: 0; right: 0; z-index: 9999; display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); pointer-events: none; }
.toast {
  display: inline-flex; align-items: center; gap: var(--sp-1); padding: var(--sp-2) var(--sp-4);
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-card);
  font-size: calc(14px * var(--fs-scale)); color: var(--text-1); box-shadow: var(--shadow-toast);
  max-width: min(86vw, 420px);
  /* 入场：一点过冲的弹簧（幅度到「看得见收进来」为止，不弹跳） */
  animation: toastIn var(--d-toast) var(--ease-pop);
}
/* 文案两行截断：toast 是轻提示不是摘要，再长也不许长成一堵墙 */
.toast-txt { min-width: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.toast-dot { width: 8px; height: 8px; border-radius: var(--r-circle); background: var(--status-good); flex-shrink: 0; box-shadow: 0 0 0 3px color-mix(in srgb, var(--status-good) 18%, transparent); }
.toast-warn .toast-dot { background: var(--warning); box-shadow: 0 0 0 3px color-mix(in srgb, var(--warning) 18%, transparent); }
.toast-error .toast-dot { background: var(--status-bad); box-shadow: 0 0 0 3px color-mix(in srgb, var(--status-bad) 18%, transparent); }
.toast-out { opacity: 0; transform: translateY(-6px) scale(.98); transition: opacity var(--d-base) ease, transform var(--d-base) ease; }
/* 同屏只留一枚（1008）：正在退场的那一枚脱离文档流，淡走的这 260ms 不占位，
   后到的那一枚就落在原来的位置上——不会出现「先被顶下去、等旧的没了再跳回来」的两段位移。
   容器本身是 position:fixed，天然是绝对定位的包含块；left/right:0 + fit-content + auto 边距
   复刻 flex 里 align-items:center 的收缩与居中，宽度算法一分不变。手机端换锚见下面那条。
   必须写成 .toast-container .toast-ghost：单类会被后面那条 .toast{pointer-events:auto}（在
   移动端 snackbar 段里，位置比这里晚）盖掉——淡走中的胶囊连同里面的撤销钮就成了隐形靶子。 */
.toast-container .toast-ghost { position: absolute; top: 0; left: 0; right: 0; width: -webkit-fit-content; width: fit-content; margin: 0 auto; pointer-events: none; }
@keyframes toastIn { from { opacity: 0; transform: translateY(-10px) scale(.96); } to { opacity: 1; transform: translateY(0) scale(1); } }
/* 撤销图标钮：图形在钮里，文案住 aria-label + title + .visually-hidden。
   延迟 60ms 弹一下——先看到提示条落定，再看到「还能反悔」那枚钮出现，视线有落点。
   幅度只到 0.55→1，不做回弹（用户定的「动效幅度适中不夸张」）。 */
.toast-act.is-icon {
  width: 32px; min-width: 32px; height: 32px; padding: 0; justify-content: center;
  animation: toastActPop var(--d-toast) var(--ease-pop) .06s both;
}
.toast-act.is-icon svg { width: 17px; height: 17px; }
@keyframes toastActPop { from { opacity: 0; transform: scale(.55); } to { opacity: 1; transform: scale(1); } }

/* ===================== Card / Panel ===================== */
/* 1030 清死样式：.summary-card / .summary-label / .summary-value / .summary-sub 一族随
   唯一使用者 pfKpiCard()（死函数，本轮已删）一起摘掉；卡片语言由 .panel 与 .pf-row 承担。 */

.panel {
  background: var(--bg-card); border: 1px solid var(--border);
  border-radius: var(--radius-card); overflow: hidden; box-shadow: var(--shadow-card);
}
.panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--border);
  font-size: var(--fs-label); font-weight: var(--fw-medium); color: var(--text-2);
}

/* ===================== Table ===================== */
.table-wrap { overflow-x: auto; }
.data-table { width: 100%; border-collapse: collapse; font-size: calc(13px * var(--fs-scale)); }
.data-table thead { background: var(--bg-header); }
.data-table th {
  padding: var(--sp-2) var(--sp-3); text-align: left; font-weight: 500; font-size: var(--fs-12); color: var(--text-3);
  white-space: nowrap; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
}
.data-table td { padding: var(--sp-3); color: var(--text-2); border-bottom: 1px solid var(--border); vertical-align: middle; }
/* 空态单元格：允许换行、不受 nowrap 影响 */
.data-table td[colspan] { white-space: normal; }
@media (hover: hover) { .data-table tbody tr:hover { background: var(--bg-hover); } }
.data-table tbody tr[data-id] { cursor: pointer; }
.memo-card, .date-card { cursor: pointer; }
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table td.code { font-family: var(--font-mono); font-size: calc(13.5px * var(--fs-scale)); font-variant-numeric: tabular-nums; color: var(--text-1); }
.col-check { width: 44px; }
.col-priority { width: 72px; }
.col-due { width: 170px; }
.col-created { width: 118px; }

/* 待办行 */
.todo-check { display: inline-flex; align-items: center; cursor: pointer; }
.todo-check input { position: absolute; opacity: 0; width: 0; height: 0; }
.todo-check-box {
  width: 16px; height: 16px; border: 1px solid var(--border-strong); border-radius: var(--r-1);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--bg-card); transition: background var(--transition-fast), border-color var(--transition-fast);
}
.todo-check-box svg { width: 10px; height: 10px; color: var(--on-primary); opacity: 0; }
@media (hover: hover) { .todo-check:hover .todo-check-box { border-color: var(--text-4); } }
.todo-check input:checked + .todo-check-box { background: var(--primary); border-color: var(--primary); }
.todo-check input:checked + .todo-check-box svg { opacity: 1; }
.todo-check input:focus-visible + .todo-check-box { outline: 2px solid var(--primary); outline-offset: 2px; }
.todo-title {
  color: var(--text-1); font-weight: var(--fw-medium); max-width: 460px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.todo-title.is-done { color: var(--text-4); text-decoration: line-through; font-weight: var(--fw-regular); }
.todo-desc {
  color: var(--text-4); font-size: var(--fs-caption); margin-top: 2px; max-width: 460px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ===================== Segmented ===================== */
.seg { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius-btn); background: var(--bg-card); overflow: hidden; }
.seg button {
  height: var(--control-h); padding: 0 var(--sp-4); border: none; background: transparent;
  font-size: var(--fs-caption); color: var(--text-3); cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.seg button + button { border-left: 1px solid var(--border); }
@media (hover: hover) { .seg button:hover { color: var(--text-1); background: var(--bg-hover); } }
.seg button.active { background: var(--primary); color: var(--on-primary); }
@media (hover: hover) { .seg button.active:hover { background: var(--primary-hover); color: var(--on-primary); } }
.seg-cnt {
  display: inline-flex; min-width: 16px; height: 16px; padding: 0 var(--sp-1); margin-left: var(--sp-1);
  border-radius: var(--r-2); background: color-mix(in srgb, var(--text-1) 12%, transparent);
  color: var(--text-2); font-size: var(--fs-10); font-family: var(--font-mono); line-height: 1;
  align-items: center; justify-content: center; vertical-align: middle;
}
.seg button.active .seg-cnt { background: color-mix(in srgb, var(--on-primary) 22%, transparent); color: var(--on-primary); }

/* ===================== Toolbar / 空态 ===================== */
.toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-3); margin-bottom: var(--sp-4); }
.toolbar .spacer { flex: 1; }
.toolbar .search-field-input, .toolbar .form-input, .toolbar select { background: var(--white); }
[data-theme="dark"] .toolbar .search-field-input,
[data-theme="dark"] .toolbar .form-input { background: var(--bg-soft); }
.toolbar[hidden] { display: none; }

.empty {
  padding: var(--sp-12) var(--sp-4); text-align: center; color: var(--text-4); font-size: var(--fs-caption);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.empty[hidden] { display: none; }
.empty-block {
  background: var(--bg-card); border: 1px solid var(--border);
  border-radius: var(--radius-card); box-shadow: var(--shadow-card);
}
.empty-ico { width: 36px; height: 36px; color: var(--text-disabled); margin-bottom: var(--sp-3); }
.empty-clear { margin-top: var(--sp-3); }

/* ===================== 概览 ===================== */
/* 1030 清死样式：.kpi-grid 全站在运行件里 0 命中（概览走 .ov-* 那套），摘掉。 */
.ov-empty { padding: var(--sp-6) var(--sp-4); text-align: center; color: var(--text-4); font-size: var(--fs-caption); }

/* ===================== 备忘卡片 ===================== */
.card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--card-gap); }
.memo-card {
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-card);
  padding: var(--sp-3) var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-3);
  box-shadow: var(--shadow-card);
  transition: border-color var(--transition-fast), transform var(--d-base) var(--ease-float), box-shadow var(--d-base) var(--ease-float);
}
@media (hover: hover) { .memo-card:hover { border-color: var(--border-strong); transform: translateY(-2px); box-shadow: var(--shadow-hover); } }
.memo-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3); }
.memo-title {
  font-size: var(--fs-body); font-weight: var(--fw-semibold); color: var(--text-1); line-height: 1.5;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}/* (1008 死规则清理) */
.memo-content {
  color: var(--text-3); font-size: var(--fs-caption); line-height: 1.6;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden; white-space: pre-wrap; word-break: break-word; min-height: 20px;
}
.memo-tags { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
/* 1011 D：备忘卡标签从卡底挪到标题下的 meta 行（分类徽章 + 创建于），与待办同一席位；
   原 .memo-foot（更新于/置顶文字）随乙案撤除——那些信息详情里都在 */
.memo-meta { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-1); margin-top: -4px; font-size: var(--fs-micro); color: var(--text-4); }
.memo-meta .todo-cat { flex: none; }
.memo-meta-sep { width: 1px; height: 11px; background: var(--border); flex-shrink: 0; }
.memo-when { white-space: nowrap; }

/* 标签筛选条 */
.chip-bar { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.chip {
  display: inline-flex; align-items: center; gap: var(--sp-1); height: 30px; padding: 0 var(--sp-3);
  /* 件档 r-2(8)：1012 站主两轮试作定案——胶囊与 r-3(12) 均被否（后者在 38px 手机上「偏圆」）。
     圆角与分段器凸块同为 8，上一排控件与下一排 chip 读成同一种圆度。 */
  border: 1px solid var(--border); border-radius: var(--r-2); background: var(--bg-card);
  color: var(--text-3); font-size: var(--fs-caption); cursor: pointer;
  transition: border-color var(--transition-fast), color var(--transition-fast), background var(--transition-fast);
}
@media (hover: hover) { .chip:hover { border-color: var(--border-strong); color: var(--text-1); } }
.chip:active { transform: scale(0.97); }
.chip.active { border-color: var(--primary); color: var(--text-1); font-weight: var(--fw-medium); }
.chip .chip-cnt { font-family: var(--font-mono); font-size: var(--fs-11); color: var(--text-4); }
.chip.active .chip-cnt { color: var(--text-2); }

/* ===================== 纪念日卡片 ===================== */
.date-list { display: flex; flex-direction: column; gap: var(--sp-3); }
.date-card {
  position: relative; overflow: hidden;
  display: flex; align-items: center; gap: var(--sp-4);
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-card);
  padding: var(--sp-3) var(--sp-4); box-shadow: var(--shadow-card);
  transition: border-color var(--transition-fast), transform var(--d-base) var(--ease-float), box-shadow var(--d-base) var(--ease-float);
}
@media (hover: hover) { .date-card:hover { border-color: var(--border-strong); transform: translateY(-2px); box-shadow: var(--shadow-hover); } }
.date-card.is-past { opacity: .72; }
.date-info { position: relative; z-index: 1; flex: 1; min-width: 0; }
.date-title { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.date-name { font-size: var(--fs-body); font-weight: var(--fw-semibold); color: var(--text-1); }/* (1008 死规则清理) */
.date-note { color: var(--text-3); font-size: var(--fs-caption); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.date-count { position: relative; z-index: 1; text-align: right; flex-shrink: 0; min-width: 84px; display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
.count-main { display: flex; align-items: baseline; gap: 2px; }
.count-num { font-family: var(--font-mono); font-size: var(--fs-num-sm); font-weight: var(--fw-semibold); color: var(--text-1); line-height: 1; }
.count-num.is-dash { color: var(--text-4); }
.count-unit { font-size: var(--fs-micro); color: var(--text-4); }
.count-label { color: var(--text-4); font-size: var(--fs-micro); }
/* 「今天 / 临近」＝右下角一枚淡类型色四分之一圆（圆心落在卡片右下角 → 露出约 ¼）：
   整卡回到白底普通描边，颜色只留角弧 + 类型色读数，比整卡轻染克制得多，多张并排也不花。
   今天 10%、临近再淡一档 8%。 */
.date-card.is-today::after, .date-card.is-soon::after {
  content: ""; position: absolute; z-index: 0; pointer-events: none;
  width: 88px; height: 88px; right: -44px; bottom: -44px; border-radius: var(--r-circle);
  background: color-mix(in srgb, var(--mood, var(--warning)) 10%, transparent);
}
.date-card.is-soon::after { background: color-mix(in srgb, var(--mood, var(--warning)) 8%, transparent); }
.today-word {
  font-size: var(--fs-num-sm); font-weight: var(--fw-bold); color: var(--mood, var(--text-1));
  line-height: 1; letter-spacing: 1px;
}
/* 纪念卡「至今 N 天」附加行：等宽数字，视觉弱于主倒计时一档 */
.count-since { color: var(--text-3); font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: var(--fs-11); }
.count-since b { color: var(--text-2); font-weight: var(--fw-semibold); }
.date-card.is-soon .count-num { color: var(--mood, var(--tag-warn-fg)); }

/* ===================== 弹窗 ===================== */
.modal-mask {
  position: fixed; inset: 0; z-index: 100; background: var(--mask);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 72px var(--sp-4) var(--sp-8); overflow-y: auto; overscroll-behavior: contain;
  animation: maskIn var(--d-fast) ease-out;
}
.modal-mask[hidden] { display: none; }
.modal-card {
  position: relative; /* 标题栏 absolute 悬浮的定位锚 */
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-lg);
  width: 100%; max-width: 480px; max-height: 82vh; display: flex; flex-direction: column;
  box-shadow: var(--shadow-modal); transform-origin: top center;
  animation: wrFormIn var(--d-modal) var(--ease-enter);
}
.modal-card.closing { animation: modalOut var(--d-fast) ease-in forwards; }
@keyframes modalOut { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(.97); } }
@keyframes maskIn { from { opacity: 0; } to { opacity: 1; } }
.modal-head {
  /* 悬浮于内容之上：body 内容从标题栏底下滚过，毛玻璃才有可见的模糊对象；
     上圆角与卡片一致，避免半透明背景在卡片圆角外露出方角（三角瑕疵） */
  position: absolute; top: 0; left: 0; right: 0; z-index: 5;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: calc(var(--sp-3) + var(--safe-top)) var(--sp-4) var(--sp-3); border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent); flex-shrink: 0;
  /* 与顶栏 .header 同款半透明毛玻璃质感（透明度同步 55% 档） */
  background: color-mix(in srgb, var(--bg-card) 55%, transparent);
  -webkit-backdrop-filter: blur(28px) saturate(180%);
  backdrop-filter: blur(28px) saturate(180%);
}
.modal-head h2 { font-size: var(--fs-h2); color: var(--text-1); }
/* 返回式关闭按钮：桌面隐藏，≤768px 显示并替代右上角 X（切换规则见移动端弹窗区块） */
.modal-back {
  display: none; align-items: center; justify-content: center; flex-shrink: 0;
  border: none; background: transparent; color: var(--text-2); cursor: pointer;
  width: 28px; height: 28px; margin-left: -6px; border-radius: var(--radius-btn);
  transition: background var(--transition-fast), color var(--transition-fast);
}
@media (hover: hover) { .modal-back:hover { background: var(--bg-hover); color: var(--text-1); } }
.modal-back svg { display: block; }
.modal-x {
  border: none; background: transparent; color: var(--text-4); font-size: calc(18px * var(--fs-scale)); line-height: 1;
  width: 28px; height: 28px; border-radius: var(--radius-btn); cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}
@media (hover: hover) { .modal-x:hover { background: var(--bg-hover); color: var(--text-1); } }
.modal-body { flex: 1 1 auto; min-height: 0; padding: calc(76px + var(--safe-top)) var(--sp-4) var(--sp-4); overflow-y: auto; } /* flex:1+min-height:0：长内容弹窗在 max-height 内正确内滚、底部不被裁（桌面此前缺，设置页等长弹窗末行被挡）；顶部 76px = 悬浮标题栏(56)+留白(20)+安全区 */
.modal-foot {
  display: flex; justify-content: flex-end; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4); border-top: 1px solid var(--border);
  background: var(--bg-soft); flex-shrink: 0; border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
.modal-foot-left { margin-right: auto; }
/* .modal-text 已废弃：确认类弹窗正文统一走文件末尾的 .cf-msg 外壳 */

/* 弹窗表单 */
/* 表单节奏（全站统一）：label 紧贴所属控件 5px，组间距 16px；
   报错/提示紧贴所属控件 6px */
.field { margin-bottom: var(--sp-4); }
.field:last-child { margin-bottom: 0; }
/* 字段标题唯一档（1104 站主令 · 实拍「新增订阅」同一张表单里标题三档并存）：
   字号 / 字重 / 墨色只在这一条里定义，端别差异全交给 --fld-label-fs
   （桌面 13px，手机端在 06 的 :root 重映射成 16px）——05/06 不再各抄一份字形。
   五条选择器 covers：普通字段、两列格里没挂 .field 的那格、开关行的小标题、
   复选框整行即标题（.check-line）、备忘「内容」那行包在格式条里的标题
   （`.field > label` 打不到它，从前一直在吃正文默认字）。
   display 与下间距属各组件自己的版式，留在各自规则里，不混进这一档。 */
.field > label, .field-row > div > label, .switch-row .switch-label, .check-line, .md-field-head > label {
  font-size: var(--fld-label-fs); font-weight: var(--fw-medium); color: var(--text-1);
}
.field > label, .field-row > div > label { display: block; margin-bottom: var(--sp-1); }
.field .req { color: var(--status-bad); margin-left: 2px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
.field-row-3 { display: grid; grid-template-columns: 1.3fr .9fr 1fr; gap: var(--sp-4); }
.form-error { color: var(--status-bad); font-size: var(--fs-label); margin-top: var(--sp-1); display: none; }
.form-error.show { display: block; }
.switch-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3); background: var(--bg-page); border: 1px solid var(--border);
  border-radius: var(--radius-btn); cursor: pointer;
}
/* 修复：上面第 837 行 .field > label { display: block }（特异性 0,1,1）一直压着
   .switch-row / .check-line（0,1,0），凡是写成 <div class="field"><label class="switch-row">
   的开关行都会退化为块级——开关掉到文字下方并跟着左对齐。补一条 class 级选择器抬回来 */
.field > .switch-row, .field > .check-line { display: flex; }
.switch-row .switch-desc { font-size: var(--fs-micro); color: var(--text-4); margin-top: 2px; }

/* ===================== 自定义下拉 ===================== */
/* 原生 select 保留在数据层（承载 value 与 change 事件），仅隐藏视觉 */
.sel-native { display: none !important; }
.sel-btn,
.dp-display {
  display: block; width: 100%; height: 36px; padding: 0 30px 0 var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--radius-btn);
  background: var(--bg-page); color: var(--fld-ink);
  font-family: var(--font-sans); font-size: calc(13px * var(--fs-scale)); line-height: 1;
  text-align: left; cursor: pointer; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
@media (hover: hover) {
.sel-btn:hover,
.dp-display:hover { border-color: var(--border-strong); }
}
.sel-btn.is-open,
.sel-btn:focus-visible,
.dp-display.is-open,
.dp-display:focus-visible {
  outline: none; border-color: var(--primary); box-shadow: 0 0 0 2px var(--focus-ring);
}
.sel-btn.is-placeholder,
.dp-display.is-placeholder { color: var(--text-hint); }

.sel-panel {
  /* fixed 定位：脱离弹窗内容区 overflow 裁切，坐标由 JS 按触发按钮计算 */
  position: fixed; left: 0; top: 0;
  z-index: 200; max-height: 240px; overflow-y: auto; overscroll-behavior: contain;
  padding: var(--sp-1); background: var(--bg-card);
  border: 1px solid var(--border); border-radius: var(--radius-card);
  box-shadow: var(--shadow-hover);
  animation: selIn var(--d-modal) var(--ease-enter);
}
.sel-panel[hidden] { display: none; }
.sel-opt {
  padding: var(--sp-2); border-radius: var(--radius-btn);
  font-size: calc(13px * var(--fs-scale)); color: var(--text-2); cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.sel-opt:focus { background: var(--bg-hover); color: var(--text-1); outline: none; }
@media (hover: hover) { .sel-opt:hover { background: var(--bg-hover); color: var(--text-1); outline: none; } }
.sel-opt.is-active { background: var(--bg-muted); color: var(--text-1); font-weight: var(--fw-medium); }
.sel-panel.is-up { animation-name: selInUp; }
@keyframes selIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
@keyframes selInUp { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }

/* ============ 日期选择 + 自定义日历 ============ */
/* 原生 input[type=date] 隐藏（仅承载数据/事件），
   可见层为 .dp-display 按钮 + .dp-panel 日历浮层。
   移动端点按日期框会唤起系统日历（浏览器内置行为，事件无法拦截），
   故必须替换可见层，系统选择器才不会出现 */
.dp-native { display: none !important; }

.date-wrap { position: relative; display: block; }
.date-wrap .dp-display { padding-right: var(--sp-8); }
.date-ico {
  position: absolute; right: 9px; top: 50%; transform: translateY(-50%);
  width: 20px; height: 20px; padding: 0; border: none; border-radius: var(--radius-btn);
  background: transparent; color: var(--text-4); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: color var(--transition-fast), background var(--transition-fast);
}
.date-ico svg { width: 15px; height: 15px; pointer-events: none; }
@media (hover: hover) { .date-ico:hover { color: var(--text-1); background: var(--bg-hover); } }
.date-ico.is-open { color: var(--text-1); }

/* 月历浮层（单例，挂 body，fixed 定位脱离弹窗裁切，坐标由 JS 计算） */
.dp-panel {
  position: fixed; left: 0; top: 0; z-index: 200;
  padding: var(--sp-2); background: var(--bg-card);
  border: 1px solid var(--border); border-radius: var(--radius-card);
  box-shadow: var(--shadow-hover);
  animation: selIn var(--d-modal) var(--ease-enter);
}
.dp-panel[hidden] { display: none; }
.dp-panel.is-up { animation-name: selInUp; }
.dp-head { display: flex; align-items: center; gap: 3px; margin-bottom: var(--sp-1); }
.dp-title { flex: 1; display: flex; align-items: center; justify-content: center; gap: 2px; min-width: 0; font-size: var(--fs-caption); font-weight: var(--fw-semibold); color: var(--text-1); }
.dp-tl {
  border: none; background: transparent; color: var(--text-1);
  font-size: var(--fs-caption); font-weight: var(--fw-semibold);
  padding: 3px var(--sp-1); border-radius: var(--radius-btn); cursor: pointer; line-height: 1.4;
  white-space: nowrap; flex-shrink: 0;
  transition: background var(--transition-fast);
}
@media (hover: hover) { .dp-tl:hover { background: var(--bg-hover); } }
.dp-caret { width: 12px; height: 12px; margin-left: var(--sp-1); color: var(--text-3); flex-shrink: 0; }
.dp-nav {
  width: 26px; height: 26px; border: 1px solid var(--border); border-radius: var(--radius-btn);
  background: var(--bg-card); color: var(--text-3); cursor: pointer;
  font-size: calc(14px * var(--fs-scale)); line-height: 1;
  display: flex; align-items: center; justify-content: center;
  transition: border-color var(--transition-fast), color var(--transition-fast), background var(--transition-fast);
}
@media (hover: hover) { .dp-nav:hover { border-color: var(--border-strong); color: var(--text-1); background: var(--bg-hover); } }
.dp-week { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; margin-bottom: var(--sp-1); }
.dp-week span { text-align: center; font-size: var(--fs-micro); color: var(--text-4); line-height: 22px; }
.dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
/* 年 / 月选择视图（点头部「2026 年」「9 月」进入） */
.dp-years {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 2px;
  max-height: 216px; overflow-y: auto; overscroll-behavior: contain;
}
.dp-year {
  height: 32px; border: none; border-radius: var(--radius-btn);
  background: transparent; color: var(--text-2); font-size: var(--fs-caption);
  font-family: var(--font-mono); font-variant-numeric: tabular-nums; cursor: pointer;
  white-space: nowrap;
  transition: background var(--transition-fast), color var(--transition-fast);
}
@media (hover: hover) { .dp-year:hover { background: var(--bg-hover); color: var(--text-1); } }
.dp-year.is-sel { background: var(--primary); color: var(--on-primary); font-weight: var(--fw-medium); }
.dp-months { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2px; }
.dp-month {
  height: 42px; border: none; border-radius: var(--radius-btn);
  background: transparent; color: var(--text-2); font-size: var(--fs-caption);
  cursor: pointer; white-space: nowrap;
  transition: background var(--transition-fast), color var(--transition-fast);
}
@media (hover: hover) { .dp-month:hover { background: var(--bg-hover); color: var(--text-1); } }
.dp-month.is-sel { background: var(--primary); color: var(--on-primary); font-weight: var(--fw-medium); }
.dp-day {
  height: 30px; border: none; border-radius: var(--radius-btn);
  background: transparent; color: var(--text-2); font-size: var(--fs-caption);
  font-family: var(--font-sans); cursor: pointer; line-height: 1;
  transition: background var(--transition-fast), color var(--transition-fast);
}
@media (hover: hover) { .dp-day:hover { background: var(--bg-hover); color: var(--text-1); } }
.dp-day.is-out { color: var(--text-disabled); }
.dp-day.is-today { box-shadow: inset 0 0 0 1px var(--border-strong); }
.dp-day.is-sel { background: var(--primary); color: var(--on-primary); font-weight: var(--fw-medium); }
@media (hover: hover) { .dp-day.is-sel:hover { background: var(--primary); color: var(--on-primary); } }
.dp-foot {
  display: flex; justify-content: space-between; gap: var(--sp-2);
  margin-top: var(--sp-2); padding-top: var(--sp-2); border-top: 1px solid var(--border);
}
.dp-quick {
  border: none; background: transparent; color: var(--primary); cursor: pointer;
  font-size: var(--fs-caption); padding: 2px var(--sp-1); border-radius: var(--radius-btn);
  transition: color var(--transition-fast);
}
@media (hover: hover) { .dp-quick:hover { text-decoration: underline; } }

/* ===================== 数据中心（弹窗内提示语；区块结构已迁往 .pfg / dd-modal） ===================== */
.dc-tip { margin-top: var(--sp-3); font-size: var(--fs-label); color: var(--text-4); line-height: 1.7; }
.dc-tip b { color: var(--text-2); }
/* 数据中心弹窗在桌面端略宽，容纳双列 KPI；移动端保持全屏（不受影响） */
@media (min-width: 769px) { .modal-card.dc-card { max-width: 520px; } }
@media (min-width: 769px) { .modal-card.hm-modal { max-width: 560px; } } /* 热力面板（年视图连续日期网格）需要更宽的画幅 */

/* ===================== 视图切换 ===================== */
.view { display: block; animation: pageSlideIn var(--d-enter) var(--ease-enter); }
.view[hidden] { display: none; }
/* 回到半途那一眼只做淡入：整页顶高 12px（手机）/侧移 20px（桌面）再落回去，
   在长列表里读作「内容跳了一下」，且整只高页面的 transform 合成还要掉帧。
   由 switchView 按「这一席存的位置 > 24px」挂上；特异性高于两端各自的 .view，
   媒体查询谁先谁后都盖得住。 */
.view.is-deep-in { animation-name: pageFadeIn; }
/* ---------- dock 席位左右推（1007 站主定） ----------
   dock 那一排是一条横向的门牌：点更右边的席位＝当前页整页往左退场、新页从屏幕外右往左推进来；
   点更左边的席位整套镜像。两页必须同时在场才读得出「推」，所以进场这一趟把新页摘成一只固定层，
   四边由 JS（pushFit）现场量出写行内样式，锚点＝「归位后这一页本来占的那块屏」：
     顶＝顶栏下缘 **+ 内容盒上内边距**（少垫这一档就是站主报的「页面贴着顶栏滑进来、到位后往下卡顿一下」，
        差的正是 --content-py 那 16px）；
     左右＝内容盒内缘（等于归位后的行宽，滑进来不许重排一次）；
     底＝滚动区下缘——dock 是浮在滚动区上的（实测 main 底 844 / dock 顶 751），
        裁在 dock 上缘等于收架那一刻凭空多出一条 90 来 px 的内容，又读成跳一下。
   旧页留在文档流里原地滑走——全程不碰 .main 的 scrollTop，避免「滑到一半先跳回页顶」。
   只在「新页要从页顶开始」时推：半途回位那一趟仍旧只淡入（见上面 .view.is-deep-in），
   那一读的是人手里的那一行，横向推它等于把人家正看着的东西换走。
   特异性 .content > .view.is-* = 0,3,0，压得住文件后段那条移动端 `.view{animation:pageUpIn}` 简写
   （媒体查询不加特异性，站内老账）；收架在 JS 里与「藏旧页」同一拍提交，一帧画完看不见交接。 */
.content > .view.is-push-layer {
  position: fixed; z-index: 3; margin: 0; overflow: hidden;
  background: var(--bg-page); will-change: transform;
}
/* 顶栏标题跟着换页走：站内同一时刻已有的弹跳语言就是 dock 的 tabPop（0.82→1.08→1），
   这里沿用它的形状、把幅度收到约三分之一——文字比图标经不起大弹，过了就是「抖」。
   **不带 opacity**：曾经从 0 淡起，真机上读作「先闪一下再抖」（1008 反馈）——
   标题换字本来就发生在那一拍，闪与弹叠在一起就是两拍噪声，只留弹。
   transform-origin 钉死左中：默认居中缩放会让整行文字左右各漏出半个字的位移，
   读起来是晃，不是弹。时长与 --d-push 同档，弹落正好在页面推到位那一刻。 */
.header-txt.is-swap { animation: headTextPop var(--d-push) var(--ease-enter); transform-origin: left center; }
/* ===== 1016 逐字滚动揭示 / 跑带的骨架（灵动岛文字与顶栏标题共用一套） =====
   拆字一律「跑带 .il-run 包着单字 .il-g」两层：跑带负责整句横向走位（装不下时），
   单字负责自己那一下上升+淡入+回缩。两层分开，才不会出现「字在滚、句子也在滚」互相拽。
   · .il-g 必须 display:inline-block 才能吃 transform；
   · 必须 white-space:pre——空格被单独拆成一枚 inline-block 时，normal 会把它压成 0 宽，
     整句就粘成一坨（这条是拆字方案最经典的翻车点）；
   · 位移/缩放/虚化全写在 JS 的关键帧里且用 em，字号一变整套自己跟着缩放，不留特例补丁。 */
.il-run { display: inline-block; white-space: nowrap; }
/* 允许折行的载体（弹窗头部标题）：跑带默认 nowrap 是给灵动岛那种「永不两行」的场合，
   长标题套上去会被顶成一列、把头部右侧的保存钮挤出界外，这里放开回正常折行。 */
.il-run.il-wrap { white-space: normal; }
.il-g { display: inline-block; white-space: pre; font-style: normal; }

/* 手指拖着换页那一趟：dock 的选中态改由手指驱动（指示块逐帧跟手），
   所以期间要给两样东西让路——① 图标那 0.3s 的 tabPop（它是「点按切换」的播报，
   拖动时再播一遍＝节奏比页面慢半拍）；② 任何色的过渡（选中色要随手指立刻到位）。 */
.tabbar-dock.is-pgdrag .menu-item.active svg { animation: none; }
.tabbar-dock.is-pgdrag .menu-item,
.tabbar-dock.is-pgdrag .menu-item svg { transition: none; }
.content > .view.is-push-out { pointer-events: none; }
.content > .view.is-slide-out-l { animation: slideOutLeft var(--d-push) var(--ease-float) both; }
.content > .view.is-slide-in-r  { animation: slideInRight var(--d-push) var(--ease-float) both; }
.content > .view.is-slide-out-r { animation: slideOutRight var(--d-push) var(--ease-float) both; }
.content > .view.is-slide-in-l  { animation: slideInLeft var(--d-push) var(--ease-float) both; }
/* 收架那一刻必须把这页的入场动画整个关掉：位移类一摘，计算出来的 animation-name 就从
   slideInRight 换回基础的 pageUpIn —— **animation-name 一变＝重新起架**，于是整页又淡一遍
   还往下弹 12px（实测 opacity 在 333ms 掉回 0 再爬），站主读作「滑完还跳一下，像在刷新」。
   横向推本身就是这一页的入场，不需要第二遍。下一次切页由 switchView 先摘掉这个类，
   所以下一趟该淡入（半途回位）或该推的时候，动画照常起。 */
.content > .view.is-pushed { animation: none; }
/* ===================== 整页左右滑翻页：触摸归属（1008 真机反馈） =====================
   页面自己声明「竖向让给浏览器、横向留给翻页」。不声明的话浏览器按 auto 做滚动仲裁：
   起步那几帧只要看着像竖向，它就开始滚 → 按规范 **pointercancel** 掐断指针流，
   我们的拖动当场死掉——真机上读作「断触/点了没反应」。声明 pan-y 之后，
   横向手势浏览器不接手，也就没有那一下取消。
   内层自己能横滑的东西单独放行（标签条、表格、热力图年视图、看图台胶片、Markdown 工具条），
   它们照旧横滑；翻页也照旧让位给它们（JS 里 pgHOwner 判）。 */
.content > .view { touch-action: pan-y; }
.chip-scroll, .table-wrap, .cat-filter, .hm-year-scroll, .dg-film, .md-fmt-bar { touch-action: auto; }
.modal-mask, .wp-mask { touch-action: auto; }/* (1008 死规则清理) */
.tbody-enter tr { animation: rowIn var(--d-modal) var(--ease-float) backwards; }
.tbody-enter tr:nth-child(2) { animation-delay: 18ms; }
.tbody-enter tr:nth-child(3) { animation-delay: 36ms; }
.tbody-enter tr:nth-child(4) { animation-delay: 54ms; }
.tbody-enter tr:nth-child(5) { animation-delay: 72ms; }
.tbody-enter tr:nth-child(6) { animation-delay: 90ms; }
.tbody-enter tr:nth-child(7) { animation-delay: 108ms; }
.tbody-enter tr:nth-child(8) { animation-delay: 126ms; }

/* 待办完成/撤销收场（js: animateTodoRemoval）：本行淡出右移 → 摘除 → 其余行 FLIP 缓动补位。
   is-flipping=钉在旧位置（禁过渡），is-settling=松手滑向真实位置；时长与 JS 常量保持一致 */
#todoBody tr.is-removing {
  opacity: 0; transform: translateX(22px); pointer-events: none;
  transition: opacity var(--d-micro) ease, transform var(--d-micro) var(--ease-float);
}
#todoBody tr.is-flipping { transition: none !important; }
#todoBody tr.is-settling { transition: transform var(--d-base) var(--ease-float); }
/* 「全部」视图重排：换位行一律「右滑出 + 淡出」(is-leaving 提供过渡，右移量由 JS 以
   行内 transform=钉Y+滑X 复合写入，保住垂直钉位)，落位处 is-arriving 淡入；
   is-flying=抬层+不透明卡底+投影，滑出时压在所在区域之上、不埋进别行。
   短距上行补位仍走 is-settling FLIP。 */
#todoBody tr.is-flying {
  position: relative; z-index: 2;
  background: var(--bg-card);
  box-shadow: 0 4px 14px rgba(0, 0, 0, .14);
}
#todoBody tr.is-leaving {
  opacity: 0; pointer-events: none;
  transition: opacity var(--d-micro) ease, transform var(--d-micro) var(--ease-float);
}
#todoBody tr.is-arriving { animation: rowIn var(--d-modal) var(--ease-float); }
@media (prefers-reduced-motion: reduce) {
  #todoBody tr.is-removing, #todoBody tr.is-settling { transition: none; }
  #todoBody tr.is-leaving { transition: none; }
  #todoBody tr.is-arriving { animation: none; }
}

/* 「全部」视图更早已完成的折叠行（js: renderTodos / toggleTodoDoneFold）：
   借时间线 .tl-more 的虚线细行语言落进表格；折叠行自己不带表格线，视觉全交给按钮，
   收合状态以按钮 aria-expanded 为准。 */
#todoBody tr.todo-done-fold td { padding: var(--sp-1) 0 2px; }
.todo-fold-btn {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); width: 100%;
  padding: var(--sp-2) var(--sp-3); cursor: pointer;
  border: 1px dashed var(--border-strong); border-radius: var(--radius-card);
  background: transparent; color: var(--text-3); font-family: inherit; font-size: var(--fs-caption);
  transition: border-color var(--transition-fast), color var(--transition-fast), background var(--transition-fast);
}
.todo-fold-btn .todo-fold-go { color: var(--text-4); font-size: var(--fs-micro); flex-shrink: 0; }
@media (hover: hover) and (pointer: fine) {
  .todo-fold-btn:hover { border-color: var(--text-4); color: var(--text-1); background: var(--bg-hover); }
}

/* ===================== 动效 Keyframes ===================== */
@keyframes pageSlideIn { from { opacity: 0; transform: translateX(-20px); } to { opacity: 1; transform: translateX(0); } }
@keyframes pageFadeIn { from { opacity: 0; } to { opacity: 1; } }   /* 深层回位专用：只淡不移（.view.is-deep-in） */
/* dock 席位左右推的四条：两条一对（往右翻＝旧页退左·新页进右），往左翻整套镜像。
   进出各 100% 自身宽＝两页严丝合缝相接，中间不露底色。 */
@keyframes slideOutLeft  { from { transform: translateX(0); }      to { transform: translateX(-100%); } }
@keyframes slideInRight  { from { transform: translateX(100%); }   to { transform: translateX(0); } }
@keyframes slideOutRight { from { transform: translateX(0); }      to { transform: translateX(100%); } }
@keyframes slideInLeft   { from { transform: translateX(-100%); }  to { transform: translateX(0); } }
@keyframes headTextPop { 0% { transform: scale(.97); } 55% { transform: scale(1.02); } 100% { transform: scale(1); } }
@keyframes listSlideIn { from { opacity: 0; transform: translateX(-20px); } to { opacity: 1; transform: translateX(0); } }
@keyframes wrFormIn { from { opacity: 0; transform: scale(1.04); } to { opacity: 1; transform: scale(1); } }
@keyframes rowIn { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-4px); }
  55% { transform: translateX(4px); }
  80% { transform: translateX(-2px); }
}
/* .shake 类已废弃：弹窗抖动改用 WAAPI（element.animate），
   避免切换 animation 属性导致入场动画（wrFormIn）重放。
   @keyframes shake 仍被登录页错误提示使用 */
