/* ==========================================================
   东智云科教育办公系统 样式表
   浅色业务风：白卡片 + 柔和阴影 + 蓝色主色
   ========================================================== */

:root {
  --primary:    #1677ff;
  --primary-h:  #4096ff;
  --primary-a:  #0958d9;
  --primary-bg: #e6f4ff;
  --success:    #52c41a;
  --success-bg: #f6ffed;
  --warning:    #fa8c16;
  --warning-bg: #fff7e6;
  --danger:     #ff4d4f;
  --danger-bg:  #fff2f0;
  --text:       #1f2329;
  --text-2:     #646a73;
  --text-3:     #8f959e;
  --border:     #e5e6eb;
  --border-2:   #f0f1f3;
  --bg:         #f5f7fa;
  --radius:     8px;
  --radius-sm:  6px;
  --shadow:     0 1px 3px rgba(0,0,0,.04), 0 4px 12px rgba(0,0,0,.03);
  --shadow-lg:  0 4px 16px rgba(0,0,0,.08), 0 12px 32px rgba(0,0,0,.06);
}

* { box-sizing: border-box; }

/* hidden 属性必须真的隐藏。浏览器自带的 [hidden]{display:none} 来自
   UA 样式表，优先级最低——本文件里任何一条给元素设了 display 的规则
   （.exp-row{display:grid} 这类）都会盖掉它，于是 JS 里
   el.hidden = true 就成了没有视觉效果的空操作。

   踩过一次：报销表单「默认只显示 3 行」的脚本一直在正常跑，
   但十行全都露着，因为 .exp-row 是 grid。加 !important 是这里的
   正解而不是偷懒——hidden 的语义就是「无条件不显示」，
   没有哪种情况需要让别的规则赢。 */
[hidden] { display: none !important; }

body {
  margin: 0;
  font-family: "PingFang SC", "Microsoft YaHei", -apple-system, "Segoe UI", sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--primary); text-decoration: none; transition: color .15s; }
a:hover { color: var(--primary-h); }

h1 { font-size: 20px; font-weight: 600; margin: 0; letter-spacing: -.2px; }
h2 { font-size: 15px; font-weight: 600; margin: 0 0 14px; }
h3 { font-size: 13px; font-weight: 600; margin: 20px 0 8px; color: var(--text-2); }
.muted { color: var(--text-3); font-size: 13px; font-weight: normal; }

/* ---------- 顶栏 ---------- */
.topbar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; justify-content: space-between;
  height: 56px; padding: 0 24px;
  background: #fff; border-bottom: 1px solid var(--border);
}
.brand {
  display: flex; align-items: center; gap: 10px;
  font-size: 16px; font-weight: 600; color: var(--text);
}
.brand:hover { color: var(--text); }
.brand-logo {
  display: grid; place-items: center;
  width: 30px; height: 30px; border-radius: var(--radius-sm);
  background: linear-gradient(135deg, var(--primary), #69b1ff);
  color: #fff;
}
/* 上传的 logo 撑满这 30px，不垫渐变底色——校徽多半自带背景，
   垫一层蓝底会变成「蓝框里一个图标」。object-fit: contain 保证
   非正方形的图不会被拉变形，留白比变形好看 */
.brand-logo.has-img { background: none; }
.brand-logo.has-img img {
  width: 30px; height: 30px; object-fit: contain; display: block;
  border-radius: var(--radius-sm);
}
.topbar-right { display: flex; align-items: center; gap: 6px; }
.topbar-user {
  display: flex; align-items: center; gap: 9px;
  padding: 5px 12px 5px 6px; margin-right: 6px;
  border-radius: 18px; background: var(--bg);
}
.avatar {
  display: grid; place-items: center;
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--primary); color: #fff;
  font-size: 13px; font-weight: 600;
}
.topbar-user .name { font-size: 13px; font-weight: 500; }
.role-tag {
  display: inline-block; padding: 1px 8px; margin-left: 2px;
  border-radius: 10px; background: var(--primary-bg); color: var(--primary-a);
  font-style: normal; font-size: 12px; font-weight: 500;
}
.icon-link {
  display: flex; align-items: center; gap: 5px;
  padding: 6px 11px; border-radius: var(--radius-sm);
  color: var(--text-2); font-size: 13px; transition: all .15s;
}
.icon-link:hover { background: var(--bg); color: var(--text); }
/* 退出按钮。改成 POST 表单后这里是 <button>，要抹掉浏览器默认样式
   才能跟旁边的 <a class="icon-link"> 长得一样 */
.logout-form { display: inline-flex; margin: 0; }
.logout-form button.icon-link {
  border: none; background: none; cursor: pointer;
  font-family: inherit; font-size: 13px;
}

/* ---------- 布局与侧栏 ---------- */
.layout { display: flex; min-height: calc(100vh - 56px); }
.sidebar {
  width: 200px; flex-shrink: 0; padding: 14px 12px;
  background: #fff; border-right: 1px solid var(--border);
}
/* 分组是 <details>，组标题是 <summary>。收起时浏览器自己把
   summary 之外的内容藏掉，不用写 display:none */
.nav-group { margin-bottom: 6px; }
.nav-group > .label {
  display: flex; align-items: center; gap: 6px;
  padding: 7px 12px; margin-bottom: 2px;
  border-radius: var(--radius-sm);
  font-size: 11px; font-weight: 600; letter-spacing: .6px;
  color: var(--text-3); text-transform: uppercase;
  cursor: pointer; user-select: none;
}
/* 去掉浏览器默认的三角。Safari 认的是 ::-webkit-details-marker */
.nav-group > .label { list-style: none; }
.nav-group > .label::-webkit-details-marker { display: none; }
.nav-group > .label:hover { background: var(--bg); color: var(--text-2); }
/* 箭头推到最右，收起时转成朝右 */
.nav-group > .label > svg {
  margin-left: auto; flex-shrink: 0;
  opacity: .6; transition: transform .18s;
}
.nav-group[open] > .label > svg { transform: rotate(0deg); }
.nav-group:not([open]) > .label > svg { transform: rotate(-90deg); }
/* 组标题上的角标只在收起时露出来：展开了就能看见链接上那个，
   两个一起显示等于同一件事说两遍 */
.group-badge { margin-left: 0; }
.nav-group[open] > .label > .group-badge { display: none; }
/* 展开的那一组标题稍微亮一点，扫一眼能看出自己在哪个模块下 */
.nav-group[open] > .label { color: var(--text-2); }
.sidebar a {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; margin-bottom: 2px;
  border-radius: var(--radius-sm);
  color: var(--text-2); font-size: 14px; transition: all .15s;
}
.sidebar a:hover { background: var(--bg); color: var(--text); }
.sidebar a.active {
  background: var(--primary-bg); color: var(--primary-a); font-weight: 500;
}
.sidebar a svg { flex-shrink: 0; opacity: .85; }
.content { flex: 1; padding: 22px 26px; min-width: 0; }

/* 页脚版权。管理员没设过就不渲染这个元素，所以不用管空状态。
   上边距用 margin 而不是把 .content 改成 flex 布局：那会影响
   每一页的内容排布，而这只是底部多一行字 */
.app-foot {
  margin-top: 28px; padding-top: 14px;
  border-top: 1px solid var(--border);
  text-align: center; font-size: 12px; color: var(--text-3);
}

/* ---------- 卡片与页头 ---------- */
.card {
  background: #fff; border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 20px; margin-bottom: 16px;
}
.page-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 18px; flex-wrap: wrap; gap: 12px;
}
.page-head .sub { margin-top: 3px; font-size: 13px; color: var(--text-3); }
.btn-group { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.btn-group form { margin: 0; }

/* ---------- 提示条 ---------- */
.flash {
  display: flex; align-items: center; gap: 9px;
  padding: 10px 14px; margin-bottom: 14px;
  border-radius: var(--radius-sm); font-size: 13px;
  animation: slideIn .25s ease;
}
@keyframes slideIn {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
.flash-success { background: var(--success-bg); color: #389e0d;
                 border: 1px solid #b7eb8f; }
.flash-error   { background: var(--danger-bg);  color: #cf1322;
                 border: 1px solid #ffccc7; }
.flash-info    { background: var(--primary-bg); color: var(--primary-a);
                 border: 1px solid #91caff; }

/* ---------- 按钮 ---------- */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 15px; border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: #fff; color: var(--text);
  font-size: 14px; font-family: inherit; line-height: 1.4;
  cursor: pointer; white-space: nowrap; transition: all .15s;
}
.btn:hover {
  color: var(--primary); border-color: var(--primary-h);
  text-decoration: none;
}
.btn:active { transform: translateY(1px); }
.btn-primary {
  background: var(--primary); border-color: var(--primary); color: #fff;
  font-weight: 500; box-shadow: 0 2px 6px rgba(22,119,255,.25);
}
.btn-primary:hover {
  background: var(--primary-h); border-color: var(--primary-h); color: #fff;
}
.btn-danger { color: var(--danger); border-color: #ffccc7; }
.btn-danger:hover {
  background: var(--danger-bg); color: var(--danger); border-color: var(--danger);
}
.btn-sm { padding: 3px 10px; font-size: 13px; gap: 4px; }
.btn-text {
  padding: 3px 8px; border-color: transparent; background: transparent;
  color: var(--primary); font-size: 13px;
}
.btn-text:hover { background: var(--primary-bg); border-color: transparent; }
.btn-text.danger { color: var(--danger); }
.btn-text.danger:hover { background: var(--danger-bg); }
/* 不可用的操作：保留占位并用 title 说明原因，比直接藏起来好排查 */
.btn-text.is-off {
  color: var(--text-3); cursor: not-allowed; background: transparent;
}
.btn-text.is-off:hover { background: transparent; }

/* ---------- 表格 ---------- */
.table-card {
  background: #fff; border-radius: var(--radius);
  box-shadow: var(--shadow); overflow: hidden;
}
.table-scroll { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th, td {
  padding: 12px 16px; text-align: left; font-size: 14px;
  border-bottom: 1px solid var(--border-2);
}
th {
  background: #fafbfc; color: var(--text-2);
  font-weight: 600; font-size: 13px; white-space: nowrap;
}
tbody tr { transition: background .12s; }
tbody tr:hover { background: #fafbfc; }
tbody tr:last-child td { border-bottom: none; }
/* 序号列：固定窄宽，不跟着内容抖 */
.col-idx { width: 56px; text-align: center; }
/* 金额列。表头和数据格用同一个类，两边一起右对齐——
   只给数字右对齐会让它们跑到表头文字的右边去，看不出哪个数归哪一列 */
th.num, td.num { text-align: right; }
/* 不计入合计的行（假期跳过、停课）：留在表里但压暗，一眼能区分 */
tbody tr.is-off { background: var(--bg); }
tbody tr.is-off td { color: var(--text-3); }
tbody tr.is-off:hover { background: #f4f6f8; }
td.actions { white-space: nowrap; }
.cell-main { font-weight: 500; }
.cell-sub { display: block; font-size: 12px; color: var(--text-3); margin-top: 1px; }

.empty { padding: 60px 20px; text-align: center; color: var(--text-3); }
.empty svg { opacity: .3; margin-bottom: 10px; }
.empty p { margin: 0; font-size: 14px; }

/* ---------- 徽标 ---------- */
.badge {
  display: inline-block; padding: 2px 9px; border-radius: 11px;
  font-size: 12px; font-weight: 500; white-space: nowrap;
  background: var(--border-2); color: var(--text-2);
}
.badge-green  { background: var(--success-bg); color: #389e0d; }
.badge-red    { background: var(--danger-bg);  color: #cf1322; }
.badge-blue   { background: var(--primary-bg); color: var(--primary-a); }
.badge-orange { background: var(--warning-bg); color: #d46b08; }
.badge-gray   { background: var(--border-2);   color: var(--text-3); }
.dot { display: inline-block; width: 6px; height: 6px;
       border-radius: 50%; margin-right: 5px; vertical-align: 1px; }
.dot-green { background: var(--success); }
.dot-red   { background: var(--danger); }

/* ---------- 表单 ---------- */
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px 20px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field.full { grid-column: 1 / -1; }
label { font-size: 13px; font-weight: 500; color: var(--text-2); }
label .req { color: var(--danger); margin-left: 2px; }
.field .tip { font-size: 12px; color: var(--text-3); }

input[type=text], input[type=password], input[type=date],
input[type=email], input[type=number], input[type=month], select, textarea {
  width: 100%; padding: 8px 12px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-size: 14px; font-family: inherit; color: var(--text);
  background: #fff; transition: all .15s;
}
input::placeholder, textarea::placeholder { color: #c0c4cc; }
input:hover, select:hover, textarea:hover { border-color: var(--primary-h); }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(22,119,255,.1);
}
input:disabled {
  background: var(--bg); color: var(--text-3); cursor: not-allowed;
}
input:disabled:hover { border-color: var(--border); }
textarea { resize: vertical; min-height: 84px; line-height: 1.65; }
select { cursor: pointer; }
/* 日期框整体可点唤出选择器（见 app.js），光标跟着变成手型 */
input[type=date] { cursor: pointer; }
input[type=date]::-webkit-calendar-picker-indicator { cursor: pointer; }

/* 数字框：去掉浏览器自带的上下箭头。分数、节数这类都是直接键入，
   箭头一格一格点没人用，还挤占了本来就不宽的输入区。 */
input[type=number] { -moz-appearance: textfield; appearance: textfield; }
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}

/* 带单位后缀的输入组，如「分数 [ 88 ] / 100 分」 */
.input-unit { display: flex; align-items: center; gap: 8px; }
.input-unit input { flex: 1; min-width: 0; }
.input-unit .unit {
  flex-shrink: 0; font-size: 13px; color: var(--text-3); white-space: nowrap;
}
/* 分数框：数字居中、字号大一点，一眼能看清填的是几分 */
.score-input {
  text-align: center; font-size: 16px; font-weight: 600;
  letter-spacing: .5px;
}
.score-input:disabled { font-weight: 400; }

.form-section {
  margin-bottom: 22px; padding-bottom: 4px;
}
.form-section > .title {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 16px; padding-bottom: 10px;
  border-bottom: 1px solid var(--border-2);
  font-size: 14px; font-weight: 600;
}
.form-section > .title::before {
  content: ''; width: 3px; height: 14px;
  border-radius: 2px; background: var(--primary);
}
.form-actions {
  display: flex; gap: 10px; margin-top: 4px;
  padding-top: 18px; border-top: 1px solid var(--border-2);
}
.checkbox-line {
  display: flex; align-items: center; gap: 8px;
  font-size: 14px; font-weight: normal; color: var(--text); cursor: pointer;
}
.checkbox-line input { width: auto; cursor: pointer; accent-color: var(--primary); }

/* ---------- 颜色单选（费用类别的徽章颜色）---------- */
.radio-row { display: flex; flex-wrap: wrap; gap: 14px; }
.radio-chip {
  display: flex; align-items: center; gap: 6px;
  font-weight: normal; cursor: pointer;
}
.radio-chip input { width: auto; cursor: pointer; accent-color: var(--primary); }

/* ---------- 日期三段下拉 ---------- */
.date-select { display: flex; gap: 8px; }
.date-select select { flex: 1; min-width: 0; }
.date-select select:first-child { flex: 1.4; }

/* ---------- 筛选栏 ---------- */
.filters {
  display: flex; gap: 12px; flex-wrap: wrap; align-items: flex-end;
  margin-bottom: 16px; padding: 16px 18px;
  background: #fff; border-radius: var(--radius); box-shadow: var(--shadow);
}
.filters .field { min-width: 140px; gap: 5px; }
.filters .field label { font-size: 12px; }
/* 关键词框给个固定宽度，不跟着容器拉满 */
.filters .field-kw { width: 210px; min-width: 210px; }
/* 起止日期挤在一格里，两个 input 平分宽度 */
.field-range { display: flex; align-items: center; gap: 6px; }
.field-range input { min-width: 0; flex: 1; }
/* 标签后面的补充说明，如「12/48 场」 */
.filters label .hint { color: var(--text-3); font-style: normal; font-weight: normal; }
/* 按钮紧跟最后一个筛选项，不推到最右侧 */
.filters .btn-group { margin-right: auto; }

/* ---------- 统计卡 ---------- */
.stat-row {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 16px; margin-bottom: 18px;
}
.stat {
  display: flex; align-items: center; gap: 14px;
  padding: 20px; background: #fff;
  border-radius: var(--radius); box-shadow: var(--shadow);
  transition: box-shadow .2s, transform .2s;
}
.stat:hover { box-shadow: var(--shadow-lg); transform: translateY(-2px); }
.stat-icon {
  display: grid; place-items: center; flex-shrink: 0;
  width: 46px; height: 46px; border-radius: 10px;
}
.stat-icon.blue   { background: var(--primary-bg); color: var(--primary); }
.stat-icon.green  { background: var(--success-bg); color: var(--success); }
.stat-icon.orange { background: var(--warning-bg); color: var(--warning); }
.stat-icon.purple { background: #f4f0ff; color: #722ed1; }
.stat .num {
  font-size: 26px; font-weight: 600; line-height: 1.2;
  letter-spacing: -.5px;
}
.stat .lbl { font-size: 13px; color: var(--text-3); margin-top: 2px; }

/* ---------- 成绩单的学期分组标题 ---------- */
.term-head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 13px 18px; border-bottom: 1px solid var(--border);
  background: #fafbfc;
}
.term-head b { font-size: 15px; }
.term-head .muted { font-size: 13px; }

/* ---------- 待审核行 ---------- */
/* 待审核的调代课左侧加一道竖条，扫一眼就知道哪几行要处理 */
tr.row-pending td:first-child { box-shadow: inset 3px 0 0 var(--warning); }
.row-note {
  display: inline-block; margin-top: 2px; padding: 1px 6px;
  font-size: 12px; color: var(--warning);
  background: var(--warning-bg); border-radius: 3px;
}

/* ---------- 及格判定提示 ---------- */
/* 录成绩时分数和及格线一变就即时反馈，不用保存完才知道判成什么 */
.pass-hint {
  display: flex; align-items: center;
  min-height: 38px; padding: 0 12px;
  border-radius: var(--radius-sm); font-size: 13px;
  background: var(--bg); color: var(--text-3);
}
.pass-hint.is-pass   { background: var(--success-bg); color: #389e0d; }
.pass-hint.is-fail   { background: var(--warning-bg); color: #d46b08; }
.pass-hint.is-absent { background: var(--border-2);  color: var(--text-3); }

/* ---------- 图表 ---------- */
.chart-grid-2 {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
  gap: 16px; margin-bottom: 18px;
}
.chart-card {
  background: #fff; border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 16px 18px 10px;
}
.chart-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin-bottom: 6px;
}
.chart-head b { font-size: 14px; }
.chart-head .muted { font-size: 12px; }
/* viewBox 缩放，容器多宽图就多宽 */
.chart { display: block; width: 100%; height: auto; overflow: visible; }
.chart-grid { stroke: var(--border-2); stroke-width: 1; }
.chart-axis { fill: var(--text-3); font-size: 11px; }
.chart-val  { fill: var(--text-2); font-size: 11px; font-weight: 600; }
.chart-line {
  fill: none; stroke: var(--primary); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.chart-dot { fill: #fff; stroke: var(--primary); stroke-width: 2; }
.chart-bar { fill: var(--primary); transition: opacity .15s; }
.chart-bar:hover { opacity: .75; }
/* 并列柱的图例。只靠颜色区分的图必须配图例 */
.chart-legend {
  display: flex; gap: 14px; flex-wrap: wrap;
  margin: 0 0 8px; font-size: 12px; color: var(--text-2);
}
.chart-legend-item { display: inline-flex; align-items: center; gap: 5px; }
.chart-legend-item i {
  width: 10px; height: 10px; border-radius: 2px; display: inline-block;
}
/* 折线图的图例画成一段线，跟图上的形状对得上——柱状图那种方块
   会让人在图上找一个色块，而那里是一条线加几个点 */
.chart-legend-item i.is-line { width: 14px; height: 3px; border-radius: 2px; }
/* 多序列折线的图例竖在图左边，一行一门课。横排时八门课要占两三行，
   把图本身挤矮；竖排还能让课名左对齐，扫一眼就对上颜色 */
.chart-with-legend { display: flex; align-items: center; gap: 12px; }
.chart-with-legend .chart { min-width: 0; flex: 1; }
.chart-legend.is-side {
  display: flex; flex-direction: column; gap: 6px; flex: none;
  max-width: 120px; margin: 0;
}
.chart-legend.is-side .chart-legend-item { align-items: baseline; }
.chart-legend.is-side .chart-legend-item i { flex: none; margin-top: 4px; }
.chart-empty {
  padding: 46px 20px; text-align: center;
  color: var(--text-3); font-size: 13px;
}

/* ---------- 审批流程 ---------- */
.flow-steps {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 16px 18px;
}
.flow-step {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 14px 9px 10px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: #fff;
}
.flow-no {
  display: grid; place-items: center; flex-shrink: 0;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--primary-bg); color: var(--primary-a);
  font-size: 12px; font-weight: 600;
}
.flow-arrow { color: var(--text-3); display: flex; }
/* 配置表单里的步骤行：序号 + 审批人 + 步骤名 三列 */
.step-rows { display: flex; flex-direction: column; gap: 12px; }
.step-row {
  display: grid; grid-template-columns: 24px 1fr 1fr;
  gap: 14px; align-items: end;
}
.step-row .flow-no { margin-bottom: 9px; }

/* 账单的费用明细行。比审批步骤多一列（项目 / 名目 / 金额），
   金额那列窄一些——它只放得下几位数字，跟名目一样宽是浪费 */
.item-row {
  display: grid; grid-template-columns: 24px 1fr 1fr 130px;
  gap: 14px; align-items: end;
}
.item-row .flow-no { margin-bottom: 9px; }
/* 按班生成时没有「名目」那一列——名目直接取项目名，不让逐个改 */
.item-row.is-batch { grid-template-columns: 24px 1fr 160px; }
@media (max-width: 720px) {
  /* 窄屏几列挤不开，序号单独一列，其余各占一行 */
  .item-row, .item-row.is-batch { grid-template-columns: 24px 1fr; }
}

/* 审批轨迹：每步一行，圆点标状态 */
.appr-chain { display: flex; flex-direction: column; gap: 4px; }
.appr-step {
  display: flex; align-items: flex-start; gap: 7px;
  font-size: 13px; color: var(--text-2); line-height: 1.5;
}
.appr-dot {
  flex-shrink: 0; width: 7px; height: 7px; margin-top: 6px;
  border-radius: 50%; background: var(--border);
}
.appr-step.is-approved .appr-dot { background: var(--success); }
.appr-step.is-rejected .appr-dot { background: var(--danger); }
.appr-step.is-pending  .appr-dot { background: var(--warning); }
.appr-step.is-approved { color: #389e0d; }
.appr-step.is-rejected { color: #cf1322; }
.appr-step.is-skipped  { color: var(--text-3); }

/* 侧栏菜单角标：待我审的条数 */
.nav-badge {
  margin-left: auto; padding: 0 6px; min-width: 18px;
  border-radius: 9px; background: var(--warning); color: #fff;
  font-size: 11px; font-style: normal; font-weight: 600;
  text-align: center; line-height: 18px;
}

/* ---------- 课时趋势条 ---------- */
.trend {
  display: grid; grid-template-columns: repeat(12, 1fr);
  gap: 8px; align-items: end; margin-top: 6px;
}
.trend-col { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.trend-bar-wrap {
  display: flex; align-items: flex-end;
  width: 100%; height: 110px;
  background: #fafbfc; border-radius: 4px;
}
.trend-bar {
  width: 100%; min-height: 2px;
  background: var(--primary); border-radius: 4px;
  transition: opacity .2s;
}
.trend-bar:hover { opacity: .8; }
.trend-col.is-cur .trend-bar { background: var(--warning); }
.trend-val { font-size: 12px; font-weight: 600; min-height: 16px; }
.trend-lbl { font-size: 12px; color: var(--text-3); text-decoration: none; }
.trend-lbl:hover { color: var(--primary); }
.trend-col.is-cur .trend-lbl { color: var(--warning); font-weight: 600; }

/* ---------- 详情表 ---------- */
.detail-table th {
  width: 120px; background: #fafbfc; font-weight: 500;
  color: var(--text-2); vertical-align: top;
}
.detail-table td { word-break: break-word; }
.detail-table tr:last-child td, .detail-table tr:last-child th {
  border-bottom: none;
}
.text-block { line-height: 1.75; white-space: pre-wrap; color: var(--text); }
.read-block {
  padding: 14px 16px; background: #fafbfc;
  border-radius: var(--radius-sm); border-left: 3px solid var(--border);
  line-height: 1.75; white-space: pre-wrap;
}
.read-block.accent { border-left-color: var(--primary); background: #f7fbff; }

/* ---------- 分页 ---------- */
.pagination {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 12px 16px;
  border-top: 1px solid var(--border-2);
  background: linear-gradient(180deg, #fcfdfe, #f8fafc);
  font-size: 13px;
}
.pg-total { color: var(--text-2); white-space: nowrap; }
.pg-total b { color: var(--text); font-weight: 600; }
.pg-range {
  margin-left: 8px; padding: 1px 7px;
  border-radius: 20px; background: var(--primary-bg);
  color: var(--primary-a); font-size: 12px; font-variant-numeric: tabular-nums;
}

/* 右侧控件区：每页条数 + 页码 + 跳转，整体右对齐 */
.pg-ctrl {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  margin: 0 0 0 auto;
}
.pg-size { display: flex; align-items: center; gap: 6px; color: var(--text-2); }
.pg-size label { font-size: 13px; color: var(--text-2); }
.pg-size select {
  width: auto; padding: 5px 26px 5px 10px; font-size: 13px;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238f959e' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 9px center;
  appearance: none; -webkit-appearance: none;
}

/* 页码按钮组：靠 -1px 负边距让相邻按钮共用一条边线 */
.pg-nav { display: flex; align-items: center; }
.pg-btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; height: 30px; padding: 0 9px; margin-left: -1px;
  border: 1px solid var(--border); background: #fff;
  color: var(--text-2); font-size: 13px; font-variant-numeric: tabular-nums;
  transition: all .15s; position: relative;
}
.pg-nav > :first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); margin-left: 0; }
.pg-nav > :last-child { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
a.pg-btn:hover {
  color: var(--primary); border-color: var(--primary-h);
  background: var(--primary-bg); z-index: 2;
}
.pg-btn.is-active {
  z-index: 3; font-weight: 600; color: #fff;
  background: var(--primary); border-color: var(--primary);
  box-shadow: 0 2px 6px rgba(22,119,255,.28);
}
.pg-btn.is-disabled {
  color: #c8ccd2; background: #fafbfc; cursor: not-allowed;
}
.pg-gap {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px; height: 30px; margin-left: -1px;
  border: 1px solid var(--border); border-left-color: var(--border);
  background: #fff; color: var(--text-3); letter-spacing: 1px;
}
.pg-jump { display: flex; align-items: center; gap: 6px; color: var(--text-2); }
.pg-jump input {
  width: 58px; padding: 5px 8px; text-align: center; font-size: 13px;
  font-variant-numeric: tabular-nums;
}
/* 去掉 number 输入框的上下小箭头，58px 宽度放不下 */
.pg-jump input::-webkit-outer-spin-button,
.pg-jump input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.pg-jump input { -moz-appearance: textfield; }

/* ---- 缴费：账单详情的二维码 ---- */
/* 左边固定宽度放码，右边信息区自适应。码不能跟着容器缩放，
   缩小了手机就扫不出来 */
.bill-cols { display: grid; grid-template-columns: 320px 1fr; gap: 16px;
  align-items: start; }
.qr-card { position: sticky; top: 16px; }
.qr-box {
  display: flex; align-items: center; justify-content: center;
  padding: 4px 20px 16px;
}
/* segno 生成的 SVG 自带宽高，限一下最大宽度免得撑破卡片 */
.qr-box svg { width: 100%; max-width: 240px; height: auto; }
.qr-dead {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  width: 240px; height: 240px; justify-content: center;
  color: var(--text-3); background: var(--bg); border-radius: 8px;
}
.qr-dead p { margin: 0; text-align: center; font-size: 13px; }
.qr-actions {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 0 20px 16px;
}
.qr-link { padding: 12px 20px; border-top: 1px solid var(--border);
  font-size: 12px; }
.qr-link code {
  display: block; margin: 4px 0; padding: 6px 8px; word-break: break-all;
  background: var(--bg); border-radius: 4px; color: var(--text-2);
  font-size: 11px;
}

/* 学情分析分享给家长的那一块（ai_report.html）。
   二维码在左、说明和操作在右；窄屏改成上下 */
.share-box {
  display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap;
}
.share-qr {
  flex: 0 0 auto; line-height: 0;
  padding: 8px; background: #fff; border: 1px solid var(--border);
  border-radius: 8px;
}
/* segno 生成的 SVG 自带宽高，限一下免得撑破卡片。
   不小于 160px：再小手机就扫不出来了 */
.share-qr svg { width: 168px; height: auto; display: block; }
.share-info { flex: 1 1 260px; min-width: 0; }
/* 链接是只读输入框而不是 code：家长链接要能一键全选复制，
   而 code 里的长串在手机上很难划中 */
.share-url input {
  width: 100%; padding: 7px 9px; font-size: 12px;
  border: 1px solid var(--border); border-radius: 4px;
  background: var(--bg); color: var(--text-2);
}

@media (max-width: 900px) {
  .pagination { gap: 10px; }
  .pg-ctrl { margin-left: 0; width: 100%; justify-content: space-between; }
  .pg-jump { display: none; }
}

/* ---------- 登录页 ---------- */
.login-page {
  display: grid; place-items: center; min-height: 100vh; padding: 20px;
  background: linear-gradient(135deg, #1677ff 0%, #0958d9 55%, #003eb3 100%);
  position: relative; overflow: hidden;
}
.login-page::before, .login-page::after {
  content: ''; position: absolute; border-radius: 50%;
  background: rgba(255,255,255,.06);
}
.login-page::before { width: 460px; height: 460px; top: -160px; right: -110px; }
.login-page::after  { width: 340px; height: 340px; bottom: -130px; left: -90px; }
.login-box {
  position: relative; z-index: 1;
  width: 380px; padding: 36px 34px;
  background: #fff; border-radius: 12px; box-shadow: var(--shadow-lg);
}
.login-logo {
  display: grid; place-items: center;
  width: 52px; height: 52px; margin: 0 auto 16px;
  border-radius: 13px; color: #fff;
  background: linear-gradient(135deg, var(--primary), #69b1ff);
  box-shadow: 0 6px 16px rgba(22,119,255,.3);
}
/* 同 .brand-logo.has-img 的理由：不垫渐变底，也不加投影 */
.login-logo.has-img { background: none; box-shadow: none; }
.login-logo.has-img img {
  width: 52px; height: 52px; object-fit: contain; display: block;
  border-radius: 13px;
}
.login-box h1 { text-align: center; font-size: 21px; margin-bottom: 6px; }
.login-box .sub {
  text-align: center; color: var(--text-3);
  font-size: 13px; margin: 0 0 26px;
}
.login-box .field { margin-bottom: 16px; }
.login-box .btn-primary {
  width: 100%; justify-content: center;
  padding: 10px; font-size: 15px; margin-top: 6px;
}

/* 登录页页脚：在卡片外面、蓝色渐变背景上，所以用半透明白字。
   z-index 要有，否则被 .login-page::after 那个装饰圆盖住 */
.login-foot {
  position: relative; z-index: 1;
  margin: 18px 0 0; text-align: center;
  font-size: 12px; color: rgba(255,255,255,.72);
}

/* 验证码：输入框和图片一行放下。图片不参与伸缩，宽高由 img 标签的
   width/height 属性给（值来自 captcha.py，改那边的 SCALE 不用改这里） */
.captcha-row { display: flex; gap: 10px; align-items: stretch; }
.captcha-row input { flex: 1; min-width: 0; }
.captcha-img {
  flex: none; max-width: 100%; height: auto;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  cursor: pointer; user-select: none; display: block;
}
.captcha-img:hover { border-color: var(--primary); }
.captcha-hint {
  margin: 7px 0 0; font-size: 12px; color: var(--text-3); line-height: 1.5;
}

/* ---------- 登记收款的分项填写 ---------- */
/* 四列：名目 / 应缴 / 已缴 / 本次收到。金额列右对齐，一列数字竖着好比 */
.pay-alloc { margin: 4px 0 16px; }
.alloc-head, .alloc-row, .alloc-foot {
  display: grid; grid-template-columns: 1fr 110px 150px 130px;
  gap: 10px; align-items: center;
}
.alloc-head {
  padding: 0 0 7px; border-bottom: 1px solid var(--border);
  font-size: 12px; color: var(--text-3);
}
.alloc-row { padding: 8px 0; border-bottom: 1px solid var(--border-2); }
.alloc-row .nm { font-size: 14px; color: var(--text); }
.alloc-head .num, .alloc-row .num, .alloc-foot .num { text-align: right; }
.alloc-row input { width: 100%; text-align: right; }
.alloc-row .badge { margin-left: 4px; font-size: 10px; }
.alloc-foot {
  padding: 10px 0 0; font-weight: 600;
}
.alloc-foot .num { font-size: 15px; color: var(--primary); }
@media (max-width: 720px) {
  /* 窄屏四列挤不开，改成名目一行、三个数字挤一行 */
  .alloc-head { display: none; }
  .alloc-row {
    grid-template-columns: 1fr 1fr;
    row-gap: 4px; padding: 10px 0;
  }
  .alloc-row .nm { grid-column: 1 / -1; font-weight: 600; }
  .alloc-foot { grid-template-columns: 1fr auto; }
}

/* 汇总表的总计行。加粗、上边框、浅底，让它跟数据行区分开——
   一列数字看到最后，得能看出哪一行是合计 */
tfoot th {
  padding: 11px 14px;
  background: var(--bg); border-top: 2px solid var(--border);
  font-size: 13px; font-weight: 600; color: var(--text);
  text-align: left; white-space: nowrap;
}

/* 表格单元格里的分项小表：审批页和账单页用来列「这笔钱各项收了多少」。
   嵌在 td 里，所以字号比正文小一号，且不能有自己的边框——
   两层框线套在一起会让表格看起来像坏了 */
.alloc-mini { border-collapse: collapse; width: 100%; min-width: 150px; }
.alloc-mini th, .alloc-mini td {
  padding: 1px 0; border: none; font-weight: normal;
  font-size: 13px; line-height: 1.5; white-space: nowrap;
}
.alloc-mini th { text-align: left; color: var(--text-2); padding-right: 10px; }
.alloc-mini td { text-align: right; color: var(--text); }
.alloc-mini .sum th, .alloc-mini .sum td {
  padding-top: 3px; border-top: 1px solid var(--border);
  font-weight: 600; color: var(--text);
}

/* 账单列表的分项显示：名目 / 应缴 / 已缴 三列各一格，一项一行平着对齐。
   行高写死，三列才对得上——交给内容自己撑的话，某一项名目换行或
   多了个「待核准」标记，就只有那一列变高 */
.fee-lines { vertical-align: top; padding-top: 10px; padding-bottom: 10px; }
.fee-lines.num { text-align: right; white-space: nowrap; }
.fee-lines .ln {
  height: 22px; line-height: 22px; font-size: 13px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fee-lines .ln.sum {
  margin-top: 3px; padding-top: 3px;
  border-top: 1px solid var(--border); font-weight: 600;
}
/* 单项单只有一行，字号跟其它列一致，不必压小 */
.fee-lines .ln.cell-main { font-size: 14px; }
/* 待核准的钱：跟在已缴后面的小字，不占一行 */
.fee-lines .pend { margin-left: 6px; font-size: 12px; color: var(--warning); }

/* ---------- 报销：明细与发票合并行 ---------- */
/* 一行 = 一项费用 + 它的那张票：名称 / 金额 / 文件 / 号码 / 开票日期。
   名称和文件框占最宽的两格——文件框里的原生文件名本来就长，
   挤窄了只能看见开头几个字 */
.exp-rows { display: flex; flex-direction: column; gap: 10px; }
.exp-row {
  display: grid; grid-template-columns: 26px 1.3fr 120px 1.4fr 1fr 150px;
  gap: 12px; align-items: end;
}
.exp-row .inv-no {
  display: grid; place-items: center;
  width: 22px; height: 22px; margin-bottom: 9px;
  border-radius: 50%; background: var(--primary-bg); color: var(--primary-a);
  font-size: 12px; font-weight: 600;
}
/* 「添加一行」跟在行列表下面，留一点间距不然贴着最后一行 */
#add-row { margin-top: 12px; }
.inv-rows { display: flex; flex-direction: column; gap: 10px; }
.inv-row {
  display: grid; grid-template-columns: 26px 1.6fr 130px 1fr 150px;
  gap: 12px; align-items: end;
}
.inv-row .inv-no {
  display: grid; place-items: center;
  width: 22px; height: 22px; margin-bottom: 9px;
  border-radius: 50%; background: var(--primary-bg); color: var(--primary-a);
  font-size: 12px; font-weight: 600;
}
/* 已上传的那些：名称还是输入框（名称可以改），金额、文件名、号码
   都是平铺的文本，所以列宽和新增行不同 */
.inv-row.is-have {
  grid-template-columns: 26px 1.3fr 100px 1.2fr 1fr auto;
  align-items: center; padding: 9px 0;
  border-bottom: 1px solid var(--border-2);
}
.inv-row.is-have .field { margin-bottom: 0; }
.inv-row.is-have .inv-no { margin-bottom: 0; }
/* 勾了删除的行压暗，提交前就能看出哪几张要没了 */
.inv-row.is-dropped { opacity: .45; text-decoration: line-through; }
.inv-name {
  display: inline-flex; align-items: center; gap: 5px;
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
}
.inv-amt { font-weight: 600; text-align: right; white-space: nowrap; }
/* 合计行。跟输入区之间留白，不然看起来像最后一行的一部分 */
.inv-total {
  margin-top: 14px; padding-top: 12px;
  border-top: 1px solid var(--border-2);
  font-size: 14px; text-align: right;
}
.inv-total b { font-size: 16px; color: var(--primary); margin-left: 4px; }
/* 「跟发票对上了没」的即时提示。绿色是对上，红色是差着钱——
   这是这个模块最要紧的一句话，颜色要够显眼 */
.inv-total .bal-ok  { margin-left: 10px; color: #389e0d; font-weight: 500; }
.inv-total .bal-bad { margin-left: 10px; color: var(--danger); font-weight: 600; }

/* 详情页：一张发票 + 挂在它下面的明细，成组显示 */
.inv-block {
  padding: 12px 0; border-bottom: 1px solid var(--border-2);
}
.inv-block:last-child { border-bottom: none; }
.inv-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 6px;
}
.inv-head .inv-amt { margin-left: auto; }
/* 明细小表缩进到发票序号右边，看得出它们是从属关系 */
.inv-block .alloc-mini { margin-left: 34px; width: calc(100% - 34px); }
/* 小计超出票面：这是填错了，标红 */
.alloc-mini td.is-over { color: var(--danger); }

@media (max-width: 900px) {
  /* 窄屏几列挤不开，各占一行；序号并到第一格那行 */
  .inv-row, .inv-row.is-have, .exp-row {
    grid-template-columns: 26px 1fr;
  }
  .inv-row > .field:not(:first-of-type),
  .exp-row > .field:not(:first-of-type),
  /* 已上传行里金额、文件名、号码不是 .field，也得各占一行 */
  .inv-row.is-have > .inv-amt,
  .inv-row.is-have > .inv-name,
  .inv-row.is-have > .muted,
  .inv-row.is-have > .checkbox-line { grid-column: 2; }
  .inv-row.is-have .inv-amt { text-align: left; }
  .inv-block .alloc-mini { margin-left: 0; width: 100%; }
}

/* ---------- 权限矩阵 ---------- */
.perm-module {
  margin-bottom: 14px; padding: 12px 14px;
  background: #fafbfc; border: 1px solid var(--border-2);
  border-radius: var(--radius-sm);
}
.perm-module > strong {
  display: flex; align-items: center; gap: 10px; margin-bottom: 8px;
  font-size: 12px; font-weight: 600; color: var(--text-3);
  letter-spacing: .3px;
}
/* 全选勾选框：小一号，颜色压淡，不跟权限项抢注意力 */
.perm-all {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; font-weight: normal; color: var(--text-3);
  cursor: pointer;
}
.perm-all input {
  width: auto; margin: 0; cursor: pointer; accent-color: var(--primary);
}
.form-section > .title .perm-all { margin-left: auto; }
.perm-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px 18px; }
.perm-grid label {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 11px; border-radius: var(--radius-sm);
  font-weight: normal; font-size: 13px; color: var(--text);
  cursor: pointer; transition: background .15s;
}
.perm-grid label:hover { background: #fff; }
.perm-grid label.is-locked { cursor: default; }
.perm-grid label.is-locked:hover { background: transparent; }
.perm-grid input { width: auto; cursor: pointer; accent-color: var(--primary); }
.perm-grid input:disabled { cursor: default; }
.perm-grid input:disabled + span { color: var(--text-3); }
.perm-grid code {
  font-size: 11px; color: var(--text-3);
  font-family: ui-monospace, Consolas, monospace;
}

/* ---------- 导入结果 ---------- */
.import-stats { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 4px; }
.import-stats .stat {
  flex: 1; min-width: 110px; padding: 12px 14px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: #fff;
}
.import-stats .stat b {
  display: block; font-size: 22px; font-weight: 600; line-height: 1.2;
  font-variant-numeric: tabular-nums;
}
.import-stats .stat span { font-size: 12px; color: var(--text-3); }
.import-stats .ok   { background: var(--success-bg); border-color: #b7eb8f; }
.import-stats .ok b { color: #389e0d; }
.import-stats .skip { background: var(--warning-bg); border-color: #ffd591; }
.import-stats .skip b { color: #d46b08; }
.import-stats .bad  { background: var(--danger-bg); border-color: #ffccc7; }
.import-stats .bad b { color: var(--danger); }
.row-err {
  display: flex; align-items: flex-start; gap: 6px;
  color: var(--danger); font-size: 13px; line-height: 1.6;
}
.row-err svg { flex: none; margin-top: 4px; }
/* 文件选择框：原生控件不吃 padding，单独放宽 */
input[type=file] {
  width: 100%; padding: 7px 12px;
  border: 1px dashed var(--border); border-radius: var(--radius-sm);
  font-size: 13px; background: #fafbfc; cursor: pointer;
}
input[type=file]:hover { border-color: var(--primary-h); }

/* ---------- 工作台的业务面板 ---------- */
/* 三块并排，窄了自动掉行。
   下限 300px 是量出来的，不是随手取的：900px 窗口下正文区宽 648px
   （减掉侧栏 200 和左右内边距 52），两列要 300×2+16=616 才装得下。
   原先写 320 时差 8px 装不下，直接掉成一列——而一张 648px 宽的面板里
   「名目 ... 数字」被拉到两头，中间空一大片，比两列窄卡片难读。
   再往下调就不行了：缴费那块「班名 + 张数 + 金额」会挤成两行 */
.dash-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 16px; margin-bottom: 18px;
}
/* 各块按自己的内容高，不拉平。
   试过 height:100% 让三块一样高，更糟：内容少的那块（比如一个不在任何
   审批流程里的人，报销那块只有一行）会在白卡片里空出三四百像素，
   看着像内容没加载出来。不拉平的话那块空白落在卡片外面，
   就是页面上的留白，没人会多看一眼。顶边对齐，读的时候三块的标题
   在一条线上。 */
.dash-panel { margin-bottom: 0; align-self: start; }
.dash-panel h2 {
  display: flex; align-items: center; gap: 7px;
  margin: 0 0 12px; font-size: 16px;
}
.dash-panel h2 svg { color: var(--text-3); flex: none; }
.dash-more {
  margin-left: auto; font-size: 13px; font-weight: 400;
}
/* 小标题。上下留白不对称：上面要和前一组拉开（隔开的是两组不同的东西），
   下面紧贴着自己那张表。原先上边距 14px 太小，紧跟在 dl 后面时
   看着像是最后一行的一部分 */
.dash-sub {
  margin: 20px 0 6px; font-size: 12px; color: var(--text-3);
  padding-top: 12px; border-top: 1px solid var(--border-2);
}
/* 只有真的排在卡片最前面时才去掉分隔线。
   用 :first-child 而不是 :first-of-type——后者是「同类里的第一个」，
   缴费那块的「欠得最多的班」前面隔着一个 dl，仍然是第一个 .dash-sub，
   于是分隔线和上边距都被清掉，标题贴在上一行数字底下像是它的一部分。 */
.dash-sub:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

/* 学期那种说明性的一行不是小标题，不给分隔线。它紧跟在卡片标题下面，
   说的是「下面这些数是哪个范围的」 */
.dash-cap {
  margin: 0 0 10px; font-size: 12px; color: var(--text-3);
}
.dash-none { margin: 0; font-size: 13px; color: var(--text-3); }

/* 一块面板的头条：要动手的那件事。做成整块可点的链接而不是
   「数字 + 旁边一个小链接」——首页上最常见的动作就是点它 */
.dash-lead {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 14px; margin-bottom: 12px;
  border-radius: var(--radius-sm); text-decoration: none;
  background: var(--primary-bg); color: var(--text);
  transition: filter .15s;
}
.dash-lead:hover { filter: brightness(.97); color: var(--text); }
.dash-lead.is-warn { background: var(--warning-bg); }
.dash-lead-n {
  flex: none; font-size: 26px; font-weight: 600; line-height: 1;
  letter-spacing: -.5px; font-variant-numeric: tabular-nums;
}
.dash-lead.is-todo .dash-lead-n { color: var(--primary); }
.dash-lead.is-warn .dash-lead-n { color: #d46b08; }
.dash-lead b { display: block; font-size: 14px; }
.dash-lead-sub {
  display: block; margin-top: 2px; font-size: 12px; color: var(--text-3);
}

/* 名目 + 数字的两列。用 dl 而不是 table：这是「标签对值」，
   不是表格数据，读屏软件读 dl 时会把两者配对念出来 */
.dash-kv {
  display: grid; grid-template-columns: auto 1fr;
  gap: 7px 14px; margin: 0; font-size: 13px;
}
.dash-kv dt { color: var(--text-3); white-space: nowrap; }
.dash-kv dd {
  margin: 0; text-align: right; font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* 面板里的小表：左边名目、右边数字。边框全去掉，只靠行距分组，
   底色显式清掉——全局的 th{background:#fafbfc} 会渗进来 */
.dash-mini { width: 100%; border-collapse: collapse; }
.dash-mini th, .dash-mini td {
  padding: 7px 0; border: 0; border-top: 1px solid var(--border-2);
  font-size: 13px; font-weight: 400; text-align: left;
  background: transparent; color: inherit; vertical-align: top;
}
.dash-mini tr:first-child th, .dash-mini tr:first-child td { border-top: 0; }
.dash-mini td {
  width: 96px; text-align: right; font-weight: 600; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.dash-mini .cell-sub { display: block; margin-top: 1px; font-weight: 400; }

/* ---------- 权限标签墙 ---------- */
.perm-wall { display: flex; flex-wrap: wrap; gap: 7px; }
.perm-wall code {
  padding: 3px 9px; border-radius: 5px;
  background: var(--bg); color: var(--text-2);
  font-size: 12px; font-family: ui-monospace, Consolas, monospace;
}

/* ---------- 错误页 ---------- */
.error-page {
  display: grid; place-items: center; min-height: 100vh;
  text-align: center; padding: 20px;
}
.error-page .code {
  font-size: 76px; font-weight: 700; line-height: 1;
  color: var(--primary); opacity: .16;
}
.error-page h2 { margin: 14px 0 22px; font-size: 17px; }

/* ---------- 响应式 ---------- */
@media (max-width: 900px) {
  .form-grid, .perm-grid { grid-template-columns: 1fr; }
  .detail-table th { width: 96px; }
  .chart-grid-2 { grid-template-columns: 1fr; }
  /* 窄屏下图例翻回横排：竖排占掉的那 120 像素在手机上是图宽的三分之一，
     图比图例要紧 */
  .chart-with-legend { display: block; }
  .chart-legend.is-side {
    flex-direction: row; flex-wrap: wrap; gap: 14px;
    max-width: none; margin-bottom: 8px;
  }
  /* 窄屏下步骤行改成上下排，序号单独占一行会很怪，所以并到审批人那行 */
  .step-row { grid-template-columns: 24px 1fr; }
  .step-row > .field:last-child { grid-column: 2; }
}
@media (max-width: 900px) {
  /* 窄屏上二维码和信息区改成上下排，sticky 也取消——
     竖着滚的时候钉住的卡片会把内容挡掉 */
  .bill-cols { grid-template-columns: 1fr; }
  .qr-card { position: static; }
}

@media (max-width: 720px) {
  .sidebar { width: 60px; padding: 12px 8px; }
  .sidebar a { justify-content: center; padding: 11px 0; }
  .sidebar a span, .nav-group > .label { display: none; }
  /* 窄屏侧栏缩成一条图标轨，组标题（也就是那个能点的开关）被藏掉了，
     所以这里必须保证所有链接都露着：收起的组在这个宽度下既看不见
     链接、又没有开关可点，那几个菜单就等于消失了。

     这件事没法用 CSS 做。details 收起时，规范规定 summary 之外的
     子节点「not rendered」，那是 UA 层面的行为，给子元素写
     display:flex 盖不回来。所以交给 sidebar.js 里的宽度监听去强制
     展开，这儿只调间距 */
  .nav-group { margin-bottom: 14px; }
  /* 折叠态侧栏放不下角标数字，缩成一个小圆点 */
  .nav-badge {
    position: absolute; margin: 0; min-width: 8px; height: 8px;
    padding: 0; border-radius: 50%; font-size: 0;
    transform: translate(12px, -10px);
  }
  .sidebar a { position: relative; }
  .content { padding: 16px 14px; }
  .topbar { padding: 0 14px; }
  .topbar-user .name, .role-tag { display: none; }
  .filters .field { min-width: 100%; }
  .filters .btn-group { margin-right: 0; width: 100%; }
}

/* ==================== 快速审核报销 ====================
   做成表格而不是卡片：审批人的动作是一列一列地扫——先扫金额，再扫
   事由，看着不对的才去点发票。卡片式每张单的字段位置随内容长短浮动，
   扫不成一条直线。

   一张单里有几张票就在明细格里叠几行，行高由那一格撑开：发票和它
   对应的金额必须在同一横线上。 */
.rev-table { table-layout: auto; }
.rev-table > tbody > tr > td { vertical-align: top; padding-top: 12px; }
.rev-table .col-pick { width: 34px; text-align: center; }
/* 事由列给足宽度。它是审批人最先读的一列，挤到 130px 会把
   「10 月招生宣传物料」断成两行，而右边明细列还空着一块 */
.rev-table .col-title { min-width: 190px; }
.rev-table .col-items { min-width: 300px; }
.rev-table .col-amt { width: 108px; text-align: right; white-space: nowrap; }
.rev-table .col-when { width: 104px; white-space: nowrap; }
/* 不能审的行压暗，但仍看得清内容——它可能正是「等别人审」的那张 */
.rev-tr.is-off { opacity: .68; }
.rev-tr.is-picked > td { background: var(--primary-bg); }

/* 报销人和当前步骤两列不许在名字中间断行。默认的 word-wrap 会把
   「张老师」拆成「张老」+「师」，把「李主任」拆成「李主」+「任」——
   人名断开比多占一点横向宽度难读得多 */
.rev-table > tbody > tr > td:nth-child(3) { white-space: nowrap; }
.rev-table > tbody > tr > td:nth-child(7) { white-space: nowrap; }

/* 明细小表。嵌在单元格里，边框和底色全去掉，只靠行距分组。
   底色必须显式清掉：全局的 th{background:#fafbfc} 会渗进来，
   在明细名称后面拖出一条灰带 */
.rev-items { width: 100%; border-collapse: collapse; }
.rev-items th, .rev-items td {
  border: 0; padding: 3px 8px 3px 0; text-align: left;
  font-weight: 400; font-size: 13px; vertical-align: middle;
  background: transparent; color: inherit; white-space: normal;
}
.rev-items > tr + tr > th, .rev-items > tr + tr > td {
  border-top: 1px dashed var(--border-2);
}
.rev-item-amt {
  width: 92px; text-align: right; white-space: nowrap;
  font-variant-numeric: tabular-nums; font-weight: 600;
}
.rev-items .is-loose .rev-item-amt { color: var(--danger); }
.rev-items .col-thumb { width: 74px; padding-right: 0; }

/* 缩略图。固定 48px 高，一页十几行高度才可预期；点开是灯箱看大图 */
.rev-thumb {
  display: flex; align-items: center; justify-content: center;
  width: 68px; height: 48px; padding: 0; cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  overflow: hidden; background: var(--bg);
}
.rev-thumb:hover { border-color: var(--primary); }
.rev-thumb img { width: 100%; height: 100%; object-fit: cover; }
/* PDF 没法直接缩略，给个能认出来的卡片，点了新窗口打开 */
.rev-thumb.is-pdf {
  flex-direction: column; gap: 1px; font-size: 10px;
  text-decoration: none; color: var(--text-3);
}
.rev-thumb.is-none { cursor: default; color: var(--danger); }

/* 底部批量条。sticky 而不是 fixed：fixed 会盖住页脚，
   而且滚到底时它悬在内容上，看着像块飘着的东西 */
.rev-bar {
  position: sticky; bottom: 0; z-index: 20;
  display: flex; flex-wrap: wrap; align-items: center; gap: 14px;
  margin-top: 16px; padding: 12px 16px;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 10px; box-shadow: 0 -2px 12px rgba(0,0,0,.06);
}
.rev-bar-sum { font-size: 13px; }
.rev-bar-note { flex: 1; min-width: 180px; }
.rev-bar .btn-primary { margin-left: auto; }

/* 发票查看器。审票的人要看清印章和金额，缩略图不够用，
   但点进详情页再返回是两次往返——就地放大最省事。
   图片和 PDF 共用这一套，见 lightbox.js */
.lightbox {
  position: fixed; inset: 0; z-index: 200;
  /* 暗底要压到看不见背后的字，不是压暗一点。
     只调透明度没用：半透明黑是乘上去的，底色 255 和文字 60 一起按同一个
     系数缩，对比比例不变（0.86 时是 36 比 8），字始终认得出来。
     0.92 把绝对差值压小，blur 把字形直接搅掉——两个一起才真的看不见。
     不支持 backdrop-filter 的浏览器退化成「只是更暗一点」，不影响使用 */
  background: rgba(0,0,0,.92);
  backdrop-filter: blur(3px);
  display: flex; align-items: center; justify-content: center;
}
/* 灯箱的 [hidden] 由文件顶部那条全局规则管，这里不用再写一遍 */

/* 舞台负责滚动。放大到超出窗口时由它出滚动条，内容本身只管变尺寸——
   给内容加 transform 会让滚动范围和看到的位置对不上。
   上边留 56px 给头部工具条，不然放大后图顶到标题底下 */
.lb-stage {
  position: absolute; inset: 56px 0 12px;
  overflow: auto; overscroll-behavior: contain;
  display: flex; align-items: center; justify-content: center;
  padding: 12px;
}
.lb-stage > * { flex: none; }

/* 内容超出舞台的那一个方向要改成靠起点对齐，不能再居中。
   居中（无论 justify-content: center 还是 margin: auto）会让溢出的部分
   一半跑到滚动区域的负方向去，那一半永远滚不到——放大之后想看的
   右边缘恰好就在那里。踩过一次：1500px 宽的图在 1280 的框里
   只能滚 122px，另外 122px 够不着。
   两个方向分开判：一张宽图往往横向溢出而纵向还有余，
   纵向跟着改成靠上会让它贴在顶边上。类由 lightbox.js 挂 */
.lb-stage.is-wide { justify-content: flex-start; }
.lb-stage.is-tall { align-items: flex-start; }
.lb-stage.is-pannable { cursor: grab; }
.lb-stage.is-pannable:active { cursor: grabbing; }
.lb-img {
  display: block; background: #fff; border-radius: 4px;
  /* 拖着平移时别选中、别被当成拖拽文件。
     touch-action: none 让触屏上的拖动交给我们的 pointer 处理，
     否则浏览器会先把它当成滚动手势吃掉 */
  user-select: none; -webkit-user-drag: none; touch-action: none;
}
.lb-pdf {
  display: block; border: 0; border-radius: 4px; background: #fff;
}
/* 工具条给实底，不用渐变。渐变到底部是透明的，标题那一行正好落在
   透明处，背后页面的字透上来和标题叠在一起——截图里读出来是
   「10月招生宣传物料 · fapiao.pdf统」，那个「统」是背后的页面标题。
   实底还顺带把这一条读成「工具条」，和下面看票的区域分开 */
.lb-head {
  position: absolute; top: 0; left: 0; right: 0;
  display: flex; align-items: center; gap: 14px;
  padding: 12px 16px; color: #fff; font-size: 13px;
  background: #1a1a1a; border-bottom: 1px solid rgba(255,255,255,.12);
}
.lb-title { flex: 1; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.lb-count { flex: none; opacity: .8; font-variant-numeric: tabular-nums; }
.lb-open { flex: none; color: #fff; text-decoration: none; opacity: .85;
  display: flex; align-items: center; gap: 4px; }
.lb-open:hover { opacity: 1; }

/* 缩放控件。挤在一组里，中间那个百分比可以点，回到适应窗口 */
.lb-zoom {
  flex: none; display: flex; align-items: center; gap: 2px;
  padding: 2px; border-radius: 16px; background: rgba(255,255,255,.14);
}
.lb-btn, .lb-pct {
  border: 0; background: transparent; color: #fff; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%; font-size: 17px; line-height: 1;
  width: 26px; height: 26px; padding: 0;
}
.lb-pct {
  width: auto; padding: 0 8px; border-radius: 13px;
  font-size: 12px; font-variant-numeric: tabular-nums;
}
/* 已经是适应窗口时把百分比压暗：它此刻点了也没变化，
   亮着会让人以为还能再退一步 */
.lb-pct.is-fit { opacity: .55; }
.lb-btn:hover, .lb-pct:hover { background: rgba(255,255,255,.26); }
.lb-fit svg { width: 15px; height: 15px; }

.lb-x, .lb-nav {
  flex: none; background: rgba(255,255,255,.14); color: #fff;
  border: 0; border-radius: 50%; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.lb-x { width: 32px; height: 32px; }
.lb-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px; font-size: 26px; line-height: 1;
}
/* 工具条和翻页箭头要压在舞台上面。三个都是 absolute，而舞台在
   DOM 里排在「上一张」后面——不给 z-index 的话舞台会盖住那个箭头，
   放大之后连翻页都点不到 */
.lb-head, .lb-nav { z-index: 2; }
.lb-x:hover, .lb-nav:hover { background: rgba(255,255,255,.28); }
.lb-prev { left: 16px; }
.lb-next { right: 16px; }

@media (max-width: 900px) {
  /* 窄屏这张表列多，靠 .table-scroll 横向滚。缩略图和明细列跟着收窄，
     省下的宽度留给事由和金额——那两列是先要看的 */
  .rev-table .col-items { min-width: 240px; }
  .rev-thumb { width: 54px; height: 40px; }
  .rev-item-amt { width: 76px; }
  .rev-bar .btn-primary { margin-left: 0; }
  .lb-nav { width: 36px; height: 36px; font-size: 20px; }
}

/* ==========================================================
   可搜索下拉框（search_select.js）
   一个班五十几个学生时，原生 <select> 只能靠滚动找人。
   套一层输入框做模糊筛选，原 select 留在里面当值的载体。
   ========================================================== */
.ss { position: relative; }
/* 原生 select 收起来但不用 display:none —— 那样它就不再是表单控件，
   required 校验和 name/value 提交都会跟着失效。挪出视口即可 */
.ss-native {
  position: absolute; width: 1px; height: 1px;
  opacity: 0; pointer-events: none; clip: rect(0 0 0 0);
}
.ss-input { cursor: text; }
/* 收起时长得跟别的下拉框一样，右边补一个小三角，
   不然用户看不出这里能点开一个列表 */
.ss-input {
  background-image:
    linear-gradient(45deg, transparent 50%, var(--text-3) 50%),
    linear-gradient(135deg, var(--text-3) 50%, transparent 50%);
  background-position: calc(100% - 15px) 55%, calc(100% - 10px) 55%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 28px;
}
.ss-list {
  position: absolute; z-index: 30; left: 0; right: 0; top: calc(100% + 4px);
  margin: 0; padding: 4px; list-style: none;
  max-height: 264px; overflow-y: auto;
  background: #fff; border: 1px solid var(--border);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
}
.ss-opt {
  padding: 7px 10px; font-size: 14px; color: var(--text);
  border-radius: var(--radius-sm); cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 键盘上下键选中的那一项。hover 和它同一个样式——
   鼠标和键盘不该有两套视觉语言 */
.ss-opt.active, .ss-opt:hover { background: var(--primary-bg); }
/* 当前已选的那一项加粗，翻开列表时一眼看到自己在哪 */
.ss-opt.sel { color: var(--primary-a); font-weight: 600; }
.ss-empty {
  padding: 10px; font-size: 13px; color: var(--text-3); text-align: center;
}
