/* JBTool 应用面样式重写版（v7）
   范围：只覆盖 /login /register /user/* /admin/* /downloads 这些吃 style.css 的页；
        首页与定价页自带内联样式，不经这一层，所以不会被我改动。
   原则：类名与结构一律沿用原文件（模板不改），只重排观感层。
   ★v7 第 136 次改档：全站转**纯浅色**（顶栏不再是 #10182a 深色块）+ 动效层。
     为什么能整块转浅：之前"深浅混着"的唯一理由是高密度数据表在浅底上更可读，
     而顶栏深色只是沿用了首页的品牌色块 —— 把顶栏换成白底 + 下沿一条发丝线之后，
     品牌识别改由「渐变品牌字 + 当前页那根滑动下划线」承担，不靠深色底。
   ★动效一律走 --ease-* 三档、时长 120~520ms；入场动画只做 opacity/transform
     （不碰 width/height/top/left，那四类会触发重排，一滚动就掉帧）；
     prefers-reduced-motion 那档在文件末尾统一把 transition/animation 全掐掉。
*/
:root {
  color-scheme: light;
  /* 底色不用纯白：大面积表格里纯白最刺眼 */
  --bg: #f5f7fa; --bg-2: #eef2f8; --card: #fff;
  --ink: #16202f; --ink-2: #46536b; --muted: #6b7789;
  --brand: #2563eb; --brand-2: #1d4ed8; --brand-soft: #eaf0fe;
  /* 与首页同一支渐变，用于品牌字与主按钮，让两侧认得出是同一个产品 */
  --grad: linear-gradient(135deg, #4f7cff 0%, #7c5cff 100%);
  --line: #e2e8f1; --line-2: #cfd8e6;
  --danger: #c0392b; --danger-bg: #fdecea; --danger-line: #f3c4bd;
  --ok: #1a7f37; --ok-bg: #e9f6ee; --ok-line: #bfe3ca;
  --warn: #9a6700; --warn-bg: #fff6e0;
  --r-s: 8px; --r-m: 12px; --r-l: 16px;
  --sp-1: 4px; --sp-2: 8px; --sp-3: 14px; --sp-4: 22px; --sp-5: 32px;
  /* 阴影分三档，之前全站没有阴影，卡片和页面贴在一起没有层次 */
  --sh-1: 0 1px 2px rgba(16, 24, 42, .06);
  --sh-2: 0 2px 6px rgba(16, 24, 42, .07), 0 1px 2px rgba(16, 24, 42, .05);
  --sh-3: 0 12px 32px -12px rgba(16, 24, 42, .22), 0 2px 6px rgba(16, 24, 42, .06);
  --ring: 0 0 0 3px rgba(37, 99, 235, .18);
  --mono: ui-monospace, "Cascadia Mono", Consolas, "Liberation Mono", monospace;
  /* ---------- 动效令牌 ----------
     三条曲线各管一类：--ease 给"状态来回"（hover/active/focus，收尾要稳不要弹），
     --ease-out 给"入场"（只减速、到位即停，所以配 both 不会回抽），
     --ease-spring 给"要一点手感的东西"（按钮抬起、弹窗落位），过冲 6% 左右。 */
  --ease: cubic-bezier(.4, 0, .2, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --ease-spring: cubic-bezier(.34, 1.4, .64, 1);
  --t-1: .14s; --t-2: .24s; --t-3: .42s; --t-4: .56s;
  /* 浅色顶栏专用：发丝线与"当前页"那根下划线的落点色 */
  --chrome-line: #e6ebf3;
}
* { box-sizing: border-box; margin: 0; padding: 0 }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth }
body {
  font-family: system-ui, -apple-system, "Segoe UI", "Microsoft YaHei", "PingFang SC", sans-serif;
  background: var(--bg); color: var(--ink);
  font-size: 14.5px; line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  /* 原来 body 没有 flex 列，footer 会浮在半屏空白里；钉住视口让主区吃掉剩余高度 */
  min-height: 100vh; display: flex; flex-direction: column;
}
a { color: inherit; text-decoration: none }
:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 6px }
::selection { background: rgba(37, 99, 235, .18) }

/* ---------- 顶部条（v7 第 136 次：深块 → 白底发丝线）---------- */
.topbar {
  background: rgba(255, 255, 255, .86); padding: 0; position: sticky; top: 0; z-index: 40;
  /* ★半透明白 + 模糊：滚动时表格从顶栏底下掠过，不透一点就整块是"贴上去的白条"，
     透太多字就糊在一起 —— .86 与 blur(10px) 是实测两档之间取的中间值。 */
  backdrop-filter: blur(10px) saturate(1.3); -webkit-backdrop-filter: blur(10px) saturate(1.3);
  border-bottom: 1px solid var(--chrome-line);
  box-shadow: 0 1px 0 rgba(16, 24, 42, .02);
  transition: box-shadow var(--t-3) var(--ease), background-color var(--t-3) var(--ease);
}
/* 滚出去一点之后再把顶栏"抬"起来：滚动驱动的投影，不支持 animation-timeline 的浏览器
   直接跳过这一档，退回上面那条静态发丝线（不是降级成"没有投影"，是保持原样）。 */
@supports (animation-timeline: scroll()) {
  .topbar {
    animation: chrome-lift linear both;
    animation-timeline: scroll(root block); animation-range: 0 96px;
  }
}
@keyframes chrome-lift {
  from { box-shadow: 0 1px 0 rgba(16, 24, 42, .02) }
  to { box-shadow: 0 10px 26px -18px rgba(16, 24, 42, .38), 0 1px 0 rgba(16, 24, 42, .05) }
}
.topbar .wrap {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
  min-height: 58px; padding-top: 6px; padding-bottom: 6px;
}
.brand { color: var(--ink); font-size: 18px; font-weight: 700; letter-spacing: .3px; line-height: 1 }
/* 品牌字用首页那支渐变，取代原来"JB 白 + Tool 蓝"的拼色 */
.brand b {
  background: var(--grad); -webkit-background-clip: text; background-clip: text;
  color: transparent; font-weight: 800;
}
.topbar nav {
  display: flex; gap: 2px; align-items: center;
  /* 后台导航 11 项，窄屏放不下；用横向滚动取代折行，折行会把顶栏撑高一倍 */
  overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none;
  flex-wrap: nowrap; padding: 4px 0; margin: 0 -4px;
}
.topbar nav::-webkit-scrollbar { display: none }
.topbar nav a {
  position: relative; color: var(--ink-2); font-size: 13.5px; padding: 7px 11px;
  border-radius: var(--r-s); white-space: nowrap;
  transition: color var(--t-1) var(--ease), background-color var(--t-1) var(--ease);
}
.topbar nav a:hover { color: var(--brand-2); background: var(--bg-2) }
/* ★当前页/悬停都靠这根滑动下划线表态，不再靠"加深底色"—— 浅色顶栏上底色差一档就看不清了。
   只做 transform:scaleX，不动 width，所以是合成层动画、不触发重排。 */
.topbar nav a::after {
  content: ""; position: absolute; left: 11px; right: 11px; bottom: 2px; height: 2px;
  border-radius: 2px; background: var(--grad);
  transform: scaleX(0); transform-origin: left center;
  transition: transform var(--t-3) var(--ease-out);
}
.topbar nav a:hover::after, .topbar nav a.on::after { transform: scaleX(1) }
.linklike {
  position: relative; background: none; border: 0; color: var(--ink-2); font: inherit; font-size: 13.5px;
  cursor: pointer; padding: 7px 11px; border-radius: var(--r-s); white-space: nowrap;
  transition: color var(--t-1) var(--ease), background-color var(--t-1) var(--ease);
}
.linklike:hover { color: var(--danger); background: var(--danger-bg) }
.inline { display: inline-flex; align-items: center }

/* ---------- ★后台导航分组 ----------
   原来 12 个链接平铺、没有层级也没有当前页标记 —— 用户说"乱"，根子在信息架构不在配色。
   现在按 概览/授权/设备/内容/日志/账户 六组走；当前页由 foot.html 里那段脚本打 .on。 */
.navgrp {
  display: flex; align-items: center; gap: 2px; flex: 0 0 auto;
  padding-right: var(--sp-3); margin-right: var(--sp-1);
  border-right: 1px solid var(--chrome-line);
}
.navgrp:last-of-type { border-right: 0; padding-right: 0; margin-right: 0 }
.navgrp-l {
  font-size: 10.5px; letter-spacing: .14em; color: var(--muted);
  padding-right: 8px; white-space: nowrap; transition: color var(--t-3) var(--ease);
}
.navgrp.has-on .navgrp-l { color: var(--brand) }
/* ★这里刻意不改 font-weight：foot.html 那段脚本是在载入之后才给当前页补 .on 的，
   加粗会让那一格宽度突变、后面所有链接集体横移一格 —— 看着就像"页面抖了一下"。 */
.topbar nav a.on { color: var(--brand-2); background: var(--brand-soft) }
form.inline.logout { margin-left: var(--sp-1); flex: 0 0 auto }

/* ---------- 版面 ---------- */
.wrap { max-width: 1240px; margin: 0 auto; padding: 0 clamp(14px, 3vw, 28px); width: 100% }
main.wrap { flex: 1 0 auto; padding-top: clamp(20px, 3.2vw, 34px); padding-bottom: clamp(30px, 5vw, 56px) }
.narrow { max-width: 460px; margin: 0 auto }

/* ---------- ★动效层（第 136 次整段新增）----------
   只做 opacity / transform 两类：这两条走合成层，不掉帧；
   width/height/top/left/margin 一律不进动画 —— 那四类每帧都要重排，
   在 13 列的 /admin/users 上会直接卡成"看着就贵"的动效。 */
@keyframes rise { from { opacity: 0; transform: translateY(12px) } to { opacity: 1; transform: none } }
@keyframes veil { from { opacity: 0 } to { opacity: 1 } }
@keyframes pop { from { opacity: 0; transform: translateY(12px) scale(.97) } to { opacity: 1; transform: none } }
@keyframes toast-in { from { opacity: 0; transform: translate(-50%, 14px) } to { opacity: 1; transform: translate(-50%, 0) } }
/* 整块面板抬进来。★仪表板除外：那一页自己有更细的编排（标题带 → 入口 → 最近动态），
   两层动画叠在一起会变成"整页在抖"。 */
main.wrap > .panel:not(.dash) { animation: rise var(--t-4) var(--ease-out) both }

/* ---------- ★认证页（登录 / 注册 / 找回密码）----------
   上一版我只把这几页的按钮和间距换了，几何与留白几乎没动 —— 用户看完的原话是
   "好像变化并不大"。而未登录时用户能看到的**就只有这几页**（`/user`、后台根路径
   都会 303 到登录页），所以"改得多"的那部分（宽表密度）他根本没看见。
   这一档做的是能一眼看出的三件事：品牌底色带、卡片落位、把三个登录方式做成一个分段控件。 */
body:has(main .panel.narrow) {
  background:
    radial-gradient(1100px 460px at 50% -140px, var(--brand-soft), rgba(255, 255, 255, 0) 72%),
    var(--bg);
}
main.wrap:has(> .panel.narrow) {
  display: flex;
  padding-top: clamp(24px, 6vh, 64px); padding-bottom: clamp(24px, 6vh, 64px);
}
/* 用 margin:auto 而不是 align-items:center 居中：flex 容器配 align-items:center 时，
   比视口高的卡片会把顶部顶到滚动区之外且滚不回来；margin:auto 没这个毛病。
   ★入场动画并到这一条里写：另起一条同名选择器会被"顶层只能定义一次"的自检拦下，
     而它拦的正是"后写的静默盖掉前写的"这类事故。 */
main.wrap:has(> .panel.narrow) > .panel.narrow {
  margin: auto; animation: rise var(--t-4) var(--ease-out) 60ms both;
}
.panel.narrow {
  width: min(468px, 100%); padding: clamp(24px, 4.5vw, 36px) clamp(20px, 4.5vw, 32px);
  box-shadow: var(--sh-3); border-color: var(--line-2);
}
.panel.narrow h2 { font-size: 24px; margin-bottom: var(--sp-4) }
/* 三种凭据方式：模板里原本是一排独立 .btn + 行内 style，且**首屏没有选中态**
   （`setTab()` 只在点击后才跑）⇒ 配套把模板改成打 `.on` 类并在载入时调一次。 */
.segs {
  display: flex; gap: 0; margin: 0 0 var(--sp-4);
  border: 1px solid var(--line-2); border-radius: var(--r-s); overflow: hidden; background: #fff;
}
.segs .btn {
  flex: 1 1 auto; min-width: 0; padding: 9px 6px; font-size: 13.5px;
  border: 0; border-radius: 0; box-shadow: none; background: transparent; color: var(--ink-2);
  transition: background-color var(--t-2) var(--ease), color var(--t-2) var(--ease),
              box-shadow var(--t-2) var(--ease);
}
.segs .btn + .btn { border-left: 1px solid var(--line) }
.segs .btn:hover { background: var(--bg-2); color: var(--ink); box-shadow: none }
/* ★不写 font-weight：点页签时字重变化会让标签在自己的格子里横向撑开一点，
   那一下"跳"比少一档字重显眼得多。选中态交给底色 + 文字色 + 下沿品牌线。 */
.segs .btn.on {
  background: var(--brand-soft); color: var(--brand);
  box-shadow: inset 0 -2px 0 var(--brand);
}
.segs .btn:focus-visible { outline-offset: -2px }

.panel {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-l);
  padding: clamp(18px, 2.6vw, 28px); box-shadow: var(--sh-2);
}
.panel h2 { font-size: 21px; line-height: 1.35; letter-spacing: -.01em; margin-bottom: var(--sp-3); font-weight: 700 }
.panel h3 { font-size: 15.5px; color: var(--ink); margin: var(--sp-4) 0 var(--sp-2); font-weight: 650 }
.panel > h2:first-child { margin-top: 0 }
.card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-m);
  padding: var(--sp-4); box-shadow: var(--sh-1);
  transition: box-shadow var(--t-2) var(--ease), border-color var(--t-2) var(--ease),
              transform var(--t-2) var(--ease-spring);
}
.card:hover { box-shadow: var(--sh-3); border-color: var(--line-2); transform: translateY(-2px) }
.card h3 { font-size: 16px; margin-bottom: 6px }
.card p { color: var(--ink-2); font-size: 14px }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--sp-3); margin-top: var(--sp-4) }

/* 设备页那张 12 列表要突破 1240 容器拉满视口（原设计保留，只把负边距跟新 padding 对齐）
   ★后半条选择器是给 /admin/users 补的：它有 13 列却是普通 .panel（模板里没标 .wide，
     而源码在 /root/jbweb-build-scratch 是只读的，我不能改模板）。
     用 :has(th:nth-child(10)) 在 CSS 侧认出"列数 ≥10 的密表"，套同一套拉满视口的处理。 */
.panel.wide, .panel:has(> table.list th:nth-child(10)) {
  max-width: none; width: auto;
  margin-left: calc(50% - 50vw + clamp(14px, 3vw, 28px));
  margin-right: calc(50% - 50vw + clamp(14px, 3vw, 28px));
}

/* ---------- 按钮 ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 15px; border-radius: var(--r-s); border: 1px solid var(--line-2);
  background: #fff; color: var(--ink); font: inherit; font-size: 14px; font-weight: 550;
  line-height: 1.4; cursor: pointer; white-space: nowrap; user-select: none;
  box-shadow: var(--sh-1);
  transition: background-color var(--t-1) var(--ease), border-color var(--t-1) var(--ease),
              box-shadow var(--t-1) var(--ease), transform var(--t-1) var(--ease-spring),
              color var(--t-1) var(--ease);
}
/* 原来 hover 只有 opacity:.9，按下去毫无反馈；改成可感知的底色+阴影变化 + 1px 抬起 */
.btn:hover { background: var(--bg-2); border-color: #b9c5d8; box-shadow: var(--sh-2); transform: translateY(-1px) }
.btn:active { transform: translateY(1px); box-shadow: none; transition-duration: 60ms }
.btn:focus-visible { outline: none; box-shadow: var(--ring) }
.btn.primary {
  position: relative; overflow: hidden;
  background: var(--grad); border-color: transparent; color: #fff;
  box-shadow: 0 1px 2px rgba(37, 99, 235, .35), 0 4px 14px -6px rgba(79, 124, 255, .55);
}
.btn.primary:hover { background: linear-gradient(135deg, #3f6cf0, #6c4bf0); border-color: transparent }
/* 主按钮上那道斜光：只做 transform，从左扫到右；pointer-events:none 保证它不吃点击 */
.btn.primary::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(105deg, rgba(255, 255, 255, 0) 35%, rgba(255, 255, 255, .38) 50%, rgba(255, 255, 255, 0) 65%);
  transform: translateX(-130%); transition: transform var(--t-4) var(--ease-out);
}
.btn.primary:hover::after { transform: translateX(130%) }
.btn.danger, button.danger { color: var(--danger); border-color: var(--danger-line); background: #fff }
.btn.danger:hover, button.danger:hover { background: var(--danger-bg); border-color: #e6a49b }
.btn.small { padding: 4px 10px; font-size: 12.5px; border-radius: 6px; box-shadow: none }
.btn.small:hover { box-shadow: var(--sh-1) }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .45; cursor: not-allowed; box-shadow: none }
.btn.wide { width: 100% }

/* ---------- 表单 ---------- */
form label { display: block; margin-bottom: 6px; font-size: 13px; font-weight: 550; color: var(--ink-2); letter-spacing: .01em }
form input, form select, form textarea {
  display: block; width: 100%; padding: 9px 12px; font: inherit; font-size: 14.5px;
  color: var(--ink); background: #fff;
  border: 1px solid var(--line-2); border-radius: var(--r-s);
  transition: border-color .16s, box-shadow .16s, background-color .16s;
}
form input::placeholder, form textarea::placeholder { color: #9aa6b8 }
form input:hover, form select:hover { border-color: #b3c0d4 }
form input:focus, form select:focus, form textarea:focus {
  outline: none; border-color: var(--brand); box-shadow: var(--ring); background: #fff;
}
form input[type="checkbox"] { width: auto; margin: 0; accent-color: var(--brand); height: auto; padding: 0 }
.rowform { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; margin: var(--sp-3) 0 var(--sp-4) }
.rowform label { margin: 0; flex: 1 1 150px; min-width: 0 }
.genform { display: flex; gap: var(--sp-3); flex-wrap: wrap; align-items: flex-end; margin: var(--sp-3) 0 var(--sp-2) }
.genform label { margin: 0; flex: 0 1 auto }
.genform input, .genform select { width: 190px; margin-top: 6px }
.genform .btn { margin-bottom: 1px }
.searchform { display: flex; gap: 8px; align-items: center; margin: var(--sp-1) 0 var(--sp-2) }
.searchform input { width: min(320px, 100%); margin: 0; padding: 8px 11px; font-size: 14px }
.filebox { display: block; margin: var(--sp-3) 0 }

/* ---------- 提示条 ---------- */
.alert, .notice {
  border-radius: var(--r-s); padding: 10px 14px; margin-bottom: var(--sp-3);
  font-size: 13.5px; line-height: 1.6; border: 1px solid; display: flex; gap: 8px; align-items: flex-start;
  /* 报错条是"跳过来就能看见"的东西：让它比面板晚 120ms 落位，视线才跟得上 */
  animation: rise var(--t-3) var(--ease-out) 120ms both;
}
.alert { background: var(--danger-bg); color: var(--danger); border-color: var(--danger-line) }
.notice { background: var(--ok-bg); color: var(--ok); border-color: var(--ok-line) }
.muted { color: var(--muted); font-size: 13px }
/* 只给段落级的 muted 加间距。上一版写的是 .panel .muted，而模板里表格里到处是
   <span class="muted">(管理员)</span> 这种行内用法 ⇒ 给行内 span 加了 margin-top，
   直接造成同一张表里行高忽高忽低（截图第 1 行 admin 明显比别的行矮）。 */
p.muted { margin-top: 10px; line-height: 1.75 }

/* ---------- 表格（本轮主战场） ---------- */
table.list, table.kv { width: 100%; border-collapse: collapse; margin: var(--sp-3) 0 var(--sp-4); font-size: 14px }
table.list th, table.list td, table.kv th, table.kv td {
  /* 内边距维持原版的 9px：上一版我改成 10px，单行看不出差别，
     但 /admin/loginlogs 那种 318 行的页面上就放大成"整页变高 1.3KB px"（实测 30208 → 31521）。
     密度靠斑马纹与表头吸顶去解决，不靠加内边距。
     ★横向从 10px 收到 7px：13 列的表每列省 6px = 78px，是补"表宽 > 视口"最便宜的一笔，
       且不改变行高（纵向仍是 9px）。 */
  border-bottom: 1px solid var(--line); padding: 9px 7px; text-align: left; vertical-align: middle;
}
/* ★表头断行是"看着乱"的头号来源：12 列时 th 被挤成两行（"到期时/间"） */
table.list th {
  font-size: 12.5px; font-weight: 650; color: var(--ink-2); letter-spacing: .02em;
  white-space: nowrap; background: var(--bg-2); border-bottom: 1px solid var(--line-2);
  position: sticky; top: 58px; z-index: 2;
}
table.list tbody tr:nth-child(even) { background: #fafbfe }
/* ★行的底色变化要有过渡：没有这一条时鼠标扫过 320 行的日志表，底色是一格一格"闪"的；
   只 90ms 就够 —— 再长会拖出"跟手不跟手"的错觉。 */
table.list tbody tr { transition: background-color 90ms var(--ease) }
table.list tbody tr:hover { background: var(--brand-soft) }
table.list tbody tr:last-child td { border-bottom: 0 }
/* 日期/时间在窄列里折行很难读，统一不折 */
/* 长值（邮箱、注册码、URL）允许按字符断，但不许把中文词拆成竖排。
   ★这两条必须与 color 写在同一条 `table.list td` 里：顶层出现两次同名选择器时，
     后写的会静默盖掉前写的，对拍会把它读成"修复无效"。 */
table.list td { color: var(--ink); word-break: normal; overflow-wrap: anywhere }
table.kv { font-size: 14.5px }
table.kv th { width: clamp(96px, 22%, 160px); color: var(--muted); font-weight: 500; white-space: nowrap }
table.kv td { color: var(--ink) }
.tablescroll { overflow-x: auto; -webkit-overflow-scrolling: touch; border-radius: var(--r-m) }
/* ★这里原来写着 `.tablescroll table.list { min-width: 100% }` —— 它比下面全局那条
   `table.list { min-width: max-content }` 特异度更高(0,2,1 > 0,1,1)，于是**只有带 .tablescroll
   的三张表**(admin_devices / admin_cmd / user_devices)拿不到 max-content 下限，被压回容器宽，
   中文列逐字竖排：实测 /user/devices@390 行高 167px、/admin/devices@1440 行高 65px。
   删掉之后由全局那条统一给下限；表格窄于容器时仍由上面的 width:100% 撑满。 */
/* ★13 列的用户表外面没有 .tablescroll 包（模板里就是裸 table），
   所以它只能被压进面板宽度：我上一版给所有 th 加 nowrap 之后，
   长文本列被饿成一字一行（截图里"已封禁(管理员手动)"竖排就是这个）。
   让带表的面板自己横向滚动、表格按内容自然撑开，列宽就不再被抢。
   :has() 不支持的浏览器退化成"和现在一样挤"，不会更糟。 */
.panel:has(> table.list), .panel:has(> .tablescroll) { overflow-x: auto }
/* 密表已经拉满视口了，面板自己那 28px 左右内边距就成了最后一格被切掉的原因。
   给"≥10 列"的面板让出这 20px（18px 上限），比再压字号划算。 */
.panel:has(> table.list th:nth-child(10)) { padding-left: clamp(12px, 1.8vw, 18px); padding-right: clamp(12px, 1.8vw, 18px) }
/* 字号 14 → 13.5：/admin/users 的 max-content 宽 1506px 比拉满视口后的可用宽还多，
   光靠收内边距补不平。这一档是"密表"与"正文"的分界，13.5px 在 1440 上仍可读。 */
table.list { min-width: max-content; font-size: 13.5px }
.mono { font-family: var(--mono); font-size: .95em; letter-spacing: .02em }
/* 数字列对齐：卡密/大小/次数这类值不等宽时竖着看会跳 */
td.mono, .codebox { font-variant-numeric: tabular-nums }

/* ---------- ★操作列 ----------
   实测线上是 <td><div class="opsrow"><a class="btn small">管理</a><a class="btn small">日志</a></div></td>，
   而 opsrow 原本 flex-wrap:wrap + 该列只有 113px 宽 ⇒ 两个按钮折成两行，把每行撑到 86px（封禁行四个按钮更高）。
   这里改成不折行、让列按内容撑开；表格本身在 .tablescroll 里，横向放不下时是整表滚动而不是把行撑高。
   （.userops 是另一版源码里的类名，线上没有，两条都留着以便将来切到那版时仍然成立。） */
.opsrow { display: flex; gap: 6px; align-items: center; flex-wrap: nowrap; white-space: nowrap }
/* ★光 flex-wrap:nowrap 不够：子项默认 flex-shrink:1，四个按钮会被挤窄而不是撑开列。
   必须让它们不伸缩，列宽自然被撑到内容宽，放不下时交给 .tablescroll 整表横滚。
   （操作列里每个按钮外面还套着一个 <form class="inline">，所以要对直接子项生效） */
.opsrow > *, .userops > * { flex: 0 0 auto }
.userops {
  display: flex; flex-direction: row; flex-wrap: nowrap; gap: 6px; align-items: center;
  min-width: 0; white-space: nowrap;
}
table.list td:last-child { white-space: nowrap }
table.list th:last-child { white-space: nowrap }
.userops .inline { display: inline-flex; gap: 4px; align-items: center }
.userops input { font-size: 12px; padding: 3px 6px; width: auto }
/* 操作列里 4 枚按钮横排就是这一列 226px 的全部来源。左右内边距 10→7 让每枚窄 6px，
   四枚省 24px；字号仍 12.5px（与 .btn.small 同档，不再单独压到 12px，两字按钮压字号会显小）。 */
.opsrow .btn.small, .userops .btn.small { padding: 4px 7px; font-size: 12.5px }
/* 这里原来还有一条 .opsrow{flex-wrap:wrap} —— 同特异度后置规则会静默盖掉上面第 200 行的 nowrap，
   导致"改了没生效"。删掉；本文件里 .opsrow 只允许出现一次定义。 */

/* ---------- 统计卡 / 标签 / 分页 ---------- */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--sp-3); margin: var(--sp-4) 0 }
.stat {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-m);
  padding: 16px 18px; box-shadow: var(--sh-1);
  transition: box-shadow var(--t-2) var(--ease), transform var(--t-2) var(--ease-spring);
}
.stat:hover { box-shadow: var(--sh-2); transform: translateY(-2px) }
.stat .num { font-size: 28px; font-weight: 750; color: var(--brand-2); line-height: 1.15; font-variant-numeric: tabular-nums }
.stat .lbl { color: var(--muted); font-size: 12.5px; margin-top: 2px }

/* ---------- ★仪表板：标题带 + KPI + 入口网格 ----------
   原来只有三张统计卡，剩下整屏空白（用户："又丑又难看…整体还是那样子"）。
   现在把"这个后台能干什么"直接摆在首页上，十个入口与顶栏分组一一对得上。 */
.dash { padding: 0 }
.dash-top {
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between;
  gap: var(--sp-4); padding: clamp(18px, 2.6vw, 28px) clamp(18px, 2.6vw, 28px) var(--sp-4);
  border-bottom: 1px solid var(--line); background: linear-gradient(180deg, var(--bg-2), rgba(255,255,255,0));
  border-radius: var(--r-l) var(--r-l) 0 0;
  animation: rise var(--t-3) var(--ease-out) both;
}
.dash-id h2 { margin-bottom: 0 }
.dash-id .muted { margin-top: 2px }
.kpis { display: flex; flex-wrap: wrap; gap: clamp(18px, 3vw, 34px) }
.kpi { display: flex; flex-direction: column; min-width: 88px }
.kpi-n { font-size: 30px; font-weight: 750; line-height: 1.1; color: var(--brand-2); font-variant-numeric: tabular-nums }
.kpi-l { font-size: 12.5px; color: var(--muted); margin-top: 2px }
.dash-sec {
  margin: 0; padding: var(--sp-4) clamp(18px, 2.6vw, 28px) 0;
  font-size: 11.5px; letter-spacing: .14em; color: var(--muted);
  animation: veil var(--t-3) var(--ease-out) both;
}
.tiles {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(214px, 1fr));
  gap: var(--sp-3); padding: var(--sp-3) clamp(18px, 2.6vw, 28px) clamp(18px, 2.6vw, 28px);
}
.tile {
  display: flex; flex-direction: column; gap: 4px; padding: 14px 16px;
  border: 1px solid var(--line); border-radius: var(--r-m); background: #fff;
  animation: rise var(--t-3) var(--ease-out) both;
  transition: border-color var(--t-2) var(--ease), box-shadow var(--t-2) var(--ease),
              transform var(--t-2) var(--ease-spring), background-color var(--t-2) var(--ease);
}
.tile b { font-size: 14.5px; color: var(--ink) }
.tile span { font-size: 12.5px; color: var(--muted); line-height: 1.55 }
.tile:hover { border-color: var(--brand); background: #fff; box-shadow: var(--sh-3); transform: translateY(-3px) }
.tile:focus-visible { outline: none; box-shadow: var(--ring) }
/* ★逐个错开入场（每格 34ms，到第 10 格约 306ms）：整屏一起淡入会读成"网页卡了一下才出来"，
   错开才读成"有东西在动"。延迟写死枚数而不是用 --i，是因为模板我不能加行内样式。 */
.tiles .tile:nth-child(2) { animation-delay: 34ms }
.tiles .tile:nth-child(3) { animation-delay: 68ms }
.tiles .tile:nth-child(4) { animation-delay: 102ms }
.tiles .tile:nth-child(5) { animation-delay: 136ms }
.tiles .tile:nth-child(6) { animation-delay: 170ms }
.tiles .tile:nth-child(7) { animation-delay: 204ms }
.tiles .tile:nth-child(8) { animation-delay: 238ms }
.tiles .tile:nth-child(9) { animation-delay: 272ms }
.tiles .tile:nth-child(10) { animation-delay: 306ms }
.dash-cols {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: var(--sp-4); padding: 0 clamp(18px, 2.6vw, 28px) clamp(18px, 2.6vw, 28px);
}
/* 两块"最近动态"排在入口之后入场（340ms ≈ 最后一格入口的延迟 + 一档时长） */
.dcol { min-width: 0; animation: rise var(--t-3) var(--ease-out) 340ms both }
.dcol h3 { margin: 0 0 8px; font-size: 14px; color: var(--ink-2) }
.dcol p.muted { margin-top: 0 }
.dcol-more { display: inline-block; margin-top: 8px; font-size: 12.5px; color: var(--brand) }
.dcol-more:hover { text-decoration: underline }
/* ★这里必须写成 `table.list.mini` 而不是 `table.mini`：
   上面有一条 `table.list { min-width: max-content }`（特异度 0,1,1），
   同特异度就得靠先后顺序决胜，而这种"靠顺序"正是本轮已经踩过两次的静默覆盖来源。
   抬到 0,2,1 之后，小表不再被整表 max-content 下限撑破半栏宽度。 */
table.list.mini { min-width: 0; font-size: 13px }
table.list.mini th, table.list.mini td { padding: 7px 8px }
.dcol .ok { color: var(--ok) }
.dcol .bad { color: var(--danger) }
.tabs { display: flex; gap: 8px; margin: var(--sp-3) 0 var(--sp-2); flex-wrap: wrap }
.tabs a {
  padding: 4px 13px; border-radius: 999px; border: 1px solid var(--line);
  color: var(--ink-2); font-size: 13px; background: #fff; box-shadow: var(--sh-1);
  transition: background-color .16s, color .16s, border-color .16s;
}
.tabs a:hover { border-color: var(--brand); color: var(--brand-2) }
.tabs a.on { background: var(--brand); border-color: var(--brand); color: #fff; box-shadow: 0 2px 8px -2px rgba(37, 99, 235, .5) }
.tag { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 12px; background: var(--bg-2); color: var(--ink-2); border: 1px solid var(--line); white-space: nowrap }
.bulkbar { display: flex; gap: var(--sp-3); align-items: center; flex-wrap: wrap; margin: var(--sp-3) 0 var(--sp-2) }
.bulkbar .chk { display: flex; gap: 6px; align-items: center; font-size: 13.5px; color: var(--ink); margin: 0; font-weight: 400 }
table.list input[type="checkbox"] { width: auto; margin: 0; accent-color: var(--brand) }

/* ---------- 弹窗与浮层 ---------- */
.modal {
  position: fixed; inset: 0; display: flex; align-items: center; justify-content: center;
  z-index: 50; padding: 18px;
  /* ★浅色模式下的遮罩不能还是 50% 深蓝灰 —— 那样整屏"压暗"，与这一版要的方向相反。
     改成浅色磨砂veil：白卡片浮在一层起雾的浅蓝上，靠模糊与投影分前后，不靠压暗。 */
  background: rgba(233, 239, 248, .72);
  backdrop-filter: blur(7px) saturate(1.15); -webkit-backdrop-filter: blur(7px) saturate(1.15);
  animation: veil var(--t-2) var(--ease-out) both;
}
.modal-box {
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-l);
  padding: 26px 28px; width: min(420px, 100%); text-align: center; box-shadow: var(--sh-3);
  animation: pop var(--t-3) var(--ease-spring) 40ms both;
}
.modal-box h3 { margin: 0 0 14px; font-size: 17px }
.codebox { font-family: var(--mono); font-size: 19px; letter-spacing: .06em; background: var(--bg); border: 1px dashed var(--line-2); border-radius: var(--r-s); padding: 12px; margin-bottom: var(--sp-3); user-select: all; word-break: break-all }
.modal-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap }
.copyable { cursor: pointer }
.copyable:hover { color: var(--brand-2); text-decoration: underline }
#toast {
  display: none; position: fixed; left: 50%; bottom: 32px; transform: translateX(-50%);
  /* 浅色档：白底药丸 + 一圈发丝边 + 托得比较远的柔影，靠影子而不是靠深色块浮起来 */
  background: rgba(255, 255, 255, .96); color: var(--ink);
  border: 1px solid var(--line-2); padding: 9px 18px; border-radius: 999px;
  font-size: 13px; z-index: 60; box-shadow: var(--sh-3); max-width: calc(100vw - 32px);
  /* ★关键帧末态写成 translate(-50%,0)：与上面那条 transform 等价，
     否则动画结束后 transform 会被关键帧的 translate(-50%,14px) 留在偏移位上。 */
  animation: toast-in var(--t-3) var(--ease-out) both;
}

/* ---------- 页脚 ---------- */
.foot { border-top: 1px solid var(--line); padding: 16px 0; color: var(--muted); font-size: 13px; background: #fff; flex-shrink: 0 }
.foot .wrap { display: flex; gap: var(--sp-3); align-items: center; justify-content: center; flex-wrap: wrap }

/* ---------- 窄屏 ---------- */
@media (max-width: 860px) {
  /* 顶栏在窄屏只留品牌+可滚导航，避免 11 项把顶栏撑高 */
  .topbar .wrap { flex-wrap: nowrap; gap: 10px }
  .topbar nav { justify-content: flex-start }
  /* 窄屏顶栏要横向滚，组名白占宽度 ⇒ 让位给链接本身 */
  .navgrp-l { display: none }
  .navgrp { padding-right: 6px; margin-right: 0; border-right: 0 }
  .dash-top { align-items: flex-start }
  .tiles { grid-template-columns: repeat(auto-fill, minmax(152px, 1fr)) }
  table.kv th { width: 100px }
  .genform input, .genform select { width: 100% }
  .genform label { flex: 1 1 100% }
  .panel.wide { margin-left: calc(50% - 50vw + 14px); margin-right: calc(50% - 50vw + 14px) }
}
@media (max-width: 620px) {
  body { font-size: 14px }
  .searchform { flex-direction: column; align-items: stretch }
  .searchform input { width: 100% }
  .stats { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)) }
  .stat .num { font-size: 22px }
  .rowform { flex-direction: column; align-items: stretch }
  .rowform label { flex: 1 1 auto }
  .modal-box { padding: 20px }
  /* 表格在手机上保留横向滚动（.tablescroll 已给），但字号收一档免得两列就把屏占满 */
  table.list, table.kv { font-size: 13px }
  table.list th, table.list td, table.kv th, table.kv td { padding: 8px 7px }
  table.list th { position: static }
}

/* ---------- 可访问性与打印 ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; scroll-behavior: auto !important }
  .btn:active { transform: none }
}
@media (forced-colors: active) {
  .btn, .panel, .card, .stat, .tag, .tabs a, .modal-box { border: 1px solid CanvasText; box-shadow: none }
  .brand b { color: CanvasText; background: none; -webkit-background-clip: border-box; background-clip: border-box }
  .topbar, #toast { background: Canvas }
  /* 高对比档下顶栏的磨砂没有意义，还会把系统给的 Canvas 盖成半透明白 ⇒ 明确关掉 */
  .topbar { backdrop-filter: none; -webkit-backdrop-filter: none; animation: none !important }
  .modal { backdrop-filter: none; -webkit-backdrop-filter: none; background: Canvas }
  .topbar nav a, .linklike { color: CanvasText }
  .topbar nav a::after { background: CanvasText }
  table.list th { background: Canvas; position: static }
  table.list tbody tr:nth-child(even) { background: transparent }
  table.list tbody tr:hover { background: transparent }
}
@media print {
  .topbar, .foot, #toast, .modal, .btn, .tabs, .bulkbar, .userops, .opsrow, form { display: none !important }
  body { background: #fff; color: #000 }
  .panel, .card, .stat { border: 1px solid #bbb; box-shadow: none; backdrop-filter: none }
  table.list th { background: #eee; position: static }
  main.wrap { padding: 0 }
}
