/* =====================================================================
   托管中心财务记账 · 清爽专业主题
   ---------------------------------------------------------------------
   设计要点（frontend-design）：
   - 主色 = 账本蓝（严谨、可信），点缀 = 琥珀金（托管中心的温度），
     用色克制：琥珀只在品牌标 / 突出数字上出现。
   - 视觉主角 = 金额列：等宽数字、明确的正负号与颜色，一眼读到钱。
   - 凭证号做成"凭证牌"（等宽小圆签），呼应手工账本里的凭证。
   - 白色吸顶导航，卡片细阴影，圆角统一，移动端触控加大。
   ===================================================================== */

:root {
  --bg: #f3f5f8;
  --surface: #ffffff;
  --primary: #1b5f9b;
  --primary-dark: #16497a;
  --primary-weak: #eaf2fb;
  --accent: #e08a2e;
  --text: #1c2733;
  /* 灰字压在白底 5.3:1、压在页面灰底 4.8:1；原来的 #6b7684 在灰底上只有 4.2:1 */
  --muted: #5d6875;
  --border: #e3e8ef;
  --income: #12805c;
  --income-weak: #e6f4ee;
  --expense: #c2412f;
  --expense-weak: #fbece9;
  --radius: 10px;
  --shadow: 0 1px 2px rgba(20, 40, 70, .04), 0 6px 18px rgba(20, 40, 70, .06);
  --font: -apple-system, BlinkMacSystemFont, "PingFang SC", "HarmonyOS Sans SC",
          "Microsoft YaHei", "Noto Sans SC", "Segoe UI", sans-serif;
  --mono: ui-monospace, "SF Mono", Consolas, "Cascadia Mono", Menlo, monospace;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.55;
  background: var(--bg);
  color: var(--text);
  -webkit-tap-highlight-color: transparent;
}

.wrap { max-width: 1040px; margin: 0 auto; padding: 0 14px; }

/* ---------------- 顶栏 ---------------- */
.topbar {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 50;
}
.topbar .wrap.top {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding-top: 10px;
  padding-bottom: 10px;
}
.brand {
  display: flex;
  align-items: center;
  gap: 9px;
  font-weight: 700;
  font-size: 16px;
  color: var(--text);
  white-space: nowrap;
  text-decoration: none;
}
.brand:hover { color: var(--primary); }
.brand-mark {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  background: linear-gradient(135deg, var(--primary), #2e7cc0);
  color: #fff;
  display: grid;
  place-items: center;
  flex: none;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .22);
}
.brand-mark svg { display: block; }

.nav { display: flex; gap: 4px; flex: 1; flex-wrap: wrap; }
.nav a {
  color: var(--muted);
  text-decoration: none;
  font-size: 14px;
  padding: 6px 10px;
  border-radius: 8px;
  transition: color .12s, background .12s;
}
.nav a:hover { color: var(--primary); background: var(--primary-weak); }
.nav a.on { color: var(--primary); background: var(--primary-weak); font-weight: 600; }
.nav a:focus-visible, .link-btn:focus-visible, .up summary:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.link-btn {
  background: none;
  border: none;
  color: var(--primary);
  cursor: pointer;
  font-size: 13px;
  text-decoration: none;
  padding: 0;
}
.link-btn:hover { text-decoration: underline; }

main.wrap.main { padding-top: 20px; padding-bottom: 56px; }

/* ---------------- 标题 / 文字 ---------------- */
h1 { font-size: 21px; margin: 6px 0 16px; letter-spacing: -.01em; }
h2 { font-size: 15px; margin: 0 0 12px; color: #33404d; }
/* 「其中：预收收款」那行不是独立收支，是上一行的成分，沿用同一组灰字小一号的声明。
   注意不能用 class="muted"：报表页整页禁用了那个类（tests/test_balance_sheet.py:153）。 */
.muted,
.tbl.report tr.sub td { color: var(--muted); font-size: 13px; }
.center { text-align: center; }
a { color: var(--primary); }

/* ---------------- 卡片 ---------------- */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px;
  margin-bottom: 16px;
  box-shadow: var(--shadow);
}

/* ---------------- 提示条 ---------------- */
.alert {
  border-radius: 8px;
  padding: 10px 12px;
  margin: 0 0 14px;
  font-size: 14px;
}
.alert.error { background: var(--expense-weak); color: #8c2417; border: 1px solid #f0cfc7; }
.alert.ok { background: var(--income-weak); color: #0e5f44; border: 1px solid #c4e6d6; }

/* ---------------- 表单 ---------------- */
form label { display: block; margin: 0 0 14px; font-size: 14px; color: #33404d; }
form label small { color: var(--muted); font-weight: 400; }
form input[type=text], form input[type=password], form input[type=date],
form input[type=month], form input[type=number], form select, form textarea {
  display: block;
  width: 100%;
  margin-top: 5px;
  padding: 9px 11px;
  font-size: 15px;
  font-family: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid #cbd3de;
  border-radius: 8px;
  transition: border-color .12s, box-shadow .12s;
}
form input:focus, form select:focus, form textarea:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(27, 95, 155, .14);
}

/* 改密即时校验提示（JS 控制 hidden 显隐） */
.field-hint.pw-mismatch { color: var(--expense); font-size: 12px; }
.field-hint.pw-mismatch[hidden] { display: none; }

/* 密码框显示/隐藏切换。
   原来是 background:none + border:none，看着像纯文字，没人知道能点；
   套 .btn.undo 那套配方（描边 + 浅底 + 主题色文字），明确它是个按钮。 */
.pwd-wrap { position: relative; display: block; }
.pwd-wrap input[type=password], .pwd-wrap input[type=text] { padding-right: 52px; }
.pwd-toggle {
  position: absolute;
  right: 5px;
  top: calc(50% + 2px);
  transform: translateY(-50%);
  background: #fff;
  border: 1px solid #cbd3de;
  color: var(--primary);
  font-size: 12px;
  font-family: inherit;
  line-height: 1;
  padding: 6px 8px;
  border-radius: 6px;
  cursor: pointer;
  min-height: 30px;
}
.pwd-toggle:hover { background: #f1f6fc; border-color: var(--primary); }
.pwd-toggle:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* 凭证截图选图反馈 */
.file-pick {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 8px;
  padding: 8px 10px;
  border: 1px dashed var(--border);
  border-radius: 8px;
  background: #fbfcfe;
}
/* 必须显式写：上面的 display:flex 盖过了浏览器对 [hidden] 的默认 display:none，
   否则还没选图，这个空框（只剩一个「清除」）就一直在表单里占着一行。 */
.file-pick[hidden] { display: none; }
.file-pick-info { font-size: 13px; color: var(--muted); }
.file-pick-previews { display: flex; gap: 6px; }
.file-pick-thumb {
  width: 48px;
  height: 48px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--border);
}

/* 表单两列网格（记账/改账表单） */
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0 16px; }
.form-grid .wide { grid-column: 1 / -1; }
.form-actions { display: flex; gap: 10px; margin-top: 4px; }

/* 改账页凭证图管理 */
.edit-images { border-top: 1px dashed var(--border); margin-top: 16px; padding-top: 14px; }
.edit-images-head { font-weight: 600; margin-bottom: 2px; }
.edit-images-head .muted { font-weight: 400; }
.img-grid { display: flex; flex-wrap: wrap; gap: 12px; margin: 10px 0 4px; }
.img-item { margin: 0; text-align: center; }
.img-item .thumb-lg {
  width: 104px; height: 104px; object-fit: cover; border-radius: 8px;
  border: 1px solid var(--border); display: block;
}
.img-item figcaption { font-size: 12.5px; margin-top: 4px; color: var(--muted); }
.img-item figcaption label { display: inline-flex; gap: 4px; align-items: center; }
.file-label { display: block; margin-top: 12px; font-weight: 600; }
.file-label input[type="file"] { display: block; margin-top: 6px; }

/* 行内筛选表单（年份/期间等） */
.row-form { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.row-form label { margin-bottom: 0; }
.row-form input, .row-form select { width: auto; min-width: 110px; margin-top: 4px; }
.row-form .btn { margin-bottom: 0; }

/* 录入表单直接铺开（原「＋ 记一笔」的展开/收起已取消：多一次点击没有意义） */
.entry-form { margin-top: 10px; }

/* ---------------- 按钮 ---------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 16px;
  font-size: 14px;
  font-family: inherit;
  line-height: 1.3;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  text-decoration: none;
  transition: background .12s, border-color .12s, transform .04s;
}
.btn:hover { background: #f6f8fb; border-color: #cfd7e2; }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.btn.primary { background: var(--primary); border-color: var(--primary); color: #fff; }
.btn.primary:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
.btn.danger { background: var(--expense); border-color: var(--expense); color: #fff; }
.btn.danger:hover { background: #a83424; border-color: #a83424; }
/* 次要操作（/entries 的「改」「传图」、/students 的「改」…）统一成一套长相：
   主题色文字 + 与 .btn 基准同色的浅描边——看着能点，又不必比普通按钮重。
   描边一旦比 .btn 深（原来用 #cbd3de > var(--border)），同一张卡里的「改」
   就会比「添加项目」还抢眼，层级倒挂。改重了 tests/test_ui_affordance 会红。
   ⚠️ .btn.undo 是另一套配方（见下），别把它并进来。 */
.btn.ghost { background: #fff; border-color: var(--border); color: var(--primary); }
.btn.ghost:hover { background: #f1f6fc; border-color: var(--primary); }
/* 破坏性行内按钮（「停用」「摘掉」「撤回申请」「撤回」）：形仍与 ghost 一致
   （白底、圆角、同尺寸），只把文字与描边换成警示色——一眼分得开哪个点一下收不回来。
   刻意**不用** .btn.danger 那种实心红：实心红留给真正提交型的危险操作，
   行内小按钮套实心红会比主操作还重，层级倒挂。警示色复用 --expense，别另造色值。 */
.btn.ghost.danger { border-color: var(--expense); color: var(--expense); }
.btn.ghost.danger:hover { background: var(--expense-weak); border-color: var(--expense); }
/* 「撤销结转」原来用 ghost（透明底、无边框）看着像纯文本，没人知道能点。
   给一个可见的描边 + 主题色文字，明确它是个按钮。 */
.btn.undo { background: #fff; border-color: #cbd3de; color: var(--primary); }
.btn.undo:hover { background: #f1f6fc; border-color: var(--primary); }
.btn.sm { padding: 3px 10px; font-size: 13px; border-radius: 7px; }
.btn.block { display: flex; width: 100%; }
.btn[disabled], .btn.disabled { opacity: .55; pointer-events: none; }

.inline { display: inline; }
.ops .inline, .ops form { display: inline-block; }

/* ---------------- 表格 ---------------- */
.table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--border);
  border-radius: 10px;
}
/* 名单表 / 收费情况的固定高度 + 内部滚动：名单表 50 来行，不封顶会把下面
   「收费情况」整张卡顶出首屏——用户不想扫完整张名单才看到收费。
   表头吸顶。注意 .tbl th 的底色是浅灰 #f7f9fc（深蓝 #305496 只属于 .tbl.report），
   但同样是不透明的，滚起来不会穿帮。border-collapse 会让 sticky 表头在滚
   动中丢掉下边框、露出 1px 亮缝，用一道 inset 阴影补上。 */
.table-wrap.capped { max-height: 45vh; overflow-y: auto; }
.table-wrap.capped > .tbl thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  box-shadow: inset 0 -1px 0 var(--border);
}
.tbl { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 660px; }
/* 报表里的表不撑最小宽度：资产负债表只有 2 列、利润表 4 列，
   硬撑到 660px 只会把「项目」列拉得老长、把金额推到屏幕外，
   逼人横向滚动才看得到数字——而数字恰恰是报表的正文。
   宁可让项目名折行，也要把金额留在首屏。 */
.tbl.report { min-width: 0; }
.tbl th, .tbl td {
  border-bottom: 1px solid var(--border);
  padding: 9px 10px;
  text-align: left;
  vertical-align: top;
}
.tbl th {
  background: #f7f9fc;
  color: #46525f;
  font-weight: 600;
  font-size: 12px;
  white-space: nowrap;
  letter-spacing: .02em;
}
/* 可排序的表头（名单表的「姓名 / 年级」）：颜色跟别的表头一样，不做成蓝色链接——
   一整行表头里跳出两个蓝词，看着像两处待办。能不能点靠一条虚线下划线提示：
   手机上**没有 hover**，只靠悬停变色的话，触摸设备上根本看不出这两列能点。
   虚线用 text-decoration 而不是 border-bottom：手机上这条链接要撑成 44px 高的
   触摸目标（见文件末尾的粗指针块），border 会跟着跑到那一格的底边上去，
   text-decoration 则始终贴着字。悬停/键盘聚焦转成实线，给一个「正指着它」的反馈。 */
.tbl th .sort-link { color: inherit;
  text-decoration: underline dotted rgba(70, 82, 95, .5);
  text-decoration-thickness: 1px; text-underline-offset: 3px; }
.tbl th .sort-link:hover, .tbl th .sort-link:focus-visible { text-decoration-style: solid; }
/* 箭头只出现在当前排的那一列 */
.tbl th .sort-arrow { margin-left: 2px; font-size: 11px; }
/* **整格都能点**：把表头格的内边距整个让给链接。<a> 默认是行内的，可点区只有
   文字那么宽——手机实测「年级」那格 24px、「姓名」40px，格子里剩下的一圈 10px
   边距看着像能点，点上去没反应。挪完之后链接铺满整格（桌面观感一模一样：
   padding 总量没变，只是从 th 换到了 a 上）。
   只圈名单表这两个可排序的表头格，别的表头没有链接。 */
.tbl.roster > thead th:has(.sort-link) { padding: 0; }
.tbl.roster > thead th .sort-link { display: block; padding: 9px 10px; }
/* 悬停高亮要排除大类行/合计行：那两行现在有明确的底色，
   再被悬停盖成 #f8fafd 会把层次抹掉（这两个 class 只有报表页在用） */
.tbl tbody tr:not(.section):not(.total):not(.row-edit-row):hover td { background: #f8fafd; }
/* 下面这组色号与 report_builder.py 顶部的 *_FILL 一一对应，
   导出的 Excel 和网页看到的是同一套颜色。改一边记得同步另一边。 */
.tbl.report th { background: #305496; color: #fff; }
.tbl .section td { background: #d9e2f3; font-weight: 700; color: #33404d; }
.tbl .total td { background: #fff2cc; font-weight: 700; border-top: 2px solid #dfe5ec; }
/* 项目名称格浅灰底。只给报表体（.stmt）——流水/清单表整列上色就太花了 */
.tbl.stmt tbody tr:not(.section):not(.total) td:first-child { background: #f2f2f2; }

/* ---------------- 名单表 / 收费情况：首列（末列）冻结 ----------------
   宽度一套规则，不分断点：窗口放得下时整张表本来就不会横滚（.tbl 撑到 660 才
   触发内层横滑），冻结列看不出什么；窗口一窄，.table-wrap 横向滚动，首列/末列
   钉住不动，中间列左右滑。名单表末列是「改」，一起钉右——否则窄屏点开「改」时
   浏览器会把内层滚到最右（和当初卡片化要治的是同一个毛病）。

   两个已知坑：
   1) border-collapse: collapse 下，sticky 单元格的边框不跟着单元格走——Chromium/
      Edge 把边框画在原位，横滑时冻结列与滚动列之间会露出 1px 亮缝。所以冻结列
      的分隔线一律用 box-shadow 画（与吸顶表头 .table-wrap.capped > .tbl thead th
      补下边框是同一招），不要用 border。
   2) 冻结格必须是不透明底色，否则横滑时会「透视」（看见底下的字从它下面滚过）。
      普通行的单元格本来无底色，这里逐状态点名补齐：普通、退班 .departed、
      悬停、合计 .total、表头。状态规则的 (0,2,1) 特异性低于本段的冻结选择器，
      不在这里再点一遍就会被本条盖成白底。

   td:first-child:not([colspan]) / td:last-child:not([colspan]) 排掉展开的编辑面板
   ——那是 <td colspan="8">，同时命中 first/last-child，不排掉的话整块面板会被粘住、
   跟着横滑跑偏。排掉后它照旧铺满整表宽、正常展开。 */
.tbl.roster > thead th:first-child,
.tbl.bills > thead th:first-child,
.tbl.roster > tbody > tr > td:first-child:not([colspan]),
.tbl.bills > tbody > tr > td:first-child:not([colspan]) {
  position: sticky;
  left: 0;
  z-index: 1;
  /* 冻结列是横滑时的锚点，读不出名字就白冻了。auto 布局把每列压到 min-content，
     而姓名列的 min-content 就是表头那两个字——手机实测只剩 44.5px，「李正宇」折成
     「李正/宇」、日期折成「2026-/02-24」。给个下限（≈三个汉字+内边距）撑住它，
     代价是中间可横滑的宽度少几十像素，值。表头格也有这一条，是靠 th 定的列宽兜底。 */
  min-width: 5em;
}
/* 年级列同理，不许折行。这条不是防自动布局压窄，是防**断字**：列被压到 44px 时
   「一年级」会被拆成「一年/级」两行（手机实测 5 个年级全折），一列断字托儿所
   看下来很难认。nowrap 让这列的 min-content 变成整词宽，自动布局要么给够、
   要么让中间去横滑——横滑本来就是这张表在窄屏的既有行为。
   表头那格不用管：.tbl th 本来就是 nowrap。 */
.tbl.roster .grade { white-space: nowrap; }
/* 名单表末列（操作「改」）钉右。bills 没有操作列，不钉。 */
.tbl.roster > thead th:last-child,
.tbl.roster > tbody > tr > td:last-child:not([colspan]) {
  position: sticky;
  right: 0;
  z-index: 1;
}
/* 表头里的冻结格要压过表头其余格（它们横滑时会从冻结格下面过）——z-index 抬到 3，
   高于 .table-wrap.capped > .tbl thead th 的 2。box-shadow 里既补冻结分隔线，
   也补 border-collapse 丢掉的下边框（该规则那一条会被本段覆盖）。 */
.tbl.roster > thead th:first-child,
.tbl.bills > thead th:first-child {
  z-index: 3;
  box-shadow: inset -1px 0 0 var(--border), inset 0 -1px 0 var(--border);
}
.tbl.roster > thead th:last-child {
  z-index: 3;
  box-shadow: inset 1px 0 0 var(--border), inset 0 -1px 0 var(--border);
}
/* 冻结分隔线：左列画在右边、右列画在左边 */
.tbl.roster > tbody > tr > td:first-child:not([colspan]),
.tbl.bills > tbody > tr > td:first-child:not([colspan]) {
  box-shadow: inset -1px 0 0 var(--border);
}
.tbl.roster > tbody > tr > td:last-child:not([colspan]) {
  box-shadow: inset 1px 0 0 var(--border);
}
/* 不透明底色：普通行 */
.tbl.roster > tbody > tr > td:first-child:not([colspan]),
.tbl.roster > tbody > tr > td:last-child:not([colspan]),
.tbl.bills > tbody > tr > td:first-child:not([colspan]) { background: var(--surface); }
/* 退班行压灰（与 tr.departed td 的 #fafbfc 同色） */
.tbl.roster > tbody > tr.departed > td:first-child:not([colspan]),
.tbl.roster > tbody > tr.departed > td:last-child:not([colspan]) { background: #fafbfc; }
/* 合计行压黄（与 .tbl .total td 的 #fff2cc 同色） */
.tbl.bills > tbody > tr.total > td:first-child:not([colspan]) { background: #fff2cc; }
/* 悬停高亮（与 .tbl tbody tr:hover td 的 #f8fafd 同色；同样排除合计行） */
.tbl.roster > tbody > tr:not(.section):not(.total):not(.row-edit-row):hover
  > td:first-child:not([colspan]),
.tbl.roster > tbody > tr:not(.section):not(.total):not(.row-edit-row):hover
  > td:last-child:not([colspan]),
.tbl.bills > tbody > tr:not(.total):hover > td:first-child:not([colspan]) {
  background: #f8fafd;
}

/* 结转勾选框：checkbox 本身只有 13×13，远低于 44px 触摸目标。
   用 label 把整格包成可点区（点空格子也能勾/取消勾），格子至少 44×44。 */
.tick { display: flex; align-items: center; justify-content: center;
        margin: 0; min-width: 44px; min-height: 44px; cursor: pointer; }
.tick input[type=checkbox] { width: 20px; height: 20px; margin: 0; cursor: pointer; }

/* 带文字的勾选框（如退班面板的「核销退班前的费用」）：同一套尺寸思路。
   两个坑都踩过：`.row-form input` 的 min-width:110px 会把 13px 的方框拉成
   110×13 的细长条；不给 label 撑高，手机上整块只有 22px，点不准。 */
.check { display: inline-flex; align-items: center; gap: 8px;
         margin: 0; min-height: 44px; cursor: pointer; }
.check input[type=checkbox] { width: 20px; height: 20px; min-width: auto; margin: 0;
                              cursor: pointer; }

/* 面板里那句「还有 X 元预收未结转」：提示得像个提示，不能混在正文里读过去 */
.warn { margin: 0; padding: 8px 10px; border-radius: 8px;
        background: #fff8e1; border: 1px solid #f0d68a; color: #7a5a00; }

/* 表格下面那段说明：收成一整块浅灰底，对应导出的 Excel 里横向合并的备注行
   （report_builder.py 的 _note，用的是同一个 #f2f2f2）。
   原来只是一行灰色小字，长句子在窄屏上散成好几行又没有边界，
   读的人分不清它是表格的注还是页面的正文。 */
.report-note { background: #f2f2f2; border-radius: 8px; padding: 10px 12px;
               margin: 10px 0; color: #55606d; font-size: 13px; line-height: 1.75; }

/* 凭证号 → 凭证牌 */
.voucher {
  display: inline-block;
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--primary);
  background: var(--primary-weak);
  border: 1px solid #d4e2f2;
  padding: 1px 7px;
  border-radius: 999px;
  white-space: nowrap;
  letter-spacing: .02em;
}

/* 金额列：等宽数字，一眼读懂 */
.amt {
  white-space: nowrap;
  text-align: right;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.inc { color: var(--income); }
.exp { color: var(--expense); }
.pos { color: var(--income); font-weight: 700; }
.neg { color: var(--expense); font-weight: 700; }

.ops { white-space: nowrap; }
/* 退款表单：金额是店长手填的输入框，窄屏占满宽度；「最多可退 X 元」单独折一行。 */
.refund-form { white-space: normal; }
.refund-form .money { width: 100%; }
.refund-form .muted { display: block; margin-top: 4px; font-size: 12.5px; }
/* 台账手机卡片里放退款控件的那一格：跟 .m-mid 一样用虚线分隔 */
.table-card-ops { margin-top: 10px; padding-top: 8px; border-top: 1px dashed var(--border); }
.table-card-ops .muted { font-size: 13px; }
.m-card.total-card { background: #fff8e6; }
.m-card.total-card .m-amt { font-weight: 700; }

/* 凭证图 */
.imgs { white-space: normal; min-width: 84px; }
.thumb {
  width: 52px;
  height: 52px;
  object-fit: cover;
  border-radius: 7px;
  margin: 2px;
  border: 1px solid var(--border);
  transition: transform .12s;
  vertical-align: middle;
}
.thumb:hover { transform: scale(1.06); }

/* 详情式上传/改密（details/summary） */
.up summary { list-style: none; cursor: pointer; }
.up summary::-webkit-details-marker { display: none; }
.up .up-form {
  margin-top: 8px;
  padding: 10px;
  border: 1px dashed #ccd5e0;
  border-radius: 8px;
  background: #fbfcfe;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-start;
}
.up .up-form input { width: 100%; }
/* 收起时干脆不布局：浏览器对闭合的 <details> 只跳过绘制、元素仍占布局，
   于是这个在 560px 宽表格「操作」列里的改密表单留了一块离屏的盒子，
   里面的 .pwd-toggle 实测落在 x=519（屏外）。 */
.up:not([open]) > .up-form { display: none; }
/* .up-form 是 flex column + align-items:flex-start，.pwd-wrap 不拉伸、只贴内容宽，
   字段和它的切换按钮于是脱离容器的右边。让它填满容器，同时允许收缩。 */
.up .up-form .pwd-wrap { width: 100%; min-width: 0; }

/* 移动端记账卡片（桌面隐藏，≤640px 显示） */
.mobile-cards { display: none; }

/* ---------------- 统计条（报表页） ---------------- */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  margin: 0 0 16px;
}
.stat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  box-shadow: var(--shadow);
  min-width: 0;
}
.stat .label { font-size: 12px; color: var(--muted); margin-bottom: 4px; }
.stat .num {
  font-size: clamp(20px, 2.2vw, 26px);
  font-weight: 700;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
  overflow-wrap: normal;
}
.stat.income .num { color: var(--income); }
.stat.expense .num { color: var(--expense); }
.stat.surplus .num { color: var(--accent); }
.stat.surplus.neg .num { color: var(--expense); }

/* 报表空态提示（当月无记录） */
.stat-empty {
  margin: -4px 0 16px;
  padding: 12px 16px;
  background: var(--surface);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  color: var(--muted);
  font-size: 13px;
  text-align: center;
}

/* ---------------- 空状态 ---------------- */
.empty {
  padding: 36px 16px;
  text-align: center;
  color: var(--muted);
}
.empty .empty-icon { font-size: 32px; opacity: .55; margin-bottom: 6px; }
.empty .empty-icon svg { display: block; margin: 0 auto; }

/* ---------------- 图表 ---------------- */
.chart { overflow-x: auto; }
.chart svg { width: 100%; height: auto; }

/* ---------------- 登录页 ---------------- */
.login-wrap {
  display: grid;
  place-items: center;
  min-height: calc(100vh - 130px);
  padding: 24px 14px;
}
.login-card { max-width: 360px; width: 100%; margin: 0; padding: 26px; }
.login-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin-bottom: 20px;
}
.login-brand .brand-mark { width: 46px; height: 46px; border-radius: 12px; }
.login-title { font-size: 18px; font-weight: 700; }

/* ---------------- 打印（报表/日记账） ---------------- */
@media print {
  .topbar, .btn, .up, form, .alert { display: none !important; }
  body { background: #fff; font-size: 12px; }
  main.wrap.main { padding: 0; }
  .card, .table-wrap { box-shadow: none; border: none; border-radius: 0; padding: 0; }
  .stat-grid { margin-bottom: 12px; }
  .tbl { min-width: 0; font-size: 11px; }
  .table-wrap { overflow: visible; }
}

/* ---------------- 移动端 ---------------- */
@media (max-width: 640px) {
  h1 { font-size: 18px; }
  .wrap { padding: 0 10px; }
  .topbar .wrap.top { gap: 8px; padding-top: 8px; padding-bottom: 8px; }
  .brand { font-size: 15px; }
  .brand-mark { width: 27px; height: 27px; border-radius: 7px; }
  .card { padding: 13px; }
  .tbl { font-size: 12px; min-width: 560px; }
  .thumb { width: 44px; height: 44px; }
  .form-grid { grid-template-columns: 1fr; }
  /* iOS 对 <16px 的输入框会触发页面缩放，统一提到 16px */
  form input[type=text], form input[type=password], form input[type=date],
  form input[type=month], form input[type=number], form select, form textarea { font-size: 16px; }
  /* 手机上 .btn 是主要触控目标，抬到触摸目标下限 44px（原来 40px 仍偏小） */
  .btn, .btn.sm { padding: 10px 14px; font-size: 14px; min-height: 44px; }
  /* 下拉 / 日期 / 月份控件同样抬到 44px，不然手指点不准 */
  form input[type=date], form input[type=month], form select { min-height: 44px; }

  /* H1：记账列表改卡片，不再横滚 */
  .entry-tbl-wrap { display: none; }
  /* 预收台账同理：整表横滚会把退款下拉和金额推到屏幕外 */
  .ledger-tbl-wrap { display: none; }
  /* 应收应付台账同理：整表横滚会把操作列（去核销/申请删除）推到屏幕外，手机上改看卡片 */
  .arap-tbl-wrap { display: none; }
  .mobile-cards { display: block; }
  .m-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 12px;
    margin-bottom: 10px;
    box-shadow: var(--shadow);
  }
  .m-top { display: flex; align-items: center; gap: 8px; }
  .m-date { font-size: 12px; color: var(--muted); white-space: nowrap; }
  .m-amt { margin-left: auto; font-size: 15px; }
  .m-mid {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px dashed var(--border);
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  .m-subject { font-weight: 600; font-size: 14px; }
  .m-desc { color: var(--muted); font-size: 13px; overflow-wrap: anywhere; }
  .m-bottom { margin-top: 10px; display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap; }
  .m-imgs { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; }
  .m-ops { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  .m-empty { padding: 28px 16px; text-align: center; color: var(--muted); }
  .m-empty .empty-icon { font-size: 30px; opacity: .55; margin-bottom: 6px; }
  .m-empty .empty-icon svg { display: block; margin: 0 auto; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* —— 记账页 Tab（应收应付）—— */
.tabs { display: flex; gap: 6px; margin: 14px 0; flex-wrap: wrap; }
.tab { padding: 7px 16px; border-radius: 8px; border: 1px solid var(--border);
       color: var(--muted); text-decoration: none; font-weight: 600; background: #fff; }
.tab:hover { border-color: var(--primary); color: var(--primary); }
/* tab 排是全站唯一的导航控件，之前没有 :focus-visible——键盘 Tab 到它只有浏览器
   默认的黑描边，和全站主题蓝不一致。补上和 .btn / .collapse > summary 同一道环。 */
.tab:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.tab.on { background: var(--primary); border-color: var(--primary); color: #fff; }
/* 375px 上 4 个 tab（含「预收与结转」）总宽 379px > 可用的 355px，会折成 3+1 两行——
   「预收与结转」孤零零掉到第二行，读起来像不属于这一排。这是两页之间唯一的导航，
   收一点横向内边距（16→10）就够排成一行（379→331），又不至于挤：相邻文字间仍有
   10+6+10=26px 的呼吸。只在 ≤400px 生效，420 起本来就是一行的照旧。 */
@media (max-width: 400px) {
  .tab { padding: 7px 10px; }
}
.pill-row { display: flex; gap: 6px; margin: 0 0 10px; flex-wrap: wrap; }
.pill { display: inline-block; padding: 3px 10px; border-radius: 20px; font-size: 12px;
        border: 1px solid var(--border); color: var(--muted); text-decoration: none;
        background: #fff; line-height: 1.5; }
.pill:hover { border-color: var(--primary); color: var(--primary); }
.pill.on { background: var(--primary-weak); border-color: var(--primary); color: var(--primary); }
.pill-ar { background: var(--income-weak); border-color: #3a9d6e; color: #1e7a4f; }
.pill-ap { background: var(--expense-weak); border-color: #d46b6b; color: #b03a3a; }
/* 「自动」：系统按名册算出来的往来，跟手工登记的那条区分开 */
.pill-auto { background: #e8f0fe; border-color: #c3d4f5; color: #305496; }
.done { color: var(--muted); }
.settle-row { border: 1px solid var(--border); border-radius: 10px; padding: 12px;
              margin-bottom: 10px; background: #fff; }
.settle-info { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.settle-info strong { min-width: 0; overflow-wrap: anywhere; }
.settle-amt { margin-left: auto; color: var(--muted); white-space: nowrap; }
.settle-amt b { color: var(--expense); }
.settle-form { display: grid; grid-template-columns: auto auto 1fr auto; gap: 10px;
               align-items: end; }
.settle-form label { margin-bottom: 0; }
.settle-form input { width: 100%; min-width: 0; }
/* 核销随单凭证截图上传：独占整行，置于第一行（日期/金额/摘要/按钮）之下 */
.settle-form label.voucher { grid-column: 1 / -1; }
@media (max-width: 720px) {
  .settle-form { grid-template-columns: 1fr 1fr; }
  .settle-amt { margin-left: 0; }
}

/* 只给屏幕阅读器的标题：页面上不占任何位置，但不让页面没有 h1 */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
           overflow: hidden; white-space: nowrap; border: 0;
           clip: rect(0 0 0 0); clip-path: inset(50%); }

/* skip link：视觉隐藏，用键盘 Tab 到它时才显形。
   纯键盘用户不必按 15 次 Tab 才能越过台账里每笔预收的 4 个停留点、到达「应结转」。 */
.skip-link { position: absolute; left: -9999px; top: 8px; z-index: 100;
             background: var(--surface); color: var(--primary);
             padding: 10px 14px; border: 2px solid var(--primary); border-radius: 8px;
             font-size: 14px; font-weight: 600; text-decoration: none;
             box-shadow: 0 3px 10px rgba(20, 30, 50, .18); }
.skip-link:focus { left: 8px; }
.skip-link:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

/* 卡片标题行：标题在左，跟随它的控件（如年份切换）在右 */
.card-head { display: flex; align-items: center; justify-content: space-between;
             gap: 6px 12px; flex-wrap: wrap; margin: 0 0 12px; }
.card-head h2 { margin: 0; }
/* 年份切换（只在有多个年份时由宏渲染出来） */
.year-form { margin: 0; }
.year-form .year-label { display: inline-flex; align-items: center; gap: 6px;
                         margin: 0; font-size: 13px; color: var(--muted); }
/* 覆盖 form select 的 width:100% / display:block / margin-top。
   注意选择器：.year-form 本身就是那个 <form>，所以不能写 "form .year-form select"
   （那是“form 里的 .year-form 里的 select”，永远匹配不到）。这里靠与 form select
   同权重 + 位置更靠后来取胜。 */
.year-form select { display: inline-block; width: auto; min-width: 0;
                    margin-top: 0; padding: 4px 8px; font-size: 13px; }
/* 手机上恢复 16px：iOS 对 <16px 的控件会整页放大，顺带把点击区抬到 ~38px */
@media (max-width: 720px) {
  .year-form select { padding: 8px 10px; font-size: 16px; }
}
/* 展开后靠右的工具行（目前只有年份下拉）。没有年份可切时模板整个不输出。 */
.list-tools { display: flex; align-items: center; justify-content: flex-end;
              margin: 0 0 10px; }

/* #records：翻页/切年份的落点。留出吸顶顶栏（约 55px）的高度，
   否则定位上来的卡片会被顶栏盖住一截。 */
#records { scroll-margin-top: 60px; }

/* —— 折叠模块：卡片默认只留一行标题，点开才铺内容 —— */
/* 记账页首屏只留「记一笔」；收支记录和申请列表都是"要看才看"的东西。 */
.collapse > summary {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  min-height: 44px;               /* 触摸目标下限（Apple HIG / Material） */
  /* 净位移为零：margin -9px 抵消 border 1px + padding 8px，标题文字的 x 坐标不变。
     但整条多了一层可见的面（浅底 + 描边 + 圆角），不再看着像普通标题。 */
  padding: 2px 8px; margin: 0 -9px;
  border: 1px solid var(--border); border-radius: 8px; background: #f8fafc;
  cursor: pointer; list-style: none;
  touch-action: manipulation;     /* 去掉移动端 300ms 点击延迟 */
}
.collapse > summary::-webkit-details-marker { display: none; }
.collapse > summary:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
/* 右侧的小箭头：收起朝下、展开朝上，纯 CSS 画，不引图标字体 */
.collapse > summary::after {
  content: ""; width: 9px; height: 9px; margin-left: auto;
  border-right: 2px solid var(--primary); border-bottom: 2px solid var(--primary);
  transform: rotate(45deg); transition: transform .18s ease-out;
}
.collapse[open] > summary::after { transform: rotate(-135deg); }
.collapse > summary:hover { background: #f1f6fc; border-color: var(--primary); color: var(--primary); }
.collapse > summary:hover::after { border-color: var(--primary); }
.collapse[open] > summary { margin-bottom: 12px; }
.collapse-t { font-size: 15px; font-weight: 600; color: #33404d; }  /* 与 h2 同规格 */
.collapse > summary:hover .collapse-t { color: var(--primary); }
.collapse-n { font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* —— 流水分页条 —— */
.pager { display: flex; align-items: center; justify-content: space-between;
         gap: 8px; margin: 14px 0 0; }
.pager .pg { display: inline-flex; align-items: center; justify-content: center;
             min-height: 36px; padding: 0 12px; border-radius: 8px;
             border: 1px solid var(--border); background: #fff;
             color: var(--primary); font-size: 14px; text-decoration: none;
             white-space: nowrap; }
.pager a.pg:hover { border-color: var(--primary); background: var(--primary-weak); }
/* 到头那一侧不用 <a>（点了也没地方去），但仍然占位，免得按钮左右跳 */
.pager .pg.off { color: var(--muted); background: #f6f8fb; border-color: transparent; }
.pager .pg-info { font-size: 13px; color: var(--muted);
                  font-variant-numeric: tabular-nums; text-align: center; }
@media (max-width: 720px) {
  /* 手机上翻页是主要操作之一，抬到触摸目标下限 44px */
  .pager a.pg, .pager .pg.off { min-height: 44px; padding: 0 14px; }
}
@media (max-width: 420px) {
  .pager .pg-info { font-size: 12px; }
  .pager a.pg, .pager .pg.off { padding: 0 10px; }
}

/* —— 隔天账改/删申请（申请模式 + 审核队列）—— */
/* 改账规则：默认折起成一行小注，需要时再展开（原先是页顶一整段正文） */
.rule-details { margin: 0 0 12px; }
.rule-details > summary { display: inline-flex; align-items: center; gap: 5px;
                          font-size: 12.5px; color: var(--muted); cursor: pointer;
                          padding: 2px 0; list-style: none; }
.rule-details > summary::-webkit-details-marker { display: none; }
.rule-details > summary::before { content: "ⓘ"; font-size: 13px; }
.rule-details > summary:hover { color: var(--primary); }
.rule-details[open] > summary { color: var(--text); }
.rule-details p { margin: 4px 0 0; max-width: 68ch; line-height: 1.7;
                  padding-left: 18px; }
.req-note { background: #fff7df; border: 1px solid #e6c96b; color: #7a5b00;
            border-radius: 8px; padding: 10px 12px; margin: 8px 0 14px; font-size: 14px;
            line-height: 1.7; }
.req-note b { color: #a37a00; }
.form-grid textarea { width: 100%; box-sizing: border-box; padding: 8px 10px;
                      border: 1px solid var(--border); border-radius: 8px;
                      font: inherit; background: #fff; resize: vertical; }
.req-list { display: flex; flex-direction: column; gap: 10px; }
/* 审核页两段（改/删账 · 期初调整）之间的小标题：比 h1 轻，比正文重，
   作用只是把两种定位方式不同的申请分开，不需要抢视觉焦点。 */
.req-section { margin: 22px 0 10px; font-size: 15px; color: var(--muted);
               font-weight: 600; }
.req-section:first-of-type { margin-top: 14px; }
.req-item { border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px;
            background: #fff; }
.req-item-main { min-width: 0; }
.req-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
            margin-bottom: 6px; }
.req-head .voucher { font-weight: 700; }
.req-status { display: inline-block; padding: 2px 9px; border-radius: 20px; font-size: 12px;
              border: 1px solid transparent; }
.req-status.pending { background: #fff3cd; border-color: #e6c96b; color: #8a6d00; }
.req-status.approved { background: #e6f6ec; border-color: #9dd6b0; color: #1e7a4f; }
.req-status.rejected { background: #fdecec; border-color: #e6a7a7; color: #b03a3a; }
.req-status.cancelled { background: #eee; border-color: #ccc; color: #777; }
.req-diff, .req-del { font-size: 13.5px; line-height: 1.6; overflow-wrap: anywhere; }
.req-diff .req-old, .req-del { color: var(--muted); }
.req-diff .req-new { color: var(--primary); }
.req-reason { margin-top: 4px; font-size: 13px; color: var(--muted);
              overflow-wrap: anywhere; }
.req-meta { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
            margin-top: 8px; font-size: 12px; }
.req-actions { margin-left: auto; display: inline-flex; gap: 8px; }

/* 台账行内「申请删除」：展开后是一行理由 + 提交（不是 confirm——删已核销的往来
   必须写理由，审核员要照它拍板）。summary 长得像按钮，展开后表单落在行下方。 */
.apply-del { display: inline-block; text-align: left; }
.apply-del > summary { display: inline-block; cursor: pointer; list-style: none; }
.apply-del > summary::-webkit-details-marker { display: none; }
.apply-del form { display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
                  margin-top: 6px; }
.apply-del input[type="text"] { min-width: 170px; }

/* 审核页的红字影响行：删掉这条往来会动哪个数字。口径由会计把关，别删也别改文案 */
.req-impact { margin-top: 6px; font-size: 13px; line-height: 1.6;
              color: #b03a3a; overflow-wrap: anywhere; }

/* =====================================================================
   首页（工作台）
   ---------------------------------------------------------------------
   两张大卡 = 日常只有的两件事（记一笔 / 看报表），其余功能收进常用工具。
   手机上两卡等分；触控目标 ≥44px；文字与图标一起给（不用纯图标按钮）。
   ===================================================================== */
.home-hello { margin: 6px 0 0; font-size: clamp(22px, 5.6vw, 27px);
              letter-spacing: -.02em; line-height: 1.25; }
.home-sub { margin: 5px 0 0; color: var(--muted); font-size: 13.5px; }

/* 提醒条：只在真有事情要办时渲染出来，办完自动消失（不做“常驻红点”） */
.home-alert {
  display: flex; align-items: center; gap: 10px; margin-top: 14px; min-height: 48px;
  padding: 12px 14px; border-radius: 12px; text-decoration: none;
  background: #fff7ea; border: 1px solid #f6e0bd; color: #8a5a12; font-size: 14px;
  transition: background .16s ease;
}
.home-alert:hover { background: #fff2dc; }
.home-alert:focus-visible { outline: 3px solid var(--primary); outline-offset: 2px; }
.home-alert .ha-ico { flex: none; display: grid; place-items: center; color: #b07d29; }
.home-alert .ha-text { min-width: 0; overflow-wrap: anywhere; }
.home-alert .ha-go { margin-left: auto; flex: none; font-weight: 600; font-size: 13px; color: #b07d29; }

.bigrow { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 14px; }
@media (max-width: 360px) { .bigrow { grid-template-columns: 1fr; } }
/* 记账员只有一个入口时铺满整行，不留半张空位；宽屏下仍收在一张卡的宽度内 */
.bigrow > .bigcard:only-child { grid-column: 1 / -1; }
@media (min-width: 760px) { .bigrow > .bigcard:only-child { grid-column: auto; max-width: 500px; } }

.bigcard {
  display: flex; flex-direction: column; text-decoration: none; color: #fff;
  border-radius: 16px; padding: 18px 16px 16px; min-height: 200px;
  box-shadow: 0 6px 18px rgba(30, 80, 150, .18);
  transition: transform .16s ease, box-shadow .16s ease;
}
@media (min-width: 760px) { .bigcard { min-height: 250px; } }
.bigcard:hover { transform: translateY(-2px); box-shadow: 0 12px 30px rgba(30, 80, 150, .26); }
.bigcard:active { transform: scale(.985); }
.bigcard:focus-visible { outline: 3px solid #1c2733; outline-offset: 3px; }
/* 渐变刻意压深：参考产品那种明亮青绿配白字只有 2.1:1，看不清。
   这里保证白字在整条渐变上都过 4.5:1（小字）/ 3:1（大字）。 */
.bigcard.tone-blue { background: linear-gradient(150deg, #3f83e4 0%, #22539a 100%); }
.bigcard.tone-green { background: linear-gradient(150deg, #35996a 0%, #1c6f42 100%); }

.bc-title { font-size: 23px; font-weight: 700; letter-spacing: -.01em; }
.bc-mid { margin-top: 16px; display: flex; flex-direction: column; }
.bc-big { font-family: var(--mono); font-variant-numeric: tabular-nums;
          font-size: 26px; font-weight: 700; line-height: 1.15; }
.bc-biglabel { margin-top: 3px; font-size: 12.5px; opacity: .92; }
/* 信息条用“压暗”而不是“提亮”：半透明白会让白字更糊，半透明黑才提对比 */
.bc-lines { margin-top: auto; display: flex; flex-direction: column; gap: 3px;
            padding: 8px 11px; border-radius: 10px; background: rgba(0, 0, 0, .20);
            font-size: 12.5px; }
.bc-line { display: flex; gap: 8px; }
.bc-line .k { opacity: .92; }
.bc-line .v { margin-left: auto; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.bc-art { margin: auto 0; display: flex; justify-content: center; opacity: .95; }
.bc-foot { margin-top: auto; padding: 7px 12px; border-radius: 999px; text-align: center;
           background: rgba(0, 0, 0, .20); font-size: 13px; }

.tools-sec { margin-top: 24px; }
.tools-sec h2 { margin: 0 0 10px; font-size: 16px; }
.toolgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (min-width: 760px) { .toolgrid { grid-template-columns: repeat(3, 1fr); } }
.tool-form { margin: 0; }
.tool {
  width: 100%; text-align: left; font: inherit; cursor: pointer;
  display: flex; align-items: center; gap: 11px; min-height: 72px;
  padding: 14px 13px; border: 0; border-radius: 14px;
  background: var(--surface); color: var(--text); text-decoration: none;
  box-shadow: var(--shadow); transition: transform .16s ease, box-shadow .16s ease;
}
.tool:hover { transform: translateY(-1px); box-shadow: 0 8px 20px rgba(20, 40, 70, .10); }
.tool:active { transform: scale(.98); }
.tool:focus-visible { outline: 3px solid var(--primary); outline-offset: 2px; }
.tool .tico { width: 40px; height: 40px; border-radius: 12px; flex: none;
              display: grid; place-items: center; }
/* 文字块必须 flex:1 + min-width:0，否则标题会被挤成多行 */
.tool .tx { flex: 1; min-width: 0; }
.tool .tt { display: block; font-size: 15px; font-weight: 600; line-height: 1.25; }
.tool .ts { display: block; margin-top: 2px; font-size: 12.5px; line-height: 1.35; color: var(--muted); }

.tico-users   { background: #eaf2fb; color: #2f6fd0; }
.tico-key     { background: #fdf3e6; color: #d8842a; }
.tico-logout  { background: #f1f3f6; color: #69737f; }
.tico-compare { background: #e9f2ff; color: #3b7fe0; }

/* 顶栏「← 首页」：子页面上回工作台的显式出口 */
.back-home { display: inline-flex; align-items: center; gap: 2px; flex: none;
             min-height: 34px; padding: 4px 9px 4px 5px; border-radius: 8px;
             color: var(--muted); text-decoration: none; font-size: 13.5px; font-weight: 600; }
.back-home:hover { color: var(--primary); background: var(--primary-weak); }
.back-home:focus-visible { outline: 3px solid var(--primary); outline-offset: 2px; }

/* =====================================================================
   期初已确认：只在顶部留一行低调摘要，需要时才展开设置
   ===================================================================== */
.opening-done { margin: 2px 0 12px; font-size: 12.5px; }
.opening-done summary { cursor: pointer; list-style: none; color: var(--muted);
                        user-select: none; display: inline-block; }
.opening-done summary::-webkit-details-marker { display: none; }
.opening-done summary::before { content: '▸ '; }
.opening-done[open] summary::before { content: '▾ '; }
.opening-done summary:hover { color: var(--primary); }
.opening-form { margin-top: 8px; }

/* ===== 学员与收费管理 / 收费项目 ===== */

/* 「收费项目 / 学员 / 月份」三格只对托管费收入有意义，默认藏起来、由 app.js 按科目
   上的 data-tuition 标记显隐。
   选择器故意写得比 .form-grid label 更具体（三个 class），免得哪天给 label 加了
   display，这几格就被顶出来，在一笔买文具的支出上一直挂着「学员」和「月份」。 */
.form-grid > .js-tuition-only { display: none; }
.form-grid > .js-tuition-only.on { display: block; }

/* 托管费那笔「算哪个月」的小标：紧跟科目，只在真记了月份的流水上出现。
   单独占一列不值得——这张表已经 7 列，手机上是横滚看的。 */
.bill-m { display: inline-block; margin-left: 6px; padding: 1px 7px;
          border-radius: 20px; font-size: 12px; line-height: 1.5;
          white-space: nowrap; background: var(--primary-weak); color: var(--primary); }

/* 名单上的收费项标签：一个人常常挂两项（午托 + 早读），堆成标签比逗号串好读 */
.fee-tag { display: inline-block; margin: 0 4px 2px 0; padding: 2px 8px;
           border-radius: 20px; font-size: 12px; line-height: 1.5;
           background: var(--primary-weak); color: var(--primary);
           border: 1px solid #cfe0f4; white-space: nowrap; }

/* 附加项（早读）压淡一档：它和套餐不是一回事——套餐一个孩子只有一档，
   附加项能叠好几个。同色同形会让人以为它们是一类可选项。 */
.fee-tag.addon { background: #f4f6f9; color: var(--muted); border-color: var(--border); }

/* 行内编辑里的小字说明（当前套餐、类型不可改），贴在上一个表单下面 */
.row-edit-body .row-hint { margin: -2px 0 2px; font-size: 12px; }

/* 已退班的行压灰。人**不**从表里拿掉——历史要有据可查，只是得一眼分得开 */
tr.departed td { color: var(--muted); background: #fafbfc; }

/* 名册页顶上的「工具行」：添加学员 + 收费项目。两块收起时各只占一行，
   并排放正好；一旦摊开，那一块自己占满整行——收费项目那张 5 列的表
   在半宽里只能横滚。:has() 就是拿来干这个的（Edge/Chrome/Safari 都已支持）。
   窄屏放不下两栏，直接摞起来。 */
.minitools { display: grid; grid-template-columns: 1fr 1fr; gap: 0 12px;
             align-items: start; min-width: 0; }
/* grid item 默认 min-width:auto（不能小于 min-content）：收费项目那张 5 列的表
   会把整条轨道顶宽，420px 下整页横向溢出 180px。给网格项解绑；
   表本身继续在 .table-wrap 里横滚是预期行为，不算问题。 */
.minitools > .card { min-width: 0; }
/* 行间距交给 .card 自己的 margin-bottom，这里只把列间距铺开。
   有一块摊开时，两块都改占整行——否则会剩下半行空白，旁边孤零零杵着一块收起的。
   宽屏拼半行是给「都收着」那个状态准备的。 */
.minitools:has(details[open]) > .card { grid-column: 1 / -1; }
@media (max-width: 800px) { .minitools { grid-template-columns: 1fr; } }

/* 名单表和收费情况**不再卡片化**（原来的两套断点重排已删）。
   这两张表现在都用「冻结列 + 中间横滑」：窗口窄了照样读得下去，而且列不再被拆散——
   卡片化当初是为了治上次那个「点最右列的『改』时内层自动滚到最右」的毛病，
   现在末列钉右，「改」永远在眼皮底下，卡片那套重排就没必要了。看上面
   「首列（末列）冻结」那段。收费项目表（.tbl.fee-items）不在此列，它仍卡片化，
   规则见下面那个 ≤640px 块——它列少、又整张收在折叠块里，卡片更省地方。 */

/* 收费项目表（在折叠块里）手机上仍是卡片：单份 DOM + 断点重排。它的「改」也在
   最右列，不重排就会重现「点开自动滚到最右」的老毛病。规则形状见 style.css 里
   名册表曾经用过的那套，只是列数不同。 */
@media (max-width: 640px) {
  /* —— 收费项目表 —— */
  .table-wrap:has(> .tbl.fee-items) { border: none; border-radius: 0; overflow: visible; }
  .tbl.fee-items { min-width: 0; }
  .tbl.fee-items > thead { display: none; }          /* 表头行隐藏 */
  .tbl.fee-items, .tbl.fee-items > tbody,
  .tbl.fee-items > tbody > tr,
  .tbl.fee-items > tbody > tr > td { display: block; }

  /* 一个收费项目 = 一张卡片 */
  .tbl.fee-items > tbody > tr:has(> td > .row-edit) {
    border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--surface); padding: 12px; margin-bottom: 10px;
    box-shadow: var(--shadow);
  }
  .tbl.fee-items > tbody > tr.departed:has(> td > .row-edit) { background: #f7f8fa; }
  /* 卡片里的每一格：字段名在左（固定窄列），值在右 */
  .tbl.fee-items > tbody > tr:has(> td > .row-edit) > td {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
    padding: 3px 0; border-bottom: none; text-align: left; background: transparent;
  }
  .tbl.fee-items > tbody > tr:has(> td > .row-edit) > td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 4.2em; color: var(--muted); font-size: 12.5px;
  }
  /* 名称是卡头：大一号、不要字段名 */
  .tbl.fee-items > tbody > tr:has(> td > .row-edit) > td:first-child {
    font-size: 16px; font-weight: 700; color: var(--text); padding-bottom: 6px;
  }
  .tbl.fee-items > tbody > tr:has(> td > .row-edit) > td:first-child::before { display: none; }
  /* 操作那格（「改」）贴右下，跟正文拉一条虚线分层 */
  .tbl.fee-items > tbody > tr:has(> td > .row-edit) > td.row-ops {
    justify-content: flex-end; margin-top: 6px; padding-top: 8px;
    border-top: 1px dashed var(--border);
  }
  .tbl.fee-items > tbody > tr:has(> td > .row-edit) > td.row-ops::before { display: none; }
  /* 展开的编辑面板：默认仍收起，只有 details[open] 才铺开（同名册表） */
  .tbl.fee-items > tbody > tr.row-edit-row { display: none; }
  .tbl.fee-items > tbody > tr:has(> td > .row-edit[open]) + tr.row-edit-row { display: block; }
  .tbl.fee-items > tbody > tr.row-edit-row > td { padding: 0 0 10px; }
}

.pill-on { background: var(--income-weak); border-color: #3a9d6e; color: #1e7a4f; }
.pill-off { background: #f2f4f7; border-color: var(--border); color: var(--muted); }

/* 行内「改」：把编辑表单压在该行下面展开，不用跳页。
   summary 当按钮用，去掉浏览器默认的三角。 */
.row-edit, .row-leave { display: inline-block; }
.row-edit > summary, .row-leave > summary {
  display: inline-block; cursor: pointer; list-style: none; }
.row-edit > summary::-webkit-details-marker,
.row-leave > summary::-webkit-details-marker { display: none; }
/* 表单**不放在那一格里**。原来 .row-edit-body 是嵌在最后那个 <td> 里的，只能
   跟着那格的宽度走——实测 35px（scrollWidth 177，内容全糊出表格右边），而且单元格
   的 min-content 反过来把姓名/入学几列挤到折行，整张表都变形。
   所以表单挪到紧跟其后的一行 <tr class="row-edit-row">，用 colspan 铺满整张表；
   显不显示看上一行的 details 有没有展开（:has()，跟 .minitools 用的是同一招）。
   details 里这时是空的，纯粹当开关——它自带的开合、键盘可达、Esc 关掉全是白送的。 */
.row-edit-row { display: none; }
.tbl tbody tr:has(td > .row-edit[open]) + .row-edit-row,
.tbl tbody tr:has(td > .row-leave[open]) + .row-edit-row { display: table-row; }
/* 退班面板跟「改」共用同一行：只显示当前点开的那个，免得两个表单叠在一起。 */
.leave-body { display: none; flex-direction: column; gap: 8px; contain: inline-size;
              padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px;
              background: var(--surface); }
.tbl tbody tr:has(td > .row-leave[open]) + .row-edit-row .leave-body { display: flex; }
.tbl tbody tr:has(td > .row-leave[open]) + .row-edit-row .row-edit-body { display: none; }
/* 展开时把上面那行的下边线撤掉：面板自己就是一块带边框的盒子，再挨一条线就成双线了。
   分隔线改由编辑行来收尾。 */
.tbl tbody tr:has(td > .row-edit[open]) > td,
.tbl tbody tr:has(td > .row-leave[open]) > td { border-bottom: none; }
.leave-settle { display: flex; flex-wrap: wrap; gap: 8px; }
.leave-settle[hidden] { display: none; }
.row-edit-row > td { padding: 0 0 12px; }
/* contain:inline-size —— 关键是别让这块内容参与表格的固有尺寸计算。
   光把它挪进 colspan 格还不够：auto 布局会拿这一格的 max-content 去分摊列宽，
   于是「展开」这个动作本身会把上面那张表的列宽重新洗一遍（手机上实测姓名列
   59→55、状态列 83→71）。加了 inline-size 包含之后，这一格对列宽贡献恒为 0，
   列宽只由其余各行决定 —— 展开前后逐列一模一样。
   面板宽度不受影响：colspan 格本来就是整张表宽。里头是 flex-wrap 的，装不下会换行
   而不是撑宽。 */
.row-edit-body { display: flex; flex-direction: column; gap: 8px; contain: inline-size;
                 padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px;
                 background: var(--surface); }
.row-edit-body .row-form { margin: 0; flex-wrap: wrap; }
/* 合并后的行内「改」：上半是档案（姓名/入学/退班），下半是收费标准（套餐/附加项/生效日）。
   中间拉一条整行分隔线分层——flex 里 flex-basis:100% 就强制换行，线自然铺满一行。 */
.row-edit-body .fee-sep { flex-basis: 100%; height: 1px; margin: 2px 0 4px;
                          background: var(--border); }

/* 手机上这块竖排。面板跨了全部 8 列 = 整表宽（≈850px），而可见窗口只有 325px：
   按行铺开的话 8 个字段加「保存」要横向拖六次才填得完，而且「保存」默认落在屏幕外
   （交互闸实测：left 433 > 右沿 351）——店长每天要做的动作，不能这么赔。
   竖排之后一行一个字段，全部从左边起头，一次都不用横拖；表宽也跟着不再有关系。
   只圈 .tbl.roster：收费情况没有编辑面板；收费项目表在手机上本来就卡片化、面板只有
   卡片宽，按行铺开正装得下——别去动它。 */
@media (max-width: 640px) {
  /* 必须是 flex-start，不能是 stretch：label 一旦被拉到整表宽（≈842px），
     里头 `form input[type=text]{width:100%}` 就跟着变成 842px 宽，连「保存」
     也会被拉成一条整宽的蓝条、文字居中到屏幕外——实测就是一整条无字蓝条。
     flex-start 让每个 label 收缩到自己的固有宽度（≈180px），输入框和按钮跟着收回来。 */
  .tbl.roster .row-edit-body .row-form { flex-direction: column; align-items: flex-start; }
  /* 退班面板同理竖排：它跟「改」同样是整表宽的一行，按行铺开一样要横拖。 */
  .tbl.roster .leave-body .row-form { flex-direction: column; align-items: flex-start; }
  .tbl.roster .leave-body .leave-settle { flex-direction: column; align-items: flex-start; }
  /* 下拉的固有宽度取决于最长的那条选项，兜个上限，别让它反过来把面板撑出去。
     60vw 永远小于可视宽度，不会引起整页横向溢出。 */
  .tbl.roster .row-edit-body .row-form label { max-width: 60vw; }
  .tbl.roster .leave-body .row-form label { max-width: 60vw; }
  /* 面板里的说明段落（「应缴 … 已收现金 … 未收 …」和预收未结转的提示）是
     flex 子项，默认被拉成整表宽（≈645px）：375px 屏上「未收 200.00」和
     「预收与结转」链接整个落在屏幕外，横滑才看得见。给它们一个视口上限，
     超了就换行——这是退班前唯一能核对数字的一行，不能靠横拖。 */
  .tbl.roster .leave-body > p { max-width: 75vw; }
  /* 分隔线在行布局里靠 flex-basis:100% 强制换行；竖排下 flex-basis 量的是**高度**，
     会把这条 1px 的线撑成一大块。改回靠自身高度，横向铺满。 */
  .tbl.roster .row-edit-body .fee-sep { flex: none; align-self: stretch; }
}

/* 欠费：整列数字里唯一该被一眼抓到的那个 */
.tbl .amt.owed { color: var(--expense); font-weight: 600; }

/* =====================================================================
   手机上「固定高度 + 内部滚动」的落点：≤640px 时名单表 / 收费情况表把 capped
   的封顶从桌面的 45vh 抬到 50vh。手机 viewport 更矮、单行更少，多露半行能少滚两下；
   抬到 50vh 也仍留一半屏给下面的「收费情况」，不至于把整张卡顶出首屏。
   （纵向滚动的实现仍在上面 .table-wrap.capped 那条基准规则里，这里只是换个高度。） */
@media (max-width: 640px) {
  .table-wrap.capped:has(> .tbl.roster),
  .table-wrap.capped:has(> .tbl.bills) { max-height: 50vh; overflow-y: auto; }
}

/* =====================================================================
   触摸设备（平板也在内）：44px 触摸下限
   ---------------------------------------------------------------------
   44px 下限原来只在 @media (max-width: 640px) 里，641–1024px 上按钮只有
   25–39px——手指出没的地方。pointer: coarse 与宽度无关，手机和平板都命中；
   鼠标（fine）完全不受影响。
   只兜 min-height（行内元素先靠 display 撑住高度），不改字号/内边距——
   平板上要保持桌面观感。这一块里出现 font-size / padding 会被
   tests/test_ui_affordance 抓红。

   ⚠️ 这一段必须放在文件**最后**：媒体查询不加特异性，靠源序取胜。放在前面时，
   后面定义的基准规则（如 .back-home 的 min-height:34px）会把它盖回去——实测
   第一版就没放最后，.back-home 在触摸下仍是 34px。放到末尾，同级选择器一律以这里为准。
   ===================================================================== */
@media (pointer: coarse) {
  .btn, .btn.sm { min-height: 44px; }
  form input[type=date], form input[type=month], form select { min-height: 44px; }
  /* 文本框族（摘要 / 金额 / 登录名…）原来 40px，差一点，一起抬上来 */
  form input[type=text], form input[type=password], form input[type=number] { min-height: 44px; }
  /* 原生选图控件只有 23px */
  form input[type=file] { min-height: 44px; }
  /* 密码「显示/隐藏」切换：上一轮刚让它看得见，触摸下却只有 30px——自相矛盾 */
  button.pwd-toggle { min-height: 44px; }
  /* 顶栏「← 首页」，原来 34px（基准 rule 在文件后段，所以这段整体后置） */
  .back-home { min-height: 44px; }
  /* 记账 / 预收页共用的 tab 排：<a> 默认行内，min-height 不生效，先变 inline-flex */
  .tab { display: inline-flex; align-items: center; min-height: 44px; }
  /* 「改账规则」行内展开器，原来 24px */
  .rule-details > summary { min-height: 44px; }
  .pager a.pg, .pager .pg.off { min-height: 44px; }
  /* 名单表表头「姓名 / 年级」的排序链接：铺满整格的那半（display:block + 内边距）
     已经在基准规则里做了，这里只兜高度——行内时触摸点只有那一行字（约 18px）。
     用 flex 不用 block：44px 高的盒子里文字得居中，压顶会显得比左右两格都高。
     内边距留在基准规则那边（本块不许碰内边距和字号，见块首注释）。 */
  .tbl.roster thead th .sort-link { display: flex; align-items: center; min-height: 44px; }
  /* 台账顶部的筛选 pill（全部 / 未清 / 已结清 / 已作废）：它们是真能点的按钮，
     基础规则只有 26px 高。inline-flex 才吃 min-height——inline-block 的
     min-height 不会把行盒撑到 44（行内盒高度由 line-height 决定）。 */
  .pill-row .pill { display: inline-flex; align-items: center; min-height: 44px; }
}
